Landfall

Annotated pkg/landfall-examples/qi_lb60_lcd/qi_lb60_lcd.c

0:89a1bc19c1fc
2018-05-13 Paul Boddie Added device libraries and programs, configuration files and examples. Also added an installation script and copyright and licensing information.
paul@0 1
/*
paul@0 2
 * Access the LCD and related GPIOs on the Ben NanoNote.
paul@0 3
 *
paul@0 4
 * (c) 2017, 2018 Paul Boddie <paul@boddie.org.uk>
paul@0 5
 *
paul@0 6
 * This program is free software; you can redistribute it and/or
paul@0 7
 * modify it under the terms of the GNU General Public License as
paul@0 8
 * published by the Free Software Foundation; either version 2 of
paul@0 9
 * the License, or (at your option) any later version.
paul@0 10
 *
paul@0 11
 * This program is distributed in the hope that it will be useful,
paul@0 12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
paul@0 13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
paul@0 14
 * GNU General Public License for more details.
paul@0 15
 *
paul@0 16
 * You should have received a copy of the GNU General Public License
paul@0 17
 * along with this program; if not, write to the Free Software
paul@0 18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
paul@0 19
 * Boston, MA  02110-1301, USA
paul@0 20
 */
paul@0 21
paul@0 22
#include <l4/devices/cpm-jz4740.h>
paul@0 23
#include <l4/devices/gpio-jz4740.h>
paul@0 24
#include <l4/io/io.h>
paul@0 25
#include <l4/re/env.h>
paul@0 26
#include <l4/re/c/mem_alloc.h>
paul@0 27
#include <l4/re/c/rm.h>
paul@0 28
#include <l4/re/c/util/cap_alloc.h>
paul@0 29
#include <l4/sys/cache.h>
paul@0 30
#include <l4/util/util.h>
paul@0 31
#include <l4/vbus/vbus.h>
paul@0 32
paul@0 33
#include <stdio.h>
paul@0 34
#include <stdint.h>
paul@0 35
paul@0 36
#include "nanonote_gpm940b0.h"
paul@0 37
#include "jzlcd.h"
paul@0 38
#include "xburst_types.h"
paul@0 39
#include "memory.h"
paul@0 40
paul@0 41
enum Jz4740_lcd_gpio
paul@0 42
{
paul@0 43
  Jz4740_lcd_gpio_spen = 21, /* serial command enable */
paul@0 44
  Jz4740_lcd_gpio_spda = 22, /* serial command clock */
paul@0 45
  Jz4740_lcd_gpio_spck = 23, /* serial command data */
paul@0 46
};
paul@0 47
paul@0 48
/* Peripheral memory regions. */
paul@0 49
paul@0 50
static l4_addr_t cpm_virt_base = 0, cpm_virt_base_end = 0;
paul@0 51
static l4_addr_t gpio_virt_base = 0, gpio_virt_base_end = 0;
paul@0 52
static l4_addr_t lcd_virt_base = 0, lcd_virt_base_end = 0;
paul@0 53
paul@0 54
/* Device abstractions. */
paul@0 55
paul@0 56
static void *gpio_port_c;
paul@0 57
static void *cpm_device = 0;
paul@0 58
static vidinfo_t *panel_info = 0;
paul@0 59
paul@0 60
/* Framebuffer and descriptor regions. */
paul@0 61
paul@0 62
static void *fb_vaddr = 0, *desc_vaddr = 0;
paul@0 63
static l4_addr_t fb_paddr = 0, desc_paddr = 0;
paul@0 64
static l4_size_t fb_size, desc_size;
paul@0 65
paul@0 66
paul@0 67
paul@0 68
// Write SPI values via the LCD GPIO pins.
paul@0 69
paul@0 70
static void spi_write_reg(uint8_t reg, uint8_t val)
paul@0 71
{
paul@0 72
  uint8_t no;
paul@0 73
  uint16_t value;
paul@0 74
paul@0 75
  jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spen, 1);
paul@0 76
  jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spck, 1);
paul@0 77
  jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spda, 0);
paul@0 78
  jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spen, 0);
paul@0 79
paul@0 80
  value = ((reg << 8) | (val & 0xFF));
paul@0 81
paul@0 82
  /* Clock data using the clock and data outputs. */
paul@0 83
paul@0 84
  for (no = 0; no < 16; no++)
paul@0 85
  {
paul@0 86
    jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spck, 0);
paul@0 87
    jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spda, value & 0x8000 ? 1 : 0);
paul@0 88
    jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spck, 1);
paul@0 89
    value = (value << 1);
paul@0 90
  }
paul@0 91
paul@0 92
  jz4740_gpio_set(gpio_port_c, Jz4740_lcd_gpio_spen, 1);
paul@0 93
}
paul@0 94
paul@0 95
// GPIO-based operations.
paul@0 96
paul@0 97
static void lcd_display_pin_init(void)
paul@0 98
{ 
paul@0 99
  Pin_slice mask = {.offset=0, .mask=(1 << Jz4740_lcd_gpio_spen) | (1 << Jz4740_lcd_gpio_spck) | (1 << Jz4740_lcd_gpio_spda)};
paul@0 100
  Pin_slice slcd8_mask = {.offset=0, .mask=0x003c00ff};
paul@0 101
paul@0 102
  /* Configure SPI pins. */
paul@0 103
paul@0 104
  jz4740_gpio_multi_setup(gpio_port_c, &mask, L4VBUS_GPIO_SETUP_OUTPUT, 0);
paul@0 105
paul@0 106
  /* Configure SLCD8 pins. */
paul@0 107
paul@0 108
  jz4740_gpio_multi_config_pad(gpio_port_c, &slcd8_mask, Function_alt, 0);
paul@0 109
}
paul@0 110
paul@0 111
static void lcd_display_on(void)
paul@0 112
{ 
paul@0 113
  spi_write_reg(0x05, 0x1e);  // GRB=0 (reset); PWM_DUTY=0b011 (70%, default); SHDB2=1, SHDB1=1 (power-related); STB=0 (standby)
paul@0 114
  spi_write_reg(0x05, 0x5e);  // GRB=1 (normal operation); ...
paul@0 115
  spi_write_reg(0x07, 0x8d);  // HBLK=141 (horizontal blanking period from start of hsync pulse to data start)
paul@0 116
  spi_write_reg(0x13, 0x01);  // IN_SEL=1 (alignment mode)
paul@0 117
  spi_write_reg(0x05, 0x5f);  // ...; STB=1 (not standby)
paul@0 118
}
paul@0 119
paul@0 120
/* CPM operations. */
paul@0 121
paul@0 122
static void lcd_set_timing(vidinfo_t *vid)
paul@0 123
{
paul@0 124
  uint32_t pclk = jz4740_lcd_get_pixel_clock(vid);
paul@0 125
paul@0 126
  jz4740_cpm_stop_lcd(cpm_device);
paul@0 127
paul@0 128
  /*
paul@0 129
  Original comment: LCDClock > 2.5*Pixclock
paul@0 130
  However, the documentation indicates that a TFT panel needs a device clock
paul@0 131
  1.5 times that of the pixel clock, and a STN panel needs a device clock 3
paul@0 132
  times that of the pixel clock.
paul@0 133
  */
paul@0 134
paul@0 135
  jz4740_cpm_set_lcd_frequencies(cpm_device, pclk, 3);
paul@0 136
  jz4740_cpm_update_output_frequency(cpm_device);
paul@0 137
  jz4740_cpm_start_lcd(cpm_device);
paul@0 138
paul@0 139
  l4_sleep(1); // 1ms == 1000us
paul@0 140
}
paul@0 141
paul@0 142
paul@0 143
paul@0 144
static int setup_memory(void)
paul@0 145
{
paul@0 146
  l4re_ds_t fbmem, descmem;
paul@0 147
  l4_size_t fb_size_out, desc_size_out;
paul@0 148
  int result = 0;
paul@0 149
paul@0 150
  if (fb_vaddr)
paul@0 151
    return 0;
paul@0 152
paul@0 153
  if (!panel_info)
paul@0 154
    return 1;
paul@0 155
paul@0 156
  fb_size = jz4740_lcd_get_screen_size(panel_info);
paul@0 157
  desc_size = jz4740_lcd_get_descriptors_size(panel_info);
paul@0 158
paul@0 159
  /* Obtain resource details describing the I/O memory. */
paul@0 160
paul@0 161
  if ((result = get_memory("jz4740-cpm", &cpm_virt_base, &cpm_virt_base_end)) < 0)
paul@0 162
    return 1;
paul@0 163
paul@0 164
  if ((result = get_memory("jz4740-lcd", &lcd_virt_base, &lcd_virt_base_end)) < 0)
paul@0 165
    return 1;
paul@0 166
paul@0 167
  if ((result = get_memory("jz4740-gpio", &gpio_virt_base, &gpio_virt_base_end)) < 0)
paul@0 168
    return 1;
paul@0 169
paul@0 170
  /* Set the framebuffer up. */
paul@0 171
paul@0 172
  fbmem = l4re_util_cap_alloc();
paul@0 173
  descmem = l4re_util_cap_alloc();
paul@0 174
paul@0 175
  if (l4_is_invalid_cap(fbmem) || l4_is_invalid_cap(descmem))
paul@0 176
    return 1;
paul@0 177
paul@0 178
  /* Allocate memory for the framebuffer at 2**6 == 64 byte == 16 word alignment,
paul@0 179
     also for the descriptors. */
paul@0 180
paul@0 181
  if (l4re_ma_alloc_align(fb_size, fbmem, L4RE_MA_CONTINUOUS | L4RE_MA_PINNED, 6) ||
paul@0 182
      l4re_ma_alloc_align(desc_size, descmem, L4RE_MA_CONTINUOUS | L4RE_MA_PINNED, 6))
paul@0 183
    return 1;
paul@0 184
paul@0 185
  /* Map the allocated memory, obtaining virtual addresses. */
paul@0 186
paul@0 187
  if (l4re_rm_attach(&fb_vaddr, fb_size,
paul@0 188
                     L4RE_RM_SEARCH_ADDR | L4RE_RM_EAGER_MAP,
paul@0 189
                     fbmem, 0, L4_PAGESHIFT) ||
paul@0 190
      l4re_rm_attach(&desc_vaddr, desc_size,
paul@0 191
                     L4RE_RM_SEARCH_ADDR | L4RE_RM_EAGER_MAP,
paul@0 192
                     descmem, 0, L4_PAGESHIFT))
paul@0 193
    return 1;
paul@0 194
paul@0 195
  fb_size_out = fb_size;
paul@0 196
  desc_size_out = desc_size;
paul@0 197
paul@0 198
  if (l4re_ds_phys(fbmem, 0, &fb_paddr, &fb_size_out) ||
paul@0 199
      l4re_ds_phys(descmem, 0, &desc_paddr, &desc_size_out))
paul@0 200
    return 1;
paul@0 201
paul@0 202
  if ((fb_size_out != fb_size) || (desc_size_out != desc_size))
paul@0 203
    return 1;
paul@0 204
paul@0 205
  cpm_device = jz4740_cpm_init(cpm_virt_base);
paul@0 206
  gpio_port_c = jz4740_gpio_init(gpio_virt_base + 0x200, gpio_virt_base + 0x300, 32);
paul@0 207
paul@0 208
  return 0;
paul@0 209
}
paul@0 210
paul@0 211
static void enable(void)
paul@0 212
{
paul@0 213
  if (setup_memory())
paul@0 214
    return;
paul@0 215
paul@0 216
  jz4740_lcd_set_base(panel_info, (void *) lcd_virt_base);
paul@0 217
  jz4740_lcd_disable(panel_info);
paul@0 218
paul@0 219
  // Initialise the LCD controller and structures.
paul@0 220
paul@0 221
  jz4740_lcd_ctrl_init(
paul@0 222
    (struct jz_fb_dma_descriptor *) desc_vaddr,
paul@0 223
    (struct jz_fb_dma_descriptor *) desc_paddr,
paul@0 224
    fb_vaddr,
paul@0 225
    (void *) fb_paddr,
paul@0 226
    panel_info);
paul@0 227
paul@0 228
  // Initialise the LCD peripheral.
paul@0 229
paul@0 230
  jz4740_lcd_hw_init(panel_info);
paul@0 231
paul@0 232
  // Initialise the clocks for the LCD controller.
paul@0 233
paul@0 234
  lcd_set_timing(panel_info);
paul@0 235
paul@0 236
  // Switch the display on using GPIO operations.
paul@0 237
paul@0 238
  lcd_display_pin_init();
paul@0 239
  lcd_display_on();
paul@0 240
paul@0 241
  // Finally, enable the peripheral.
paul@0 242
paul@0 243
  jz4740_lcd_enable(panel_info);
paul@0 244
}
paul@0 245
paul@0 246
paul@0 247
paul@0 248
int main(void)
paul@0 249
{
paul@0 250
  l4_size_t *fb;
paul@0 251
  l4_size_t i, mask, value, onpix, offpix;
paul@0 252
paul@0 253
  /* Configure the LCD. */
paul@0 254
paul@0 255
  panel_info = &nanonote_panel_info;
paul@0 256
  enable();
paul@0 257
paul@0 258
  fb = (l4_size_t *) fb_vaddr;
paul@0 259
paul@0 260
  /* Try and show some values. */
paul@0 261
paul@0 262
  onpix = 0xffaaffaa; offpix = 0x11551155;
paul@0 263
  mask = 0x80000000; value = 0x80000001;
paul@0 264
paul@0 265
  i = 0;
paul@0 266
paul@0 267
  while (i < fb_size / 4)
paul@0 268
  {
paul@0 269
    fb[i] = value & mask ? onpix : offpix;
paul@0 270
    i++;
paul@0 271
paul@0 272
    if ((i % 10) == 0)
paul@0 273
    {
paul@0 274
      if (mask == 1) mask = 0x80000000;
paul@0 275
      else mask >>= 1;
paul@0 276
paul@0 277
      onpix = (onpix >> 8) | ((onpix & 0xff) << 24);
paul@0 278
      offpix = (offpix >> 8) | ((offpix & 0xff) << 24);
paul@0 279
paul@0 280
      if (i == 3200) value = jz4740_cpm_get_lcd_pixel_divider(cpm_device);
paul@0 281
      else if (i == 6400) value = jz4740_cpm_get_lcd_pixel_frequency(cpm_device);
paul@0 282
      else if (i == 9600) value = (l4_size_t) fb_vaddr;
paul@0 283
      else if (i == 12800) value = fb_paddr;
paul@0 284
      else if (i == 16000) value = ((struct jz_fb_dma_descriptor *) desc_vaddr)[0].fsadr;
paul@0 285
      else if (i == 19200) value = REG32(lcd_virt_base + 0x00);
paul@0 286
      else if (i == 22400) value = REG32(lcd_virt_base + 0x04);
paul@0 287
      else if (i == 25600) value = REG32(lcd_virt_base + 0x30);
paul@0 288
      else if (i == 28800) value = REG32(lcd_virt_base + 0x40);
paul@0 289
    }
paul@0 290
  }
paul@0 291
paul@0 292
  l4_cache_clean_data((long unsigned int) fb_vaddr, (long unsigned int) fb_vaddr + fb_size);
paul@0 293
paul@0 294
  while (1);
paul@0 295
paul@0 296
  return 0;
paul@0 297
}