// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2017
*
* Authors: Philippe Cornu <philippe.cornu@st.com>
* Yannick Fertre <yannick.fertre@st.com>
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <video/mipi_display.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
/*** Manufacturer Command Set ***/
#define MCS_CMD_MODE_SW 0 xFE /* CMD Mode Switch */
#define MCS_CMD1_UCS 0 x00 /* User Command Set (UCS = CMD1) */
#define MCS_CMD2_P0 0 x01 /* Manufacture Command Set Page0 (CMD2 P0) */
#define MCS_CMD2_P1 0 x02 /* Manufacture Command Set Page1 (CMD2 P1) */
#define MCS_CMD2_P2 0 x03 /* Manufacture Command Set Page2 (CMD2 P2) */
#define MCS_CMD2_P3 0 x04 /* Manufacture Command Set Page3 (CMD2 P3) */
/* CMD2 P0 commands (Display Options and Power) */
#define MCS_STBCTR 0 x12 /* TE1 Output Setting Zig-Zag Connection */
#define MCS_SGOPCTR 0 x16 /* Source Bias Current */
#define MCS_SDCTR 0 x1A /* Source Output Delay Time */
#define MCS_INVCTR 0 x1B /* Inversion Type */
#define MCS_EXT_PWR_IC 0 x24 /* External PWR IC Control */
#define MCS_SETAVDD 0 x27 /* PFM Control for AVDD Output */
#define MCS_SETAVEE 0 x29 /* PFM Control for AVEE Output */
#define MCS_BT2CTR 0 x2B /* DDVDL Charge Pump Control */
#define MCS_BT3CTR 0 x2F /* VGH Charge Pump Control */
#define MCS_BT4CTR 0 x34 /* VGL Charge Pump Control */
#define MCS_VCMCTR 0 x46 /* VCOM Output Level Control */
#define MCS_SETVGN 0 x52 /* VG M/S N Control */
#define MCS_SETVGP 0 x54 /* VG M/S P Control */
#define MCS_SW_CTRL 0 x5F /* Interface Control for PFM and MIPI */
/* CMD2 P2 commands (GOA Timing Control) - no description in datasheet */
#define GOA_VSTV1 0 x00
#define GOA_VSTV2 0 x07
#define GOA_VCLK1 0 x0E
#define GOA_VCLK2 0 x17
#define GOA_VCLK_OPT1 0 x20
#define GOA_BICLK1 0 x2A
#define GOA_BICLK2 0 x37
#define GOA_BICLK3 0 x44
#define GOA_BICLK4 0 x4F
#define GOA_BICLK_OPT1 0 x5B
#define GOA_BICLK_OPT2 0 x60
#define MCS_GOA_GPO1 0 x6D
#define MCS_GOA_GPO2 0 x71
#define MCS_GOA_EQ 0 x74
#define MCS_GOA_CLK_GALLON 0 x7C
#define MCS_GOA_FS_SEL0 0 x7E
#define MCS_GOA_FS_SEL1 0 x87
#define MCS_GOA_FS_SEL2 0 x91
#define MCS_GOA_FS_SEL3 0 x9B
#define MCS_GOA_BS_SEL0 0 xAC
#define MCS_GOA_BS_SEL1 0 xB5
#define MCS_GOA_BS_SEL2 0 xBF
#define MCS_GOA_BS_SEL3 0 xC9
#define MCS_GOA_BS_SEL4 0 xD3
/* CMD2 P3 commands (Gamma) */
#define MCS_GAMMA_VP 0 x60 /* Gamma VP1~VP16 */
#define MCS_GAMMA_VN 0 x70 /* Gamma VN1~VN16 */
struct rm68200 {
struct device *dev;
struct drm_panel panel;
struct gpio_desc *reset_gpio;
struct regulator *supply;
};
static const struct drm_display_mode default_mode = {
.clock = 54000 ,
.hdisplay = 720 ,
.hsync_start = 720 + 48 ,
.hsync_end = 720 + 48 + 9 ,
.htotal = 720 + 48 + 9 + 48 ,
.vdisplay = 1280 ,
.vsync_start = 1280 + 12 ,
.vsync_end = 1280 + 12 + 5 ,
.vtotal = 1280 + 12 + 5 + 12 ,
.flags = 0 ,
.width_mm = 68 ,
.height_mm = 122 ,
};
static inline struct rm68200 *panel_to_rm68200(struct drm_panel *panel)
{
return container_of(panel, struct rm68200, panel);
}
static void rm68200_dcs_write_buf(struct rm68200 *ctx, const void *data,
size_t len)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
int err;
err = mipi_dsi_dcs_write_buffer(dsi, data, len);
if (err < 0 )
dev_err_ratelimited(ctx->dev, "MIPI DSI DCS write buffer failed: %d\n" , err);
}
static void rm68200_dcs_write_cmd(struct rm68200 *ctx, u8 cmd, u8 value)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
int err;
err = mipi_dsi_dcs_write(dsi, cmd, &value, 1 );
if (err < 0 )
dev_err_ratelimited(ctx->dev, "MIPI DSI DCS write failed: %d\n" , err);
}
#define dcs_write_seq(ctx, seq...) \
({ \
static const u8 d[] = { seq }; \
\
rm68200_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \
})
/*
* This panel is not able to auto-increment all cmd addresses so for some of
* them, we need to send them one by one...
*/
#define dcs_write_cmd_seq(ctx, cmd, seq...) \
({ \
static const u8 d[] = { seq }; \
unsigned int i; \
\
for (i = 0 ; i < ARRAY_SIZE(d) ; i++) \
rm68200_dcs_write_cmd(ctx, cmd + i, d[i]); \
})
static void rm68200_init_sequence(struct rm68200 *ctx)
{
/* Enter CMD2 with page 0 */
dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD2_P0);
dcs_write_cmd_seq(ctx, MCS_EXT_PWR_IC, 0 xC0, 0 x53, 0 x00);
dcs_write_seq(ctx, MCS_BT2CTR, 0 xE5);
dcs_write_seq(ctx, MCS_SETAVDD, 0 x0A);
dcs_write_seq(ctx, MCS_SETAVEE, 0 x0A);
dcs_write_seq(ctx, MCS_SGOPCTR, 0 x52);
dcs_write_seq(ctx, MCS_BT3CTR, 0 x53);
dcs_write_seq(ctx, MCS_BT4CTR, 0 x5A);
dcs_write_seq(ctx, MCS_INVCTR, 0 x00);
dcs_write_seq(ctx, MCS_STBCTR, 0 x0A);
dcs_write_seq(ctx, MCS_SDCTR, 0 x06);
dcs_write_seq(ctx, MCS_VCMCTR, 0 x56);
dcs_write_seq(ctx, MCS_SETVGN, 0 xA0, 0 x00);
dcs_write_seq(ctx, MCS_SETVGP, 0 xA0, 0 x00);
dcs_write_seq(ctx, MCS_SW_CTRL, 0 x11); /* 2 data lanes, see doc */
dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD2_P2);
dcs_write_seq(ctx, GOA_VSTV1, 0 x05);
dcs_write_seq(ctx, 0 x02, 0 x0B);
dcs_write_seq(ctx, 0 x03, 0 x0F);
dcs_write_seq(ctx, 0 x04, 0 x7D, 0 x00, 0 x50);
dcs_write_cmd_seq(ctx, GOA_VSTV2, 0 x05, 0 x16, 0 x0D, 0 x11, 0 x7D, 0 x00,
0 x50);
dcs_write_cmd_seq(ctx, GOA_VCLK1, 0 x07, 0 x08, 0 x01, 0 x02, 0 x00, 0 x7D,
0 x00, 0 x85, 0 x08);
dcs_write_cmd_seq(ctx, GOA_VCLK2, 0 x03, 0 x04, 0 x05, 0 x06, 0 x00, 0 x7D,
0 x00, 0 x85, 0 x08);
dcs_write_seq(ctx, GOA_VCLK_OPT1, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00,
0 x00, 0 x00, 0 x00, 0 x00);
dcs_write_cmd_seq(ctx, GOA_BICLK1, 0 x07, 0 x08);
dcs_write_seq(ctx, 0 x2D, 0 x01);
dcs_write_seq(ctx, 0 x2F, 0 x02, 0 x00, 0 x40, 0 x05, 0 x08, 0 x54, 0 x7D,
0 x00);
dcs_write_cmd_seq(ctx, GOA_BICLK2, 0 x03, 0 x04, 0 x05, 0 x06, 0 x00);
dcs_write_seq(ctx, 0 x3D, 0 x40);
dcs_write_seq(ctx, 0 x3F, 0 x05, 0 x08, 0 x54, 0 x7D, 0 x00);
dcs_write_seq(ctx, GOA_BICLK3, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00,
0 x00, 0 x00, 0 x00, 0 x00, 0 x00);
dcs_write_seq(ctx, GOA_BICLK4, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00,
0 x00, 0 x00);
dcs_write_seq(ctx, 0 x58, 0 x00, 0 x00, 0 x00);
dcs_write_seq(ctx, GOA_BICLK_OPT1, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00);
dcs_write_seq(ctx, GOA_BICLK_OPT2, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00,
0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00);
dcs_write_seq(ctx, MCS_GOA_GPO1, 0 x00, 0 x00, 0 x00, 0 x00);
dcs_write_seq(ctx, MCS_GOA_GPO2, 0 x00, 0 x20, 0 x00);
dcs_write_seq(ctx, MCS_GOA_EQ, 0 x08, 0 x08, 0 x08, 0 x08, 0 x08, 0 x08,
0 x00, 0 x00);
dcs_write_seq(ctx, MCS_GOA_CLK_GALLON, 0 x00, 0 x00);
dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL0, 0 xBF, 0 x02, 0 x06, 0 x14, 0 x10,
0 x16, 0 x12, 0 x08, 0 x3F);
dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL1, 0 x3F, 0 x3F, 0 x3F, 0 x3F, 0 x0C,
0 x0A, 0 x0E, 0 x3F, 0 x3F, 0 x00);
dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL2, 0 x04, 0 x3F, 0 x3F, 0 x3F, 0 x3F,
0 x05, 0 x01, 0 x3F, 0 x3F, 0 x0F);
dcs_write_cmd_seq(ctx, MCS_GOA_FS_SEL3, 0 x0B, 0 x0D, 0 x3F, 0 x3F, 0 x3F,
0 x3F);
dcs_write_cmd_seq(ctx, 0 xA2, 0 x3F, 0 x09, 0 x13, 0 x17, 0 x11, 0 x15);
dcs_write_cmd_seq(ctx, 0 xA9, 0 x07, 0 x03, 0 x3F);
dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL0, 0 x3F, 0 x05, 0 x01, 0 x17, 0 x13,
0 x15, 0 x11, 0 x0F, 0 x3F);
dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL1, 0 x3F, 0 x3F, 0 x3F, 0 x3F, 0 x0B,
0 x0D, 0 x09, 0 x3F, 0 x3F, 0 x07);
dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL2, 0 x03, 0 x3F, 0 x3F, 0 x3F, 0 x3F,
0 x02, 0 x06, 0 x3F, 0 x3F, 0 x08);
dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL3, 0 x0C, 0 x0A, 0 x3F, 0 x3F, 0 x3F,
0 x3F, 0 x3F, 0 x0E, 0 x10, 0 x14);
dcs_write_cmd_seq(ctx, MCS_GOA_BS_SEL4, 0 x12, 0 x16, 0 x00, 0 x04, 0 x3F);
dcs_write_seq(ctx, 0 xDC, 0 x02);
dcs_write_seq(ctx, 0 xDE, 0 x12);
dcs_write_seq(ctx, MCS_CMD_MODE_SW, 0 x0E); /* No documentation */
dcs_write_seq(ctx, 0 x01, 0 x75);
dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD2_P3);
dcs_write_cmd_seq(ctx, MCS_GAMMA_VP, 0 x00, 0 x0C, 0 x12, 0 x0E, 0 x06,
0 x12, 0 x0E, 0 x0B, 0 x15, 0 x0B, 0 x10, 0 x07, 0 x0F,
0 x12, 0 x0C, 0 x00);
dcs_write_cmd_seq(ctx, MCS_GAMMA_VN, 0 x00, 0 x0C, 0 x12, 0 x0E, 0 x06,
0 x12, 0 x0E, 0 x0B, 0 x15, 0 x0B, 0 x10, 0 x07, 0 x0F,
0 x12, 0 x0C, 0 x00);
/* Exit CMD2 */
dcs_write_seq(ctx, MCS_CMD_MODE_SW, MCS_CMD1_UCS);
}
static int rm68200_unprepare(struct drm_panel *panel)
{
struct rm68200 *ctx = panel_to_rm68200(panel);
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
int ret;
ret = mipi_dsi_dcs_set_display_off(dsi);
if (ret)
dev_warn(panel->dev, "failed to set display off: %d\n" , ret);
ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
if (ret)
dev_warn(panel->dev, "failed to enter sleep mode: %d\n" , ret);
msleep(120 );
if (ctx->reset_gpio) {
gpiod_set_value_cansleep(ctx->reset_gpio, 1 );
msleep(20 );
}
regulator_disable(ctx->supply);
return 0 ;
}
static int rm68200_prepare(struct drm_panel *panel)
{
struct rm68200 *ctx = panel_to_rm68200(panel);
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
int ret;
ret = regulator_enable(ctx->supply);
if (ret < 0 ) {
dev_err(ctx->dev, "failed to enable supply: %d\n" , ret);
return ret;
}
if (ctx->reset_gpio) {
gpiod_set_value_cansleep(ctx->reset_gpio, 1 );
msleep(20 );
gpiod_set_value_cansleep(ctx->reset_gpio, 0 );
msleep(100 );
}
rm68200_init_sequence(ctx);
ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
if (ret)
return ret;
msleep(125 );
ret = mipi_dsi_dcs_set_display_on(dsi);
if (ret)
return ret;
msleep(20 );
return 0 ;
}
static int rm68200_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
struct drm_display_mode *mode;
mode = drm_mode_duplicate(connector->dev, &default_mode);
if (!mode) {
dev_err(panel->dev, "failed to add mode %ux%u@%u\n" ,
default_mode.hdisplay, default_mode.vdisplay,
drm_mode_vrefresh(&default_mode));
return -ENOMEM;
}
drm_mode_set_name(mode);
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
connector->display_info.width_mm = mode->width_mm;
connector->display_info.height_mm = mode->height_mm;
return 1 ;
}
static const struct drm_panel_funcs rm68200_drm_funcs = {
.unprepare = rm68200_unprepare,
.prepare = rm68200_prepare,
.get_modes = rm68200_get_modes,
};
static int rm68200_probe(struct mipi_dsi_device *dsi)
{
struct device *dev = &dsi->dev;
struct rm68200 *ctx;
int ret;
ctx = devm_drm_panel_alloc(dev, struct rm68200, panel,
&rm68200_drm_funcs,
DRM_MODE_CONNECTOR_DSI);
if (IS_ERR(ctx))
return PTR_ERR(ctx);
ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset" , GPIOD_OUT_LOW);
if (IS_ERR(ctx->reset_gpio)) {
ret = PTR_ERR(ctx->reset_gpio);
dev_err(dev, "cannot get reset GPIO: %d\n" , ret);
return ret;
}
ctx->supply = devm_regulator_get(dev, "power" );
if (IS_ERR(ctx->supply)) {
ret = PTR_ERR(ctx->supply);
if (ret != -EPROBE_DEFER)
dev_err(dev, "cannot get regulator: %d\n" , ret);
return ret;
}
mipi_dsi_set_drvdata(dsi, ctx);
ctx->dev = dev;
dsi->lanes = 2 ;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
ret = drm_panel_of_backlight(&ctx->panel);
if (ret)
return ret;
drm_panel_add(&ctx->panel);
ret = mipi_dsi_attach(dsi);
if (ret < 0 ) {
dev_err(dev, "mipi_dsi_attach() failed: %d\n" , ret);
drm_panel_remove(&ctx->panel);
return ret;
}
return 0 ;
}
static void rm68200_remove(struct mipi_dsi_device *dsi)
{
struct rm68200 *ctx = mipi_dsi_get_drvdata(dsi);
mipi_dsi_detach(dsi);
drm_panel_remove(&ctx->panel);
}
static const struct of_device_id raydium_rm68200_of_match[] = {
{ .compatible = "raydium,rm68200" },
{ }
};
MODULE_DEVICE_TABLE(of, raydium_rm68200_of_match);
static struct mipi_dsi_driver raydium_rm68200_driver = {
.probe = rm68200_probe,
.remove = rm68200_remove,
.driver = {
.name = "panel-raydium-rm68200" ,
.of_match_table = raydium_rm68200_of_match,
},
};
module_mipi_dsi_driver(raydium_rm68200_driver);
MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>" );
MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>" );
MODULE_DESCRIPTION("DRM Driver for Raydium RM68200 MIPI DSI panel" );
MODULE_LICENSE("GPL v2" );
Messung V0.5 in Prozent C=97 H=97 G=96
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland