Copyright 2007-2011 Haiku, Inc. All rights reserved.
Distributed under the terms of the MIT license.
Authors:
Gerald Zajac
*/
#include <KernelExport.h>
#include <PCI.h>
#include <drivers/bios.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <graphic_driver.h>
#include <boot_item.h>
#include "DriverInterface.h"
#undef TRACE
#ifdef ENABLE_DEBUG_TRACE
# define TRACE(x...) dprintf("ati: " x)
#else
# define TRACE(x...) ;
#endif
#define ATI_ACCELERANT_NAME "ati.accelerant"
#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))
#define VESA_MODES_BOOT_INFO "vesa_modes/v1"
#define SKD_HANDLER_INSTALLED 0x80000000
#define MAX_DEVICES 4
#define DEVICE_FORMAT "%04X_%04X_%02X%02X%02X"
#define M64_BIOS_SIZE 0x10000 // 64KB
#define R128_BIOS_SIZE 0x10000 // 64KB
int32 api_version = B_CUR_DRIVER_API_VERSION;
#define VENDOR_ID 0x1002 // ATI vendor ID
#define M64_CLOCK_INTERNAL 4
#define M64_CONFIG_CHIP_ID 0x0CE0 // offset in register area
#define M64_CFG_CHIP_TYPE 0x0000FFFF
struct ChipInfo {
uint16 chipID;
ChipType chipType;
const char* chipName;
};
static char sRage128_GL[] = "RAGE 128 GL";
static char sRage128_VR[] = "RAGE 128 VR";
static char sRage128_Pro_GL[] = "RAGE 128 PRO GL";
static char sRage128_Pro_VR[] = "RAGE 128 PRO VR";
static char sRage128_Pro_Ultra[] = "RAGE 128 PRO Ultra";
static const ChipInfo chipTable[] = {
{ 0x4742, MACH64_264GTPRO, "3D RAGE PRO, AGP" },
{ 0x4744, MACH64_264GTPRO, "3D RAGE PRO, AGP" },
{ 0x4749, MACH64_264GTPRO, "3D RAGE PRO, PCI" },
{ 0x474C, MACH64_264XL, "3D RAGE XC, PCI" },
{ 0x474D, MACH64_264XL, "3D RAGE XL, AGP" },
{ 0x474E, MACH64_264XL, "3D RAGE XC, AGP" },
{ 0x474F, MACH64_264XL, "3D RAGE XL, PCI" },
{ 0x4750, MACH64_264GTPRO, "3D RAGE PRO, PCI" },
{ 0x4751, MACH64_264GTPRO, "3D RAGE PRO, PCI" },
{ 0x4752, MACH64_264XL, "3D RAGE XL, PCI" },
{ 0x4753, MACH64_264XL, "3D RAGE XC, PCI" },
{ 0x4754, MACH64_264GT, "3D RAGE II" },
{ 0x4755, MACH64_264GTDVD, "3D RAGE II+" },
{ 0x4756, MACH64_264GT2C, "3D RAGE IIC, PCI" },
{ 0x4757, MACH64_264GT2C, "3D RAGE IIC, AGP" },
{ 0x4759, MACH64_264GT2C, "3D RAGE IIC, PCI" },
{ 0x475A, MACH64_264GT2C, "3D RAGE IIC, AGP" },
{ 0x4C42, MACH64_264LTPRO, "3D RAGE LT PRO, AGP" },
{ 0x4C44, MACH64_264LTPRO, "3D RAGE LT PRO, AGP" },
{ 0x4C47, MACH64_264LT, "3D RAGE LT" },
{ 0x4C49, MACH64_264LTPRO, "3D RAGE LT PRO, PCI" },
{ 0x4C4D, MACH64_MOBILITY, "3D RAGE Mobility, AGP" },
{ 0x4C4E, MACH64_MOBILITY, "3D RAGE Mobility, AGP" },
{ 0x4C50, MACH64_264LTPRO, "3D RAGE LT PRO, PCI" },
{ 0x4C51, MACH64_264LTPRO, "3D RAGE LT PRO, PCI" },
{ 0x4C52, MACH64_MOBILITY, "3D RAGE Mobility, PCI" },
{ 0x4C53, MACH64_MOBILITY, "3D RAGE Mobility, PCI" },
{ 0x5654, MACH64_264VT, "264VT2" },
{ 0x5655, MACH64_264VT3, "264VT3" },
{ 0x5656, MACH64_264VT4, "264VT4" },
{ 0x4C45, RAGE128_MOBILITY, "RAGE 128 Mobility 3" },
{ 0x4C46, RAGE128_MOBILITY, "RAGE 128 Mobility 3" },
{ 0x4D46, RAGE128_MOBILITY, "RAGE 128 Mobility 4" },
{ 0x4D4C, RAGE128_MOBILITY, "RAGE 128 Mobility 4" },
{ 0x5041, RAGE128_PRO_GL, sRage128_Pro_GL },
{ 0x5042, RAGE128_PRO_GL, sRage128_Pro_GL },
{ 0x5043, RAGE128_PRO_GL, sRage128_Pro_GL },
{ 0x5044, RAGE128_PRO_GL, sRage128_Pro_GL },
{ 0x5045, RAGE128_PRO_GL, sRage128_Pro_GL },
{ 0x5046, RAGE128_PRO_GL, sRage128_Pro_GL },
{ 0x5047, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5048, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5049, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x504A, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x504B, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x504C, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x504D, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x504E, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x504F, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5050, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5051, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5052, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5053, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5054, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5055, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5056, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5057, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5058, RAGE128_PRO_VR, sRage128_Pro_VR },
{ 0x5245, RAGE128_GL, sRage128_GL },
{ 0x5246, RAGE128_GL, sRage128_GL },
{ 0x5247, RAGE128_GL, sRage128_GL },
{ 0x524B, RAGE128_VR, sRage128_VR },
{ 0x524C, RAGE128_VR, sRage128_VR },
{ 0x5345, RAGE128_VR, sRage128_VR },
{ 0x5346, RAGE128_VR, sRage128_VR },
{ 0x5347, RAGE128_VR, sRage128_VR },
{ 0x5348, RAGE128_VR, sRage128_VR },
{ 0x534B, RAGE128_GL, sRage128_GL },
{ 0x534C, RAGE128_GL, sRage128_GL },
{ 0x534D, RAGE128_GL, sRage128_GL },
{ 0x534E, RAGE128_GL, sRage128_GL },
{ 0x5446, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra },
{ 0x544C, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra },
{ 0x5452, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra },
{ 0x5453, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra },
{ 0x5454, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra },
{ 0x5455, RAGE128_PRO_ULTRA, sRage128_Pro_Ultra },
{ 0, ATI_NONE, NULL }
};
struct DeviceInfo {
uint32 openCount;
int32 flags;
area_id sharedArea;
SharedInfo* sharedInfo;
vuint8* regs;
const ChipInfo* pChipInfo;
pci_info pciInfo;
char name[B_OS_NAME_LENGTH];
};
static Benaphore gLock;
static DeviceInfo gDeviceInfo[MAX_DEVICES];
static char* gDeviceNames[MAX_DEVICES + 1];
static pci_module_info* gPCI;
static status_t device_open(const char* name, uint32 flags, void** cookie);
static status_t device_close(void* dev);
static status_t device_free(void* dev);
static status_t device_read(void* dev, off_t pos, void* buf, size_t* len);
static status_t device_write(void* dev, off_t pos, const void* buf, size_t* len);
static status_t device_ioctl(void* dev, uint32 msg, void* buf, size_t len);
static device_hooks gDeviceHooks =
{
device_open,
device_close,
device_free,
device_ioctl,
device_read,
device_write,
NULL,
NULL,
NULL,
NULL
};
static inline uint32
GetPCI(pci_info& info, uint8 offset, uint8 size)
{
return gPCI->read_pci_config(info.bus, info.device, info.function, offset,
size);
}
static inline void
SetPCI(pci_info& info, uint8 offset, uint8 size, uint32 value)
{
gPCI->write_pci_config(info.bus, info.device, info.function, offset, size,
value);
}
static bool
InterruptIsVBI()
{
return false;
}
static void
ClearVBI()
{
}
static void
EnableVBI()
{
}
static void
DisableVBI()
{
}
static status_t
GetEdidFromBIOS(edid1_raw& edidRaw)
{
#define ADDRESS_SEGMENT(address) ((addr_t)(address) >> 4)
#define ADDRESS_OFFSET(address) ((addr_t)(address) & 0xf)
bios_module_info* biosModule;
status_t status = get_module(B_BIOS_MODULE_NAME, (module_info**)&biosModule);
if (status != B_OK) {
TRACE("GetEdidFromBIOS(): failed to get BIOS module: 0x%" B_PRIx32 "\n",
status);
return status;
}
bios_state* state;
status = biosModule->prepare(&state);
if (status != B_OK) {
TRACE("GetEdidFromBIOS(): bios_prepare() failed: 0x%" B_PRIx32 "\n",
status);
put_module(B_BIOS_MODULE_NAME);
return status;
}
bios_regs regs = {};
regs.eax = 0x4f15;
regs.ebx = 0;
regs.ecx = 0;
regs.es = 0;
regs.edi = 0;
status = biosModule->interrupt(state, 0x10, ®s);
if (status == B_OK) {
if (regs.eax != 0x4f)
status = B_NOT_SUPPORTED;
if ((regs.ebx & 3) == 0)
status = B_NOT_SUPPORTED;
}
if (status == B_OK) {
edid1_raw* edid = (edid1_raw*)biosModule->allocate_mem(state,
sizeof(edid1_raw));
if (edid == NULL) {
status = B_NO_MEMORY;
goto out;
}
regs.eax = 0x4f15;
regs.ebx = 1;
regs.ecx = 0;
regs.edx = 0;
regs.es = ADDRESS_SEGMENT(edid);
regs.edi = ADDRESS_OFFSET(edid);
status = biosModule->interrupt(state, 0x10, ®s);
if (status == B_OK) {
if (regs.eax != 0x4f) {
status = B_NOT_SUPPORTED;
} else {
uint8 sum = 0;
uint8 allOr = 0;
uint8* dest = (uint8*)&edidRaw;
uint8* src = (uint8*)edid;
for (uint32 j = 0; j < sizeof(edidRaw); j++) {
sum += *src;
allOr |= *src;
*dest++ = *src++;
}
if (allOr == 0) {
TRACE("GetEdidFromBIOS(); EDID info contains only zeros\n");
status = B_ERROR;
} else if (sum != 0) {
TRACE("GetEdidFromBIOS(); Checksum error in EDID info\n");
status = B_ERROR;
}
}
}
}
out:
biosModule->finish(state);
put_module(B_BIOS_MODULE_NAME);
return status;
}
static status_t
SetVesaDisplayMode(uint16 mode)
{
#define SET_MODE_MASK 0x01ff
#define SET_MODE_LINEAR_BUFFER (1 << 14)
bios_module_info* biosModule;
status_t status = get_module(B_BIOS_MODULE_NAME, (module_info**)&biosModule);
if (status != B_OK) {
TRACE("SetVesaDisplayMode(0x%x): failed to get BIOS module: 0x%" B_PRIx32
"\n", mode, status);
return status;
}
bios_state* state;
status = biosModule->prepare(&state);
if (status != B_OK) {
TRACE("SetVesaDisplayMode(0x%x): bios_prepare() failed: 0x%" B_PRIx32
"\n", mode, status);
put_module(B_BIOS_MODULE_NAME);
return status;
}
bios_regs regs = {};
regs.eax = 0x4f02;
regs.ebx = (mode & SET_MODE_MASK) | SET_MODE_LINEAR_BUFFER;
status = biosModule->interrupt(state, 0x10, ®s);
if (status != B_OK) {
TRACE("SetVesaDisplayMode(0x%x): BIOS interrupt failed\n", mode);
}
if (status == B_OK && (regs.eax & 0xffff) != 0x4f) {
TRACE("SetVesaDisplayMode(0x%x): BIOS returned 0x%04" B_PRIx32 "\n",
mode, regs.eax & 0xffff);
status = B_ERROR;
}
biosModule->finish(state);
put_module(B_BIOS_MODULE_NAME);
return status;
}
#define BIOS8(v) (romAddr[v])
#define BIOS16(v) (romAddr[v] | \
(romAddr[(v) + 1] << 8))
#define BIOS32(v) (romAddr[v] | \
(romAddr[(v) + 1] << 8) | \
(romAddr[(v) + 2] << 16) | \
(romAddr[(v) + 3] << 24))
static status_t
Mach64_GetBiosParameters(DeviceInfo& di, uint8& clockType)
{
clockType = M64_CLOCK_INTERNAL;
SharedInfo& si = *(di.sharedInfo);
M64_Params& params = si.m64Params;
params.clockNumberToProgram = 3;
si.panelX = 0;
si.panelY = 0;
uint8* romAddr;
area_id romArea = map_physical_memory("ATI Mach64 ROM",
#if defined(__x86__) || defined(__x86_64__)
0x000c0000,
#else
di.pciInfo.u.h0.rom_base,
#endif
M64_BIOS_SIZE,
B_ANY_KERNEL_ADDRESS,
B_KERNEL_READ_AREA,
(void**)&(romAddr));
if (romArea < 0) {
TRACE("Mach64_GetBiosParameters(), ROM mapping error: %" B_PRId32 "\n",
romArea);
return romArea;
}
if (BIOS8(0) != 0x55 || BIOS8(1) != 0xaa) {
TRACE("Mach64_GetBiosParameters(), ROM does not contain BIOS signature\n");
delete_area(romArea);
return B_ERROR;
}
uint32 romTable = BIOS16(0x48);
uint32 clockTable = BIOS16(romTable + 16);
clockType = BIOS8(clockTable);
params.clockNumberToProgram = BIOS8(clockTable + 6);
params.maxPixelClock = BIOS16(clockTable + 4) * 10;
params.refFreq = BIOS16(clockTable + 8);
params.refDivider = BIOS16(clockTable + 10);
if (si.chipType == MACH64_MOBILITY) {
uint32 lcdTable = BIOS16(0x78);
if (BIOS32(lcdTable) == 0x544d5224) {
uint32 lcdPanelInfo = BIOS16(lcdTable + 10);
si.panelX = BIOS16(lcdPanelInfo + 25);
si.panelY = BIOS16(lcdPanelInfo + 27);
TRACE("Mobility LCD Panel size: %dx%d\n", si.panelX, si.panelY);
} else {
TRACE("Mobility LCD table signature 0x%x in BIOS is incorrect\n",
BIOS32(lcdTable));
}
}
delete_area(romArea);
return B_OK;
}
static status_t
Rage128_GetBiosParameters(DeviceInfo& di)
{
SharedInfo& si = *(di.sharedInfo);
R128_PLLParams& pll = si.r128PLLParams;
pll.reference_freq = 2950;
pll.reference_div = 65;
pll.min_pll_freq = 12500;
pll.max_pll_freq = 25000;
pll.xclk = 10300;
si.panelX = 0;
si.panelY = 0;
si.panelPowerDelay = 1;
uint8* romAddr;
area_id romArea = map_physical_memory("ATI Rage128 ROM",
#if defined(__x86__) || defined(__x86_64__)
0x000c0000,
#else
di.pciInfo.u.h0.rom_base,
#endif
R128_BIOS_SIZE,
B_ANY_KERNEL_ADDRESS,
B_KERNEL_READ_AREA,
(void**)&(romAddr));
if (romArea < 0) {
TRACE("Rage128_GetBiosParameters(), ROM mapping error: %" B_PRId32
"\n", romArea);
return romArea;
}
if (BIOS8(0) != 0x55 || BIOS8(1) != 0xaa) {
TRACE("Rage128_GetBiosParameters(), ROM does not contain BIOS signature\n");
delete_area(romArea);
return B_ERROR;
}
uint16 biosHeader = BIOS16(0x48);
uint16 pllInfoBlock = BIOS16(biosHeader + 0x30);
pll.reference_freq = BIOS16(pllInfoBlock + 0x0e);
pll.reference_div = BIOS16(pllInfoBlock + 0x10);
pll.min_pll_freq = BIOS32(pllInfoBlock + 0x12);
pll.max_pll_freq = BIOS32(pllInfoBlock + 0x16);
pll.xclk = BIOS16(pllInfoBlock + 0x08);
TRACE("PLL parameters: rf=%d rd=%d min=%" B_PRId32 " max=%" B_PRId32
"; xclk=%d\n",
pll.reference_freq, pll.reference_div, pll.min_pll_freq,
pll.max_pll_freq, pll.xclk);
if (si.chipType == RAGE128_MOBILITY) {
int i;
for (i = 4; i < R128_BIOS_SIZE - 8; i++) {
if (BIOS8(i) == 'M' &&
BIOS8(i + 1) == '3' &&
BIOS8(i + 2) == ' ' &&
BIOS8(i + 3) == ' ' &&
BIOS8(i + 4) == ' ' &&
BIOS8(i + 5) == ' ' &&
BIOS8(i + 6) == ' ' &&
BIOS8(i + 7) == ' ') {
int fpHeader = i - 2;
for (i = fpHeader + 20; i < fpHeader + 84; i += 2) {
if (BIOS16(i) != 0) {
int fpStart = BIOS16(i);
si.panelX = BIOS16(fpStart + 25);
si.panelY = BIOS16(fpStart + 27);
si.panelPowerDelay = BIOS8(fpStart + 56);
TRACE("LCD Panel size: %dx%d Panel type: 0x%x power delay: %d\n",
si.panelX, si.panelY, BIOS16(fpStart + 29),
si.panelPowerDelay);
break;
}
}
break;
}
}
}
delete_area(romArea);
return B_OK;
}
static status_t
MapDevice(DeviceInfo& di)
{
SharedInfo& si = *(di.sharedInfo);
pci_info& pciInfo = di.pciInfo;
SetPCI(pciInfo, PCI_command, 2, GetPCI(pciInfo, PCI_command, 2)
| PCI_command_io | PCI_command_memory | PCI_command_master);
if (di.pciInfo.u.h0.rom_size > 0) {
SetPCI(pciInfo, PCI_rom_base, 4,
GetPCI(pciInfo, PCI_rom_base, 4) | 0x00000001);
}
phys_addr_t videoRamAddr = pciInfo.u.h0.base_registers[0];
uint32 videoRamSize = pciInfo.u.h0.base_register_sizes[0];
si.videoMemPCI = videoRamAddr;
char frameBufferAreaName[] = "ATI frame buffer";
si.videoMemArea = map_physical_memory(
frameBufferAreaName,
videoRamAddr,
videoRamSize,
B_ANY_KERNEL_BLOCK_ADDRESS | B_MTR_WC,
B_READ_AREA + B_WRITE_AREA,
(void**)&(si.videoMemAddr));
if (si.videoMemArea < 0) {
si.videoMemArea = map_physical_memory(
frameBufferAreaName,
videoRamAddr,
videoRamSize,
B_ANY_KERNEL_BLOCK_ADDRESS,
B_READ_AREA + B_WRITE_AREA,
(void**)&(si.videoMemAddr));
}
if (si.videoMemArea < 0)
return si.videoMemArea;
phys_addr_t regsBase = pciInfo.u.h0.base_registers[2];
uint32 regAreaSize = pciInfo.u.h0.base_register_sizes[2];
if (MACH64_FAMILY(si.chipType) && (regsBase == 0 || regAreaSize == 0)) {
uint32 regsOffset = 0x7ff000;
addr_t regs = addr_t(si.videoMemAddr) + regsOffset;
uint32 chipInfo = *((vuint32*)(regs + M64_CONFIG_CHIP_ID));
if (si.deviceID != (chipInfo & M64_CFG_CHIP_TYPE)) {
delete_area(si.videoMemArea);
si.videoMemArea = -1;
TRACE("Mach64 register area not found\n");
return B_ERROR;
}
regsBase = videoRamAddr + regsOffset;
regAreaSize = 0x1000;
TRACE("Register address is at end of frame buffer memory at 0x%"
B_PRIxPHYSADDR "\n", regsBase);
}
si.regsArea = map_physical_memory("ATI mmio registers",
regsBase,
regAreaSize,
B_ANY_KERNEL_ADDRESS,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA,
(void**)&di.regs);
if (si.regsArea < 0) {
delete_area(si.videoMemArea);
si.videoMemArea = -1;
}
return si.regsArea;
}
static void
UnmapDevice(DeviceInfo& di)
{
SharedInfo& si = *(di.sharedInfo);
if (si.regsArea >= 0)
delete_area(si.regsArea);
if (si.videoMemArea >= 0)
delete_area(si.videoMemArea);
si.regsArea = si.videoMemArea = -1;
si.videoMemAddr = (addr_t)NULL;
di.regs = NULL;
}
static int32
InterruptHandler(void* data)
{
int32 handled = B_UNHANDLED_INTERRUPT;
DeviceInfo& di = *((DeviceInfo*)data);
int32* flags = &(di.flags);
if (atomic_or(flags, SKD_HANDLER_INSTALLED) & SKD_HANDLER_INSTALLED)
return B_UNHANDLED_INTERRUPT;
if (InterruptIsVBI()) {
ClearVBI();
handled = B_HANDLED_INTERRUPT;
sem_id& sem = di.sharedInfo->vertBlankSem;
if (sem >= 0) {
int32 blocked;
if ((get_sem_count(sem, &blocked) == B_OK) && (blocked < 0)) {
release_sem_etc(sem, -blocked, B_DO_NOT_RESCHEDULE);
handled = B_INVOKE_SCHEDULER;
}
}
}
atomic_and(flags, ~SKD_HANDLER_INSTALLED);
return handled;
}
static void
InitInterruptHandler(DeviceInfo& di)
{
SharedInfo& si = *(di.sharedInfo);
DisableVBI();
si.bInterruptAssigned = false;
si.vertBlankSem = create_sem(0, di.name);
if (si.vertBlankSem < 0)
return;
thread_id threadID = find_thread(NULL);
thread_info threadInfo;
status_t status = get_thread_info(threadID, &threadInfo);
if (status == B_OK)
status = set_sem_owner(si.vertBlankSem, threadInfo.team);
if (status == B_OK && di.pciInfo.u.h0.interrupt_pin != 0x00
&& di.pciInfo.u.h0.interrupt_line != 0xff) {
status = install_io_interrupt_handler(di.pciInfo.u.h0.interrupt_line,
InterruptHandler, (void*)(&di), 0);
if (status == B_OK)
si.bInterruptAssigned = true;
}
if (status != B_OK) {
delete_sem(si.vertBlankSem);
si.vertBlankSem = -1;
}
}
static status_t
InitDevice(DeviceInfo& di)
{
size_t vesaModeTableSize = 0;
VesaMode* vesaModes = (VesaMode*)get_boot_item(VESA_MODES_BOOT_INFO,
&vesaModeTableSize);
size_t sharedSize = (sizeof(SharedInfo) + 7) & ~7;
di.sharedArea = create_area("ATI shared info",
(void**) &(di.sharedInfo),
B_ANY_KERNEL_ADDRESS,
ROUND_TO_PAGE_SIZE(sharedSize + vesaModeTableSize),
B_FULL_LOCK,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA);
if (di.sharedArea < 0)
return di.sharedArea;
SharedInfo& si = *(di.sharedInfo);
memset(&si, 0, sharedSize);
if (vesaModes != NULL) {
si.vesaModeTableOffset = sharedSize;
si.vesaModeCount = vesaModeTableSize / sizeof(VesaMode);
memcpy((uint8*)&si + si.vesaModeTableOffset, vesaModes,
vesaModeTableSize);
}
pci_info& pciInfo = di.pciInfo;
si.vendorID = pciInfo.vendor_id;
si.deviceID = pciInfo.device_id;
si.revision = pciInfo.revision;
si.chipType = di.pChipInfo->chipType;
strcpy(si.chipName, di.pChipInfo->chipName);
TRACE("Chip revision: 0x%x\n", si.revision);
if (si.chipType == MACH64_264GT && si.revision & 0x7)
si.chipType = MACH64_264GTB;
if (si.chipType == MACH64_264VT && si.revision & 0x7)
si.chipType = MACH64_264VTB;
status_t status = MapDevice(di);
if (status >= 0) {
if (MACH64_FAMILY(si.chipType)) {
uint8 clockType;
Mach64_GetBiosParameters(di, clockType);
if (clockType != M64_CLOCK_INTERNAL) {
TRACE("Video chip clock type %d not supported\n", clockType);
status = B_UNSUPPORTED;
}
}
else if (RAGE128_FAMILY(si.chipType))
Rage128_GetBiosParameters(di);
}
if (status < 0) {
delete_area(di.sharedArea);
di.sharedArea = -1;
di.sharedInfo = NULL;
return status;
}
InitInterruptHandler(di);
TRACE("Interrupt assigned: %s\n", si.bInterruptAssigned ? "yes" : "no");
return B_OK;
}
static const ChipInfo*
GetNextSupportedDevice(uint32& pciIndex, pci_info& pciInfo)
{
while (gPCI->get_nth_pci_info(pciIndex, &pciInfo) == B_OK) {
if (pciInfo.vendor_id == VENDOR_ID) {
const ChipInfo* pDevice = chipTable;
while (pDevice->chipID != 0) {
if (pDevice->chipID == pciInfo.device_id) {
if (pDevice->chipType == MACH64_264VT
&& (pciInfo.revision & 0x7) == 0)
break;
return pDevice;
}
pDevice++;
}
}
pciIndex++;
}
return NULL;
}
status_t
init_hardware(void)
{
if (get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI) != B_OK)
return B_ERROR;
uint32 pciIndex = 0;
pci_info pciInfo;
const ChipInfo* pDevice = GetNextSupportedDevice(pciIndex, pciInfo);
TRACE("init_hardware() - %s\n",
pDevice == NULL ? "no supported devices" : "device supported");
put_module(B_PCI_MODULE_NAME);
return (pDevice == NULL ? B_ERROR : B_OK);
}
status_t
init_driver(void)
{
if (get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI) != B_OK)
return B_ERROR;
status_t status = gLock.Init("ATI driver lock");
if (status < B_OK)
return status;
uint32 pciIndex = 0;
uint32 count = 0;
while (count < MAX_DEVICES) {
DeviceInfo& di = gDeviceInfo[count];
const ChipInfo* pDevice = GetNextSupportedDevice(pciIndex, di.pciInfo);
if (pDevice == NULL)
break;
sprintf(di.name, "graphics/" DEVICE_FORMAT,
di.pciInfo.vendor_id, di.pciInfo.device_id,
di.pciInfo.bus, di.pciInfo.device, di.pciInfo.function);
TRACE("init_driver() match found; name: %s\n", di.name);
gDeviceNames[count] = di.name;
di.openCount = 0;
di.sharedArea = -1;
di.sharedInfo = NULL;
di.pChipInfo = pDevice;
count++;
pciIndex++;
}
gDeviceNames[count] = NULL;
TRACE("init_driver() %" B_PRIu32 " supported devices\n", count);
return B_OK;
}
void
uninit_driver(void)
{
gLock.Delete();
put_module(B_PCI_MODULE_NAME);
}
const char**
publish_devices(void)
{
return (const char**)gDeviceNames;
}
device_hooks*
find_device(const char* name)
{
int i = 0;
while (gDeviceNames[i] != NULL) {
if (strcmp(name, gDeviceNames[i]) == 0)
return &gDeviceHooks;
i++;
}
return NULL;
}
static status_t
device_open(const char* name, uint32 , void** cookie)
{
status_t status = B_OK;
TRACE("device_open() - name: %s\n", name);
int32 i = 0;
while (gDeviceNames[i] != NULL && (strcmp(name, gDeviceNames[i]) != 0))
i++;
if (gDeviceNames[i] == NULL)
return B_BAD_VALUE;
DeviceInfo& di = gDeviceInfo[i];
gLock.Acquire();
if (di.openCount == 0)
status = InitDevice(di);
gLock.Release();
if (status == B_OK) {
di.openCount++;
*cookie = &di;
}
TRACE("device_open() returning 0x%" B_PRIx32 ", open count: %" B_PRIu32 "\n", status,
di.openCount);
return status;
}
static status_t
device_read(void* dev, off_t pos, void* buf, size_t* len)
{
(void)dev;
(void)pos;
(void)buf;
*len = 0;
return B_NOT_ALLOWED;
}
static status_t
device_write(void* dev, off_t pos, const void* buf, size_t* len)
{
(void)dev;
(void)pos;
(void)buf;
*len = 0;
return B_NOT_ALLOWED;
}
static status_t
device_close(void* dev)
{
(void)dev;
return B_NO_ERROR;
}
static status_t
device_free(void* dev)
{
DeviceInfo& di = *((DeviceInfo*)dev);
SharedInfo& si = *(di.sharedInfo);
pci_info& pciInfo = di.pciInfo;
TRACE("enter device_free()\n");
gLock.Acquire();
if (di.openCount <= 1) {
DisableVBI();
if (si.bInterruptAssigned) {
remove_io_interrupt_handler(pciInfo.u.h0.interrupt_line,
InterruptHandler, &di);
}
if (si.vertBlankSem >= 0)
delete_sem(si.vertBlankSem);
si.vertBlankSem = -1;
UnmapDevice(di);
delete_area(di.sharedArea);
di.sharedArea = -1;
di.sharedInfo = NULL;
}
if (di.openCount > 0)
di.openCount--;
gLock.Release();
TRACE("exit device_free() openCount: %" B_PRIu32 "\n", di.openCount);
return B_OK;
}
static status_t
device_ioctl(void* dev, uint32 msg, void* buffer, size_t bufferLength)
{
DeviceInfo& di = *((DeviceInfo*)dev);
TRACE("device_ioctl(); ioctl: %" B_PRIu32 ", buffer: %#08" B_PRIxADDR
", bufLen: %" B_PRIuSIZE "\n", msg, (addr_t)buffer, bufferLength);
switch (msg) {
case B_GET_ACCELERANT_SIGNATURE:
{
status_t status = user_strlcpy((char*)buffer, ATI_ACCELERANT_NAME,
bufferLength);
if (status < B_OK)
return status;
return B_OK;
}
case ATI_DEVICE_NAME:
{
status_t status = user_strlcpy((char*)buffer, di.name,
B_OS_NAME_LENGTH);
if (status < B_OK)
return status;
return B_OK;
}
case ATI_GET_SHARED_DATA:
if (bufferLength != sizeof(area_id))
return B_BAD_DATA;
return user_memcpy(buffer, &di.sharedArea, sizeof(area_id));
case ATI_GET_EDID:
{
if (bufferLength != sizeof(edid1_raw))
return B_BAD_DATA;
edid1_raw rawEdid;
status_t status = GetEdidFromBIOS(rawEdid);
if (status != B_OK)
return status;
return user_memcpy((edid1_raw*)buffer, &rawEdid, sizeof(rawEdid));
}
case ATI_SET_VESA_DISPLAY_MODE:
{
if (bufferLength != sizeof(uint16))
return B_BAD_DATA;
uint16 value;
status_t status = user_memcpy(&value, buffer, sizeof(uint16));
if (status < B_OK)
return status;
return SetVesaDisplayMode(value);
}
case ATI_RUN_INTERRUPTS:
{
if (bufferLength != sizeof(bool))
return B_BAD_DATA;
bool value;
status_t res = user_memcpy(&value, buffer, sizeof(bool));
if (res < B_OK)
return res;
if (value)
EnableVBI();
else
DisableVBI();
return B_OK;
}
}
return B_DEV_INVALID_IOCTL;
}