panel-lg-lb035q02.c (5471B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * LG.Philips LB035Q02 LCD Panel Driver 4 * 5 * Copyright (C) 2019 Texas Instruments Incorporated 6 * 7 * Based on the omapdrm-specific panel-lgphilips-lb035q02 driver 8 * 9 * Copyright (C) 2013 Texas Instruments Incorporated 10 * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 11 * 12 * Based on a driver by: Steve Sakoman <steve@sakoman.com> 13 */ 14 15#include <linux/gpio/consumer.h> 16#include <linux/module.h> 17#include <linux/spi/spi.h> 18 19#include <drm/drm_connector.h> 20#include <drm/drm_modes.h> 21#include <drm/drm_panel.h> 22 23struct lb035q02_device { 24 struct drm_panel panel; 25 26 struct spi_device *spi; 27 struct gpio_desc *enable_gpio; 28}; 29 30#define to_lb035q02_device(p) container_of(p, struct lb035q02_device, panel) 31 32static int lb035q02_write(struct lb035q02_device *lcd, u16 reg, u16 val) 33{ 34 struct spi_message msg; 35 struct spi_transfer index_xfer = { 36 .len = 3, 37 .cs_change = 1, 38 }; 39 struct spi_transfer value_xfer = { 40 .len = 3, 41 }; 42 u8 buffer[16]; 43 44 spi_message_init(&msg); 45 46 /* register index */ 47 buffer[0] = 0x70; 48 buffer[1] = 0x00; 49 buffer[2] = reg & 0x7f; 50 index_xfer.tx_buf = buffer; 51 spi_message_add_tail(&index_xfer, &msg); 52 53 /* register value */ 54 buffer[4] = 0x72; 55 buffer[5] = val >> 8; 56 buffer[6] = val; 57 value_xfer.tx_buf = buffer + 4; 58 spi_message_add_tail(&value_xfer, &msg); 59 60 return spi_sync(lcd->spi, &msg); 61} 62 63static int lb035q02_init(struct lb035q02_device *lcd) 64{ 65 /* Init sequence from page 28 of the lb035q02 spec. */ 66 static const struct { 67 u16 index; 68 u16 value; 69 } init_data[] = { 70 { 0x01, 0x6300 }, 71 { 0x02, 0x0200 }, 72 { 0x03, 0x0177 }, 73 { 0x04, 0x04c7 }, 74 { 0x05, 0xffc0 }, 75 { 0x06, 0xe806 }, 76 { 0x0a, 0x4008 }, 77 { 0x0b, 0x0000 }, 78 { 0x0d, 0x0030 }, 79 { 0x0e, 0x2800 }, 80 { 0x0f, 0x0000 }, 81 { 0x16, 0x9f80 }, 82 { 0x17, 0x0a0f }, 83 { 0x1e, 0x00c1 }, 84 { 0x30, 0x0300 }, 85 { 0x31, 0x0007 }, 86 { 0x32, 0x0000 }, 87 { 0x33, 0x0000 }, 88 { 0x34, 0x0707 }, 89 { 0x35, 0x0004 }, 90 { 0x36, 0x0302 }, 91 { 0x37, 0x0202 }, 92 { 0x3a, 0x0a0d }, 93 { 0x3b, 0x0806 }, 94 }; 95 96 unsigned int i; 97 int ret; 98 99 for (i = 0; i < ARRAY_SIZE(init_data); ++i) { 100 ret = lb035q02_write(lcd, init_data[i].index, 101 init_data[i].value); 102 if (ret < 0) 103 return ret; 104 } 105 106 return 0; 107} 108 109static int lb035q02_disable(struct drm_panel *panel) 110{ 111 struct lb035q02_device *lcd = to_lb035q02_device(panel); 112 113 gpiod_set_value_cansleep(lcd->enable_gpio, 0); 114 115 return 0; 116} 117 118static int lb035q02_enable(struct drm_panel *panel) 119{ 120 struct lb035q02_device *lcd = to_lb035q02_device(panel); 121 122 gpiod_set_value_cansleep(lcd->enable_gpio, 1); 123 124 return 0; 125} 126 127static const struct drm_display_mode lb035q02_mode = { 128 .clock = 6500, 129 .hdisplay = 320, 130 .hsync_start = 320 + 20, 131 .hsync_end = 320 + 20 + 2, 132 .htotal = 320 + 20 + 2 + 68, 133 .vdisplay = 240, 134 .vsync_start = 240 + 4, 135 .vsync_end = 240 + 4 + 2, 136 .vtotal = 240 + 4 + 2 + 18, 137 .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, 138 .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, 139 .width_mm = 70, 140 .height_mm = 53, 141}; 142 143static int lb035q02_get_modes(struct drm_panel *panel, 144 struct drm_connector *connector) 145{ 146 struct drm_display_mode *mode; 147 148 mode = drm_mode_duplicate(connector->dev, &lb035q02_mode); 149 if (!mode) 150 return -ENOMEM; 151 152 drm_mode_set_name(mode); 153 drm_mode_probed_add(connector, mode); 154 155 connector->display_info.width_mm = lb035q02_mode.width_mm; 156 connector->display_info.height_mm = lb035q02_mode.height_mm; 157 /* 158 * FIXME: According to the datasheet pixel data is sampled on the 159 * rising edge of the clock, but the code running on the Gumstix Overo 160 * Palo35 indicates sampling on the negative edge. This should be 161 * tested on a real device. 162 */ 163 connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH 164 | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE 165 | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; 166 167 return 1; 168} 169 170static const struct drm_panel_funcs lb035q02_funcs = { 171 .disable = lb035q02_disable, 172 .enable = lb035q02_enable, 173 .get_modes = lb035q02_get_modes, 174}; 175 176static int lb035q02_probe(struct spi_device *spi) 177{ 178 struct lb035q02_device *lcd; 179 int ret; 180 181 lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); 182 if (!lcd) 183 return -ENOMEM; 184 185 spi_set_drvdata(spi, lcd); 186 lcd->spi = spi; 187 188 lcd->enable_gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW); 189 if (IS_ERR(lcd->enable_gpio)) { 190 dev_err(&spi->dev, "failed to parse enable gpio\n"); 191 return PTR_ERR(lcd->enable_gpio); 192 } 193 194 ret = lb035q02_init(lcd); 195 if (ret < 0) 196 return ret; 197 198 drm_panel_init(&lcd->panel, &lcd->spi->dev, &lb035q02_funcs, 199 DRM_MODE_CONNECTOR_DPI); 200 201 drm_panel_add(&lcd->panel); 202 203 return 0; 204} 205 206static void lb035q02_remove(struct spi_device *spi) 207{ 208 struct lb035q02_device *lcd = spi_get_drvdata(spi); 209 210 drm_panel_remove(&lcd->panel); 211 drm_panel_disable(&lcd->panel); 212} 213 214static const struct of_device_id lb035q02_of_match[] = { 215 { .compatible = "lgphilips,lb035q02", }, 216 { /* sentinel */ }, 217}; 218 219MODULE_DEVICE_TABLE(of, lb035q02_of_match); 220 221static const struct spi_device_id lb035q02_ids[] = { 222 { "lb035q02", 0 }, 223 { /* sentinel */ } 224}; 225 226MODULE_DEVICE_TABLE(spi, lb035q02_ids); 227 228static struct spi_driver lb035q02_driver = { 229 .probe = lb035q02_probe, 230 .remove = lb035q02_remove, 231 .id_table = lb035q02_ids, 232 .driver = { 233 .name = "panel-lg-lb035q02", 234 .of_match_table = lb035q02_of_match, 235 }, 236}; 237 238module_spi_driver(lb035q02_driver); 239 240MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 241MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver"); 242MODULE_LICENSE("GPL");