#include <sys/param.h>
#include <sys/types.h>
#include <sys/cdefs.h>
#include <sys/mman.h>

#include <dev/pci/pcireg.h>

#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pci.h>
#include <stdio.h>
#include <unistd.h>

#define	ISSET(x, y)	((x) & (y))

#define	INTEL_BUS	0
#define	INTEL_DEV	2
#define	INTEL_FUNC	0

#define	INTEL_NREGS	(2 * 1024 * 1024)

#define BLC_PWM_CTL2		0x61250 /* 965+ only */
#define   BLM_PWM_ENABLE		(1 << 31)
#define   BLM_COMBINATION_MODE		(1 << 30) /* gen4 only */
#define   BLM_PIPE_SELECT		(1 << 29)
#define   BLM_PIPE_SELECT_IVB		(3 << 29)
#define   BLM_PIPE_A			(0 << 29)
#define   BLM_PIPE_B			(1 << 29)
#define   BLM_PIPE_C			(2 << 29) /* ivb + */
#define   BLM_PIPE(pipe)		((pipe) << 29)
#define   BLM_POLARITY_I965		(1 << 28) /* gen4 only */
#define   BLM_PHASE_IN_INTERUPT_STATUS	(1 << 26)
#define   BLM_PHASE_IN_ENABLE		(1 << 25)
#define   BLM_PHASE_IN_INTERUPT_ENABL	(1 << 24)
#define   BLM_PHASE_IN_TIME_BASE_SHIFT	(16)
#define   BLM_PHASE_IN_TIME_BASE_MASK	(0xff << 16)
#define   BLM_PHASE_IN_COUNT_SHIFT	(8)
#define   BLM_PHASE_IN_COUNT_MASK	(0xff << 8)
#define   BLM_PHASE_IN_INCR_SHIFT	(0)
#define   BLM_PHASE_IN_INCR_MASK	(0xff << 0)
#define BLC_PWM_CTL		0x61254

#define BLC_PWM_PCH_CTL1	0xc8250
#define   BLM_PCH_PWM_ENABLE			(1 << 31)
#define   BLM_PCH_OVERRIDE_ENABLE		(1 << 30)
#define   BLM_PCH_POLARITY			(1 << 29)
#define BLC_PWM_PCH_CTL2	0xc8254
#define	  BLC_PWM_PCH_CTL2_MAX_MASK		__BITS(31, 16)

#define BLC_PWM_CPU_CTL2	0x48250
#define BLC_PWM_CPU_CTL		0x48254

#define   BACKLIGHT_DUTY_CYCLE_SHIFT		(0)
#define   BACKLIGHT_DUTY_CYCLE_MASK		(0xffff)
#define   BACKLIGHT_DUTY_CYCLE_MASK_PNV		(0xfffe)
#define   BLM_POLARITY_PNV			(1 << 0) /* pnv only */

static uint32_t __unused
read32(const void *regs, unsigned int offset)
{
	uint32_t v;

	(void)fprintf(stderr, "read %08x -> ", offset);
	v = ((const uint32_t *)regs)[offset >> 2];
	__insn_barrier();
	(void)fprintf(stderr, "%08"PRIx32"\n", v);
	return v;
}

static void __unused
write32(void *regs, unsigned int offset, uint32_t v)
{

	(void)fprintf(stderr, "write %08x <- %08"PRIx32"\n", offset, v);
	__insn_barrier();
	((uint32_t *)regs)[offset >> 2] = v;
}

int
main(int argc, char **argv)
{
	int pci_fd;
	uint32_t reg, paddr0, paddr1, pmask0, pmask1;
	uint64_t paddr, pmask, psize;
	void *regs;
	uint32_t max_backlight;

	pci_fd = open("/dev/pci0", O_RDWR);
	if (pci_fd == -1)
		err(1, "open(/dev/pci0)");

	pci_conf_print(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC);

	if (pcibus_conf_read(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
		PCI_BAR(0), &reg) == -1)
		err(1, "pcibus_conf_read (type)");

	if (printf("mapreg = %08"PRIx32"\n", (uint32_t)reg) < 0)
		err(1, "printf");

	if (PCI_MAPREG_TYPE(reg) != PCI_MAPREG_TYPE_MEM)
		errx(1, "non-memory map");
	if (PCI_MAPREG_MEM_TYPE(reg) != PCI_MAPREG_MEM_TYPE_64BIT)
		errx(1, "non-64-bit map");

	(void)pmask0;
	(void)pmask1;
	(void)pmask;
	(void)psize;

	if (pcibus_conf_read(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
		PCI_BAR(0), &paddr0) == -1)
		err(1, "pcibus_conf_read (paddr0)");
#if 0
	if (pcibus_conf_write(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
		PCI_BAR(0), UINT32_C(0xffffffff)) == -1)
		err(1, "pcibus_conf_write (0xffffffff)");
	if (pcibus_conf_read(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
		PCI_BAR(0), &pmask0) == -1)
		err(1, "pcibus_conf_read (pmask0)");
	if (pcibus_conf_write(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
		PCI_BAR(0), paddr0) == -1)
		err(1, "pcibus_conf_write (paddr0)");
#endif
	assert(PCI_MAPREG_MEM_TYPE(reg) == PCI_MAPREG_MEM_TYPE_64BIT);
	if (pcibus_conf_read(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
		PCI_BAR(0) + 4, &paddr1) == -1)
		err(1, "pcibus_conf_read (paddr0)");
#if 0
	if ((pmask0 & 0x80000000) == 0) {
		if (pcibus_conf_write(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
			PCI_BAR(0) + 4, UINT32_C(0xffffffff)) == -1)
			err(1, "pcibus_conf_write (0xffffffff)");
		if (pcibus_conf_read(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
			PCI_BAR(0) + 4, &pmask1) == -1)
			err(1, "pcibus_conf_read (pmask1)");
		if (pcibus_conf_write(pci_fd, INTEL_BUS, INTEL_DEV, INTEL_FUNC,
			PCI_BAR(0) + 4, paddr1) == -1)
			err(1, "pcibus_conf_write (paddr1)");
	}
#endif
	if (printf("paddr0 %08"PRIx32", paddr1 %08"PRIx32"\n", paddr0, paddr1)
	    < 0)
		err(1, "printf");
#if 0
	if (printf("pmask0 %08"PRIx32", pmask1 %08"PRIx32"\n", pmask0, pmask1)
	    < 0)
		err(1, "printf");
#endif

	paddr = (((uint64_t)paddr1) << 32) | paddr0;
#if 0
	pmask = (((uint64_t)pmask1) << 32) | pmask0;
#endif

	if (printf("paddr %016"PRIx64"\n", paddr) < 0)
		err(1, "printf");
#if 0
	if (printf("pmask %016"PRIx64"\n", pmask) < 0)
		err(1, "printf");
#endif

	paddr &= UINT64_C(0xfffffffffffffff0);
	if (printf("paddr %016"PRIx64"\n", paddr) < 0)
		err(1, "printf");

#if 0
	psize = (pmask & UINT64_C(0xfffffffffffffff0)) &
	    -(pmask & UINT64_C(0xfffffffffffffff0));
	if (printf("psize %016"PRIx64"\n", psize) < 0)
		err(1, "printf");
#endif

	regs = mmap(NULL, INTEL_NREGS, (PROT_READ | PROT_WRITE), MAP_PRIVATE,
	    pci_fd, paddr);
	if (regs == MAP_FAILED)
		err(1, "mmap(%016"PRIx64")", paddr);

	if (printf("BLC_PWM_CTL2 = %"PRIx32"\n",
		read32(regs, BLC_PWM_CTL2)) < 0)
		err(1, "printf");
	if (printf("BLC_PWM_CTL = %"PRIx32"\n",
		read32(regs, BLC_PWM_CTL)) < 0)
		err(1, "printf");
	if (printf("BLC_PWM_PCH_CTL1 = %"PRIx32"\n",
		read32(regs, BLC_PWM_PCH_CTL1)) < 0)
		err(1, "printf");
	if (printf("BLC_PWM_PCH_CTL2 = %"PRIx32"\n",
		read32(regs, BLC_PWM_PCH_CTL2)) < 0)
		err(1, "printf");

	max_backlight = __SHIFTOUT(read32(regs, BLC_PWM_PCH_CTL2),
	    BLC_PWM_PCH_CTL2_MAX_MASK);

	if (printf("max %04"PRIx32"\n", max_backlight) < 0)
		err(1, "printf");
	if (printf("BLC_PWM_CPU_CTL2 %"PRIx32"\n",
		read32(regs, BLC_PWM_CPU_CTL2)) < 0)
		err(1, "printf");
	if (printf("comb? %d\n",
		((read32(regs, BLC_PWM_CTL2) & BLM_COMBINATION_MODE) != 0))
	    < 0)
		err(1, "printf");

	if (printf("BLC_PWM_CPU_CTL %"PRIx32"\n",
		read32(regs, BLC_PWM_CPU_CTL))
	    < 0)
		err(1, "printf");

	write32(regs, BLC_PWM_CPU_CTL,
	    ((read32(regs, BLC_PWM_CPU_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK)
		| MIN(0x0400, max_backlight)));

	if (printf("BLC_PWM_CPU_CTL %"PRIx32"\n",
		read32(regs, BLC_PWM_CPU_CTL))
	    < 0)
		err(1, "printf");

	if (munmap(regs, INTEL_NREGS) == -1)
		warn("munmap");

	if (close(pci_fd) == -1)
		warn("close");

	return 0;
}