From 7b453164dd98bae0120dcddc8e4e981447d4259a Mon Sep 17 00:00:00 2001 From: Gurjant Kalsi Date: Wed, 16 Dec 2015 11:57:01 -0800 Subject: [PATCH] [qemu][m4][gfx][display] Get the VNC based LCD working on the qemu-m4 port. --- include/lib/gfx.h | 1 + lib/gfx/gfx.c | 23 ++- scripts/do-qemum4 | 5 +- target/qemu-m4/include/target/m4display.h | 26 +++ target/qemu-m4/init.c | 11 +- target/qemu-m4/m4display.c | 217 ++++++++++++++++++++++ target/qemu-m4/rules.mk | 6 +- 7 files changed, 280 insertions(+), 9 deletions(-) create mode 100644 target/qemu-m4/include/target/m4display.h create mode 100644 target/qemu-m4/m4display.c diff --git a/include/lib/gfx.h b/include/lib/gfx.h index 99f3cc38..8c68daa8 100644 --- a/include/lib/gfx.h +++ b/include/lib/gfx.h @@ -34,6 +34,7 @@ typedef enum { GFX_FORMAT_NONE, GFX_FORMAT_RGB_565, GFX_FORMAT_RGB_332, + GFX_FORMAT_RGB_2220, GFX_FORMAT_ARGB_8888, GFX_FORMAT_RGB_x888, GFX_FORMAT_MONO, diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c index 1e5622f9..2b0f1a3b 100644 --- a/lib/gfx/gfx.c +++ b/lib/gfx/gfx.c @@ -83,6 +83,17 @@ static uint32_t ARGB8888_to_RGB332(uint32_t in) return out; } +static uint32_t ARGB8888_to_RGB2220(uint32_t in) +{ + uint8_t out = 0; + + out = ((in >> 6) & 0x3) << 2; + out |= ((in >> 14) & 0x3) << 4; + out |= ((in >> 22) & 0x3) << 6; + + return out; +} + /** * @brief Copy a rectangle of pixels from one part of the display to another. */ @@ -333,7 +344,7 @@ static void fillrect32(gfx_surface *surface, uint x, uint y, uint width, uint he } } -void gfx_line(gfx_surface* surface, uint x1, uint y1, uint x2, uint y2, uint color) +void gfx_line(gfx_surface *surface, uint x1, uint y1, uint x2, uint y2, uint color) { if (unlikely(x1 >= surface->width)) return; @@ -618,6 +629,14 @@ gfx_surface *gfx_create_surface(void *ptr, uint width, uint height, uint stride, surface->pixelsize = 1; surface->len = (surface->height * surface->stride * surface->pixelsize); break; + case GFX_FORMAT_RGB_2220: + surface->translate_color = &ARGB8888_to_RGB2220; + surface->copyrect = ©rect8; + surface->fillrect = &fillrect8; + surface->putpixel = &putpixel8; + surface->pixelsize = 1; + surface->len = (surface->height * surface->stride * surface->pixelsize); + break; default: dprintf(INFO, "invalid graphics format\n"); DEBUG_ASSERT(0); @@ -641,7 +660,7 @@ gfx_surface *gfx_create_surface(void *ptr, uint width, uint height, uint stride, */ gfx_surface *gfx_create_surface_from_display(struct display_info *info) { - gfx_surface* surface; + gfx_surface *surface; surface = gfx_create_surface(info->framebuffer, info->width, info->height, info->stride, info->format); surface->flush = info->flush; diff --git a/scripts/do-qemum4 b/scripts/do-qemum4 index 91793ec9..c776657a 100755 --- a/scripts/do-qemum4 +++ b/scripts/do-qemum4 @@ -40,11 +40,12 @@ fi PROJECT="qemu-m4-test" -ARGS=" -serial tcp::12345,server,nowait " # Logs -ARGS+=" -serial tcp::12344,server,nowait " # Debug +ARGS=" -serial tcp::12345,server,nowait " # Logs +ARGS+=" -serial tcp::12344,server,nowait " # Debug ARGS+=" -serial stdio" # Console ARGS+=" -machine pebble-s4-bb -cpu cortex-m4" ARGS+=" -pflash build-${PROJECT}/lk.bin " +ARGS+=" -monitor tcp::12346,server,nowait" if [ $DO_CMPCTMALLOC == 1 ]; then MAKE_VARS=LK_HEAP_IMPLEMENTATION=cmpctmalloc diff --git a/target/qemu-m4/include/target/m4display.h b/target/qemu-m4/include/target/m4display.h new file mode 100644 index 00000000..e51fd283 --- /dev/null +++ b/target/qemu-m4/include/target/m4display.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015 Gurjant Kalsi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +void init_display(void); diff --git a/target/qemu-m4/init.c b/target/qemu-m4/init.c index dcff892d..c0bb36f7 100644 --- a/target/qemu-m4/init.c +++ b/target/qemu-m4/init.c @@ -32,17 +32,20 @@ #include #include #include +#include void target_early_init(void) { - stm32_debug_early_init(); + stm32_debug_early_init(); } void target_init(void) { - TRACE_ENTRY; + TRACE_ENTRY; - stm32_debug_init(); + stm32_debug_init(); - TRACE_EXIT; + init_display(); + + TRACE_EXIT; } \ No newline at end of file diff --git a/target/qemu-m4/m4display.c b/target/qemu-m4/m4display.c new file mode 100644 index 00000000..c038dfed --- /dev/null +++ b/target/qemu-m4/m4display.c @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2015 Gurjant Kalsi + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define LOCAL_TRACE 0 + +#define CMD_DISPLAY_NULL 0x00 +#define CMD_DISPLAY_SET_PARAM 0x01 +#define CMD_DISPLAY_OFF 0x02 +#define CMD_DISPLAY_ON 0x03 +#define CMD_DISPLAY_DRAW_SCENE 0x04 +#define CMD_DISPLAY_FRAME_BEGIN 0x05 + +#define SCENE_BLACK 0x00 +#define SCENE_SPLASH 0x01 +#define SCENE_UPDATE 0x02 +#define SCENE_ERROR 0x03 + +#define M4DISPLAY_WIDTH 180 +#define M4DISPLAY_HEIGHT 180 + +static uint8_t framebuffer[M4DISPLAY_HEIGHT][M4DISPLAY_WIDTH]; + +static const char programming_header[] = " Lattice\0iCEcube2 2014.08.26723\0Part: iCE40LP1K-CM36\0Date: Jan 30 2015 15:11:"; + +static void chip_select(bool val) +{ + if (val) { + gpio_set(GPIO(GPIO_PORT_G, 8), true); + } else { + gpio_set(GPIO(GPIO_PORT_G, 8), false); + } +} + +static void reset(bool val) +{ + if (val) { + gpio_set(GPIO(GPIO_PORT_G, 15), true); + } else { + gpio_set(GPIO(GPIO_PORT_G, 15), false); + } +} + +static void setup_pins(void) +{ + + GPIO_InitTypeDef GPIO_InitStruct; + + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); + + GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(GPIOA, &GPIO_InitStruct); + + // connect SPI6 pins to SPI alternate function + GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI6); + GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI6); + GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI6); + + // enable clock for used IO pins + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); + + /* Configure the chip select pin + in this case we will use PE7 */ + GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(GPIOG, &GPIO_InitStruct); + + GPIOE->BSRRL |= GPIO_Pin_7; // set PE7 high + + // Setup display CS Pin + gpio_config(GPIO(GPIO_PORT_G, 8), GPIO_OUTPUT); + + // Setup display reset pin + gpio_config(GPIO(GPIO_PORT_G, 15), GPIO_OUTPUT); + + // enable peripheral clock + RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI6, ENABLE); +} + +void init_display(void) +{ + // Setting up pins. + setup_pins(); + + SPI_InitTypeDef init_struct; + + init_struct.SPI_Direction = SPI_Direction_1Line_Tx; + init_struct.SPI_Mode = SPI_Mode_Master; + init_struct.SPI_DataSize = SPI_DataSize_8b; + init_struct.SPI_CPOL = SPI_CPOL_Low; + init_struct.SPI_CPHA = SPI_CPHA_1Edge; + init_struct.SPI_NSS = SPI_NSS_Hard; + init_struct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; + init_struct.SPI_FirstBit = SPI_FirstBit_MSB; + + SPI_Init(SPI6, &init_struct); + + SPI_Cmd(SPI6, ENABLE); // enable SPI6 + + chip_select(true); + reset(true); + chip_select(false); + + const uint8_t draw_splash_cmds[] = { 0x04, 0x01 }; + + for (size_t i = 0; i < countof(draw_splash_cmds); i++) { + SPI_I2S_SendData(SPI6, draw_splash_cmds[i]); + } + + chip_select(true); + chip_select(false); + + uint8_t enable_display_cmds[] = { 0x03 }; + + for (size_t i = 0; i < countof(enable_display_cmds); i++) { + SPI_I2S_SendData(SPI6, enable_display_cmds[i]); + } + + chip_select(true); + reset(false); + chip_select(false); + reset(true); + + + for (size_t i = 0; i < countof(programming_header); i++) { + SPI_I2S_SendData(SPI6, programming_header[i]); + } + + chip_select(true); +} + +static void s4lcd_flush(uint starty, uint endy) +{ + + chip_select(false); + + uint8_t frame_begin_cmd[] = { 0x05 }; + + for (size_t i = 0; i < countof(frame_begin_cmd); i++) { + SPI_I2S_SendData(SPI6, frame_begin_cmd[i]); + } + + size_t msb_idx = M4DISPLAY_WIDTH / 2; + + uint8_t scramble_buf[M4DISPLAY_WIDTH]; + for (int i = 0; i < M4DISPLAY_HEIGHT; i++) { + uint8_t *lsb = scramble_buf; + uint8_t *msb = &(scramble_buf[msb_idx]); + for (int j = 0; j < M4DISPLAY_WIDTH; j += 2) { + + *msb++ = ((framebuffer[i][j] & 0b01010100) | ((framebuffer[i][j+1] & 0b01010100) << 1)) >> 2; + *lsb++ = ((framebuffer[i][j] & 0b10101000) | ((framebuffer[i][j+1] & 0b10101000) >> 1)) >> 2; + } + + for (int j = 0; j < M4DISPLAY_WIDTH; j++) { + SPI_I2S_SendData(SPI6, scramble_buf[j]); + } + } + + chip_select(true); +} + +status_t display_get_info(struct display_info *info) +{ + LTRACEF("display_info %p\n", info); + + info->framebuffer = (void *)framebuffer; + info->format = GFX_FORMAT_RGB_2220; + info->width = M4DISPLAY_WIDTH; + info->height = M4DISPLAY_HEIGHT; + info->stride = M4DISPLAY_WIDTH; + info->flush = s4lcd_flush; + + return NO_ERROR; +} diff --git a/target/qemu-m4/rules.mk b/target/qemu-m4/rules.mk index 705e25b7..253c56dc 100644 --- a/target/qemu-m4/rules.mk +++ b/target/qemu-m4/rules.mk @@ -16,7 +16,11 @@ GLOBAL_DEFINES += \ PKTBUF_POOL_SIZE=16 MODULE_SRCS += \ - $(LOCAL_DIR)/init.c + $(LOCAL_DIR)/init.c \ + $(LOCAL_DIR)/m4display.c + +MODULE_DEPS += \ + lib/gfx include make/module.mk