电子说
1、概述
tinydrm驱动里合并了panel-mipi-dbi模块。从名字上来看,这个模块是用来驱动MIPI-DBI屏幕用的。TinyDRM对比fbtft有以下优点:
DRM在用户要求刷新屏幕的时候才刷新,而fbtft刷新屏幕是按照固定的帧率进行刷新。
fbtft只能一次刷新整个屏幕,而DRM没有这样的限制。
fbtft在探测(probe)到屏幕后打开它,DRM在使用屏幕的时候才会打开它。
fbtft的rotate值变成了rotation
DRM支持双缓冲和page-flips(应该是一种可以避免图像撕裂现象(tearing effect)的算法)。
DRM支持在GPU中渲染,然后在屏幕上显示。
当然TinyDRM的缺点也是有的:只能使用兼容标准MIPI-DCS命令集的显示IC,非标准命令集的无法使用。这里提供一个简单的办法确定屏幕是否兼容MIPI-DCS指令集:查看屏幕的0x2A,0x2B,0x2C命令对应的操作,如果它们的操作分别是行地址设置(Column address set),列地址设置(Row address set)和显存写入(Memory write),则表示该屏幕应该兼容MIPI-DCS指令集;
本文的知识点主要来源以下连接,本人只是做了简单的整合和汇总,以方便于需要的朋友:
Milk-V Duo tinydrm驱动屏幕(ili9488/st7789) - Duo - MilkV Community
Milk-V Duo SPI驱动点亮屏幕(st7789) - Duo - MilkV Community,,
2、硬件连接:
使用SPI2及3个IO连接到SPI屏,如图所示,红色方框的圈出的管脚连接到SPI屏对应的管脚,红色字体指的是SPI的管脚;
3、修改SDK
1)、下载最新版本的SDK,旧版本的SDK可能需要自己去配置管脚
git clone https://github.com/milkv-duo/duo-buildroot-sdk.git --depth=1
2)、添加ILI9488的驱动代码:
在目录XXX_SDK/linux_5.10/drivers/gpu/drm/tiny/下创建ili9488.c
// SPDX-License-Identifier: GPL-2.0 /* * DRM driver for Ilitek ILI9488 panels * * Copyright 2020 Kamlesh Gurudasani < kamlesh.gurudasani@gmail.com > */ #include < linux/delay.h > #include < linux/dma-buf.h > #include < linux/gpio/consumer.h > #include < linux/module.h > #include < linux/property.h > #include < linux/spi/spi.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_cma_helper.h > #include < drm/drm_gem_framebuffer_helper.h > #include < drm/drm_mipi_dbi.h > #include < drm/drm_rect.h > #define ILI9488_VCOM_CONTROL_1 0xC5 #define ILI9488_COLUMN_ADDRESS_SET 0x2A #define ILI9488_PAGE_ADDRESS_SET 0x2B #define ILI9488_MEMORY_WRITE 0x2C #define ILI9488_POSITIVE_GAMMA_CORRECTION0xE0 #define ILI9488_NEGATIVE_GAMMA_CORRECTION0xE1 #define ILI9488_POWER_CONTROL_1 0xC0 #define ILI9488_POWER_CONTROL_2 0xC1 #define ILI9488_MEMORY_ACCESS_CONTROL 0x36 #define ILI9488_COLMOD_PIXEL_FORMAT_SET 0x3A #define ILI9488_INTERFACE_MODE_CONTROL 0xB0 #define ILI9488_FRAME_RATE_CONTROL_PARTIAL 0xB3 #define ILI9488_DISPLAY_INVERSION_CONTROL0xB4 #define ILI9488_SET_IMAGE_FUNCTION 0xE9 #define ILI9488_ADJUST_CONTROL_3 0xF7 #define ILI9488_ADJUST_CONTROL_3 0xF7 #define ILI9488_DISPLAY_ON 0x29 #define ILI9488_DISPLAY_OFF 0x28 #define ILI9488_ENTER_SLEEP_MODE 0x10 #define ILI9488_DBI_BPP18 0x06 #define ILI9488_DPI_BPP18 0x60 #define ILI9488_FRAME_RATE_CONTROL_NORMAL0xB1 #define ILI9488_SLEEP_OUT 0x11 #define ILI9488_MADCTL_BGRBIT(3) #define ILI9488_MADCTL_MVBIT(5) #define ILI9488_MADCTL_MXBIT(6) #define ILI9488_MADCTL_MYBIT(7) static void ili9488_rgb565_to_rgb666_line(u8 *dst, u16 *sbuf,unsignedint pixels) { unsignedint x; for (x = 0; x < pixels; x++) { *dst++ = ((*sbuf & 0xF800) >> 8); *dst++ = ((*sbuf & 0x07E0) >> 3); *dst++ = ((*sbuf & 0x001F) < < 3); sbuf++; } } static void ili9488_rgb565_to_rgb666(u8 *dst, void *vaddr,struct drm_framebuffer *fb,struct drm_rect *rect) { size_t linepixels = rect- >x2 - rect->x1; size_t src_len = linepixels * sizeof(u16); size_t dst_len = linepixels * 3; unsignedint y, lines = rect->y2 - rect->y1; u16 *sbuf; /* * The cma memory is write-combined so reads are uncached. * Speed up by fetching one line at a time. */ sbuf = kmalloc(src_len, GFP_KERNEL); if (!sbuf) return; vaddr += rect->y1 * fb->pitches[0] + rect->x1 * sizeof(u16); for (y = 0; y < lines; y++) { memcpy(sbuf, vaddr, src_len); ili9488_rgb565_to_rgb666_line(dst, sbuf, linepixels); vaddr += fb- >pitches[0]; dst += dst_len; } kfree(sbuf); } static int ili9488_buf_copy(void *dst, struct drm_framebuffer *fb,struct drm_rect *rect) { struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); struct dma_buf_attachment *import_attach = cma_obj->base.import_attach; struct drm_format_name_buf format_name; void *src = cma_obj->vaddr; int ret = 0; if (import_attach) { ret = dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); if (ret) return ret; } switch (fb->format->format) { case DRM_FORMAT_RGB565: ili9488_rgb565_to_rgb666(dst, src, fb, rect); break; default: dev_err_once(fb->dev->dev, "Format is not supported: %sn", drm_get_format_name(fb->format->format, &format_name)); return -EINVAL; } if (import_attach) ret = dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); return ret; } static void ili9488_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect) { struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(fb->dev); struct mipi_dbi *dbi = &dbidev->dbi; int idx, ret = 0; void *tr; bool full; unsignedint height = rect->y2 - rect->y1; unsignedint width = rect->x2 - rect->x1; // if (!dbidev->enabled) // return; if (!drm_dev_enter(fb->dev, &idx)) return; full = width == fb->width && height == fb->height; DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "n", fb->base.id, DRM_RECT_ARG(rect)); /* Always invoke copy buffer routine as the display supports * only RGB666 format which is not implemented in DRM */ if (!dbi->dc || !full || fb->format->format == DRM_FORMAT_RGB565) { tr = dbidev->tx_buf; ret = ili9488_buf_copy(dbidev->tx_buf, fb, rect); if (ret) goto err_msg; } else { tr = cma_obj->vaddr; } mipi_dbi_command(dbi, ILI9488_COLUMN_ADDRESS_SET, (rect->x1 >> 8) & 0xFF, rect->x1 & 0xFF, (rect->x2 >> 8) & 0xFF, (rect->x2 - 1) & 0xFF); mipi_dbi_command(dbi, ILI9488_PAGE_ADDRESS_SET, (rect->y1 >> 8) & 0xFF, rect->y1 & 0xFF, (rect->y2 >> 8) & 0xFF, (rect->y2 - 1) & 0xFF); ret = mipi_dbi_command_buf(dbi, ILI9488_MEMORY_WRITE, tr, width * height * 3); err_msg: if (ret) dev_err_once(fb->dev->dev, "Failed to update display %dn", ret); drm_dev_exit(idx); } static void ili9488_pipe_update(struct drm_simple_display_pipe *pipe,struct drm_plane_state *old_state) { struct drm_plane_state *state = pipe->plane.state; struct drm_rect rect; if (drm_atomic_helper_damage_merged(old_state, state, &rect)) ili9488_fb_dirty(state->fb, &rect); } 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 drm_framebuffer *fb = plane_state->fb; struct mipi_dbi *dbi = &dbidev->dbi; u8 addr_mode; int ret, idx; struct drm_rect rect = { .x1 = 0, .x2 = fb->width, .y1 = 0, .y2 = fb->height, }; if (!drm_dev_enter(pipe->crtc.dev, &idx)) return; DRM_DEBUG_KMS("n"); ret = mipi_dbi_poweron_conditional_reset(dbidev); if (ret < 0) goto out_exit; if (ret == 1) goto out_enable; mipi_dbi_command(dbi, ILI9488_POSITIVE_GAMMA_CORRECTION, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0a, 0x3f, 0x78, 0x4c, 0x09, 0x0a, 0x08, 0x16, 0x1a, 0x0f); mipi_dbi_command(dbi, ILI9488_NEGATIVE_GAMMA_CORRECTION, 0x00, 0x16, 0x19, 0x03, 0x0f, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0e, 0x0d, 0x35, 0x37, 0x0f); mipi_dbi_command(dbi, ILI9488_POWER_CONTROL_1, 0x17, 0x15); mipi_dbi_command(dbi, ILI9488_POWER_CONTROL_2, 0x41); mipi_dbi_command(dbi, ILI9488_VCOM_CONTROL_1, 0x00, 0x12, 0x80); mipi_dbi_command(dbi, ILI9488_COLMOD_PIXEL_FORMAT_SET, ILI9488_DBI_BPP18 | ILI9488_DPI_BPP18); mipi_dbi_command(dbi, ILI9488_INTERFACE_MODE_CONTROL, 0x80); mipi_dbi_command(dbi, ILI9488_FRAME_RATE_CONTROL_NORMAL, 0xa0); mipi_dbi_command(dbi, ILI9488_DISPLAY_INVERSION_CONTROL, 0x02); mipi_dbi_command(dbi, ILI9488_SET_IMAGE_FUNCTION, 0x00); mipi_dbi_command(dbi, ILI9488_ADJUST_CONTROL_3,0xa9, 0x51, 0x2c, 0x82); mipi_dbi_command(dbi, ILI9488_SLEEP_OUT); msleep(120); mipi_dbi_command(dbi, ILI9488_DISPLAY_ON); // dbidev- >enabled = true; ili9488_fb_dirty(fb, &rect); out_enable: switch (dbidev->rotation) { default: addr_mode = ILI9488_MADCTL_MX; break; case 90: addr_mode = ILI9488_MADCTL_MV; break; case 180: addr_mode = ILI9488_MADCTL_MY; break; case 270: 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); out_exit: drm_dev_exit(idx); } static void ili9488_pipe_disable(struct drm_simple_display_pipe *pipe) { struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev); /* * This callback is not protected by drm_dev_enter/exit since we want to * turn off the display on regular driver unload. It's highly unlikely * that the underlying SPI controller is gone should this be called * after unplug. */ DRM_DEBUG_KMS("n"); // if (!dbidev->enabled) // return; mipi_dbi_command(&dbidev->dbi, MIPI_DCS_SET_DISPLAY_OFF); // dbidev->enabled = false; } static const u32 ili9488_formats[] = { DRM_FORMAT_RGB565, }; static const struct drm_simple_display_pipe_funcs ili9488_pipe_funcs = { .enable = ili9488_pipe_enable, .disable = ili9488_pipe_disable, .update = ili9488_pipe_update, .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, }; static const struct drm_display_mode ili9488_mode = { DRM_SIMPLE_MODE(320, 480, 49, 73), }; 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, .debugfs_init = mipi_dbi_debugfs_init, .name = "ili9488", .desc = "Ilitek ILI9488", .date = "20200607", .major = 1, .minor = 0, }; static const struct of_device_id ili9488_of_match[] = { { .compatible = "eastrising,er-tft035-6" }, { } }; MODULE_DEVICE_TABLE(of, ili9488_of_match); static const struct spi_device_id ili9488_id[] = { { "er-tft035-6", 0 }, { } }; MODULE_DEVICE_TABLE(spi, ili9488_id); static int ili9488_probe(struct spi_device *spi) { struct device *dev = &spi->dev; struct mipi_dbi_dev *dbidev; struct drm_device *drm; struct mipi_dbi *dbi; struct gpio_desc *dc; u32 rotation = 0; size_t bufsize; int ret; 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; // ret = devm_drm_dev_init(dev, drm, &ili9488_driver); // if (ret) { // kfree(dbidev); // return ret; // } drm_mode_config_init(drm); bufsize = ili9488_mode.vdisplay * ili9488_mode.hdisplay * 3; 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); } dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW); if (IS_ERR(dc)) { DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'n"); return PTR_ERR(dc); } dbidev->backlight = devm_of_find_backlight(dev); if (IS_ERR(dbidev->backlight)) return PTR_ERR(dbidev->backlight); device_property_read_u32(dev, "rotation", &rotation); ret = mipi_dbi_spi_init(spi, dbi, dc); if (ret) return ret; dbidev->drm.mode_config.preferred_depth = 16; ret = mipi_dbi_dev_init_with_formats(dbidev, &ili9488_pipe_funcs, ili9488_formats, ARRAY_SIZE(ili9488_formats), &ili9488_mode, rotation, bufsize); if (ret) return ret; drm_mode_config_reset(drm); ret = drm_dev_register(drm, 0); if (ret) return ret; spi_set_drvdata(spi, drm); drm_fbdev_generic_setup(drm, 0); return 0; } static int ili9488_remove(struct spi_device *spi) { struct drm_device *drm = spi_get_drvdata(spi); drm_dev_unplug(drm); drm_atomic_helper_shutdown(drm); return 0; } static void ili9488_shutdown(struct spi_device *spi) { drm_atomic_helper_shutdown(spi_get_drvdata(spi)); } static struct spi_driver ili9488_spi_driver = { .driver = { .name = "ili9488", .owner = THIS_MODULE, .of_match_table = ili9488_of_match, }, .id_table = ili9488_id, .probe = ili9488_probe, .remove = ili9488_remove, .shutdown = ili9488_shutdown, }; module_spi_driver(ili9488_spi_driver); MODULE_DESCRIPTION("Ilitek ILI9488 DRM driver"); MODULE_AUTHOR("Kamlesh Gurudasani < kamlesh.gurudasani@gmail.com >"); MODULE_LICENSE("GPL");
3)、修改/linux_5.10/drivers/gpu/drm/tiny/Kconfig文件: 在Kconfig底部添加
config TINYDRM_ILI9488
tristate "DRM support for ILI9488 display panels" depends on DRM && SPI select DRM_KMS_HELPER select DRM_KMS_CMA_HELPER select DRM_MIPI_DBI select BACKLIGHT_CLASS_DEVICE help DRM driver for the following Ilitek ILI9488 panel If M is selected the module will be called ili9488
4)、修改/linux_5.10/drivers/gpu/drm/tiny/Makefile文件,底部添加
obj-$(CONFIG_TINYDRM_ILI9488) += ili9488.o
5)、修改/build/boards/cv180x/cv1800b_milkv_duo_sd/linux/cvitek_cv1800b_milkv_duo_sd_defconfig文件: 底部添加下述内容,注意上层文件夹也有一个同名的文件,别修改错了文件;
CONFIG_TTY=y CONFIG_VT=y CONFIG_CONSOLE_TRANSLATIONS=y CONFIG_VT_CONSOLE=y CONFIG_HW_CONSOLE=y CONFIG_VT_HW_CONSOLE_BINDING=y CONFIG_FB_CMDLINE=y CONFIG_FB_NOTIFY=y CONFIG_FONT_SUPPORT=y CONFIG_FONTS=y CONFIG_FONT_8x16=y CONFIG_VGA_CONSOLE=y CONFIG_DUMMY_CONSOLE=y CONFIG_DUMMY_CONSOLE_COLUMNS=80 CONFIG_DUMMY_CONSOLE_ROWS=25 CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y CONFIG_DRM=y CONFIG_DRM_MIPI_DBI=yCONFIG_DRM_KMS_HELPER=y CONFIG_DRM_KMS_FB_HELPER=y CONFIG_DRM_FBDEV_EMULATION=y CONFIG_DRM_FBDEV_OVERALLOC=100 CONFIG_DRM_GEM_CMA_HELPER=y CONFIG_DRM_KMS_CMA_HELPER=y CONFIG_DRM_PANEL=y CONFIG_DRM_BRIDGE=y CONFIG_DRM_PANEL_BRIDGE=y CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y CONFIG_TINYDRM_ILI9488=y
6)、 修改/build/boards/cv180x/cv1800b_milkv_duo_sd/dts_riscv/cv1800b_milkv_duo_sd.dts文件,注意,修改&SPI2部分即可
/dts-v1/; #include "cv180x_base_riscv.dtsi" #include "cv180x_asic_qfn.dtsi" #include "cv180x_asic_sd.dtsi" #include "cv180x_default_memmap.dtsi" &mipi_rx{ snsr-reset = < &portc 8 GPIO_ACTIVE_LOW >, < &portc 8 GPIO_ACTIVE_LOW >, < &portc 8 GPIO_ACTIVE_LOW >; }; &spi2 { status = "okay"; /delete-node/ spidev@0; display@0{ compatible = "eastrising,er-tft035-6"; //匹配驱动名称,使用st7789的话修改为sitronix,st7789v_240x320 reg = < 0 >; status = "okay"; spi-max-frequency = < 80000000 >; spi-cpol; spi-cpha; dc-gpios = < &porta 24 GPIO_ACTIVE_HIGH >; //GPIOA_24做DC脚 reset-gpios = < &porta 23 GPIO_ACTIVE_HIGH >; //GPIOA_23做Reset脚 rotation = < 180 >; }; }; &uart4 { status = "okay"; }; &i2c1 { status = "okay"; clock-frequency = < 100000 >; }; / { };
7) 、修改build/boards/default/dts/cv180x/cv180x_base.dtsi
spi2:spi2@041A0000 {
compatible = "snps,dw-apb-ssi";
reg = <0x0 0x041A0000 0x0 0x10000>;
clocks = <&clk CV180X_CLK_SPI>;
#address-cells = <1>;
#size-cells = <0>;
bias-pull-up; //添加本行,作用是端口上拉
};
8) 、修改 u-boot-2021.10includeconfigscv180x-asic.h
搜到
#define SET_BOOTARGS "setenv bootargs ${root} ${mtdparts} " “console=$consoledev,$baudrate $othbootargs;”
将其替换为为:
#define SET_BOOTARGS "setenv bootargs ${root} ${mtdparts} " “console=tty0 console=$consoledev,$baudrate $othbootargs;”
注意标点符号必须为英文类型,否则会发生编译错误;
编译SDK:
cd duo-buildroot-sdk/
./build.sh
4、下载,上电:
如果屏幕没有输出请使用cvi_pinmux工具检查管脚配置是否正确,如果管脚不正确,请自行配置管脚,如果顺利,就会串口输出的内容同时在屏幕上显示;
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !