// SPDX-License-Identifier: GPL-2.0-only
/*
* ld9040 AMOLED LCD drm_panel driver.
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd
* Derived from drivers/video/backlight/ld9040.c
*
* Andrzej Hajda <a.hajda@samsung.com>
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <video/mipi_display.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
/* Manufacturer Command Set */
#define MCS_MANPWR 0 xb0
#define MCS_ELVSS_ON 0 xb1
#define MCS_USER_SETTING 0 xf0
#define MCS_DISPCTL 0 xf2
#define MCS_POWER_CTRL 0 xf4
#define MCS_GTCON 0 xf7
#define MCS_PANEL_CONDITION 0 xf8
#define MCS_GAMMA_SET1 0 xf9
#define MCS_GAMMA_CTRL 0 xfb
/* array of gamma tables for gamma value 2.2 */
static u8 const ld9040_gammas[25 ][22 ] = {
{ 0 xf9, 0 x00, 0 x13, 0 xb2, 0 xba, 0 xd2, 0 x00, 0 x30, 0 x00, 0 xaf, 0 xc0,
0 xb8, 0 xcd, 0 x00, 0 x3d, 0 x00, 0 xa8, 0 xb8, 0 xb7, 0 xcd, 0 x00, 0 x44 },
{ 0 xf9, 0 x00, 0 x13, 0 xb9, 0 xb9, 0 xd0, 0 x00, 0 x3c, 0 x00, 0 xaf, 0 xbf,
0 xb6, 0 xcb, 0 x00, 0 x4b, 0 x00, 0 xa8, 0 xb9, 0 xb5, 0 xcc, 0 x00, 0 x52 },
{ 0 xf9, 0 x00, 0 x13, 0 xba, 0 xb9, 0 xcd, 0 x00, 0 x41, 0 x00, 0 xb0, 0 xbe,
0 xb5, 0 xc9, 0 x00, 0 x51, 0 x00, 0 xa9, 0 xb9, 0 xb5, 0 xca, 0 x00, 0 x57 },
{ 0 xf9, 0 x00, 0 x13, 0 xb9, 0 xb8, 0 xcd, 0 x00, 0 x46, 0 x00, 0 xb1, 0 xbc,
0 xb5, 0 xc8, 0 x00, 0 x56, 0 x00, 0 xaa, 0 xb8, 0 xb4, 0 xc9, 0 x00, 0 x5d },
{ 0 xf9, 0 x00, 0 x13, 0 xba, 0 xb8, 0 xcb, 0 x00, 0 x4b, 0 x00, 0 xb3, 0 xbc,
0 xb4, 0 xc7, 0 x00, 0 x5c, 0 x00, 0 xac, 0 xb8, 0 xb4, 0 xc8, 0 x00, 0 x62 },
{ 0 xf9, 0 x00, 0 x13, 0 xbb, 0 xb7, 0 xca, 0 x00, 0 x4f, 0 x00, 0 xb4, 0 xbb,
0 xb3, 0 xc7, 0 x00, 0 x60, 0 x00, 0 xad, 0 xb8, 0 xb4, 0 xc7, 0 x00, 0 x67 },
{ 0 xf9, 0 x00, 0 x47, 0 xba, 0 xb6, 0 xca, 0 x00, 0 x53, 0 x00, 0 xb5, 0 xbb,
0 xb3, 0 xc6, 0 x00, 0 x65, 0 x00, 0 xae, 0 xb8, 0 xb3, 0 xc7, 0 x00, 0 x6c },
{ 0 xf9, 0 x00, 0 x71, 0 xbb, 0 xb5, 0 xc8, 0 x00, 0 x57, 0 x00, 0 xb5, 0 xbb,
0 xb0, 0 xc5, 0 x00, 0 x6a, 0 x00, 0 xae, 0 xb9, 0 xb1, 0 xc6, 0 x00, 0 x70 },
{ 0 xf9, 0 x00, 0 x7b, 0 xbb, 0 xb4, 0 xc8, 0 x00, 0 x5b, 0 x00, 0 xb5, 0 xba,
0 xb1, 0 xc4, 0 x00, 0 x6e, 0 x00, 0 xae, 0 xb9, 0 xb0, 0 xc5, 0 x00, 0 x75 },
{ 0 xf9, 0 x00, 0 x82, 0 xba, 0 xb4, 0 xc7, 0 x00, 0 x5f, 0 x00, 0 xb5, 0 xba,
0 xb0, 0 xc3, 0 x00, 0 x72, 0 x00, 0 xae, 0 xb8, 0 xb0, 0 xc3, 0 x00, 0 x7a },
{ 0 xf9, 0 x00, 0 x89, 0 xba, 0 xb3, 0 xc8, 0 x00, 0 x62, 0 x00, 0 xb6, 0 xba,
0 xaf, 0 xc3, 0 x00, 0 x76, 0 x00, 0 xaf, 0 xb7, 0 xae, 0 xc4, 0 x00, 0 x7e },
{ 0 xf9, 0 x00, 0 x8b, 0 xb9, 0 xb3, 0 xc7, 0 x00, 0 x65, 0 x00, 0 xb7, 0 xb8,
0 xaf, 0 xc3, 0 x00, 0 x7a, 0 x00, 0 x80, 0 xb6, 0 xae, 0 xc4, 0 x00, 0 x81 },
{ 0 xf9, 0 x00, 0 x93, 0 xba, 0 xb3, 0 xc5, 0 x00, 0 x69, 0 x00, 0 xb8, 0 xb9,
0 xae, 0 xc1, 0 x00, 0 x7f, 0 x00, 0 xb0, 0 xb6, 0 xae, 0 xc3, 0 x00, 0 x85 },
{ 0 xf9, 0 x00, 0 x97, 0 xba, 0 xb2, 0 xc5, 0 x00, 0 x6c, 0 x00, 0 xb8, 0 xb8,
0 xae, 0 xc1, 0 x00, 0 x82, 0 x00, 0 xb0, 0 xb6, 0 xae, 0 xc2, 0 x00, 0 x89 },
{ 0 xf9, 0 x00, 0 x9a, 0 xba, 0 xb1, 0 xc4, 0 x00, 0 x6f, 0 x00, 0 xb8, 0 xb8,
0 xad, 0 xc0, 0 x00, 0 x86, 0 x00, 0 xb0, 0 xb7, 0 xad, 0 xc0, 0 x00, 0 x8d },
{ 0 xf9, 0 x00, 0 x9c, 0 xb9, 0 xb0, 0 xc4, 0 x00, 0 x72, 0 x00, 0 xb8, 0 xb8,
0 xac, 0 xbf, 0 x00, 0 x8a, 0 x00, 0 xb0, 0 xb6, 0 xac, 0 xc0, 0 x00, 0 x91 },
{ 0 xf9, 0 x00, 0 x9e, 0 xba, 0 xb0, 0 xc2, 0 x00, 0 x75, 0 x00, 0 xb9, 0 xb8,
0 xab, 0 xbe, 0 x00, 0 x8e, 0 x00, 0 xb0, 0 xb6, 0 xac, 0 xbf, 0 x00, 0 x94 },
{ 0 xf9, 0 x00, 0 xa0, 0 xb9, 0 xaf, 0 xc3, 0 x00, 0 x77, 0 x00, 0 xb9, 0 xb7,
0 xab, 0 xbe, 0 x00, 0 x90, 0 x00, 0 xb0, 0 xb6, 0 xab, 0 xbf, 0 x00, 0 x97 },
{ 0 xf9, 0 x00, 0 xa2, 0 xb9, 0 xaf, 0 xc2, 0 x00, 0 x7a, 0 x00, 0 xb9, 0 xb7,
0 xaa, 0 xbd, 0 x00, 0 x94, 0 x00, 0 xb0, 0 xb5, 0 xab, 0 xbf, 0 x00, 0 x9a },
{ 0 xf9, 0 x00, 0 xa4, 0 xb9, 0 xaf, 0 xc1, 0 x00, 0 x7d, 0 x00, 0 xb9, 0 xb6,
0 xaa, 0 xbb, 0 x00, 0 x97, 0 x00, 0 xb1, 0 xb5, 0 xaa, 0 xbf, 0 x00, 0 x9d },
{ 0 xf9, 0 x00, 0 xa4, 0 xb8, 0 xb0, 0 xbf, 0 x00, 0 x80, 0 x00, 0 xb8, 0 xb6,
0 xaa, 0 xbc, 0 x00, 0 x9a, 0 x00, 0 xb0, 0 xb5, 0 xab, 0 xbd, 0 x00, 0 xa0 },
{ 0 xf9, 0 x00, 0 xa8, 0 xb8, 0 xae, 0 xbe, 0 x00, 0 x84, 0 x00, 0 xb9, 0 xb7,
0 xa8, 0 xbc, 0 x00, 0 x9d, 0 x00, 0 xb2, 0 xb5, 0 xaa, 0 xbc, 0 x00, 0 xa4 },
{ 0 xf9, 0 x00, 0 xa9, 0 xb6, 0 xad, 0 xbf, 0 x00, 0 x86, 0 x00, 0 xb8, 0 xb5,
0 xa8, 0 xbc, 0 x00, 0 xa0, 0 x00, 0 xb3, 0 xb3, 0 xa9, 0 xbc, 0 x00, 0 xa7 },
{ 0 xf9, 0 x00, 0 xa9, 0 xb7, 0 xae, 0 xbd, 0 x00, 0 x89, 0 x00, 0 xb7, 0 xb6,
0 xa8, 0 xba, 0 x00, 0 xa4, 0 x00, 0 xb1, 0 xb4, 0 xaa, 0 xbb, 0 x00, 0 xaa },
{ 0 xf9, 0 x00, 0 xa7, 0 xb4, 0 xae, 0 xbf, 0 x00, 0 x91, 0 x00, 0 xb2, 0 xb4,
0 xaa, 0 xbb, 0 x00, 0 xac, 0 x00, 0 xb3, 0 xb1, 0 xaa, 0 xbc, 0 x00, 0 xb3 },
};
struct ld9040 {
struct device *dev;
struct drm_panel panel;
struct regulator_bulk_data supplies[2 ];
struct gpio_desc *reset_gpio;
u32 power_on_delay;
u32 reset_delay;
struct videomode vm;
u32 width_mm;
u32 height_mm;
int brightness;
/* This field is tested by functions directly accessing bus before
* transfer, transfer is skipped if it is set. In case of transfer
* failure or unexpected response the field is set to error value.
* Such construct allows to eliminate many checks in higher level
* functions.
*/
int error;
};
static inline struct ld9040 *panel_to_ld9040(struct drm_panel *panel)
{
return container_of(panel, struct ld9040, panel);
}
static int ld9040_clear_error(struct ld9040 *ctx)
{
int ret = ctx->error;
ctx->error = 0 ;
return ret;
}
static int ld9040_spi_write_word(struct ld9040 *ctx, u16 data)
{
struct spi_device *spi = to_spi_device(ctx->dev);
struct spi_transfer xfer = {
.len = 2 ,
.tx_buf = &data,
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(spi, &msg);
}
static void ld9040_dcs_write(struct ld9040 *ctx, const u8 *data, size_t len)
{
int ret = 0 ;
if (ctx->error < 0 || len == 0 )
return ;
dev_dbg(ctx->dev, "writing dcs seq: %*ph\n" , (int )len, data);
ret = ld9040_spi_write_word(ctx, *data);
while (!ret && --len) {
++data;
ret = ld9040_spi_write_word(ctx, *data | 0 x100);
}
if (ret) {
dev_err(ctx->dev, "error %d writing dcs seq: %*ph\n" , ret,
(int )len, data);
ctx->error = ret;
}
usleep_range(300 , 310 );
}
#define ld9040_dcs_write_seq_static(ctx, seq...) \
({\
static const u8 d[] = { seq };\
ld9040_dcs_write(ctx, d, ARRAY_SIZE(d));\
})
static void ld9040_brightness_set(struct ld9040 *ctx)
{
ld9040_dcs_write(ctx, ld9040_gammas[ctx->brightness],
ARRAY_SIZE(ld9040_gammas[ctx->brightness]));
ld9040_dcs_write_seq_static(ctx, MCS_GAMMA_CTRL, 0 x02, 0 x5a);
}
static void ld9040_init(struct ld9040 *ctx)
{
ld9040_dcs_write_seq_static(ctx, MCS_USER_SETTING, 0 x5a, 0 x5a);
ld9040_dcs_write_seq_static(ctx, MCS_PANEL_CONDITION,
0 x05, 0 x5e, 0 x96, 0 x6b, 0 x7d, 0 x0d, 0 x3f, 0 x00,
0 x00, 0 x32, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00,
0 x07, 0 x05, 0 x1f, 0 x1f, 0 x1f, 0 x00, 0 x00);
ld9040_dcs_write_seq_static(ctx, MCS_DISPCTL,
0 x02, 0 x06, 0 x0a, 0 x10, 0 x10);
ld9040_dcs_write_seq_static(ctx, MCS_MANPWR, 0 x04);
ld9040_dcs_write_seq_static(ctx, MCS_POWER_CTRL,
0 x0a, 0 x87, 0 x25, 0 x6a, 0 x44, 0 x02, 0 x88);
ld9040_dcs_write_seq_static(ctx, MCS_ELVSS_ON, 0 x0f, 0 x00, 0 x16);
ld9040_dcs_write_seq_static(ctx, MCS_GTCON, 0 x09, 0 x00, 0 x00);
ld9040_brightness_set(ctx);
ld9040_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);
ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);
}
static int ld9040_power_on(struct ld9040 *ctx)
{
int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
if (ret < 0 )
return ret;
msleep(ctx->power_on_delay);
gpiod_set_value(ctx->reset_gpio, 0 );
msleep(ctx->reset_delay);
gpiod_set_value(ctx->reset_gpio, 1 );
msleep(ctx->reset_delay);
return 0 ;
}
static int ld9040_power_off(struct ld9040 *ctx)
{
return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
}
static int ld9040_disable(struct drm_panel *panel)
{
return 0 ;
}
static int ld9040_unprepare(struct drm_panel *panel)
{
struct ld9040 *ctx = panel_to_ld9040(panel);
msleep(120 );
ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);
ld9040_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);
msleep(40 );
ld9040_clear_error(ctx);
return ld9040_power_off(ctx);
}
static int ld9040_prepare(struct drm_panel *panel)
{
struct ld9040 *ctx = panel_to_ld9040(panel);
int ret;
ret = ld9040_power_on(ctx);
if (ret < 0 )
return ret;
ld9040_init(ctx);
ret = ld9040_clear_error(ctx);
if (ret < 0 )
ld9040_unprepare(panel);
return ret;
}
static int ld9040_enable(struct drm_panel *panel)
{
return 0 ;
}
static int ld9040_get_modes(struct drm_panel *panel,
struct drm_connector *connector)
{
struct ld9040 *ctx = panel_to_ld9040(panel);
struct drm_display_mode *mode;
mode = drm_mode_create(connector->dev);
if (!mode) {
dev_err(panel->dev, "failed to create a new display mode\n" );
return 0 ;
}
drm_display_mode_from_videomode(&ctx->vm, mode);
mode->width_mm = ctx->width_mm;
mode->height_mm = ctx->height_mm;
connector->display_info.width_mm = mode->width_mm;
connector->display_info.height_mm = mode->height_mm;
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
return 1 ;
}
static const struct drm_panel_funcs ld9040_drm_funcs = {
.disable = ld9040_disable,
.unprepare = ld9040_unprepare,
.prepare = ld9040_prepare,
.enable = ld9040_enable,
.get_modes = ld9040_get_modes,
};
static int ld9040_parse_dt(struct ld9040 *ctx)
{
struct device *dev = ctx->dev;
struct device_node *np = dev->of_node;
int ret;
ret = of_get_videomode(np, &ctx->vm, 0 );
if (ret < 0 )
return ret;
of_property_read_u32(np, "power-on-delay" , &ctx->power_on_delay);
of_property_read_u32(np, "reset-delay" , &ctx->reset_delay);
of_property_read_u32(np, "panel-width-mm" , &ctx->width_mm);
of_property_read_u32(np, "panel-height-mm" , &ctx->height_mm);
return 0 ;
}
static int ld9040_bl_update_status(struct backlight_device *dev)
{
struct ld9040 *ctx = bl_get_data(dev);
ctx->brightness = backlight_get_brightness(dev);
ld9040_brightness_set(ctx);
return 0 ;
}
static const struct backlight_ops ld9040_bl_ops = {
.update_status = ld9040_bl_update_status,
};
static const struct backlight_properties ld9040_bl_props = {
.type = BACKLIGHT_RAW,
.scale = BACKLIGHT_SCALE_NON_LINEAR,
.max_brightness = ARRAY_SIZE(ld9040_gammas) - 1 ,
.brightness = ARRAY_SIZE(ld9040_gammas) - 1 ,
};
static int ld9040_probe(struct spi_device *spi)
{
struct backlight_device *bldev;
struct device *dev = &spi->dev;
struct ld9040 *ctx;
int ret;
ctx = devm_drm_panel_alloc(dev, struct ld9040, panel,
&ld9040_drm_funcs,
DRM_MODE_CONNECTOR_DPI);
if (IS_ERR(ctx))
return PTR_ERR(ctx);
spi_set_drvdata(spi, ctx);
ctx->dev = dev;
ctx->brightness = ld9040_bl_props.brightness;
ret = ld9040_parse_dt(ctx);
if (ret < 0 )
return ret;
ctx->supplies[0 ].supply = "vdd3" ;
ctx->supplies[1 ].supply = "vci" ;
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),
ctx->supplies);
if (ret < 0 )
return ret;
ctx->reset_gpio = devm_gpiod_get(dev, "reset" , GPIOD_OUT_HIGH);
if (IS_ERR(ctx->reset_gpio)) {
dev_err(dev, "cannot get reset-gpios %ld\n" ,
PTR_ERR(ctx->reset_gpio));
return PTR_ERR(ctx->reset_gpio);
}
spi->bits_per_word = 9 ;
ret = spi_setup(spi);
if (ret < 0 ) {
dev_err(dev, "spi setup failed.\n" );
return ret;
}
bldev = devm_backlight_device_register(dev, dev_name(dev), dev,
ctx, &ld9040_bl_ops,
&ld9040_bl_props);
if (IS_ERR(bldev))
return PTR_ERR(bldev);
drm_panel_add(&ctx->panel);
return 0 ;
}
static void ld9040_remove(struct spi_device *spi)
{
struct ld9040 *ctx = spi_get_drvdata(spi);
ld9040_power_off(ctx);
drm_panel_remove(&ctx->panel);
}
static const struct of_device_id ld9040_of_match[] = {
{ .compatible = "samsung,ld9040" },
{ }
};
MODULE_DEVICE_TABLE(of, ld9040_of_match);
static const struct spi_device_id ld9040_ids[] = {
{ "ld9040" , },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, ld9040_ids);
static struct spi_driver ld9040_driver = {
.probe = ld9040_probe,
.remove = ld9040_remove,
.id_table = ld9040_ids,
.driver = {
.name = "panel-samsung-ld9040" ,
.of_match_table = ld9040_of_match,
},
};
module_spi_driver(ld9040_driver);
MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>" );
MODULE_DESCRIPTION("ld9040 LCD Driver" );
MODULE_LICENSE("GPL v2" );
Messung V0.5 in Prozent C=95 H=97 G=95
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland