/*      $NetBSD: igma.c,v 1.6 2021/08/07 16:19:14 thorpej Exp $ */

/*
* Copyright (c) 2014 Michael van Elst
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/*
* Intel Graphic Media Accelerator
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: igma.c,v 1.6 2021/08/07 16:19:14 thorpej Exp $");

#include "vga.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/bus.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/pciio.h>

#include <dev/i2c/i2cvar.h>
#include <dev/i2c/i2c_bitbang.h>
#include <dev/i2c/ddcvar.h>

#include <dev/videomode/videomode.h>
#include <dev/videomode/edidvar.h>

#include <dev/wscons/wsdisplayvar.h>

#if NVGA > 0
#include <dev/ic/mc6845reg.h>
#include <dev/ic/pcdisplayvar.h>
#include <dev/ic/vgareg.h>
#include <dev/ic/vgavar.h>
#endif

#include <dev/pci/igmareg.h>
#include <dev/pci/igmavar.h>

#include "igmafb.h"

struct igma_softc;
struct igma_i2c {
       struct igma_softc       *ii_sc;
       bus_addr_t              ii_reg;
       struct i2c_controller   ii_i2c;
       const char              *ii_name;
       u_int32_t               ii_dir;
};

struct igma_softc {
       device_t                sc_dev;
       struct igma_chip        sc_chip;
       struct igma_i2c         sc_ii[GMBUS_NUM_PORTS];
};

static int igma_match(device_t, cfdata_t, void *);
static void igma_attach(device_t, device_t, void *);
static int igma_print(void *, const char *);

static void igma_i2c_attach(struct igma_softc *);

CFATTACH_DECL_NEW(igma, sizeof(struct igma_softc),
   igma_match, igma_attach, NULL, NULL);

static int igma_i2c_acquire_bus(void *, int);
static void igma_i2c_release_bus(void *, int);
static int igma_i2c_send_start(void *, int);
static int igma_i2c_send_stop(void *, int);
static int igma_i2c_initiate_xfer(void *, i2c_addr_t, int);
static int igma_i2c_read_byte(void *, uint8_t *, int);
static int igma_i2c_write_byte(void *, uint8_t, int);
static void igma_i2cbb_set_bits(void *, uint32_t);
static void igma_i2cbb_set_dir(void *, uint32_t);
static uint32_t igma_i2cbb_read(void *);

static void igma_reg_barrier(const struct igma_chip *, int);
static u_int32_t igma_reg_read(const struct igma_chip *, int);
static void igma_reg_write(const struct igma_chip *, int, u_int32_t);
static u_int8_t igma_vga_read(const struct igma_chip *, int);
static void igma_vga_write(const struct igma_chip *, int , u_int8_t);
#if 0
static u_int8_t igma_crtc_read(const struct igma_chip *, int);
static void igma_crtc_write(const struct igma_chip *, int, u_int8_t);
#endif

static const struct i2c_bitbang_ops igma_i2cbb_ops = {
       igma_i2cbb_set_bits,
       igma_i2cbb_set_dir,
       igma_i2cbb_read,
       { 1, 2, 0, 1 }
};

static const struct igma_chip_ops igma_bus_ops = {
       igma_reg_barrier,
       igma_reg_read,
       igma_reg_write,
       igma_vga_read,
       igma_vga_write,
#if 0
       igma_crtc_read,
       igma_crtc_write,
#endif
};

static struct igma_product {
       u_int16_t product;
       int gentype;
       int num_pipes;
} const igma_products[] = {
       /* i830 */
       { PCI_PRODUCT_INTEL_82830MP_IV,           200,2 },
       /* i845g */
       { PCI_PRODUCT_INTEL_82845G_IGD,           200,2 },
       /* i85x */
       { PCI_PRODUCT_INTEL_82855GM_IGD,          200,2 },
// 0x358e ?
       /* i865g */
       { PCI_PRODUCT_INTEL_82865_IGD,            200,2 },
       /* i915g */
       { PCI_PRODUCT_INTEL_82915G_IGD,           200,2 },
       { PCI_PRODUCT_INTEL_E7221_IGD,            200,2 },
       /* i915gm */
       { PCI_PRODUCT_INTEL_82915GM_IGD,          300,2 },
       /* i945g */
       { PCI_PRODUCT_INTEL_82945P_IGD,           300,2 },
       /* i945gm */
       { PCI_PRODUCT_INTEL_82945GM_IGD,          300,2 },
       { PCI_PRODUCT_INTEL_82945GM_IGD_1,        300,2 },
       { PCI_PRODUCT_INTEL_82945GME_IGD,         300,2 },
       /* i965g */
       { PCI_PRODUCT_INTEL_82946GZ_IGD,          300,2 },
       { PCI_PRODUCT_INTEL_82G35_IGD,            300,2 },
       { PCI_PRODUCT_INTEL_82G35_IGD_1,          300,2 },
       { PCI_PRODUCT_INTEL_82965Q_IGD,           300,2 },
       { PCI_PRODUCT_INTEL_82965Q_IGD_1,         300,2 },
       { PCI_PRODUCT_INTEL_82965G_IGD,           300,2 },
       { PCI_PRODUCT_INTEL_82965G_IGD_1,         300,2 },
       /* g33 */
       { PCI_PRODUCT_INTEL_82G33_IGD,            300,2 },
       { PCI_PRODUCT_INTEL_82G33_IGD_1,          300,2 },
       { PCI_PRODUCT_INTEL_82Q33_IGD,            300,2 },
       { PCI_PRODUCT_INTEL_82Q33_IGD_1,          300,2 },
       { PCI_PRODUCT_INTEL_82Q35_IGD,            300,2 },
       { PCI_PRODUCT_INTEL_82Q35_IGD_1,          300,2 },
       /* pineview */
       { PCI_PRODUCT_INTEL_PINEVIEW_IGD,         350,2 },
       { PCI_PRODUCT_INTEL_PINEVIEW_M_IGD,       350,2 },
       /* i965gm */
       { PCI_PRODUCT_INTEL_82965PM_IGD,          400,2 },
       { PCI_PRODUCT_INTEL_82965PM_IGD_1,        400,2 },
       { PCI_PRODUCT_INTEL_82965GME_IGD,         400,2 },
       /* gm45 */
       { PCI_PRODUCT_INTEL_82GM45_IGD,           450,2 },
       { PCI_PRODUCT_INTEL_82GM45_IGD_1,         450,2 },
       /* g45 */
       { PCI_PRODUCT_INTEL_82IGD_E_IGD,          450,2 },
       { PCI_PRODUCT_INTEL_82Q45_IGD,            450,2 },
       { PCI_PRODUCT_INTEL_82G45_IGD,            450,2 },
       { PCI_PRODUCT_INTEL_82G41_IGD,            450,2 },
       { PCI_PRODUCT_INTEL_82B43_IGD,            450,2 },
// 0x2e92 ?
       /* ironlake d */
       { PCI_PRODUCT_INTEL_IRONLAKE_D_IGD,       500,2 },
       /* ironlake m */
       { PCI_PRODUCT_INTEL_IRONLAKE_M_IGD,       500,2 },
       /* sandy bridge */
       { PCI_PRODUCT_INTEL_SANDYBRIDGE_IGD,      600,2 },
       { PCI_PRODUCT_INTEL_SANDYBRIDGE_IGD_1,    600,2 },
       { PCI_PRODUCT_INTEL_SANDYBRIDGE_IGD_2,    600,2 },
       /* sandy bridge m */
       { PCI_PRODUCT_INTEL_SANDYBRIDGE_M_IGD,    600,2 },
       { PCI_PRODUCT_INTEL_SANDYBRIDGE_M_IGD_1,  600,2 },
       { PCI_PRODUCT_INTEL_SANDYBRIDGE_M_IGD_2,  600,2 },
       /* sandy bridge s */
       { PCI_PRODUCT_INTEL_SANDYBRIDGE_S_IGD,    600,2 },
       /* ivy bridge */
       { PCI_PRODUCT_INTEL_IVYBRIDGE_IGD,        700,3 },
       { PCI_PRODUCT_INTEL_IVYBRIDGE_IGD_1,      700,3 },
       /* ivy bridge m */
       { PCI_PRODUCT_INTEL_IVYBRIDGE_M_IGD,      700,3 },
       { PCI_PRODUCT_INTEL_IVYBRIDGE_M_IGD_1,    700,3 },
       /* ivy bridge s */
       { PCI_PRODUCT_INTEL_IVYBRIDGE_S_IGD,      700,3 },
       { PCI_PRODUCT_INTEL_IVYBRIDGE_S_IGD_1,    700,3 },
#if 0
       /* valleyview d */
       /* valleyview m */
       { PCI_PRODUCT_INTEL_HASWELL_IGD_1,        800,3 },
       /* haswell d */
       { PCI_PRODUCT_INTEL_HASWELL_IGD,          800,3 },
       { PCI_PRODUCT_INTEL_HASWELL_IGD_1,        800,3 },
       /* haswell m */
       /* broadwell d */
       /* broadwell m */
#endif
};

static int
igma_newpch_match(const struct pci_attach_args *pa)
{
       if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_INTEL)
               return 0;
       switch (0xff00 & PCI_PRODUCT(pa->pa_id)) {
       case 0x3b00: /* ibex peak */
       case 0x1c00: /* cougar point */
       case 0x1e00: /* panther point */
       case 0x8c00: /* lynx point */
       case 0x9c00: /* lynx point lp */
               return 1;
       }

       return 0;
}

static const struct igma_product *
igma_lookup(const struct pci_attach_args *pa)
{
       const struct igma_product *ip;
       int i;

       if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_INTEL)
               return NULL;
       for (i=0; i < __arraycount(igma_products); ++i) {
               ip = &igma_products[i];
               if (PCI_PRODUCT(pa->pa_id) == ip->product)
                       return ip;
       }
       return NULL;
}

static void
igma_product_to_chip(const struct pci_attach_args *pa, struct igma_chip *cd)
{
       const struct igma_product *ip;
       struct pci_attach_args PA;

       ip = igma_lookup(pa);
       KASSERT(ip != NULL);

       cd->ops = &igma_bus_ops;
       cd->num_gmbus = 6;
       cd->num_pipes = ip->num_pipes;
       cd->quirks = 0;
       cd->backlight_factor = 1;

       cd->gpio_offset = OLD_GPIOA;
       cd->vga_cntrl = PCH_VGA_CNTRL;
       cd->backlight_cntrl = OLD_BLC_PWM_CTL;
       cd->backlight_cntrl2 = OLD_BLC_PWM_CTL2;

       PA = *pa;
       if (pci_find_device(&PA, igma_newpch_match)) {
               cd->gpio_offset = PCH_GPIOA;
               cd->vga_cntrl = CPU_VGA_CNTRL;
               cd->backlight_cntrl = CPU_BLC_PWM_CTL;
               cd->backlight_cntrl2 = CPU_BLC_PWM_CTL2;
       }

       switch (ip->gentype) {
       case 200:
               cd->backlight_factor = 2;
               break;
       case 300:
       case 350:
               cd->backlight_factor = 2;
               cd->quirks |= IGMA_PFITDISABLE_QUIRK;
               break;
       case 450:
               cd->pri_cntrl = PRI_CTRL_NOTRICKLE;
               cd->quirks  |= IGMA_PLANESTART_QUIRK;
               break;
       default:
               cd->pri_cntrl = 0;
               break;
       }
}

static void
igma_adjust_chip(struct igma_softc *sc, struct igma_chip *cd)
{
       const struct igma_chip_ops *co = cd->ops;
       u_int32_t reg;

       reg = co->read_reg(cd, cd->vga_cntrl);
       if (reg & VGA_PIPE_B_SELECT)
               cd->use_pipe = 1;
}

static int
igma_print(void *aux, const char *pnp)
{
       if (pnp)
               aprint_normal("drm at %s", pnp);
       return (UNCONF);
}

static int
igma_match(device_t parent, cfdata_t match, void *aux)
{
       struct pci_attach_args *pa = (struct pci_attach_args *)aux;
       const struct igma_product *ip;

       if (PCI_CLASS(pa->pa_class) != PCI_CLASS_DISPLAY)
               return 0;

       ip = igma_lookup(pa);
       if (ip != NULL)
               return 100;

       return 0;
}

static void
igma_attach(device_t parent, device_t self, void *aux)
{
       struct igma_softc *sc = device_private(self);
       const struct pci_attach_args *pa = (struct pci_attach_args *)aux;
       struct igma_attach_args iaa;
       bus_space_tag_t gttmmt, gmt, regt;
       bus_space_handle_t gttmmh, gmh, regh;
       bus_addr_t gttmmb, gmb;

       pci_aprint_devinfo(pa, NULL);

       sc->sc_dev = self;

       /* Initialize according to chip type */
       igma_product_to_chip(pa, &sc->sc_chip);

       if (pci_mapreg_map(pa, PCI_BAR0, PCI_MAPREG_TYPE_MEM,
                       BUS_SPACE_MAP_LINEAR,
                       &gttmmt, &gttmmh, &gttmmb, NULL)) {
               aprint_error_dev(sc->sc_dev, "unable to map GTTMM\n");
               return;
       }
       sc->sc_chip.mmiot = gttmmt;
       if (bus_space_subregion(gttmmt, gttmmh, 0, 2*1024*1024,
                       &sc->sc_chip.mmioh)) {
               aprint_error_dev(sc->sc_dev, "unable to submap MMIO\n");
               return;
       }
       sc->sc_chip.gttt = gttmmt;
       if (bus_space_subregion(gttmmt, gttmmh, 2*1024*1024, 2*1024*1024,
                       &sc->sc_chip.gtth)) {
               aprint_error_dev(sc->sc_dev, "unable to submap GTT\n");
               return;
       }

       if (pci_mapreg_map(pa, PCI_BAR2, PCI_MAPREG_TYPE_MEM,
                       BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE,
                       &gmt, &gmh, &gmb, NULL)) {
               aprint_error_dev(sc->sc_dev, "unable to map aperture\n");
               return;
       }
       sc->sc_chip.gmt = gmt;
       sc->sc_chip.gmh = gmh;
       sc->sc_chip.gmb = gmb;

       if (pci_mapreg_map(pa, PCI_BAR4, PCI_MAPREG_TYPE_IO, 0,
                       &regt, &regh, NULL, NULL)) {
               aprint_error_dev(sc->sc_dev, "unable to map IO registers\n");
               return;
       }

#if NVGA > 0
       iaa.iaa_console = vga_cndetach() ? true : false;
#else
       iaa.iaa_console = 0;
#endif
       sc->sc_chip.vgat = regt;
       if (bus_space_map(regt, 0x3c0, 0x10, 0, &sc->sc_chip.vgah)) {
               aprint_error_dev(sc->sc_dev, "unable to map VGA registers\n");
               return;
       }

       /* Check hardware for more information */
       igma_adjust_chip(sc, &sc->sc_chip);

       aprint_normal("%s: VGA_CNTRL: 0x%x\n",device_xname(sc->sc_dev),
               sc->sc_chip.vga_cntrl);
       aprint_normal("%s: GPIO_OFFSET: 0x%x\n",device_xname(sc->sc_dev),
               sc->sc_chip.gpio_offset);
       aprint_normal("%s: BACKLIGHT_CTRL: 0x%x\n",device_xname(sc->sc_dev),
               sc->sc_chip.backlight_cntrl);
       aprint_normal("%s: BACKLIGHT_CTRL2: 0x%x\n",device_xname(sc->sc_dev),
               sc->sc_chip.backlight_cntrl2);

#if NIGMAFB > 0
       strcpy(iaa.iaa_name, "igmafb");
       iaa.iaa_chip = sc->sc_chip;
       config_found(sc->sc_dev, &iaa, igma_print,
           CFARGS(.iattr = "igmabus"));
#endif

       igma_i2c_attach(sc);
}

static void
igma_i2c_attach(struct igma_softc *sc)
{
       struct igma_i2c *ii;
       int i;
#if 0
       struct i2cbus_attach_args iba;
#endif

       for (i=0; i<sc->sc_chip.num_gmbus; ++i) {
               ii = &sc->sc_ii[i];
               ii->ii_sc = sc;

               /* XXX */
               ii->ii_reg = sc->sc_chip.gpio_offset - PCH_GPIOA;
               switch (i) {
               case 0:
                       ii->ii_reg += PCH_GPIOB;
                       ii->ii_name = "ssc";
                       break;
               case 1:
                       ii->ii_reg += PCH_GPIOA;
                       ii->ii_name = "vga";
                       break;
               case 2:
                       ii->ii_reg += PCH_GPIOC;
                       ii->ii_name = "panel";
                       break;
               case 3:
                       ii->ii_reg += PCH_GPIOD;
                       ii->ii_name = "dpc";
                       break;
               case 4:
                       ii->ii_reg += PCH_GPIOE;
                       ii->ii_name = "dpb";
                       break;
               case 5:
                       ii->ii_reg += PCH_GPIOF;
                       ii->ii_name = "dpd";
                       break;
               default:
                       panic("don't know GMBUS %d\n",i);
               }

               iic_tag_init(&ii->ii_i2c);
               ii->ii_i2c.ic_cookie = ii;
               ii->ii_i2c.ic_send_start = igma_i2c_send_start;
               ii->ii_i2c.ic_send_stop = igma_i2c_send_stop;
               ii->ii_i2c.ic_initiate_xfer = igma_i2c_initiate_xfer;
               ii->ii_i2c.ic_read_byte = igma_i2c_read_byte;
               ii->ii_i2c.ic_write_byte = igma_i2c_write_byte;

#if 0
               memset(&iba, 0, sizeof(iba));
               iba.iba_tag = &ii->ii_i2c;
               config_found(sc->sc_dev, &iba, iicbus_print,
                   CFARGS(.iattr = "i2cbus"));
#endif
       }
}

/*
* I2C interface
*/

static int
igma_i2c_send_start(void *cookie, int flags)
{
       return i2c_bitbang_send_start(cookie, flags, &igma_i2cbb_ops);
}

static int
igma_i2c_send_stop(void *cookie, int flags)
{
       return i2c_bitbang_send_stop(cookie, flags, &igma_i2cbb_ops);
}

static int
igma_i2c_initiate_xfer(void *cookie, i2c_addr_t addr, int flags)
{
       return i2c_bitbang_initiate_xfer(cookie, addr, flags, &igma_i2cbb_ops);
}

static int
igma_i2c_read_byte(void *cookie, uint8_t *valp, int flags)
{
       return i2c_bitbang_read_byte(cookie, valp, flags, &igma_i2cbb_ops);
}

static int
igma_i2c_write_byte(void *cookie, uint8_t val, int flags)
{
       return i2c_bitbang_write_byte(cookie, val, flags, &igma_i2cbb_ops);
}

static void
igma_i2cbb_set_bits(void *cookie, uint32_t bits)
{
       struct igma_i2c *ii = cookie;
       struct igma_softc *sc = ii->ii_sc;
       const struct igma_chip *cd = &sc->sc_chip;
       const struct igma_chip_ops *co = cd->ops;
       uint32_t reg;

       reg = co->read_reg(cd, ii->ii_reg);
       reg &= GPIO_DATA_PULLUP_DISABLE | GPIO_CLOCK_PULLUP_DISABLE;

       if ((bits | ii->ii_dir) & 1)
               /* make data input, signal is pulled high */
               reg |= GPIO_DATA_DIR_IN | GPIO_DATA_DIR_MASK;
       else
               /* make data output, signal is driven low */
               reg |= GPIO_DATA_DIR_OUT | GPIO_DATA_DIR_MASK
                       | GPIO_DATA_VAL_MASK;

       if (bits & 2)
               /* make clock input, signal is pulled high */
               reg |= GPIO_CLOCK_DIR_IN | GPIO_CLOCK_DIR_MASK;
       else
               /* make clock output, signal is driven low */
               reg |= GPIO_CLOCK_DIR_OUT | GPIO_CLOCK_DIR_MASK
                       | GPIO_CLOCK_VAL_MASK;

       co->write_reg(cd, ii->ii_reg, reg);
#if 1
       reg = co->read_reg(cd, ii->ii_reg);
#else
       co->barrier(cd, ii->ii_reg);
#endif
}

static void
igma_i2cbb_set_dir(void *cookie, uint32_t bits)
{
       struct igma_i2c *ii = cookie;

       ii->ii_dir = bits;
}

static uint32_t
igma_i2cbb_read(void *cookie)
{
       struct igma_i2c *ii = cookie;
       struct igma_softc *sc = ii->ii_sc;
       const struct igma_chip *cd = &sc->sc_chip;
       const struct igma_chip_ops *co = cd->ops;
       uint32_t reg;
       int sda, scl;

       reg = co->read_reg(cd, ii->ii_reg);

       sda = reg & GPIO_DATA_VAL_IN;
       scl = reg & GPIO_CLOCK_VAL_IN;

       reg = (sda ? 1 : 0) | (scl ? 2 : 0);
       return reg;
}

static void
igma_reg_barrier(const struct igma_chip *cd, int r)
{
       bus_space_barrier(cd->mmiot, cd->mmioh, r, sizeof(u_int32_t),
               BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
}

static u_int32_t
igma_reg_read(const struct igma_chip *cd, int r)
{
       return bus_space_read_4(cd->mmiot, cd->mmioh, r);
}

static void
igma_reg_write(const struct igma_chip *cd, int r, u_int32_t v)
{
       bus_space_write_4(cd->mmiot, cd->mmioh, r, v);
}

static u_int8_t
igma_vga_read(const struct igma_chip *cd, int r)
{
       bus_space_write_1(cd->vgat, cd->vgah, 0x4, r | 0x20);
       return bus_space_read_1(cd->vgat, cd->vgah, 0x5);
}

static void
igma_vga_write(const struct igma_chip *cd, int r, u_int8_t v)
{
       bus_space_write_1(cd->vgat, cd->vgah, 0x4, r | 0x20);
       bus_space_write_1(cd->vgat, cd->vgah, 0x5, v);
}

#if 0
static u_int8_t
igma_crtc_read(const struct igma_chip *cd, int r)
{
       bus_space_write_1(cd->crtct, cd->crtch, 0x4, r);
       return bus_space_read_1(cd->crtct, cd->crtch, 0x5);
}

static void
igma_crtc_write(const struct igma_chip *cd, int r, u_int8_t v)
{
       bus_space_write_1(cd->crtct, cd->crtch, 0x4, r);
       bus_space_write_1(cd->crtct, cd->crtch, 0x5, v);
}
#endif