[PATCH v1 3/5] tinydrm: add support for parallel data displays

From: Sam Ravnborg
Date: Thu Aug 02 2018 - 15:46:00 EST


Utilising the pardata bus/driver add support for
displays connected to a parallel data bus.
There is no specific protocol implemented,
but the display is tied to the tinydrm pipe.

Only monochrome displays supported using the
XRGB8888 format.

Signed-off-by: Sam Ravnborg <sam@xxxxxxxxxxxx>
---
drivers/gpu/drm/tinydrm/Kconfig | 3 +
drivers/gpu/drm/tinydrm/Makefile | 1 +
drivers/gpu/drm/tinydrm/pardata-dbi.c | 417 ++++++++++++++++++++++++++++++++++
include/drm/tinydrm/pardata-dbi.h | 257 +++++++++++++++++++++
4 files changed, 678 insertions(+)
create mode 100644 drivers/gpu/drm/tinydrm/pardata-dbi.c
create mode 100644 include/drm/tinydrm/pardata-dbi.h

diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index 4592a5e3f20b..435de2f8d8f5 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -10,6 +10,9 @@ menuconfig DRM_TINYDRM
config TINYDRM_MIPI_DBI
tristate

+config TINYDRM_PARDATA_DBI
+ tristate
+
config TINYDRM_ILI9225
tristate "DRM support for ILI9225 display panels"
depends on DRM_TINYDRM && SPI
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 49a111929724..0b52df08b0a4 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -2,6 +2,7 @@ obj-$(CONFIG_DRM_TINYDRM) += core/

# Controllers
obj-$(CONFIG_TINYDRM_MIPI_DBI) += mipi-dbi.o
+obj-$(CONFIG_TINYDRM_PARDATA_DBI) += pardata-dbi.o

# Displays
obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o
diff --git a/drivers/gpu/drm/tinydrm/pardata-dbi.c b/drivers/gpu/drm/tinydrm/pardata-dbi.c
new file mode 100644
index 000000000000..09bdfdba6291
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/pardata-dbi.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Parallel Data Display Bus Interface LCD controller support
+ */
+
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/dma-buf.h>
+#include <linux/pardata.h>
+#include <linux/module.h>
+
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <drm/tinydrm/pardata-dbi.h>
+
+/**
+ * pardata_strobe_8080_write - using the 8080 interface create
+ * an enable strobe
+ *
+ * The interface requires that readwrite and chipselect are
+ * setup before the strobe of "e".
+ * There are timings constraints that is handled using udelay().
+ * Drivers can use this for the strobe_write callback.
+ *
+ * @pdd: pardata data
+ */
+void pardata_strobe_8080_write(struct pardata_data *pdd)
+{
+ gpiod_set_value_cansleep(pdd->bus->pin_readwrite, 0);
+
+ if (pdd->pin_cs)
+ gpiod_set_value_cansleep(pdd->pin_cs, 0);
+ /* min 90 nsec from cs/rs/rw to e */
+ udelay(1);
+ gpiod_set_value_cansleep(pdd->bus->pin_enable, 1);
+ /* data setup time 220 ns*/
+ udelay(2);
+ gpiod_set_value_cansleep(pdd->bus->pin_enable, 0);
+ /* data hold time 20 ns */
+ udelay(1);
+ if (pdd->pin_cs)
+ gpiod_set_value_cansleep(pdd->pin_cs, 1);
+}
+EXPORT_SYMBOL_GPL(pardata_strobe_8080_write);
+
+/**
+ * pardata_strobe_6800_write - using the 6800 interface create
+ * an enable strobe
+ *
+ * The interface requires that read + write and chipselect are
+ * setup before the strobe of "e".
+ * There are timings constraints that is handled using udelay()
+ * Drivers can use this for the strobe_write callback.
+ *
+ * @pdd: pardata data
+ */
+
+void pardata_strobe_6800_write(struct pardata_data *pdd)
+{
+ gpiod_set_value_cansleep(pdd->bus->pin_read, 0);
+ gpiod_set_value_cansleep(pdd->bus->pin_write, 1);
+
+ if (pdd->pin_cs)
+ gpiod_set_value_cansleep(pdd->pin_cs, 0);
+ /* min 90 nsec from cs/rs/rw to e */
+ udelay(1);
+ gpiod_set_value_cansleep(pdd->bus->pin_enable, 1);
+ /* data setup time 220 ns*/
+ udelay(2);
+ gpiod_set_value_cansleep(pdd->bus->pin_enable, 0);
+ /* data hold time 20 ns */
+ udelay(1);
+ if (pdd->pin_cs)
+ gpiod_set_value_cansleep(pdd->pin_cs, 1);
+}
+EXPORT_SYMBOL_GPL(pardata_strobe_6800_write);
+
+
+static int pardata_write_clip(struct pardata_data *pdd,
+ struct drm_framebuffer *fb,
+ u8 *data,
+ struct drm_clip_rect *clip)
+{
+ bool line_by_line;
+ size_t linelen;
+ size_t len;
+ int x, y;
+ u8 *buf;
+
+ linelen = clip->x2 - clip->x1;
+ len = linelen * (clip->y2 - clip->y1) / 8;
+ buf = kzalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /*
+ * If the clip is the full width then we need only one
+ * write operation.
+ */
+ line_by_line = (clip->x2 - clip->x1) != fb->width;
+
+ for (y = clip->y1; y < clip->y2; y++) {
+ for (x = clip->x1; x < clip->x2; x++) {
+ unsigned int index;
+ u8 cc;
+
+ index = x + y * linelen;
+ cc = data[index];
+
+ /* Most significant bit determine bit on/off */
+ if (cc & 0x80)
+ buf[index / 8] |= BIT(index % 8);
+ }
+ if (line_by_line) {
+ int offset;
+
+ offset = y * fb->width + clip->x1;
+ pardata_write_buf(pdd, offset, &buf[offset], linelen);
+ }
+ }
+ if (!line_by_line) {
+ int offset;
+
+ offset = clip->y1 * fb->width;
+ pardata_write_buf(pdd, offset, &buf[offset], len);
+ }
+
+ kfree(buf);
+
+ return 0;
+}
+
+/**
+ * pardata_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
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+static int pardata_buf_copy(void *dst, struct drm_framebuffer *fb,
+ struct drm_clip_rect *clip)
+{
+ struct dma_buf_attachment *import_attach;
+ struct drm_gem_cma_object *cma_obj;
+ void *src;
+ int ret;
+
+ cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+ import_attach = cma_obj->base.import_attach;
+ src = cma_obj->vaddr;
+ ret = 0;
+
+ if (import_attach) {
+ ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
+ DMA_FROM_DEVICE);
+ if (ret)
+ return ret;
+ }
+
+ tinydrm_xrgb8888_to_gray8(dst, src, fb, clip);
+
+ if (import_attach)
+ ret = dma_buf_end_cpu_access(import_attach->dmabuf,
+ DMA_FROM_DEVICE);
+ return ret;
+}
+
+static int pardata_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 drm_format_name_buf format_name;
+ struct drm_gem_cma_object *cma_obj;
+ struct tinydrm_device *tdev;
+ struct drm_clip_rect clip;
+ struct pardata_data *pdd;
+ bool full;
+ int ret;
+
+ cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+ tdev = fb->dev->dev_private;
+ pdd = pardata_from_tinydrm(tdev);
+ ret = 0;
+
+ if (!pdd->enabled)
+ return 0;
+
+ if (fb->format->format != DRM_FORMAT_XRGB8888) {
+ dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
+ drm_get_format_name(fb->format->format,
+ &format_name));
+ return -EINVAL;
+ }
+
+ full = tinydrm_merge_clips(&clip, clips, num_clips, flags,
+ fb->width, fb->height);
+
+ DRM_DEV_DEBUG(&pdd->pddev->dev,
+ "Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n",
+ fb->base.id, clip.x1, clip.x2, clip.y1, clip.y2);
+
+ if (!full) {
+ ret = pardata_buf_copy(pdd->tx_buf, fb, &clip);
+ if (!ret)
+ ret = pardata_write_clip(pdd, fb, pdd->tx_buf, &clip);
+ } else {
+ size_t len;
+
+ len = fb->width * fb->height;
+ ret = pardata_write_buf(pdd, 0, cma_obj->vaddr, len);
+ }
+
+ return ret;
+}
+
+static const struct drm_framebuffer_funcs mipi_dbi_fb_funcs = {
+ .destroy = drm_gem_fb_destroy,
+ .create_handle = drm_gem_fb_create_handle,
+ .dirty = tinydrm_fb_dirty,
+};
+
+/**
+ * pardata_enable_flush - enable helper
+ *
+ * @pdd: pardata data
+ * @crtc_state: crtc state
+ * @plane_state: plane state
+ *
+ * This function sets &pdd->enabled, flushes the whole framebuffer and
+ * enables the backlight. Drivers can use this in their
+ * &drm_simple_display_pipe_funcs->enable callback.
+ */
+void pardata_enable_flush(struct pardata_data *pdd,
+ struct drm_crtc_state *crtc_state,
+ struct drm_plane_state *plane_state)
+{
+ struct tinydrm_device *tdev;
+ struct drm_framebuffer *fb;
+
+ tdev = &pdd->tinydrm;
+ fb = plane_state->fb;
+ pdd->enabled = true;
+
+ if (fb)
+ tdev->fb_dirty(fb, NULL, 0, 0, NULL, 0);
+
+ backlight_enable(pdd->backlight);
+}
+EXPORT_SYMBOL_GPL(pardata_enable_flush);
+
+static void pardata_blank(struct pardata_data *pdd)
+{
+ struct drm_device *drm;
+ u16 height, width;
+ size_t len;
+
+ drm = pdd->tinydrm.drm;
+ height = drm->mode_config.min_height;
+ width = drm->mode_config.min_width;
+
+ len = width * height;
+
+ memset(pdd->tx_buf, 0, len);
+ pardata_write_buf(pdd, 0, pdd->tx_buf, len);
+}
+
+/**
+ * pardata_pipe_disable - pipe disable helper
+ *
+ * @pipe: Display pipe
+ *
+ * This function disables backlight if present, if not the display memory is
+ * blanked. The regulator is disabled if in use. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->disable callback.
+ */
+void pardata_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+ struct tinydrm_device *tdev;
+ struct pardata_data *pdd;
+
+ tdev = pipe_to_tinydrm(pipe);
+ pdd = pardata_from_tinydrm(tdev);
+
+ pdd->enabled = false;
+
+ if (pdd->backlight)
+ backlight_disable(pdd->backlight);
+ else
+ pardata_blank(pdd);
+
+ if (pdd->regulator)
+ regulator_disable(pdd->regulator);
+}
+EXPORT_SYMBOL_GPL(pardata_pipe_disable);
+
+static const uint32_t pardata_formats[] = {
+ DRM_FORMAT_XRGB8888,
+};
+
+/**
+ * pardata_init - initialization
+ * @dev: Parent device
+ * @pdd: pardata data to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ *
+ * This function initializes a &pardata data structure and it's underlying
+ * @tinydrm_device. It also sets up the display pipeline.
+ *
+ * Supported formats: Emulated XRGB8888.
+ *
+ * Objects created by this function will be automatically freed on driver
+ * detach (devres).
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int pardata_init(struct device *dev,
+ struct pardata_data *pdd,
+ const struct drm_simple_display_pipe_funcs *pipe_funcs,
+ struct drm_driver *driver,
+ const struct drm_display_mode *mode)
+{
+ struct tinydrm_device *tdev;
+ size_t bufsize;
+ int ret;
+
+ /* shortcut to pardatabus_data */
+ pdd->bus = dev_get_drvdata(dev->parent);
+
+ tdev = &pdd->tinydrm;
+ bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16);
+
+ pdd->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL);
+ if (!pdd->tx_buf)
+ return -ENOMEM;
+
+ ret = devm_tinydrm_init(dev, tdev, &mipi_dbi_fb_funcs, driver);
+ if (ret)
+ return ret;
+
+ tdev->fb_dirty = pardata_fb_dirty;
+
+ ret = tinydrm_display_pipe_init(tdev, pipe_funcs,
+ DRM_MODE_CONNECTOR_VIRTUAL,
+ pardata_formats,
+ ARRAY_SIZE(pardata_formats),
+ mode,
+ 0);
+ if (ret)
+ return ret;
+
+ tdev->drm->mode_config.preferred_depth = 16;
+
+ drm_mode_config_reset(tdev->drm);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pardata_init);
+
+/**
+ * pardata_hw_reset - Hardware reset of controller
+ *
+ * @pdd: pardata data
+ *
+ * Reset controller if the &pardata->pin_reset gpio is set.
+ */
+void pardata_hw_reset(struct pardata_data *pdd)
+{
+ if (!pdd->pin_reset)
+ return;
+
+ gpiod_set_value_cansleep(pdd->pin_reset, 0);
+ usleep_range(20, 1000);
+ gpiod_set_value_cansleep(pdd->pin_reset, 1);
+ msleep(120);
+}
+EXPORT_SYMBOL_GPL(pardata_hw_reset);
+
+/**
+ * pardata_poweron_reset - poweron and reset
+ * @pdd: pardata data
+ *
+ * This function enables the regulator if used and does a hardware reset.
+ *
+ * Returns:
+ * Zero on success, or a negative error code.
+ */
+int pardata_poweron_reset(struct pardata_data *pdd)
+{
+ int ret;
+
+
+ if (pdd->regulator) {
+ ret = regulator_enable(pdd->regulator);
+ if (ret) {
+ DRM_DEV_ERROR(&pdd->pddev->dev,
+ "Failed to enable regulator (%d)\n",
+ ret);
+ return ret;
+ }
+ }
+
+ pardata_hw_reset(pdd);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pardata_poweron_reset);
+
+MODULE_LICENSE("GPL v2");
diff --git a/include/drm/tinydrm/pardata-dbi.h b/include/drm/tinydrm/pardata-dbi.h
new file mode 100644
index 000000000000..d72d9a1dff27
--- /dev/null
+++ b/include/drm/tinydrm/pardata-dbi.h
@@ -0,0 +1,257 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Parallel Data Display Bus Interface LCD controller support
+ */
+
+#include <linux/pardata.h>
+
+#include <drm/tinydrm/tinydrm.h>
+
+/**
+ * pardata_pins - the data pins
+ *
+ * The order is important due to use of writing to gpio arrays.
+ * The first four pins (PIN_DB0 to PIN_DB3) are not used for
+ * interfaces using only four data bits.
+ */
+enum pardata_pins {
+ PIN_DB0,
+ PIN_DB1,
+ PIN_DB2,
+ PIN_DB3,
+ PIN_DB4,
+ PIN_DB5,
+ PIN_DB6,
+ PIN_DB7,
+ PIN_NUM, /* Number of pins */
+};
+
+struct pardata_data {
+ /**
+ * @tinydrm - tinydrm base
+ */
+ struct tinydrm_device tinydrm;
+
+ /**
+ * dev - pardata device
+ */
+ struct pardata_device *pddev;
+
+ /**
+ * @busdata - data for the bus
+ */
+ struct pardatabus_data *bus;
+
+ /**
+ * @pin_reset: Optional GPIO reset pin
+ */
+ struct gpio_desc *pin_reset;
+
+ /**
+ * @pin_cs: Optional chip select pin
+ */
+ struct gpio_desc *pin_cs;
+
+ /**
+ * @tx_buf: Poitner to buffer to transer to display RAM
+ */
+ u8 *tx_buf;
+
+ /**
+ * @backlight - backlight device (optional)
+ */
+ struct backlight_device *backlight;
+
+ /**
+ * @regulator - power regulator (optional)
+ */
+ struct regulator *regulator;
+
+ /**
+ * @strobe_write - activate chip select to move data to controller
+ *
+ * This callback is used to set read/write and activate
+ * pin_e and pin_cs - with respect to the timing required by
+ * the controller.
+ *
+ * Use pardata_strobe_8080_write() for 8080 style interface or
+ * pardata_strobe_6800_write() for 6800 style interface.
+ * has individual pins for read and write.
+ *
+ * Parameters:
+ *
+ * pdd: pardata data
+ */
+ void (*strobe_write)(struct pardata_data *pdd);
+
+ /**
+ * write_reg - write value to register
+ *
+ * This callback is used to write the value to the register.
+ *
+ * Parameters:
+ *
+ * pdd: pardata data
+ * reg: The register to write
+ * value: The value to write to the register
+ *
+ * Returns:
+ * 0 on success, negative value on error
+ */
+ int (*write_reg)(struct pardata_data *pdd,
+ unsigned int reg, unsigned int value);
+
+ /**
+ * write_buf - write content of buffer to controller
+ *
+ * This callback writes the data to the controller at offset
+ * and forward. The write operation shall be as efficient as
+ * possible as this will be called every time the content on
+ * the display changes
+ *
+ * Parameters:
+ *
+ * pdd: pardata data
+ * offset: The offset into the display memory of the controller where
+ * the data shall be written.
+ * data: Pointer to the data to write to display memory
+ * len: Number of bytes to write to the display memory
+ *
+ * Returns:
+ * 0 on success, negative value on error
+ */
+ int (*write_buf)(struct pardata_data *pdd,
+ u8 offset,
+ u8 *data,
+ size_t len);
+
+ /**
+ * @enabled: Pipeline is enabled (internal)
+ */
+ bool enabled;
+};
+
+static inline struct pardata_data *
+pardata_from_tinydrm(struct tinydrm_device *tdev)
+{
+ return container_of(tdev, struct pardata_data, tinydrm);
+}
+
+static inline void pardata_strobe_write(struct pardata_data *pdd)
+{
+ if (pdd && pdd->strobe_write)
+ pdd->strobe_write(pdd);
+ else
+ DRM_DEV_ERROR(&pdd->pddev->dev, "No strobe_write callback");
+}
+
+static inline int pardata_write_reg(struct pardata_data *pdd,
+ unsigned int reg, unsigned int value)
+{
+ if (pdd && pdd->write_reg)
+ pdd->write_reg(pdd, reg, value);
+ else
+ DRM_DEV_ERROR(&pdd->pddev->dev, "No write_reg callback");
+
+ return 0;
+}
+
+static inline int pardata_write_buf(struct pardata_data *pdd,
+ u8 offset, u8 *data, size_t len)
+{
+ if (pdd && pdd->write_buf)
+ pdd->write_buf(pdd, offset, data, len);
+ else
+ DRM_DEV_ERROR(&pdd->pddev->dev, "No write_buf callback");
+
+ return 0;
+}
+
+/**
+ * pardata_strobe_8080_write - using the 8080 interface create
+ * an enable strobe
+ *
+ * The interface requires that readwrite and chipselect are
+ * setup before the strobe of "e".
+ * There are timings constraints that is handled using udelay().
+ * Drivers can use this for the strobe_write callback.
+ *
+ * @pdd: pardata data
+ */
+void pardata_strobe_8080_write(struct pardata_data *pdd);
+
+/**
+ * pardata_strobe_6800_write - using the 6800 interface create
+ * an enable strobe
+ *
+ * The interface requires that read + write and chipselect are
+ * setup before the strobe of "e".
+ * There are timings constraints that is handled using udelay()
+ * Drivers can use this for the strobe_write callback.
+ *
+ * @pdd: pardata data
+ */
+void pardata_strobe_6800_write(struct pardata_data *pdd);
+
+/**
+ * pardata_poweron_reset - poweron and reset display
+ *
+ * @pdd: pardata data
+ *
+ * This function enables the regulator if used and does a hardware reset.
+ * Returns:
+ * Zero on success, or a negative error code.
+ */
+int pardata_poweron_reset(struct pardata_data *pdd);
+
+/**
+ * pardata_enable_flush - enable helper
+ *
+ * @pdd: parDATA DATA
+ * @crtc_state: crtc state
+ * @plane_state: plane state
+ *
+ * This function sets &pardata->enabled, flushes the whole framebuffer and
+ * enables the backlight. Drivers can use this in their
+ * &drm_simple_display_pipe_funcs->enable callback.
+ */
+void pardata_enable_flush(struct pardata_data *pdd,
+ struct drm_crtc_state *crtc_state,
+ struct drm_plane_state *plane_state);
+
+/**
+ * pardata_pipe_disable - pipe disable helper
+ *
+ * @pipe: Display pipe
+ *
+ * This function disables backlight if present, if not the display memory is
+ * blanked. The regulator is disabled if in use. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->disable callback.
+ */
+void pardata_pipe_disable(struct drm_simple_display_pipe *pipe);
+
+/**
+ * pardata_init - initialization
+ *
+ * @dev: Parent device
+ * @pdd: pardata data to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ *
+ * This function initializes a &pardata data structure and it's underlying
+ * @tinydrm_device. It also sets up the display pipeline.
+ *
+ * Supported formats: Emulated XRGB8888.
+ *
+ * Objects created by this function will be automatically freed on driver
+ * detach (devres).
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int pardata_init(struct device *dev,
+ struct pardata_data *pdd,
+ const struct drm_simple_display_pipe_funcs *pipe_funcs,
+ struct drm_driver *driver,
+ const struct drm_display_mode *mode);
--
2.12.0