// Copyright (c) 2001-2005 Rowley Associates Limited.
//
// This file may be distributed under the terms of the License Agreement
// provided with this software.
//
// THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
////////////////////////////////////////////////////////////////////////////////
//
//                     Atmel AT91SAM7A1-EK LCD Example
//
// Description
// -----------
// This example demonstrates using the AT91SAM7A1-EK's LCD port.
//
////////////////////////////////////////////////////////////////////////////////

#include <targets/AT91SAM7A1.h>

#define CORECLK_FREQUENCY 30000000

#define PWM_DLY(n) (*(&PWM_DLY_0 + (n * 2)))
#define PWM_PUL(n) (*(&PWM_PUL_0 + (n * 2)))

/* LCD Definitions */
#define LCD_E             (1 << 8)
#define LCD_RS            (1 << 9)
#define LCD_RW            (1 << 10)

#define LCD_DATA          (0xFF)
#define LCD_PIO_CONTROL   (LCD_RS | LCD_RW | LCD_E)

#define LCD_CLEAR_DISPLAY (0x01)
#define LCD_ENTRY_MODE    (0x06)
#define LCD_DISPLAY_ON    (0x0C)
#define LCD_FUNCTION_SET  (0x38)

#define LCD_WIDTH         (16)

void
lcdWaitBusy()
{
  int busy;
  UPIO_ODR = LCD_DATA;
  UPIO_CODR = LCD_RS;
  do
    {
      UPIO_SODR = LCD_RW;
      UPIO_SODR = LCD_E;
      busy = UPIO_PDSR & 0x80;
      UPIO_CODR = LCD_E;
    }
  while (busy);
}

void
lcdWriteControl(unsigned char v)
{
  lcdWaitBusy();
  UPIO_OER = LCD_DATA;
  UPIO_CODR = LCD_RS;
  UPIO_CODR = LCD_RW;
  UPIO_SODR = LCD_E;
  UPIO_SODR = v;
  UPIO_CODR = LCD_E;
  UPIO_SODR = LCD_RW;
  UPIO_CODR = LCD_DATA;
}

void
lcdWriteData(unsigned char v)
{
  lcdWaitBusy();
  UPIO_OER = LCD_DATA;
  UPIO_SODR = LCD_RS;
  UPIO_CODR = LCD_RW;
  UPIO_SODR = LCD_E;
  UPIO_SODR = v;
  UPIO_CODR = LCD_E;
  UPIO_SODR = LCD_RW;
  UPIO_CODR = LCD_DATA;
}

void
pwmSetOutput(unsigned char channel, unsigned long frequency, unsigned long dutycycle)
{
  unsigned long clk = CORECLK_FREQUENCY / 2;
  unsigned long prescaler = 0;
  unsigned long delay;
  unsigned long pulse;
  PWM_ECR = 3; /* Enable PWM and PIO clocks */
  PWM_MR &= ~(0x1F << (channel * 8)); /* Clear PWM channel's mode register */
  PWM_PDR = 1 << 16 + channel; /* Disable PWM channel's PIO */
  /* Calculate the delay, pulse and prescaler values */
  for (;;)
    {
      delay = (clk * dutycycle) / frequency;
      pulse = (clk / (frequency / 256)) - delay;
      if (delay <= 0xFFFF && pulse <= 0xFFFF)
        break;
      ++prescaler;
      clk >>= 1;
    }
  PWM_MR = prescaler << (channel * 8); /* Set PWM channel's prescaler value */
  PWM_DLY(channel) = delay; /* Set PWM channel's delay value */
  PWM_PUL(channel) = pulse; /* Set PWM channel's pule value */
  PWM_CR = 1 << ((channel * 2) + 1); /* Enable PWM channel */
}

void
lcdSetContrast(unsigned frequency, unsigned dutycycle)
{
  pwmSetOutput(1, frequency * 256, dutycycle);
}

void
lcdInit()
{
#ifdef VDD_PERIPH_5V
  lcdSetContrast(32768, 3);
#else
  lcdSetContrast(32768, 23);
#endif
  UPIO_ECR = 1;
  UPIO_OER = LCD_RW | LCD_E | LCD_RS | LCD_DATA;
  lcdWriteControl(LCD_FUNCTION_SET);
  lcdWriteControl(LCD_DISPLAY_ON);
  lcdWriteControl(LCD_CLEAR_DISPLAY);
  lcdWriteControl(LCD_ENTRY_MODE);
}

void
lcdDisplay(int x, int y, const char *str)
{
  int n = LCD_WIDTH - x;
  lcdWriteControl((y == 0 ? 0x80 : 0xC0) + x);
  while (*str && n--)
    lcdWriteData(*str++);
}

static void
scroll(int x, int y, const char *str)
{
  int l = strlen(str);
  int i;
  for (i = 0; i < l; ++i)
    {
      lcdDisplay(x, y, str + i);
      delay(200000);
    }
}

int
main(void)
{
  int i;
  lcdInit();
  for (i = 1; ; ++i)
    {
      char str[256];
      int x;
      sprintf(str, "Hello World %d", i);
      x = (LCD_WIDTH - strlen(str)) / 2;
      lcdDisplay(x > 0 ? x : 0, 0, str);
      scroll(0, 1, "                Rowley Associates - CrossWorks for ARM ");
    }
  return 0;
}

