424 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * ld9040 AMOLED LCD drm_panel driver.4 *5 * Copyright (c) 2014 Samsung Electronics Co., Ltd6 * Derived from drivers/video/backlight/ld9040.c7 *8 * Andrzej Hajda <a.hajda@samsung.com>9*/10 11#include <linux/backlight.h>12#include <linux/delay.h>13#include <linux/gpio/consumer.h>14#include <linux/module.h>15#include <linux/of.h>16#include <linux/regulator/consumer.h>17#include <linux/spi/spi.h>18 19#include <video/mipi_display.h>20#include <video/of_videomode.h>21#include <video/videomode.h>22 23#include <drm/drm_modes.h>24#include <drm/drm_panel.h>25 26/* Manufacturer Command Set */27#define MCS_MANPWR 0xb028#define MCS_ELVSS_ON 0xb129#define MCS_USER_SETTING 0xf030#define MCS_DISPCTL 0xf231#define MCS_POWER_CTRL 0xf432#define MCS_GTCON 0xf733#define MCS_PANEL_CONDITION 0xf834#define MCS_GAMMA_SET1 0xf935#define MCS_GAMMA_CTRL 0xfb36 37/* array of gamma tables for gamma value 2.2 */38static u8 const ld9040_gammas[25][22] = {39 { 0xf9, 0x00, 0x13, 0xb2, 0xba, 0xd2, 0x00, 0x30, 0x00, 0xaf, 0xc0,40 0xb8, 0xcd, 0x00, 0x3d, 0x00, 0xa8, 0xb8, 0xb7, 0xcd, 0x00, 0x44 },41 { 0xf9, 0x00, 0x13, 0xb9, 0xb9, 0xd0, 0x00, 0x3c, 0x00, 0xaf, 0xbf,42 0xb6, 0xcb, 0x00, 0x4b, 0x00, 0xa8, 0xb9, 0xb5, 0xcc, 0x00, 0x52 },43 { 0xf9, 0x00, 0x13, 0xba, 0xb9, 0xcd, 0x00, 0x41, 0x00, 0xb0, 0xbe,44 0xb5, 0xc9, 0x00, 0x51, 0x00, 0xa9, 0xb9, 0xb5, 0xca, 0x00, 0x57 },45 { 0xf9, 0x00, 0x13, 0xb9, 0xb8, 0xcd, 0x00, 0x46, 0x00, 0xb1, 0xbc,46 0xb5, 0xc8, 0x00, 0x56, 0x00, 0xaa, 0xb8, 0xb4, 0xc9, 0x00, 0x5d },47 { 0xf9, 0x00, 0x13, 0xba, 0xb8, 0xcb, 0x00, 0x4b, 0x00, 0xb3, 0xbc,48 0xb4, 0xc7, 0x00, 0x5c, 0x00, 0xac, 0xb8, 0xb4, 0xc8, 0x00, 0x62 },49 { 0xf9, 0x00, 0x13, 0xbb, 0xb7, 0xca, 0x00, 0x4f, 0x00, 0xb4, 0xbb,50 0xb3, 0xc7, 0x00, 0x60, 0x00, 0xad, 0xb8, 0xb4, 0xc7, 0x00, 0x67 },51 { 0xf9, 0x00, 0x47, 0xba, 0xb6, 0xca, 0x00, 0x53, 0x00, 0xb5, 0xbb,52 0xb3, 0xc6, 0x00, 0x65, 0x00, 0xae, 0xb8, 0xb3, 0xc7, 0x00, 0x6c },53 { 0xf9, 0x00, 0x71, 0xbb, 0xb5, 0xc8, 0x00, 0x57, 0x00, 0xb5, 0xbb,54 0xb0, 0xc5, 0x00, 0x6a, 0x00, 0xae, 0xb9, 0xb1, 0xc6, 0x00, 0x70 },55 { 0xf9, 0x00, 0x7b, 0xbb, 0xb4, 0xc8, 0x00, 0x5b, 0x00, 0xb5, 0xba,56 0xb1, 0xc4, 0x00, 0x6e, 0x00, 0xae, 0xb9, 0xb0, 0xc5, 0x00, 0x75 },57 { 0xf9, 0x00, 0x82, 0xba, 0xb4, 0xc7, 0x00, 0x5f, 0x00, 0xb5, 0xba,58 0xb0, 0xc3, 0x00, 0x72, 0x00, 0xae, 0xb8, 0xb0, 0xc3, 0x00, 0x7a },59 { 0xf9, 0x00, 0x89, 0xba, 0xb3, 0xc8, 0x00, 0x62, 0x00, 0xb6, 0xba,60 0xaf, 0xc3, 0x00, 0x76, 0x00, 0xaf, 0xb7, 0xae, 0xc4, 0x00, 0x7e },61 { 0xf9, 0x00, 0x8b, 0xb9, 0xb3, 0xc7, 0x00, 0x65, 0x00, 0xb7, 0xb8,62 0xaf, 0xc3, 0x00, 0x7a, 0x00, 0x80, 0xb6, 0xae, 0xc4, 0x00, 0x81 },63 { 0xf9, 0x00, 0x93, 0xba, 0xb3, 0xc5, 0x00, 0x69, 0x00, 0xb8, 0xb9,64 0xae, 0xc1, 0x00, 0x7f, 0x00, 0xb0, 0xb6, 0xae, 0xc3, 0x00, 0x85 },65 { 0xf9, 0x00, 0x97, 0xba, 0xb2, 0xc5, 0x00, 0x6c, 0x00, 0xb8, 0xb8,66 0xae, 0xc1, 0x00, 0x82, 0x00, 0xb0, 0xb6, 0xae, 0xc2, 0x00, 0x89 },67 { 0xf9, 0x00, 0x9a, 0xba, 0xb1, 0xc4, 0x00, 0x6f, 0x00, 0xb8, 0xb8,68 0xad, 0xc0, 0x00, 0x86, 0x00, 0xb0, 0xb7, 0xad, 0xc0, 0x00, 0x8d },69 { 0xf9, 0x00, 0x9c, 0xb9, 0xb0, 0xc4, 0x00, 0x72, 0x00, 0xb8, 0xb8,70 0xac, 0xbf, 0x00, 0x8a, 0x00, 0xb0, 0xb6, 0xac, 0xc0, 0x00, 0x91 },71 { 0xf9, 0x00, 0x9e, 0xba, 0xb0, 0xc2, 0x00, 0x75, 0x00, 0xb9, 0xb8,72 0xab, 0xbe, 0x00, 0x8e, 0x00, 0xb0, 0xb6, 0xac, 0xbf, 0x00, 0x94 },73 { 0xf9, 0x00, 0xa0, 0xb9, 0xaf, 0xc3, 0x00, 0x77, 0x00, 0xb9, 0xb7,74 0xab, 0xbe, 0x00, 0x90, 0x00, 0xb0, 0xb6, 0xab, 0xbf, 0x00, 0x97 },75 { 0xf9, 0x00, 0xa2, 0xb9, 0xaf, 0xc2, 0x00, 0x7a, 0x00, 0xb9, 0xb7,76 0xaa, 0xbd, 0x00, 0x94, 0x00, 0xb0, 0xb5, 0xab, 0xbf, 0x00, 0x9a },77 { 0xf9, 0x00, 0xa4, 0xb9, 0xaf, 0xc1, 0x00, 0x7d, 0x00, 0xb9, 0xb6,78 0xaa, 0xbb, 0x00, 0x97, 0x00, 0xb1, 0xb5, 0xaa, 0xbf, 0x00, 0x9d },79 { 0xf9, 0x00, 0xa4, 0xb8, 0xb0, 0xbf, 0x00, 0x80, 0x00, 0xb8, 0xb6,80 0xaa, 0xbc, 0x00, 0x9a, 0x00, 0xb0, 0xb5, 0xab, 0xbd, 0x00, 0xa0 },81 { 0xf9, 0x00, 0xa8, 0xb8, 0xae, 0xbe, 0x00, 0x84, 0x00, 0xb9, 0xb7,82 0xa8, 0xbc, 0x00, 0x9d, 0x00, 0xb2, 0xb5, 0xaa, 0xbc, 0x00, 0xa4 },83 { 0xf9, 0x00, 0xa9, 0xb6, 0xad, 0xbf, 0x00, 0x86, 0x00, 0xb8, 0xb5,84 0xa8, 0xbc, 0x00, 0xa0, 0x00, 0xb3, 0xb3, 0xa9, 0xbc, 0x00, 0xa7 },85 { 0xf9, 0x00, 0xa9, 0xb7, 0xae, 0xbd, 0x00, 0x89, 0x00, 0xb7, 0xb6,86 0xa8, 0xba, 0x00, 0xa4, 0x00, 0xb1, 0xb4, 0xaa, 0xbb, 0x00, 0xaa },87 { 0xf9, 0x00, 0xa7, 0xb4, 0xae, 0xbf, 0x00, 0x91, 0x00, 0xb2, 0xb4,88 0xaa, 0xbb, 0x00, 0xac, 0x00, 0xb3, 0xb1, 0xaa, 0xbc, 0x00, 0xb3 },89};90 91struct ld9040 {92 struct device *dev;93 struct drm_panel panel;94 95 struct regulator_bulk_data supplies[2];96 struct gpio_desc *reset_gpio;97 u32 power_on_delay;98 u32 reset_delay;99 struct videomode vm;100 u32 width_mm;101 u32 height_mm;102 103 int brightness;104 105 /* This field is tested by functions directly accessing bus before106 * transfer, transfer is skipped if it is set. In case of transfer107 * failure or unexpected response the field is set to error value.108 * Such construct allows to eliminate many checks in higher level109 * functions.110 */111 int error;112};113 114static inline struct ld9040 *panel_to_ld9040(struct drm_panel *panel)115{116 return container_of(panel, struct ld9040, panel);117}118 119static int ld9040_clear_error(struct ld9040 *ctx)120{121 int ret = ctx->error;122 123 ctx->error = 0;124 return ret;125}126 127static int ld9040_spi_write_word(struct ld9040 *ctx, u16 data)128{129 struct spi_device *spi = to_spi_device(ctx->dev);130 struct spi_transfer xfer = {131 .len = 2,132 .tx_buf = &data,133 };134 struct spi_message msg;135 136 spi_message_init(&msg);137 spi_message_add_tail(&xfer, &msg);138 139 return spi_sync(spi, &msg);140}141 142static void ld9040_dcs_write(struct ld9040 *ctx, const u8 *data, size_t len)143{144 int ret = 0;145 146 if (ctx->error < 0 || len == 0)147 return;148 149 dev_dbg(ctx->dev, "writing dcs seq: %*ph\n", (int)len, data);150 ret = ld9040_spi_write_word(ctx, *data);151 152 while (!ret && --len) {153 ++data;154 ret = ld9040_spi_write_word(ctx, *data | 0x100);155 }156 157 if (ret) {158 dev_err(ctx->dev, "error %d writing dcs seq: %*ph\n", ret,159 (int)len, data);160 ctx->error = ret;161 }162 163 usleep_range(300, 310);164}165 166#define ld9040_dcs_write_seq_static(ctx, seq...) \167({\168 static const u8 d[] = { seq };\169 ld9040_dcs_write(ctx, d, ARRAY_SIZE(d));\170})171 172static void ld9040_brightness_set(struct ld9040 *ctx)173{174 ld9040_dcs_write(ctx, ld9040_gammas[ctx->brightness],175 ARRAY_SIZE(ld9040_gammas[ctx->brightness]));176 177 ld9040_dcs_write_seq_static(ctx, MCS_GAMMA_CTRL, 0x02, 0x5a);178}179 180static void ld9040_init(struct ld9040 *ctx)181{182 ld9040_dcs_write_seq_static(ctx, MCS_USER_SETTING, 0x5a, 0x5a);183 ld9040_dcs_write_seq_static(ctx, MCS_PANEL_CONDITION,184 0x05, 0x5e, 0x96, 0x6b, 0x7d, 0x0d, 0x3f, 0x00,185 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,186 0x07, 0x05, 0x1f, 0x1f, 0x1f, 0x00, 0x00);187 ld9040_dcs_write_seq_static(ctx, MCS_DISPCTL,188 0x02, 0x06, 0x0a, 0x10, 0x10);189 ld9040_dcs_write_seq_static(ctx, MCS_MANPWR, 0x04);190 ld9040_dcs_write_seq_static(ctx, MCS_POWER_CTRL,191 0x0a, 0x87, 0x25, 0x6a, 0x44, 0x02, 0x88);192 ld9040_dcs_write_seq_static(ctx, MCS_ELVSS_ON, 0x0f, 0x00, 0x16);193 ld9040_dcs_write_seq_static(ctx, MCS_GTCON, 0x09, 0x00, 0x00);194 ld9040_brightness_set(ctx);195 ld9040_dcs_write_seq_static(ctx, MIPI_DCS_EXIT_SLEEP_MODE);196 ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_ON);197}198 199static int ld9040_power_on(struct ld9040 *ctx)200{201 int ret;202 203 ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);204 if (ret < 0)205 return ret;206 207 msleep(ctx->power_on_delay);208 gpiod_set_value(ctx->reset_gpio, 0);209 msleep(ctx->reset_delay);210 gpiod_set_value(ctx->reset_gpio, 1);211 msleep(ctx->reset_delay);212 213 return 0;214}215 216static int ld9040_power_off(struct ld9040 *ctx)217{218 return regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);219}220 221static int ld9040_disable(struct drm_panel *panel)222{223 return 0;224}225 226static int ld9040_unprepare(struct drm_panel *panel)227{228 struct ld9040 *ctx = panel_to_ld9040(panel);229 230 msleep(120);231 ld9040_dcs_write_seq_static(ctx, MIPI_DCS_SET_DISPLAY_OFF);232 ld9040_dcs_write_seq_static(ctx, MIPI_DCS_ENTER_SLEEP_MODE);233 msleep(40);234 235 ld9040_clear_error(ctx);236 237 return ld9040_power_off(ctx);238}239 240static int ld9040_prepare(struct drm_panel *panel)241{242 struct ld9040 *ctx = panel_to_ld9040(panel);243 int ret;244 245 ret = ld9040_power_on(ctx);246 if (ret < 0)247 return ret;248 249 ld9040_init(ctx);250 251 ret = ld9040_clear_error(ctx);252 253 if (ret < 0)254 ld9040_unprepare(panel);255 256 return ret;257}258 259static int ld9040_enable(struct drm_panel *panel)260{261 return 0;262}263 264static int ld9040_get_modes(struct drm_panel *panel,265 struct drm_connector *connector)266{267 struct ld9040 *ctx = panel_to_ld9040(panel);268 struct drm_display_mode *mode;269 270 mode = drm_mode_create(connector->dev);271 if (!mode) {272 dev_err(panel->dev, "failed to create a new display mode\n");273 return 0;274 }275 276 drm_display_mode_from_videomode(&ctx->vm, mode);277 mode->width_mm = ctx->width_mm;278 mode->height_mm = ctx->height_mm;279 connector->display_info.width_mm = mode->width_mm;280 connector->display_info.height_mm = mode->height_mm;281 282 mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;283 drm_mode_probed_add(connector, mode);284 285 return 1;286}287 288static const struct drm_panel_funcs ld9040_drm_funcs = {289 .disable = ld9040_disable,290 .unprepare = ld9040_unprepare,291 .prepare = ld9040_prepare,292 .enable = ld9040_enable,293 .get_modes = ld9040_get_modes,294};295 296static int ld9040_parse_dt(struct ld9040 *ctx)297{298 struct device *dev = ctx->dev;299 struct device_node *np = dev->of_node;300 int ret;301 302 ret = of_get_videomode(np, &ctx->vm, 0);303 if (ret < 0)304 return ret;305 306 of_property_read_u32(np, "power-on-delay", &ctx->power_on_delay);307 of_property_read_u32(np, "reset-delay", &ctx->reset_delay);308 of_property_read_u32(np, "panel-width-mm", &ctx->width_mm);309 of_property_read_u32(np, "panel-height-mm", &ctx->height_mm);310 311 return 0;312}313 314static int ld9040_bl_update_status(struct backlight_device *dev)315{316 struct ld9040 *ctx = bl_get_data(dev);317 318 ctx->brightness = backlight_get_brightness(dev);319 ld9040_brightness_set(ctx);320 321 return 0;322}323 324static const struct backlight_ops ld9040_bl_ops = {325 .update_status = ld9040_bl_update_status,326};327 328static const struct backlight_properties ld9040_bl_props = {329 .type = BACKLIGHT_RAW,330 .scale = BACKLIGHT_SCALE_NON_LINEAR,331 .max_brightness = ARRAY_SIZE(ld9040_gammas) - 1,332 .brightness = ARRAY_SIZE(ld9040_gammas) - 1,333};334 335static int ld9040_probe(struct spi_device *spi)336{337 struct backlight_device *bldev;338 struct device *dev = &spi->dev;339 struct ld9040 *ctx;340 int ret;341 342 ctx = devm_kzalloc(dev, sizeof(struct ld9040), GFP_KERNEL);343 if (!ctx)344 return -ENOMEM;345 346 spi_set_drvdata(spi, ctx);347 348 ctx->dev = dev;349 ctx->brightness = ld9040_bl_props.brightness;350 351 ret = ld9040_parse_dt(ctx);352 if (ret < 0)353 return ret;354 355 ctx->supplies[0].supply = "vdd3";356 ctx->supplies[1].supply = "vci";357 ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies),358 ctx->supplies);359 if (ret < 0)360 return ret;361 362 ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);363 if (IS_ERR(ctx->reset_gpio)) {364 dev_err(dev, "cannot get reset-gpios %ld\n",365 PTR_ERR(ctx->reset_gpio));366 return PTR_ERR(ctx->reset_gpio);367 }368 369 spi->bits_per_word = 9;370 ret = spi_setup(spi);371 if (ret < 0) {372 dev_err(dev, "spi setup failed.\n");373 return ret;374 }375 376 drm_panel_init(&ctx->panel, dev, &ld9040_drm_funcs,377 DRM_MODE_CONNECTOR_DPI);378 379 bldev = devm_backlight_device_register(dev, dev_name(dev), dev,380 ctx, &ld9040_bl_ops,381 &ld9040_bl_props);382 if (IS_ERR(bldev))383 return PTR_ERR(bldev);384 385 drm_panel_add(&ctx->panel);386 387 return 0;388}389 390static void ld9040_remove(struct spi_device *spi)391{392 struct ld9040 *ctx = spi_get_drvdata(spi);393 394 ld9040_power_off(ctx);395 drm_panel_remove(&ctx->panel);396}397 398static const struct of_device_id ld9040_of_match[] = {399 { .compatible = "samsung,ld9040" },400 { }401};402MODULE_DEVICE_TABLE(of, ld9040_of_match);403 404static const struct spi_device_id ld9040_ids[] = {405 { "ld9040", },406 { /* sentinel */ }407};408MODULE_DEVICE_TABLE(spi, ld9040_ids);409 410static struct spi_driver ld9040_driver = {411 .probe = ld9040_probe,412 .remove = ld9040_remove,413 .id_table = ld9040_ids,414 .driver = {415 .name = "panel-samsung-ld9040",416 .of_match_table = ld9040_of_match,417 },418};419module_spi_driver(ld9040_driver);420 421MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");422MODULE_DESCRIPTION("ld9040 LCD Driver");423MODULE_LICENSE("GPL v2");424