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 23 struct 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 32 static 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 63 static 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 109 static 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 118 static 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 127 static 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 .vrefresh = 60, 138 .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, 139 .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, 140 .width_mm = 70, 141 .height_mm = 53, 142 }; 143 144 static int lb035q02_get_modes(struct drm_panel *panel) 145 { 146 struct drm_connector *connector = panel->connector; 147 struct drm_display_mode *mode; 148 149 mode = drm_mode_duplicate(panel->drm, &lb035q02_mode); 150 if (!mode) 151 return -ENOMEM; 152 153 drm_mode_set_name(mode); 154 drm_mode_probed_add(connector, mode); 155 156 connector->display_info.width_mm = lb035q02_mode.width_mm; 157 connector->display_info.height_mm = lb035q02_mode.height_mm; 158 /* 159 * FIXME: According to the datasheet pixel data is sampled on the 160 * rising edge of the clock, but the code running on the Gumstix Overo 161 * Palo35 indicates sampling on the negative edge. This should be 162 * tested on a real device. 163 */ 164 connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH 165 | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE 166 | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; 167 168 return 1; 169 } 170 171 static const struct drm_panel_funcs lb035q02_funcs = { 172 .disable = lb035q02_disable, 173 .enable = lb035q02_enable, 174 .get_modes = lb035q02_get_modes, 175 }; 176 177 static int lb035q02_probe(struct spi_device *spi) 178 { 179 struct lb035q02_device *lcd; 180 int ret; 181 182 lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); 183 if (!lcd) 184 return -ENOMEM; 185 186 spi_set_drvdata(spi, lcd); 187 lcd->spi = spi; 188 189 lcd->enable_gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW); 190 if (IS_ERR(lcd->enable_gpio)) { 191 dev_err(&spi->dev, "failed to parse enable gpio\n"); 192 return PTR_ERR(lcd->enable_gpio); 193 } 194 195 ret = lb035q02_init(lcd); 196 if (ret < 0) 197 return ret; 198 199 drm_panel_init(&lcd->panel, &lcd->spi->dev, &lb035q02_funcs, 200 DRM_MODE_CONNECTOR_DPI); 201 202 return drm_panel_add(&lcd->panel); 203 } 204 205 static int lb035q02_remove(struct spi_device *spi) 206 { 207 struct lb035q02_device *lcd = spi_get_drvdata(spi); 208 209 drm_panel_remove(&lcd->panel); 210 drm_panel_disable(&lcd->panel); 211 212 return 0; 213 } 214 215 static const struct of_device_id lb035q02_of_match[] = { 216 { .compatible = "lgphilips,lb035q02", }, 217 { /* sentinel */ }, 218 }; 219 220 MODULE_DEVICE_TABLE(of, lb035q02_of_match); 221 222 static const struct spi_device_id lb035q02_ids[] = { 223 { "lb035q02", 0 }, 224 { /* sentinel */ } 225 }; 226 227 MODULE_DEVICE_TABLE(spi, lb035q02_ids); 228 229 static struct spi_driver lb035q02_driver = { 230 .probe = lb035q02_probe, 231 .remove = lb035q02_remove, 232 .id_table = lb035q02_ids, 233 .driver = { 234 .name = "panel-lg-lb035q02", 235 .of_match_table = lb035q02_of_match, 236 }, 237 }; 238 239 module_spi_driver(lb035q02_driver); 240 241 MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 242 MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver"); 243 MODULE_LICENSE("GPL"); 244