162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Raydium RM67191 MIPI-DSI panel driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2019 NXP 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/backlight.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1162306a36Sopenharmony_ci#include <linux/media-bus-format.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <video/mipi_display.h> 1762306a36Sopenharmony_ci#include <video/of_videomode.h> 1862306a36Sopenharmony_ci#include <video/videomode.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <drm/drm_crtc.h> 2162306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h> 2262306a36Sopenharmony_ci#include <drm/drm_panel.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci/* Panel specific color-format bits */ 2562306a36Sopenharmony_ci#define COL_FMT_16BPP 0x55 2662306a36Sopenharmony_ci#define COL_FMT_18BPP 0x66 2762306a36Sopenharmony_ci#define COL_FMT_24BPP 0x77 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* Write Manufacture Command Set Control */ 3062306a36Sopenharmony_ci#define WRMAUCCTR 0xFE 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* Manufacturer Command Set pages (CMD2) */ 3362306a36Sopenharmony_cistruct cmd_set_entry { 3462306a36Sopenharmony_ci u8 cmd; 3562306a36Sopenharmony_ci u8 param; 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* 3962306a36Sopenharmony_ci * There is no description in the Reference Manual about these commands. 4062306a36Sopenharmony_ci * We received them from vendor, so just use them as is. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_cistatic const struct cmd_set_entry manufacturer_cmd_set[] = { 4362306a36Sopenharmony_ci {0xFE, 0x0B}, 4462306a36Sopenharmony_ci {0x28, 0x40}, 4562306a36Sopenharmony_ci {0x29, 0x4F}, 4662306a36Sopenharmony_ci {0xFE, 0x0E}, 4762306a36Sopenharmony_ci {0x4B, 0x00}, 4862306a36Sopenharmony_ci {0x4C, 0x0F}, 4962306a36Sopenharmony_ci {0x4D, 0x20}, 5062306a36Sopenharmony_ci {0x4E, 0x40}, 5162306a36Sopenharmony_ci {0x4F, 0x60}, 5262306a36Sopenharmony_ci {0x50, 0xA0}, 5362306a36Sopenharmony_ci {0x51, 0xC0}, 5462306a36Sopenharmony_ci {0x52, 0xE0}, 5562306a36Sopenharmony_ci {0x53, 0xFF}, 5662306a36Sopenharmony_ci {0xFE, 0x0D}, 5762306a36Sopenharmony_ci {0x18, 0x08}, 5862306a36Sopenharmony_ci {0x42, 0x00}, 5962306a36Sopenharmony_ci {0x08, 0x41}, 6062306a36Sopenharmony_ci {0x46, 0x02}, 6162306a36Sopenharmony_ci {0x72, 0x09}, 6262306a36Sopenharmony_ci {0xFE, 0x0A}, 6362306a36Sopenharmony_ci {0x24, 0x17}, 6462306a36Sopenharmony_ci {0x04, 0x07}, 6562306a36Sopenharmony_ci {0x1A, 0x0C}, 6662306a36Sopenharmony_ci {0x0F, 0x44}, 6762306a36Sopenharmony_ci {0xFE, 0x04}, 6862306a36Sopenharmony_ci {0x00, 0x0C}, 6962306a36Sopenharmony_ci {0x05, 0x08}, 7062306a36Sopenharmony_ci {0x06, 0x08}, 7162306a36Sopenharmony_ci {0x08, 0x08}, 7262306a36Sopenharmony_ci {0x09, 0x08}, 7362306a36Sopenharmony_ci {0x0A, 0xE6}, 7462306a36Sopenharmony_ci {0x0B, 0x8C}, 7562306a36Sopenharmony_ci {0x1A, 0x12}, 7662306a36Sopenharmony_ci {0x1E, 0xE0}, 7762306a36Sopenharmony_ci {0x29, 0x93}, 7862306a36Sopenharmony_ci {0x2A, 0x93}, 7962306a36Sopenharmony_ci {0x2F, 0x02}, 8062306a36Sopenharmony_ci {0x31, 0x02}, 8162306a36Sopenharmony_ci {0x33, 0x05}, 8262306a36Sopenharmony_ci {0x37, 0x2D}, 8362306a36Sopenharmony_ci {0x38, 0x2D}, 8462306a36Sopenharmony_ci {0x3A, 0x1E}, 8562306a36Sopenharmony_ci {0x3B, 0x1E}, 8662306a36Sopenharmony_ci {0x3D, 0x27}, 8762306a36Sopenharmony_ci {0x3F, 0x80}, 8862306a36Sopenharmony_ci {0x40, 0x40}, 8962306a36Sopenharmony_ci {0x41, 0xE0}, 9062306a36Sopenharmony_ci {0x4F, 0x2F}, 9162306a36Sopenharmony_ci {0x50, 0x1E}, 9262306a36Sopenharmony_ci {0xFE, 0x06}, 9362306a36Sopenharmony_ci {0x00, 0xCC}, 9462306a36Sopenharmony_ci {0x05, 0x05}, 9562306a36Sopenharmony_ci {0x07, 0xA2}, 9662306a36Sopenharmony_ci {0x08, 0xCC}, 9762306a36Sopenharmony_ci {0x0D, 0x03}, 9862306a36Sopenharmony_ci {0x0F, 0xA2}, 9962306a36Sopenharmony_ci {0x32, 0xCC}, 10062306a36Sopenharmony_ci {0x37, 0x05}, 10162306a36Sopenharmony_ci {0x39, 0x83}, 10262306a36Sopenharmony_ci {0x3A, 0xCC}, 10362306a36Sopenharmony_ci {0x41, 0x04}, 10462306a36Sopenharmony_ci {0x43, 0x83}, 10562306a36Sopenharmony_ci {0x44, 0xCC}, 10662306a36Sopenharmony_ci {0x49, 0x05}, 10762306a36Sopenharmony_ci {0x4B, 0xA2}, 10862306a36Sopenharmony_ci {0x4C, 0xCC}, 10962306a36Sopenharmony_ci {0x51, 0x03}, 11062306a36Sopenharmony_ci {0x53, 0xA2}, 11162306a36Sopenharmony_ci {0x75, 0xCC}, 11262306a36Sopenharmony_ci {0x7A, 0x03}, 11362306a36Sopenharmony_ci {0x7C, 0x83}, 11462306a36Sopenharmony_ci {0x7D, 0xCC}, 11562306a36Sopenharmony_ci {0x82, 0x02}, 11662306a36Sopenharmony_ci {0x84, 0x83}, 11762306a36Sopenharmony_ci {0x85, 0xEC}, 11862306a36Sopenharmony_ci {0x86, 0x0F}, 11962306a36Sopenharmony_ci {0x87, 0xFF}, 12062306a36Sopenharmony_ci {0x88, 0x00}, 12162306a36Sopenharmony_ci {0x8A, 0x02}, 12262306a36Sopenharmony_ci {0x8C, 0xA2}, 12362306a36Sopenharmony_ci {0x8D, 0xEA}, 12462306a36Sopenharmony_ci {0x8E, 0x01}, 12562306a36Sopenharmony_ci {0x8F, 0xE8}, 12662306a36Sopenharmony_ci {0xFE, 0x06}, 12762306a36Sopenharmony_ci {0x90, 0x0A}, 12862306a36Sopenharmony_ci {0x92, 0x06}, 12962306a36Sopenharmony_ci {0x93, 0xA0}, 13062306a36Sopenharmony_ci {0x94, 0xA8}, 13162306a36Sopenharmony_ci {0x95, 0xEC}, 13262306a36Sopenharmony_ci {0x96, 0x0F}, 13362306a36Sopenharmony_ci {0x97, 0xFF}, 13462306a36Sopenharmony_ci {0x98, 0x00}, 13562306a36Sopenharmony_ci {0x9A, 0x02}, 13662306a36Sopenharmony_ci {0x9C, 0xA2}, 13762306a36Sopenharmony_ci {0xAC, 0x04}, 13862306a36Sopenharmony_ci {0xFE, 0x06}, 13962306a36Sopenharmony_ci {0xB1, 0x12}, 14062306a36Sopenharmony_ci {0xB2, 0x17}, 14162306a36Sopenharmony_ci {0xB3, 0x17}, 14262306a36Sopenharmony_ci {0xB4, 0x17}, 14362306a36Sopenharmony_ci {0xB5, 0x17}, 14462306a36Sopenharmony_ci {0xB6, 0x11}, 14562306a36Sopenharmony_ci {0xB7, 0x08}, 14662306a36Sopenharmony_ci {0xB8, 0x09}, 14762306a36Sopenharmony_ci {0xB9, 0x06}, 14862306a36Sopenharmony_ci {0xBA, 0x07}, 14962306a36Sopenharmony_ci {0xBB, 0x17}, 15062306a36Sopenharmony_ci {0xBC, 0x17}, 15162306a36Sopenharmony_ci {0xBD, 0x17}, 15262306a36Sopenharmony_ci {0xBE, 0x17}, 15362306a36Sopenharmony_ci {0xBF, 0x17}, 15462306a36Sopenharmony_ci {0xC0, 0x17}, 15562306a36Sopenharmony_ci {0xC1, 0x17}, 15662306a36Sopenharmony_ci {0xC2, 0x17}, 15762306a36Sopenharmony_ci {0xC3, 0x17}, 15862306a36Sopenharmony_ci {0xC4, 0x0F}, 15962306a36Sopenharmony_ci {0xC5, 0x0E}, 16062306a36Sopenharmony_ci {0xC6, 0x00}, 16162306a36Sopenharmony_ci {0xC7, 0x01}, 16262306a36Sopenharmony_ci {0xC8, 0x10}, 16362306a36Sopenharmony_ci {0xFE, 0x06}, 16462306a36Sopenharmony_ci {0x95, 0xEC}, 16562306a36Sopenharmony_ci {0x8D, 0xEE}, 16662306a36Sopenharmony_ci {0x44, 0xEC}, 16762306a36Sopenharmony_ci {0x4C, 0xEC}, 16862306a36Sopenharmony_ci {0x32, 0xEC}, 16962306a36Sopenharmony_ci {0x3A, 0xEC}, 17062306a36Sopenharmony_ci {0x7D, 0xEC}, 17162306a36Sopenharmony_ci {0x75, 0xEC}, 17262306a36Sopenharmony_ci {0x00, 0xEC}, 17362306a36Sopenharmony_ci {0x08, 0xEC}, 17462306a36Sopenharmony_ci {0x85, 0xEC}, 17562306a36Sopenharmony_ci {0xA6, 0x21}, 17662306a36Sopenharmony_ci {0xA7, 0x05}, 17762306a36Sopenharmony_ci {0xA9, 0x06}, 17862306a36Sopenharmony_ci {0x82, 0x06}, 17962306a36Sopenharmony_ci {0x41, 0x06}, 18062306a36Sopenharmony_ci {0x7A, 0x07}, 18162306a36Sopenharmony_ci {0x37, 0x07}, 18262306a36Sopenharmony_ci {0x05, 0x06}, 18362306a36Sopenharmony_ci {0x49, 0x06}, 18462306a36Sopenharmony_ci {0x0D, 0x04}, 18562306a36Sopenharmony_ci {0x51, 0x04}, 18662306a36Sopenharmony_ci}; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic const u32 rad_bus_formats[] = { 18962306a36Sopenharmony_ci MEDIA_BUS_FMT_RGB888_1X24, 19062306a36Sopenharmony_ci MEDIA_BUS_FMT_RGB666_1X18, 19162306a36Sopenharmony_ci MEDIA_BUS_FMT_RGB565_1X16, 19262306a36Sopenharmony_ci}; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic const u32 rad_bus_flags = DRM_BUS_FLAG_DE_LOW | 19562306a36Sopenharmony_ci DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistruct rad_panel { 19862306a36Sopenharmony_ci struct drm_panel panel; 19962306a36Sopenharmony_ci struct mipi_dsi_device *dsi; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci struct gpio_desc *reset; 20262306a36Sopenharmony_ci struct backlight_device *backlight; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci struct regulator_bulk_data *supplies; 20562306a36Sopenharmony_ci unsigned int num_supplies; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci bool prepared; 20862306a36Sopenharmony_ci bool enabled; 20962306a36Sopenharmony_ci}; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic const struct drm_display_mode default_mode = { 21262306a36Sopenharmony_ci .clock = 132000, 21362306a36Sopenharmony_ci .hdisplay = 1080, 21462306a36Sopenharmony_ci .hsync_start = 1080 + 20, 21562306a36Sopenharmony_ci .hsync_end = 1080 + 20 + 2, 21662306a36Sopenharmony_ci .htotal = 1080 + 20 + 2 + 34, 21762306a36Sopenharmony_ci .vdisplay = 1920, 21862306a36Sopenharmony_ci .vsync_start = 1920 + 10, 21962306a36Sopenharmony_ci .vsync_end = 1920 + 10 + 2, 22062306a36Sopenharmony_ci .vtotal = 1920 + 10 + 2 + 4, 22162306a36Sopenharmony_ci .width_mm = 68, 22262306a36Sopenharmony_ci .height_mm = 121, 22362306a36Sopenharmony_ci .flags = DRM_MODE_FLAG_NHSYNC | 22462306a36Sopenharmony_ci DRM_MODE_FLAG_NVSYNC, 22562306a36Sopenharmony_ci}; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic inline struct rad_panel *to_rad_panel(struct drm_panel *panel) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci return container_of(panel, struct rad_panel, panel); 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci size_t i; 23562306a36Sopenharmony_ci size_t count = ARRAY_SIZE(manufacturer_cmd_set); 23662306a36Sopenharmony_ci int ret = 0; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci for (i = 0; i < count; i++) { 23962306a36Sopenharmony_ci const struct cmd_set_entry *entry = &manufacturer_cmd_set[i]; 24062306a36Sopenharmony_ci u8 buffer[2] = { entry->cmd, entry->param }; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci ret = mipi_dsi_generic_write(dsi, &buffer, sizeof(buffer)); 24362306a36Sopenharmony_ci if (ret < 0) 24462306a36Sopenharmony_ci return ret; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return ret; 24862306a36Sopenharmony_ci}; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic int color_format_from_dsi_format(enum mipi_dsi_pixel_format format) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci switch (format) { 25362306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB565: 25462306a36Sopenharmony_ci return COL_FMT_16BPP; 25562306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB666: 25662306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB666_PACKED: 25762306a36Sopenharmony_ci return COL_FMT_18BPP; 25862306a36Sopenharmony_ci case MIPI_DSI_FMT_RGB888: 25962306a36Sopenharmony_ci return COL_FMT_24BPP; 26062306a36Sopenharmony_ci default: 26162306a36Sopenharmony_ci return COL_FMT_24BPP; /* for backward compatibility */ 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci}; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic int rad_panel_prepare(struct drm_panel *panel) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct rad_panel *rad = to_rad_panel(panel); 26862306a36Sopenharmony_ci int ret; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if (rad->prepared) 27162306a36Sopenharmony_ci return 0; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci ret = regulator_bulk_enable(rad->num_supplies, rad->supplies); 27462306a36Sopenharmony_ci if (ret) 27562306a36Sopenharmony_ci return ret; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (rad->reset) { 27862306a36Sopenharmony_ci gpiod_set_value_cansleep(rad->reset, 1); 27962306a36Sopenharmony_ci usleep_range(3000, 5000); 28062306a36Sopenharmony_ci gpiod_set_value_cansleep(rad->reset, 0); 28162306a36Sopenharmony_ci usleep_range(18000, 20000); 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci rad->prepared = true; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci return 0; 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistatic int rad_panel_unprepare(struct drm_panel *panel) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci struct rad_panel *rad = to_rad_panel(panel); 29262306a36Sopenharmony_ci int ret; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci if (!rad->prepared) 29562306a36Sopenharmony_ci return 0; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci /* 29862306a36Sopenharmony_ci * Right after asserting the reset, we need to release it, so that the 29962306a36Sopenharmony_ci * touch driver can have an active connection with the touch controller 30062306a36Sopenharmony_ci * even after the display is turned off. 30162306a36Sopenharmony_ci */ 30262306a36Sopenharmony_ci if (rad->reset) { 30362306a36Sopenharmony_ci gpiod_set_value_cansleep(rad->reset, 1); 30462306a36Sopenharmony_ci usleep_range(15000, 17000); 30562306a36Sopenharmony_ci gpiod_set_value_cansleep(rad->reset, 0); 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci ret = regulator_bulk_disable(rad->num_supplies, rad->supplies); 30962306a36Sopenharmony_ci if (ret) 31062306a36Sopenharmony_ci return ret; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci rad->prepared = false; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci return 0; 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_cistatic int rad_panel_enable(struct drm_panel *panel) 31862306a36Sopenharmony_ci{ 31962306a36Sopenharmony_ci struct rad_panel *rad = to_rad_panel(panel); 32062306a36Sopenharmony_ci struct mipi_dsi_device *dsi = rad->dsi; 32162306a36Sopenharmony_ci struct device *dev = &dsi->dev; 32262306a36Sopenharmony_ci int color_format = color_format_from_dsi_format(dsi->format); 32362306a36Sopenharmony_ci int ret; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (rad->enabled) 32662306a36Sopenharmony_ci return 0; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci dsi->mode_flags |= MIPI_DSI_MODE_LPM; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci ret = rad_panel_push_cmd_list(dsi); 33162306a36Sopenharmony_ci if (ret < 0) { 33262306a36Sopenharmony_ci dev_err(dev, "Failed to send MCS (%d)\n", ret); 33362306a36Sopenharmony_ci goto fail; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci /* Select User Command Set table (CMD1) */ 33762306a36Sopenharmony_ci ret = mipi_dsi_generic_write(dsi, (u8[]){ WRMAUCCTR, 0x00 }, 2); 33862306a36Sopenharmony_ci if (ret < 0) 33962306a36Sopenharmony_ci goto fail; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci /* Software reset */ 34262306a36Sopenharmony_ci ret = mipi_dsi_dcs_soft_reset(dsi); 34362306a36Sopenharmony_ci if (ret < 0) { 34462306a36Sopenharmony_ci dev_err(dev, "Failed to do Software Reset (%d)\n", ret); 34562306a36Sopenharmony_ci goto fail; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci usleep_range(15000, 17000); 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci /* Set DSI mode */ 35162306a36Sopenharmony_ci ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, 0x0B }, 2); 35262306a36Sopenharmony_ci if (ret < 0) { 35362306a36Sopenharmony_ci dev_err(dev, "Failed to set DSI mode (%d)\n", ret); 35462306a36Sopenharmony_ci goto fail; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci /* Set tear ON */ 35762306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); 35862306a36Sopenharmony_ci if (ret < 0) { 35962306a36Sopenharmony_ci dev_err(dev, "Failed to set tear ON (%d)\n", ret); 36062306a36Sopenharmony_ci goto fail; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci /* Set tear scanline */ 36362306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0x380); 36462306a36Sopenharmony_ci if (ret < 0) { 36562306a36Sopenharmony_ci dev_err(dev, "Failed to set tear scanline (%d)\n", ret); 36662306a36Sopenharmony_ci goto fail; 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci /* Set pixel format */ 36962306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_pixel_format(dsi, color_format); 37062306a36Sopenharmony_ci dev_dbg(dev, "Interface color format set to 0x%x\n", color_format); 37162306a36Sopenharmony_ci if (ret < 0) { 37262306a36Sopenharmony_ci dev_err(dev, "Failed to set pixel format (%d)\n", ret); 37362306a36Sopenharmony_ci goto fail; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci /* Exit sleep mode */ 37662306a36Sopenharmony_ci ret = mipi_dsi_dcs_exit_sleep_mode(dsi); 37762306a36Sopenharmony_ci if (ret < 0) { 37862306a36Sopenharmony_ci dev_err(dev, "Failed to exit sleep mode (%d)\n", ret); 37962306a36Sopenharmony_ci goto fail; 38062306a36Sopenharmony_ci } 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci usleep_range(5000, 7000); 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_display_on(dsi); 38562306a36Sopenharmony_ci if (ret < 0) { 38662306a36Sopenharmony_ci dev_err(dev, "Failed to set display ON (%d)\n", ret); 38762306a36Sopenharmony_ci goto fail; 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci backlight_enable(rad->backlight); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci rad->enabled = true; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci return 0; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_cifail: 39762306a36Sopenharmony_ci gpiod_set_value_cansleep(rad->reset, 1); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci return ret; 40062306a36Sopenharmony_ci} 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_cistatic int rad_panel_disable(struct drm_panel *panel) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci struct rad_panel *rad = to_rad_panel(panel); 40562306a36Sopenharmony_ci struct mipi_dsi_device *dsi = rad->dsi; 40662306a36Sopenharmony_ci struct device *dev = &dsi->dev; 40762306a36Sopenharmony_ci int ret; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci if (!rad->enabled) 41062306a36Sopenharmony_ci return 0; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci dsi->mode_flags |= MIPI_DSI_MODE_LPM; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci backlight_disable(rad->backlight); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci usleep_range(10000, 12000); 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_display_off(dsi); 41962306a36Sopenharmony_ci if (ret < 0) { 42062306a36Sopenharmony_ci dev_err(dev, "Failed to set display OFF (%d)\n", ret); 42162306a36Sopenharmony_ci return ret; 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci usleep_range(5000, 10000); 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci ret = mipi_dsi_dcs_enter_sleep_mode(dsi); 42762306a36Sopenharmony_ci if (ret < 0) { 42862306a36Sopenharmony_ci dev_err(dev, "Failed to enter sleep mode (%d)\n", ret); 42962306a36Sopenharmony_ci return ret; 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci rad->enabled = false; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci return 0; 43562306a36Sopenharmony_ci} 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_cistatic int rad_panel_get_modes(struct drm_panel *panel, 43862306a36Sopenharmony_ci struct drm_connector *connector) 43962306a36Sopenharmony_ci{ 44062306a36Sopenharmony_ci struct drm_display_mode *mode; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, &default_mode); 44362306a36Sopenharmony_ci if (!mode) { 44462306a36Sopenharmony_ci dev_err(panel->dev, "failed to add mode %ux%u@%u\n", 44562306a36Sopenharmony_ci default_mode.hdisplay, default_mode.vdisplay, 44662306a36Sopenharmony_ci drm_mode_vrefresh(&default_mode)); 44762306a36Sopenharmony_ci return -ENOMEM; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci drm_mode_set_name(mode); 45162306a36Sopenharmony_ci mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 45262306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci connector->display_info.width_mm = mode->width_mm; 45562306a36Sopenharmony_ci connector->display_info.height_mm = mode->height_mm; 45662306a36Sopenharmony_ci connector->display_info.bus_flags = rad_bus_flags; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci drm_display_info_set_bus_formats(&connector->display_info, 45962306a36Sopenharmony_ci rad_bus_formats, 46062306a36Sopenharmony_ci ARRAY_SIZE(rad_bus_formats)); 46162306a36Sopenharmony_ci return 1; 46262306a36Sopenharmony_ci} 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_cistatic int rad_bl_get_brightness(struct backlight_device *bl) 46562306a36Sopenharmony_ci{ 46662306a36Sopenharmony_ci struct mipi_dsi_device *dsi = bl_get_data(bl); 46762306a36Sopenharmony_ci struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); 46862306a36Sopenharmony_ci u16 brightness; 46962306a36Sopenharmony_ci int ret; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci if (!rad->prepared) 47262306a36Sopenharmony_ci return 0; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci ret = mipi_dsi_dcs_get_display_brightness(dsi, &brightness); 47762306a36Sopenharmony_ci if (ret < 0) 47862306a36Sopenharmony_ci return ret; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci bl->props.brightness = brightness; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci return brightness & 0xff; 48362306a36Sopenharmony_ci} 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic int rad_bl_update_status(struct backlight_device *bl) 48662306a36Sopenharmony_ci{ 48762306a36Sopenharmony_ci struct mipi_dsi_device *dsi = bl_get_data(bl); 48862306a36Sopenharmony_ci struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); 48962306a36Sopenharmony_ci int ret = 0; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci if (!rad->prepared) 49262306a36Sopenharmony_ci return 0; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci ret = mipi_dsi_dcs_set_display_brightness(dsi, bl->props.brightness); 49762306a36Sopenharmony_ci if (ret < 0) 49862306a36Sopenharmony_ci return ret; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci return 0; 50162306a36Sopenharmony_ci} 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_cistatic const struct backlight_ops rad_bl_ops = { 50462306a36Sopenharmony_ci .update_status = rad_bl_update_status, 50562306a36Sopenharmony_ci .get_brightness = rad_bl_get_brightness, 50662306a36Sopenharmony_ci}; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_cistatic const struct drm_panel_funcs rad_panel_funcs = { 50962306a36Sopenharmony_ci .prepare = rad_panel_prepare, 51062306a36Sopenharmony_ci .unprepare = rad_panel_unprepare, 51162306a36Sopenharmony_ci .enable = rad_panel_enable, 51262306a36Sopenharmony_ci .disable = rad_panel_disable, 51362306a36Sopenharmony_ci .get_modes = rad_panel_get_modes, 51462306a36Sopenharmony_ci}; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_cistatic const char * const rad_supply_names[] = { 51762306a36Sopenharmony_ci "v3p3", 51862306a36Sopenharmony_ci "v1p8", 51962306a36Sopenharmony_ci}; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_cistatic int rad_init_regulators(struct rad_panel *rad) 52262306a36Sopenharmony_ci{ 52362306a36Sopenharmony_ci struct device *dev = &rad->dsi->dev; 52462306a36Sopenharmony_ci int i; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci rad->num_supplies = ARRAY_SIZE(rad_supply_names); 52762306a36Sopenharmony_ci rad->supplies = devm_kcalloc(dev, rad->num_supplies, 52862306a36Sopenharmony_ci sizeof(*rad->supplies), GFP_KERNEL); 52962306a36Sopenharmony_ci if (!rad->supplies) 53062306a36Sopenharmony_ci return -ENOMEM; 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci for (i = 0; i < rad->num_supplies; i++) 53362306a36Sopenharmony_ci rad->supplies[i].supply = rad_supply_names[i]; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci return devm_regulator_bulk_get(dev, rad->num_supplies, rad->supplies); 53662306a36Sopenharmony_ci}; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_cistatic int rad_panel_probe(struct mipi_dsi_device *dsi) 53962306a36Sopenharmony_ci{ 54062306a36Sopenharmony_ci struct device *dev = &dsi->dev; 54162306a36Sopenharmony_ci struct device_node *np = dev->of_node; 54262306a36Sopenharmony_ci struct rad_panel *panel; 54362306a36Sopenharmony_ci struct backlight_properties bl_props; 54462306a36Sopenharmony_ci int ret; 54562306a36Sopenharmony_ci u32 video_mode; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL); 54862306a36Sopenharmony_ci if (!panel) 54962306a36Sopenharmony_ci return -ENOMEM; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci mipi_dsi_set_drvdata(dsi, panel); 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci panel->dsi = dsi; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci dsi->format = MIPI_DSI_FMT_RGB888; 55662306a36Sopenharmony_ci dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci ret = of_property_read_u32(np, "video-mode", &video_mode); 55962306a36Sopenharmony_ci if (!ret) { 56062306a36Sopenharmony_ci switch (video_mode) { 56162306a36Sopenharmony_ci case 0: 56262306a36Sopenharmony_ci /* burst mode */ 56362306a36Sopenharmony_ci dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST; 56462306a36Sopenharmony_ci break; 56562306a36Sopenharmony_ci case 1: 56662306a36Sopenharmony_ci /* non-burst mode with sync event */ 56762306a36Sopenharmony_ci break; 56862306a36Sopenharmony_ci case 2: 56962306a36Sopenharmony_ci /* non-burst mode with sync pulse */ 57062306a36Sopenharmony_ci dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE; 57162306a36Sopenharmony_ci break; 57262306a36Sopenharmony_ci default: 57362306a36Sopenharmony_ci dev_warn(dev, "invalid video mode %d\n", video_mode); 57462306a36Sopenharmony_ci break; 57562306a36Sopenharmony_ci } 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci ret = of_property_read_u32(np, "dsi-lanes", &dsi->lanes); 57962306a36Sopenharmony_ci if (ret) { 58062306a36Sopenharmony_ci dev_err(dev, "Failed to get dsi-lanes property (%d)\n", ret); 58162306a36Sopenharmony_ci return ret; 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci panel->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); 58562306a36Sopenharmony_ci if (IS_ERR(panel->reset)) 58662306a36Sopenharmony_ci return PTR_ERR(panel->reset); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci memset(&bl_props, 0, sizeof(bl_props)); 58962306a36Sopenharmony_ci bl_props.type = BACKLIGHT_RAW; 59062306a36Sopenharmony_ci bl_props.brightness = 255; 59162306a36Sopenharmony_ci bl_props.max_brightness = 255; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci panel->backlight = devm_backlight_device_register(dev, dev_name(dev), 59462306a36Sopenharmony_ci dev, dsi, &rad_bl_ops, 59562306a36Sopenharmony_ci &bl_props); 59662306a36Sopenharmony_ci if (IS_ERR(panel->backlight)) { 59762306a36Sopenharmony_ci ret = PTR_ERR(panel->backlight); 59862306a36Sopenharmony_ci dev_err(dev, "Failed to register backlight (%d)\n", ret); 59962306a36Sopenharmony_ci return ret; 60062306a36Sopenharmony_ci } 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci ret = rad_init_regulators(panel); 60362306a36Sopenharmony_ci if (ret) 60462306a36Sopenharmony_ci return ret; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci drm_panel_init(&panel->panel, dev, &rad_panel_funcs, 60762306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DSI); 60862306a36Sopenharmony_ci dev_set_drvdata(dev, panel); 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci drm_panel_add(&panel->panel); 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci ret = mipi_dsi_attach(dsi); 61362306a36Sopenharmony_ci if (ret) 61462306a36Sopenharmony_ci drm_panel_remove(&panel->panel); 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci return ret; 61762306a36Sopenharmony_ci} 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_cistatic void rad_panel_remove(struct mipi_dsi_device *dsi) 62062306a36Sopenharmony_ci{ 62162306a36Sopenharmony_ci struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); 62262306a36Sopenharmony_ci struct device *dev = &dsi->dev; 62362306a36Sopenharmony_ci int ret; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci ret = mipi_dsi_detach(dsi); 62662306a36Sopenharmony_ci if (ret) 62762306a36Sopenharmony_ci dev_err(dev, "Failed to detach from host (%d)\n", ret); 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci drm_panel_remove(&rad->panel); 63062306a36Sopenharmony_ci} 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_cistatic void rad_panel_shutdown(struct mipi_dsi_device *dsi) 63362306a36Sopenharmony_ci{ 63462306a36Sopenharmony_ci struct rad_panel *rad = mipi_dsi_get_drvdata(dsi); 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci rad_panel_disable(&rad->panel); 63762306a36Sopenharmony_ci rad_panel_unprepare(&rad->panel); 63862306a36Sopenharmony_ci} 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_cistatic const struct of_device_id rad_of_match[] = { 64162306a36Sopenharmony_ci { .compatible = "raydium,rm67191", }, 64262306a36Sopenharmony_ci { /* sentinel */ } 64362306a36Sopenharmony_ci}; 64462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rad_of_match); 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_cistatic struct mipi_dsi_driver rad_panel_driver = { 64762306a36Sopenharmony_ci .driver = { 64862306a36Sopenharmony_ci .name = "panel-raydium-rm67191", 64962306a36Sopenharmony_ci .of_match_table = rad_of_match, 65062306a36Sopenharmony_ci }, 65162306a36Sopenharmony_ci .probe = rad_panel_probe, 65262306a36Sopenharmony_ci .remove = rad_panel_remove, 65362306a36Sopenharmony_ci .shutdown = rad_panel_shutdown, 65462306a36Sopenharmony_ci}; 65562306a36Sopenharmony_cimodule_mipi_dsi_driver(rad_panel_driver); 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ciMODULE_AUTHOR("Robert Chiras <robert.chiras@nxp.com>"); 65862306a36Sopenharmony_ciMODULE_DESCRIPTION("DRM Driver for Raydium RM67191 MIPI DSI panel"); 65962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 660