/*
  Copyright (C) 2014  Michał Masłowski  <mtjm@mtjm.eu>

  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 3 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.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Try several backlight PWM frequencies and duty cycles to see which
   expose the noise on X60 with coreboot.
*/

#include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pci/pci.h>

static struct pci_access *pacc;
static uint32_t *map;
static int fd;

#define REGISTER 0x61254

static void
init (void)
{
  fd = -1;
  /* Find the device. */
  struct pci_dev *dev;
  pacc = pci_alloc ();
  pacc->writeable = 1;
  pci_init (pacc);
  pci_scan_bus (pacc);
  for (dev = pacc->devices; dev != NULL; dev = dev->next)
    {
      pci_fill_info (dev, PCI_FILL_IDENT | PCI_FILL_BASES);
      if (dev->vendor_id != 0x8086 || dev->device_id != 0x27a2)
        continue;
      break;
    }
  if (dev == NULL)
    {
      fprintf (stderr, "Device not found.\n");
      goto fail;
    }
  /* Map a part of its MMIO. */
  fd = open ("/dev/mem", O_RDWR);
  if (fd == -1)
    {
      fprintf (stderr, "Cannot open memory.  Are you root?\n");
      goto fail;
    }
  map = mmap (NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, dev->base_addr[0] + (REGISTER & (~4095)));
  if (map != MAP_FAILED)
    return;
  else
    perror ("mmap failed");
 fail:
  if (pacc != NULL)
    pci_cleanup (pacc);
  if (fd != -1)
    close (fd);
  exit (1);
}

static void
set_pwm (uint16_t freq, uint16_t duty)
{
  assert (duty <= freq);
  map[((REGISTER - 4) & 4095) >> 2] = 0x80000000;
  map[(REGISTER & 4095) >> 2] = ((freq | 1) << 16) | duty;
}

int
main (void)
{
  init ();
  int exponent = 8;
  int diff = 0;
  int divisor = 2;
  uint16_t freq = 0x61;
  uint16_t duties[] = {
#if 1
    // i945
    0xf,
    0x13,
    0x19,
    0x1f,
    0x23,
    0x29,
    0x2f,
    0x35,
    0x39,
    0x3f,
    0x45,
    0x4b,
    0x4f,
    0x55,
    0x5b,
    0x61
#else
    // gm45
    0x2,
    0x4,
    0x5,
    0x7,
    0x9,
    0xb,
    0xd,
    0x11,
    0x14,
    0x17,
    0x1c,
    0x20,
    0x27,
    0x31,
    0x41,
    0x61,
#endif
  };
#if 1
  for (unsigned int di = 0; di < sizeof (duties) / sizeof (duties[0]); di++)
    {
      uint16_t duty = duties[di];
#else
      for (uint16_t duty = 0; duty < 0x61; duty++)
        {
#endif
#if 0
        }
#endif
      uint16_t pwm_freq = (freq + diff) << exponent;
      pwm_freq += (pwm_freq >> divisor);
      //pwm_freq |= 0x33;
      uint16_t pwm_duty = (duty + diff) << exponent;
      pwm_duty += (pwm_duty >> divisor);
      //pwm_duty |= 0x32;
      set_pwm (pwm_freq, pwm_duty);
      sleep (1);
    }
  if (pacc != NULL)
    pci_cleanup (pacc);
  if (map != NULL)
    munmap (map, 4096);
  if (fd != -1)
    close (fd);
  return 0;
}