Add LCD register abstraction for MIPI DBI/DCS like controllers. This hides LCD controller interface implementation details. When built with debugfs, the register is available to userspace. Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- drivers/gpu/drm/tinydrm/Kconfig | 2 + drivers/gpu/drm/tinydrm/Makefile | 1 + drivers/gpu/drm/tinydrm/lcdreg/Kconfig | 3 + drivers/gpu/drm/tinydrm/lcdreg/Makefile | 3 + drivers/gpu/drm/tinydrm/lcdreg/internal.h | 8 + drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c | 190 ++++++++++++++++ drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c | 281 ++++++++++++++++++++++++ include/drm/tinydrm/lcdreg.h | 126 +++++++++++ 8 files changed, 614 insertions(+) create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Kconfig create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Makefile create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/internal.h create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c create mode 100644 include/drm/tinydrm/lcdreg.h diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig index e26e5ed..739be06 100644 --- a/drivers/gpu/drm/tinydrm/Kconfig +++ b/drivers/gpu/drm/tinydrm/Kconfig @@ -9,3 +9,5 @@ menuconfig DRM_TINYDRM help Choose this option if you have a tinydrm supported display. If M is selected the module will be called tinydrm. + +source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig" diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile index 7476ed1..f4a92d9 100644 --- a/drivers/gpu/drm/tinydrm/Makefile +++ b/drivers/gpu/drm/tinydrm/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_DRM_TINYDRM) += core/ +obj-$(CONFIG_LCDREG) += lcdreg/ diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig new file mode 100644 index 0000000..41383b1 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig @@ -0,0 +1,3 @@ +config LCDREG + tristate + depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Makefile b/drivers/gpu/drm/tinydrm/lcdreg/Makefile new file mode 100644 index 0000000..c9ea774 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/lcdreg/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_LCDREG) += lcdreg.o +lcdreg-y += lcdreg-core.o +lcdreg-$(CONFIG_DEBUG_FS) += lcdreg-debugfs.o diff --git a/drivers/gpu/drm/tinydrm/lcdreg/internal.h b/drivers/gpu/drm/tinydrm/lcdreg/internal.h new file mode 100644 index 0000000..140fcfd --- /dev/null +++ b/drivers/gpu/drm/tinydrm/lcdreg/internal.h @@ -0,0 +1,8 @@ + +#ifdef CONFIG_DEBUG_FS +void lcdreg_debugfs_init(struct lcdreg *reg); +void lcdreg_debugfs_exit(struct lcdreg *reg); +#else +static inline void lcdreg_debugfs_init(struct lcdreg *reg) { } +static inline void lcdreg_debugfs_exit(struct lcdreg *reg) { } +#endif diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c new file mode 100644 index 0000000..bda848c --- /dev/null +++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c @@ -0,0 +1,190 @@ +//#define DEBUG +/* + * 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/lcdreg.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "internal.h" + +/** + * Write to LCD register + * + * @reg: LCD register + * @regnr: Register number + * @transfer: Transfer to write + */ +int lcdreg_write(struct lcdreg *reg, unsigned regnr, + struct lcdreg_transfer *transfer) +{ + if (WARN_ON_ONCE(!reg || !reg->write || !transfer)) + return -EINVAL; + + if (!transfer->width) + transfer->width = reg->def_width; + + dev_dbg(reg->dev, + "lcdreg_write: regnr=0x%02x, index=%u, count=%u, width=%u\n", + regnr, transfer->index, transfer->count, transfer->width); + lcdreg_dbg_transfer_buf(transfer); + + return reg->write(reg, regnr, transfer); +} +EXPORT_SYMBOL(lcdreg_write); + +/** + * Write 32-bit wide buffer to LCD register + * @reg: lcdreg + * @regnr: Register number + * @buf: Buffer to write + * @count: Number of words to write + */ +int lcdreg_write_buf32(struct lcdreg *reg, unsigned regnr, const u32 *buf, + unsigned count) +{ + struct lcdreg_transfer tr = { + .index = 1, + .width = reg->def_width, + .count = count, + }; + int i, ret; + + if (!buf) + return -EINVAL; + + tr.buf = kmalloc_array(count, sizeof(*buf), GFP_KERNEL); + if (!tr.buf) + return -ENOMEM; + + if (reg->def_width <= 8) + for (i = 0; i < tr.count; i++) + ((u8 *)tr.buf)[i] = buf[i]; + else + for (i = 0; i < tr.count; i++) + ((u16 *)tr.buf)[i] = buf[i]; + ret = lcdreg_write(reg, regnr, &tr); + kfree(tr.buf); + + return ret; +} +EXPORT_SYMBOL(lcdreg_write_buf32); + +/** + * Read from LCD register + * + * @reg: LCD register + * @regnr: Register number + * @transfer: Transfer to read into + */ +int lcdreg_read(struct lcdreg *reg, unsigned regnr, + struct lcdreg_transfer *transfer) +{ + int ret; + + if (WARN_ON_ONCE(!reg || !transfer)) + return -EINVAL; + + if (!reg->read) + return -EOPNOTSUPP; + + if (!transfer->width) + transfer->width = reg->def_width; + + dev_dbg(reg->dev, + "lcdreg_read: regnr=0x%02x, index=%u, count=%u, width=%u\n", + regnr, transfer->index, transfer->count, transfer->width); + + ret = reg->read(reg, regnr, transfer); + + lcdreg_dbg_transfer_buf(transfer); + + return ret; +} +EXPORT_SYMBOL(lcdreg_read); + +/** + * Read from LCD register into 32-bit wide buffer + * @reg: LCD register + * @regnr: Register number + * @buf: Buffer to read into + * @count: Number of words to read + */ +int lcdreg_readreg_buf32(struct lcdreg *reg, unsigned regnr, u32 *buf, + unsigned count) +{ + struct lcdreg_transfer tr = { + .index = 1, + .count = count, + }; + int i, ret; + + if (!buf || !count) + return -EINVAL; + + tr.buf = kmalloc_array(count, sizeof(*buf), GFP_KERNEL); + if (!tr.buf) + return -ENOMEM; + + ret = lcdreg_read(reg, regnr, &tr); + if (ret) { + kfree(tr.buf); + return ret; + } + + if (reg->def_width <= 8) + for (i = 0; i < count; i++) + buf[i] = ((u8 *)tr.buf)[i]; + else + for (i = 0; i < count; i++) + buf[i] = ((u16 *)tr.buf)[i]; + kfree(tr.buf); + + return ret; +} +EXPORT_SYMBOL(lcdreg_readreg_buf32); + +static void devm_lcdreg_release(struct device *dev, void *res) +{ + struct lcdreg *reg = *(struct lcdreg **)res; + + lcdreg_debugfs_exit(reg); + mutex_destroy(®->lock); +} + +/** + * Device managed lcdreg initialization + * + * @dev: Device backing the LCD register + * @reg: LCD register + */ +struct lcdreg *devm_lcdreg_init(struct device *dev, struct lcdreg *reg) +{ + struct lcdreg **ptr; + + if (!dev || !reg) + return ERR_PTR(-EINVAL); + + ptr = devres_alloc(devm_lcdreg_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + *ptr = reg; + devres_add(dev, ptr); + reg->dev = dev; + mutex_init(®->lock); + lcdreg_debugfs_init(reg); + + return reg; +} +EXPORT_SYMBOL(devm_lcdreg_init); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c new file mode 100644 index 0000000..9fcc13d --- /dev/null +++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c @@ -0,0 +1,281 @@ +/* + * 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/lcdreg.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#define READ_RESULT_SIZE 16 + +static struct dentry *lcdreg_debugfs_root; + +static int lcdreg_userbuf_to_u32(const char __user *user_buf, size_t count, + u32 *dest, size_t dest_size) +{ + char *buf, *start; + int ret = -EINVAL; + int i; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, user_buf, count)) { + kfree(buf); + return -EFAULT; + } + + /* turn whitespace into end-of-string for number parsing */ + for (i = 0; i < count; i++) + if (buf[i] == ' ' || buf[i] == '\n' || buf[i] == '\t') + buf[i] = '\0'; + + i = 0; + start = buf; + while (start < buf + count) { + /* skip "whitespace" */ + if (*start == '\0') { + start++; + continue; + } + + if (i == dest_size) { + ret = -EFBIG; + break; + } + + ret = kstrtou32(start, 0, &dest[i++]); + if (ret) + break; + + /* move past this number */ + while (*start != '\0') + start++; + }; + + kfree(buf); + + return ret ? : i; +} + +static ssize_t lcdreg_debugfs_write_file(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct lcdreg *reg = file->private_data; + int ret; + u32 txbuf[128]; + + ret = lcdreg_userbuf_to_u32(user_buf, count, txbuf, ARRAY_SIZE(txbuf)); + if (ret < 0) + return ret; + + mutex_lock(®->lock); + ret = lcdreg_write_buf32(reg, txbuf[0], txbuf + 1, ret - 1); + mutex_unlock(®->lock); + + return ret ? : count; +} + +static const struct file_operations lcdreg_debugfs_write_fops = { + .open = simple_open, + .write = lcdreg_debugfs_write_file, + .llseek = default_llseek, +}; + +static ssize_t lcdreg_debugfs_read_wr(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct lcdreg *reg = file->private_data; + int ret; + + ret = lcdreg_userbuf_to_u32(user_buf, count, + ®->debugfs_read_reg, 1); + + return ret < 0 ? ret : count; +} + + +static int lcdreg_debugfs_readreg(struct lcdreg *reg) +{ + struct lcdreg_transfer tr = { + .index = 1, + .width = reg->debugfs_read_width, + .count = 1, + }; + char *buf = reg->debugfs_read_result; + int ret; + + tr.buf = kmalloc(lcdreg_bytes_per_word(tr.width), GFP_KERNEL); + if (!tr.buf) + return -ENOMEM; + + mutex_lock(®->lock); + ret = lcdreg_read(reg, reg->debugfs_read_reg, &tr); + mutex_unlock(®->lock); + if (ret) + goto error_out; + + switch (tr.width) { + case 8: + snprintf(buf, READ_RESULT_SIZE, "0x%02x\n", *(u8 *)tr.buf); + break; + case 16: + snprintf(buf, READ_RESULT_SIZE, "0x%04x\n", *(u16 *)tr.buf); + break; + case 24: + case 32: + snprintf(buf, READ_RESULT_SIZE, "0x%08x\n", *(u32 *)tr.buf); + break; + default: + ret = -EINVAL; + } + +error_out: + kfree(tr.buf); + + return ret; +} + +static ssize_t lcdreg_debugfs_read_rd(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct lcdreg *reg = file->private_data; + int ret; + + if (*ppos < 0 || !count) + return -EINVAL; + + if (*reg->debugfs_read_result == '\0') { + ret = lcdreg_debugfs_readreg(reg); + if (ret) + return ret; + } + + if (*ppos >= strlen(reg->debugfs_read_result)) { + *reg->debugfs_read_result = '\0'; + return 0; + } + + return simple_read_from_buffer(user_buf, count, ppos, + reg->debugfs_read_result, + strlen(reg->debugfs_read_result)); +} + +static const struct file_operations lcdreg_debugfs_read_fops = { + .open = simple_open, + .read = lcdreg_debugfs_read_rd, + .write = lcdreg_debugfs_read_wr, + .llseek = default_llseek, +}; + +static ssize_t lcdreg_debugfs_reset_wr(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct lcdreg *reg = file->private_data; + + lcdreg_reset(reg); + + return count; +} + +static const struct file_operations lcdreg_debugfs_reset_fops = { + .open = simple_open, + .write = lcdreg_debugfs_reset_wr, + .llseek = default_llseek, +}; + +static int lcdreg_debugfs_readwidth_set(void *data, u64 val) +{ + struct lcdreg *reg = data; + + reg->debugfs_read_width = val; + + return 0; +} + +static int lcdreg_debugfs_readwidth_get(void *data, u64 *val) +{ + struct lcdreg *reg = data; + + /* + * def_width is not set when lcdreg_debugfs_init() is run, it's + * set later by the controller init code. Hence the need for this + * late assignment. + */ + if (!reg->debugfs_read_width) + reg->debugfs_read_width = reg->def_width; + + *val = reg->debugfs_read_width; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(lcdreg_debugfs_readwidth_fops, + lcdreg_debugfs_readwidth_get, + lcdreg_debugfs_readwidth_set, "%llu\n"); + +void lcdreg_debugfs_init(struct lcdreg *reg) +{ + if (IS_ERR_OR_NULL(lcdreg_debugfs_root)) + return; + + reg->debugfs_read_result = devm_kzalloc(reg->dev, READ_RESULT_SIZE, + GFP_KERNEL); + if (!reg->debugfs_read_result) + return; + + reg->debugfs = debugfs_create_dir(dev_name(reg->dev), + lcdreg_debugfs_root); + if (!reg->debugfs) { + dev_warn(reg->dev, "Failed to create debugfs directory\n"); + return; + } + + debugfs_create_file("write", 0220, reg->debugfs, reg, + &lcdreg_debugfs_write_fops); + if (reg->read) { + debugfs_create_file("read_width", 0660, reg->debugfs, reg, + &lcdreg_debugfs_readwidth_fops); + debugfs_create_file("read", 0660, reg->debugfs, reg, + &lcdreg_debugfs_read_fops); + } + if (reg->reset) { + debugfs_create_file("reset", 0220, reg->debugfs, reg, + &lcdreg_debugfs_reset_fops); + } +} + +void lcdreg_debugfs_exit(struct lcdreg *reg) +{ + debugfs_remove_recursive(reg->debugfs); +} + +static int lcdreg_debugfs_module_init(void) +{ + lcdreg_debugfs_root = debugfs_create_dir("lcdreg", NULL); + if (!lcdreg_debugfs_root) + pr_warn("lcdreg: Failed to create debugfs root\n"); + + return 0; +} +module_init(lcdreg_debugfs_module_init); + +static void lcdreg_debugfs_module_exit(void) +{ + debugfs_remove_recursive(lcdreg_debugfs_root); +} +module_exit(lcdreg_debugfs_module_exit); + +MODULE_LICENSE("GPL"); diff --git a/include/drm/tinydrm/lcdreg.h b/include/drm/tinydrm/lcdreg.h new file mode 100644 index 0000000..74e5e50 --- /dev/null +++ b/include/drm/tinydrm/lcdreg.h @@ -0,0 +1,126 @@ +/* + * 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_LCDREG_H +#define __LINUX_LCDREG_H + +#include <linux/device.h> +#include <linux/mutex.h> + +/** + * struct lcdreg_transfer - LCD register transfer + * @index: register index (address) + * Known under the following names: + * D/C (command=0, data=1) + * RS (register selection: index=0, data=1) + * D/I (data/index: index=0, data=1) + * @buf: data array to transfer + * @count: number of items in array + * @width: override default regwidth + */ +struct lcdreg_transfer { + unsigned index; + void *buf; + unsigned count; + unsigned width; +}; + +/** + * struct lcdreg - interface to LCD register + * @dev: device interface + * @lock: mutex for register access locking + * @def_width: default register width + * @bits_per_word_mask: Bitmask of bits per word supported by the hardware. + * The driver can emulate more word widths. + * @readable: register is readable + * @little_endian: register has little endian byte order + * @write: write to register + * @read: read from register (optional) + * @reset: reset controller (optional) + */ +struct lcdreg { + struct device *dev; + struct mutex lock; + unsigned def_width; + bool readable; + bool little_endian; + u32 bits_per_word_mask; +#define LCDREG_BPW_MASK(bits) BIT((bits) - 1) + + int (*write)(struct lcdreg *reg, unsigned regnr, + struct lcdreg_transfer *transfer); + int (*read)(struct lcdreg *reg, unsigned regnr, + struct lcdreg_transfer *transfer); + void (*reset)(struct lcdreg *reg); + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; + u32 debugfs_read_width; + u32 debugfs_read_reg; + char *debugfs_read_result; +#endif +}; + +struct lcdreg *devm_lcdreg_init(struct device *dev, struct lcdreg *reg); +int lcdreg_write(struct lcdreg *reg, unsigned regnr, + struct lcdreg_transfer *transfer); +int lcdreg_write_buf32(struct lcdreg *reg, unsigned regnr, const u32 *data, + unsigned count); + +#define lcdreg_writereg(lcdreg, regnr, seq...) \ +({\ + u32 d[] = { seq };\ + lcdreg_write_buf32(lcdreg, regnr, d, ARRAY_SIZE(d));\ +}) + +int lcdreg_read(struct lcdreg *reg, unsigned regnr, + struct lcdreg_transfer *transfer); +int lcdreg_readreg_buf32(struct lcdreg *reg, unsigned regnr, u32 *buf, + unsigned count); + +static inline void lcdreg_reset(struct lcdreg *reg) +{ + if (reg->reset) + reg->reset(reg); +} + +static inline bool lcdreg_is_readable(struct lcdreg *reg) +{ + return reg->readable; +} + +static inline unsigned lcdreg_bytes_per_word(unsigned bits_per_word) +{ + if (bits_per_word <= 8) + return 1; + else if (bits_per_word <= 16) + return 2; + else /* bits_per_word <= 32 */ + return 4; +} + +static inline bool lcdreg_bpw_supported(struct lcdreg *reg, unsigned bpw) +{ + return LCDREG_BPW_MASK(bpw) & reg->bits_per_word_mask; +} + +#if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG) +static inline void lcdreg_dbg_transfer_buf(struct lcdreg_transfer *tr) +{ + int groupsize = lcdreg_bytes_per_word(tr->width); + size_t len = min_t(size_t, 32, tr->count * groupsize); + + print_hex_dump_debug(" buf=", DUMP_PREFIX_NONE, 32, groupsize, + tr->buf, len, false); +} +#else +static inline void lcdreg_dbg_transfer_buf(struct lcdreg_transfer *tr) { } +#endif /* DEBUG || CONFIG_DYNAMIC_DEBUG */ + +#endif /* __LINUX_LCDREG_H */ -- 2.2.2 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel