/*
* MIPI Display Bus Interface (DBI) LCD controller support
*
* Copyright 2016 Noralf Trønnes
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dma-buf.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_rect.h>
#include <drm/tinydrm/mipi-dbi.h>
#include <drm/tinydrm/tinydrm-helpers.h>
#include <video/mipi_display.h>
#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
#define DCS_POWER_MODE_DISPLAY BIT(2)
#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE BIT(3)
#define DCS_POWER_MODE_SLEEP_MODE BIT(4)
#define DCS_POWER_MODE_PARTIAL_MODE BIT(5)
#define DCS_POWER_MODE_IDLE_MODE BIT(6)
#define DCS_POWER_MODE_RESERVED_MASK (BIT(0) | BIT(1) | BIT(7))
/**
* DOC: overview
*
* This library provides helpers for MIPI Display Bus Interface (DBI)
* compatible display controllers.
*
* Many controllers for tiny lcd displays are MIPI compliant and can use this
* library. If a controller uses registers 0x2A and 0x2B to set the area to
* update and uses register 0x2C to write to frame memory, it is most likely
* MIPI compliant.
*
* Only MIPI Type 1 displays are supported since a full frame memory is needed.
*
* There are 3 MIPI DBI implementation types:
*
* A. Motorola 6800 type parallel bus
*
* B. Intel 8080 type parallel bus
*
* C. SPI type with 3 options:
*
* 1. 9-bit with the Data/Command signal as the ninth bit
* 2. Same as above except it's sent as 16 bits
* 3. 8-bit with the Data/Command signal as a separate D/CX pin
*
* Currently mipi_dbi only supports Type C options 1 and 3 with
* mipi_dbi_spi_init().
*/
#define MIPI_DBI_DEBUG_COMMAND(cmd, data, len) \
({ \
if (!len) \
DRM_DEBUG_DRIVER("cmd=%02x\n", cmd); \
else if (len <= 32) \
DRM_DEBUG_DRIVER("cmd=%02x, par=%*ph\n", cmd, (int)len, data);\
else \
DRM_DEBUG_DRIVER("cmd=%02x, len=%zu\n", cmd, len); \
})
static const u8 mipi_dbi_dcs_read_commands[] = {
MIPI_DCS_GET_DISPLAY_ID,
MIPI_DCS_GET_RED_CHANNEL,
MIPI_DCS_GET_GREEN_CHANNEL,
MIPI_DCS_GET_BLUE_CHANNEL,
MIPI_DCS_GET_DISPLAY_STATUS,
MIPI_DCS_GET_POWER_MODE,
MIPI_DCS_GET_ADDRESS_MODE,
MIPI_DCS_GET_PIXEL_FORMAT,
MIPI_DCS_GET_DISPLAY_MODE,
MIPI_DCS_GET_SIGNAL_MODE,
MIPI_DCS_GET_DIAGNOSTIC_RESULT,
MIPI_DCS_READ_MEMORY_START,
MIPI_DCS_READ_MEMORY_CONTINUE,
MIPI_DCS_GET_SCANLINE,
MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
MIPI_DCS_GET_CONTROL_DISPLAY,
MIPI_DCS_GET_POWER_SAVE,
MIPI_DCS_GET_CABC_MIN_BRIGHTNESS,
MIPI_DCS_READ_DDB_START,
MIPI_DCS_READ_DDB_CONTINUE,
0, /* sentinel */
};
static bool mipi_dbi_command_is_read(struct mipi_dbi *mipi, u8 cmd)
{
unsigned int i;
if (!mipi->read_commands)
return false;
for (i = 0; i < 0xff; i++) {
if (!mipi->read_commands[i])
return false;
if (cmd == mipi->read_commands[i])
return true;
}
return false;
}
/**
* mipi_dbi_command_read - MIPI DCS read command
* @mipi: MIPI structure
* @cmd: Command
* @val: Value read
*
* Send MIPI DCS read command to the controller.
*
* Returns:
* Zero on success, negative error code on failure.
*/
int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val)
{
if (!mipi->read_commands)
return -EACCES;
if (!mipi_dbi_command_is_read(mipi, cmd))
return -EINVAL;
return mipi_dbi_command_buf(mipi, cmd, val, 1);
}
EXPORT_SYMBOL(mipi_dbi_command_read);
/**
* mipi_dbi_command_buf - MIPI DCS command with parameter(s) in an array
* @mipi: MIPI structure
* @cmd: Command
* @data: Parameter buffer
* @len: Buffer length
*
* Returns:
* Zero on success, negative error code on failure.
*/
int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len)
{
int ret;
mutex_lock(&mipi->cmdlock);
ret = mipi->command(mipi, cmd, data, len);
mutex_unlock(&mipi->cmdlock);
return ret;
}
EXPORT_SYMBOL(mipi_dbi_command_buf);
/**
* mipi_dbi_buf_copy - Copy a framebuffer, transforming it if necessary
* @dst: The destination buffer
* @fb: The source framebuffer
* @clip: Clipping rectangle of the area to be copied
* @swap: When true, swap MSB/LSB of 16-bit values
*
* Returns:
* Zero on success, negative error code on failure.
*/
int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
struct drm_rect *clip, bool swap)
{
struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
struct dma_buf_attachment *import_attach = cma_obj->base.import_attach;
struct drm_format_name_buf format_name;
void *src = cma_obj->vaddr;
int ret = 0;
if (import_attach) {
ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
DMA_FROM_DEVICE);
if (ret)
return ret;
}
switch (fb->format->format) {
case DRM_FORMAT_RGB565:
if (swap)
tinydrm_swab16(dst, src, fb, clip);
else
tinydrm_memcpy(dst, src, fb, clip);
break;
case DRM_FORMAT_XRGB8888:
tinydrm_xrgb8888_to_rgb565(dst, src, fb, clip, swap);
break;
default:
dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
drm_get_format_name(fb->format->format,
&format_name