* Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
*/
#include "intel_extreme.h"
#include "AreaKeeper.h"
#include "driver.h"
#include "utility.h"
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <driver_settings.h>
#include <util/kernel_cpp.h>
#define TRACE_DEVICE
#ifdef TRACE_DEVICE
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
static void
init_overlay_registers(overlay_registers* registers)
{
memset(registers, 0, B_PAGE_SIZE);
registers->contrast_correction = 0x48;
registers->saturation_cos_correction = 0x9a;
}
static void
read_settings(bool &hardwareCursor)
{
hardwareCursor = false;
void* settings = load_driver_settings("intel_extreme");
if (settings != NULL) {
hardwareCursor = get_driver_boolean_parameter(settings,
"hardware_cursor", true, true);
unload_driver_settings(settings);
}
}
static int32
release_vblank_sem(intel_info &info)
{
int32 count;
if (get_sem_count(info.shared_info->vblank_sem, &count) == B_OK
&& count < 0) {
release_sem_etc(info.shared_info->vblank_sem, -count,
B_DO_NOT_RESCHEDULE);
return B_INVOKE_SCHEDULER;
}
return B_HANDLED_INTERRUPT;
}
static int32
intel_interrupt_handler(void* data)
{
intel_info &info = *(intel_info*)data;
uint16 identity = read16(info, find_reg(info, INTEL_INTERRUPT_IDENTITY));
if (identity == 0)
return B_UNHANDLED_INTERRUPT;
int32 handled = B_HANDLED_INTERRUPT;
bool hasPCH = info.device_type.HasPlatformControlHub();
uint16 mask = hasPCH ? PCH_INTERRUPT_VBLANK_PIPEA : INTERRUPT_VBLANK_PIPEA;
if ((identity & mask) != 0) {
handled = release_vblank_sem(info);
write32(info, INTEL_DISPLAY_A_PIPE_STATUS,
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
}
mask = hasPCH ? PCH_INTERRUPT_VBLANK_PIPEB : INTERRUPT_VBLANK_PIPEB;
if ((identity & mask) != 0) {
handled = release_vblank_sem(info);
write32(info, INTEL_DISPLAY_B_PIPE_STATUS,
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
}
write16(info, find_reg(info, INTEL_INTERRUPT_IDENTITY), identity);
return handled;
}
static void
init_interrupt_handler(intel_info &info)
{
info.shared_info->vblank_sem = create_sem(0, "intel extreme vblank");
if (info.shared_info->vblank_sem < B_OK)
return;
status_t status = B_OK;
thread_id thread = find_thread(NULL);
thread_info threadInfo;
if (get_thread_info(thread, &threadInfo) != B_OK
|| set_sem_owner(info.shared_info->vblank_sem, threadInfo.team)
!= B_OK) {
status = B_ERROR;
}
if (status == B_OK && info.pci->u.h0.interrupt_pin != 0x00
&& info.pci->u.h0.interrupt_line != 0xff) {
info.fake_interrupts = false;
status = install_io_interrupt_handler(info.pci->u.h0.interrupt_line,
&intel_interrupt_handler, (void*)&info, 0);
if (status == B_OK) {
write32(info, INTEL_DISPLAY_A_PIPE_STATUS,
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
write32(info, INTEL_DISPLAY_B_PIPE_STATUS,
DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
write16(info, find_reg(info, INTEL_INTERRUPT_IDENTITY), ~0);
bool hasPCH = info.device_type.HasPlatformControlHub();
uint16 enable = hasPCH
? (PCH_INTERRUPT_VBLANK_PIPEA | PCH_INTERRUPT_VBLANK_PIPEB)
: (INTERRUPT_VBLANK_PIPEA | INTERRUPT_VBLANK_PIPEB);
write16(info, find_reg(info, INTEL_INTERRUPT_ENABLED), enable);
write16(info, find_reg(info, INTEL_INTERRUPT_MASK), ~enable);
}
}
if (status < B_OK) {
info.fake_interrupts = true;
TRACE((DEVICE_NAME "Fake interrupt mode (no PCI interrupt line "
"assigned)"));
status = B_ERROR;
}
if (status < B_OK) {
delete_sem(info.shared_info->vblank_sem);
info.shared_info->vblank_sem = B_ERROR;
}
}
status_t
intel_free_memory(intel_info &info, addr_t base)
{
return gGART->free_memory(info.aperture, base);
}
status_t
intel_allocate_memory(intel_info &info, size_t size, size_t alignment,
uint32 flags, addr_t* _base, phys_addr_t* _physicalBase)
{
return gGART->allocate_memory(info.aperture, size, alignment,
flags, _base, _physicalBase);
}
status_t
intel_extreme_init(intel_info &info)
{
info.aperture = gGART->map_aperture(info.pci->bus, info.pci->device,
info.pci->function, 0, &info.aperture_base);
if (info.aperture < B_OK)
return info.aperture;
AreaKeeper sharedCreator;
info.shared_area = sharedCreator.Create("intel extreme shared info",
(void**)&info.shared_info, B_ANY_KERNEL_ADDRESS,
ROUND_TO_PAGE_SIZE(sizeof(intel_shared_info)) + 3 * B_PAGE_SIZE,
B_FULL_LOCK, 0);
if (info.shared_area < B_OK) {
gGART->unmap_aperture(info.aperture);
return info.shared_area;
}
memset((void*)info.shared_info, 0, sizeof(intel_shared_info));
int fbIndex = 0;
int mmioIndex = 1;
if (info.device_type.InFamily(INTEL_TYPE_9xx)) {
mmioIndex = 0;
fbIndex = 2;
}
bool hardwareCursor;
read_settings(hardwareCursor);
AreaKeeper mmioMapper;
info.registers_area = mmioMapper.Map("intel extreme mmio",
(void*)info.pci->u.h0.base_registers[mmioIndex],
info.pci->u.h0.base_register_sizes[mmioIndex],
B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
(void**)&info.registers);
if (mmioMapper.InitCheck() < B_OK) {
dprintf(DEVICE_NAME ": could not map memory I/O!\n");
gGART->unmap_aperture(info.aperture);
return info.registers_area;
}
uint32* blocks = info.shared_info->register_blocks;
blocks[REGISTER_BLOCK(REGS_FLAT)] = 0;
if (info.device_type.HasPlatformControlHub()) {
blocks[REGISTER_BLOCK(REGS_NORTH_SHARED)]
= PCH_NORTH_SHARED_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_NORTH_PIPE_AND_PORT)]
= PCH_NORTH_PIPE_AND_PORT_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_NORTH_PLANE_CONTROL)]
= PCH_NORTH_PLANE_CONTROL_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_SOUTH_SHARED)]
= PCH_SOUTH_SHARED_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_SOUTH_TRANSCODER_PORT)]
= PCH_SOUTH_TRANSCODER_AND_PORT_REGISTER_BASE;
} else {
blocks[REGISTER_BLOCK(REGS_NORTH_SHARED)]
= MCH_SHARED_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_NORTH_PIPE_AND_PORT)]
= MCH_PIPE_AND_PORT_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_NORTH_PLANE_CONTROL)]
= MCH_PLANE_CONTROL_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_SOUTH_SHARED)]
= ICH_SHARED_REGISTER_BASE;
blocks[REGISTER_BLOCK(REGS_SOUTH_TRANSCODER_PORT)]
= ICH_PORT_REGISTER_BASE;
}
set_pci_config(info.pci, PCI_command, 2, get_pci_config(info.pci,
PCI_command, 2) | PCI_command_io | PCI_command_memory
| PCI_command_master);
ring_buffer &primary = info.shared_info->primary_ring_buffer;
if (intel_allocate_memory(info, 16 * B_PAGE_SIZE, 0, 0,
(addr_t*)&primary.base) == B_OK) {
primary.register_base = INTEL_PRIMARY_RING_BUFFER;
primary.size = 16 * B_PAGE_SIZE;
primary.offset = (addr_t)primary.base - info.aperture_base;
}
if (info.pci->device_id == 0x2a02 || info.pci->device_id == 0x2a12) {
dprintf("i965GM/i965GME quirk\n");
write32(info, 0x6204, (1L << 29));
} else if (info.device_type.InGroup(INTEL_TYPE_SNB)) {
dprintf("SandyBridge clock gating\n");
write32(info, 0x42020, (1L << 28) | (1L << 7) | (1L << 5));
} else if (info.device_type.InGroup(INTEL_TYPE_ILK)) {
dprintf("IronLake clock gating\n");
write32(info, 0x42020, (1L << 7) | (1L << 5));
} else if (info.device_type.InGroup(INTEL_TYPE_G4x)) {
dprintf("G4x clock gating\n");
write32(info, 0x6204, 0);
write32(info, 0x6208, (1L << 9) | (1L << 7) | (1L << 6));
write32(info, 0x6210, 0);
uint32 gateValue = (1L << 28) | (1L << 3) | (1L << 2);
if ((info.device_type.type & INTEL_TYPE_MOBILE) == INTEL_TYPE_MOBILE) {
dprintf("G4x mobile clock gating\n");
gateValue |= 1L << 18;
}
write32(info, 0x6200, gateValue);
} else {
dprintf("i965 quirk\n");
write32(info, 0x6204, (1L << 29) | (1L << 23));
}
write32(info, 0x7408, 0x10);
sharedCreator.Detach();
mmioMapper.Detach();
aperture_info apertureInfo;
gGART->get_aperture_info(info.aperture, &apertureInfo);
info.shared_info->registers_area = info.registers_area;
info.shared_info->graphics_memory = (uint8*)info.aperture_base;
info.shared_info->physical_graphics_memory = apertureInfo.physical_base;
info.shared_info->graphics_memory_size = apertureInfo.size;
info.shared_info->frame_buffer = 0;
info.shared_info->dpms_mode = B_DPMS_ON;
if (info.device_type.InFamily(INTEL_TYPE_9xx)) {
info.shared_info->pll_info.reference_frequency = 96000;
info.shared_info->pll_info.max_frequency = 400000;
info.shared_info->pll_info.min_frequency = 20000;
} else {
info.shared_info->pll_info.reference_frequency = 48000;
info.shared_info->pll_info.max_frequency = 350000;
info.shared_info->pll_info.min_frequency = 25000;
}
info.shared_info->pll_info.divisor_register = INTEL_DISPLAY_A_PLL_DIVISOR_0;
info.shared_info->device_type = info.device_type;
#ifdef __HAIKU__
strlcpy(info.shared_info->device_identifier, info.device_identifier,
sizeof(info.shared_info->device_identifier));
#else
strcpy(info.shared_info->device_identifier, info.device_identifier);
#endif
if (intel_allocate_memory(info, B_PAGE_SIZE, 0,
intel_uses_physical_overlay(*info.shared_info)
? B_APERTURE_NEED_PHYSICAL : 0,
(addr_t*)&info.overlay_registers,
&info.shared_info->physical_overlay_registers) == B_OK) {
info.shared_info->overlay_offset = (addr_t)info.overlay_registers
- info.aperture_base;
}
init_overlay_registers(info.overlay_registers);
if (intel_allocate_memory(info, B_PAGE_SIZE, 0, B_APERTURE_NEED_PHYSICAL,
(addr_t*)info.shared_info->status_page,
&info.shared_info->physical_status_page) == B_OK) {
}
if (hardwareCursor) {
intel_allocate_memory(info, B_PAGE_SIZE, 0, B_APERTURE_NEED_PHYSICAL,
(addr_t*)&info.shared_info->cursor_memory,
&info.shared_info->physical_cursor_memory);
}
init_interrupt_handler(info);
TRACE((DEVICE_NAME "_init() completed successfully!\n"));
return B_OK;
}
void
intel_extreme_uninit(intel_info &info)
{
TRACE((DEVICE_NAME": intel_extreme_uninit()\n"));
if (!info.fake_interrupts && info.shared_info->vblank_sem > 0) {
write16(info, find_reg(info, INTEL_INTERRUPT_ENABLED), 0);
write16(info, find_reg(info, INTEL_INTERRUPT_MASK), ~0);
remove_io_interrupt_handler(info.pci->u.h0.interrupt_line,
intel_interrupt_handler, &info);
}
gGART->unmap_aperture(info.aperture);
delete_area(info.registers_area);
delete_area(info.shared_area);
}