root/sys/dev/pci/igma.c
/*      $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