blob: 0586279ce214fac7d2a7e6c87a9409a6e32eb5af [file] [log] [blame]
// QEMU ATI VGABIOS Extension.
//
// This file may be distributed under the terms of the GNU LGPLv3 license.
#include "biosvar.h" // GET_GLOBAL
#include "bregs.h" // struct bregs
#include "hw/pci.h" // pci_config_readl
#include "hw/pci_regs.h" // PCI_BASE_ADDRESS_0
#include "output.h" // dprintf
#include "stdvga.h" // VGAREG_SEQU_ADDRESS
#include "string.h" // memset16_far
#include "vgabios.h" // SET_VGA
#include "vgautil.h" // VBE_total_memory
#include "vgafb.h" // memset_high
#include "svgamodes.h"
#define MM_INDEX 0x0000
#define MM_DATA 0x0004
#define CRTC_GEN_CNTL 0x0050
#define CRTC_EXT_CNTL 0x0054
#define CRTC_H_TOTAL_DISP 0x0200
#define CRTC_V_TOTAL_DISP 0x0208
#define CRTC_OFFSET 0x0224
#define CRTC_PITCH 0x022c
/* CRTC control values (CRTC_GEN_CNTL) */
#define CRTC2_EXT_DISP_EN 0x01000000
#define CRTC2_EN 0x02000000
#define CRTC_PIX_WIDTH_MASK 0x00000700
#define CRTC_PIX_WIDTH_4BPP 0x00000100
#define CRTC_PIX_WIDTH_8BPP 0x00000200
#define CRTC_PIX_WIDTH_15BPP 0x00000300
#define CRTC_PIX_WIDTH_16BPP 0x00000400
#define CRTC_PIX_WIDTH_24BPP 0x00000500
#define CRTC_PIX_WIDTH_32BPP 0x00000600
/* CRTC_EXT_CNTL */
#define CRT_CRTC_DISPLAY_DIS 0x00000400
#define CRT_CRTC_ON 0x00008000
static u32 ati_io_addr VAR16 = 0;
int
is_ati_mode(struct vgamode_s *vmode_g)
{
unsigned int mcount = GET_GLOBAL(svga_mcount);
return (vmode_g >= &svga_modes[0].info &&
vmode_g <= &svga_modes[mcount-1].info);
}
struct vgamode_s *
ati_find_mode(int mode)
{
u32 io_addr = GET_GLOBAL(ati_io_addr);
struct generic_svga_mode *table_g = svga_modes;
unsigned int mcount = GET_GLOBAL(svga_mcount);
if (io_addr) {
while (table_g < &svga_modes[mcount]) {
if (GET_GLOBAL(table_g->mode) == mode)
return &table_g->info;
table_g++;
}
}
return stdvga_find_mode(mode);
}
void
ati_list_modes(u16 seg, u16 *dest, u16 *last)
{
u32 io_addr = GET_GLOBAL(ati_io_addr);
unsigned int mcount = GET_GLOBAL(svga_mcount);
dprintf(1, "%s: ati ext %s\n", __func__, io_addr ? "yes" : "no");
if (io_addr) {
int i;
for (i=0; i<mcount && dest<last; i++) {
u16 mode = GET_GLOBAL(svga_modes[i].mode);
if (mode == 0xffff)
continue;
SET_FARVAR(seg, *dest, mode);
dest++;
}
}
stdvga_list_modes(seg, dest, last);
}
/****************************************************************
* Mode setting
****************************************************************/
static inline void ati_write(u32 reg, u32 val)
{
u32 io_addr = GET_GLOBAL(ati_io_addr);
if (reg < 0x100) {
outl(val, io_addr + reg);
} else {
outl(reg, io_addr + MM_INDEX);
outl(val, io_addr + MM_DATA);
}
}
static void ati_clear(u32 offset, u32 size)
{
u8 data[64];
void *datap = MAKE_FLATPTR(GET_SEG(SS), data);
void *fb = (void*)(GET_GLOBAL(VBE_framebuffer) + offset);
u32 i, pos;
for (i = 0; i < sizeof(data); i++)
data[i] = 0;
for (pos = 0; pos < size; pos += sizeof(data)) {
memcpy_high(fb, datap, sizeof(data));
fb += sizeof(data);
}
}
static int
ati_ext_mode(struct generic_svga_mode *table, int flags)
{
u32 width = GET_GLOBAL(table->info.width);
u32 height = GET_GLOBAL(table->info.height);
u32 depth = GET_GLOBAL(table->info.depth);
u32 stride = width;
u32 offset = 0;
u32 pxmask = 0;
u32 bytes = 0;
dprintf(1, "%s: 0x%x, %dx%d-%d\n", __func__,
GET_GLOBAL(table->mode),
width, height, depth);
switch (depth) {
case 8: pxmask = CRTC_PIX_WIDTH_8BPP; bytes = 1; break;
case 15: pxmask = CRTC_PIX_WIDTH_15BPP; bytes = 2; break;
case 16: pxmask = CRTC_PIX_WIDTH_16BPP; bytes = 2; break;
case 24: pxmask = CRTC_PIX_WIDTH_24BPP; bytes = 3; break;
case 32: pxmask = CRTC_PIX_WIDTH_32BPP; bytes = 4; break;
}
/* disable display */
ati_write(CRTC_EXT_CNTL, CRT_CRTC_DISPLAY_DIS);
/* modeset */
ati_write(CRTC_GEN_CNTL, CRTC2_EXT_DISP_EN | CRTC2_EN | pxmask);
ati_write(CRTC_H_TOTAL_DISP, ((width / 8) - 1) << 16);
ati_write(CRTC_V_TOTAL_DISP, (height - 1) << 16);
ati_write(CRTC_OFFSET, offset);
ati_write(CRTC_PITCH, stride / 8);
/* clear screen */
if (!(flags & MF_NOCLEARMEM)) {
u32 size = width * height * bytes;
ati_clear(offset, size);
}
/* enable display */
ati_write(CRTC_EXT_CNTL, 0);
return 0;
}
int
ati_set_mode(struct vgamode_s *vmode_g, int flags)
{
struct generic_svga_mode *table_g =
container_of(vmode_g, struct generic_svga_mode, info);
if (is_ati_mode(vmode_g)) {
return ati_ext_mode(table_g, flags);
}
ati_write(CRTC_GEN_CNTL, 0);
return stdvga_set_mode(vmode_g, flags);
}
/****************************************************************
* init
****************************************************************/
int
ati_setup(void)
{
int ret = stdvga_setup();
if (ret)
return ret;
dprintf(1, "%s:%d\n", __func__, __LINE__);
if (GET_GLOBAL(HaveRunInit))
return 0;
int bdf = GET_GLOBAL(VgaBDF);
if (!CONFIG_VGA_PCI || bdf == 0)
return 0;
u32 bar = pci_config_readl(bdf, PCI_BASE_ADDRESS_0);
u32 lfb_addr = bar & PCI_BASE_ADDRESS_MEM_MASK;
pci_config_writel(bdf, PCI_BASE_ADDRESS_0, ~0);
u32 barmask = pci_config_readl(bdf, PCI_BASE_ADDRESS_0);
u32 totalmem = ~(barmask & PCI_BASE_ADDRESS_MEM_MASK) + 1;
pci_config_writel(bdf, PCI_BASE_ADDRESS_0, bar);
bar = pci_config_readl(bdf, PCI_BASE_ADDRESS_1);
u32 io_addr = bar & PCI_BASE_ADDRESS_IO_MASK;
bar = pci_config_readl(bdf, PCI_BASE_ADDRESS_2);
u32 mmio_addr = bar & PCI_BASE_ADDRESS_MEM_MASK;
dprintf(1, "ati: bdf %02x:%02x.%x, lfb 0x%x, %d MB, io 0x%x, mmio 0x%x\n",
pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf), pci_bdf_to_fn(bdf),
lfb_addr, totalmem / (1024 * 1024), io_addr, mmio_addr);
SET_VGA(VBE_framebuffer, lfb_addr);
SET_VGA(VBE_total_memory, totalmem);
SET_VGA(ati_io_addr, io_addr);
// Validate modes
struct generic_svga_mode *m = svga_modes;
unsigned int mcount = GET_GLOBAL(svga_mcount);
for (; m < &svga_modes[mcount]; m++) {
u8 memmodel = GET_GLOBAL(m->info.memmodel);
u16 width = GET_GLOBAL(m->info.width);
u16 height = GET_GLOBAL(m->info.height);
u32 mem = (height * DIV_ROUND_UP(width * vga_bpp(&m->info), 8)
* stdvga_vram_ratio(&m->info));
if (width % 8 != 0 ||
width > 0x7ff * 8 ||
height > 0xfff ||
mem > totalmem ||
memmodel != MM_DIRECT) {
dprintf(1, "ati: removing mode 0x%x\n", GET_GLOBAL(m->mode));
SET_VGA(m->mode, 0xffff);
}
}
return 0;
}