Add support for MIPI DBI interfaced controllers. Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- drivers/gpu/drm/tinydrm/Kconfig | 3 + drivers/gpu/drm/tinydrm/Makefile | 3 + drivers/gpu/drm/tinydrm/mipi-dbi.c | 253 +++++++++++++++++++++++++++++++++++++ include/drm/tinydrm/mipi-dbi.h | 24 ++++ 4 files changed, 283 insertions(+) create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c create mode 100644 include/drm/tinydrm/mipi-dbi.h diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig index 739be06..8c41eee 100644 --- a/drivers/gpu/drm/tinydrm/Kconfig +++ b/drivers/gpu/drm/tinydrm/Kconfig @@ -10,4 +10,7 @@ menuconfig DRM_TINYDRM Choose this option if you have a tinydrm supported display. If M is selected the module will be called tinydrm. +config TINYDRM_MIPI_DBI + tristate + source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig" diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile index f4a92d9..35ba822 100644 --- a/drivers/gpu/drm/tinydrm/Makefile +++ b/drivers/gpu/drm/tinydrm/Makefile @@ -1,2 +1,5 @@ obj-$(CONFIG_DRM_TINYDRM) += core/ obj-$(CONFIG_LCDREG) += lcdreg/ + +# Controllers +obj-$(CONFIG_TINYDRM_MIPI_DBI) += mipi-dbi.o diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c new file mode 100644 index 0000000..1ddccb7 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c @@ -0,0 +1,253 @@ +/* + * 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 <drm/drm_gem_cma_helper.h> +#include <drm/tinydrm/lcdreg.h> +#include <drm/tinydrm/tinydrm.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/swab.h> +#include <video/mipi_display.h> + +#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)) + +/* TODO: Move common functions to a separate module */ +void tinydrm_xrgb8888_to_rgb565(u32 *src, u16 *dst, unsigned num_pixels, + bool swap_bytes) +{ + int i; + + for (i = 0; i < num_pixels; i++) { + *dst = ((*src & 0x00F80000) >> 8) | + ((*src & 0x0000FC00) >> 5) | + ((*src & 0x000000F8) >> 3); + if (swap_bytes) + *dst = swab16(*dst); + src++; + dst++; + } +} + +int tinydrm_update_rgb565_lcdreg(struct lcdreg *reg, u32 regnr, + struct drm_framebuffer *fb, void *vmem, + struct drm_clip_rect *clip) +{ + unsigned width = clip->x2 - clip->x1 + 1; + unsigned height = clip->y2 - clip->y1 + 1; + unsigned num_pixels = width * height; + struct lcdreg_transfer tr = { + .index = 1, + .width = 16, + .count = num_pixels + }; + bool byte_swap = false; + u16 *buf = NULL; + int ret; + + dev_dbg(reg->dev, "%s: x1=%u, x2=%u, y1=%u, y2=%u : width=%u, height=%u\n", + __func__, clip->x1, clip->x2, clip->y1, clip->y2, width, height); + dev_dbg_once(reg->dev, "pixel_format = %s, bpw = 0x%08x\n", + drm_get_format_name(fb->pixel_format), + reg->bits_per_word_mask); + + if (width != fb->width) { + dev_err(reg->dev, + "Only full width clips are supported: x1=%u, x2=%u\n", + clip->x1, clip->x2); + return -EINVAL; + } + + switch (fb->pixel_format) { + case DRM_FORMAT_RGB565: + vmem += clip->y1 * width * 2; + tr.buf = vmem; + break; + case DRM_FORMAT_XRGB8888: + vmem += clip->y1 * width * 4; + buf = kmalloc(num_pixels * sizeof(u16), GFP_KERNEL); + if (!buf) + return -ENOMEM; + +#if defined(__LITTLE_ENDIAN) + byte_swap = !lcdreg_bpw_supported(reg, 16); +#endif + tinydrm_xrgb8888_to_rgb565(vmem, buf, num_pixels, byte_swap); + tr.buf = buf; + if (byte_swap) { + tr.width = 8; + tr.count *= 2; + } + break; + default: + dev_err_once(reg->dev, "pixel_format '%s' is not supported\n", + drm_get_format_name(fb->pixel_format)); + return -EINVAL; + } + + ret = lcdreg_write(reg, regnr, &tr); + kfree(buf); + + return ret; +} + +/* TODO remove when the drm_clip_rect functions have a home */ +void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height); +void drm_clip_rect_merge(struct drm_clip_rect *dst, + struct drm_clip_rect *src, unsigned num_clips, + unsigned flags, u32 width, u32 height); + +static int mipi_dbi_dirtyfb(struct drm_framebuffer *fb, void *vmem, + unsigned flags, unsigned color, + struct drm_clip_rect *clips, unsigned num_clips) +{ + struct tinydrm_device *tdev = fb->dev->dev_private; + struct lcdreg *reg = tdev->lcdreg; + struct drm_clip_rect clip = { 0 }; + int ret; + + drm_clip_rect_merge(&clip, clips, num_clips, flags, + fb->width, fb->height); + drm_clip_rect_sanetize(&clip, fb->width, fb->height); + + dev_dbg(tdev->base->dev, "%s: vmem=%p, x1=%u, x2=%u, y1=%u, y2=%u\n", + __func__, vmem, clip.x1, clip.x2, clip.y1, clip.y2); + + /* Only full width is supported */ + clip.x1 = 0; + clip.x2 = fb->width - 1; + + lcdreg_writereg(reg, MIPI_DCS_SET_COLUMN_ADDRESS, + (clip.x1 >> 8) & 0xFF, clip.x1 & 0xFF, + (clip.x2 >> 8) & 0xFF, clip.x2 & 0xFF); + lcdreg_writereg(reg, MIPI_DCS_SET_PAGE_ADDRESS, + (clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF, + (clip.y2 >> 8) & 0xFF, clip.y2 & 0xFF); + + ret = tinydrm_update_rgb565_lcdreg(reg, MIPI_DCS_WRITE_MEMORY_START, + fb, vmem, &clip); + if (ret) + dev_err_once(tdev->base->dev, "Failed to update display %d\n", + ret); + + if (tdev->prepared && !tdev->enabled) + tinydrm_enable(tdev); + + return ret; +} + +int mipi_dbi_init(struct device *dev, struct tinydrm_device *tdev) +{ + tdev->lcdreg->def_width = 8; + tdev->dirtyfb = mipi_dbi_dirtyfb; + + return 0; +} +EXPORT_SYMBOL(mipi_dbi_init); + +/* Returns true if the display can be verified to be on */ +bool mipi_dbi_display_is_on(struct lcdreg *reg) +{ + u32 val; + + if (!lcdreg_is_readable(reg)) + return false; + + if (lcdreg_readreg_buf32(reg, MIPI_DCS_GET_POWER_MODE, &val, 1)) + return false; + + val &= ~DCS_POWER_MODE_RESERVED_MASK; + + if (val != (DCS_POWER_MODE_DISPLAY | + DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE)) + return false; + + DRM_DEBUG_DRIVER("Display is ON\n"); + + return true; +} +EXPORT_SYMBOL(mipi_dbi_display_is_on); + +void mipi_dbi_debug_dump_regs(struct lcdreg *reg) +{ + u32 val[4]; + int ret; + + if (!(lcdreg_is_readable(reg) && (drm_debug & DRM_UT_DRIVER))) + return; + + ret = lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_ID, val, 3); + if (ret) { + dev_warn(reg->dev, + "failed to read from controller: %d", ret); + return; + } + + DRM_DEBUG_DRIVER("Display ID (%02x): %02x %02x %02x\n", + MIPI_DCS_GET_DISPLAY_ID, val[0], val[1], val[2]); + + lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_STATUS, val, 4); + DRM_DEBUG_DRIVER("Display status (%02x): %02x %02x %02x %02x\n", + MIPI_DCS_GET_DISPLAY_STATUS, val[0], val[1], val[2], val[3]); + + lcdreg_readreg_buf32(reg, MIPI_DCS_GET_POWER_MODE, val, 1); + DRM_DEBUG_DRIVER("Power mode (%02x): %02x\n", + MIPI_DCS_GET_POWER_MODE, val[0]); + + lcdreg_readreg_buf32(reg, MIPI_DCS_GET_ADDRESS_MODE, val, 1); + DRM_DEBUG_DRIVER("Address mode (%02x): %02x\n", + MIPI_DCS_GET_ADDRESS_MODE, val[0]); + + lcdreg_readreg_buf32(reg, MIPI_DCS_GET_PIXEL_FORMAT, val, 1); + DRM_DEBUG_DRIVER("Pixel format (%02x): %02x\n", + MIPI_DCS_GET_PIXEL_FORMAT, val[0]); + + lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_MODE, val, 1); + DRM_DEBUG_DRIVER("Display mode (%02x): %02x\n", + MIPI_DCS_GET_DISPLAY_MODE, val[0]); + + lcdreg_readreg_buf32(reg, MIPI_DCS_GET_SIGNAL_MODE, val, 1); + DRM_DEBUG_DRIVER("Display signal mode (%02x): %02x\n", + MIPI_DCS_GET_SIGNAL_MODE, val[0]); + + lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DIAGNOSTIC_RESULT, val, 1); + DRM_DEBUG_DRIVER("Diagnostic result (%02x): %02x\n", + MIPI_DCS_GET_DIAGNOSTIC_RESULT, val[0]); +} +EXPORT_SYMBOL(mipi_dbi_debug_dump_regs); + +int mipi_dbi_panel_unprepare(struct drm_panel *panel) +{ + struct tinydrm_device *tdev = tinydrm_from_panel(panel); + struct lcdreg *reg = tdev->lcdreg; + + /* + * Only do this if we have turned off backlight because if it's on the + * display will in most cases turn all white when the pixels are + * turned off. + */ + if (tdev->backlight) { + lcdreg_writereg(reg, MIPI_DCS_SET_DISPLAY_OFF); + lcdreg_writereg(reg, MIPI_DCS_ENTER_SLEEP_MODE); + } + + if (tdev->regulator) + regulator_disable(tdev->regulator); + + return 0; +} +EXPORT_SYMBOL(mipi_dbi_panel_unprepare); + +MODULE_LICENSE("GPL"); diff --git a/include/drm/tinydrm/mipi-dbi.h b/include/drm/tinydrm/mipi-dbi.h new file mode 100644 index 0000000..108a73b --- /dev/null +++ b/include/drm/tinydrm/mipi-dbi.h @@ -0,0 +1,24 @@ +/* + * 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. + */ + +#ifndef __LINUX_MIPI_DBI_H +#define __LINUX_MIPI_DBI_H + +struct tinydrm_device; +struct drm_panel; +struct lcdreg; + +int mipi_dbi_init(struct device *dev, struct tinydrm_device *tdev); +bool mipi_dbi_display_is_on(struct lcdreg *reg); +void mipi_dbi_debug_dump_regs(struct lcdreg *reg); +int mipi_dbi_panel_unprepare(struct drm_panel *panel); + +#endif /* __LINUX_MIPI_DBI_H */ -- 2.2.2 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel