diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/gpu/drm/tinydrm/core | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/tinydrm/core')
-rw-r--r-- | drivers/gpu/drm/tinydrm/core/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 267 | ||||
-rw-r--r-- | drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c | 449 | ||||
-rw-r--r-- | drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c | 207 |
4 files changed, 926 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile new file mode 100644 index 000000000..fb221e6f8 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/Makefile @@ -0,0 +1,3 @@ +tinydrm-y := tinydrm-core.o tinydrm-pipe.o tinydrm-helpers.o + +obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c new file mode 100644 index 000000000..19c7f70ad --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) 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_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/tinydrm/tinydrm.h> +#include <linux/device.h> +#include <linux/dma-buf.h> + +/** + * DOC: overview + * + * This library provides driver helpers for very simple display hardware. + * + * It is based on &drm_simple_display_pipe coupled with a &drm_connector which + * has only one fixed &drm_display_mode. The framebuffers are backed by the + * cma helper and have support for framebuffer flushing (dirty). + * fbdev support is also included. + * + */ + +/** + * DOC: core + * + * The driver allocates &tinydrm_device, initializes it using + * devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init() + * and registers the DRM device using devm_tinydrm_register(). + */ + +/** + * tinydrm_gem_cma_prime_import_sg_table - Produce a CMA GEM object from + * another driver's scatter/gather table of pinned pages + * @drm: DRM device to import into + * @attach: DMA-BUF attachment + * @sgt: Scatter/gather table of pinned pages + * + * This function imports a scatter/gather table exported via DMA-BUF by + * another driver using drm_gem_cma_prime_import_sg_table(). It sets the + * kernel virtual address on the CMA object. Drivers should use this as their + * &drm_driver->gem_prime_import_sg_table callback if they need the virtual + * address. tinydrm_gem_cma_free_object() should be used in combination with + * this function. + * + * Returns: + * A pointer to a newly created GEM object or an ERR_PTR-encoded negative + * error code on failure. + */ +struct drm_gem_object * +tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + struct drm_gem_cma_object *cma_obj; + struct drm_gem_object *obj; + void *vaddr; + + vaddr = dma_buf_vmap(attach->dmabuf); + if (!vaddr) { + DRM_ERROR("Failed to vmap PRIME buffer\n"); + return ERR_PTR(-ENOMEM); + } + + obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt); + if (IS_ERR(obj)) { + dma_buf_vunmap(attach->dmabuf, vaddr); + return obj; + } + + cma_obj = to_drm_gem_cma_obj(obj); + cma_obj->vaddr = vaddr; + + return obj; +} +EXPORT_SYMBOL(tinydrm_gem_cma_prime_import_sg_table); + +/** + * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM + * object + * @gem_obj: GEM object to free + * + * This function frees the backing memory of the CMA GEM object, cleans up the + * GEM object state and frees the memory used to store the object itself using + * drm_gem_cma_free_object(). It also handles PRIME buffers which has the kernel + * virtual address set by tinydrm_gem_cma_prime_import_sg_table(). Drivers + * can use this as their &drm_driver->gem_free_object_unlocked callback. + */ +void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj) +{ + if (gem_obj->import_attach) { + struct drm_gem_cma_object *cma_obj; + + cma_obj = to_drm_gem_cma_obj(gem_obj); + dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr); + cma_obj->vaddr = NULL; + } + + drm_gem_cma_free_object(gem_obj); +} +EXPORT_SYMBOL_GPL(tinydrm_gem_cma_free_object); + +static struct drm_framebuffer * +tinydrm_fb_create(struct drm_device *drm, struct drm_file *file_priv, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct tinydrm_device *tdev = drm->dev_private; + + return drm_gem_fb_create_with_funcs(drm, file_priv, mode_cmd, + tdev->fb_funcs); +} + +static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = { + .fb_create = tinydrm_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev, + const struct drm_framebuffer_funcs *fb_funcs, + struct drm_driver *driver) +{ + struct drm_device *drm; + + mutex_init(&tdev->dirty_lock); + tdev->fb_funcs = fb_funcs; + + /* + * We don't embed drm_device, because that prevent us from using + * devm_kzalloc() to allocate tinydrm_device in the driver since + * drm_dev_unref() frees the structure. The devm_ functions provide + * for easy error handling. + */ + drm = drm_dev_alloc(driver, parent); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + tdev->drm = drm; + drm->dev_private = tdev; + drm_mode_config_init(drm); + drm->mode_config.funcs = &tinydrm_mode_config_funcs; + + return 0; +} + +static void tinydrm_fini(struct tinydrm_device *tdev) +{ + drm_mode_config_cleanup(tdev->drm); + mutex_destroy(&tdev->dirty_lock); + tdev->drm->dev_private = NULL; + drm_dev_unref(tdev->drm); +} + +static void devm_tinydrm_release(void *data) +{ + tinydrm_fini(data); +} + +/** + * devm_tinydrm_init - Initialize tinydrm device + * @parent: Parent device object + * @tdev: tinydrm device + * @fb_funcs: Framebuffer functions + * @driver: DRM driver + * + * This function initializes @tdev, the underlying DRM device and it's + * mode_config. Resources will be automatically freed on driver detach (devres) + * using drm_mode_config_cleanup() and drm_dev_unref(). + * + * Returns: + * Zero on success, negative error code on failure. + */ +int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev, + const struct drm_framebuffer_funcs *fb_funcs, + struct drm_driver *driver) +{ + int ret; + + ret = tinydrm_init(parent, tdev, fb_funcs, driver); + if (ret) + return ret; + + ret = devm_add_action(parent, devm_tinydrm_release, tdev); + if (ret) + tinydrm_fini(tdev); + + return ret; +} +EXPORT_SYMBOL(devm_tinydrm_init); + +static int tinydrm_register(struct tinydrm_device *tdev) +{ + struct drm_device *drm = tdev->drm; + int ret; + + ret = drm_dev_register(tdev->drm, 0); + if (ret) + return ret; + + ret = drm_fbdev_generic_setup(drm, 0); + if (ret) + DRM_ERROR("Failed to initialize fbdev: %d\n", ret); + + return 0; +} + +static void tinydrm_unregister(struct tinydrm_device *tdev) +{ + drm_atomic_helper_shutdown(tdev->drm); + drm_dev_unregister(tdev->drm); +} + +static void devm_tinydrm_register_release(void *data) +{ + tinydrm_unregister(data); +} + +/** + * devm_tinydrm_register - Register tinydrm device + * @tdev: tinydrm device + * + * This function registers the underlying DRM device and fbdev. + * These resources will be automatically unregistered on driver detach (devres) + * and the display pipeline will be disabled. + * + * Returns: + * Zero on success, negative error code on failure. + */ +int devm_tinydrm_register(struct tinydrm_device *tdev) +{ + struct device *dev = tdev->drm->dev; + int ret; + + ret = tinydrm_register(tdev); + if (ret) + return ret; + + ret = devm_add_action(dev, devm_tinydrm_register_release, tdev); + if (ret) + tinydrm_unregister(tdev); + + return ret; +} +EXPORT_SYMBOL(devm_tinydrm_register); + +/** + * tinydrm_shutdown - Shutdown tinydrm + * @tdev: tinydrm device + * + * This function makes sure that the display pipeline is disabled. + * Used by drivers in their shutdown callback to turn off the display + * on machine shutdown and reboot. + */ +void tinydrm_shutdown(struct tinydrm_device *tdev) +{ + drm_atomic_helper_shutdown(tdev->drm); +} +EXPORT_SYMBOL(tinydrm_shutdown); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c new file mode 100644 index 000000000..dcd390163 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) 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/backlight.h> +#include <linux/dma-buf.h> +#include <linux/pm.h> +#include <linux/spi/spi.h> +#include <linux/swab.h> + +#include <drm/tinydrm/tinydrm.h> +#include <drm/tinydrm/tinydrm-helpers.h> + +static unsigned int spi_max; +module_param(spi_max, uint, 0400); +MODULE_PARM_DESC(spi_max, "Set a lower SPI max transfer size"); + +/** + * tinydrm_merge_clips - Merge clip rectangles + * @dst: Destination clip rectangle + * @src: Source clip rectangle(s) + * @num_clips: Number of @src clip rectangles + * @flags: Dirty fb ioctl flags + * @max_width: Maximum width of @dst + * @max_height: Maximum height of @dst + * + * This function merges @src clip rectangle(s) into @dst. If @src is NULL, + * @max_width and @min_width is used to set a full @dst clip rectangle. + * + * Returns: + * true if it's a full clip, false otherwise + */ +bool tinydrm_merge_clips(struct drm_clip_rect *dst, + struct drm_clip_rect *src, unsigned int num_clips, + unsigned int flags, u32 max_width, u32 max_height) +{ + unsigned int i; + + if (!src || !num_clips) { + dst->x1 = 0; + dst->x2 = max_width; + dst->y1 = 0; + dst->y2 = max_height; + return true; + } + + dst->x1 = ~0; + dst->y1 = ~0; + dst->x2 = 0; + dst->y2 = 0; + + for (i = 0; i < num_clips; i++) { + if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY) + i++; + dst->x1 = min(dst->x1, src[i].x1); + dst->x2 = max(dst->x2, src[i].x2); + dst->y1 = min(dst->y1, src[i].y1); + dst->y2 = max(dst->y2, src[i].y2); + } + + if (dst->x2 > max_width || dst->y2 > max_height || + dst->x1 >= dst->x2 || dst->y1 >= dst->y2) { + DRM_DEBUG_KMS("Illegal clip: x1=%u, x2=%u, y1=%u, y2=%u\n", + dst->x1, dst->x2, dst->y1, dst->y2); + dst->x1 = 0; + dst->y1 = 0; + dst->x2 = max_width; + dst->y2 = max_height; + } + + return (dst->x2 - dst->x1) == max_width && + (dst->y2 - dst->y1) == max_height; +} +EXPORT_SYMBOL(tinydrm_merge_clips); + +int tinydrm_fb_dirty(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int flags, unsigned int color, + struct drm_clip_rect *clips, + unsigned int num_clips) +{ + struct tinydrm_device *tdev = fb->dev->dev_private; + struct drm_plane *plane = &tdev->pipe.plane; + int ret = 0; + + drm_modeset_lock(&plane->mutex, NULL); + + /* fbdev can flush even when we're not interested */ + if (plane->state->fb == fb) { + mutex_lock(&tdev->dirty_lock); + ret = tdev->fb_dirty(fb, file_priv, flags, + color, clips, num_clips); + mutex_unlock(&tdev->dirty_lock); + } + + drm_modeset_unlock(&plane->mutex); + + if (ret) + dev_err_once(fb->dev->dev, + "Failed to update display %d\n", ret); + + return ret; +} +EXPORT_SYMBOL(tinydrm_fb_dirty); + +/** + * tinydrm_memcpy - Copy clip buffer + * @dst: Destination buffer + * @vaddr: Source buffer + * @fb: DRM framebuffer + * @clip: Clip rectangle area to copy + */ +void tinydrm_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb, + struct drm_clip_rect *clip) +{ + unsigned int cpp = drm_format_plane_cpp(fb->format->format, 0); + unsigned int pitch = fb->pitches[0]; + void *src = vaddr + (clip->y1 * pitch) + (clip->x1 * cpp); + size_t len = (clip->x2 - clip->x1) * cpp; + unsigned int y; + + for (y = clip->y1; y < clip->y2; y++) { + memcpy(dst, src, len); + src += pitch; + dst += len; + } +} +EXPORT_SYMBOL(tinydrm_memcpy); + +/** + * tinydrm_swab16 - Swap bytes into clip buffer + * @dst: RGB565 destination buffer + * @vaddr: RGB565 source buffer + * @fb: DRM framebuffer + * @clip: Clip rectangle area to copy + */ +void tinydrm_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb, + struct drm_clip_rect *clip) +{ + size_t len = (clip->x2 - clip->x1) * sizeof(u16); + unsigned int x, y; + u16 *src, *buf; + + /* + * The cma memory is write-combined so reads are uncached. + * Speed up by fetching one line at a time. + */ + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return; + + for (y = clip->y1; y < clip->y2; y++) { + src = vaddr + (y * fb->pitches[0]); + src += clip->x1; + memcpy(buf, src, len); + src = buf; + for (x = clip->x1; x < clip->x2; x++) + *dst++ = swab16(*src++); + } + + kfree(buf); +} +EXPORT_SYMBOL(tinydrm_swab16); + +/** + * tinydrm_xrgb8888_to_rgb565 - Convert XRGB8888 to RGB565 clip buffer + * @dst: RGB565 destination buffer + * @vaddr: XRGB8888 source buffer + * @fb: DRM framebuffer + * @clip: Clip rectangle area to copy + * @swap: Swap bytes + * + * Drivers can use this function for RGB565 devices that don't natively + * support XRGB8888. + */ +void tinydrm_xrgb8888_to_rgb565(u16 *dst, void *vaddr, + struct drm_framebuffer *fb, + struct drm_clip_rect *clip, bool swap) +{ + size_t len = (clip->x2 - clip->x1) * sizeof(u32); + unsigned int x, y; + u32 *src, *buf; + u16 val16; + + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return; + + for (y = clip->y1; y < clip->y2; y++) { + src = vaddr + (y * fb->pitches[0]); + src += clip->x1; + memcpy(buf, src, len); + src = buf; + for (x = clip->x1; x < clip->x2; x++) { + val16 = ((*src & 0x00F80000) >> 8) | + ((*src & 0x0000FC00) >> 5) | + ((*src & 0x000000F8) >> 3); + src++; + if (swap) + *dst++ = swab16(val16); + else + *dst++ = val16; + } + } + + kfree(buf); +} +EXPORT_SYMBOL(tinydrm_xrgb8888_to_rgb565); + +/** + * tinydrm_xrgb8888_to_gray8 - Convert XRGB8888 to grayscale + * @dst: 8-bit grayscale destination buffer + * @vaddr: XRGB8888 source buffer + * @fb: DRM framebuffer + * @clip: Clip rectangle area to copy + * + * Drm doesn't have native monochrome or grayscale support. + * Such drivers can announce the commonly supported XR24 format to userspace + * and use this function to convert to the native format. + * + * Monochrome drivers will use the most significant bit, + * where 1 means foreground color and 0 background color. + * + * ITU BT.601 is used for the RGB -> luma (brightness) conversion. + */ +void tinydrm_xrgb8888_to_gray8(u8 *dst, void *vaddr, struct drm_framebuffer *fb, + struct drm_clip_rect *clip) +{ + unsigned int len = (clip->x2 - clip->x1) * sizeof(u32); + unsigned int x, y; + void *buf; + u32 *src; + + if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888)) + return; + /* + * The cma memory is write-combined so reads are uncached. + * Speed up by fetching one line at a time. + */ + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return; + + for (y = clip->y1; y < clip->y2; y++) { + src = vaddr + (y * fb->pitches[0]); + src += clip->x1; + memcpy(buf, src, len); + src = buf; + for (x = clip->x1; x < clip->x2; x++) { + u8 r = (*src & 0x00ff0000) >> 16; + u8 g = (*src & 0x0000ff00) >> 8; + u8 b = *src & 0x000000ff; + + /* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */ + *dst++ = (3 * r + 6 * g + b) / 10; + src++; + } + } + + kfree(buf); +} +EXPORT_SYMBOL(tinydrm_xrgb8888_to_gray8); + +#if IS_ENABLED(CONFIG_SPI) + +/** + * tinydrm_spi_max_transfer_size - Determine max SPI transfer size + * @spi: SPI device + * @max_len: Maximum buffer size needed (optional) + * + * This function returns the maximum size to use for SPI transfers. It checks + * the SPI master, the optional @max_len and the module parameter spi_max and + * returns the smallest. + * + * Returns: + * Maximum size for SPI transfers + */ +size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len) +{ + size_t ret; + + ret = min(spi_max_transfer_size(spi), spi->master->max_dma_len); + if (max_len) + ret = min(ret, max_len); + if (spi_max) + ret = min_t(size_t, ret, spi_max); + ret &= ~0x3; + if (ret < 4) + ret = 4; + + return ret; +} +EXPORT_SYMBOL(tinydrm_spi_max_transfer_size); + +/** + * tinydrm_spi_bpw_supported - Check if bits per word is supported + * @spi: SPI device + * @bpw: Bits per word + * + * This function checks to see if the SPI master driver supports @bpw. + * + * Returns: + * True if @bpw is supported, false otherwise. + */ +bool tinydrm_spi_bpw_supported(struct spi_device *spi, u8 bpw) +{ + u32 bpw_mask = spi->master->bits_per_word_mask; + + if (bpw == 8) + return true; + + if (!bpw_mask) { + dev_warn_once(&spi->dev, + "bits_per_word_mask not set, assume 8-bit only\n"); + return false; + } + + if (bpw_mask & SPI_BPW_MASK(bpw)) + return true; + + return false; +} +EXPORT_SYMBOL(tinydrm_spi_bpw_supported); + +static void +tinydrm_dbg_spi_print(struct spi_device *spi, struct spi_transfer *tr, + const void *buf, int idx, bool tx) +{ + u32 speed_hz = tr->speed_hz ? tr->speed_hz : spi->max_speed_hz; + char linebuf[3 * 32]; + + hex_dump_to_buffer(buf, tr->len, 16, + DIV_ROUND_UP(tr->bits_per_word, 8), + linebuf, sizeof(linebuf), false); + + printk(KERN_DEBUG + " tr(%i): speed=%u%s, bpw=%i, len=%u, %s_buf=[%s%s]\n", idx, + speed_hz > 1000000 ? speed_hz / 1000000 : speed_hz / 1000, + speed_hz > 1000000 ? "MHz" : "kHz", tr->bits_per_word, tr->len, + tx ? "tx" : "rx", linebuf, tr->len > 16 ? " ..." : ""); +} + +/* called through tinydrm_dbg_spi_message() */ +void _tinydrm_dbg_spi_message(struct spi_device *spi, struct spi_message *m) +{ + struct spi_transfer *tmp; + int i = 0; + + list_for_each_entry(tmp, &m->transfers, transfer_list) { + + if (tmp->tx_buf) + tinydrm_dbg_spi_print(spi, tmp, tmp->tx_buf, i, true); + if (tmp->rx_buf) + tinydrm_dbg_spi_print(spi, tmp, tmp->rx_buf, i, false); + i++; + } +} +EXPORT_SYMBOL(_tinydrm_dbg_spi_message); + +/** + * tinydrm_spi_transfer - SPI transfer helper + * @spi: SPI device + * @speed_hz: Override speed (optional) + * @header: Optional header transfer + * @bpw: Bits per word + * @buf: Buffer to transfer + * @len: Buffer length + * + * This SPI transfer helper breaks up the transfer of @buf into chunks which + * the SPI master driver can handle. If the machine is Little Endian and the + * SPI master driver doesn't support 16 bits per word, it swaps the bytes and + * does a 8-bit transfer. + * If @header is set, it is prepended to each SPI message. + * + * Returns: + * Zero on success, negative error code on failure. + */ +int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz, + struct spi_transfer *header, u8 bpw, const void *buf, + size_t len) +{ + struct spi_transfer tr = { + .bits_per_word = bpw, + .speed_hz = speed_hz, + }; + struct spi_message m; + u16 *swap_buf = NULL; + size_t max_chunk; + size_t chunk; + int ret = 0; + + if (WARN_ON_ONCE(bpw != 8 && bpw != 16)) + return -EINVAL; + + max_chunk = tinydrm_spi_max_transfer_size(spi, 0); + + if (drm_debug & DRM_UT_DRIVER) + pr_debug("[drm:%s] bpw=%u, max_chunk=%zu, transfers:\n", + __func__, bpw, max_chunk); + + if (bpw == 16 && !tinydrm_spi_bpw_supported(spi, 16)) { + tr.bits_per_word = 8; + if (tinydrm_machine_little_endian()) { + swap_buf = kmalloc(min(len, max_chunk), GFP_KERNEL); + if (!swap_buf) + return -ENOMEM; + } + } + + spi_message_init(&m); + if (header) + spi_message_add_tail(header, &m); + spi_message_add_tail(&tr, &m); + + while (len) { + chunk = min(len, max_chunk); + + tr.tx_buf = buf; + tr.len = chunk; + + if (swap_buf) { + const u16 *buf16 = buf; + unsigned int i; + + for (i = 0; i < chunk / 2; i++) + swap_buf[i] = swab16(buf16[i]); + + tr.tx_buf = swap_buf; + } + + buf += chunk; + len -= chunk; + + tinydrm_dbg_spi_message(spi, &m); + ret = spi_sync(spi, &m); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(tinydrm_spi_transfer); + +#endif /* CONFIG_SPI */ diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c new file mode 100644 index 000000000..7e8e24d0b --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) 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_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_modes.h> +#include <drm/tinydrm/tinydrm.h> + +struct tinydrm_connector { + struct drm_connector base; + struct drm_display_mode mode; +}; + +static inline struct tinydrm_connector * +to_tinydrm_connector(struct drm_connector *connector) +{ + return container_of(connector, struct tinydrm_connector, base); +} + +static int tinydrm_connector_get_modes(struct drm_connector *connector) +{ + struct tinydrm_connector *tconn = to_tinydrm_connector(connector); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &tconn->mode); + if (!mode) { + DRM_ERROR("Failed to duplicate mode\n"); + return 0; + } + + if (mode->name[0] == '\0') + drm_mode_set_name(mode); + + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + if (mode->width_mm) { + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + } + + return 1; +} + +static const struct drm_connector_helper_funcs tinydrm_connector_hfuncs = { + .get_modes = tinydrm_connector_get_modes, +}; + +static enum drm_connector_status +tinydrm_connector_detect(struct drm_connector *connector, bool force) +{ + if (drm_dev_is_unplugged(connector->dev)) + return connector_status_disconnected; + + return connector->status; +} + +static void tinydrm_connector_destroy(struct drm_connector *connector) +{ + struct tinydrm_connector *tconn = to_tinydrm_connector(connector); + + drm_connector_cleanup(connector); + kfree(tconn); +} + +static const struct drm_connector_funcs tinydrm_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .detect = tinydrm_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = tinydrm_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +struct drm_connector * +tinydrm_connector_create(struct drm_device *drm, + const struct drm_display_mode *mode, + int connector_type) +{ + struct tinydrm_connector *tconn; + struct drm_connector *connector; + int ret; + + tconn = kzalloc(sizeof(*tconn), GFP_KERNEL); + if (!tconn) + return ERR_PTR(-ENOMEM); + + drm_mode_copy(&tconn->mode, mode); + connector = &tconn->base; + + drm_connector_helper_add(connector, &tinydrm_connector_hfuncs); + ret = drm_connector_init(drm, connector, &tinydrm_connector_funcs, + connector_type); + if (ret) { + kfree(tconn); + return ERR_PTR(ret); + } + + connector->status = connector_status_connected; + + return connector; +} + +/** + * tinydrm_display_pipe_update - Display pipe update helper + * @pipe: Simple display pipe + * @old_state: Old plane state + * + * This function does a full framebuffer flush if the plane framebuffer + * has changed. It also handles vblank events. Drivers can use this as their + * &drm_simple_display_pipe_funcs->update callback. + */ +void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); + struct drm_framebuffer *fb = pipe->plane.state->fb; + struct drm_crtc *crtc = &tdev->pipe.crtc; + + if (fb && (fb != old_state->fb)) { + if (tdev->fb_dirty) + tdev->fb_dirty(fb, NULL, 0, 0, NULL, 0); + } + + if (crtc->state->event) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + crtc->state->event = NULL; + } +} +EXPORT_SYMBOL(tinydrm_display_pipe_update); + +static int tinydrm_rotate_mode(struct drm_display_mode *mode, + unsigned int rotation) +{ + if (rotation == 0 || rotation == 180) { + return 0; + } else if (rotation == 90 || rotation == 270) { + swap(mode->hdisplay, mode->vdisplay); + swap(mode->hsync_start, mode->vsync_start); + swap(mode->hsync_end, mode->vsync_end); + swap(mode->htotal, mode->vtotal); + swap(mode->width_mm, mode->height_mm); + return 0; + } else { + return -EINVAL; + } +} + +/** + * tinydrm_display_pipe_init - Initialize display pipe + * @tdev: tinydrm device + * @funcs: Display pipe functions + * @connector_type: Connector type + * @formats: Array of supported formats (DRM_FORMAT\_\*) + * @format_count: Number of elements in @formats + * @mode: Supported mode + * @rotation: Initial @mode rotation in degrees Counter Clock Wise + * + * This function sets up a &drm_simple_display_pipe with a &drm_connector that + * has one fixed &drm_display_mode which is rotated according to @rotation. + * + * Returns: + * Zero on success, negative error code on failure. + */ +int +tinydrm_display_pipe_init(struct tinydrm_device *tdev, + const struct drm_simple_display_pipe_funcs *funcs, + int connector_type, + const uint32_t *formats, + unsigned int format_count, + const struct drm_display_mode *mode, + unsigned int rotation) +{ + struct drm_device *drm = tdev->drm; + struct drm_display_mode mode_copy; + struct drm_connector *connector; + int ret; + + drm_mode_copy(&mode_copy, mode); + ret = tinydrm_rotate_mode(&mode_copy, rotation); + if (ret) { + DRM_ERROR("Illegal rotation value %u\n", rotation); + return -EINVAL; + } + + drm->mode_config.min_width = mode_copy.hdisplay; + drm->mode_config.max_width = mode_copy.hdisplay; + drm->mode_config.min_height = mode_copy.vdisplay; + drm->mode_config.max_height = mode_copy.vdisplay; + + connector = tinydrm_connector_create(drm, &mode_copy, connector_type); + if (IS_ERR(connector)) + return PTR_ERR(connector); + + return drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats, + format_count, NULL, connector); +} +EXPORT_SYMBOL(tinydrm_display_pipe_init); |