/* Copyright 2006 by Chris Osborn <fozztexx@fozztexx.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/parport.h>
#include <linux/ppdev.h>
#include <fcntl.h>
#include <stdlib.h>
#include <time.h>

#define ICM_NOTSHUTDOWN	0x10
#define ICM_NOTDECODE	0x20
#define ICM_HEXA	0x40
#define ICM_DATA	0x80

#define SCROLL_DELAY	250000
#define DISPLAY_DIGITS	8

/* Segment numbering

  _0x40_
|        |
0x02     0x20
|        |
  _0x04_
|        |
0x08     0x10
|        |
  _0x01_

*/
  
unsigned char segments[] = {
  /* Control chars */
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  /* Symbols and numbers */
  0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x20, /*  !"#$%&' */
  0x00, 0x00, 0x00, 0x00, 0x11, 0x04, 0x00, 0x2c, /* ()*+,-./ */
  0x7b, 0x30, 0x6d, 0x75, 0x36, 0x57, 0x5f, 0x70, /* 01234567 */
  0x7f, 0x77, 0x41, 0x51, 0x00, 0x00, 0x00, 0x00, /* 89:;<=>? */

  /* Uppercase */
  0x6f, 0x7e, 0x1f, 0x4b, 0x3d, 0x4f, 0x4e, 0x5b, /* @ABCDEFG */
  0x3e, 0x10, 0x39, 0x5e, 0x0b, 0x58, 0x1c, 0x1d, /* HIJKLMNO */
  0x6e, 0x67, 0x0c, 0x53, 0x0f, 0x3b, 0x19, 0x23, /* PQRSTUVW */
  0x5c, 0x37, 0x69, 0x00, 0x16, 0x00, 0x00, 0x01, /* XYZ[\]^_ */

  /* Lowercase */
  0x06, 0x7e, 0x1f, 0x4b, 0x3d, 0x4f, 0x4e, 0x5b, /* `abcdefg */
  0x3e, 0x10, 0x39, 0x5e, 0x0b, 0x58, 0x1c, 0x1d, /* hijklmno */
  0x6e, 0x67, 0x0c, 0x53, 0x0f, 0x3b, 0x19, 0x23, /* pqrstuvw */
  0x5c, 0x37, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, /* xyz{|}~  */
};

void display_string(const char *str,  int fd)
{
  unsigned char status, control, data;
  unsigned char mask;
  struct ppdev_frob_struct frob;
  char buf[DISPLAY_DIGITS + 1];
  int i;


  for (i = 0; i < DISPLAY_DIGITS; i++)
    buf[i] = ' ';
  strncpy(buf, str, DISPLAY_DIGITS);

  /* Turn off write and mode, pins are inverted */
  frob.mask = PARPORT_CONTROL_STROBE | PARPORT_CONTROL_SELECT;
  frob.val = PARPORT_CONTROL_SELECT;
  ioctl(fd, PPFCONTROL, &frob);

  data = ICM_NOTSHUTDOWN | ICM_HEXA;
  ioctl(fd, PPWDATA, &data);

  /* Try to get the chip into a known state */
  for (i = 0; i < 9; i++) {
    frob.mask = PARPORT_CONTROL_STROBE;
    frob.val = PARPORT_CONTROL_STROBE;
    ioctl(fd, PPFCONTROL, &frob);
    frob.val = 0;
    ioctl(fd, PPFCONTROL, &frob);
  }
  
  data = ICM_NOTSHUTDOWN | ICM_NOTDECODE | ICM_DATA;
  ioctl(fd, PPWDATA, &data);

  /* Start control mode */
  frob.mask = PARPORT_CONTROL_SELECT;
  frob.val = 0;
  ioctl(fd, PPFCONTROL, &frob);
  
  /* Pulse write */
  frob.mask = PARPORT_CONTROL_STROBE;
  frob.val = PARPORT_CONTROL_STROBE;
  ioctl(fd, PPFCONTROL, &frob);
  
  /* End the pulse */
  frob.val = 0;
  ioctl(fd, PPFCONTROL, &frob);

  /* End control mode */
  frob.mask = PARPORT_CONTROL_SELECT;
  frob.val = PARPORT_CONTROL_SELECT;
  ioctl(fd, PPFCONTROL, &frob);

  for (i = 0; i < 8; i++) {
    /* Data goes in reverse */    
    data = buf[7 - i];
    data = 0x80 ^ ((data & 0x80) | (segments[data & 0x7f]));
    ioctl(fd, PPWDATA, &data);

    /* Pulse write */
    frob.mask = PARPORT_CONTROL_STROBE;
    frob.val = PARPORT_CONTROL_STROBE;
    ioctl(fd, PPFCONTROL, &frob);
  
    /* End the pulse */
    frob.val = 0;
    ioctl(fd, PPFCONTROL, &frob);
  }

  return;
}

int main(int argc, char *argv[])
{
  int fd;
  int mode;
  char *p;
  int i, j, k;
  time_t t;
  struct tm *tp;
  char buf[DISPLAY_DIGITS+1];

    
  fd = open("/dev/parport0", O_RDWR);
  if (fd == -1) {
    perror("open");
    return 1;
  }

  if (ioctl(fd, PPEXCL)) {
    perror("PPEXCL");
    close(fd);
    return 1;
  }
    
  if (ioctl(fd, PPCLAIM)) {
    perror("PPCLAIM");
    close(fd);
    return 1;
  }
    
  mode = IEEE1284_MODE_COMPAT;
  if (ioctl(fd, PPNEGOT, &mode)) {
    perror("PPNEGOT");
    close(fd);
    return 1;
  }

  if (argc < 2) {
    t = time(NULL);
    tp = localtime(&t);

    strcpy(buf, "        ");
    if (1) {
      if (tp->tm_hour > 12) {
	buf[(DISPLAY_DIGITS - 4) / 2] = '0' + (tp->tm_hour - 12) / 10;
	buf[(DISPLAY_DIGITS - 4) / 2 + 1] = '0' + (tp->tm_hour - 12) % 10;
      }
      else if (tp->tm_hour < 1) {
	buf[(DISPLAY_DIGITS - 4) / 2] = '1';
	buf[(DISPLAY_DIGITS - 4) / 2 + 1] = '2';
      }
      else {
	buf[(DISPLAY_DIGITS - 4) / 2] = '0' + tp->tm_hour / 10;
	buf[(DISPLAY_DIGITS - 4) / 2 + 1] = '0' + tp->tm_hour % 10;
      }
      if (tp->tm_hour > 11)
	buf[(DISPLAY_DIGITS - 4) / 2] |= 0x80;
    }
    else {
      buf[(DISPLAY_DIGITS - 4) / 2] = '0' + tp->tm_hour / 10;
      buf[(DISPLAY_DIGITS - 4) / 2 + 1] = '0' + tp->tm_hour % 10;
    }
    
    buf[(DISPLAY_DIGITS - 4) / 2 + 2] = '0' + tp->tm_min / 10;
    buf[(DISPLAY_DIGITS - 4) / 2 + 3] = '0' + tp->tm_min % 10;

    buf[(DISPLAY_DIGITS - 4) / 2 + 1] |= 0x80;
    buf[(DISPLAY_DIGITS - 4) / 2 + 2] |= 0x80;
  
    if ((buf[(DISPLAY_DIGITS - 4) / 2] & 0x7f) == '0')
      buf[(DISPLAY_DIGITS - 4) / 2] =
	(buf[(DISPLAY_DIGITS - 4) / 2] & 0x80) | ' ';

    display_string(buf, fd);
  }
  else {
    if (strlen(argv[1]) <= DISPLAY_DIGITS)
      display_string(argv[1], fd);
    else {
      j = strlen(argv[1]);
      p = malloc(j + 16);
      strcpy(p, "        ");
      strcat(p, argv[1]);
      strcat(p, "        ");

      if (argc == 3)
	k = atoi(argv[2]);
      else
	k = 1;
      for (; k; k--)
	for (i = 0; i < j + DISPLAY_DIGITS; i++) {
	  display_string(p + i, fd);
	  usleep(SCROLL_DELAY);
	}
    }
  }
    
  close(fd);
}  
