lkml.org 
[lkml]   [2017]   [Feb]   [11]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v4 2/7] drm/tinydrm: Add helper functions
    Date
    Add common functionality needed by many tinydrm drivers.

    Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
    Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
    Acked-by: Thierry Reding <treding@nvidia.com>
    ---

    Changes since version 3:
    - Fix backlight dependency by selecting it
    - Use #if IS_ENABLED(CONFIG_SPI) instead of #ifdef to handle tristate

    Documentation/gpu/tinydrm.rst | 9 +
    drivers/gpu/drm/tinydrm/Kconfig | 2 +
    drivers/gpu/drm/tinydrm/core/Makefile | 2 +-
    drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c | 460 +++++++++++++++++++++++++
    include/drm/tinydrm/tinydrm-helpers.h | 100 ++++++
    5 files changed, 572 insertions(+), 1 deletion(-)
    create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
    create mode 100644 include/drm/tinydrm/tinydrm-helpers.h

    diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
    index ec4a20d..fb256d2 100644
    --- a/Documentation/gpu/tinydrm.rst
    +++ b/Documentation/gpu/tinydrm.rst
    @@ -19,3 +19,12 @@ Core functionality

    .. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
    :export:
    +
    +Additional helpers
    +==================
    +
    +.. kernel-doc:: include/drm/tinydrm/tinydrm-helpers.h
    + :internal:
    +
    +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
    + :export:
    diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
    index ffb873f..adf3626 100644
    --- a/drivers/gpu/drm/tinydrm/Kconfig
    +++ b/drivers/gpu/drm/tinydrm/Kconfig
    @@ -3,6 +3,8 @@ menuconfig DRM_TINYDRM
    depends on DRM
    select DRM_KMS_HELPER
    select DRM_KMS_CMA_HELPER
    + select BACKLIGHT_LCD_SUPPORT
    + select BACKLIGHT_CLASS_DEVICE
    help
    Choose this option if you have a tinydrm supported display.
    If M is selected the module will be called tinydrm.
    diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
    index 4f14a0f..fb221e6 100644
    --- a/drivers/gpu/drm/tinydrm/core/Makefile
    +++ b/drivers/gpu/drm/tinydrm/core/Makefile
    @@ -1,3 +1,3 @@
    -tinydrm-y := tinydrm-core.o tinydrm-pipe.o
    +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-helpers.c b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
    new file mode 100644
    index 0000000..3ccda6c
    --- /dev/null
    +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
    @@ -0,0 +1,460 @@
    +/*
    + * 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/tinydrm/tinydrm.h>
    +#include <drm/tinydrm/tinydrm-helpers.h>
    +#include <linux/backlight.h>
    +#include <linux/pm.h>
    +#include <linux/spi/spi.h>
    +#include <linux/swab.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);
    +
    +/**
    + * 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_of_find_backlight - Find backlight device in device-tree
    + * @dev: Device
    + *
    + * This function looks for a DT node pointed to by a property named 'backlight'
    + * and uses of_find_backlight_by_node() to get the backlight device.
    + * Additionally if the brightness property is zero, it is set to
    + * max_brightness.
    + *
    + * Returns:
    + * NULL if there's no backlight property.
    + * Error pointer -EPROBE_DEFER if the DT node is found, but no backlight device
    + * is found.
    + * If the backlight device is found, a pointer to the structure is returned.
    + */
    +struct backlight_device *tinydrm_of_find_backlight(struct device *dev)
    +{
    + struct backlight_device *backlight;
    + struct device_node *np;
    +
    + np = of_parse_phandle(dev->of_node, "backlight", 0);
    + if (!np)
    + return NULL;
    +
    + backlight = of_find_backlight_by_node(np);
    + of_node_put(np);
    +
    + if (!backlight)
    + return ERR_PTR(-EPROBE_DEFER);
    +
    + if (!backlight->props.brightness) {
    + backlight->props.brightness = backlight->props.max_brightness;
    + DRM_DEBUG_KMS("Backlight brightness set to %d\n",
    + backlight->props.brightness);
    + }
    +
    + return backlight;
    +}
    +EXPORT_SYMBOL(tinydrm_of_find_backlight);
    +
    +/**
    + * tinydrm_enable_backlight - Enable backlight helper
    + * @backlight: Backlight device
    + *
    + * Returns:
    + * Zero on success, negative error code on failure.
    + */
    +int tinydrm_enable_backlight(struct backlight_device *backlight)
    +{
    + unsigned int old_state;
    + int ret;
    +
    + if (!backlight)
    + return 0;
    +
    + old_state = backlight->props.state;
    + backlight->props.state &= ~BL_CORE_FBBLANK;
    + DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
    + backlight->props.state);
    +
    + ret = backlight_update_status(backlight);
    + if (ret)
    + DRM_ERROR("Failed to enable backlight %d\n", ret);
    +
    + return ret;
    +}
    +EXPORT_SYMBOL(tinydrm_enable_backlight);
    +
    +/**
    + * tinydrm_disable_backlight - Disable backlight helper
    + * @backlight: Backlight device
    + *
    + * Returns:
    + * Zero on success, negative error code on failure.
    + */
    +int tinydrm_disable_backlight(struct backlight_device *backlight)
    +{
    + unsigned int old_state;
    + int ret;
    +
    + if (!backlight)
    + return 0;
    +
    + old_state = backlight->props.state;
    + backlight->props.state |= BL_CORE_FBBLANK;
    + DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
    + backlight->props.state);
    + ret = backlight_update_status(backlight);
    + if (ret)
    + DRM_ERROR("Failed to disable backlight %d\n", ret);
    +
    + return ret;
    +}
    +EXPORT_SYMBOL(tinydrm_disable_backlight);
    +
    +#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;
    + struct list_head *pos;
    + int i = 0;
    +
    + list_for_each(pos, &m->transfers) {
    + tmp = list_entry(pos, struct spi_transfer, 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/include/drm/tinydrm/tinydrm-helpers.h b/include/drm/tinydrm/tinydrm-helpers.h
    new file mode 100644
    index 0000000..78175fe
    --- /dev/null
    +++ b/include/drm/tinydrm/tinydrm-helpers.h
    @@ -0,0 +1,100 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef __LINUX_TINYDRM_HELPERS_H
    +#define __LINUX_TINYDRM_HELPERS_H
    +
    +struct backlight_device;
    +struct tinydrm_device;
    +struct drm_clip_rect;
    +struct spi_transfer;
    +struct spi_message;
    +struct spi_device;
    +struct device;
    +
    +/**
    + * tinydrm_machine_little_endian - Machine is little endian
    + *
    + * Returns:
    + * true if *defined(__LITTLE_ENDIAN)*, false otherwise
    + */
    +static inline bool tinydrm_machine_little_endian(void)
    +{
    +#if defined(__LITTLE_ENDIAN)
    + return true;
    +#else
    + return false;
    +#endif
    +}
    +
    +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);
    +void tinydrm_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
    + struct drm_clip_rect *clip);
    +void tinydrm_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
    + struct drm_clip_rect *clip);
    +void tinydrm_xrgb8888_to_rgb565(u16 *dst, void *vaddr,
    + struct drm_framebuffer *fb,
    + struct drm_clip_rect *clip, bool swap);
    +
    +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE
    +struct backlight_device *tinydrm_of_find_backlight(struct device *dev);
    +int tinydrm_enable_backlight(struct backlight_device *backlight);
    +int tinydrm_disable_backlight(struct backlight_device *backlight);
    +#else
    +static inline struct backlight_device *
    +tinydrm_of_find_backlight(struct device *dev)
    +{
    + return NULL;
    +}
    +
    +static inline int tinydrm_enable_backlight(struct backlight_device *backlight)
    +{
    + return 0;
    +}
    +
    +static inline int
    +tinydrm_disable_backlight(struct backlight_device *backlight)
    +{
    + return 0;
    +}
    +#endif
    +
    +size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len);
    +bool tinydrm_spi_bpw_supported(struct spi_device *spi, u8 bpw);
    +int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
    + struct spi_transfer *header, u8 bpw, const void *buf,
    + size_t len);
    +void _tinydrm_dbg_spi_message(struct spi_device *spi, struct spi_message *m);
    +
    +#ifdef DEBUG
    +/**
    + * tinydrm_dbg_spi_message - Dump SPI message
    + * @spi: SPI device
    + * @m: SPI message
    + *
    + * Dumps info about the transfers in a SPI message including buffer content.
    + * DEBUG has to be defined for this function to be enabled alongside setting
    + * the DRM_UT_DRIVER bit of &drm_debug.
    + */
    +static inline void tinydrm_dbg_spi_message(struct spi_device *spi,
    + struct spi_message *m)
    +{
    + if (drm_debug & DRM_UT_DRIVER)
    + _tinydrm_dbg_spi_message(spi, m);
    +}
    +#else
    +static inline void tinydrm_dbg_spi_message(struct spi_device *spi,
    + struct spi_message *m)
    +{
    +}
    +#endif /* DEBUG */
    +
    +#endif /* __LINUX_TINYDRM_HELPERS_H */
    --
    2.10.2
    \
     
     \ /
      Last update: 2017-02-11 19:50    [W:3.828 / U:0.048 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site