408 lines
9.8 KiB
C
408 lines
9.8 KiB
C
/* $OpenBSD: agp.c,v 1.51 2024/05/24 06:02:53 jsg Exp $ */
|
|
/*-
|
|
* Copyright (c) 2000 Doug Rabson
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD: src/sys/pci/agp.c,v 1.12 2001/05/19 01:28:07 alfred Exp $
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <uvm/uvm_extern.h>
|
|
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
#include <dev/pci/agpvar.h>
|
|
#include <dev/pci/agpreg.h>
|
|
|
|
void agp_attach(struct device *, struct device *, void *);
|
|
int agp_probe(struct device *, void *, void *);
|
|
|
|
int agpvga_match(struct pci_attach_args *);
|
|
|
|
int
|
|
agpdev_print(void *aux, const char *pnp)
|
|
{
|
|
if (pnp) {
|
|
printf("agp at %s", pnp);
|
|
}
|
|
return (UNCONF);
|
|
}
|
|
|
|
int
|
|
agpbus_probe(struct agp_attach_args *aa)
|
|
{
|
|
struct pci_attach_args *pa = aa->aa_pa;
|
|
|
|
if (strncmp(aa->aa_busname, "agp", 3) == 0 &&
|
|
PCI_CLASS(pa->pa_class) == PCI_CLASS_BRIDGE &&
|
|
PCI_SUBCLASS(pa->pa_class) == PCI_SUBCLASS_BRIDGE_HOST)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Find the video card hanging off the agp bus XXX assumes only one bus
|
|
*/
|
|
int
|
|
agpvga_match(struct pci_attach_args *pa)
|
|
{
|
|
if (PCI_CLASS(pa->pa_class) == PCI_CLASS_DISPLAY &&
|
|
PCI_SUBCLASS(pa->pa_class) == PCI_SUBCLASS_DISPLAY_VGA) {
|
|
if (pci_get_capability(pa->pa_pc, pa->pa_tag, PCI_CAP_AGP,
|
|
NULL, NULL))
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
struct device *
|
|
agp_attach_bus(struct pci_attach_args *pa, const struct agp_methods *methods,
|
|
bus_addr_t apaddr, bus_size_t apsize, struct device *dev)
|
|
{
|
|
struct agpbus_attach_args arg;
|
|
|
|
arg.aa_methods = methods;
|
|
arg.aa_pa = pa;
|
|
arg.aa_apaddr = apaddr;
|
|
arg.aa_apsize = apsize;
|
|
|
|
printf("\n"); /* newline from the driver that called us */
|
|
return (config_found(dev, &arg, agpdev_print));
|
|
}
|
|
|
|
int
|
|
agp_probe(struct device *parent, void *match, void *aux)
|
|
{
|
|
/*
|
|
* we don't do any checking here, driver we're attaching this
|
|
* interface to should have already done it.
|
|
*/
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
agp_attach(struct device *parent, struct device *self, void *aux)
|
|
{
|
|
struct agpbus_attach_args *aa = aux;
|
|
struct pci_attach_args *pa = aa->aa_pa;
|
|
struct agp_softc *sc = (struct agp_softc *)self;
|
|
u_int memsize;
|
|
int i;
|
|
|
|
sc->sc_chipc = parent;
|
|
sc->sc_methods = aa->aa_methods;
|
|
sc->sc_apaddr = aa->aa_apaddr;
|
|
sc->sc_apsize = aa->aa_apsize;
|
|
|
|
static const int agp_max[][2] = {
|
|
{0, 0},
|
|
{32, 4},
|
|
{64, 28},
|
|
{128, 96},
|
|
{256, 204},
|
|
{512, 440},
|
|
{1024, 942},
|
|
{2048, 1920},
|
|
{4096, 3932}
|
|
};
|
|
|
|
/*
|
|
* Work out an upper bound for agp memory allocation. This
|
|
* uses a heuristic table from the Linux driver.
|
|
*/
|
|
memsize = ptoa(physmem) >> 20;
|
|
|
|
for (i = 0; i < nitems(agp_max) && memsize > agp_max[i][0]; i++)
|
|
;
|
|
if (i == nitems(agp_max))
|
|
i = nitems(agp_max) - 1;
|
|
sc->sc_maxmem = agp_max[i][1] << 20;
|
|
|
|
sc->sc_pcitag = pa->pa_tag;
|
|
sc->sc_pc = pa->pa_pc;
|
|
sc->sc_id = pa->pa_id;
|
|
sc->sc_dmat = pa->pa_dmat;
|
|
sc->sc_memt = pa->pa_memt;
|
|
|
|
pci_get_capability(sc->sc_pc, sc->sc_pcitag, PCI_CAP_AGP,
|
|
&sc->sc_capoff, NULL);
|
|
|
|
printf(": aperture at 0x%lx, size 0x%lx\n", (u_long)sc->sc_apaddr,
|
|
(u_long)sc->sc_apsize);
|
|
}
|
|
|
|
const struct cfattach agp_ca = {
|
|
sizeof(struct agp_softc), agp_probe, agp_attach,
|
|
NULL, NULL
|
|
};
|
|
|
|
struct cfdriver agp_cd = {
|
|
NULL, "agp", DV_DULL
|
|
};
|
|
|
|
struct agp_gatt *
|
|
agp_alloc_gatt(bus_dma_tag_t dmat, u_int32_t apsize)
|
|
{
|
|
struct agp_gatt *gatt;
|
|
u_int32_t entries = apsize >> AGP_PAGE_SHIFT;
|
|
|
|
gatt = malloc(sizeof(*gatt), M_AGP, M_NOWAIT | M_ZERO);
|
|
if (!gatt)
|
|
return (NULL);
|
|
gatt->ag_entries = entries;
|
|
gatt->ag_size = entries * sizeof(u_int32_t);
|
|
|
|
if (agp_alloc_dmamem(dmat, gatt->ag_size, &gatt->ag_dmamap,
|
|
&gatt->ag_physical, &gatt->ag_dmaseg) != 0) {
|
|
free(gatt, M_AGP, sizeof *gatt);
|
|
return (NULL);
|
|
}
|
|
|
|
if (bus_dmamem_map(dmat, &gatt->ag_dmaseg, 1, gatt->ag_size,
|
|
(caddr_t *)&gatt->ag_virtual, BUS_DMA_NOWAIT) != 0) {
|
|
agp_free_dmamem(dmat, gatt->ag_size, gatt->ag_dmamap,
|
|
&gatt->ag_dmaseg);
|
|
free(gatt, M_AGP, sizeof *gatt);
|
|
return (NULL);
|
|
}
|
|
|
|
agp_flush_cache();
|
|
|
|
return (gatt);
|
|
}
|
|
|
|
void
|
|
agp_free_gatt(bus_dma_tag_t dmat, struct agp_gatt *gatt)
|
|
{
|
|
bus_dmamem_unmap(dmat, (caddr_t)gatt->ag_virtual, gatt->ag_size);
|
|
agp_free_dmamem(dmat, gatt->ag_size, gatt->ag_dmamap, &gatt->ag_dmaseg);
|
|
free(gatt, M_AGP, sizeof *gatt);
|
|
}
|
|
|
|
int
|
|
agp_generic_enable(struct agp_softc *sc, u_int32_t mode)
|
|
{
|
|
struct pci_attach_args pa;
|
|
pcireg_t tstatus, mstatus, command;
|
|
int rq, sba, fw, rate, capoff;
|
|
|
|
if (pci_find_device(&pa, agpvga_match) == 0 ||
|
|
pci_get_capability(pa.pa_pc, pa.pa_tag, PCI_CAP_AGP,
|
|
&capoff, NULL) == 0) {
|
|
printf("agp_generic_enable: not an AGP capable device\n");
|
|
return (-1);
|
|
}
|
|
|
|
tstatus = pci_conf_read(sc->sc_pc, sc->sc_pcitag,
|
|
sc->sc_capoff + AGP_STATUS);
|
|
/* display agp mode */
|
|
mstatus = pci_conf_read(pa.pa_pc, pa.pa_tag,
|
|
capoff + AGP_STATUS);
|
|
|
|
/* Set RQ to the min of mode, tstatus and mstatus */
|
|
rq = AGP_MODE_GET_RQ(mode);
|
|
if (AGP_MODE_GET_RQ(tstatus) < rq)
|
|
rq = AGP_MODE_GET_RQ(tstatus);
|
|
if (AGP_MODE_GET_RQ(mstatus) < rq)
|
|
rq = AGP_MODE_GET_RQ(mstatus);
|
|
|
|
/* Set SBA if all three can deal with SBA */
|
|
sba = (AGP_MODE_GET_SBA(tstatus)
|
|
& AGP_MODE_GET_SBA(mstatus)
|
|
& AGP_MODE_GET_SBA(mode));
|
|
|
|
/* Similar for FW */
|
|
fw = (AGP_MODE_GET_FW(tstatus)
|
|
& AGP_MODE_GET_FW(mstatus)
|
|
& AGP_MODE_GET_FW(mode));
|
|
|
|
/* Figure out the max rate */
|
|
rate = (AGP_MODE_GET_RATE(tstatus)
|
|
& AGP_MODE_GET_RATE(mstatus)
|
|
& AGP_MODE_GET_RATE(mode));
|
|
if (rate & AGP_MODE_RATE_4x)
|
|
rate = AGP_MODE_RATE_4x;
|
|
else if (rate & AGP_MODE_RATE_2x)
|
|
rate = AGP_MODE_RATE_2x;
|
|
else
|
|
rate = AGP_MODE_RATE_1x;
|
|
|
|
/* Construct the new mode word and tell the hardware */
|
|
command = AGP_MODE_SET_RQ(0, rq);
|
|
command = AGP_MODE_SET_SBA(command, sba);
|
|
command = AGP_MODE_SET_FW(command, fw);
|
|
command = AGP_MODE_SET_RATE(command, rate);
|
|
command = AGP_MODE_SET_AGP(command, 1);
|
|
|
|
pci_conf_write(sc->sc_pc, sc->sc_pcitag,
|
|
sc->sc_capoff + AGP_COMMAND, command);
|
|
pci_conf_write(pa.pa_pc, pa.pa_tag, capoff + AGP_COMMAND, command);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Allocates a single-segment block of zeroed, wired dma memory.
|
|
*/
|
|
int
|
|
agp_alloc_dmamem(bus_dma_tag_t tag, size_t size, bus_dmamap_t *mapp,
|
|
bus_addr_t *baddr, bus_dma_segment_t *seg)
|
|
{
|
|
int error, level = 0, nseg;
|
|
|
|
if ((error = bus_dmamem_alloc(tag, size, PAGE_SIZE, 0,
|
|
seg, 1, &nseg, BUS_DMA_NOWAIT | BUS_DMA_ZERO)) != 0)
|
|
goto out;
|
|
level++;
|
|
|
|
if ((error = bus_dmamap_create(tag, size, nseg, size, 0,
|
|
BUS_DMA_NOWAIT, mapp)) != 0)
|
|
goto out;
|
|
level++;
|
|
|
|
if ((error = bus_dmamap_load_raw(tag, *mapp, seg, nseg, size,
|
|
BUS_DMA_NOWAIT)) != 0)
|
|
goto out;
|
|
|
|
*baddr = (*mapp)->dm_segs[0].ds_addr;
|
|
|
|
return (0);
|
|
out:
|
|
switch (level) {
|
|
case 2:
|
|
bus_dmamap_destroy(tag, *mapp);
|
|
/* FALLTHROUGH */
|
|
case 1:
|
|
bus_dmamem_free(tag, seg, nseg);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
agp_free_dmamem(bus_dma_tag_t tag, size_t size, bus_dmamap_t map,
|
|
bus_dma_segment_t *seg)
|
|
{
|
|
bus_dmamap_unload(tag, map);
|
|
bus_dmamap_destroy(tag, map);
|
|
bus_dmamem_free(tag, seg, 1);
|
|
}
|
|
|
|
/* Implementation of the kernel api */
|
|
|
|
void *
|
|
agp_find_device(int unit)
|
|
{
|
|
if (unit >= agp_cd.cd_ndevs || unit < 0)
|
|
return (NULL);
|
|
return (agp_cd.cd_devs[unit]);
|
|
}
|
|
|
|
enum agp_acquire_state
|
|
agp_state(void *dev)
|
|
{
|
|
struct agp_softc *sc = (struct agp_softc *) dev;
|
|
return (sc->sc_state);
|
|
}
|
|
|
|
void
|
|
agp_get_info(void *dev, struct agp_info *info)
|
|
{
|
|
struct agp_softc *sc = (struct agp_softc *)dev;
|
|
|
|
if (sc->sc_capoff != 0)
|
|
info->ai_mode = pci_conf_read(sc->sc_pc, sc->sc_pcitag,
|
|
AGP_STATUS + sc->sc_capoff);
|
|
else
|
|
info->ai_mode = 0; /* i810 doesn't have real AGP */
|
|
info->ai_aperture_base = sc->sc_apaddr;
|
|
info->ai_aperture_size = sc->sc_apsize;
|
|
info->ai_memory_allowed = sc->sc_maxmem;
|
|
info->ai_memory_used = sc->sc_allocated;
|
|
info->ai_devid = sc->sc_id;
|
|
}
|
|
|
|
int
|
|
agp_acquire(void *dev)
|
|
{
|
|
struct agp_softc *sc = (struct agp_softc *)dev;
|
|
|
|
if (sc->sc_chipc == NULL)
|
|
return (EINVAL);
|
|
|
|
if (sc->sc_state != AGP_ACQUIRE_FREE)
|
|
return (EBUSY);
|
|
sc->sc_state = AGP_ACQUIRE_KERNEL;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
agp_release(void *dev)
|
|
{
|
|
struct agp_softc *sc = (struct agp_softc *)dev;
|
|
|
|
if (sc->sc_state == AGP_ACQUIRE_FREE)
|
|
return (0);
|
|
|
|
if (sc->sc_state != AGP_ACQUIRE_KERNEL)
|
|
return (EBUSY);
|
|
|
|
sc->sc_state = AGP_ACQUIRE_FREE;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
agp_enable(void *dev, u_int32_t mode)
|
|
{
|
|
struct agp_softc *sc = dev;
|
|
int ret;
|
|
|
|
if (sc->sc_methods->enable != NULL) {
|
|
ret = sc->sc_methods->enable(sc->sc_chipc, mode);
|
|
} else {
|
|
ret = agp_generic_enable(sc, mode);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
paddr_t
|
|
agp_mmap(struct agp_softc *sc, off_t off, int prot)
|
|
{
|
|
if (sc->sc_chipc == NULL)
|
|
return (-1);
|
|
|
|
if (off >= sc->sc_apsize)
|
|
return (-1);
|
|
|
|
if (sc->sc_apaddr == 0)
|
|
return (-1);
|
|
|
|
return bus_space_mmap(sc->sc_memt, sc->sc_apaddr, off, prot, 0);
|
|
}
|