blob: a7242ea0b39bd13f5a2cd0a54166d4202136ccdc [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/display.h>
#include <ddk/protocol/pci.h>
#include <hw/pci.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#define INTEL_I915_VID (0x8086)
#define INTEL_I915_BROADWELL_DID (0x1616)
#define INTEL_I915_REG_WINDOW_SIZE (0x1000000u)
#define INTEL_I915_FB_WINDOW_SIZE (0x10000000u)
#define BACKLIGHT_CTRL_OFFSET (0xc8250)
#define BACKLIGHT_CTRL_BIT ((uint32_t)(1u << 31))
typedef struct intel_i915_device {
void* regs;
uint64_t regs_size;
zx_handle_t regs_handle;
void* framebuffer;
uint64_t framebuffer_size;
zx_handle_t framebuffer_handle;
zx_display_info_t info;
uint32_t flags;
} intel_i915_device_t;
#define FLAGS_BACKLIGHT 1
static void intel_i915_enable_backlight(intel_i915_device_t* dev, bool enable) {
if (dev->flags & FLAGS_BACKLIGHT) {
void* backlight_ctrl = (uint8_t*)dev->regs + BACKLIGHT_CTRL_OFFSET;
uint32_t tmp = pcie_read32(backlight_ctrl);
if (enable)
tmp |= BACKLIGHT_CTRL_BIT;
else
tmp &= ~BACKLIGHT_CTRL_BIT;
pcie_write32(backlight_ctrl, tmp);
}
}
// implement display protocol
static zx_status_t intel_i915_set_mode(void* ctx, zx_display_info_t* info) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t intel_i915_get_mode(void* ctx, zx_display_info_t* info) {
assert(info);
intel_i915_device_t* device = ctx;
memcpy(info, &device->info, sizeof(zx_display_info_t));
return ZX_OK;
}
static zx_status_t intel_i915_get_framebuffer(void* ctx, void** framebuffer) {
assert(framebuffer);
intel_i915_device_t* device = ctx;
(*framebuffer) = device->framebuffer;
return ZX_OK;
}
static display_protocol_ops_t intel_i915_display_proto = {
.set_mode = intel_i915_set_mode,
.get_mode = intel_i915_get_mode,
.get_framebuffer = intel_i915_get_framebuffer,
};
// implement device protocol
static zx_status_t intel_i915_open(void* ctx, zx_device_t** out, uint32_t flags) {
intel_i915_device_t* device = ctx;
intel_i915_enable_backlight(device, true);
return ZX_OK;
}
static zx_status_t intel_i915_close(void* ctx, uint32_t flags) {
return ZX_OK;
}
static void intel_i915_release(void* ctx) {
intel_i915_device_t* device = ctx;
intel_i915_enable_backlight(device, false);
if (device->regs) {
zx_handle_close(device->regs_handle);
device->regs_handle = -1;
}
if (device->framebuffer) {
zx_handle_close(device->framebuffer_handle);
device->framebuffer_handle = -1;
}
free(device);
}
static zx_protocol_device_t intel_i915_device_proto = {
.version = DEVICE_OPS_VERSION,
.open = intel_i915_open,
.close = intel_i915_close,
.release = intel_i915_release,
};
// implement driver object:
static zx_status_t intel_i915_bind(void* ctx, zx_device_t* dev) {
pci_protocol_t pci;
if (device_get_protocol(dev, ZX_PROTOCOL_PCI, &pci))
return ZX_ERR_NOT_SUPPORTED;
// map resources and initialize the device
intel_i915_device_t* device = calloc(1, sizeof(intel_i915_device_t));
if (!device)
return ZX_ERR_NO_MEMORY;
const pci_config_t* pci_config;
size_t config_size;
zx_handle_t cfg_handle = ZX_HANDLE_INVALID;
zx_status_t status = pci_map_resource(&pci, PCI_RESOURCE_CONFIG,
ZX_CACHE_POLICY_UNCACHED_DEVICE,
(void**)&pci_config,
&config_size, &cfg_handle);
if (status == ZX_OK) {
if (pci_config->device_id == INTEL_I915_BROADWELL_DID) {
// TODO: this should be based on the specific target
device->flags |= FLAGS_BACKLIGHT;
}
zx_handle_close(cfg_handle);
}
// map register window
status = pci_map_resource(&pci, PCI_RESOURCE_BAR_0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
&device->regs, &device->regs_size, &device->regs_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "i915: failed to map bar 0: %d\n", status);
goto fail;
}
// map framebuffer window
status = pci_map_resource(&pci, PCI_RESOURCE_BAR_2, ZX_CACHE_POLICY_WRITE_COMBINING,
&device->framebuffer,
&device->framebuffer_size,
&device->framebuffer_handle);
if (status != ZX_OK) {
zxlogf(ERROR, "i915: failed to map bar 2: %d\n", status);
goto fail;
}
zx_display_info_t* di = &device->info;
uint32_t format, width, height, stride;
status = zx_bootloader_fb_get_info(&format, &width, &height, &stride);
if (status == ZX_OK) {
di->format = format;
di->width = width;
di->height = height;
di->stride = stride;
} else {
di->format = ZX_PIXEL_FORMAT_RGB_565;
di->width = 2560 / 2;
di->height = 1700 / 2;
di->stride = 2560 / 2;
}
di->flags = ZX_DISPLAY_FLAG_HW_FRAMEBUFFER;
// TODO remove when the gfxconsole moves to user space
intel_i915_enable_backlight(device, true);
zx_set_framebuffer(get_root_resource(), device->framebuffer, device->framebuffer_size,
format, width, height, stride);
// create and add the display (char) device
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "intel_i915_disp",
.ctx = device,
.ops = &intel_i915_device_proto,
.proto_id = ZX_PROTOCOL_DISPLAY,
.proto_ops = &intel_i915_display_proto,
};
status = device_add(dev, &args, NULL);
if (status != ZX_OK) {
goto fail;
}
zxlogf(SPEW, "i915: reg=%p regsize=0x%" PRIx64 " fb=%p fbsize=0x%" PRIx64 "\n",
device->regs, device->regs_size, device->framebuffer, device->framebuffer_size);
return ZX_OK;
fail:
free(device);
return status;
}
static zx_driver_ops_t intel_i915_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = intel_i915_bind,
};
// clang-format off
ZIRCON_DRIVER_BEGIN(intel_i915, intel_i915_driver_ops, "zircon", "*0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
BI_ABORT_IF(NE, BIND_PCI_VID, INTEL_I915_VID),
BI_MATCH_IF(EQ, BIND_PCI_CLASS, 0x3), // Display class
ZIRCON_DRIVER_END(intel_i915)