David Lechner | eac99d4 | 2017-08-07 12:39:39 -0500 | [diff] [blame] | 1 | /* |
| 2 | * DRM driver for Sitronix ST7586 panels |
| 3 | * |
| 4 | * Copyright 2017 David Lechner <david@lechnology.com> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | */ |
| 11 | |
| 12 | #include <linux/delay.h> |
| 13 | #include <linux/dma-buf.h> |
| 14 | #include <linux/gpio/consumer.h> |
| 15 | #include <linux/module.h> |
| 16 | #include <linux/property.h> |
| 17 | #include <linux/spi/spi.h> |
| 18 | #include <video/mipi_display.h> |
| 19 | |
| 20 | #include <drm/tinydrm/mipi-dbi.h> |
| 21 | #include <drm/tinydrm/tinydrm-helpers.h> |
| 22 | |
| 23 | /* controller-specific commands */ |
| 24 | #define ST7586_DISP_MODE_GRAY 0x38 |
| 25 | #define ST7586_DISP_MODE_MONO 0x39 |
| 26 | #define ST7586_ENABLE_DDRAM 0x3a |
| 27 | #define ST7586_SET_DISP_DUTY 0xb0 |
| 28 | #define ST7586_SET_PART_DISP 0xb4 |
| 29 | #define ST7586_SET_NLINE_INV 0xb5 |
| 30 | #define ST7586_SET_VOP 0xc0 |
| 31 | #define ST7586_SET_BIAS_SYSTEM 0xc3 |
| 32 | #define ST7586_SET_BOOST_LEVEL 0xc4 |
| 33 | #define ST7586_SET_VOP_OFFSET 0xc7 |
| 34 | #define ST7586_ENABLE_ANALOG 0xd0 |
| 35 | #define ST7586_AUTO_READ_CTRL 0xd7 |
| 36 | #define ST7586_OTP_RW_CTRL 0xe0 |
| 37 | #define ST7586_OTP_CTRL_OUT 0xe1 |
| 38 | #define ST7586_OTP_READ 0xe3 |
| 39 | |
| 40 | #define ST7586_DISP_CTRL_MX BIT(6) |
| 41 | #define ST7586_DISP_CTRL_MY BIT(7) |
| 42 | |
| 43 | /* |
| 44 | * The ST7586 controller has an unusual pixel format where 2bpp grayscale is |
| 45 | * packed 3 pixels per byte with the first two pixels using 3 bits and the 3rd |
| 46 | * pixel using only 2 bits. |
| 47 | * |
| 48 | * | D7 | D6 | D5 || | || 2bpp | |
| 49 | * | (D4) | (D3) | (D2) || D1 | D0 || GRAY | |
| 50 | * +------+------+------++------+------++------+ |
| 51 | * | 1 | 1 | 1 || 1 | 1 || 0 0 | black |
| 52 | * | 1 | 0 | 0 || 1 | 0 || 0 1 | dark gray |
| 53 | * | 0 | 1 | 0 || 0 | 1 || 1 0 | light gray |
| 54 | * | 0 | 0 | 0 || 0 | 0 || 1 1 | white |
| 55 | */ |
| 56 | |
| 57 | static const u8 st7586_lookup[] = { 0x7, 0x4, 0x2, 0x0 }; |
| 58 | |
| 59 | static void st7586_xrgb8888_to_gray332(u8 *dst, void *vaddr, |
| 60 | struct drm_framebuffer *fb, |
| 61 | struct drm_clip_rect *clip) |
| 62 | { |
| 63 | size_t len = (clip->x2 - clip->x1) * (clip->y2 - clip->y1); |
| 64 | unsigned int x, y; |
| 65 | u8 *src, *buf, val; |
| 66 | |
| 67 | buf = kmalloc(len, GFP_KERNEL); |
| 68 | if (!buf) |
| 69 | return; |
| 70 | |
| 71 | tinydrm_xrgb8888_to_gray8(buf, vaddr, fb, clip); |
| 72 | src = buf; |
| 73 | |
| 74 | for (y = clip->y1; y < clip->y2; y++) { |
| 75 | for (x = clip->x1; x < clip->x2; x += 3) { |
| 76 | val = st7586_lookup[*src++ >> 6] << 5; |
| 77 | val |= st7586_lookup[*src++ >> 6] << 2; |
| 78 | val |= st7586_lookup[*src++ >> 6] >> 1; |
| 79 | *dst++ = val; |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | kfree(buf); |
| 84 | } |
| 85 | |
| 86 | static int st7586_buf_copy(void *dst, struct drm_framebuffer *fb, |
| 87 | struct drm_clip_rect *clip) |
| 88 | { |
| 89 | struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); |
| 90 | struct dma_buf_attachment *import_attach = cma_obj->base.import_attach; |
| 91 | void *src = cma_obj->vaddr; |
| 92 | int ret = 0; |
| 93 | |
| 94 | if (import_attach) { |
| 95 | ret = dma_buf_begin_cpu_access(import_attach->dmabuf, |
| 96 | DMA_FROM_DEVICE); |
| 97 | if (ret) |
| 98 | return ret; |
| 99 | } |
| 100 | |
| 101 | st7586_xrgb8888_to_gray332(dst, src, fb, clip); |
| 102 | |
| 103 | if (import_attach) |
| 104 | ret = dma_buf_end_cpu_access(import_attach->dmabuf, |
| 105 | DMA_FROM_DEVICE); |
| 106 | |
| 107 | return ret; |
| 108 | } |
| 109 | |
| 110 | static int st7586_fb_dirty(struct drm_framebuffer *fb, |
| 111 | struct drm_file *file_priv, unsigned int flags, |
| 112 | unsigned int color, struct drm_clip_rect *clips, |
| 113 | unsigned int num_clips) |
| 114 | { |
| 115 | struct tinydrm_device *tdev = fb->dev->dev_private; |
| 116 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); |
| 117 | struct drm_clip_rect clip; |
| 118 | int start, end; |
| 119 | int ret = 0; |
| 120 | |
| 121 | mutex_lock(&tdev->dirty_lock); |
| 122 | |
| 123 | if (!mipi->enabled) |
| 124 | goto out_unlock; |
| 125 | |
| 126 | /* fbdev can flush even when we're not interested */ |
| 127 | if (tdev->pipe.plane.fb != fb) |
| 128 | goto out_unlock; |
| 129 | |
| 130 | tinydrm_merge_clips(&clip, clips, num_clips, flags, fb->width, |
| 131 | fb->height); |
| 132 | |
| 133 | /* 3 pixels per byte, so grow clip to nearest multiple of 3 */ |
| 134 | clip.x1 = rounddown(clip.x1, 3); |
| 135 | clip.x2 = roundup(clip.x2, 3); |
| 136 | |
| 137 | DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id, |
| 138 | clip.x1, clip.x2, clip.y1, clip.y2); |
| 139 | |
| 140 | ret = st7586_buf_copy(mipi->tx_buf, fb, &clip); |
| 141 | if (ret) |
| 142 | goto out_unlock; |
| 143 | |
| 144 | /* Pixels are packed 3 per byte */ |
| 145 | start = clip.x1 / 3; |
| 146 | end = clip.x2 / 3; |
| 147 | |
| 148 | mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS, |
| 149 | (start >> 8) & 0xFF, start & 0xFF, |
| 150 | (end >> 8) & 0xFF, (end - 1) & 0xFF); |
| 151 | mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS, |
| 152 | (clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF, |
| 153 | (clip.y2 >> 8) & 0xFF, (clip.y2 - 1) & 0xFF); |
| 154 | |
| 155 | ret = mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START, |
| 156 | (u8 *)mipi->tx_buf, |
| 157 | (end - start) * (clip.y2 - clip.y1)); |
| 158 | |
| 159 | out_unlock: |
| 160 | mutex_unlock(&tdev->dirty_lock); |
| 161 | |
| 162 | if (ret) |
| 163 | dev_err_once(fb->dev->dev, "Failed to update display %d\n", |
| 164 | ret); |
| 165 | |
| 166 | return ret; |
| 167 | } |
| 168 | |
| 169 | static const struct drm_framebuffer_funcs st7586_fb_funcs = { |
| 170 | .destroy = drm_fb_cma_destroy, |
| 171 | .create_handle = drm_fb_cma_create_handle, |
| 172 | .dirty = st7586_fb_dirty, |
| 173 | }; |
| 174 | |
Colin Ian King | f3bbc90 | 2017-08-16 10:23:06 +0100 | [diff] [blame] | 175 | static void st7586_pipe_enable(struct drm_simple_display_pipe *pipe, |
| 176 | struct drm_crtc_state *crtc_state) |
David Lechner | eac99d4 | 2017-08-07 12:39:39 -0500 | [diff] [blame] | 177 | { |
| 178 | struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); |
| 179 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); |
| 180 | struct drm_framebuffer *fb = pipe->plane.fb; |
| 181 | struct device *dev = tdev->drm->dev; |
| 182 | int ret; |
| 183 | u8 addr_mode; |
| 184 | |
| 185 | DRM_DEBUG_KMS("\n"); |
| 186 | |
| 187 | mipi_dbi_hw_reset(mipi); |
| 188 | ret = mipi_dbi_command(mipi, ST7586_AUTO_READ_CTRL, 0x9f); |
| 189 | if (ret) { |
| 190 | dev_err(dev, "Error sending command %d\n", ret); |
| 191 | return; |
| 192 | } |
| 193 | |
| 194 | mipi_dbi_command(mipi, ST7586_OTP_RW_CTRL, 0x00); |
| 195 | |
| 196 | msleep(10); |
| 197 | |
| 198 | mipi_dbi_command(mipi, ST7586_OTP_READ); |
| 199 | |
| 200 | msleep(20); |
| 201 | |
| 202 | mipi_dbi_command(mipi, ST7586_OTP_CTRL_OUT); |
| 203 | mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE); |
| 204 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF); |
| 205 | |
| 206 | msleep(50); |
| 207 | |
| 208 | mipi_dbi_command(mipi, ST7586_SET_VOP_OFFSET, 0x00); |
| 209 | mipi_dbi_command(mipi, ST7586_SET_VOP, 0xe3, 0x00); |
| 210 | mipi_dbi_command(mipi, ST7586_SET_BIAS_SYSTEM, 0x02); |
| 211 | mipi_dbi_command(mipi, ST7586_SET_BOOST_LEVEL, 0x04); |
| 212 | mipi_dbi_command(mipi, ST7586_ENABLE_ANALOG, 0x1d); |
| 213 | mipi_dbi_command(mipi, ST7586_SET_NLINE_INV, 0x00); |
| 214 | mipi_dbi_command(mipi, ST7586_DISP_MODE_GRAY); |
| 215 | mipi_dbi_command(mipi, ST7586_ENABLE_DDRAM, 0x02); |
| 216 | |
| 217 | switch (mipi->rotation) { |
| 218 | default: |
| 219 | addr_mode = 0x00; |
| 220 | break; |
| 221 | case 90: |
| 222 | addr_mode = ST7586_DISP_CTRL_MY; |
| 223 | break; |
| 224 | case 180: |
| 225 | addr_mode = ST7586_DISP_CTRL_MX | ST7586_DISP_CTRL_MY; |
| 226 | break; |
| 227 | case 270: |
| 228 | addr_mode = ST7586_DISP_CTRL_MX; |
| 229 | break; |
| 230 | } |
| 231 | mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode); |
| 232 | |
| 233 | mipi_dbi_command(mipi, ST7586_SET_DISP_DUTY, 0x7f); |
| 234 | mipi_dbi_command(mipi, ST7586_SET_PART_DISP, 0xa0); |
| 235 | mipi_dbi_command(mipi, MIPI_DCS_SET_PARTIAL_AREA, 0x00, 0x00, 0x00, 0x77); |
| 236 | mipi_dbi_command(mipi, MIPI_DCS_EXIT_INVERT_MODE); |
| 237 | |
| 238 | msleep(100); |
| 239 | |
| 240 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON); |
| 241 | |
| 242 | mipi->enabled = true; |
| 243 | |
| 244 | if (fb) |
| 245 | fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0); |
| 246 | } |
| 247 | |
| 248 | static void st7586_pipe_disable(struct drm_simple_display_pipe *pipe) |
| 249 | { |
| 250 | struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); |
| 251 | struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev); |
| 252 | |
| 253 | DRM_DEBUG_KMS("\n"); |
| 254 | |
| 255 | if (!mipi->enabled) |
| 256 | return; |
| 257 | |
| 258 | mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF); |
| 259 | mipi->enabled = false; |
| 260 | } |
| 261 | |
| 262 | static const u32 st7586_formats[] = { |
| 263 | DRM_FORMAT_XRGB8888, |
| 264 | }; |
| 265 | |
| 266 | static int st7586_init(struct device *dev, struct mipi_dbi *mipi, |
| 267 | const struct drm_simple_display_pipe_funcs *pipe_funcs, |
| 268 | struct drm_driver *driver, const struct drm_display_mode *mode, |
| 269 | unsigned int rotation) |
| 270 | { |
| 271 | size_t bufsize = (mode->vdisplay + 2) / 3 * mode->hdisplay; |
| 272 | struct tinydrm_device *tdev = &mipi->tinydrm; |
| 273 | int ret; |
| 274 | |
| 275 | mutex_init(&mipi->cmdlock); |
| 276 | |
| 277 | mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL); |
| 278 | if (!mipi->tx_buf) |
| 279 | return -ENOMEM; |
| 280 | |
| 281 | ret = devm_tinydrm_init(dev, tdev, &st7586_fb_funcs, driver); |
| 282 | if (ret) |
| 283 | return ret; |
| 284 | |
| 285 | ret = tinydrm_display_pipe_init(tdev, pipe_funcs, |
| 286 | DRM_MODE_CONNECTOR_VIRTUAL, |
| 287 | st7586_formats, |
| 288 | ARRAY_SIZE(st7586_formats), |
| 289 | mode, rotation); |
| 290 | if (ret) |
| 291 | return ret; |
| 292 | |
| 293 | tdev->drm->mode_config.preferred_depth = 32; |
| 294 | mipi->rotation = rotation; |
| 295 | |
| 296 | drm_mode_config_reset(tdev->drm); |
| 297 | |
| 298 | DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n", |
| 299 | tdev->drm->mode_config.preferred_depth, rotation); |
| 300 | |
| 301 | return 0; |
| 302 | } |
| 303 | |
| 304 | static const struct drm_simple_display_pipe_funcs st7586_pipe_funcs = { |
| 305 | .enable = st7586_pipe_enable, |
| 306 | .disable = st7586_pipe_disable, |
| 307 | .update = tinydrm_display_pipe_update, |
| 308 | .prepare_fb = tinydrm_display_pipe_prepare_fb, |
| 309 | }; |
| 310 | |
| 311 | static const struct drm_display_mode st7586_mode = { |
| 312 | TINYDRM_MODE(178, 128, 37, 27), |
| 313 | }; |
| 314 | |
| 315 | DEFINE_DRM_GEM_CMA_FOPS(st7586_fops); |
| 316 | |
| 317 | static struct drm_driver st7586_driver = { |
| 318 | .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | |
| 319 | DRIVER_ATOMIC, |
| 320 | .fops = &st7586_fops, |
| 321 | TINYDRM_GEM_DRIVER_OPS, |
| 322 | .lastclose = tinydrm_lastclose, |
| 323 | .debugfs_init = mipi_dbi_debugfs_init, |
| 324 | .name = "st7586", |
| 325 | .desc = "Sitronix ST7586", |
| 326 | .date = "20170801", |
| 327 | .major = 1, |
| 328 | .minor = 0, |
| 329 | }; |
| 330 | |
| 331 | static const struct of_device_id st7586_of_match[] = { |
| 332 | { .compatible = "lego,ev3-lcd" }, |
| 333 | {}, |
| 334 | }; |
| 335 | MODULE_DEVICE_TABLE(of, st7586_of_match); |
| 336 | |
| 337 | static const struct spi_device_id st7586_id[] = { |
| 338 | { "ev3-lcd", 0 }, |
| 339 | { }, |
| 340 | }; |
| 341 | MODULE_DEVICE_TABLE(spi, st7586_id); |
| 342 | |
| 343 | static int st7586_probe(struct spi_device *spi) |
| 344 | { |
| 345 | struct device *dev = &spi->dev; |
| 346 | struct tinydrm_device *tdev; |
| 347 | struct mipi_dbi *mipi; |
| 348 | struct gpio_desc *a0; |
| 349 | u32 rotation = 0; |
| 350 | int ret; |
| 351 | |
| 352 | mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL); |
| 353 | if (!mipi) |
| 354 | return -ENOMEM; |
| 355 | |
| 356 | mipi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); |
| 357 | if (IS_ERR(mipi->reset)) { |
| 358 | dev_err(dev, "Failed to get gpio 'reset'\n"); |
| 359 | return PTR_ERR(mipi->reset); |
| 360 | } |
| 361 | |
| 362 | a0 = devm_gpiod_get(dev, "a0", GPIOD_OUT_LOW); |
| 363 | if (IS_ERR(a0)) { |
| 364 | dev_err(dev, "Failed to get gpio 'a0'\n"); |
| 365 | return PTR_ERR(a0); |
| 366 | } |
| 367 | |
| 368 | device_property_read_u32(dev, "rotation", &rotation); |
| 369 | |
| 370 | ret = mipi_dbi_spi_init(spi, mipi, a0); |
| 371 | if (ret) |
| 372 | return ret; |
| 373 | |
| 374 | /* Cannot read from this controller via SPI */ |
| 375 | mipi->read_commands = NULL; |
| 376 | |
| 377 | /* |
| 378 | * we are using 8-bit data, so we are not actually swapping anything, |
| 379 | * but setting mipi->swap_bytes makes mipi_dbi_typec3_command() do the |
| 380 | * right thing and not use 16-bit transfers (which results in swapped |
| 381 | * bytes on little-endian systems and causes out of order data to be |
| 382 | * sent to the display). |
| 383 | */ |
| 384 | mipi->swap_bytes = true; |
| 385 | |
| 386 | ret = st7586_init(&spi->dev, mipi, &st7586_pipe_funcs, &st7586_driver, |
| 387 | &st7586_mode, rotation); |
| 388 | if (ret) |
| 389 | return ret; |
| 390 | |
| 391 | tdev = &mipi->tinydrm; |
| 392 | |
| 393 | ret = devm_tinydrm_register(tdev); |
| 394 | if (ret) |
| 395 | return ret; |
| 396 | |
| 397 | spi_set_drvdata(spi, mipi); |
| 398 | |
| 399 | DRM_DEBUG_DRIVER("Initialized %s:%s @%uMHz on minor %d\n", |
| 400 | tdev->drm->driver->name, dev_name(dev), |
| 401 | spi->max_speed_hz / 1000000, |
| 402 | tdev->drm->primary->index); |
| 403 | |
| 404 | return 0; |
| 405 | } |
| 406 | |
| 407 | static void st7586_shutdown(struct spi_device *spi) |
| 408 | { |
| 409 | struct mipi_dbi *mipi = spi_get_drvdata(spi); |
| 410 | |
| 411 | tinydrm_shutdown(&mipi->tinydrm); |
| 412 | } |
| 413 | |
| 414 | static struct spi_driver st7586_spi_driver = { |
| 415 | .driver = { |
| 416 | .name = "st7586", |
| 417 | .owner = THIS_MODULE, |
| 418 | .of_match_table = st7586_of_match, |
| 419 | }, |
| 420 | .id_table = st7586_id, |
| 421 | .probe = st7586_probe, |
| 422 | .shutdown = st7586_shutdown, |
| 423 | }; |
| 424 | module_spi_driver(st7586_spi_driver); |
| 425 | |
| 426 | MODULE_DESCRIPTION("Sitronix ST7586 DRM driver"); |
| 427 | MODULE_AUTHOR("David Lechner <david@lechnology.com>"); |
| 428 | MODULE_LICENSE("GPL"); |