Skip to content

Commit

Permalink
ODROID-COMMON: gpu/drm: Add new Tiny DRM driver with Ili9488
Browse files Browse the repository at this point in the history
Signed-off-by: Dongjin Kim <tobetter@gmail.com>
Change-Id: I6e4f783ab642cc735ef445d010afb1cd930757c6
  • Loading branch information
tobetter committed Dec 6, 2022
1 parent 42ef2d8 commit 20a18f4
Show file tree
Hide file tree
Showing 3 changed files with 380 additions and 0 deletions.
13 changes: 13 additions & 0 deletions drivers/gpu/drm/tiny/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ config TINYDRM_ILI9486

If M is selected the module will be called ili9486.

config TINYDRM_ILI9488_PIO
tristate "DRM support for ILI9488 display panels (8bit PIO)"
depends on DRM
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
select DRM_MIPI_DBI
select BACKLIGHT_CLASS_DEVICE
help
DRM driver for th following Ilitek ILI9488 panels:
* Hardkernel 3.5" 480x320 TFT (HKTFT 3.5")

If M is selected the module will be called hktft35.

config TINYDRM_MI0283QT
tristate "DRM support for MI0283QT"
depends on DRM && SPI
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/tiny/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ obj-$(CONFIG_TINYDRM_ILI9163) += ili9163.o
obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o
obj-$(CONFIG_TINYDRM_ILI9341) += ili9341.o
obj-$(CONFIG_TINYDRM_ILI9486) += ili9486.o
obj-$(CONFIG_TINYDRM_ILI9488_PIO) += ili9488_pio.o
obj-$(CONFIG_TINYDRM_MI0283QT) += mi0283qt.o
obj-$(CONFIG_TINYDRM_REPAPER) += repaper.o
obj-$(CONFIG_TINYDRM_ST7586) += st7586.o
Expand Down
366 changes: 366 additions & 0 deletions drivers/gpu/drm/tiny/ili9488_pio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DRM driver for Hardkernel 3.5 ISP TFT display
*
* Copyright 2021 Dongjin Kim <tobetter@gmail.com>
*
*/

#include <linux/delay.h>
#include <linux/dma-buf.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <video/mipi_display.h>

#include <drm/drm_atomic_helper.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_mipi_dbi.h>
#include <drm/drm_rect.h>

#define ILI9488_COLUMN_ADDR 0x2a
#define ILI9488_PAGE_ADDR 0x2b
#define ILI9488_MEMORY_WRITE 0x2c
#define ILI9488_ITFCTR1 0xb0
#define ILI9488_FRMCTR1 0xb1
#define ILI9488_PWCTRL1 0xc2
#define ILI9488_VMCTRL1 0xc5
#define ILI9488_PGAMCTRL 0xe0
#define ILI9488_NGAMCTRL 0xe1
#define ILI9488_MADCTL_BGR BIT(3)
#define ILI9488_MADCTL_MV BIT(5)
#define ILI9488_MADCTL_MX BIT(6)
#define ILI9488_MADCTL_MY BIT(7)

struct ili9488_data {
struct mipi_dbi_dev *dbidev;
struct gpio_desc *wr;
struct gpio_desc *cs;
struct gpio_desc *db[8];
void __iomem *membase;
u32 mask;
u32 bits[8];
u32 bits_wr;
};

static struct ili9488_data *pdata;
static u32 *rgb;

static u32 ili9488_rgb565_to_gpiobus(struct ili9488_data *pdata, u8 color)
{
int i;
u32 value = 0;

for (i = 0; i < 8; i++) {
if (color & 1)
value |= pdata->bits[i];
else
value &= ~(pdata->bits[i]);
color >>= 1;
}

return value;
}

static int ili9488_bus_write(struct mipi_dbi *dbi, u8 data)
{
int i;

if (pdata->membase) {
u32 v = (readl(pdata->membase) & ~pdata->mask) | *(rgb + data);
writel(v, pdata->membase);
writel(v | pdata->bits_wr, pdata->membase);
return 0;
}

gpiod_set_value(pdata->wr, 0);
for (i = 0; i < 8; i++) {
gpiod_set_value(pdata->db[i], data & 1);
data >>= 1;
}
gpiod_set_value(pdata->wr, 1);

return 0;
}

static int ili9488_command(struct mipi_dbi *dbi, u8 *cmd, u8 *par, size_t num)
{
u8 *p = par;

gpiod_set_value(dbi->dc, 0);
ili9488_bus_write(dbi, *cmd);
gpiod_set_value(dbi->dc, 1);

while (num--)
ili9488_bus_write(dbi, *p++);

return 0;
}

static void ili9488_pipe_enable(struct drm_simple_display_pipe *pipe,
struct drm_crtc_state *crtc_state,
struct drm_plane_state *plane_state)
{
struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
struct mipi_dbi *dbi = &dbidev->dbi;
u8 addr_mode;
int ret, idx;

if (!drm_dev_enter(pipe->crtc.dev, &idx))
return;

ret = mipi_dbi_poweron_reset(dbidev);
if (ret < 0)
goto out_exit;
if (ret == 1)
goto out_enable;

gpiod_set_value(pdata->cs, 0);

mipi_dbi_command(dbi, ILI9488_ITFCTR1, 0x00);
mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
msleep(250);

mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
mipi_dbi_command(dbi, ILI9488_PWCTRL1, 0x33);
mipi_dbi_command(dbi, ILI9488_VMCTRL1, 0x00, 0x1e, 0x80, 0x00);
mipi_dbi_command(dbi, ILI9488_FRMCTR1, 0xb0, 0x11);
mipi_dbi_command(dbi, ILI9488_PGAMCTRL,
0x00, 0x04, 0x0e, 0x08, 0x17, 0x0a, 0x40, 0x79,
0x4d, 0x07, 0x0e, 0x0a, 0x1a, 0x1d, 0x0f);
mipi_dbi_command(dbi, ILI9488_NGAMCTRL,
0x00, 0x1b, 0x1f, 0x02, 0x10, 0x05, 0x32, 0x34,
0x43, 0x02, 0x0a, 0x09, 0x33, 0x37, 0x0f);
mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
msleep(100);

out_enable:
switch (dbidev->rotation) {
case 90:
addr_mode = ILI9488_MADCTL_MY;
break;
case 180:
addr_mode = ILI9488_MADCTL_MV;
break;
case 270:
addr_mode = ILI9488_MADCTL_MX;
break;
default:
addr_mode = ILI9488_MADCTL_MV | ILI9488_MADCTL_MY |
ILI9488_MADCTL_MX;
break;
}

addr_mode |= ILI9488_MADCTL_BGR;
mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
mipi_dbi_enable_flush(dbidev, crtc_state, plane_state);

out_exit:
drm_dev_exit(idx);
}

static const struct drm_simple_display_pipe_funcs ili9488_pipe_funcs = {
.enable = ili9488_pipe_enable,
.disable = mipi_dbi_pipe_disable,
.update = mipi_dbi_pipe_update,
.prepare_fb = drm_gem_simple_display_pipe_prepare_fb,
};

static const struct drm_display_mode ili9488_mode = {
DRM_SIMPLE_MODE(480, 320, 73, 49),
};

DEFINE_DRM_GEM_CMA_FOPS(ili9488_fops);

static struct drm_driver ili9488_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &ili9488_fops,
DRM_GEM_CMA_DRIVER_OPS_VMAP,
.name = "ili9488",
.desc = "Ilitek ILI9488",
.date = "20210201",
.major = 1,
.minor = 0,
};

static int ili9488_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct mipi_dbi *dbi;
struct drm_device *drm;
struct mipi_dbi_dev *dbidev;
int ret;
int i;
u32 rotation = 0;
struct resource res;
char str[32];

pdata = devm_kzalloc(dev, sizeof(struct ili9488_data), GFP_KERNEL);
if (!pdata)
return -ENOMEM;

dbidev = devm_drm_dev_alloc(dev, &ili9488_driver,
struct mipi_dbi_dev, drm);
if (IS_ERR(dbidev))
return PTR_ERR(dbidev);

dbi = &dbidev->dbi;
drm = &dbidev->drm;

dbi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(dbi->reset)) {
DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
return PTR_ERR(dbi->reset);
}

dbi->dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
if (IS_ERR(dbi->dc)) {
DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n");
return PTR_ERR(dbi->dc);
}

pdata->wr = devm_gpiod_get(dev, "wr", GPIOD_OUT_HIGH);
if (IS_ERR(pdata->wr)) {
DRM_DEV_ERROR(dev, "Failed to get gpio 'wr'\n");
return PTR_ERR(pdata->wr);
}

pdata->cs = devm_gpiod_get(dev, "cs", GPIOD_OUT_LOW);
if (IS_ERR(pdata->cs)) {
DRM_DEV_ERROR(dev, "Failed to get gpio 'cs'\n");
return PTR_ERR(pdata->cs);
}

for (i = 0; i < 8; i++) {
struct gpio_desc *desc;
int gpio = of_get_named_gpio(np, "db-gpios", i);
if (gpio < 0)
break; /* FIXME */

desc = gpio_to_desc(gpio);

devm_gpio_request(dev, gpio, NULL);
gpiod_direction_output(desc, 1);

pdata->db[i] = desc;
}

ret = of_address_to_resource(np, 0, &res);
if (!ret) {
pdata->membase = devm_ioremap(dev, res.start,
resource_size(&res));
if (!IS_ERR(pdata->membase)) {
for (i = 0; i < 8; i++) {
sprintf(str, "db-bits-%d", i);
ret = of_property_read_u32(np, str,
&pdata->bits[i]);
if (ret)
continue;
pdata->mask |= pdata->bits[i];
}

ret = of_property_read_u32(np, "db-bits-wr",
&pdata->bits_wr);
if (!ret)
pdata->mask |= pdata->bits_wr;
}
}

dbidev->backlight = devm_of_find_backlight(dev);
if (IS_ERR(dbidev->backlight))
return PTR_ERR(dbidev->backlight);

device_property_read_u32(dev, "rotation", &rotation);

rgb = devm_kzalloc(dev, sizeof(u32) * 256, GFP_KERNEL);
if (rgb) {
u32 *p = rgb;
for (i = 0; i < 256; i++)
*p++ = ili9488_rgb565_to_gpiobus(pdata, i);
}

gpiod_set_value(pdata->wr, 1);
gpiod_set_value(dbi->dc, 0);

/* override the command function set in mipi_dbi_spi_init() */
dbi->command = ili9488_command;
dbi->read_commands = NULL;
dbi->swap_bytes = true;

ret = mipi_dbi_dev_init(dbidev, &ili9488_pipe_funcs,
&ili9488_mode, rotation);
if (ret)
return ret;

drm_mode_config_reset(drm);

ret = drm_dev_register(drm, 0);
if (ret)
return ret;

platform_set_drvdata(pdev, pdata);
pdata->dbidev = dbidev;

drm_fbdev_generic_setup(drm, 0);

return 0;
}

static int ili9488_remove(struct platform_device *pdev)
{
struct ili9488_data *pdata = platform_get_drvdata(pdev);
struct mipi_dbi_dev *dbidev = pdata->dbidev;
struct drm_device *drm = &dbidev->drm;

drm_dev_unplug(drm);
drm_atomic_helper_shutdown(drm);

return 0;
}

static const struct of_device_id ili9488_dt_ids[] = {
{ .compatible = "ili9488", 0 },
{ .compatible = "hardkernel,ili9488", 0 },
{ },
};

MODULE_DEVICE_TABLE(of, ili9488_dt_ids);

static struct platform_driver ili9488_platform_driver = {
.driver = {
.name = "ili9488",
.of_match_table = ili9488_dt_ids,
},
.probe = ili9488_probe,
.remove = ili9488_remove,
};

static int __init ili9488_init(void)
{
return platform_driver_register(&ili9488_platform_driver);
}

static void __exit ili9488_exit(void)
{
platform_driver_unregister(&ili9488_platform_driver);
}

module_init(ili9488_init);
module_exit(ili9488_exit);

MODULE_DESCRIPTION("Ilitek ILI9488 DRM driver (8bit PIO mode)");
MODULE_AUTHOR("Dongjin Kim <tobetter@gmail.com>");
MODULE_LICENSE("GPL");

0 comments on commit 20a18f4

Please sign in to comment.