162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * MIPI-DSI Novatek NT35560-based panel controller. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Supported panels include: 662306a36Sopenharmony_ci * Sony ACX424AKM - a 480x854 AMOLED DSI panel 762306a36Sopenharmony_ci * Sony ACX424AKP - a 480x864 AMOLED DSI panel 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyright (C) Linaro Ltd. 2019-2021 1062306a36Sopenharmony_ci * Author: Linus Walleij 1162306a36Sopenharmony_ci * Based on code and know-how from Marcus Lorentzon 1262306a36Sopenharmony_ci * Copyright (C) ST-Ericsson SA 2010 1362306a36Sopenharmony_ci * Based on code and know-how from Johan Olson and Joakim Wesslen 1462306a36Sopenharmony_ci * Copyright (C) Sony Ericsson Mobile Communications 2010 1562306a36Sopenharmony_ci */ 1662306a36Sopenharmony_ci#include <linux/backlight.h> 1762306a36Sopenharmony_ci#include <linux/delay.h> 1862306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1962306a36Sopenharmony_ci#include <linux/module.h> 2062306a36Sopenharmony_ci#include <linux/of.h> 2162306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <video/mipi_display.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h> 2662306a36Sopenharmony_ci#include <drm/drm_modes.h> 2762306a36Sopenharmony_ci#include <drm/drm_panel.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define NT35560_DCS_READ_ID1 0xDA 3062306a36Sopenharmony_ci#define NT35560_DCS_READ_ID2 0xDB 3162306a36Sopenharmony_ci#define NT35560_DCS_READ_ID3 0xDC 3262306a36Sopenharmony_ci#define NT35560_DCS_SET_MDDI 0xAE 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* 3562306a36Sopenharmony_ci * Sony seems to use vendor ID 0x81 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_ci#define DISPLAY_SONY_ACX424AKP_ID1 0x8103 3862306a36Sopenharmony_ci#define DISPLAY_SONY_ACX424AKP_ID2 0x811a 3962306a36Sopenharmony_ci#define DISPLAY_SONY_ACX424AKP_ID3 0x811b 4062306a36Sopenharmony_ci/* 4162306a36Sopenharmony_ci * The fourth ID looks like a bug, vendor IDs begin at 0x80 4262306a36Sopenharmony_ci * and panel 00 ... seems like default values. 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_ci#define DISPLAY_SONY_ACX424AKP_ID4 0x8000 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistruct nt35560_config { 4762306a36Sopenharmony_ci const struct drm_display_mode *vid_mode; 4862306a36Sopenharmony_ci const struct drm_display_mode *cmd_mode; 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistruct nt35560 { 5262306a36Sopenharmony_ci const struct nt35560_config *conf; 5362306a36Sopenharmony_ci struct drm_panel panel; 5462306a36Sopenharmony_ci struct device *dev; 5562306a36Sopenharmony_ci struct regulator *supply; 5662306a36Sopenharmony_ci struct gpio_desc *reset_gpio; 5762306a36Sopenharmony_ci bool video_mode; 5862306a36Sopenharmony_ci}; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic const struct drm_display_mode sony_acx424akp_vid_mode = { 6162306a36Sopenharmony_ci .clock = 27234, 6262306a36Sopenharmony_ci .hdisplay = 480, 6362306a36Sopenharmony_ci .hsync_start = 480 + 15, 6462306a36Sopenharmony_ci .hsync_end = 480 + 15 + 0, 6562306a36Sopenharmony_ci .htotal = 480 + 15 + 0 + 15, 6662306a36Sopenharmony_ci .vdisplay = 864, 6762306a36Sopenharmony_ci .vsync_start = 864 + 14, 6862306a36Sopenharmony_ci .vsync_end = 864 + 14 + 1, 6962306a36Sopenharmony_ci .vtotal = 864 + 14 + 1 + 11, 7062306a36Sopenharmony_ci .width_mm = 48, 7162306a36Sopenharmony_ci .height_mm = 84, 7262306a36Sopenharmony_ci .flags = DRM_MODE_FLAG_PVSYNC, 7362306a36Sopenharmony_ci}; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* 7662306a36Sopenharmony_ci * The timings are not very helpful as the display is used in 7762306a36Sopenharmony_ci * command mode using the maximum HS frequency. 7862306a36Sopenharmony_ci */ 7962306a36Sopenharmony_cistatic const struct drm_display_mode sony_acx424akp_cmd_mode = { 8062306a36Sopenharmony_ci .clock = 35478, 8162306a36Sopenharmony_ci .hdisplay = 480, 8262306a36Sopenharmony_ci .hsync_start = 480 + 154, 8362306a36Sopenharmony_ci .hsync_end = 480 + 154 + 16, 8462306a36Sopenharmony_ci .htotal = 480 + 154 + 16 + 32, 8562306a36Sopenharmony_ci .vdisplay = 864, 8662306a36Sopenharmony_ci .vsync_start = 864 + 1, 8762306a36Sopenharmony_ci .vsync_end = 864 + 1 + 1, 8862306a36Sopenharmony_ci .vtotal = 864 + 1 + 1 + 1, 8962306a36Sopenharmony_ci /* 9062306a36Sopenharmony_ci * Some desired refresh rate, experiments at the maximum "pixel" 9162306a36Sopenharmony_ci * clock speed (HS clock 420 MHz) yields around 117Hz. 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_ci .width_mm = 48, 9462306a36Sopenharmony_ci .height_mm = 84, 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic const struct nt35560_config sony_acx424akp_data = { 9862306a36Sopenharmony_ci .vid_mode = &sony_acx424akp_vid_mode, 9962306a36Sopenharmony_ci .cmd_mode = &sony_acx424akp_cmd_mode, 10062306a36Sopenharmony_ci}; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic const struct drm_display_mode sony_acx424akm_vid_mode = { 10362306a36Sopenharmony_ci .clock = 27234, 10462306a36Sopenharmony_ci .hdisplay = 480, 10562306a36Sopenharmony_ci .hsync_start = 480 + 15, 10662306a36Sopenharmony_ci .hsync_end = 480 + 15 + 0, 10762306a36Sopenharmony_ci .htotal = 480 + 15 + 0 + 15, 10862306a36Sopenharmony_ci .vdisplay = 854, 10962306a36Sopenharmony_ci .vsync_start = 854 + 14, 11062306a36Sopenharmony_ci .vsync_end = 854 + 14 + 1, 11162306a36Sopenharmony_ci .vtotal = 854 + 14 + 1 + 11, 11262306a36Sopenharmony_ci .width_mm = 46, 11362306a36Sopenharmony_ci .height_mm = 82, 11462306a36Sopenharmony_ci .flags = DRM_MODE_FLAG_PVSYNC, 11562306a36Sopenharmony_ci}; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci/* 11862306a36Sopenharmony_ci * The timings are not very helpful as the display is used in 11962306a36Sopenharmony_ci * command mode using the maximum HS frequency. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_cistatic const struct drm_display_mode sony_acx424akm_cmd_mode = { 12262306a36Sopenharmony_ci .clock = 35478, 12362306a36Sopenharmony_ci .hdisplay = 480, 12462306a36Sopenharmony_ci .hsync_start = 480 + 154, 12562306a36Sopenharmony_ci .hsync_end = 480 + 154 + 16, 12662306a36Sopenharmony_ci .htotal = 480 + 154 + 16 + 32, 12762306a36Sopenharmony_ci .vdisplay = 854, 12862306a36Sopenharmony_ci .vsync_start = 854 + 1, 12962306a36Sopenharmony_ci .vsync_end = 854 + 1 + 1, 13062306a36Sopenharmony_ci .vtotal = 854 + 1 + 1 + 1, 13162306a36Sopenharmony_ci .width_mm = 46, 13262306a36Sopenharmony_ci .height_mm = 82, 13362306a36Sopenharmony_ci}; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic const struct nt35560_config sony_acx424akm_data = { 13662306a36Sopenharmony_ci .vid_mode = &sony_acx424akm_vid_mode, 13762306a36Sopenharmony_ci .cmd_mode = &sony_acx424akm_cmd_mode, 13862306a36Sopenharmony_ci}; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic inline struct nt35560 *panel_to_nt35560(struct drm_panel *panel) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci return container_of(panel, struct nt35560, panel); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci#define FOSC 20 /* 20Mhz */ 14662306a36Sopenharmony_ci#define SCALE_FACTOR_NS_DIV_MHZ 1000 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic int nt35560_set_brightness(struct backlight_device *bl) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci struct nt35560 *nt = bl_get_data(bl); 15162306a36Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); 15262306a36Sopenharmony_ci int period_ns = 1023; 15362306a36Sopenharmony_ci int duty_ns = bl->props.brightness; 15462306a36Sopenharmony_ci u8 pwm_ratio; 15562306a36Sopenharmony_ci u8 pwm_div; 15662306a36Sopenharmony_ci u8 par; 15762306a36Sopenharmony_ci int ret; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (backlight_is_blank(bl)) { 16062306a36Sopenharmony_ci /* Disable backlight */ 16162306a36Sopenharmony_ci par = 0x00; 16262306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 16362306a36Sopenharmony_ci &par, 1); 16462306a36Sopenharmony_ci if (ret) { 16562306a36Sopenharmony_ci dev_err(nt->dev, "failed to disable display backlight (%d)\n", ret); 16662306a36Sopenharmony_ci return ret; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci return 0; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci /* Calculate the PWM duty cycle in n/256's */ 17262306a36Sopenharmony_ci pwm_ratio = max(((duty_ns * 256) / period_ns) - 1, 1); 17362306a36Sopenharmony_ci pwm_div = max(1, 17462306a36Sopenharmony_ci ((FOSC * period_ns) / 256) / 17562306a36Sopenharmony_ci SCALE_FACTOR_NS_DIV_MHZ); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* Set up PWM dutycycle ONE byte (differs from the standard) */ 17862306a36Sopenharmony_ci dev_dbg(nt->dev, "calculated duty cycle %02x\n", pwm_ratio); 17962306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 18062306a36Sopenharmony_ci &pwm_ratio, 1); 18162306a36Sopenharmony_ci if (ret < 0) { 18262306a36Sopenharmony_ci dev_err(nt->dev, "failed to set display PWM ratio (%d)\n", ret); 18362306a36Sopenharmony_ci return ret; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* 18762306a36Sopenharmony_ci * Sequence to write PWMDIV: 18862306a36Sopenharmony_ci * address data 18962306a36Sopenharmony_ci * 0xF3 0xAA CMD2 Unlock 19062306a36Sopenharmony_ci * 0x00 0x01 Enter CMD2 page 0 19162306a36Sopenharmony_ci * 0X7D 0x01 No reload MTP of CMD2 P1 19262306a36Sopenharmony_ci * 0x22 PWMDIV 19362306a36Sopenharmony_ci * 0x7F 0xAA CMD2 page 1 lock 19462306a36Sopenharmony_ci */ 19562306a36Sopenharmony_ci par = 0xaa; 19662306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, 0xf3, &par, 1); 19762306a36Sopenharmony_ci if (ret < 0) { 19862306a36Sopenharmony_ci dev_err(nt->dev, "failed to unlock CMD 2 (%d)\n", ret); 19962306a36Sopenharmony_ci return ret; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci par = 0x01; 20262306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, 0x00, &par, 1); 20362306a36Sopenharmony_ci if (ret < 0) { 20462306a36Sopenharmony_ci dev_err(nt->dev, "failed to enter page 1 (%d)\n", ret); 20562306a36Sopenharmony_ci return ret; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci par = 0x01; 20862306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, 0x7d, &par, 1); 20962306a36Sopenharmony_ci if (ret < 0) { 21062306a36Sopenharmony_ci dev_err(nt->dev, "failed to disable MTP reload (%d)\n", ret); 21162306a36Sopenharmony_ci return ret; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, 0x22, &pwm_div, 1); 21462306a36Sopenharmony_ci if (ret < 0) { 21562306a36Sopenharmony_ci dev_err(nt->dev, "failed to set PWM divisor (%d)\n", ret); 21662306a36Sopenharmony_ci return ret; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci par = 0xaa; 21962306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, 0x7f, &par, 1); 22062306a36Sopenharmony_ci if (ret < 0) { 22162306a36Sopenharmony_ci dev_err(nt->dev, "failed to lock CMD 2 (%d)\n", ret); 22262306a36Sopenharmony_ci return ret; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* Enable backlight */ 22662306a36Sopenharmony_ci par = 0x24; 22762306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 22862306a36Sopenharmony_ci &par, 1); 22962306a36Sopenharmony_ci if (ret < 0) { 23062306a36Sopenharmony_ci dev_err(nt->dev, "failed to enable display backlight (%d)\n", ret); 23162306a36Sopenharmony_ci return ret; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci return 0; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic const struct backlight_ops nt35560_bl_ops = { 23862306a36Sopenharmony_ci .update_status = nt35560_set_brightness, 23962306a36Sopenharmony_ci}; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic const struct backlight_properties nt35560_bl_props = { 24262306a36Sopenharmony_ci .type = BACKLIGHT_RAW, 24362306a36Sopenharmony_ci .brightness = 512, 24462306a36Sopenharmony_ci .max_brightness = 1023, 24562306a36Sopenharmony_ci}; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_cistatic int nt35560_read_id(struct nt35560 *nt) 24862306a36Sopenharmony_ci{ 24962306a36Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); 25062306a36Sopenharmony_ci u8 vendor, version, panel; 25162306a36Sopenharmony_ci u16 val; 25262306a36Sopenharmony_ci int ret; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID1, &vendor, 1); 25562306a36Sopenharmony_ci if (ret < 0) { 25662306a36Sopenharmony_ci dev_err(nt->dev, "could not vendor ID byte\n"); 25762306a36Sopenharmony_ci return ret; 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID2, &version, 1); 26062306a36Sopenharmony_ci if (ret < 0) { 26162306a36Sopenharmony_ci dev_err(nt->dev, "could not read device version byte\n"); 26262306a36Sopenharmony_ci return ret; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci ret = mipi_dsi_dcs_read(dsi, NT35560_DCS_READ_ID3, &panel, 1); 26562306a36Sopenharmony_ci if (ret < 0) { 26662306a36Sopenharmony_ci dev_err(nt->dev, "could not read panel ID byte\n"); 26762306a36Sopenharmony_ci return ret; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if (vendor == 0x00) { 27162306a36Sopenharmony_ci dev_err(nt->dev, "device vendor ID is zero\n"); 27262306a36Sopenharmony_ci return -ENODEV; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci val = (vendor << 8) | panel; 27662306a36Sopenharmony_ci switch (val) { 27762306a36Sopenharmony_ci case DISPLAY_SONY_ACX424AKP_ID1: 27862306a36Sopenharmony_ci case DISPLAY_SONY_ACX424AKP_ID2: 27962306a36Sopenharmony_ci case DISPLAY_SONY_ACX424AKP_ID3: 28062306a36Sopenharmony_ci case DISPLAY_SONY_ACX424AKP_ID4: 28162306a36Sopenharmony_ci dev_info(nt->dev, "MTP vendor: %02x, version: %02x, panel: %02x\n", 28262306a36Sopenharmony_ci vendor, version, panel); 28362306a36Sopenharmony_ci break; 28462306a36Sopenharmony_ci default: 28562306a36Sopenharmony_ci dev_info(nt->dev, "unknown vendor: %02x, version: %02x, panel: %02x\n", 28662306a36Sopenharmony_ci vendor, version, panel); 28762306a36Sopenharmony_ci break; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci return 0; 29162306a36Sopenharmony_ci} 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cistatic int nt35560_power_on(struct nt35560 *nt) 29462306a36Sopenharmony_ci{ 29562306a36Sopenharmony_ci int ret; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci ret = regulator_enable(nt->supply); 29862306a36Sopenharmony_ci if (ret) { 29962306a36Sopenharmony_ci dev_err(nt->dev, "failed to enable supply (%d)\n", ret); 30062306a36Sopenharmony_ci return ret; 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci /* Assert RESET */ 30462306a36Sopenharmony_ci gpiod_set_value_cansleep(nt->reset_gpio, 1); 30562306a36Sopenharmony_ci udelay(20); 30662306a36Sopenharmony_ci /* De-assert RESET */ 30762306a36Sopenharmony_ci gpiod_set_value_cansleep(nt->reset_gpio, 0); 30862306a36Sopenharmony_ci usleep_range(11000, 20000); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci return 0; 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_cistatic void nt35560_power_off(struct nt35560 *nt) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci /* Assert RESET */ 31662306a36Sopenharmony_ci gpiod_set_value_cansleep(nt->reset_gpio, 1); 31762306a36Sopenharmony_ci usleep_range(11000, 20000); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci regulator_disable(nt->supply); 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cistatic int nt35560_prepare(struct drm_panel *panel) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci struct nt35560 *nt = panel_to_nt35560(panel); 32562306a36Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); 32662306a36Sopenharmony_ci const u8 mddi = 3; 32762306a36Sopenharmony_ci int ret; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci ret = nt35560_power_on(nt); 33062306a36Sopenharmony_ci if (ret) 33162306a36Sopenharmony_ci return ret; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci ret = nt35560_read_id(nt); 33462306a36Sopenharmony_ci if (ret) { 33562306a36Sopenharmony_ci dev_err(nt->dev, "failed to read panel ID (%d)\n", ret); 33662306a36Sopenharmony_ci goto err_power_off; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci /* Enabe tearing mode: send TE (tearing effect) at VBLANK */ 34062306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_tear_on(dsi, 34162306a36Sopenharmony_ci MIPI_DSI_DCS_TEAR_MODE_VBLANK); 34262306a36Sopenharmony_ci if (ret) { 34362306a36Sopenharmony_ci dev_err(nt->dev, "failed to enable vblank TE (%d)\n", ret); 34462306a36Sopenharmony_ci goto err_power_off; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* 34862306a36Sopenharmony_ci * Set MDDI 34962306a36Sopenharmony_ci * 35062306a36Sopenharmony_ci * This presumably deactivates the Qualcomm MDDI interface and 35162306a36Sopenharmony_ci * selects DSI, similar code is found in other drivers such as the 35262306a36Sopenharmony_ci * Sharp LS043T1LE01 which makes us suspect that this panel may be 35362306a36Sopenharmony_ci * using a Novatek NT35565 or similar display driver chip that shares 35462306a36Sopenharmony_ci * this command. Due to the lack of documentation we cannot know for 35562306a36Sopenharmony_ci * sure. 35662306a36Sopenharmony_ci */ 35762306a36Sopenharmony_ci ret = mipi_dsi_dcs_write(dsi, NT35560_DCS_SET_MDDI, 35862306a36Sopenharmony_ci &mddi, sizeof(mddi)); 35962306a36Sopenharmony_ci if (ret < 0) { 36062306a36Sopenharmony_ci dev_err(nt->dev, "failed to set MDDI (%d)\n", ret); 36162306a36Sopenharmony_ci goto err_power_off; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci /* Exit sleep mode */ 36562306a36Sopenharmony_ci ret = mipi_dsi_dcs_exit_sleep_mode(dsi); 36662306a36Sopenharmony_ci if (ret) { 36762306a36Sopenharmony_ci dev_err(nt->dev, "failed to exit sleep mode (%d)\n", ret); 36862306a36Sopenharmony_ci goto err_power_off; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci msleep(140); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_display_on(dsi); 37362306a36Sopenharmony_ci if (ret) { 37462306a36Sopenharmony_ci dev_err(nt->dev, "failed to turn display on (%d)\n", ret); 37562306a36Sopenharmony_ci goto err_power_off; 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci if (nt->video_mode) { 37862306a36Sopenharmony_ci /* In video mode turn peripheral on */ 37962306a36Sopenharmony_ci ret = mipi_dsi_turn_on_peripheral(dsi); 38062306a36Sopenharmony_ci if (ret) { 38162306a36Sopenharmony_ci dev_err(nt->dev, "failed to turn on peripheral\n"); 38262306a36Sopenharmony_ci goto err_power_off; 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci return 0; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cierr_power_off: 38962306a36Sopenharmony_ci nt35560_power_off(nt); 39062306a36Sopenharmony_ci return ret; 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_cistatic int nt35560_unprepare(struct drm_panel *panel) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct nt35560 *nt = panel_to_nt35560(panel); 39662306a36Sopenharmony_ci struct mipi_dsi_device *dsi = to_mipi_dsi_device(nt->dev); 39762306a36Sopenharmony_ci int ret; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_display_off(dsi); 40062306a36Sopenharmony_ci if (ret) { 40162306a36Sopenharmony_ci dev_err(nt->dev, "failed to turn display off (%d)\n", ret); 40262306a36Sopenharmony_ci return ret; 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci /* Enter sleep mode */ 40662306a36Sopenharmony_ci ret = mipi_dsi_dcs_enter_sleep_mode(dsi); 40762306a36Sopenharmony_ci if (ret) { 40862306a36Sopenharmony_ci dev_err(nt->dev, "failed to enter sleep mode (%d)\n", ret); 40962306a36Sopenharmony_ci return ret; 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci msleep(85); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci nt35560_power_off(nt); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci return 0; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic int nt35560_get_modes(struct drm_panel *panel, 42062306a36Sopenharmony_ci struct drm_connector *connector) 42162306a36Sopenharmony_ci{ 42262306a36Sopenharmony_ci struct nt35560 *nt = panel_to_nt35560(panel); 42362306a36Sopenharmony_ci const struct nt35560_config *conf = nt->conf; 42462306a36Sopenharmony_ci struct drm_display_mode *mode; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci if (nt->video_mode) 42762306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, 42862306a36Sopenharmony_ci conf->vid_mode); 42962306a36Sopenharmony_ci else 43062306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, 43162306a36Sopenharmony_ci conf->cmd_mode); 43262306a36Sopenharmony_ci if (!mode) { 43362306a36Sopenharmony_ci dev_err(panel->dev, "bad mode or failed to add mode\n"); 43462306a36Sopenharmony_ci return -EINVAL; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci drm_mode_set_name(mode); 43762306a36Sopenharmony_ci mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci connector->display_info.width_mm = mode->width_mm; 44062306a36Sopenharmony_ci connector->display_info.height_mm = mode->height_mm; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci return 1; /* Number of modes */ 44562306a36Sopenharmony_ci} 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_cistatic const struct drm_panel_funcs nt35560_drm_funcs = { 44862306a36Sopenharmony_ci .unprepare = nt35560_unprepare, 44962306a36Sopenharmony_ci .prepare = nt35560_prepare, 45062306a36Sopenharmony_ci .get_modes = nt35560_get_modes, 45162306a36Sopenharmony_ci}; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_cistatic int nt35560_probe(struct mipi_dsi_device *dsi) 45462306a36Sopenharmony_ci{ 45562306a36Sopenharmony_ci struct device *dev = &dsi->dev; 45662306a36Sopenharmony_ci struct nt35560 *nt; 45762306a36Sopenharmony_ci int ret; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci nt = devm_kzalloc(dev, sizeof(struct nt35560), GFP_KERNEL); 46062306a36Sopenharmony_ci if (!nt) 46162306a36Sopenharmony_ci return -ENOMEM; 46262306a36Sopenharmony_ci nt->video_mode = of_property_read_bool(dev->of_node, 46362306a36Sopenharmony_ci "enforce-video-mode"); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci mipi_dsi_set_drvdata(dsi, nt); 46662306a36Sopenharmony_ci nt->dev = dev; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci nt->conf = of_device_get_match_data(dev); 46962306a36Sopenharmony_ci if (!nt->conf) { 47062306a36Sopenharmony_ci dev_err(dev, "missing device configuration\n"); 47162306a36Sopenharmony_ci return -ENODEV; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci dsi->lanes = 2; 47562306a36Sopenharmony_ci dsi->format = MIPI_DSI_FMT_RGB888; 47662306a36Sopenharmony_ci /* 47762306a36Sopenharmony_ci * FIXME: these come from the ST-Ericsson vendor driver for the 47862306a36Sopenharmony_ci * HREF520 and seems to reflect limitations in the PLLs on that 47962306a36Sopenharmony_ci * platform, if you have the datasheet, please cross-check the 48062306a36Sopenharmony_ci * actual max rates. 48162306a36Sopenharmony_ci */ 48262306a36Sopenharmony_ci dsi->lp_rate = 19200000; 48362306a36Sopenharmony_ci dsi->hs_rate = 420160000; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (nt->video_mode) 48662306a36Sopenharmony_ci /* Burst mode using event for sync */ 48762306a36Sopenharmony_ci dsi->mode_flags = 48862306a36Sopenharmony_ci MIPI_DSI_MODE_VIDEO | 48962306a36Sopenharmony_ci MIPI_DSI_MODE_VIDEO_BURST; 49062306a36Sopenharmony_ci else 49162306a36Sopenharmony_ci dsi->mode_flags = 49262306a36Sopenharmony_ci MIPI_DSI_CLOCK_NON_CONTINUOUS; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci nt->supply = devm_regulator_get(dev, "vddi"); 49562306a36Sopenharmony_ci if (IS_ERR(nt->supply)) 49662306a36Sopenharmony_ci return PTR_ERR(nt->supply); 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci /* This asserts RESET by default */ 49962306a36Sopenharmony_ci nt->reset_gpio = devm_gpiod_get_optional(dev, "reset", 50062306a36Sopenharmony_ci GPIOD_OUT_HIGH); 50162306a36Sopenharmony_ci if (IS_ERR(nt->reset_gpio)) 50262306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(nt->reset_gpio), 50362306a36Sopenharmony_ci "failed to request GPIO\n"); 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci drm_panel_init(&nt->panel, dev, &nt35560_drm_funcs, 50662306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DSI); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci nt->panel.backlight = devm_backlight_device_register(dev, "nt35560", dev, nt, 50962306a36Sopenharmony_ci &nt35560_bl_ops, &nt35560_bl_props); 51062306a36Sopenharmony_ci if (IS_ERR(nt->panel.backlight)) 51162306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(nt->panel.backlight), 51262306a36Sopenharmony_ci "failed to register backlight device\n"); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci drm_panel_add(&nt->panel); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci ret = mipi_dsi_attach(dsi); 51762306a36Sopenharmony_ci if (ret < 0) { 51862306a36Sopenharmony_ci drm_panel_remove(&nt->panel); 51962306a36Sopenharmony_ci return ret; 52062306a36Sopenharmony_ci } 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci return 0; 52362306a36Sopenharmony_ci} 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_cistatic void nt35560_remove(struct mipi_dsi_device *dsi) 52662306a36Sopenharmony_ci{ 52762306a36Sopenharmony_ci struct nt35560 *nt = mipi_dsi_get_drvdata(dsi); 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci mipi_dsi_detach(dsi); 53062306a36Sopenharmony_ci drm_panel_remove(&nt->panel); 53162306a36Sopenharmony_ci} 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cistatic const struct of_device_id nt35560_of_match[] = { 53462306a36Sopenharmony_ci { 53562306a36Sopenharmony_ci .compatible = "sony,acx424akp", 53662306a36Sopenharmony_ci .data = &sony_acx424akp_data, 53762306a36Sopenharmony_ci }, 53862306a36Sopenharmony_ci { 53962306a36Sopenharmony_ci .compatible = "sony,acx424akm", 54062306a36Sopenharmony_ci .data = &sony_acx424akm_data, 54162306a36Sopenharmony_ci }, 54262306a36Sopenharmony_ci { /* sentinel */ } 54362306a36Sopenharmony_ci}; 54462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, nt35560_of_match); 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_cistatic struct mipi_dsi_driver nt35560_driver = { 54762306a36Sopenharmony_ci .probe = nt35560_probe, 54862306a36Sopenharmony_ci .remove = nt35560_remove, 54962306a36Sopenharmony_ci .driver = { 55062306a36Sopenharmony_ci .name = "panel-novatek-nt35560", 55162306a36Sopenharmony_ci .of_match_table = nt35560_of_match, 55262306a36Sopenharmony_ci }, 55362306a36Sopenharmony_ci}; 55462306a36Sopenharmony_cimodule_mipi_dsi_driver(nt35560_driver); 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ciMODULE_AUTHOR("Linus Wallei <linus.walleij@linaro.org>"); 55762306a36Sopenharmony_ciMODULE_DESCRIPTION("MIPI-DSI Novatek NT35560 Panel Driver"); 55862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 559