src/sys/dev/pci/agp.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);
}