diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau')
137 files changed, 68463 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/Kconfig b/drivers/gpu/drm/nouveau/Kconfig new file mode 100644 index 00000000..97a81260 --- /dev/null +++ b/drivers/gpu/drm/nouveau/Kconfig @@ -0,0 +1,57 @@ +config DRM_NOUVEAU + tristate "Nouveau (nVidia) cards" + depends on DRM && PCI + select FW_LOADER + select DRM_KMS_HELPER + select DRM_TTM + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select FB + select FRAMEBUFFER_CONSOLE if !EXPERT + select FB_BACKLIGHT if DRM_NOUVEAU_BACKLIGHT + select ACPI_VIDEO if ACPI && X86 && BACKLIGHT_CLASS_DEVICE && VIDEO_OUTPUT_CONTROL && INPUT + select ACPI_WMI if ACPI + select MXM_WMI if ACPI + select POWER_SUPPLY + help + Choose this option for open-source nVidia support. + +config DRM_NOUVEAU_BACKLIGHT + bool "Support for backlight control" + depends on DRM_NOUVEAU + default y + help + Say Y here if you want to control the backlight of your display + (e.g. a laptop panel). + +config DRM_NOUVEAU_DEBUG + bool "Build in Nouveau's debugfs support" + depends on DRM_NOUVEAU && DEBUG_FS + default y + help + Say Y here if you want Nouveau to output debugging information + via debugfs. + +menu "I2C encoder or helper chips" + depends on DRM && DRM_KMS_HELPER && I2C + +config DRM_I2C_CH7006 + tristate "Chrontel ch7006 TV encoder" + default m if DRM_NOUVEAU + help + Support for Chrontel ch7006 and similar TV encoders, found + on some nVidia video cards. + + This driver is currently only useful if you're also using + the nouveau driver. + +config DRM_I2C_SIL164 + tristate "Silicon Image sil164 TMDS transmitter" + default m if DRM_NOUVEAU + help + Support for sil164 and similar single-link (or dual-link + when used in pairs) TMDS transmitters, used in some nVidia + video cards. + +endmenu diff --git a/drivers/gpu/drm/nouveau/Makefile b/drivers/gpu/drm/nouveau/Makefile new file mode 100644 index 00000000..1a2ad7eb --- /dev/null +++ b/drivers/gpu/drm/nouveau/Makefile @@ -0,0 +1,47 @@ +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. + +ccflags-y := -Iinclude/drm +nouveau-y := nouveau_drv.o nouveau_state.o nouveau_channel.o nouveau_mem.o \ + nouveau_object.o nouveau_irq.o nouveau_notifier.o \ + nouveau_sgdma.o nouveau_dma.o nouveau_util.o \ + nouveau_bo.o nouveau_fence.o nouveau_gem.o nouveau_ttm.o \ + nouveau_hw.o nouveau_calc.o nouveau_bios.o nouveau_i2c.o \ + nouveau_display.o nouveau_connector.o nouveau_fbcon.o \ + nouveau_hdmi.o nouveau_dp.o nouveau_ramht.o \ + nouveau_pm.o nouveau_volt.o nouveau_perf.o nouveau_temp.o \ + nouveau_mm.o nouveau_vm.o nouveau_mxm.o nouveau_gpio.o \ + nv04_timer.o \ + nv04_mc.o nv40_mc.o nv50_mc.o \ + nv04_fb.o nv10_fb.o nv20_fb.o nv30_fb.o nv40_fb.o \ + nv50_fb.o nvc0_fb.o \ + nv04_fifo.o nv10_fifo.o nv40_fifo.o nv50_fifo.o nvc0_fifo.o \ + nv04_graph.o nv10_graph.o nv20_graph.o \ + nv40_graph.o nv50_graph.o nvc0_graph.o \ + nv40_grctx.o nv50_grctx.o nvc0_grctx.o \ + nv84_crypt.o nv98_crypt.o \ + nva3_copy.o nvc0_copy.o \ + nv31_mpeg.o nv50_mpeg.o \ + nv84_bsp.o \ + nv84_vp.o \ + nv98_ppp.o \ + nv04_instmem.o nv50_instmem.o nvc0_instmem.o \ + nv04_dac.o nv04_dfp.o nv04_tv.o nv17_tv.o nv17_tv_modes.o \ + nv04_crtc.o nv04_display.o nv04_cursor.o \ + nv50_evo.o nv50_crtc.o nv50_dac.o nv50_sor.o \ + nv50_cursor.o nv50_display.o \ + nvd0_display.o \ + nv04_fbcon.o nv50_fbcon.o nvc0_fbcon.o \ + nv10_gpio.o nv50_gpio.o \ + nv50_calc.o \ + nv04_pm.o nv40_pm.o nv50_pm.o nva3_pm.o nvc0_pm.o \ + nv50_vram.o nvc0_vram.o \ + nv50_vm.o nvc0_vm.o + +nouveau-$(CONFIG_DRM_NOUVEAU_DEBUG) += nouveau_debugfs.o +nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o +nouveau-$(CONFIG_DRM_NOUVEAU_BACKLIGHT) += nouveau_backlight.o +nouveau-$(CONFIG_ACPI) += nouveau_acpi.o + +obj-$(CONFIG_DRM_NOUVEAU)+= nouveau.o diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c new file mode 100644 index 00000000..284bd25d --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c @@ -0,0 +1,430 @@ +#include <linux/pci.h> +#include <linux/acpi.h> +#include <linux/slab.h> +#include <acpi/acpi_drivers.h> +#include <acpi/acpi_bus.h> +#include <acpi/video.h> +#include <acpi/acpi.h> +#include <linux/mxm-wmi.h> + +#include "drmP.h" +#include "drm.h" +#include "drm_sarea.h" +#include "drm_crtc_helper.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nv50_display.h" +#include "nouveau_connector.h" + +#include <linux/vga_switcheroo.h> + +#define NOUVEAU_DSM_LED 0x02 +#define NOUVEAU_DSM_LED_STATE 0x00 +#define NOUVEAU_DSM_LED_OFF 0x10 +#define NOUVEAU_DSM_LED_STAMINA 0x11 +#define NOUVEAU_DSM_LED_SPEED 0x12 + +#define NOUVEAU_DSM_POWER 0x03 +#define NOUVEAU_DSM_POWER_STATE 0x00 +#define NOUVEAU_DSM_POWER_SPEED 0x01 +#define NOUVEAU_DSM_POWER_STAMINA 0x02 + +#define NOUVEAU_DSM_OPTIMUS_FN 0x1A +#define NOUVEAU_DSM_OPTIMUS_ARGS 0x03000001 + +static struct nouveau_dsm_priv { + bool dsm_detected; + bool optimus_detected; + acpi_handle dhandle; + acpi_handle rom_handle; +} nouveau_dsm_priv; + +#define NOUVEAU_DSM_HAS_MUX 0x1 +#define NOUVEAU_DSM_HAS_OPT 0x2 + +static const char nouveau_dsm_muid[] = { + 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, + 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4, +}; + +static const char nouveau_op_dsm_muid[] = { + 0xF8, 0xD8, 0x86, 0xA4, 0xDA, 0x0B, 0x1B, 0x47, + 0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0, +}; + +static int nouveau_optimus_dsm(acpi_handle handle, int func, int arg, uint32_t *result) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_object_list input; + union acpi_object params[4]; + union acpi_object *obj; + int i, err; + char args_buff[4]; + + input.count = 4; + input.pointer = params; + params[0].type = ACPI_TYPE_BUFFER; + params[0].buffer.length = sizeof(nouveau_op_dsm_muid); + params[0].buffer.pointer = (char *)nouveau_op_dsm_muid; + params[1].type = ACPI_TYPE_INTEGER; + params[1].integer.value = 0x00000100; + params[2].type = ACPI_TYPE_INTEGER; + params[2].integer.value = func; + params[3].type = ACPI_TYPE_BUFFER; + params[3].buffer.length = 4; + /* ACPI is little endian, AABBCCDD becomes {DD,CC,BB,AA} */ + for (i = 0; i < 4; i++) + args_buff[i] = (arg >> i * 8) & 0xFF; + params[3].buffer.pointer = args_buff; + + err = acpi_evaluate_object(handle, "_DSM", &input, &output); + if (err) { + printk(KERN_INFO "failed to evaluate _DSM: %d\n", err); + return err; + } + + obj = (union acpi_object *)output.pointer; + + if (obj->type == ACPI_TYPE_INTEGER) + if (obj->integer.value == 0x80000002) { + return -ENODEV; + } + + if (obj->type == ACPI_TYPE_BUFFER) { + if (obj->buffer.length == 4 && result) { + *result = 0; + *result |= obj->buffer.pointer[0]; + *result |= (obj->buffer.pointer[1] << 8); + *result |= (obj->buffer.pointer[2] << 16); + *result |= (obj->buffer.pointer[3] << 24); + } + } + + kfree(output.pointer); + return 0; +} + +static int nouveau_dsm(acpi_handle handle, int func, int arg, uint32_t *result) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_object_list input; + union acpi_object params[4]; + union acpi_object *obj; + int err; + + input.count = 4; + input.pointer = params; + params[0].type = ACPI_TYPE_BUFFER; + params[0].buffer.length = sizeof(nouveau_dsm_muid); + params[0].buffer.pointer = (char *)nouveau_dsm_muid; + params[1].type = ACPI_TYPE_INTEGER; + params[1].integer.value = 0x00000102; + params[2].type = ACPI_TYPE_INTEGER; + params[2].integer.value = func; + params[3].type = ACPI_TYPE_INTEGER; + params[3].integer.value = arg; + + err = acpi_evaluate_object(handle, "_DSM", &input, &output); + if (err) { + printk(KERN_INFO "failed to evaluate _DSM: %d\n", err); + return err; + } + + obj = (union acpi_object *)output.pointer; + + if (obj->type == ACPI_TYPE_INTEGER) + if (obj->integer.value == 0x80000002) + return -ENODEV; + + if (obj->type == ACPI_TYPE_BUFFER) { + if (obj->buffer.length == 4 && result) { + *result = 0; + *result |= obj->buffer.pointer[0]; + *result |= (obj->buffer.pointer[1] << 8); + *result |= (obj->buffer.pointer[2] << 16); + *result |= (obj->buffer.pointer[3] << 24); + } + } + + kfree(output.pointer); + return 0; +} + +/* Returns 1 if a DSM function is usable and 0 otherwise */ +static int nouveau_test_dsm(acpi_handle test_handle, + int (*dsm_func)(acpi_handle, int, int, uint32_t *), + int sfnc) +{ + u32 result = 0; + + /* Function 0 returns a Buffer containing available functions. The args + * parameter is ignored for function 0, so just put 0 in it */ + if (dsm_func(test_handle, 0, 0, &result)) + return 0; + + /* ACPI Spec v4 9.14.1: if bit 0 is zero, no function is supported. If + * the n-th bit is enabled, function n is supported */ + return result & 1 && result & (1 << sfnc); +} + +static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id) +{ + mxm_wmi_call_mxmx(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0); + mxm_wmi_call_mxds(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0); + return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id, NULL); +} + +static int nouveau_dsm_set_discrete_state(acpi_handle handle, enum vga_switcheroo_state state) +{ + int arg; + if (state == VGA_SWITCHEROO_ON) + arg = NOUVEAU_DSM_POWER_SPEED; + else + arg = NOUVEAU_DSM_POWER_STAMINA; + nouveau_dsm(handle, NOUVEAU_DSM_POWER, arg, NULL); + return 0; +} + +static int nouveau_dsm_switchto(enum vga_switcheroo_client_id id) +{ + /* perhaps the _DSM functions are mutually exclusive, but prepare for + * the future */ + if (!nouveau_dsm_priv.dsm_detected && nouveau_dsm_priv.optimus_detected) + return 0; + if (id == VGA_SWITCHEROO_IGD) + return nouveau_dsm_switch_mux(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_LED_STAMINA); + else + return nouveau_dsm_switch_mux(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_LED_SPEED); +} + +static int nouveau_dsm_power_state(enum vga_switcheroo_client_id id, + enum vga_switcheroo_state state) +{ + if (id == VGA_SWITCHEROO_IGD) + return 0; + + /* Optimus laptops have the card already disabled in + * nouveau_switcheroo_set_state */ + if (!nouveau_dsm_priv.dsm_detected && nouveau_dsm_priv.optimus_detected) + return 0; + + return nouveau_dsm_set_discrete_state(nouveau_dsm_priv.dhandle, state); +} + +static int nouveau_dsm_init(void) +{ + return 0; +} + +static int nouveau_dsm_get_client_id(struct pci_dev *pdev) +{ + /* easy option one - intel vendor ID means Integrated */ + if (pdev->vendor == PCI_VENDOR_ID_INTEL) + return VGA_SWITCHEROO_IGD; + + /* is this device on Bus 0? - this may need improving */ + if (pdev->bus->number == 0) + return VGA_SWITCHEROO_IGD; + + return VGA_SWITCHEROO_DIS; +} + +static struct vga_switcheroo_handler nouveau_dsm_handler = { + .switchto = nouveau_dsm_switchto, + .power_state = nouveau_dsm_power_state, + .init = nouveau_dsm_init, + .get_client_id = nouveau_dsm_get_client_id, +}; + +static int nouveau_dsm_pci_probe(struct pci_dev *pdev) +{ + acpi_handle dhandle, nvidia_handle; + acpi_status status; + int retval = 0; + + dhandle = DEVICE_ACPI_HANDLE(&pdev->dev); + if (!dhandle) + return false; + + status = acpi_get_handle(dhandle, "_DSM", &nvidia_handle); + if (ACPI_FAILURE(status)) { + return false; + } + + if (nouveau_test_dsm(dhandle, nouveau_dsm, NOUVEAU_DSM_POWER)) + retval |= NOUVEAU_DSM_HAS_MUX; + + if (nouveau_test_dsm(dhandle, nouveau_optimus_dsm, + NOUVEAU_DSM_OPTIMUS_FN)) + retval |= NOUVEAU_DSM_HAS_OPT; + + if (retval) + nouveau_dsm_priv.dhandle = dhandle; + + return retval; +} + +static bool nouveau_dsm_detect(void) +{ + char acpi_method_name[255] = { 0 }; + struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; + struct pci_dev *pdev = NULL; + int has_dsm = 0; + int has_optimus = 0; + int vga_count = 0; + bool guid_valid; + int retval; + bool ret = false; + + /* lookup the MXM GUID */ + guid_valid = mxm_wmi_supported(); + + if (guid_valid) + printk("MXM: GUID detected in BIOS\n"); + + /* now do DSM detection */ + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { + vga_count++; + + retval = nouveau_dsm_pci_probe(pdev); + if (retval & NOUVEAU_DSM_HAS_MUX) + has_dsm |= 1; + if (retval & NOUVEAU_DSM_HAS_OPT) + has_optimus = 1; + } + + if (vga_count == 2 && has_dsm && guid_valid) { + acpi_get_name(nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME, + &buffer); + printk(KERN_INFO "VGA switcheroo: detected DSM switching method %s handle\n", + acpi_method_name); + nouveau_dsm_priv.dsm_detected = true; + ret = true; + } + + if (has_optimus == 1) { + acpi_get_name(nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME, + &buffer); + printk(KERN_INFO "VGA switcheroo: detected Optimus DSM method %s handle\n", + acpi_method_name); + nouveau_dsm_priv.optimus_detected = true; + ret = true; + } + + return ret; +} + +void nouveau_register_dsm_handler(void) +{ + bool r; + + r = nouveau_dsm_detect(); + if (!r) + return; + + vga_switcheroo_register_handler(&nouveau_dsm_handler); +} + +/* Must be called for Optimus models before the card can be turned off */ +void nouveau_switcheroo_optimus_dsm(void) +{ + u32 result = 0; + if (!nouveau_dsm_priv.optimus_detected) + return; + + nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FN, + NOUVEAU_DSM_OPTIMUS_ARGS, &result); +} + +void nouveau_unregister_dsm_handler(void) +{ + vga_switcheroo_unregister_handler(); +} + +/* retrieve the ROM in 4k blocks */ +static int nouveau_rom_call(acpi_handle rom_handle, uint8_t *bios, + int offset, int len) +{ + acpi_status status; + union acpi_object rom_arg_elements[2], *obj; + struct acpi_object_list rom_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL}; + + rom_arg.count = 2; + rom_arg.pointer = &rom_arg_elements[0]; + + rom_arg_elements[0].type = ACPI_TYPE_INTEGER; + rom_arg_elements[0].integer.value = offset; + + rom_arg_elements[1].type = ACPI_TYPE_INTEGER; + rom_arg_elements[1].integer.value = len; + + status = acpi_evaluate_object(rom_handle, NULL, &rom_arg, &buffer); + if (ACPI_FAILURE(status)) { + printk(KERN_INFO "failed to evaluate ROM got %s\n", acpi_format_exception(status)); + return -ENODEV; + } + obj = (union acpi_object *)buffer.pointer; + memcpy(bios+offset, obj->buffer.pointer, len); + kfree(buffer.pointer); + return len; +} + +bool nouveau_acpi_rom_supported(struct pci_dev *pdev) +{ + acpi_status status; + acpi_handle dhandle, rom_handle; + + if (!nouveau_dsm_priv.dsm_detected && !nouveau_dsm_priv.optimus_detected) + return false; + + dhandle = DEVICE_ACPI_HANDLE(&pdev->dev); + if (!dhandle) + return false; + + status = acpi_get_handle(dhandle, "_ROM", &rom_handle); + if (ACPI_FAILURE(status)) + return false; + + nouveau_dsm_priv.rom_handle = rom_handle; + return true; +} + +int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len) +{ + return nouveau_rom_call(nouveau_dsm_priv.rom_handle, bios, offset, len); +} + +int +nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct acpi_device *acpidev; + acpi_handle handle; + int type, ret; + void *edid; + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_LVDS: + case DRM_MODE_CONNECTOR_eDP: + type = ACPI_VIDEO_DISPLAY_LCD; + break; + default: + return -EINVAL; + } + + handle = DEVICE_ACPI_HANDLE(&dev->pdev->dev); + if (!handle) + return -ENODEV; + + ret = acpi_bus_get_device(handle, &acpidev); + if (ret) + return -ENODEV; + + ret = acpi_video_get_edid(acpidev, type, -1, &edid); + if (ret < 0) + return ret; + + nv_connector->edid = kmemdup(edid, EDID_LENGTH, GFP_KERNEL); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_backlight.c b/drivers/gpu/drm/nouveau/nouveau_backlight.c new file mode 100644 index 00000000..fa22b28e --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_backlight.c @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2009 Red Hat <mjg@redhat.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* + * Authors: + * Matthew Garrett <mjg@redhat.com> + * + * Register locations derived from NVClock by Roderick Colenbrander + */ + +#include <linux/backlight.h> +#include <linux/acpi.h> + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_reg.h" +#include "nouveau_encoder.h" + +static int +nv40_get_intensity(struct backlight_device *bd) +{ + struct drm_device *dev = bl_get_data(bd); + int val = (nv_rd32(dev, NV40_PMC_BACKLIGHT) & NV40_PMC_BACKLIGHT_MASK) + >> 16; + + return val; +} + +static int +nv40_set_intensity(struct backlight_device *bd) +{ + struct drm_device *dev = bl_get_data(bd); + int val = bd->props.brightness; + int reg = nv_rd32(dev, NV40_PMC_BACKLIGHT); + + nv_wr32(dev, NV40_PMC_BACKLIGHT, + (val << 16) | (reg & ~NV40_PMC_BACKLIGHT_MASK)); + + return 0; +} + +static const struct backlight_ops nv40_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .get_brightness = nv40_get_intensity, + .update_status = nv40_set_intensity, +}; + +static int +nv40_backlight_init(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct backlight_properties props; + struct backlight_device *bd; + + if (!(nv_rd32(dev, NV40_PMC_BACKLIGHT) & NV40_PMC_BACKLIGHT_MASK)) + return 0; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 31; + bd = backlight_device_register("nv_backlight", &connector->kdev, dev, + &nv40_bl_ops, &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + dev_priv->backlight = bd; + bd->props.brightness = nv40_get_intensity(bd); + backlight_update_status(bd); + + return 0; +} + +static int +nv50_get_intensity(struct backlight_device *bd) +{ + struct nouveau_encoder *nv_encoder = bl_get_data(bd); + struct drm_device *dev = nv_encoder->base.base.dev; + int or = nv_encoder->or; + u32 div = 1025; + u32 val; + + val = nv_rd32(dev, NV50_PDISP_SOR_PWM_CTL(or)); + val &= NV50_PDISP_SOR_PWM_CTL_VAL; + return ((val * 100) + (div / 2)) / div; +} + +static int +nv50_set_intensity(struct backlight_device *bd) +{ + struct nouveau_encoder *nv_encoder = bl_get_data(bd); + struct drm_device *dev = nv_encoder->base.base.dev; + int or = nv_encoder->or; + u32 div = 1025; + u32 val = (bd->props.brightness * div) / 100; + + nv_wr32(dev, NV50_PDISP_SOR_PWM_CTL(or), + NV50_PDISP_SOR_PWM_CTL_NEW | val); + return 0; +} + +static const struct backlight_ops nv50_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .get_brightness = nv50_get_intensity, + .update_status = nv50_set_intensity, +}; + +static int +nva3_get_intensity(struct backlight_device *bd) +{ + struct nouveau_encoder *nv_encoder = bl_get_data(bd); + struct drm_device *dev = nv_encoder->base.base.dev; + int or = nv_encoder->or; + u32 div, val; + + div = nv_rd32(dev, NV50_PDISP_SOR_PWM_DIV(or)); + val = nv_rd32(dev, NV50_PDISP_SOR_PWM_CTL(or)); + val &= NVA3_PDISP_SOR_PWM_CTL_VAL; + if (div && div >= val) + return ((val * 100) + (div / 2)) / div; + + return 100; +} + +static int +nva3_set_intensity(struct backlight_device *bd) +{ + struct nouveau_encoder *nv_encoder = bl_get_data(bd); + struct drm_device *dev = nv_encoder->base.base.dev; + int or = nv_encoder->or; + u32 div, val; + + div = nv_rd32(dev, NV50_PDISP_SOR_PWM_DIV(or)); + val = (bd->props.brightness * div) / 100; + if (div) { + nv_wr32(dev, NV50_PDISP_SOR_PWM_CTL(or), val | + NV50_PDISP_SOR_PWM_CTL_NEW | + NVA3_PDISP_SOR_PWM_CTL_UNK); + return 0; + } + + return -EINVAL; +} + +static const struct backlight_ops nva3_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .get_brightness = nva3_get_intensity, + .update_status = nva3_set_intensity, +}; + +static int +nv50_backlight_init(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_encoder *nv_encoder; + struct backlight_properties props; + struct backlight_device *bd; + const struct backlight_ops *ops; + + nv_encoder = find_encoder(connector, OUTPUT_LVDS); + if (!nv_encoder) { + nv_encoder = find_encoder(connector, OUTPUT_DP); + if (!nv_encoder) + return -ENODEV; + } + + if (!nv_rd32(dev, NV50_PDISP_SOR_PWM_CTL(nv_encoder->or))) + return 0; + + if (dev_priv->chipset <= 0xa0 || + dev_priv->chipset == 0xaa || + dev_priv->chipset == 0xac) + ops = &nv50_bl_ops; + else + ops = &nva3_bl_ops; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 100; + bd = backlight_device_register("nv_backlight", &connector->kdev, + nv_encoder, ops, &props); + if (IS_ERR(bd)) + return PTR_ERR(bd); + + dev_priv->backlight = bd; + bd->props.brightness = bd->ops->get_brightness(bd); + backlight_update_status(bd); + return 0; +} + +int +nouveau_backlight_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_connector *connector; + +#ifdef CONFIG_ACPI + if (acpi_video_backlight_support()) { + NV_INFO(dev, "ACPI backlight interface available, " + "not registering our own\n"); + return 0; + } +#endif + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->connector_type != DRM_MODE_CONNECTOR_LVDS && + connector->connector_type != DRM_MODE_CONNECTOR_eDP) + continue; + + switch (dev_priv->card_type) { + case NV_40: + return nv40_backlight_init(connector); + case NV_50: + return nv50_backlight_init(connector); + default: + break; + } + } + + + return 0; +} + +void +nouveau_backlight_exit(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->backlight) { + backlight_device_unregister(dev_priv->backlight); + dev_priv->backlight = NULL; + } +} diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c b/drivers/gpu/drm/nouveau/nouveau_bios.c new file mode 100644 index 00000000..0be4a815 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_bios.c @@ -0,0 +1,6546 @@ +/* + * Copyright 2005-2006 Erik Waling + * Copyright 2006 Stephane Marchesin + * Copyright 2007-2009 Stuart Bennett + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "drmP.h" +#define NV_DEBUG_NOTRACE +#include "nouveau_drv.h" +#include "nouveau_hw.h" +#include "nouveau_encoder.h" +#include "nouveau_gpio.h" + +#include <linux/io-mapping.h> + +/* these defines are made up */ +#define NV_CIO_CRE_44_HEADA 0x0 +#define NV_CIO_CRE_44_HEADB 0x3 +#define FEATURE_MOBILE 0x10 /* also FEATURE_QUADRO for BMP */ + +#define EDID1_LEN 128 + +#define BIOSLOG(sip, fmt, arg...) NV_DEBUG(sip->dev, fmt, ##arg) +#define LOG_OLD_VALUE(x) + +struct init_exec { + bool execute; + bool repeat; +}; + +static bool nv_cksum(const uint8_t *data, unsigned int length) +{ + /* + * There's a few checksums in the BIOS, so here's a generic checking + * function. + */ + int i; + uint8_t sum = 0; + + for (i = 0; i < length; i++) + sum += data[i]; + + if (sum) + return true; + + return false; +} + +static int +score_vbios(struct nvbios *bios, const bool writeable) +{ + if (!bios->data || bios->data[0] != 0x55 || bios->data[1] != 0xAA) { + NV_TRACEWARN(bios->dev, "... BIOS signature not found\n"); + return 0; + } + + if (nv_cksum(bios->data, bios->data[2] * 512)) { + NV_TRACEWARN(bios->dev, "... BIOS checksum invalid\n"); + /* if a ro image is somewhat bad, it's probably all rubbish */ + return writeable ? 2 : 1; + } + + NV_TRACE(bios->dev, "... appears to be valid\n"); + return 3; +} + +static void +bios_shadow_prom(struct nvbios *bios) +{ + struct drm_device *dev = bios->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 pcireg, access; + u16 pcir; + int i; + + /* enable access to rom */ + if (dev_priv->card_type >= NV_50) + pcireg = 0x088050; + else + pcireg = NV_PBUS_PCI_NV_20; + access = nv_mask(dev, pcireg, 0x00000001, 0x00000000); + + /* bail if no rom signature, with a workaround for a PROM reading + * issue on some chipsets. the first read after a period of + * inactivity returns the wrong result, so retry the first header + * byte a few times before giving up as a workaround + */ + i = 16; + do { + if (nv_rd08(dev, NV_PROM_OFFSET + 0) == 0x55) + break; + } while (i--); + + if (!i || nv_rd08(dev, NV_PROM_OFFSET + 1) != 0xaa) + goto out; + + /* additional check (see note below) - read PCI record header */ + pcir = nv_rd08(dev, NV_PROM_OFFSET + 0x18) | + nv_rd08(dev, NV_PROM_OFFSET + 0x19) << 8; + if (nv_rd08(dev, NV_PROM_OFFSET + pcir + 0) != 'P' || + nv_rd08(dev, NV_PROM_OFFSET + pcir + 1) != 'C' || + nv_rd08(dev, NV_PROM_OFFSET + pcir + 2) != 'I' || + nv_rd08(dev, NV_PROM_OFFSET + pcir + 3) != 'R') + goto out; + + /* read entire bios image to system memory */ + bios->length = nv_rd08(dev, NV_PROM_OFFSET + 2) * 512; + bios->data = kmalloc(bios->length, GFP_KERNEL); + if (bios->data) { + for (i = 0; i < bios->length; i++) + bios->data[i] = nv_rd08(dev, NV_PROM_OFFSET + i); + } + +out: + /* disable access to rom */ + nv_wr32(dev, pcireg, access); +} + +static void +bios_shadow_pramin(struct nvbios *bios) +{ + struct drm_device *dev = bios->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 bar0 = 0; + int i; + + if (dev_priv->card_type >= NV_50) { + u64 addr = (u64)(nv_rd32(dev, 0x619f04) & 0xffffff00) << 8; + if (!addr) { + addr = (u64)nv_rd32(dev, 0x001700) << 16; + addr += 0xf0000; + } + + bar0 = nv_mask(dev, 0x001700, 0xffffffff, addr >> 16); + } + + /* bail if no rom signature */ + if (nv_rd08(dev, NV_PRAMIN_OFFSET + 0) != 0x55 || + nv_rd08(dev, NV_PRAMIN_OFFSET + 1) != 0xaa) + goto out; + + bios->length = nv_rd08(dev, NV_PRAMIN_OFFSET + 2) * 512; + bios->data = kmalloc(bios->length, GFP_KERNEL); + if (bios->data) { + for (i = 0; i < bios->length; i++) + bios->data[i] = nv_rd08(dev, NV_PRAMIN_OFFSET + i); + } + +out: + if (dev_priv->card_type >= NV_50) + nv_wr32(dev, 0x001700, bar0); +} + +static void +bios_shadow_pci(struct nvbios *bios) +{ + struct pci_dev *pdev = bios->dev->pdev; + size_t length; + + if (!pci_enable_rom(pdev)) { + void __iomem *rom = pci_map_rom(pdev, &length); + if (rom && length) { + bios->data = kmalloc(length, GFP_KERNEL); + if (bios->data) { + memcpy_fromio(bios->data, rom, length); + bios->length = length; + } + } + if (rom) + pci_unmap_rom(pdev, rom); + + pci_disable_rom(pdev); + } +} + +static void +bios_shadow_acpi(struct nvbios *bios) +{ + struct pci_dev *pdev = bios->dev->pdev; + int ptr, len, ret; + u8 data[3]; + + if (!nouveau_acpi_rom_supported(pdev)) + return; + + ret = nouveau_acpi_get_bios_chunk(data, 0, sizeof(data)); + if (ret != sizeof(data)) + return; + + bios->length = min(data[2] * 512, 65536); + bios->data = kmalloc(bios->length, GFP_KERNEL); + if (!bios->data) + return; + + len = bios->length; + ptr = 0; + while (len) { + int size = (len > ROM_BIOS_PAGE) ? ROM_BIOS_PAGE : len; + + ret = nouveau_acpi_get_bios_chunk(bios->data, ptr, size); + if (ret != size) { + kfree(bios->data); + bios->data = NULL; + return; + } + + len -= size; + ptr += size; + } +} + +struct methods { + const char desc[8]; + void (*shadow)(struct nvbios *); + const bool rw; + int score; + u32 size; + u8 *data; +}; + +static bool +bios_shadow(struct drm_device *dev) +{ + struct methods shadow_methods[] = { + { "PRAMIN", bios_shadow_pramin, true, 0, 0, NULL }, + { "PROM", bios_shadow_prom, false, 0, 0, NULL }, + { "ACPI", bios_shadow_acpi, true, 0, 0, NULL }, + { "PCIROM", bios_shadow_pci, true, 0, 0, NULL }, + {} + }; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct methods *mthd, *best; + + if (nouveau_vbios) { + mthd = shadow_methods; + do { + if (strcasecmp(nouveau_vbios, mthd->desc)) + continue; + NV_INFO(dev, "VBIOS source: %s\n", mthd->desc); + + mthd->shadow(bios); + mthd->score = score_vbios(bios, mthd->rw); + if (mthd->score) + return true; + } while ((++mthd)->shadow); + + NV_ERROR(dev, "VBIOS source \'%s\' invalid\n", nouveau_vbios); + } + + mthd = shadow_methods; + do { + NV_TRACE(dev, "Checking %s for VBIOS\n", mthd->desc); + mthd->shadow(bios); + mthd->score = score_vbios(bios, mthd->rw); + mthd->size = bios->length; + mthd->data = bios->data; + } while (mthd->score != 3 && (++mthd)->shadow); + + mthd = shadow_methods; + best = mthd; + do { + if (mthd->score > best->score) { + kfree(best->data); + best = mthd; + } + } while ((++mthd)->shadow); + + if (best->score) { + NV_TRACE(dev, "Using VBIOS from %s\n", best->desc); + bios->length = best->size; + bios->data = best->data; + return true; + } + + NV_ERROR(dev, "No valid VBIOS image found\n"); + return false; +} + +struct init_tbl_entry { + char *name; + uint8_t id; + /* Return: + * > 0: success, length of opcode + * 0: success, but abort further parsing of table (INIT_DONE etc) + * < 0: failure, table parsing will be aborted + */ + int (*handler)(struct nvbios *, uint16_t, struct init_exec *); +}; + +static int parse_init_table(struct nvbios *, uint16_t, struct init_exec *); + +#define MACRO_INDEX_SIZE 2 +#define MACRO_SIZE 8 +#define CONDITION_SIZE 12 +#define IO_FLAG_CONDITION_SIZE 9 +#define IO_CONDITION_SIZE 5 +#define MEM_INIT_SIZE 66 + +static void still_alive(void) +{ +#if 0 + sync(); + mdelay(2); +#endif +} + +static uint32_t +munge_reg(struct nvbios *bios, uint32_t reg) +{ + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + struct dcb_entry *dcbent = bios->display.output; + + if (dev_priv->card_type < NV_50) + return reg; + + if (reg & 0x80000000) { + BUG_ON(bios->display.crtc < 0); + reg += bios->display.crtc * 0x800; + } + + if (reg & 0x40000000) { + BUG_ON(!dcbent); + + reg += (ffs(dcbent->or) - 1) * 0x800; + if ((reg & 0x20000000) && !(dcbent->sorconf.link & 1)) + reg += 0x00000080; + } + + reg &= ~0xe0000000; + return reg; +} + +static int +valid_reg(struct nvbios *bios, uint32_t reg) +{ + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + struct drm_device *dev = bios->dev; + + /* C51 has misaligned regs on purpose. Marvellous */ + if (reg & 0x2 || + (reg & 0x1 && dev_priv->vbios.chip_version != 0x51)) + NV_ERROR(dev, "======= misaligned reg 0x%08X =======\n", reg); + + /* warn on C51 regs that haven't been verified accessible in tracing */ + if (reg & 0x1 && dev_priv->vbios.chip_version == 0x51 && + reg != 0x130d && reg != 0x1311 && reg != 0x60081d) + NV_WARN(dev, "=== C51 misaligned reg 0x%08X not verified ===\n", + reg); + + if (reg >= (8*1024*1024)) { + NV_ERROR(dev, "=== reg 0x%08x out of mapped bounds ===\n", reg); + return 0; + } + + return 1; +} + +static bool +valid_idx_port(struct nvbios *bios, uint16_t port) +{ + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + struct drm_device *dev = bios->dev; + + /* + * If adding more ports here, the read/write functions below will need + * updating so that the correct mmio range (PRMCIO, PRMDIO, PRMVIO) is + * used for the port in question + */ + if (dev_priv->card_type < NV_50) { + if (port == NV_CIO_CRX__COLOR) + return true; + if (port == NV_VIO_SRX) + return true; + } else { + if (port == NV_CIO_CRX__COLOR) + return true; + } + + NV_ERROR(dev, "========== unknown indexed io port 0x%04X ==========\n", + port); + + return false; +} + +static bool +valid_port(struct nvbios *bios, uint16_t port) +{ + struct drm_device *dev = bios->dev; + + /* + * If adding more ports here, the read/write functions below will need + * updating so that the correct mmio range (PRMCIO, PRMDIO, PRMVIO) is + * used for the port in question + */ + if (port == NV_VIO_VSE2) + return true; + + NV_ERROR(dev, "========== unknown io port 0x%04X ==========\n", port); + + return false; +} + +static uint32_t +bios_rd32(struct nvbios *bios, uint32_t reg) +{ + uint32_t data; + + reg = munge_reg(bios, reg); + if (!valid_reg(bios, reg)) + return 0; + + /* + * C51 sometimes uses regs with bit0 set in the address. For these + * cases there should exist a translation in a BIOS table to an IO + * port address which the BIOS uses for accessing the reg + * + * These only seem to appear for the power control regs to a flat panel, + * and the GPIO regs at 0x60081*. In C51 mmio traces the normal regs + * for 0x1308 and 0x1310 are used - hence the mask below. An S3 + * suspend-resume mmio trace from a C51 will be required to see if this + * is true for the power microcode in 0x14.., or whether the direct IO + * port access method is needed + */ + if (reg & 0x1) + reg &= ~0x1; + + data = nv_rd32(bios->dev, reg); + + BIOSLOG(bios, " Read: Reg: 0x%08X, Data: 0x%08X\n", reg, data); + + return data; +} + +static void +bios_wr32(struct nvbios *bios, uint32_t reg, uint32_t data) +{ + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + + reg = munge_reg(bios, reg); + if (!valid_reg(bios, reg)) + return; + + /* see note in bios_rd32 */ + if (reg & 0x1) + reg &= 0xfffffffe; + + LOG_OLD_VALUE(bios_rd32(bios, reg)); + BIOSLOG(bios, " Write: Reg: 0x%08X, Data: 0x%08X\n", reg, data); + + if (dev_priv->vbios.execute) { + still_alive(); + nv_wr32(bios->dev, reg, data); + } +} + +static uint8_t +bios_idxprt_rd(struct nvbios *bios, uint16_t port, uint8_t index) +{ + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + struct drm_device *dev = bios->dev; + uint8_t data; + + if (!valid_idx_port(bios, port)) + return 0; + + if (dev_priv->card_type < NV_50) { + if (port == NV_VIO_SRX) + data = NVReadVgaSeq(dev, bios->state.crtchead, index); + else /* assume NV_CIO_CRX__COLOR */ + data = NVReadVgaCrtc(dev, bios->state.crtchead, index); + } else { + uint32_t data32; + + data32 = bios_rd32(bios, NV50_PDISPLAY_VGACRTC(index & ~3)); + data = (data32 >> ((index & 3) << 3)) & 0xff; + } + + BIOSLOG(bios, " Indexed IO read: Port: 0x%04X, Index: 0x%02X, " + "Head: 0x%02X, Data: 0x%02X\n", + port, index, bios->state.crtchead, data); + return data; +} + +static void +bios_idxprt_wr(struct nvbios *bios, uint16_t port, uint8_t index, uint8_t data) +{ + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + struct drm_device *dev = bios->dev; + + if (!valid_idx_port(bios, port)) + return; + + /* + * The current head is maintained in the nvbios member state.crtchead. + * We trap changes to CR44 and update the head variable and hence the + * register set written. + * As CR44 only exists on CRTC0, we update crtchead to head0 in advance + * of the write, and to head1 after the write + */ + if (port == NV_CIO_CRX__COLOR && index == NV_CIO_CRE_44 && + data != NV_CIO_CRE_44_HEADB) + bios->state.crtchead = 0; + + LOG_OLD_VALUE(bios_idxprt_rd(bios, port, index)); + BIOSLOG(bios, " Indexed IO write: Port: 0x%04X, Index: 0x%02X, " + "Head: 0x%02X, Data: 0x%02X\n", + port, index, bios->state.crtchead, data); + + if (bios->execute && dev_priv->card_type < NV_50) { + still_alive(); + if (port == NV_VIO_SRX) + NVWriteVgaSeq(dev, bios->state.crtchead, index, data); + else /* assume NV_CIO_CRX__COLOR */ + NVWriteVgaCrtc(dev, bios->state.crtchead, index, data); + } else + if (bios->execute) { + uint32_t data32, shift = (index & 3) << 3; + + still_alive(); + + data32 = bios_rd32(bios, NV50_PDISPLAY_VGACRTC(index & ~3)); + data32 &= ~(0xff << shift); + data32 |= (data << shift); + bios_wr32(bios, NV50_PDISPLAY_VGACRTC(index & ~3), data32); + } + + if (port == NV_CIO_CRX__COLOR && + index == NV_CIO_CRE_44 && data == NV_CIO_CRE_44_HEADB) + bios->state.crtchead = 1; +} + +static uint8_t +bios_port_rd(struct nvbios *bios, uint16_t port) +{ + uint8_t data, head = bios->state.crtchead; + + if (!valid_port(bios, port)) + return 0; + + data = NVReadPRMVIO(bios->dev, head, NV_PRMVIO0_OFFSET + port); + + BIOSLOG(bios, " IO read: Port: 0x%04X, Head: 0x%02X, Data: 0x%02X\n", + port, head, data); + + return data; +} + +static void +bios_port_wr(struct nvbios *bios, uint16_t port, uint8_t data) +{ + int head = bios->state.crtchead; + + if (!valid_port(bios, port)) + return; + + LOG_OLD_VALUE(bios_port_rd(bios, port)); + BIOSLOG(bios, " IO write: Port: 0x%04X, Head: 0x%02X, Data: 0x%02X\n", + port, head, data); + + if (!bios->execute) + return; + + still_alive(); + NVWritePRMVIO(bios->dev, head, NV_PRMVIO0_OFFSET + port, data); +} + +static bool +io_flag_condition_met(struct nvbios *bios, uint16_t offset, uint8_t cond) +{ + /* + * The IO flag condition entry has 2 bytes for the CRTC port; 1 byte + * for the CRTC index; 1 byte for the mask to apply to the value + * retrieved from the CRTC; 1 byte for the shift right to apply to the + * masked CRTC value; 2 bytes for the offset to the flag array, to + * which the shifted value is added; 1 byte for the mask applied to the + * value read from the flag array; and 1 byte for the value to compare + * against the masked byte from the flag table. + */ + + uint16_t condptr = bios->io_flag_condition_tbl_ptr + cond * IO_FLAG_CONDITION_SIZE; + uint16_t crtcport = ROM16(bios->data[condptr]); + uint8_t crtcindex = bios->data[condptr + 2]; + uint8_t mask = bios->data[condptr + 3]; + uint8_t shift = bios->data[condptr + 4]; + uint16_t flagarray = ROM16(bios->data[condptr + 5]); + uint8_t flagarraymask = bios->data[condptr + 7]; + uint8_t cmpval = bios->data[condptr + 8]; + uint8_t data; + + BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, " + "Shift: 0x%02X, FlagArray: 0x%04X, FAMask: 0x%02X, " + "Cmpval: 0x%02X\n", + offset, crtcport, crtcindex, mask, shift, flagarray, flagarraymask, cmpval); + + data = bios_idxprt_rd(bios, crtcport, crtcindex); + + data = bios->data[flagarray + ((data & mask) >> shift)]; + data &= flagarraymask; + + BIOSLOG(bios, "0x%04X: Checking if 0x%02X equals 0x%02X\n", + offset, data, cmpval); + + return (data == cmpval); +} + +static bool +bios_condition_met(struct nvbios *bios, uint16_t offset, uint8_t cond) +{ + /* + * The condition table entry has 4 bytes for the address of the + * register to check, 4 bytes for a mask to apply to the register and + * 4 for a test comparison value + */ + + uint16_t condptr = bios->condition_tbl_ptr + cond * CONDITION_SIZE; + uint32_t reg = ROM32(bios->data[condptr]); + uint32_t mask = ROM32(bios->data[condptr + 4]); + uint32_t cmpval = ROM32(bios->data[condptr + 8]); + uint32_t data; + + BIOSLOG(bios, "0x%04X: Cond: 0x%02X, Reg: 0x%08X, Mask: 0x%08X\n", + offset, cond, reg, mask); + + data = bios_rd32(bios, reg) & mask; + + BIOSLOG(bios, "0x%04X: Checking if 0x%08X equals 0x%08X\n", + offset, data, cmpval); + + return (data == cmpval); +} + +static bool +io_condition_met(struct nvbios *bios, uint16_t offset, uint8_t cond) +{ + /* + * The IO condition entry has 2 bytes for the IO port address; 1 byte + * for the index to write to io_port; 1 byte for the mask to apply to + * the byte read from io_port+1; and 1 byte for the value to compare + * against the masked byte. + */ + + uint16_t condptr = bios->io_condition_tbl_ptr + cond * IO_CONDITION_SIZE; + uint16_t io_port = ROM16(bios->data[condptr]); + uint8_t port_index = bios->data[condptr + 2]; + uint8_t mask = bios->data[condptr + 3]; + uint8_t cmpval = bios->data[condptr + 4]; + + uint8_t data = bios_idxprt_rd(bios, io_port, port_index) & mask; + + BIOSLOG(bios, "0x%04X: Checking if 0x%02X equals 0x%02X\n", + offset, data, cmpval); + + return (data == cmpval); +} + +static int +nv50_pll_set(struct drm_device *dev, uint32_t reg, uint32_t clk) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pll_vals pll; + struct pll_lims pll_limits; + u32 ctrl, mask, coef; + int ret; + + ret = get_pll_limits(dev, reg, &pll_limits); + if (ret) + return ret; + + clk = nouveau_calc_pll_mnp(dev, &pll_limits, clk, &pll); + if (!clk) + return -ERANGE; + + coef = pll.N1 << 8 | pll.M1; + ctrl = pll.log2P << 16; + mask = 0x00070000; + if (reg == 0x004008) { + mask |= 0x01f80000; + ctrl |= (pll_limits.log2p_bias << 19); + ctrl |= (pll.log2P << 22); + } + + if (!dev_priv->vbios.execute) + return 0; + + nv_mask(dev, reg + 0, mask, ctrl); + nv_wr32(dev, reg + 4, coef); + return 0; +} + +static int +setPLL(struct nvbios *bios, uint32_t reg, uint32_t clk) +{ + struct drm_device *dev = bios->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + /* clk in kHz */ + struct pll_lims pll_lim; + struct nouveau_pll_vals pllvals; + int ret; + + if (dev_priv->card_type >= NV_50) + return nv50_pll_set(dev, reg, clk); + + /* high regs (such as in the mac g5 table) are not -= 4 */ + ret = get_pll_limits(dev, reg > 0x405c ? reg : reg - 4, &pll_lim); + if (ret) + return ret; + + clk = nouveau_calc_pll_mnp(dev, &pll_lim, clk, &pllvals); + if (!clk) + return -ERANGE; + + if (bios->execute) { + still_alive(); + nouveau_hw_setpll(dev, reg, &pllvals); + } + + return 0; +} + +static int dcb_entry_idx_from_crtchead(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + + /* + * For the results of this function to be correct, CR44 must have been + * set (using bios_idxprt_wr to set crtchead), CR58 set for CR57 = 0, + * and the DCB table parsed, before the script calling the function is + * run. run_digital_op_script is example of how to do such setup + */ + + uint8_t dcb_entry = NVReadVgaCrtc5758(dev, bios->state.crtchead, 0); + + if (dcb_entry > bios->dcb.entries) { + NV_ERROR(dev, "CR58 doesn't have a valid DCB entry currently " + "(%02X)\n", dcb_entry); + dcb_entry = 0x7f; /* unused / invalid marker */ + } + + return dcb_entry; +} + +static struct nouveau_i2c_chan * +init_i2c_device_find(struct drm_device *dev, int i2c_index) +{ + if (i2c_index == 0xff) { + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_table *dcb = &dev_priv->vbios.dcb; + /* note: dcb_entry_idx_from_crtchead needs pre-script set-up */ + int idx = dcb_entry_idx_from_crtchead(dev); + + i2c_index = NV_I2C_DEFAULT(0); + if (idx != 0x7f && dcb->entry[idx].i2c_upper_default) + i2c_index = NV_I2C_DEFAULT(1); + } + + return nouveau_i2c_find(dev, i2c_index); +} + +static uint32_t +get_tmds_index_reg(struct drm_device *dev, uint8_t mlv) +{ + /* + * For mlv < 0x80, it is an index into a table of TMDS base addresses. + * For mlv == 0x80 use the "or" value of the dcb_entry indexed by + * CR58 for CR57 = 0 to index a table of offsets to the basic + * 0x6808b0 address. + * For mlv == 0x81 use the "or" value of the dcb_entry indexed by + * CR58 for CR57 = 0 to index a table of offsets to the basic + * 0x6808b0 address, and then flip the offset by 8. + */ + + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + const int pramdac_offset[13] = { + 0, 0, 0x8, 0, 0x2000, 0, 0, 0, 0x2008, 0, 0, 0, 0x2000 }; + const uint32_t pramdac_table[4] = { + 0x6808b0, 0x6808b8, 0x6828b0, 0x6828b8 }; + + if (mlv >= 0x80) { + int dcb_entry, dacoffset; + + /* note: dcb_entry_idx_from_crtchead needs pre-script set-up */ + dcb_entry = dcb_entry_idx_from_crtchead(dev); + if (dcb_entry == 0x7f) + return 0; + dacoffset = pramdac_offset[bios->dcb.entry[dcb_entry].or]; + if (mlv == 0x81) + dacoffset ^= 8; + return 0x6808b0 + dacoffset; + } else { + if (mlv >= ARRAY_SIZE(pramdac_table)) { + NV_ERROR(dev, "Magic Lookup Value too big (%02X)\n", + mlv); + return 0; + } + return pramdac_table[mlv]; + } +} + +static int +init_io_restrict_prog(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_IO_RESTRICT_PROG opcode: 0x32 ('2') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): CRTC port + * offset + 3 (8 bit): CRTC index + * offset + 4 (8 bit): mask + * offset + 5 (8 bit): shift + * offset + 6 (8 bit): count + * offset + 7 (32 bit): register + * offset + 11 (32 bit): configuration 1 + * ... + * + * Starting at offset + 11 there are "count" 32 bit values. + * To find out which value to use read index "CRTC index" on "CRTC + * port", AND this value with "mask" and then bit shift right "shift" + * bits. Read the appropriate value using this index and write to + * "register" + */ + + uint16_t crtcport = ROM16(bios->data[offset + 1]); + uint8_t crtcindex = bios->data[offset + 3]; + uint8_t mask = bios->data[offset + 4]; + uint8_t shift = bios->data[offset + 5]; + uint8_t count = bios->data[offset + 6]; + uint32_t reg = ROM32(bios->data[offset + 7]); + uint8_t config; + uint32_t configval; + int len = 11 + count * 4; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, " + "Shift: 0x%02X, Count: 0x%02X, Reg: 0x%08X\n", + offset, crtcport, crtcindex, mask, shift, count, reg); + + config = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) >> shift; + if (config > count) { + NV_ERROR(bios->dev, + "0x%04X: Config 0x%02X exceeds maximal bound 0x%02X\n", + offset, config, count); + return len; + } + + configval = ROM32(bios->data[offset + 11 + config * 4]); + + BIOSLOG(bios, "0x%04X: Writing config %02X\n", offset, config); + + bios_wr32(bios, reg, configval); + + return len; +} + +static int +init_repeat(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_REPEAT opcode: 0x33 ('3') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): count + * + * Execute script following this opcode up to INIT_REPEAT_END + * "count" times + */ + + uint8_t count = bios->data[offset + 1]; + uint8_t i; + + /* no iexec->execute check by design */ + + BIOSLOG(bios, "0x%04X: Repeating following segment %d times\n", + offset, count); + + iexec->repeat = true; + + /* + * count - 1, as the script block will execute once when we leave this + * opcode -- this is compatible with bios behaviour as: + * a) the block is always executed at least once, even if count == 0 + * b) the bios interpreter skips to the op following INIT_END_REPEAT, + * while we don't + */ + for (i = 0; i < count - 1; i++) + parse_init_table(bios, offset + 2, iexec); + + iexec->repeat = false; + + return 2; +} + +static int +init_io_restrict_pll(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_IO_RESTRICT_PLL opcode: 0x34 ('4') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): CRTC port + * offset + 3 (8 bit): CRTC index + * offset + 4 (8 bit): mask + * offset + 5 (8 bit): shift + * offset + 6 (8 bit): IO flag condition index + * offset + 7 (8 bit): count + * offset + 8 (32 bit): register + * offset + 12 (16 bit): frequency 1 + * ... + * + * Starting at offset + 12 there are "count" 16 bit frequencies (10kHz). + * Set PLL register "register" to coefficients for frequency n, + * selected by reading index "CRTC index" of "CRTC port" ANDed with + * "mask" and shifted right by "shift". + * + * If "IO flag condition index" > 0, and condition met, double + * frequency before setting it. + */ + + uint16_t crtcport = ROM16(bios->data[offset + 1]); + uint8_t crtcindex = bios->data[offset + 3]; + uint8_t mask = bios->data[offset + 4]; + uint8_t shift = bios->data[offset + 5]; + int8_t io_flag_condition_idx = bios->data[offset + 6]; + uint8_t count = bios->data[offset + 7]; + uint32_t reg = ROM32(bios->data[offset + 8]); + uint8_t config; + uint16_t freq; + int len = 12 + count * 2; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, " + "Shift: 0x%02X, IO Flag Condition: 0x%02X, " + "Count: 0x%02X, Reg: 0x%08X\n", + offset, crtcport, crtcindex, mask, shift, + io_flag_condition_idx, count, reg); + + config = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) >> shift; + if (config > count) { + NV_ERROR(bios->dev, + "0x%04X: Config 0x%02X exceeds maximal bound 0x%02X\n", + offset, config, count); + return len; + } + + freq = ROM16(bios->data[offset + 12 + config * 2]); + + if (io_flag_condition_idx > 0) { + if (io_flag_condition_met(bios, offset, io_flag_condition_idx)) { + BIOSLOG(bios, "0x%04X: Condition fulfilled -- " + "frequency doubled\n", offset); + freq *= 2; + } else + BIOSLOG(bios, "0x%04X: Condition not fulfilled -- " + "frequency unchanged\n", offset); + } + + BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Config: 0x%02X, Freq: %d0kHz\n", + offset, reg, config, freq); + + setPLL(bios, reg, freq * 10); + + return len; +} + +static int +init_end_repeat(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_END_REPEAT opcode: 0x36 ('6') + * + * offset (8 bit): opcode + * + * Marks the end of the block for INIT_REPEAT to repeat + */ + + /* no iexec->execute check by design */ + + /* + * iexec->repeat flag necessary to go past INIT_END_REPEAT opcode when + * we're not in repeat mode + */ + if (iexec->repeat) + return 0; + + return 1; +} + +static int +init_copy(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_COPY opcode: 0x37 ('7') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): register + * offset + 5 (8 bit): shift + * offset + 6 (8 bit): srcmask + * offset + 7 (16 bit): CRTC port + * offset + 9 (8 bit): CRTC index + * offset + 10 (8 bit): mask + * + * Read index "CRTC index" on "CRTC port", AND with "mask", OR with + * (REGVAL("register") >> "shift" & "srcmask") and write-back to CRTC + * port + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint8_t shift = bios->data[offset + 5]; + uint8_t srcmask = bios->data[offset + 6]; + uint16_t crtcport = ROM16(bios->data[offset + 7]); + uint8_t crtcindex = bios->data[offset + 9]; + uint8_t mask = bios->data[offset + 10]; + uint32_t data; + uint8_t crtcdata; + + if (!iexec->execute) + return 11; + + BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Shift: 0x%02X, SrcMask: 0x%02X, " + "Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X\n", + offset, reg, shift, srcmask, crtcport, crtcindex, mask); + + data = bios_rd32(bios, reg); + + if (shift < 0x80) + data >>= shift; + else + data <<= (0x100 - shift); + + data &= srcmask; + + crtcdata = bios_idxprt_rd(bios, crtcport, crtcindex) & mask; + crtcdata |= (uint8_t)data; + bios_idxprt_wr(bios, crtcport, crtcindex, crtcdata); + + return 11; +} + +static int +init_not(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_NOT opcode: 0x38 ('8') + * + * offset (8 bit): opcode + * + * Invert the current execute / no-execute condition (i.e. "else") + */ + if (iexec->execute) + BIOSLOG(bios, "0x%04X: ------ Skipping following commands ------\n", offset); + else + BIOSLOG(bios, "0x%04X: ------ Executing following commands ------\n", offset); + + iexec->execute = !iexec->execute; + return 1; +} + +static int +init_io_flag_condition(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_IO_FLAG_CONDITION opcode: 0x39 ('9') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): condition number + * + * Check condition "condition number" in the IO flag condition table. + * If condition not met skip subsequent opcodes until condition is + * inverted (INIT_NOT), or we hit INIT_RESUME + */ + + uint8_t cond = bios->data[offset + 1]; + + if (!iexec->execute) + return 2; + + if (io_flag_condition_met(bios, offset, cond)) + BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset); + else { + BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset); + iexec->execute = false; + } + + return 2; +} + +static int +init_dp_condition(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_DP_CONDITION opcode: 0x3A ('') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): "sub" opcode + * offset + 2 (8 bit): unknown + * + */ + + struct dcb_entry *dcb = bios->display.output; + struct drm_device *dev = bios->dev; + uint8_t cond = bios->data[offset + 1]; + uint8_t *table, *entry; + + BIOSLOG(bios, "0x%04X: subop 0x%02X\n", offset, cond); + + if (!iexec->execute) + return 3; + + table = nouveau_dp_bios_data(dev, dcb, &entry); + if (!table) + return 3; + + switch (cond) { + case 0: + entry = dcb_conn(dev, dcb->connector); + if (!entry || entry[0] != DCB_CONNECTOR_eDP) + iexec->execute = false; + break; + case 1: + case 2: + if ((table[0] < 0x40 && !(entry[5] & cond)) || + (table[0] == 0x40 && !(entry[4] & cond))) + iexec->execute = false; + break; + case 5: + { + struct nouveau_i2c_chan *auxch; + int ret; + + auxch = nouveau_i2c_find(dev, bios->display.output->i2c_index); + if (!auxch) { + NV_ERROR(dev, "0x%04X: couldn't get auxch\n", offset); + return 3; + } + + ret = nouveau_dp_auxch(auxch, 9, 0xd, &cond, 1); + if (ret) { + NV_ERROR(dev, "0x%04X: auxch rd fail: %d\n", offset, ret); + return 3; + } + + if (!(cond & 1)) + iexec->execute = false; + } + break; + default: + NV_WARN(dev, "0x%04X: unknown INIT_3A op: %d\n", offset, cond); + break; + } + + if (iexec->execute) + BIOSLOG(bios, "0x%04X: continuing to execute\n", offset); + else + BIOSLOG(bios, "0x%04X: skipping following commands\n", offset); + + return 3; +} + +static int +init_op_3b(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_3B opcode: 0x3B ('') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): crtc index + * + */ + + uint8_t or = ffs(bios->display.output->or) - 1; + uint8_t index = bios->data[offset + 1]; + uint8_t data; + + if (!iexec->execute) + return 2; + + data = bios_idxprt_rd(bios, 0x3d4, index); + bios_idxprt_wr(bios, 0x3d4, index, data & ~(1 << or)); + return 2; +} + +static int +init_op_3c(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_3C opcode: 0x3C ('') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): crtc index + * + */ + + uint8_t or = ffs(bios->display.output->or) - 1; + uint8_t index = bios->data[offset + 1]; + uint8_t data; + + if (!iexec->execute) + return 2; + + data = bios_idxprt_rd(bios, 0x3d4, index); + bios_idxprt_wr(bios, 0x3d4, index, data | (1 << or)); + return 2; +} + +static int +init_idx_addr_latched(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_INDEX_ADDRESS_LATCHED opcode: 0x49 ('I') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): control register + * offset + 5 (32 bit): data register + * offset + 9 (32 bit): mask + * offset + 13 (32 bit): data + * offset + 17 (8 bit): count + * offset + 18 (8 bit): address 1 + * offset + 19 (8 bit): data 1 + * ... + * + * For each of "count" address and data pairs, write "data n" to + * "data register", read the current value of "control register", + * and write it back once ANDed with "mask", ORed with "data", + * and ORed with "address n" + */ + + uint32_t controlreg = ROM32(bios->data[offset + 1]); + uint32_t datareg = ROM32(bios->data[offset + 5]); + uint32_t mask = ROM32(bios->data[offset + 9]); + uint32_t data = ROM32(bios->data[offset + 13]); + uint8_t count = bios->data[offset + 17]; + int len = 18 + count * 2; + uint32_t value; + int i; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: ControlReg: 0x%08X, DataReg: 0x%08X, " + "Mask: 0x%08X, Data: 0x%08X, Count: 0x%02X\n", + offset, controlreg, datareg, mask, data, count); + + for (i = 0; i < count; i++) { + uint8_t instaddress = bios->data[offset + 18 + i * 2]; + uint8_t instdata = bios->data[offset + 19 + i * 2]; + + BIOSLOG(bios, "0x%04X: Address: 0x%02X, Data: 0x%02X\n", + offset, instaddress, instdata); + + bios_wr32(bios, datareg, instdata); + value = bios_rd32(bios, controlreg) & mask; + value |= data; + value |= instaddress; + bios_wr32(bios, controlreg, value); + } + + return len; +} + +static int +init_io_restrict_pll2(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_IO_RESTRICT_PLL2 opcode: 0x4A ('J') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): CRTC port + * offset + 3 (8 bit): CRTC index + * offset + 4 (8 bit): mask + * offset + 5 (8 bit): shift + * offset + 6 (8 bit): count + * offset + 7 (32 bit): register + * offset + 11 (32 bit): frequency 1 + * ... + * + * Starting at offset + 11 there are "count" 32 bit frequencies (kHz). + * Set PLL register "register" to coefficients for frequency n, + * selected by reading index "CRTC index" of "CRTC port" ANDed with + * "mask" and shifted right by "shift". + */ + + uint16_t crtcport = ROM16(bios->data[offset + 1]); + uint8_t crtcindex = bios->data[offset + 3]; + uint8_t mask = bios->data[offset + 4]; + uint8_t shift = bios->data[offset + 5]; + uint8_t count = bios->data[offset + 6]; + uint32_t reg = ROM32(bios->data[offset + 7]); + int len = 11 + count * 4; + uint8_t config; + uint32_t freq; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, " + "Shift: 0x%02X, Count: 0x%02X, Reg: 0x%08X\n", + offset, crtcport, crtcindex, mask, shift, count, reg); + + if (!reg) + return len; + + config = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) >> shift; + if (config > count) { + NV_ERROR(bios->dev, + "0x%04X: Config 0x%02X exceeds maximal bound 0x%02X\n", + offset, config, count); + return len; + } + + freq = ROM32(bios->data[offset + 11 + config * 4]); + + BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Config: 0x%02X, Freq: %dkHz\n", + offset, reg, config, freq); + + setPLL(bios, reg, freq); + + return len; +} + +static int +init_pll2(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_PLL2 opcode: 0x4B ('K') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): register + * offset + 5 (32 bit): freq + * + * Set PLL register "register" to coefficients for frequency "freq" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint32_t freq = ROM32(bios->data[offset + 5]); + + if (!iexec->execute) + return 9; + + BIOSLOG(bios, "0x%04X: Reg: 0x%04X, Freq: %dkHz\n", + offset, reg, freq); + + setPLL(bios, reg, freq); + return 9; +} + +static int +init_i2c_byte(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_I2C_BYTE opcode: 0x4C ('L') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): DCB I2C table entry index + * offset + 2 (8 bit): I2C slave address + * offset + 3 (8 bit): count + * offset + 4 (8 bit): I2C register 1 + * offset + 5 (8 bit): mask 1 + * offset + 6 (8 bit): data 1 + * ... + * + * For each of "count" registers given by "I2C register n" on the device + * addressed by "I2C slave address" on the I2C bus given by + * "DCB I2C table entry index", read the register, AND the result with + * "mask n" and OR it with "data n" before writing it back to the device + */ + + struct drm_device *dev = bios->dev; + uint8_t i2c_index = bios->data[offset + 1]; + uint8_t i2c_address = bios->data[offset + 2] >> 1; + uint8_t count = bios->data[offset + 3]; + struct nouveau_i2c_chan *chan; + int len = 4 + count * 3; + int ret, i; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X, " + "Count: 0x%02X\n", + offset, i2c_index, i2c_address, count); + + chan = init_i2c_device_find(dev, i2c_index); + if (!chan) { + NV_ERROR(dev, "0x%04X: i2c bus not found\n", offset); + return len; + } + + for (i = 0; i < count; i++) { + uint8_t reg = bios->data[offset + 4 + i * 3]; + uint8_t mask = bios->data[offset + 5 + i * 3]; + uint8_t data = bios->data[offset + 6 + i * 3]; + union i2c_smbus_data val; + + ret = i2c_smbus_xfer(&chan->adapter, i2c_address, 0, + I2C_SMBUS_READ, reg, + I2C_SMBUS_BYTE_DATA, &val); + if (ret < 0) { + NV_ERROR(dev, "0x%04X: i2c rd fail: %d\n", offset, ret); + return len; + } + + BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X, Value: 0x%02X, " + "Mask: 0x%02X, Data: 0x%02X\n", + offset, reg, val.byte, mask, data); + + if (!bios->execute) + continue; + + val.byte &= mask; + val.byte |= data; + ret = i2c_smbus_xfer(&chan->adapter, i2c_address, 0, + I2C_SMBUS_WRITE, reg, + I2C_SMBUS_BYTE_DATA, &val); + if (ret < 0) { + NV_ERROR(dev, "0x%04X: i2c wr fail: %d\n", offset, ret); + return len; + } + } + + return len; +} + +static int +init_zm_i2c_byte(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_ZM_I2C_BYTE opcode: 0x4D ('M') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): DCB I2C table entry index + * offset + 2 (8 bit): I2C slave address + * offset + 3 (8 bit): count + * offset + 4 (8 bit): I2C register 1 + * offset + 5 (8 bit): data 1 + * ... + * + * For each of "count" registers given by "I2C register n" on the device + * addressed by "I2C slave address" on the I2C bus given by + * "DCB I2C table entry index", set the register to "data n" + */ + + struct drm_device *dev = bios->dev; + uint8_t i2c_index = bios->data[offset + 1]; + uint8_t i2c_address = bios->data[offset + 2] >> 1; + uint8_t count = bios->data[offset + 3]; + struct nouveau_i2c_chan *chan; + int len = 4 + count * 2; + int ret, i; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X, " + "Count: 0x%02X\n", + offset, i2c_index, i2c_address, count); + + chan = init_i2c_device_find(dev, i2c_index); + if (!chan) { + NV_ERROR(dev, "0x%04X: i2c bus not found\n", offset); + return len; + } + + for (i = 0; i < count; i++) { + uint8_t reg = bios->data[offset + 4 + i * 2]; + union i2c_smbus_data val; + + val.byte = bios->data[offset + 5 + i * 2]; + + BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X, Data: 0x%02X\n", + offset, reg, val.byte); + + if (!bios->execute) + continue; + + ret = i2c_smbus_xfer(&chan->adapter, i2c_address, 0, + I2C_SMBUS_WRITE, reg, + I2C_SMBUS_BYTE_DATA, &val); + if (ret < 0) { + NV_ERROR(dev, "0x%04X: i2c wr fail: %d\n", offset, ret); + return len; + } + } + + return len; +} + +static int +init_zm_i2c(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_ZM_I2C opcode: 0x4E ('N') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): DCB I2C table entry index + * offset + 2 (8 bit): I2C slave address + * offset + 3 (8 bit): count + * offset + 4 (8 bit): data 1 + * ... + * + * Send "count" bytes ("data n") to the device addressed by "I2C slave + * address" on the I2C bus given by "DCB I2C table entry index" + */ + + struct drm_device *dev = bios->dev; + uint8_t i2c_index = bios->data[offset + 1]; + uint8_t i2c_address = bios->data[offset + 2] >> 1; + uint8_t count = bios->data[offset + 3]; + int len = 4 + count; + struct nouveau_i2c_chan *chan; + struct i2c_msg msg; + uint8_t data[256]; + int ret, i; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X, " + "Count: 0x%02X\n", + offset, i2c_index, i2c_address, count); + + chan = init_i2c_device_find(dev, i2c_index); + if (!chan) { + NV_ERROR(dev, "0x%04X: i2c bus not found\n", offset); + return len; + } + + for (i = 0; i < count; i++) { + data[i] = bios->data[offset + 4 + i]; + + BIOSLOG(bios, "0x%04X: Data: 0x%02X\n", offset, data[i]); + } + + if (bios->execute) { + msg.addr = i2c_address; + msg.flags = 0; + msg.len = count; + msg.buf = data; + ret = i2c_transfer(&chan->adapter, &msg, 1); + if (ret != 1) { + NV_ERROR(dev, "0x%04X: i2c wr fail: %d\n", offset, ret); + return len; + } + } + + return len; +} + +static int +init_tmds(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_TMDS opcode: 0x4F ('O') (non-canon name) + * + * offset (8 bit): opcode + * offset + 1 (8 bit): magic lookup value + * offset + 2 (8 bit): TMDS address + * offset + 3 (8 bit): mask + * offset + 4 (8 bit): data + * + * Read the data reg for TMDS address "TMDS address", AND it with mask + * and OR it with data, then write it back + * "magic lookup value" determines which TMDS base address register is + * used -- see get_tmds_index_reg() + */ + + struct drm_device *dev = bios->dev; + uint8_t mlv = bios->data[offset + 1]; + uint32_t tmdsaddr = bios->data[offset + 2]; + uint8_t mask = bios->data[offset + 3]; + uint8_t data = bios->data[offset + 4]; + uint32_t reg, value; + + if (!iexec->execute) + return 5; + + BIOSLOG(bios, "0x%04X: MagicLookupValue: 0x%02X, TMDSAddr: 0x%02X, " + "Mask: 0x%02X, Data: 0x%02X\n", + offset, mlv, tmdsaddr, mask, data); + + reg = get_tmds_index_reg(bios->dev, mlv); + if (!reg) { + NV_ERROR(dev, "0x%04X: no tmds_index_reg\n", offset); + return 5; + } + + bios_wr32(bios, reg, + tmdsaddr | NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE); + value = (bios_rd32(bios, reg + 4) & mask) | data; + bios_wr32(bios, reg + 4, value); + bios_wr32(bios, reg, tmdsaddr); + + return 5; +} + +static int +init_zm_tmds_group(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_ZM_TMDS_GROUP opcode: 0x50 ('P') (non-canon name) + * + * offset (8 bit): opcode + * offset + 1 (8 bit): magic lookup value + * offset + 2 (8 bit): count + * offset + 3 (8 bit): addr 1 + * offset + 4 (8 bit): data 1 + * ... + * + * For each of "count" TMDS address and data pairs write "data n" to + * "addr n". "magic lookup value" determines which TMDS base address + * register is used -- see get_tmds_index_reg() + */ + + struct drm_device *dev = bios->dev; + uint8_t mlv = bios->data[offset + 1]; + uint8_t count = bios->data[offset + 2]; + int len = 3 + count * 2; + uint32_t reg; + int i; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: MagicLookupValue: 0x%02X, Count: 0x%02X\n", + offset, mlv, count); + + reg = get_tmds_index_reg(bios->dev, mlv); + if (!reg) { + NV_ERROR(dev, "0x%04X: no tmds_index_reg\n", offset); + return len; + } + + for (i = 0; i < count; i++) { + uint8_t tmdsaddr = bios->data[offset + 3 + i * 2]; + uint8_t tmdsdata = bios->data[offset + 4 + i * 2]; + + bios_wr32(bios, reg + 4, tmdsdata); + bios_wr32(bios, reg, tmdsaddr); + } + + return len; +} + +static int +init_cr_idx_adr_latch(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_CR_INDEX_ADDRESS_LATCHED opcode: 0x51 ('Q') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): CRTC index1 + * offset + 2 (8 bit): CRTC index2 + * offset + 3 (8 bit): baseaddr + * offset + 4 (8 bit): count + * offset + 5 (8 bit): data 1 + * ... + * + * For each of "count" address and data pairs, write "baseaddr + n" to + * "CRTC index1" and "data n" to "CRTC index2" + * Once complete, restore initial value read from "CRTC index1" + */ + uint8_t crtcindex1 = bios->data[offset + 1]; + uint8_t crtcindex2 = bios->data[offset + 2]; + uint8_t baseaddr = bios->data[offset + 3]; + uint8_t count = bios->data[offset + 4]; + int len = 5 + count; + uint8_t oldaddr, data; + int i; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: Index1: 0x%02X, Index2: 0x%02X, " + "BaseAddr: 0x%02X, Count: 0x%02X\n", + offset, crtcindex1, crtcindex2, baseaddr, count); + + oldaddr = bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, crtcindex1); + + for (i = 0; i < count; i++) { + bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex1, + baseaddr + i); + data = bios->data[offset + 5 + i]; + bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex2, data); + } + + bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex1, oldaddr); + + return len; +} + +static int +init_cr(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_CR opcode: 0x52 ('R') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): CRTC index + * offset + 2 (8 bit): mask + * offset + 3 (8 bit): data + * + * Assign the value of at "CRTC index" ANDed with mask and ORed with + * data back to "CRTC index" + */ + + uint8_t crtcindex = bios->data[offset + 1]; + uint8_t mask = bios->data[offset + 2]; + uint8_t data = bios->data[offset + 3]; + uint8_t value; + + if (!iexec->execute) + return 4; + + BIOSLOG(bios, "0x%04X: Index: 0x%02X, Mask: 0x%02X, Data: 0x%02X\n", + offset, crtcindex, mask, data); + + value = bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, crtcindex) & mask; + value |= data; + bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex, value); + + return 4; +} + +static int +init_zm_cr(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_ZM_CR opcode: 0x53 ('S') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): CRTC index + * offset + 2 (8 bit): value + * + * Assign "value" to CRTC register with index "CRTC index". + */ + + uint8_t crtcindex = ROM32(bios->data[offset + 1]); + uint8_t data = bios->data[offset + 2]; + + if (!iexec->execute) + return 3; + + bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex, data); + + return 3; +} + +static int +init_zm_cr_group(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_ZM_CR_GROUP opcode: 0x54 ('T') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): count + * offset + 2 (8 bit): CRTC index 1 + * offset + 3 (8 bit): value 1 + * ... + * + * For "count", assign "value n" to CRTC register with index + * "CRTC index n". + */ + + uint8_t count = bios->data[offset + 1]; + int len = 2 + count * 2; + int i; + + if (!iexec->execute) + return len; + + for (i = 0; i < count; i++) + init_zm_cr(bios, offset + 2 + 2 * i - 1, iexec); + + return len; +} + +static int +init_condition_time(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_CONDITION_TIME opcode: 0x56 ('V') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): condition number + * offset + 2 (8 bit): retries / 50 + * + * Check condition "condition number" in the condition table. + * Bios code then sleeps for 2ms if the condition is not met, and + * repeats up to "retries" times, but on one C51 this has proved + * insufficient. In mmiotraces the driver sleeps for 20ms, so we do + * this, and bail after "retries" times, or 2s, whichever is less. + * If still not met after retries, clear execution flag for this table. + */ + + uint8_t cond = bios->data[offset + 1]; + uint16_t retries = bios->data[offset + 2] * 50; + unsigned cnt; + + if (!iexec->execute) + return 3; + + if (retries > 100) + retries = 100; + + BIOSLOG(bios, "0x%04X: Condition: 0x%02X, Retries: 0x%02X\n", + offset, cond, retries); + + if (!bios->execute) /* avoid 2s delays when "faking" execution */ + retries = 1; + + for (cnt = 0; cnt < retries; cnt++) { + if (bios_condition_met(bios, offset, cond)) { + BIOSLOG(bios, "0x%04X: Condition met, continuing\n", + offset); + break; + } else { + BIOSLOG(bios, "0x%04X: " + "Condition not met, sleeping for 20ms\n", + offset); + mdelay(20); + } + } + + if (!bios_condition_met(bios, offset, cond)) { + NV_WARN(bios->dev, + "0x%04X: Condition still not met after %dms, " + "skipping following opcodes\n", offset, 20 * retries); + iexec->execute = false; + } + + return 3; +} + +static int +init_ltime(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_LTIME opcode: 0x57 ('V') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): time + * + * Sleep for "time" milliseconds. + */ + + unsigned time = ROM16(bios->data[offset + 1]); + + if (!iexec->execute) + return 3; + + BIOSLOG(bios, "0x%04X: Sleeping for 0x%04X milliseconds\n", + offset, time); + + mdelay(time); + + return 3; +} + +static int +init_zm_reg_sequence(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_ZM_REG_SEQUENCE opcode: 0x58 ('X') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): base register + * offset + 5 (8 bit): count + * offset + 6 (32 bit): value 1 + * ... + * + * Starting at offset + 6 there are "count" 32 bit values. + * For "count" iterations set "base register" + 4 * current_iteration + * to "value current_iteration" + */ + + uint32_t basereg = ROM32(bios->data[offset + 1]); + uint32_t count = bios->data[offset + 5]; + int len = 6 + count * 4; + int i; + + if (!iexec->execute) + return len; + + BIOSLOG(bios, "0x%04X: BaseReg: 0x%08X, Count: 0x%02X\n", + offset, basereg, count); + + for (i = 0; i < count; i++) { + uint32_t reg = basereg + i * 4; + uint32_t data = ROM32(bios->data[offset + 6 + i * 4]); + + bios_wr32(bios, reg, data); + } + + return len; +} + +static int +init_sub_direct(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_SUB_DIRECT opcode: 0x5B ('[') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): subroutine offset (in bios) + * + * Calls a subroutine that will execute commands until INIT_DONE + * is found. + */ + + uint16_t sub_offset = ROM16(bios->data[offset + 1]); + + if (!iexec->execute) + return 3; + + BIOSLOG(bios, "0x%04X: Executing subroutine at 0x%04X\n", + offset, sub_offset); + + parse_init_table(bios, sub_offset, iexec); + + BIOSLOG(bios, "0x%04X: End of 0x%04X subroutine\n", offset, sub_offset); + + return 3; +} + +static int +init_jump(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_JUMP opcode: 0x5C ('\') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): offset (in bios) + * + * Continue execution of init table from 'offset' + */ + + uint16_t jmp_offset = ROM16(bios->data[offset + 1]); + + if (!iexec->execute) + return 3; + + BIOSLOG(bios, "0x%04X: Jump to 0x%04X\n", offset, jmp_offset); + return jmp_offset - offset; +} + +static int +init_i2c_if(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_I2C_IF opcode: 0x5E ('^') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): DCB I2C table entry index + * offset + 2 (8 bit): I2C slave address + * offset + 3 (8 bit): I2C register + * offset + 4 (8 bit): mask + * offset + 5 (8 bit): data + * + * Read the register given by "I2C register" on the device addressed + * by "I2C slave address" on the I2C bus given by "DCB I2C table + * entry index". Compare the result AND "mask" to "data". + * If they're not equal, skip subsequent opcodes until condition is + * inverted (INIT_NOT), or we hit INIT_RESUME + */ + + uint8_t i2c_index = bios->data[offset + 1]; + uint8_t i2c_address = bios->data[offset + 2] >> 1; + uint8_t reg = bios->data[offset + 3]; + uint8_t mask = bios->data[offset + 4]; + uint8_t data = bios->data[offset + 5]; + struct nouveau_i2c_chan *chan; + union i2c_smbus_data val; + int ret; + + /* no execute check by design */ + + BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X\n", + offset, i2c_index, i2c_address); + + chan = init_i2c_device_find(bios->dev, i2c_index); + if (!chan) + return -ENODEV; + + ret = i2c_smbus_xfer(&chan->adapter, i2c_address, 0, + I2C_SMBUS_READ, reg, + I2C_SMBUS_BYTE_DATA, &val); + if (ret < 0) { + BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X, Value: [no device], " + "Mask: 0x%02X, Data: 0x%02X\n", + offset, reg, mask, data); + iexec->execute = 0; + return 6; + } + + BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X, Value: 0x%02X, " + "Mask: 0x%02X, Data: 0x%02X\n", + offset, reg, val.byte, mask, data); + + iexec->execute = ((val.byte & mask) == data); + + return 6; +} + +static int +init_copy_nv_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_COPY_NV_REG opcode: 0x5F ('_') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): src reg + * offset + 5 (8 bit): shift + * offset + 6 (32 bit): src mask + * offset + 10 (32 bit): xor + * offset + 14 (32 bit): dst reg + * offset + 18 (32 bit): dst mask + * + * Shift REGVAL("src reg") right by (signed) "shift", AND result with + * "src mask", then XOR with "xor". Write this OR'd with + * (REGVAL("dst reg") AND'd with "dst mask") to "dst reg" + */ + + uint32_t srcreg = *((uint32_t *)(&bios->data[offset + 1])); + uint8_t shift = bios->data[offset + 5]; + uint32_t srcmask = *((uint32_t *)(&bios->data[offset + 6])); + uint32_t xor = *((uint32_t *)(&bios->data[offset + 10])); + uint32_t dstreg = *((uint32_t *)(&bios->data[offset + 14])); + uint32_t dstmask = *((uint32_t *)(&bios->data[offset + 18])); + uint32_t srcvalue, dstvalue; + + if (!iexec->execute) + return 22; + + BIOSLOG(bios, "0x%04X: SrcReg: 0x%08X, Shift: 0x%02X, SrcMask: 0x%08X, " + "Xor: 0x%08X, DstReg: 0x%08X, DstMask: 0x%08X\n", + offset, srcreg, shift, srcmask, xor, dstreg, dstmask); + + srcvalue = bios_rd32(bios, srcreg); + + if (shift < 0x80) + srcvalue >>= shift; + else + srcvalue <<= (0x100 - shift); + + srcvalue = (srcvalue & srcmask) ^ xor; + + dstvalue = bios_rd32(bios, dstreg) & dstmask; + + bios_wr32(bios, dstreg, dstvalue | srcvalue); + + return 22; +} + +static int +init_zm_index_io(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_ZM_INDEX_IO opcode: 0x62 ('b') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): CRTC port + * offset + 3 (8 bit): CRTC index + * offset + 4 (8 bit): data + * + * Write "data" to index "CRTC index" of "CRTC port" + */ + uint16_t crtcport = ROM16(bios->data[offset + 1]); + uint8_t crtcindex = bios->data[offset + 3]; + uint8_t data = bios->data[offset + 4]; + + if (!iexec->execute) + return 5; + + bios_idxprt_wr(bios, crtcport, crtcindex, data); + + return 5; +} + +static inline void +bios_md32(struct nvbios *bios, uint32_t reg, + uint32_t mask, uint32_t val) +{ + bios_wr32(bios, reg, (bios_rd32(bios, reg) & ~mask) | val); +} + +static uint32_t +peek_fb(struct drm_device *dev, struct io_mapping *fb, + uint32_t off) +{ + uint32_t val = 0; + + if (off < pci_resource_len(dev->pdev, 1)) { + uint8_t __iomem *p = + io_mapping_map_atomic_wc(fb, off & PAGE_MASK); + + val = ioread32(p + (off & ~PAGE_MASK)); + + io_mapping_unmap_atomic(p); + } + + return val; +} + +static void +poke_fb(struct drm_device *dev, struct io_mapping *fb, + uint32_t off, uint32_t val) +{ + if (off < pci_resource_len(dev->pdev, 1)) { + uint8_t __iomem *p = + io_mapping_map_atomic_wc(fb, off & PAGE_MASK); + + iowrite32(val, p + (off & ~PAGE_MASK)); + wmb(); + + io_mapping_unmap_atomic(p); + } +} + +static inline bool +read_back_fb(struct drm_device *dev, struct io_mapping *fb, + uint32_t off, uint32_t val) +{ + poke_fb(dev, fb, off, val); + return val == peek_fb(dev, fb, off); +} + +static int +nv04_init_compute_mem(struct nvbios *bios) +{ + struct drm_device *dev = bios->dev; + uint32_t patt = 0xdeadbeef; + struct io_mapping *fb; + int i; + + /* Map the framebuffer aperture */ + fb = io_mapping_create_wc(pci_resource_start(dev->pdev, 1), + pci_resource_len(dev->pdev, 1)); + if (!fb) + return -ENOMEM; + + /* Sequencer and refresh off */ + NVWriteVgaSeq(dev, 0, 1, NVReadVgaSeq(dev, 0, 1) | 0x20); + bios_md32(bios, NV04_PFB_DEBUG_0, 0, NV04_PFB_DEBUG_0_REFRESH_OFF); + + bios_md32(bios, NV04_PFB_BOOT_0, ~0, + NV04_PFB_BOOT_0_RAM_AMOUNT_16MB | + NV04_PFB_BOOT_0_RAM_WIDTH_128 | + NV04_PFB_BOOT_0_RAM_TYPE_SGRAM_16MBIT); + + for (i = 0; i < 4; i++) + poke_fb(dev, fb, 4 * i, patt); + + poke_fb(dev, fb, 0x400000, patt + 1); + + if (peek_fb(dev, fb, 0) == patt + 1) { + bios_md32(bios, NV04_PFB_BOOT_0, NV04_PFB_BOOT_0_RAM_TYPE, + NV04_PFB_BOOT_0_RAM_TYPE_SDRAM_16MBIT); + bios_md32(bios, NV04_PFB_DEBUG_0, + NV04_PFB_DEBUG_0_REFRESH_OFF, 0); + + for (i = 0; i < 4; i++) + poke_fb(dev, fb, 4 * i, patt); + + if ((peek_fb(dev, fb, 0xc) & 0xffff) != (patt & 0xffff)) + bios_md32(bios, NV04_PFB_BOOT_0, + NV04_PFB_BOOT_0_RAM_WIDTH_128 | + NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_8MB); + + } else if ((peek_fb(dev, fb, 0xc) & 0xffff0000) != + (patt & 0xffff0000)) { + bios_md32(bios, NV04_PFB_BOOT_0, + NV04_PFB_BOOT_0_RAM_WIDTH_128 | + NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_4MB); + + } else if (peek_fb(dev, fb, 0) != patt) { + if (read_back_fb(dev, fb, 0x800000, patt)) + bios_md32(bios, NV04_PFB_BOOT_0, + NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_8MB); + else + bios_md32(bios, NV04_PFB_BOOT_0, + NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_4MB); + + bios_md32(bios, NV04_PFB_BOOT_0, NV04_PFB_BOOT_0_RAM_TYPE, + NV04_PFB_BOOT_0_RAM_TYPE_SGRAM_8MBIT); + + } else if (!read_back_fb(dev, fb, 0x800000, patt)) { + bios_md32(bios, NV04_PFB_BOOT_0, NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_8MB); + + } + + /* Refresh on, sequencer on */ + bios_md32(bios, NV04_PFB_DEBUG_0, NV04_PFB_DEBUG_0_REFRESH_OFF, 0); + NVWriteVgaSeq(dev, 0, 1, NVReadVgaSeq(dev, 0, 1) & ~0x20); + + io_mapping_free(fb); + return 0; +} + +static const uint8_t * +nv05_memory_config(struct nvbios *bios) +{ + /* Defaults for BIOSes lacking a memory config table */ + static const uint8_t default_config_tab[][2] = { + { 0x24, 0x00 }, + { 0x28, 0x00 }, + { 0x24, 0x01 }, + { 0x1f, 0x00 }, + { 0x0f, 0x00 }, + { 0x17, 0x00 }, + { 0x06, 0x00 }, + { 0x00, 0x00 } + }; + int i = (bios_rd32(bios, NV_PEXTDEV_BOOT_0) & + NV_PEXTDEV_BOOT_0_RAMCFG) >> 2; + + if (bios->legacy.mem_init_tbl_ptr) + return &bios->data[bios->legacy.mem_init_tbl_ptr + 2 * i]; + else + return default_config_tab[i]; +} + +static int +nv05_init_compute_mem(struct nvbios *bios) +{ + struct drm_device *dev = bios->dev; + const uint8_t *ramcfg = nv05_memory_config(bios); + uint32_t patt = 0xdeadbeef; + struct io_mapping *fb; + int i, v; + + /* Map the framebuffer aperture */ + fb = io_mapping_create_wc(pci_resource_start(dev->pdev, 1), + pci_resource_len(dev->pdev, 1)); + if (!fb) + return -ENOMEM; + + /* Sequencer off */ + NVWriteVgaSeq(dev, 0, 1, NVReadVgaSeq(dev, 0, 1) | 0x20); + + if (bios_rd32(bios, NV04_PFB_BOOT_0) & NV04_PFB_BOOT_0_UMA_ENABLE) + goto out; + + bios_md32(bios, NV04_PFB_DEBUG_0, NV04_PFB_DEBUG_0_REFRESH_OFF, 0); + + /* If present load the hardcoded scrambling table */ + if (bios->legacy.mem_init_tbl_ptr) { + uint32_t *scramble_tab = (uint32_t *)&bios->data[ + bios->legacy.mem_init_tbl_ptr + 0x10]; + + for (i = 0; i < 8; i++) + bios_wr32(bios, NV04_PFB_SCRAMBLE(i), + ROM32(scramble_tab[i])); + } + + /* Set memory type/width/length defaults depending on the straps */ + bios_md32(bios, NV04_PFB_BOOT_0, 0x3f, ramcfg[0]); + + if (ramcfg[1] & 0x80) + bios_md32(bios, NV04_PFB_CFG0, 0, NV04_PFB_CFG0_SCRAMBLE); + + bios_md32(bios, NV04_PFB_CFG1, 0x700001, (ramcfg[1] & 1) << 20); + bios_md32(bios, NV04_PFB_CFG1, 0, 1); + + /* Probe memory bus width */ + for (i = 0; i < 4; i++) + poke_fb(dev, fb, 4 * i, patt); + + if (peek_fb(dev, fb, 0xc) != patt) + bios_md32(bios, NV04_PFB_BOOT_0, + NV04_PFB_BOOT_0_RAM_WIDTH_128, 0); + + /* Probe memory length */ + v = bios_rd32(bios, NV04_PFB_BOOT_0) & NV04_PFB_BOOT_0_RAM_AMOUNT; + + if (v == NV04_PFB_BOOT_0_RAM_AMOUNT_32MB && + (!read_back_fb(dev, fb, 0x1000000, ++patt) || + !read_back_fb(dev, fb, 0, ++patt))) + bios_md32(bios, NV04_PFB_BOOT_0, NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_16MB); + + if (v == NV04_PFB_BOOT_0_RAM_AMOUNT_16MB && + !read_back_fb(dev, fb, 0x800000, ++patt)) + bios_md32(bios, NV04_PFB_BOOT_0, NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_8MB); + + if (!read_back_fb(dev, fb, 0x400000, ++patt)) + bios_md32(bios, NV04_PFB_BOOT_0, NV04_PFB_BOOT_0_RAM_AMOUNT, + NV04_PFB_BOOT_0_RAM_AMOUNT_4MB); + +out: + /* Sequencer on */ + NVWriteVgaSeq(dev, 0, 1, NVReadVgaSeq(dev, 0, 1) & ~0x20); + + io_mapping_free(fb); + return 0; +} + +static int +nv10_init_compute_mem(struct nvbios *bios) +{ + struct drm_device *dev = bios->dev; + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + const int mem_width[] = { 0x10, 0x00, 0x20 }; + const int mem_width_count = (dev_priv->chipset >= 0x17 ? 3 : 2); + uint32_t patt = 0xdeadbeef; + struct io_mapping *fb; + int i, j, k; + + /* Map the framebuffer aperture */ + fb = io_mapping_create_wc(pci_resource_start(dev->pdev, 1), + pci_resource_len(dev->pdev, 1)); + if (!fb) + return -ENOMEM; + + bios_wr32(bios, NV10_PFB_REFCTRL, NV10_PFB_REFCTRL_VALID_1); + + /* Probe memory bus width */ + for (i = 0; i < mem_width_count; i++) { + bios_md32(bios, NV04_PFB_CFG0, 0x30, mem_width[i]); + + for (j = 0; j < 4; j++) { + for (k = 0; k < 4; k++) + poke_fb(dev, fb, 0x1c, 0); + + poke_fb(dev, fb, 0x1c, patt); + poke_fb(dev, fb, 0x3c, 0); + + if (peek_fb(dev, fb, 0x1c) == patt) + goto mem_width_found; + } + } + +mem_width_found: + patt <<= 1; + + /* Probe amount of installed memory */ + for (i = 0; i < 4; i++) { + int off = bios_rd32(bios, NV04_PFB_FIFO_DATA) - 0x100000; + + poke_fb(dev, fb, off, patt); + poke_fb(dev, fb, 0, 0); + + peek_fb(dev, fb, 0); + peek_fb(dev, fb, 0); + peek_fb(dev, fb, 0); + peek_fb(dev, fb, 0); + + if (peek_fb(dev, fb, off) == patt) + goto amount_found; + } + + /* IC missing - disable the upper half memory space. */ + bios_md32(bios, NV04_PFB_CFG0, 0x1000, 0); + +amount_found: + io_mapping_free(fb); + return 0; +} + +static int +nv20_init_compute_mem(struct nvbios *bios) +{ + struct drm_device *dev = bios->dev; + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + uint32_t mask = (dev_priv->chipset >= 0x25 ? 0x300 : 0x900); + uint32_t amount, off; + struct io_mapping *fb; + + /* Map the framebuffer aperture */ + fb = io_mapping_create_wc(pci_resource_start(dev->pdev, 1), + pci_resource_len(dev->pdev, 1)); + if (!fb) + return -ENOMEM; + + bios_wr32(bios, NV10_PFB_REFCTRL, NV10_PFB_REFCTRL_VALID_1); + + /* Allow full addressing */ + bios_md32(bios, NV04_PFB_CFG0, 0, mask); + + amount = bios_rd32(bios, NV04_PFB_FIFO_DATA); + for (off = amount; off > 0x2000000; off -= 0x2000000) + poke_fb(dev, fb, off - 4, off); + + amount = bios_rd32(bios, NV04_PFB_FIFO_DATA); + if (amount != peek_fb(dev, fb, amount - 4)) + /* IC missing - disable the upper half memory space. */ + bios_md32(bios, NV04_PFB_CFG0, mask, 0); + + io_mapping_free(fb); + return 0; +} + +static int +init_compute_mem(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_COMPUTE_MEM opcode: 0x63 ('c') + * + * offset (8 bit): opcode + * + * This opcode is meant to set the PFB memory config registers + * appropriately so that we can correctly calculate how much VRAM it + * has (on nv10 and better chipsets the amount of installed VRAM is + * subsequently reported in NV_PFB_CSTATUS (0x10020C)). + * + * The implementation of this opcode in general consists of several + * parts: + * + * 1) Determination of memory type and density. Only necessary for + * really old chipsets, the memory type reported by the strap bits + * (0x101000) is assumed to be accurate on nv05 and newer. + * + * 2) Determination of the memory bus width. Usually done by a cunning + * combination of writes to offsets 0x1c and 0x3c in the fb, and + * seeing whether the written values are read back correctly. + * + * Only necessary on nv0x-nv1x and nv34, on the other cards we can + * trust the straps. + * + * 3) Determination of how many of the card's RAM pads have ICs + * attached, usually done by a cunning combination of writes to an + * offset slightly less than the maximum memory reported by + * NV_PFB_CSTATUS, then seeing if the test pattern can be read back. + * + * This appears to be a NOP on IGPs and NV4x or newer chipsets, both io + * logs of the VBIOS and kmmio traces of the binary driver POSTing the + * card show nothing being done for this opcode. Why is it still listed + * in the table?! + */ + + /* no iexec->execute check by design */ + + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + int ret; + + if (dev_priv->chipset >= 0x40 || + dev_priv->chipset == 0x1a || + dev_priv->chipset == 0x1f) + ret = 0; + else if (dev_priv->chipset >= 0x20 && + dev_priv->chipset != 0x34) + ret = nv20_init_compute_mem(bios); + else if (dev_priv->chipset >= 0x10) + ret = nv10_init_compute_mem(bios); + else if (dev_priv->chipset >= 0x5) + ret = nv05_init_compute_mem(bios); + else + ret = nv04_init_compute_mem(bios); + + if (ret) + return ret; + + return 1; +} + +static int +init_reset(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_RESET opcode: 0x65 ('e') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): register + * offset + 5 (32 bit): value1 + * offset + 9 (32 bit): value2 + * + * Assign "value1" to "register", then assign "value2" to "register" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint32_t value1 = ROM32(bios->data[offset + 5]); + uint32_t value2 = ROM32(bios->data[offset + 9]); + uint32_t pci_nv_19, pci_nv_20; + + /* no iexec->execute check by design */ + + pci_nv_19 = bios_rd32(bios, NV_PBUS_PCI_NV_19); + bios_wr32(bios, NV_PBUS_PCI_NV_19, pci_nv_19 & ~0xf00); + + bios_wr32(bios, reg, value1); + + udelay(10); + + bios_wr32(bios, reg, value2); + bios_wr32(bios, NV_PBUS_PCI_NV_19, pci_nv_19); + + pci_nv_20 = bios_rd32(bios, NV_PBUS_PCI_NV_20); + pci_nv_20 &= ~NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED; /* 0xfffffffe */ + bios_wr32(bios, NV_PBUS_PCI_NV_20, pci_nv_20); + + return 13; +} + +static int +init_configure_mem(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_CONFIGURE_MEM opcode: 0x66 ('f') + * + * offset (8 bit): opcode + * + * Equivalent to INIT_DONE on bios version 3 or greater. + * For early bios versions, sets up the memory registers, using values + * taken from the memory init table + */ + + /* no iexec->execute check by design */ + + uint16_t meminitoffs = bios->legacy.mem_init_tbl_ptr + MEM_INIT_SIZE * (bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_SCRATCH4__INDEX) >> 4); + uint16_t seqtbloffs = bios->legacy.sdr_seq_tbl_ptr, meminitdata = meminitoffs + 6; + uint32_t reg, data; + + if (bios->major_version > 2) + return 0; + + bios_idxprt_wr(bios, NV_VIO_SRX, NV_VIO_SR_CLOCK_INDEX, bios_idxprt_rd( + bios, NV_VIO_SRX, NV_VIO_SR_CLOCK_INDEX) | 0x20); + + if (bios->data[meminitoffs] & 1) + seqtbloffs = bios->legacy.ddr_seq_tbl_ptr; + + for (reg = ROM32(bios->data[seqtbloffs]); + reg != 0xffffffff; + reg = ROM32(bios->data[seqtbloffs += 4])) { + + switch (reg) { + case NV04_PFB_PRE: + data = NV04_PFB_PRE_CMD_PRECHARGE; + break; + case NV04_PFB_PAD: + data = NV04_PFB_PAD_CKE_NORMAL; + break; + case NV04_PFB_REF: + data = NV04_PFB_REF_CMD_REFRESH; + break; + default: + data = ROM32(bios->data[meminitdata]); + meminitdata += 4; + if (data == 0xffffffff) + continue; + } + + bios_wr32(bios, reg, data); + } + + return 1; +} + +static int +init_configure_clk(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_CONFIGURE_CLK opcode: 0x67 ('g') + * + * offset (8 bit): opcode + * + * Equivalent to INIT_DONE on bios version 3 or greater. + * For early bios versions, sets up the NVClk and MClk PLLs, using + * values taken from the memory init table + */ + + /* no iexec->execute check by design */ + + uint16_t meminitoffs = bios->legacy.mem_init_tbl_ptr + MEM_INIT_SIZE * (bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_SCRATCH4__INDEX) >> 4); + int clock; + + if (bios->major_version > 2) + return 0; + + clock = ROM16(bios->data[meminitoffs + 4]) * 10; + setPLL(bios, NV_PRAMDAC_NVPLL_COEFF, clock); + + clock = ROM16(bios->data[meminitoffs + 2]) * 10; + if (bios->data[meminitoffs] & 1) /* DDR */ + clock *= 2; + setPLL(bios, NV_PRAMDAC_MPLL_COEFF, clock); + + return 1; +} + +static int +init_configure_preinit(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_CONFIGURE_PREINIT opcode: 0x68 ('h') + * + * offset (8 bit): opcode + * + * Equivalent to INIT_DONE on bios version 3 or greater. + * For early bios versions, does early init, loading ram and crystal + * configuration from straps into CR3C + */ + + /* no iexec->execute check by design */ + + uint32_t straps = bios_rd32(bios, NV_PEXTDEV_BOOT_0); + uint8_t cr3c = ((straps << 2) & 0xf0) | (straps & 0x40) >> 6; + + if (bios->major_version > 2) + return 0; + + bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, + NV_CIO_CRE_SCRATCH4__INDEX, cr3c); + + return 1; +} + +static int +init_io(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_IO opcode: 0x69 ('i') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): CRTC port + * offset + 3 (8 bit): mask + * offset + 4 (8 bit): data + * + * Assign ((IOVAL("crtc port") & "mask") | "data") to "crtc port" + */ + + struct drm_nouveau_private *dev_priv = bios->dev->dev_private; + uint16_t crtcport = ROM16(bios->data[offset + 1]); + uint8_t mask = bios->data[offset + 3]; + uint8_t data = bios->data[offset + 4]; + + if (!iexec->execute) + return 5; + + BIOSLOG(bios, "0x%04X: Port: 0x%04X, Mask: 0x%02X, Data: 0x%02X\n", + offset, crtcport, mask, data); + + /* + * I have no idea what this does, but NVIDIA do this magic sequence + * in the places where this INIT_IO happens.. + */ + if (dev_priv->card_type >= NV_50 && crtcport == 0x3c3 && data == 1) { + int i; + + bios_wr32(bios, 0x614100, (bios_rd32( + bios, 0x614100) & 0x0fffffff) | 0x00800000); + + bios_wr32(bios, 0x00e18c, bios_rd32( + bios, 0x00e18c) | 0x00020000); + + bios_wr32(bios, 0x614900, (bios_rd32( + bios, 0x614900) & 0x0fffffff) | 0x00800000); + + bios_wr32(bios, 0x000200, bios_rd32( + bios, 0x000200) & ~0x40000000); + + mdelay(10); + + bios_wr32(bios, 0x00e18c, bios_rd32( + bios, 0x00e18c) & ~0x00020000); + + bios_wr32(bios, 0x000200, bios_rd32( + bios, 0x000200) | 0x40000000); + + bios_wr32(bios, 0x614100, 0x00800018); + bios_wr32(bios, 0x614900, 0x00800018); + + mdelay(10); + + bios_wr32(bios, 0x614100, 0x10000018); + bios_wr32(bios, 0x614900, 0x10000018); + + for (i = 0; i < 3; i++) + bios_wr32(bios, 0x614280 + (i*0x800), bios_rd32( + bios, 0x614280 + (i*0x800)) & 0xf0f0f0f0); + + for (i = 0; i < 2; i++) + bios_wr32(bios, 0x614300 + (i*0x800), bios_rd32( + bios, 0x614300 + (i*0x800)) & 0xfffff0f0); + + for (i = 0; i < 3; i++) + bios_wr32(bios, 0x614380 + (i*0x800), bios_rd32( + bios, 0x614380 + (i*0x800)) & 0xfffff0f0); + + for (i = 0; i < 2; i++) + bios_wr32(bios, 0x614200 + (i*0x800), bios_rd32( + bios, 0x614200 + (i*0x800)) & 0xfffffff0); + + for (i = 0; i < 2; i++) + bios_wr32(bios, 0x614108 + (i*0x800), bios_rd32( + bios, 0x614108 + (i*0x800)) & 0x0fffffff); + return 5; + } + + bios_port_wr(bios, crtcport, (bios_port_rd(bios, crtcport) & mask) | + data); + return 5; +} + +static int +init_sub(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_SUB opcode: 0x6B ('k') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): script number + * + * Execute script number "script number", as a subroutine + */ + + uint8_t sub = bios->data[offset + 1]; + + if (!iexec->execute) + return 2; + + BIOSLOG(bios, "0x%04X: Calling script %d\n", offset, sub); + + parse_init_table(bios, + ROM16(bios->data[bios->init_script_tbls_ptr + sub * 2]), + iexec); + + BIOSLOG(bios, "0x%04X: End of script %d\n", offset, sub); + + return 2; +} + +static int +init_ram_condition(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_RAM_CONDITION opcode: 0x6D ('m') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): mask + * offset + 2 (8 bit): cmpval + * + * Test if (NV04_PFB_BOOT_0 & "mask") equals "cmpval". + * If condition not met skip subsequent opcodes until condition is + * inverted (INIT_NOT), or we hit INIT_RESUME + */ + + uint8_t mask = bios->data[offset + 1]; + uint8_t cmpval = bios->data[offset + 2]; + uint8_t data; + + if (!iexec->execute) + return 3; + + data = bios_rd32(bios, NV04_PFB_BOOT_0) & mask; + + BIOSLOG(bios, "0x%04X: Checking if 0x%08X equals 0x%08X\n", + offset, data, cmpval); + + if (data == cmpval) + BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset); + else { + BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset); + iexec->execute = false; + } + + return 3; +} + +static int +init_nv_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_NV_REG opcode: 0x6E ('n') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): register + * offset + 5 (32 bit): mask + * offset + 9 (32 bit): data + * + * Assign ((REGVAL("register") & "mask") | "data") to "register" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint32_t mask = ROM32(bios->data[offset + 5]); + uint32_t data = ROM32(bios->data[offset + 9]); + + if (!iexec->execute) + return 13; + + BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Mask: 0x%08X, Data: 0x%08X\n", + offset, reg, mask, data); + + bios_wr32(bios, reg, (bios_rd32(bios, reg) & mask) | data); + + return 13; +} + +static int +init_macro(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_MACRO opcode: 0x6F ('o') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): macro number + * + * Look up macro index "macro number" in the macro index table. + * The macro index table entry has 1 byte for the index in the macro + * table, and 1 byte for the number of times to repeat the macro. + * The macro table entry has 4 bytes for the register address and + * 4 bytes for the value to write to that register + */ + + uint8_t macro_index_tbl_idx = bios->data[offset + 1]; + uint16_t tmp = bios->macro_index_tbl_ptr + (macro_index_tbl_idx * MACRO_INDEX_SIZE); + uint8_t macro_tbl_idx = bios->data[tmp]; + uint8_t count = bios->data[tmp + 1]; + uint32_t reg, data; + int i; + + if (!iexec->execute) + return 2; + + BIOSLOG(bios, "0x%04X: Macro: 0x%02X, MacroTableIndex: 0x%02X, " + "Count: 0x%02X\n", + offset, macro_index_tbl_idx, macro_tbl_idx, count); + + for (i = 0; i < count; i++) { + uint16_t macroentryptr = bios->macro_tbl_ptr + (macro_tbl_idx + i) * MACRO_SIZE; + + reg = ROM32(bios->data[macroentryptr]); + data = ROM32(bios->data[macroentryptr + 4]); + + bios_wr32(bios, reg, data); + } + + return 2; +} + +static int +init_done(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_DONE opcode: 0x71 ('q') + * + * offset (8 bit): opcode + * + * End the current script + */ + + /* mild retval abuse to stop parsing this table */ + return 0; +} + +static int +init_resume(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_RESUME opcode: 0x72 ('r') + * + * offset (8 bit): opcode + * + * End the current execute / no-execute condition + */ + + if (iexec->execute) + return 1; + + iexec->execute = true; + BIOSLOG(bios, "0x%04X: ---- Executing following commands ----\n", offset); + + return 1; +} + +static int +init_time(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_TIME opcode: 0x74 ('t') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): time + * + * Sleep for "time" microseconds. + */ + + unsigned time = ROM16(bios->data[offset + 1]); + + if (!iexec->execute) + return 3; + + BIOSLOG(bios, "0x%04X: Sleeping for 0x%04X microseconds\n", + offset, time); + + if (time < 1000) + udelay(time); + else + mdelay((time + 900) / 1000); + + return 3; +} + +static int +init_condition(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_CONDITION opcode: 0x75 ('u') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): condition number + * + * Check condition "condition number" in the condition table. + * If condition not met skip subsequent opcodes until condition is + * inverted (INIT_NOT), or we hit INIT_RESUME + */ + + uint8_t cond = bios->data[offset + 1]; + + if (!iexec->execute) + return 2; + + BIOSLOG(bios, "0x%04X: Condition: 0x%02X\n", offset, cond); + + if (bios_condition_met(bios, offset, cond)) + BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset); + else { + BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset); + iexec->execute = false; + } + + return 2; +} + +static int +init_io_condition(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_IO_CONDITION opcode: 0x76 + * + * offset (8 bit): opcode + * offset + 1 (8 bit): condition number + * + * Check condition "condition number" in the io condition table. + * If condition not met skip subsequent opcodes until condition is + * inverted (INIT_NOT), or we hit INIT_RESUME + */ + + uint8_t cond = bios->data[offset + 1]; + + if (!iexec->execute) + return 2; + + BIOSLOG(bios, "0x%04X: IO condition: 0x%02X\n", offset, cond); + + if (io_condition_met(bios, offset, cond)) + BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset); + else { + BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset); + iexec->execute = false; + } + + return 2; +} + +static int +init_index_io(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_INDEX_IO opcode: 0x78 ('x') + * + * offset (8 bit): opcode + * offset + 1 (16 bit): CRTC port + * offset + 3 (8 bit): CRTC index + * offset + 4 (8 bit): mask + * offset + 5 (8 bit): data + * + * Read value at index "CRTC index" on "CRTC port", AND with "mask", + * OR with "data", write-back + */ + + uint16_t crtcport = ROM16(bios->data[offset + 1]); + uint8_t crtcindex = bios->data[offset + 3]; + uint8_t mask = bios->data[offset + 4]; + uint8_t data = bios->data[offset + 5]; + uint8_t value; + + if (!iexec->execute) + return 6; + + BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, " + "Data: 0x%02X\n", + offset, crtcport, crtcindex, mask, data); + + value = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) | data; + bios_idxprt_wr(bios, crtcport, crtcindex, value); + + return 6; +} + +static int +init_pll(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_PLL opcode: 0x79 ('y') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): register + * offset + 5 (16 bit): freq + * + * Set PLL register "register" to coefficients for frequency (10kHz) + * "freq" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint16_t freq = ROM16(bios->data[offset + 5]); + + if (!iexec->execute) + return 7; + + BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Freq: %d0kHz\n", offset, reg, freq); + + setPLL(bios, reg, freq * 10); + + return 7; +} + +static int +init_zm_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_ZM_REG opcode: 0x7A ('z') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): register + * offset + 5 (32 bit): value + * + * Assign "value" to "register" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint32_t value = ROM32(bios->data[offset + 5]); + + if (!iexec->execute) + return 9; + + if (reg == 0x000200) + value |= 1; + + bios_wr32(bios, reg, value); + + return 9; +} + +static int +init_ram_restrict_pll(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_RAM_RESTRICT_PLL opcode: 0x87 ('') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): PLL type + * offset + 2 (32 bit): frequency 0 + * + * Uses the RAMCFG strap of PEXTDEV_BOOT as an index into the table at + * ram_restrict_table_ptr. The value read from there is used to select + * a frequency from the table starting at 'frequency 0' to be + * programmed into the PLL corresponding to 'type'. + * + * The PLL limits table on cards using this opcode has a mapping of + * 'type' to the relevant registers. + */ + + struct drm_device *dev = bios->dev; + uint32_t strap = (bios_rd32(bios, NV_PEXTDEV_BOOT_0) & 0x0000003c) >> 2; + uint8_t index = bios->data[bios->ram_restrict_tbl_ptr + strap]; + uint8_t type = bios->data[offset + 1]; + uint32_t freq = ROM32(bios->data[offset + 2 + (index * 4)]); + uint8_t *pll_limits = &bios->data[bios->pll_limit_tbl_ptr], *entry; + int len = 2 + bios->ram_restrict_group_count * 4; + int i; + + if (!iexec->execute) + return len; + + if (!bios->pll_limit_tbl_ptr || (pll_limits[0] & 0xf0) != 0x30) { + NV_ERROR(dev, "PLL limits table not version 3.x\n"); + return len; /* deliberate, allow default clocks to remain */ + } + + entry = pll_limits + pll_limits[1]; + for (i = 0; i < pll_limits[3]; i++, entry += pll_limits[2]) { + if (entry[0] == type) { + uint32_t reg = ROM32(entry[3]); + + BIOSLOG(bios, "0x%04X: " + "Type %02x Reg 0x%08x Freq %dKHz\n", + offset, type, reg, freq); + + setPLL(bios, reg, freq); + return len; + } + } + + NV_ERROR(dev, "PLL type 0x%02x not found in PLL limits table", type); + return len; +} + +static int +init_8c(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_8C opcode: 0x8C ('') + * + * NOP so far.... + * + */ + + return 1; +} + +static int +init_8d(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_8D opcode: 0x8D ('') + * + * NOP so far.... + * + */ + + return 1; +} + +static int +init_gpio(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_GPIO opcode: 0x8E ('') + * + * offset (8 bit): opcode + * + * Loop over all entries in the DCB GPIO table, and initialise + * each GPIO according to various values listed in each entry + */ + + if (iexec->execute && bios->execute) + nouveau_gpio_reset(bios->dev); + + return 1; +} + +static int +init_ram_restrict_zm_reg_group(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_RAM_RESTRICT_ZM_REG_GROUP opcode: 0x8F ('') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): reg + * offset + 5 (8 bit): regincrement + * offset + 6 (8 bit): count + * offset + 7 (32 bit): value 1,1 + * ... + * + * Use the RAMCFG strap of PEXTDEV_BOOT as an index into the table at + * ram_restrict_table_ptr. The value read from here is 'n', and + * "value 1,n" gets written to "reg". This repeats "count" times and on + * each iteration 'm', "reg" increases by "regincrement" and + * "value m,n" is used. The extent of n is limited by a number read + * from the 'M' BIT table, herein called "blocklen" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint8_t regincrement = bios->data[offset + 5]; + uint8_t count = bios->data[offset + 6]; + uint32_t strap_ramcfg, data; + /* previously set by 'M' BIT table */ + uint16_t blocklen = bios->ram_restrict_group_count * 4; + int len = 7 + count * blocklen; + uint8_t index; + int i; + + /* critical! to know the length of the opcode */; + if (!blocklen) { + NV_ERROR(bios->dev, + "0x%04X: Zero block length - has the M table " + "been parsed?\n", offset); + return -EINVAL; + } + + if (!iexec->execute) + return len; + + strap_ramcfg = (bios_rd32(bios, NV_PEXTDEV_BOOT_0) >> 2) & 0xf; + index = bios->data[bios->ram_restrict_tbl_ptr + strap_ramcfg]; + + BIOSLOG(bios, "0x%04X: Reg: 0x%08X, RegIncrement: 0x%02X, " + "Count: 0x%02X, StrapRamCfg: 0x%02X, Index: 0x%02X\n", + offset, reg, regincrement, count, strap_ramcfg, index); + + for (i = 0; i < count; i++) { + data = ROM32(bios->data[offset + 7 + index * 4 + blocklen * i]); + + bios_wr32(bios, reg, data); + + reg += regincrement; + } + + return len; +} + +static int +init_copy_zm_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_COPY_ZM_REG opcode: 0x90 ('') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): src reg + * offset + 5 (32 bit): dst reg + * + * Put contents of "src reg" into "dst reg" + */ + + uint32_t srcreg = ROM32(bios->data[offset + 1]); + uint32_t dstreg = ROM32(bios->data[offset + 5]); + + if (!iexec->execute) + return 9; + + bios_wr32(bios, dstreg, bios_rd32(bios, srcreg)); + + return 9; +} + +static int +init_zm_reg_group_addr_latched(struct nvbios *bios, uint16_t offset, + struct init_exec *iexec) +{ + /* + * INIT_ZM_REG_GROUP_ADDRESS_LATCHED opcode: 0x91 ('') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): dst reg + * offset + 5 (8 bit): count + * offset + 6 (32 bit): data 1 + * ... + * + * For each of "count" values write "data n" to "dst reg" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint8_t count = bios->data[offset + 5]; + int len = 6 + count * 4; + int i; + + if (!iexec->execute) + return len; + + for (i = 0; i < count; i++) { + uint32_t data = ROM32(bios->data[offset + 6 + 4 * i]); + bios_wr32(bios, reg, data); + } + + return len; +} + +static int +init_reserved(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_RESERVED opcode: 0x92 ('') + * + * offset (8 bit): opcode + * + * Seemingly does nothing + */ + + return 1; +} + +static int +init_96(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_96 opcode: 0x96 ('') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): sreg + * offset + 5 (8 bit): sshift + * offset + 6 (8 bit): smask + * offset + 7 (8 bit): index + * offset + 8 (32 bit): reg + * offset + 12 (32 bit): mask + * offset + 16 (8 bit): shift + * + */ + + uint16_t xlatptr = bios->init96_tbl_ptr + (bios->data[offset + 7] * 2); + uint32_t reg = ROM32(bios->data[offset + 8]); + uint32_t mask = ROM32(bios->data[offset + 12]); + uint32_t val; + + val = bios_rd32(bios, ROM32(bios->data[offset + 1])); + if (bios->data[offset + 5] < 0x80) + val >>= bios->data[offset + 5]; + else + val <<= (0x100 - bios->data[offset + 5]); + val &= bios->data[offset + 6]; + + val = bios->data[ROM16(bios->data[xlatptr]) + val]; + val <<= bios->data[offset + 16]; + + if (!iexec->execute) + return 17; + + bios_wr32(bios, reg, (bios_rd32(bios, reg) & mask) | val); + return 17; +} + +static int +init_97(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_97 opcode: 0x97 ('') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): register + * offset + 5 (32 bit): mask + * offset + 9 (32 bit): value + * + * Adds "value" to "register" preserving the fields specified + * by "mask" + */ + + uint32_t reg = ROM32(bios->data[offset + 1]); + uint32_t mask = ROM32(bios->data[offset + 5]); + uint32_t add = ROM32(bios->data[offset + 9]); + uint32_t val; + + val = bios_rd32(bios, reg); + val = (val & mask) | ((val + add) & ~mask); + + if (!iexec->execute) + return 13; + + bios_wr32(bios, reg, val); + return 13; +} + +static int +init_auxch(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_AUXCH opcode: 0x98 ('') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): address + * offset + 5 (8 bit): count + * offset + 6 (8 bit): mask 0 + * offset + 7 (8 bit): data 0 + * ... + * + */ + + struct drm_device *dev = bios->dev; + struct nouveau_i2c_chan *auxch; + uint32_t addr = ROM32(bios->data[offset + 1]); + uint8_t count = bios->data[offset + 5]; + int len = 6 + count * 2; + int ret, i; + + if (!bios->display.output) { + NV_ERROR(dev, "INIT_AUXCH: no active output\n"); + return len; + } + + auxch = init_i2c_device_find(dev, bios->display.output->i2c_index); + if (!auxch) { + NV_ERROR(dev, "INIT_AUXCH: couldn't get auxch %d\n", + bios->display.output->i2c_index); + return len; + } + + if (!iexec->execute) + return len; + + offset += 6; + for (i = 0; i < count; i++, offset += 2) { + uint8_t data; + + ret = nouveau_dp_auxch(auxch, 9, addr, &data, 1); + if (ret) { + NV_ERROR(dev, "INIT_AUXCH: rd auxch fail %d\n", ret); + return len; + } + + data &= bios->data[offset + 0]; + data |= bios->data[offset + 1]; + + ret = nouveau_dp_auxch(auxch, 8, addr, &data, 1); + if (ret) { + NV_ERROR(dev, "INIT_AUXCH: wr auxch fail %d\n", ret); + return len; + } + } + + return len; +} + +static int +init_zm_auxch(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_ZM_AUXCH opcode: 0x99 ('') + * + * offset (8 bit): opcode + * offset + 1 (32 bit): address + * offset + 5 (8 bit): count + * offset + 6 (8 bit): data 0 + * ... + * + */ + + struct drm_device *dev = bios->dev; + struct nouveau_i2c_chan *auxch; + uint32_t addr = ROM32(bios->data[offset + 1]); + uint8_t count = bios->data[offset + 5]; + int len = 6 + count; + int ret, i; + + if (!bios->display.output) { + NV_ERROR(dev, "INIT_ZM_AUXCH: no active output\n"); + return len; + } + + auxch = init_i2c_device_find(dev, bios->display.output->i2c_index); + if (!auxch) { + NV_ERROR(dev, "INIT_ZM_AUXCH: couldn't get auxch %d\n", + bios->display.output->i2c_index); + return len; + } + + if (!iexec->execute) + return len; + + offset += 6; + for (i = 0; i < count; i++, offset++) { + ret = nouveau_dp_auxch(auxch, 8, addr, &bios->data[offset], 1); + if (ret) { + NV_ERROR(dev, "INIT_ZM_AUXCH: wr auxch fail %d\n", ret); + return len; + } + } + + return len; +} + +static int +init_i2c_long_if(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * INIT_I2C_LONG_IF opcode: 0x9A ('') + * + * offset (8 bit): opcode + * offset + 1 (8 bit): DCB I2C table entry index + * offset + 2 (8 bit): I2C slave address + * offset + 3 (16 bit): I2C register + * offset + 5 (8 bit): mask + * offset + 6 (8 bit): data + * + * Read the register given by "I2C register" on the device addressed + * by "I2C slave address" on the I2C bus given by "DCB I2C table + * entry index". Compare the result AND "mask" to "data". + * If they're not equal, skip subsequent opcodes until condition is + * inverted (INIT_NOT), or we hit INIT_RESUME + */ + + uint8_t i2c_index = bios->data[offset + 1]; + uint8_t i2c_address = bios->data[offset + 2] >> 1; + uint8_t reglo = bios->data[offset + 3]; + uint8_t reghi = bios->data[offset + 4]; + uint8_t mask = bios->data[offset + 5]; + uint8_t data = bios->data[offset + 6]; + struct nouveau_i2c_chan *chan; + uint8_t buf0[2] = { reghi, reglo }; + uint8_t buf1[1]; + struct i2c_msg msg[2] = { + { i2c_address, 0, 1, buf0 }, + { i2c_address, I2C_M_RD, 1, buf1 }, + }; + int ret; + + /* no execute check by design */ + + BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X\n", + offset, i2c_index, i2c_address); + + chan = init_i2c_device_find(bios->dev, i2c_index); + if (!chan) + return -ENODEV; + + + ret = i2c_transfer(&chan->adapter, msg, 2); + if (ret < 0) { + BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X:0x%02X, Value: [no device], " + "Mask: 0x%02X, Data: 0x%02X\n", + offset, reghi, reglo, mask, data); + iexec->execute = 0; + return 7; + } + + BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X:0x%02X, Value: 0x%02X, " + "Mask: 0x%02X, Data: 0x%02X\n", + offset, reghi, reglo, buf1[0], mask, data); + + iexec->execute = ((buf1[0] & mask) == data); + + return 7; +} + +static struct init_tbl_entry itbl_entry[] = { + /* command name , id , length , offset , mult , command handler */ + /* INIT_PROG (0x31, 15, 10, 4) removed due to no example of use */ + { "INIT_IO_RESTRICT_PROG" , 0x32, init_io_restrict_prog }, + { "INIT_REPEAT" , 0x33, init_repeat }, + { "INIT_IO_RESTRICT_PLL" , 0x34, init_io_restrict_pll }, + { "INIT_END_REPEAT" , 0x36, init_end_repeat }, + { "INIT_COPY" , 0x37, init_copy }, + { "INIT_NOT" , 0x38, init_not }, + { "INIT_IO_FLAG_CONDITION" , 0x39, init_io_flag_condition }, + { "INIT_DP_CONDITION" , 0x3A, init_dp_condition }, + { "INIT_OP_3B" , 0x3B, init_op_3b }, + { "INIT_OP_3C" , 0x3C, init_op_3c }, + { "INIT_INDEX_ADDRESS_LATCHED" , 0x49, init_idx_addr_latched }, + { "INIT_IO_RESTRICT_PLL2" , 0x4A, init_io_restrict_pll2 }, + { "INIT_PLL2" , 0x4B, init_pll2 }, + { "INIT_I2C_BYTE" , 0x4C, init_i2c_byte }, + { "INIT_ZM_I2C_BYTE" , 0x4D, init_zm_i2c_byte }, + { "INIT_ZM_I2C" , 0x4E, init_zm_i2c }, + { "INIT_TMDS" , 0x4F, init_tmds }, + { "INIT_ZM_TMDS_GROUP" , 0x50, init_zm_tmds_group }, + { "INIT_CR_INDEX_ADDRESS_LATCHED" , 0x51, init_cr_idx_adr_latch }, + { "INIT_CR" , 0x52, init_cr }, + { "INIT_ZM_CR" , 0x53, init_zm_cr }, + { "INIT_ZM_CR_GROUP" , 0x54, init_zm_cr_group }, + { "INIT_CONDITION_TIME" , 0x56, init_condition_time }, + { "INIT_LTIME" , 0x57, init_ltime }, + { "INIT_ZM_REG_SEQUENCE" , 0x58, init_zm_reg_sequence }, + /* INIT_INDIRECT_REG (0x5A, 7, 0, 0) removed due to no example of use */ + { "INIT_SUB_DIRECT" , 0x5B, init_sub_direct }, + { "INIT_JUMP" , 0x5C, init_jump }, + { "INIT_I2C_IF" , 0x5E, init_i2c_if }, + { "INIT_COPY_NV_REG" , 0x5F, init_copy_nv_reg }, + { "INIT_ZM_INDEX_IO" , 0x62, init_zm_index_io }, + { "INIT_COMPUTE_MEM" , 0x63, init_compute_mem }, + { "INIT_RESET" , 0x65, init_reset }, + { "INIT_CONFIGURE_MEM" , 0x66, init_configure_mem }, + { "INIT_CONFIGURE_CLK" , 0x67, init_configure_clk }, + { "INIT_CONFIGURE_PREINIT" , 0x68, init_configure_preinit }, + { "INIT_IO" , 0x69, init_io }, + { "INIT_SUB" , 0x6B, init_sub }, + { "INIT_RAM_CONDITION" , 0x6D, init_ram_condition }, + { "INIT_NV_REG" , 0x6E, init_nv_reg }, + { "INIT_MACRO" , 0x6F, init_macro }, + { "INIT_DONE" , 0x71, init_done }, + { "INIT_RESUME" , 0x72, init_resume }, + /* INIT_RAM_CONDITION2 (0x73, 9, 0, 0) removed due to no example of use */ + { "INIT_TIME" , 0x74, init_time }, + { "INIT_CONDITION" , 0x75, init_condition }, + { "INIT_IO_CONDITION" , 0x76, init_io_condition }, + { "INIT_INDEX_IO" , 0x78, init_index_io }, + { "INIT_PLL" , 0x79, init_pll }, + { "INIT_ZM_REG" , 0x7A, init_zm_reg }, + { "INIT_RAM_RESTRICT_PLL" , 0x87, init_ram_restrict_pll }, + { "INIT_8C" , 0x8C, init_8c }, + { "INIT_8D" , 0x8D, init_8d }, + { "INIT_GPIO" , 0x8E, init_gpio }, + { "INIT_RAM_RESTRICT_ZM_REG_GROUP" , 0x8F, init_ram_restrict_zm_reg_group }, + { "INIT_COPY_ZM_REG" , 0x90, init_copy_zm_reg }, + { "INIT_ZM_REG_GROUP_ADDRESS_LATCHED" , 0x91, init_zm_reg_group_addr_latched }, + { "INIT_RESERVED" , 0x92, init_reserved }, + { "INIT_96" , 0x96, init_96 }, + { "INIT_97" , 0x97, init_97 }, + { "INIT_AUXCH" , 0x98, init_auxch }, + { "INIT_ZM_AUXCH" , 0x99, init_zm_auxch }, + { "INIT_I2C_LONG_IF" , 0x9A, init_i2c_long_if }, + { NULL , 0 , NULL } +}; + +#define MAX_TABLE_OPS 1000 + +static int +parse_init_table(struct nvbios *bios, uint16_t offset, struct init_exec *iexec) +{ + /* + * Parses all commands in an init table. + * + * We start out executing all commands found in the init table. Some + * opcodes may change the status of iexec->execute to SKIP, which will + * cause the following opcodes to perform no operation until the value + * is changed back to EXECUTE. + */ + + int count = 0, i, ret; + uint8_t id; + + /* catch NULL script pointers */ + if (offset == 0) + return 0; + + /* + * Loop until INIT_DONE causes us to break out of the loop + * (or until offset > bios length just in case... ) + * (and no more than MAX_TABLE_OPS iterations, just in case... ) + */ + while ((offset < bios->length) && (count++ < MAX_TABLE_OPS)) { + id = bios->data[offset]; + + /* Find matching id in itbl_entry */ + for (i = 0; itbl_entry[i].name && (itbl_entry[i].id != id); i++) + ; + + if (!itbl_entry[i].name) { + NV_ERROR(bios->dev, + "0x%04X: Init table command not found: " + "0x%02X\n", offset, id); + return -ENOENT; + } + + BIOSLOG(bios, "0x%04X: [ (0x%02X) - %s ]\n", offset, + itbl_entry[i].id, itbl_entry[i].name); + + /* execute eventual command handler */ + ret = (*itbl_entry[i].handler)(bios, offset, iexec); + if (ret < 0) { + NV_ERROR(bios->dev, "0x%04X: Failed parsing init " + "table opcode: %s %d\n", offset, + itbl_entry[i].name, ret); + } + + if (ret <= 0) + break; + + /* + * Add the offset of the current command including all data + * of that command. The offset will then be pointing on the + * next op code. + */ + offset += ret; + } + + if (offset >= bios->length) + NV_WARN(bios->dev, + "Offset 0x%04X greater than known bios image length. " + "Corrupt image?\n", offset); + if (count >= MAX_TABLE_OPS) + NV_WARN(bios->dev, + "More than %d opcodes to a table is unlikely, " + "is the bios image corrupt?\n", MAX_TABLE_OPS); + + return 0; +} + +static void +parse_init_tables(struct nvbios *bios) +{ + /* Loops and calls parse_init_table() for each present table. */ + + int i = 0; + uint16_t table; + struct init_exec iexec = {true, false}; + + if (bios->old_style_init) { + if (bios->init_script_tbls_ptr) + parse_init_table(bios, bios->init_script_tbls_ptr, &iexec); + if (bios->extra_init_script_tbl_ptr) + parse_init_table(bios, bios->extra_init_script_tbl_ptr, &iexec); + + return; + } + + while ((table = ROM16(bios->data[bios->init_script_tbls_ptr + i]))) { + NV_INFO(bios->dev, + "Parsing VBIOS init table %d at offset 0x%04X\n", + i / 2, table); + BIOSLOG(bios, "0x%04X: ------ Executing following commands ------\n", table); + + parse_init_table(bios, table, &iexec); + i += 2; + } +} + +static uint16_t clkcmptable(struct nvbios *bios, uint16_t clktable, int pxclk) +{ + int compare_record_len, i = 0; + uint16_t compareclk, scriptptr = 0; + + if (bios->major_version < 5) /* pre BIT */ + compare_record_len = 3; + else + compare_record_len = 4; + + do { + compareclk = ROM16(bios->data[clktable + compare_record_len * i]); + if (pxclk >= compareclk * 10) { + if (bios->major_version < 5) { + uint8_t tmdssub = bios->data[clktable + 2 + compare_record_len * i]; + scriptptr = ROM16(bios->data[bios->init_script_tbls_ptr + tmdssub * 2]); + } else + scriptptr = ROM16(bios->data[clktable + 2 + compare_record_len * i]); + break; + } + i++; + } while (compareclk); + + return scriptptr; +} + +static void +run_digital_op_script(struct drm_device *dev, uint16_t scriptptr, + struct dcb_entry *dcbent, int head, bool dl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct init_exec iexec = {true, false}; + + NV_TRACE(dev, "0x%04X: Parsing digital output script table\n", + scriptptr); + bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_44, + head ? NV_CIO_CRE_44_HEADB : NV_CIO_CRE_44_HEADA); + /* note: if dcb entries have been merged, index may be misleading */ + NVWriteVgaCrtc5758(dev, head, 0, dcbent->index); + parse_init_table(bios, scriptptr, &iexec); + + nv04_dfp_bind_head(dev, dcbent, head, dl); +} + +static int call_lvds_manufacturer_script(struct drm_device *dev, struct dcb_entry *dcbent, int head, enum LVDS_script script) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + uint8_t sub = bios->data[bios->fp.xlated_entry + script] + (bios->fp.link_c_increment && dcbent->or & OUTPUT_C ? 1 : 0); + uint16_t scriptofs = ROM16(bios->data[bios->init_script_tbls_ptr + sub * 2]); + + if (!bios->fp.xlated_entry || !sub || !scriptofs) + return -EINVAL; + + run_digital_op_script(dev, scriptofs, dcbent, head, bios->fp.dual_link); + + if (script == LVDS_PANEL_OFF) { + /* off-on delay in ms */ + mdelay(ROM16(bios->data[bios->fp.xlated_entry + 7])); + } +#ifdef __powerpc__ + /* Powerbook specific quirks */ + if (script == LVDS_RESET && + (dev->pci_device == 0x0179 || dev->pci_device == 0x0189 || + dev->pci_device == 0x0329)) + nv_write_tmds(dev, dcbent->or, 0, 0x02, 0x72); +#endif + + return 0; +} + +static int run_lvds_table(struct drm_device *dev, struct dcb_entry *dcbent, int head, enum LVDS_script script, int pxclk) +{ + /* + * The BIT LVDS table's header has the information to setup the + * necessary registers. Following the standard 4 byte header are: + * A bitmask byte and a dual-link transition pxclk value for use in + * selecting the init script when not using straps; 4 script pointers + * for panel power, selected by output and on/off; and 8 table pointers + * for panel init, the needed one determined by output, and bits in the + * conf byte. These tables are similar to the TMDS tables, consisting + * of a list of pxclks and script pointers. + */ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + unsigned int outputset = (dcbent->or == 4) ? 1 : 0; + uint16_t scriptptr = 0, clktable; + + /* + * For now we assume version 3.0 table - g80 support will need some + * changes + */ + + switch (script) { + case LVDS_INIT: + return -ENOSYS; + case LVDS_BACKLIGHT_ON: + case LVDS_PANEL_ON: + scriptptr = ROM16(bios->data[bios->fp.lvdsmanufacturerpointer + 7 + outputset * 2]); + break; + case LVDS_BACKLIGHT_OFF: + case LVDS_PANEL_OFF: + scriptptr = ROM16(bios->data[bios->fp.lvdsmanufacturerpointer + 11 + outputset * 2]); + break; + case LVDS_RESET: + clktable = bios->fp.lvdsmanufacturerpointer + 15; + if (dcbent->or == 4) + clktable += 8; + + if (dcbent->lvdsconf.use_straps_for_mode) { + if (bios->fp.dual_link) + clktable += 4; + if (bios->fp.if_is_24bit) + clktable += 2; + } else { + /* using EDID */ + int cmpval_24bit = (dcbent->or == 4) ? 4 : 1; + + if (bios->fp.dual_link) { + clktable += 4; + cmpval_24bit <<= 1; + } + + if (bios->fp.strapless_is_24bit & cmpval_24bit) + clktable += 2; + } + + clktable = ROM16(bios->data[clktable]); + if (!clktable) { + NV_ERROR(dev, "Pixel clock comparison table not found\n"); + return -ENOENT; + } + scriptptr = clkcmptable(bios, clktable, pxclk); + } + + if (!scriptptr) { + NV_ERROR(dev, "LVDS output init script not found\n"); + return -ENOENT; + } + run_digital_op_script(dev, scriptptr, dcbent, head, bios->fp.dual_link); + + return 0; +} + +int call_lvds_script(struct drm_device *dev, struct dcb_entry *dcbent, int head, enum LVDS_script script, int pxclk) +{ + /* + * LVDS operations are multiplexed in an effort to present a single API + * which works with two vastly differing underlying structures. + * This acts as the demux + */ + + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + uint8_t lvds_ver = bios->data[bios->fp.lvdsmanufacturerpointer]; + uint32_t sel_clk_binding, sel_clk; + int ret; + + if (bios->fp.last_script_invoc == (script << 1 | head) || !lvds_ver || + (lvds_ver >= 0x30 && script == LVDS_INIT)) + return 0; + + if (!bios->fp.lvds_init_run) { + bios->fp.lvds_init_run = true; + call_lvds_script(dev, dcbent, head, LVDS_INIT, pxclk); + } + + if (script == LVDS_PANEL_ON && bios->fp.reset_after_pclk_change) + call_lvds_script(dev, dcbent, head, LVDS_RESET, pxclk); + if (script == LVDS_RESET && bios->fp.power_off_for_reset) + call_lvds_script(dev, dcbent, head, LVDS_PANEL_OFF, pxclk); + + NV_TRACE(dev, "Calling LVDS script %d:\n", script); + + /* don't let script change pll->head binding */ + sel_clk_binding = bios_rd32(bios, NV_PRAMDAC_SEL_CLK) & 0x50000; + + if (lvds_ver < 0x30) + ret = call_lvds_manufacturer_script(dev, dcbent, head, script); + else + ret = run_lvds_table(dev, dcbent, head, script, pxclk); + + bios->fp.last_script_invoc = (script << 1 | head); + + sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK) & ~0x50000; + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, sel_clk | sel_clk_binding); + /* some scripts set a value in NV_PBUS_POWERCTRL_2 and break video overlay */ + nvWriteMC(dev, NV_PBUS_POWERCTRL_2, 0); + + return ret; +} + +struct lvdstableheader { + uint8_t lvds_ver, headerlen, recordlen; +}; + +static int parse_lvds_manufacturer_table_header(struct drm_device *dev, struct nvbios *bios, struct lvdstableheader *lth) +{ + /* + * BMP version (0xa) LVDS table has a simple header of version and + * record length. The BIT LVDS table has the typical BIT table header: + * version byte, header length byte, record length byte, and a byte for + * the maximum number of records that can be held in the table. + */ + + uint8_t lvds_ver, headerlen, recordlen; + + memset(lth, 0, sizeof(struct lvdstableheader)); + + if (bios->fp.lvdsmanufacturerpointer == 0x0) { + NV_ERROR(dev, "Pointer to LVDS manufacturer table invalid\n"); + return -EINVAL; + } + + lvds_ver = bios->data[bios->fp.lvdsmanufacturerpointer]; + + switch (lvds_ver) { + case 0x0a: /* pre NV40 */ + headerlen = 2; + recordlen = bios->data[bios->fp.lvdsmanufacturerpointer + 1]; + break; + case 0x30: /* NV4x */ + headerlen = bios->data[bios->fp.lvdsmanufacturerpointer + 1]; + if (headerlen < 0x1f) { + NV_ERROR(dev, "LVDS table header not understood\n"); + return -EINVAL; + } + recordlen = bios->data[bios->fp.lvdsmanufacturerpointer + 2]; + break; + case 0x40: /* G80/G90 */ + headerlen = bios->data[bios->fp.lvdsmanufacturerpointer + 1]; + if (headerlen < 0x7) { + NV_ERROR(dev, "LVDS table header not understood\n"); + return -EINVAL; + } + recordlen = bios->data[bios->fp.lvdsmanufacturerpointer + 2]; + break; + default: + NV_ERROR(dev, + "LVDS table revision %d.%d not currently supported\n", + lvds_ver >> 4, lvds_ver & 0xf); + return -ENOSYS; + } + + lth->lvds_ver = lvds_ver; + lth->headerlen = headerlen; + lth->recordlen = recordlen; + + return 0; +} + +static int +get_fp_strap(struct drm_device *dev, struct nvbios *bios) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* + * The fp strap is normally dictated by the "User Strap" in + * PEXTDEV_BOOT_0[20:16], but on BMP cards when bit 2 of the + * Internal_Flags struct at 0x48 is set, the user strap gets overriden + * by the PCI subsystem ID during POST, but not before the previous user + * strap has been committed to CR58 for CR57=0xf on head A, which may be + * read and used instead + */ + + if (bios->major_version < 5 && bios->data[0x48] & 0x4) + return NVReadVgaCrtc5758(dev, 0, 0xf) & 0xf; + + if (dev_priv->card_type >= NV_50) + return (bios_rd32(bios, NV_PEXTDEV_BOOT_0) >> 24) & 0xf; + else + return (bios_rd32(bios, NV_PEXTDEV_BOOT_0) >> 16) & 0xf; +} + +static int parse_fp_mode_table(struct drm_device *dev, struct nvbios *bios) +{ + uint8_t *fptable; + uint8_t fptable_ver, headerlen = 0, recordlen, fpentries = 0xf, fpindex; + int ret, ofs, fpstrapping; + struct lvdstableheader lth; + + if (bios->fp.fptablepointer == 0x0) { + /* Apple cards don't have the fp table; the laptops use DDC */ + /* The table is also missing on some x86 IGPs */ +#ifndef __powerpc__ + NV_ERROR(dev, "Pointer to flat panel table invalid\n"); +#endif + bios->digital_min_front_porch = 0x4b; + return 0; + } + + fptable = &bios->data[bios->fp.fptablepointer]; + fptable_ver = fptable[0]; + + switch (fptable_ver) { + /* + * BMP version 0x5.0x11 BIOSen have version 1 like tables, but no + * version field, and miss one of the spread spectrum/PWM bytes. + * This could affect early GF2Go parts (not seen any appropriate ROMs + * though). Here we assume that a version of 0x05 matches this case + * (combining with a BMP version check would be better), as the + * common case for the panel type field is 0x0005, and that is in + * fact what we are reading the first byte of. + */ + case 0x05: /* some NV10, 11, 15, 16 */ + recordlen = 42; + ofs = -1; + break; + case 0x10: /* some NV15/16, and NV11+ */ + recordlen = 44; + ofs = 0; + break; + case 0x20: /* NV40+ */ + headerlen = fptable[1]; + recordlen = fptable[2]; + fpentries = fptable[3]; + /* + * fptable[4] is the minimum + * RAMDAC_FP_HCRTC -> RAMDAC_FP_HSYNC_START gap + */ + bios->digital_min_front_porch = fptable[4]; + ofs = -7; + break; + default: + NV_ERROR(dev, + "FP table revision %d.%d not currently supported\n", + fptable_ver >> 4, fptable_ver & 0xf); + return -ENOSYS; + } + + if (!bios->is_mobile) /* !mobile only needs digital_min_front_porch */ + return 0; + + ret = parse_lvds_manufacturer_table_header(dev, bios, <h); + if (ret) + return ret; + + if (lth.lvds_ver == 0x30 || lth.lvds_ver == 0x40) { + bios->fp.fpxlatetableptr = bios->fp.lvdsmanufacturerpointer + + lth.headerlen + 1; + bios->fp.xlatwidth = lth.recordlen; + } + if (bios->fp.fpxlatetableptr == 0x0) { + NV_ERROR(dev, "Pointer to flat panel xlat table invalid\n"); + return -EINVAL; + } + + fpstrapping = get_fp_strap(dev, bios); + + fpindex = bios->data[bios->fp.fpxlatetableptr + + fpstrapping * bios->fp.xlatwidth]; + + if (fpindex > fpentries) { + NV_ERROR(dev, "Bad flat panel table index\n"); + return -ENOENT; + } + + /* nv4x cards need both a strap value and fpindex of 0xf to use DDC */ + if (lth.lvds_ver > 0x10) + bios->fp_no_ddc = fpstrapping != 0xf || fpindex != 0xf; + + /* + * If either the strap or xlated fpindex value are 0xf there is no + * panel using a strap-derived bios mode present. this condition + * includes, but is different from, the DDC panel indicator above + */ + if (fpstrapping == 0xf || fpindex == 0xf) + return 0; + + bios->fp.mode_ptr = bios->fp.fptablepointer + headerlen + + recordlen * fpindex + ofs; + + NV_TRACE(dev, "BIOS FP mode: %dx%d (%dkHz pixel clock)\n", + ROM16(bios->data[bios->fp.mode_ptr + 11]) + 1, + ROM16(bios->data[bios->fp.mode_ptr + 25]) + 1, + ROM16(bios->data[bios->fp.mode_ptr + 7]) * 10); + + return 0; +} + +bool nouveau_bios_fp_mode(struct drm_device *dev, struct drm_display_mode *mode) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + uint8_t *mode_entry = &bios->data[bios->fp.mode_ptr]; + + if (!mode) /* just checking whether we can produce a mode */ + return bios->fp.mode_ptr; + + memset(mode, 0, sizeof(struct drm_display_mode)); + /* + * For version 1.0 (version in byte 0): + * bytes 1-2 are "panel type", including bits on whether Colour/mono, + * single/dual link, and type (TFT etc.) + * bytes 3-6 are bits per colour in RGBX + */ + mode->clock = ROM16(mode_entry[7]) * 10; + /* bytes 9-10 is HActive */ + mode->hdisplay = ROM16(mode_entry[11]) + 1; + /* + * bytes 13-14 is HValid Start + * bytes 15-16 is HValid End + */ + mode->hsync_start = ROM16(mode_entry[17]) + 1; + mode->hsync_end = ROM16(mode_entry[19]) + 1; + mode->htotal = ROM16(mode_entry[21]) + 1; + /* bytes 23-24, 27-30 similarly, but vertical */ + mode->vdisplay = ROM16(mode_entry[25]) + 1; + mode->vsync_start = ROM16(mode_entry[31]) + 1; + mode->vsync_end = ROM16(mode_entry[33]) + 1; + mode->vtotal = ROM16(mode_entry[35]) + 1; + mode->flags |= (mode_entry[37] & 0x10) ? + DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC; + mode->flags |= (mode_entry[37] & 0x1) ? + DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC; + /* + * bytes 38-39 relate to spread spectrum settings + * bytes 40-43 are something to do with PWM + */ + + mode->status = MODE_OK; + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + return bios->fp.mode_ptr; +} + +int nouveau_bios_parse_lvds_table(struct drm_device *dev, int pxclk, bool *dl, bool *if_is_24bit) +{ + /* + * The LVDS table header is (mostly) described in + * parse_lvds_manufacturer_table_header(): the BIT header additionally + * contains the dual-link transition pxclk (in 10s kHz), at byte 5 - if + * straps are not being used for the panel, this specifies the frequency + * at which modes should be set up in the dual link style. + * + * Following the header, the BMP (ver 0xa) table has several records, + * indexed by a separate xlat table, indexed in turn by the fp strap in + * EXTDEV_BOOT. Each record had a config byte, followed by 6 script + * numbers for use by INIT_SUB which controlled panel init and power, + * and finally a dword of ms to sleep between power off and on + * operations. + * + * In the BIT versions, the table following the header serves as an + * integrated config and xlat table: the records in the table are + * indexed by the FP strap nibble in EXTDEV_BOOT, and each record has + * two bytes - the first as a config byte, the second for indexing the + * fp mode table pointed to by the BIT 'D' table + * + * DDC is not used until after card init, so selecting the correct table + * entry and setting the dual link flag for EDID equipped panels, + * requiring tests against the native-mode pixel clock, cannot be done + * until later, when this function should be called with non-zero pxclk + */ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + int fpstrapping = get_fp_strap(dev, bios), lvdsmanufacturerindex = 0; + struct lvdstableheader lth; + uint16_t lvdsofs; + int ret, chip_version = bios->chip_version; + + ret = parse_lvds_manufacturer_table_header(dev, bios, <h); + if (ret) + return ret; + + switch (lth.lvds_ver) { + case 0x0a: /* pre NV40 */ + lvdsmanufacturerindex = bios->data[ + bios->fp.fpxlatemanufacturertableptr + + fpstrapping]; + + /* we're done if this isn't the EDID panel case */ + if (!pxclk) + break; + + if (chip_version < 0x25) { + /* nv17 behaviour + * + * It seems the old style lvds script pointer is reused + * to select 18/24 bit colour depth for EDID panels. + */ + lvdsmanufacturerindex = + (bios->legacy.lvds_single_a_script_ptr & 1) ? + 2 : 0; + if (pxclk >= bios->fp.duallink_transition_clk) + lvdsmanufacturerindex++; + } else if (chip_version < 0x30) { + /* nv28 behaviour (off-chip encoder) + * + * nv28 does a complex dance of first using byte 121 of + * the EDID to choose the lvdsmanufacturerindex, then + * later attempting to match the EDID manufacturer and + * product IDs in a table (signature 'pidt' (panel id + * table?)), setting an lvdsmanufacturerindex of 0 and + * an fp strap of the match index (or 0xf if none) + */ + lvdsmanufacturerindex = 0; + } else { + /* nv31, nv34 behaviour */ + lvdsmanufacturerindex = 0; + if (pxclk >= bios->fp.duallink_transition_clk) + lvdsmanufacturerindex = 2; + if (pxclk >= 140000) + lvdsmanufacturerindex = 3; + } + + /* + * nvidia set the high nibble of (cr57=f, cr58) to + * lvdsmanufacturerindex in this case; we don't + */ + break; + case 0x30: /* NV4x */ + case 0x40: /* G80/G90 */ + lvdsmanufacturerindex = fpstrapping; + break; + default: + NV_ERROR(dev, "LVDS table revision not currently supported\n"); + return -ENOSYS; + } + + lvdsofs = bios->fp.xlated_entry = bios->fp.lvdsmanufacturerpointer + lth.headerlen + lth.recordlen * lvdsmanufacturerindex; + switch (lth.lvds_ver) { + case 0x0a: + bios->fp.power_off_for_reset = bios->data[lvdsofs] & 1; + bios->fp.reset_after_pclk_change = bios->data[lvdsofs] & 2; + bios->fp.dual_link = bios->data[lvdsofs] & 4; + bios->fp.link_c_increment = bios->data[lvdsofs] & 8; + *if_is_24bit = bios->data[lvdsofs] & 16; + break; + case 0x30: + case 0x40: + /* + * No sign of the "power off for reset" or "reset for panel + * on" bits, but it's safer to assume we should + */ + bios->fp.power_off_for_reset = true; + bios->fp.reset_after_pclk_change = true; + + /* + * It's ok lvdsofs is wrong for nv4x edid case; dual_link is + * over-written, and if_is_24bit isn't used + */ + bios->fp.dual_link = bios->data[lvdsofs] & 1; + bios->fp.if_is_24bit = bios->data[lvdsofs] & 2; + bios->fp.strapless_is_24bit = bios->data[bios->fp.lvdsmanufacturerpointer + 4]; + bios->fp.duallink_transition_clk = ROM16(bios->data[bios->fp.lvdsmanufacturerpointer + 5]) * 10; + break; + } + + /* set dual_link flag for EDID case */ + if (pxclk && (chip_version < 0x25 || chip_version > 0x28)) + bios->fp.dual_link = (pxclk >= bios->fp.duallink_transition_clk); + + *dl = bios->fp.dual_link; + + return 0; +} + +/* BIT 'U'/'d' table encoder subtables have hashes matching them to + * a particular set of encoders. + * + * This function returns true if a particular DCB entry matches. + */ +bool +bios_encoder_match(struct dcb_entry *dcb, u32 hash) +{ + if ((hash & 0x000000f0) != (dcb->location << 4)) + return false; + if ((hash & 0x0000000f) != dcb->type) + return false; + if (!(hash & (dcb->or << 16))) + return false; + + switch (dcb->type) { + case OUTPUT_TMDS: + case OUTPUT_LVDS: + case OUTPUT_DP: + if (hash & 0x00c00000) { + if (!(hash & (dcb->sorconf.link << 22))) + return false; + } + default: + return true; + } +} + +int +nouveau_bios_run_display_table(struct drm_device *dev, u16 type, int pclk, + struct dcb_entry *dcbent, int crtc) +{ + /* + * The display script table is located by the BIT 'U' table. + * + * It contains an array of pointers to various tables describing + * a particular output type. The first 32-bits of the output + * tables contains similar information to a DCB entry, and is + * used to decide whether that particular table is suitable for + * the output you want to access. + * + * The "record header length" field here seems to indicate the + * offset of the first configuration entry in the output tables. + * This is 10 on most cards I've seen, but 12 has been witnessed + * on DP cards, and there's another script pointer within the + * header. + * + * offset + 0 ( 8 bits): version + * offset + 1 ( 8 bits): header length + * offset + 2 ( 8 bits): record length + * offset + 3 ( 8 bits): number of records + * offset + 4 ( 8 bits): record header length + * offset + 5 (16 bits): pointer to first output script table + */ + + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + uint8_t *table = &bios->data[bios->display.script_table_ptr]; + uint8_t *otable = NULL; + uint16_t script; + int i; + + if (!bios->display.script_table_ptr) { + NV_ERROR(dev, "No pointer to output script table\n"); + return 1; + } + + /* + * Nothing useful has been in any of the pre-2.0 tables I've seen, + * so until they are, we really don't need to care. + */ + if (table[0] < 0x20) + return 1; + + if (table[0] != 0x20 && table[0] != 0x21) { + NV_ERROR(dev, "Output script table version 0x%02x unknown\n", + table[0]); + return 1; + } + + /* + * The output script tables describing a particular output type + * look as follows: + * + * offset + 0 (32 bits): output this table matches (hash of DCB) + * offset + 4 ( 8 bits): unknown + * offset + 5 ( 8 bits): number of configurations + * offset + 6 (16 bits): pointer to some script + * offset + 8 (16 bits): pointer to some script + * + * headerlen == 10 + * offset + 10 : configuration 0 + * + * headerlen == 12 + * offset + 10 : pointer to some script + * offset + 12 : configuration 0 + * + * Each config entry is as follows: + * + * offset + 0 (16 bits): unknown, assumed to be a match value + * offset + 2 (16 bits): pointer to script table (clock set?) + * offset + 4 (16 bits): pointer to script table (reset?) + * + * There doesn't appear to be a count value to say how many + * entries exist in each script table, instead, a 0 value in + * the first 16-bit word seems to indicate both the end of the + * list and the default entry. The second 16-bit word in the + * script tables is a pointer to the script to execute. + */ + + NV_DEBUG_KMS(dev, "Searching for output entry for %d %d %d\n", + dcbent->type, dcbent->location, dcbent->or); + for (i = 0; i < table[3]; i++) { + otable = ROMPTR(dev, table[table[1] + (i * table[2])]); + if (otable && bios_encoder_match(dcbent, ROM32(otable[0]))) + break; + } + + if (!otable) { + NV_DEBUG_KMS(dev, "failed to match any output table\n"); + return 1; + } + + if (pclk < -2 || pclk > 0) { + /* Try to find matching script table entry */ + for (i = 0; i < otable[5]; i++) { + if (ROM16(otable[table[4] + i*6]) == type) + break; + } + + if (i == otable[5]) { + NV_ERROR(dev, "Table 0x%04x not found for %d/%d, " + "using first\n", + type, dcbent->type, dcbent->or); + i = 0; + } + } + + if (pclk == 0) { + script = ROM16(otable[6]); + if (!script) { + NV_DEBUG_KMS(dev, "output script 0 not found\n"); + return 1; + } + + NV_DEBUG_KMS(dev, "0x%04X: parsing output script 0\n", script); + nouveau_bios_run_init_table(dev, script, dcbent, crtc); + } else + if (pclk == -1) { + script = ROM16(otable[8]); + if (!script) { + NV_DEBUG_KMS(dev, "output script 1 not found\n"); + return 1; + } + + NV_DEBUG_KMS(dev, "0x%04X: parsing output script 1\n", script); + nouveau_bios_run_init_table(dev, script, dcbent, crtc); + } else + if (pclk == -2) { + if (table[4] >= 12) + script = ROM16(otable[10]); + else + script = 0; + if (!script) { + NV_DEBUG_KMS(dev, "output script 2 not found\n"); + return 1; + } + + NV_DEBUG_KMS(dev, "0x%04X: parsing output script 2\n", script); + nouveau_bios_run_init_table(dev, script, dcbent, crtc); + } else + if (pclk > 0) { + script = ROM16(otable[table[4] + i*6 + 2]); + if (script) + script = clkcmptable(bios, script, pclk); + if (!script) { + NV_DEBUG_KMS(dev, "clock script 0 not found\n"); + return 1; + } + + NV_DEBUG_KMS(dev, "0x%04X: parsing clock script 0\n", script); + nouveau_bios_run_init_table(dev, script, dcbent, crtc); + } else + if (pclk < 0) { + script = ROM16(otable[table[4] + i*6 + 4]); + if (script) + script = clkcmptable(bios, script, -pclk); + if (!script) { + NV_DEBUG_KMS(dev, "clock script 1 not found\n"); + return 1; + } + + NV_DEBUG_KMS(dev, "0x%04X: parsing clock script 1\n", script); + nouveau_bios_run_init_table(dev, script, dcbent, crtc); + } + + return 0; +} + + +int run_tmds_table(struct drm_device *dev, struct dcb_entry *dcbent, int head, int pxclk) +{ + /* + * the pxclk parameter is in kHz + * + * This runs the TMDS regs setting code found on BIT bios cards + * + * For ffs(or) == 1 use the first table, for ffs(or) == 2 and + * ffs(or) == 3, use the second. + */ + + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + int cv = bios->chip_version; + uint16_t clktable = 0, scriptptr; + uint32_t sel_clk_binding, sel_clk; + + /* pre-nv17 off-chip tmds uses scripts, post nv17 doesn't */ + if (cv >= 0x17 && cv != 0x1a && cv != 0x20 && + dcbent->location != DCB_LOC_ON_CHIP) + return 0; + + switch (ffs(dcbent->or)) { + case 1: + clktable = bios->tmds.output0_script_ptr; + break; + case 2: + case 3: + clktable = bios->tmds.output1_script_ptr; + break; + } + + if (!clktable) { + NV_ERROR(dev, "Pixel clock comparison table not found\n"); + return -EINVAL; + } + + scriptptr = clkcmptable(bios, clktable, pxclk); + + if (!scriptptr) { + NV_ERROR(dev, "TMDS output init script not found\n"); + return -ENOENT; + } + + /* don't let script change pll->head binding */ + sel_clk_binding = bios_rd32(bios, NV_PRAMDAC_SEL_CLK) & 0x50000; + run_digital_op_script(dev, scriptptr, dcbent, head, pxclk >= 165000); + sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK) & ~0x50000; + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, sel_clk | sel_clk_binding); + + return 0; +} + +struct pll_mapping { + u8 type; + u32 reg; +}; + +static struct pll_mapping nv04_pll_mapping[] = { + { PLL_CORE , NV_PRAMDAC_NVPLL_COEFF }, + { PLL_MEMORY, NV_PRAMDAC_MPLL_COEFF }, + { PLL_VPLL0 , NV_PRAMDAC_VPLL_COEFF }, + { PLL_VPLL1 , NV_RAMDAC_VPLL2 }, + {} +}; + +static struct pll_mapping nv40_pll_mapping[] = { + { PLL_CORE , 0x004000 }, + { PLL_MEMORY, 0x004020 }, + { PLL_VPLL0 , NV_PRAMDAC_VPLL_COEFF }, + { PLL_VPLL1 , NV_RAMDAC_VPLL2 }, + {} +}; + +static struct pll_mapping nv50_pll_mapping[] = { + { PLL_CORE , 0x004028 }, + { PLL_SHADER, 0x004020 }, + { PLL_UNK03 , 0x004000 }, + { PLL_MEMORY, 0x004008 }, + { PLL_UNK40 , 0x00e810 }, + { PLL_UNK41 , 0x00e818 }, + { PLL_UNK42 , 0x00e824 }, + { PLL_VPLL0 , 0x614100 }, + { PLL_VPLL1 , 0x614900 }, + {} +}; + +static struct pll_mapping nv84_pll_mapping[] = { + { PLL_CORE , 0x004028 }, + { PLL_SHADER, 0x004020 }, + { PLL_MEMORY, 0x004008 }, + { PLL_VDEC , 0x004030 }, + { PLL_UNK41 , 0x00e818 }, + { PLL_VPLL0 , 0x614100 }, + { PLL_VPLL1 , 0x614900 }, + {} +}; + +u32 +get_pll_register(struct drm_device *dev, enum pll_types type) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct pll_mapping *map; + int i; + + if (dev_priv->card_type < NV_40) + map = nv04_pll_mapping; + else + if (dev_priv->card_type < NV_50) + map = nv40_pll_mapping; + else { + u8 *plim = &bios->data[bios->pll_limit_tbl_ptr]; + + if (plim[0] >= 0x30) { + u8 *entry = plim + plim[1]; + for (i = 0; i < plim[3]; i++, entry += plim[2]) { + if (entry[0] == type) + return ROM32(entry[3]); + } + + return 0; + } + + if (dev_priv->chipset == 0x50) + map = nv50_pll_mapping; + else + map = nv84_pll_mapping; + } + + while (map->reg) { + if (map->type == type) + return map->reg; + map++; + } + + return 0; +} + +int get_pll_limits(struct drm_device *dev, uint32_t limit_match, struct pll_lims *pll_lim) +{ + /* + * PLL limits table + * + * Version 0x10: NV30, NV31 + * One byte header (version), one record of 24 bytes + * Version 0x11: NV36 - Not implemented + * Seems to have same record style as 0x10, but 3 records rather than 1 + * Version 0x20: Found on Geforce 6 cards + * Trivial 4 byte BIT header. 31 (0x1f) byte record length + * Version 0x21: Found on Geforce 7, 8 and some Geforce 6 cards + * 5 byte header, fifth byte of unknown purpose. 35 (0x23) byte record + * length in general, some (integrated) have an extra configuration byte + * Version 0x30: Found on Geforce 8, separates the register mapping + * from the limits tables. + */ + + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + int cv = bios->chip_version, pllindex = 0; + uint8_t pll_lim_ver = 0, headerlen = 0, recordlen = 0, entries = 0; + uint32_t crystal_strap_mask, crystal_straps; + + if (!bios->pll_limit_tbl_ptr) { + if (cv == 0x30 || cv == 0x31 || cv == 0x35 || cv == 0x36 || + cv >= 0x40) { + NV_ERROR(dev, "Pointer to PLL limits table invalid\n"); + return -EINVAL; + } + } else + pll_lim_ver = bios->data[bios->pll_limit_tbl_ptr]; + + crystal_strap_mask = 1 << 6; + /* open coded dev->twoHeads test */ + if (cv > 0x10 && cv != 0x15 && cv != 0x1a && cv != 0x20) + crystal_strap_mask |= 1 << 22; + crystal_straps = nvReadEXTDEV(dev, NV_PEXTDEV_BOOT_0) & + crystal_strap_mask; + + switch (pll_lim_ver) { + /* + * We use version 0 to indicate a pre limit table bios (single stage + * pll) and load the hard coded limits instead. + */ + case 0: + break; + case 0x10: + case 0x11: + /* + * Strictly v0x11 has 3 entries, but the last two don't seem + * to get used. + */ + headerlen = 1; + recordlen = 0x18; + entries = 1; + pllindex = 0; + break; + case 0x20: + case 0x21: + case 0x30: + case 0x40: + headerlen = bios->data[bios->pll_limit_tbl_ptr + 1]; + recordlen = bios->data[bios->pll_limit_tbl_ptr + 2]; + entries = bios->data[bios->pll_limit_tbl_ptr + 3]; + break; + default: + NV_ERROR(dev, "PLL limits table revision 0x%X not currently " + "supported\n", pll_lim_ver); + return -ENOSYS; + } + + /* initialize all members to zero */ + memset(pll_lim, 0, sizeof(struct pll_lims)); + + /* if we were passed a type rather than a register, figure + * out the register and store it + */ + if (limit_match > PLL_MAX) + pll_lim->reg = limit_match; + else { + pll_lim->reg = get_pll_register(dev, limit_match); + if (!pll_lim->reg) + return -ENOENT; + } + + if (pll_lim_ver == 0x10 || pll_lim_ver == 0x11) { + uint8_t *pll_rec = &bios->data[bios->pll_limit_tbl_ptr + headerlen + recordlen * pllindex]; + + pll_lim->vco1.minfreq = ROM32(pll_rec[0]); + pll_lim->vco1.maxfreq = ROM32(pll_rec[4]); + pll_lim->vco2.minfreq = ROM32(pll_rec[8]); + pll_lim->vco2.maxfreq = ROM32(pll_rec[12]); + pll_lim->vco1.min_inputfreq = ROM32(pll_rec[16]); + pll_lim->vco2.min_inputfreq = ROM32(pll_rec[20]); + pll_lim->vco1.max_inputfreq = pll_lim->vco2.max_inputfreq = INT_MAX; + + /* these values taken from nv30/31/36 */ + pll_lim->vco1.min_n = 0x1; + if (cv == 0x36) + pll_lim->vco1.min_n = 0x5; + pll_lim->vco1.max_n = 0xff; + pll_lim->vco1.min_m = 0x1; + pll_lim->vco1.max_m = 0xd; + pll_lim->vco2.min_n = 0x4; + /* + * On nv30, 31, 36 (i.e. all cards with two stage PLLs with this + * table version (apart from nv35)), N2 is compared to + * maxN2 (0x46) and 10 * maxM2 (0x4), so set maxN2 to 0x28 and + * save a comparison + */ + pll_lim->vco2.max_n = 0x28; + if (cv == 0x30 || cv == 0x35) + /* only 5 bits available for N2 on nv30/35 */ + pll_lim->vco2.max_n = 0x1f; + pll_lim->vco2.min_m = 0x1; + pll_lim->vco2.max_m = 0x4; + pll_lim->max_log2p = 0x7; + pll_lim->max_usable_log2p = 0x6; + } else if (pll_lim_ver == 0x20 || pll_lim_ver == 0x21) { + uint16_t plloffs = bios->pll_limit_tbl_ptr + headerlen; + uint8_t *pll_rec; + int i; + + /* + * First entry is default match, if nothing better. warn if + * reg field nonzero + */ + if (ROM32(bios->data[plloffs])) + NV_WARN(dev, "Default PLL limit entry has non-zero " + "register field\n"); + + for (i = 1; i < entries; i++) + if (ROM32(bios->data[plloffs + recordlen * i]) == pll_lim->reg) { + pllindex = i; + break; + } + + if ((dev_priv->card_type >= NV_50) && (pllindex == 0)) { + NV_ERROR(dev, "Register 0x%08x not found in PLL " + "limits table", pll_lim->reg); + return -ENOENT; + } + + pll_rec = &bios->data[plloffs + recordlen * pllindex]; + + BIOSLOG(bios, "Loading PLL limits for reg 0x%08x\n", + pllindex ? pll_lim->reg : 0); + + /* + * Frequencies are stored in tables in MHz, kHz are more + * useful, so we convert. + */ + + /* What output frequencies can each VCO generate? */ + pll_lim->vco1.minfreq = ROM16(pll_rec[4]) * 1000; + pll_lim->vco1.maxfreq = ROM16(pll_rec[6]) * 1000; + pll_lim->vco2.minfreq = ROM16(pll_rec[8]) * 1000; + pll_lim->vco2.maxfreq = ROM16(pll_rec[10]) * 1000; + + /* What input frequencies they accept (past the m-divider)? */ + pll_lim->vco1.min_inputfreq = ROM16(pll_rec[12]) * 1000; + pll_lim->vco2.min_inputfreq = ROM16(pll_rec[14]) * 1000; + pll_lim->vco1.max_inputfreq = ROM16(pll_rec[16]) * 1000; + pll_lim->vco2.max_inputfreq = ROM16(pll_rec[18]) * 1000; + + /* What values are accepted as multiplier and divider? */ + pll_lim->vco1.min_n = pll_rec[20]; + pll_lim->vco1.max_n = pll_rec[21]; + pll_lim->vco1.min_m = pll_rec[22]; + pll_lim->vco1.max_m = pll_rec[23]; + pll_lim->vco2.min_n = pll_rec[24]; + pll_lim->vco2.max_n = pll_rec[25]; + pll_lim->vco2.min_m = pll_rec[26]; + pll_lim->vco2.max_m = pll_rec[27]; + + pll_lim->max_usable_log2p = pll_lim->max_log2p = pll_rec[29]; + if (pll_lim->max_log2p > 0x7) + /* pll decoding in nv_hw.c assumes never > 7 */ + NV_WARN(dev, "Max log2 P value greater than 7 (%d)\n", + pll_lim->max_log2p); + if (cv < 0x60) + pll_lim->max_usable_log2p = 0x6; + pll_lim->log2p_bias = pll_rec[30]; + + if (recordlen > 0x22) + pll_lim->refclk = ROM32(pll_rec[31]); + + if (recordlen > 0x23 && pll_rec[35]) + NV_WARN(dev, + "Bits set in PLL configuration byte (%x)\n", + pll_rec[35]); + + /* C51 special not seen elsewhere */ + if (cv == 0x51 && !pll_lim->refclk) { + uint32_t sel_clk = bios_rd32(bios, NV_PRAMDAC_SEL_CLK); + + if ((pll_lim->reg == NV_PRAMDAC_VPLL_COEFF && sel_clk & 0x20) || + (pll_lim->reg == NV_RAMDAC_VPLL2 && sel_clk & 0x80)) { + if (bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_CHIP_ID_INDEX) < 0xa3) + pll_lim->refclk = 200000; + else + pll_lim->refclk = 25000; + } + } + } else if (pll_lim_ver == 0x30) { /* ver 0x30 */ + uint8_t *entry = &bios->data[bios->pll_limit_tbl_ptr + headerlen]; + uint8_t *record = NULL; + int i; + + BIOSLOG(bios, "Loading PLL limits for register 0x%08x\n", + pll_lim->reg); + + for (i = 0; i < entries; i++, entry += recordlen) { + if (ROM32(entry[3]) == pll_lim->reg) { + record = &bios->data[ROM16(entry[1])]; + break; + } + } + + if (!record) { + NV_ERROR(dev, "Register 0x%08x not found in PLL " + "limits table", pll_lim->reg); + return -ENOENT; + } + + pll_lim->vco1.minfreq = ROM16(record[0]) * 1000; + pll_lim->vco1.maxfreq = ROM16(record[2]) * 1000; + pll_lim->vco2.minfreq = ROM16(record[4]) * 1000; + pll_lim->vco2.maxfreq = ROM16(record[6]) * 1000; + pll_lim->vco1.min_inputfreq = ROM16(record[8]) * 1000; + pll_lim->vco2.min_inputfreq = ROM16(record[10]) * 1000; + pll_lim->vco1.max_inputfreq = ROM16(record[12]) * 1000; + pll_lim->vco2.max_inputfreq = ROM16(record[14]) * 1000; + pll_lim->vco1.min_n = record[16]; + pll_lim->vco1.max_n = record[17]; + pll_lim->vco1.min_m = record[18]; + pll_lim->vco1.max_m = record[19]; + pll_lim->vco2.min_n = record[20]; + pll_lim->vco2.max_n = record[21]; + pll_lim->vco2.min_m = record[22]; + pll_lim->vco2.max_m = record[23]; + pll_lim->max_usable_log2p = pll_lim->max_log2p = record[25]; + pll_lim->log2p_bias = record[27]; + pll_lim->refclk = ROM32(record[28]); + } else if (pll_lim_ver) { /* ver 0x40 */ + uint8_t *entry = &bios->data[bios->pll_limit_tbl_ptr + headerlen]; + uint8_t *record = NULL; + int i; + + BIOSLOG(bios, "Loading PLL limits for register 0x%08x\n", + pll_lim->reg); + + for (i = 0; i < entries; i++, entry += recordlen) { + if (ROM32(entry[3]) == pll_lim->reg) { + record = &bios->data[ROM16(entry[1])]; + break; + } + } + + if (!record) { + NV_ERROR(dev, "Register 0x%08x not found in PLL " + "limits table", pll_lim->reg); + return -ENOENT; + } + + pll_lim->vco1.minfreq = ROM16(record[0]) * 1000; + pll_lim->vco1.maxfreq = ROM16(record[2]) * 1000; + pll_lim->vco1.min_inputfreq = ROM16(record[4]) * 1000; + pll_lim->vco1.max_inputfreq = ROM16(record[6]) * 1000; + pll_lim->vco1.min_m = record[8]; + pll_lim->vco1.max_m = record[9]; + pll_lim->vco1.min_n = record[10]; + pll_lim->vco1.max_n = record[11]; + pll_lim->min_p = record[12]; + pll_lim->max_p = record[13]; + pll_lim->refclk = ROM16(entry[9]) * 1000; + } + + /* + * By now any valid limit table ought to have set a max frequency for + * vco1, so if it's zero it's either a pre limit table bios, or one + * with an empty limit table (seen on nv18) + */ + if (!pll_lim->vco1.maxfreq) { + pll_lim->vco1.minfreq = bios->fminvco; + pll_lim->vco1.maxfreq = bios->fmaxvco; + pll_lim->vco1.min_inputfreq = 0; + pll_lim->vco1.max_inputfreq = INT_MAX; + pll_lim->vco1.min_n = 0x1; + pll_lim->vco1.max_n = 0xff; + pll_lim->vco1.min_m = 0x1; + if (crystal_straps == 0) { + /* nv05 does this, nv11 doesn't, nv10 unknown */ + if (cv < 0x11) + pll_lim->vco1.min_m = 0x7; + pll_lim->vco1.max_m = 0xd; + } else { + if (cv < 0x11) + pll_lim->vco1.min_m = 0x8; + pll_lim->vco1.max_m = 0xe; + } + if (cv < 0x17 || cv == 0x1a || cv == 0x20) + pll_lim->max_log2p = 4; + else + pll_lim->max_log2p = 5; + pll_lim->max_usable_log2p = pll_lim->max_log2p; + } + + if (!pll_lim->refclk) + switch (crystal_straps) { + case 0: + pll_lim->refclk = 13500; + break; + case (1 << 6): + pll_lim->refclk = 14318; + break; + case (1 << 22): + pll_lim->refclk = 27000; + break; + case (1 << 22 | 1 << 6): + pll_lim->refclk = 25000; + break; + } + + NV_DEBUG(dev, "pll.vco1.minfreq: %d\n", pll_lim->vco1.minfreq); + NV_DEBUG(dev, "pll.vco1.maxfreq: %d\n", pll_lim->vco1.maxfreq); + NV_DEBUG(dev, "pll.vco1.min_inputfreq: %d\n", pll_lim->vco1.min_inputfreq); + NV_DEBUG(dev, "pll.vco1.max_inputfreq: %d\n", pll_lim->vco1.max_inputfreq); + NV_DEBUG(dev, "pll.vco1.min_n: %d\n", pll_lim->vco1.min_n); + NV_DEBUG(dev, "pll.vco1.max_n: %d\n", pll_lim->vco1.max_n); + NV_DEBUG(dev, "pll.vco1.min_m: %d\n", pll_lim->vco1.min_m); + NV_DEBUG(dev, "pll.vco1.max_m: %d\n", pll_lim->vco1.max_m); + if (pll_lim->vco2.maxfreq) { + NV_DEBUG(dev, "pll.vco2.minfreq: %d\n", pll_lim->vco2.minfreq); + NV_DEBUG(dev, "pll.vco2.maxfreq: %d\n", pll_lim->vco2.maxfreq); + NV_DEBUG(dev, "pll.vco2.min_inputfreq: %d\n", pll_lim->vco2.min_inputfreq); + NV_DEBUG(dev, "pll.vco2.max_inputfreq: %d\n", pll_lim->vco2.max_inputfreq); + NV_DEBUG(dev, "pll.vco2.min_n: %d\n", pll_lim->vco2.min_n); + NV_DEBUG(dev, "pll.vco2.max_n: %d\n", pll_lim->vco2.max_n); + NV_DEBUG(dev, "pll.vco2.min_m: %d\n", pll_lim->vco2.min_m); + NV_DEBUG(dev, "pll.vco2.max_m: %d\n", pll_lim->vco2.max_m); + } + if (!pll_lim->max_p) { + NV_DEBUG(dev, "pll.max_log2p: %d\n", pll_lim->max_log2p); + NV_DEBUG(dev, "pll.log2p_bias: %d\n", pll_lim->log2p_bias); + } else { + NV_DEBUG(dev, "pll.min_p: %d\n", pll_lim->min_p); + NV_DEBUG(dev, "pll.max_p: %d\n", pll_lim->max_p); + } + NV_DEBUG(dev, "pll.refclk: %d\n", pll_lim->refclk); + + return 0; +} + +static void parse_bios_version(struct drm_device *dev, struct nvbios *bios, uint16_t offset) +{ + /* + * offset + 0 (8 bits): Micro version + * offset + 1 (8 bits): Minor version + * offset + 2 (8 bits): Chip version + * offset + 3 (8 bits): Major version + */ + + bios->major_version = bios->data[offset + 3]; + bios->chip_version = bios->data[offset + 2]; + NV_TRACE(dev, "Bios version %02x.%02x.%02x.%02x\n", + bios->data[offset + 3], bios->data[offset + 2], + bios->data[offset + 1], bios->data[offset]); +} + +static void parse_script_table_pointers(struct nvbios *bios, uint16_t offset) +{ + /* + * Parses the init table segment for pointers used in script execution. + * + * offset + 0 (16 bits): init script tables pointer + * offset + 2 (16 bits): macro index table pointer + * offset + 4 (16 bits): macro table pointer + * offset + 6 (16 bits): condition table pointer + * offset + 8 (16 bits): io condition table pointer + * offset + 10 (16 bits): io flag condition table pointer + * offset + 12 (16 bits): init function table pointer + */ + + bios->init_script_tbls_ptr = ROM16(bios->data[offset]); + bios->macro_index_tbl_ptr = ROM16(bios->data[offset + 2]); + bios->macro_tbl_ptr = ROM16(bios->data[offset + 4]); + bios->condition_tbl_ptr = ROM16(bios->data[offset + 6]); + bios->io_condition_tbl_ptr = ROM16(bios->data[offset + 8]); + bios->io_flag_condition_tbl_ptr = ROM16(bios->data[offset + 10]); + bios->init_function_tbl_ptr = ROM16(bios->data[offset + 12]); +} + +static int parse_bit_A_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) +{ + /* + * Parses the load detect values for g80 cards. + * + * offset + 0 (16 bits): loadval table pointer + */ + + uint16_t load_table_ptr; + uint8_t version, headerlen, entrylen, num_entries; + + if (bitentry->length != 3) { + NV_ERROR(dev, "Do not understand BIT A table\n"); + return -EINVAL; + } + + load_table_ptr = ROM16(bios->data[bitentry->offset]); + + if (load_table_ptr == 0x0) { + NV_DEBUG(dev, "Pointer to BIT loadval table invalid\n"); + return -EINVAL; + } + + version = bios->data[load_table_ptr]; + + if (version != 0x10) { + NV_ERROR(dev, "BIT loadval table version %d.%d not supported\n", + version >> 4, version & 0xF); + return -ENOSYS; + } + + headerlen = bios->data[load_table_ptr + 1]; + entrylen = bios->data[load_table_ptr + 2]; + num_entries = bios->data[load_table_ptr + 3]; + + if (headerlen != 4 || entrylen != 4 || num_entries != 2) { + NV_ERROR(dev, "Do not understand BIT loadval table\n"); + return -EINVAL; + } + + /* First entry is normal dac, 2nd tv-out perhaps? */ + bios->dactestval = ROM32(bios->data[load_table_ptr + headerlen]) & 0x3ff; + + return 0; +} + +static int parse_bit_C_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) +{ + /* + * offset + 8 (16 bits): PLL limits table pointer + * + * There's more in here, but that's unknown. + */ + + if (bitentry->length < 10) { + NV_ERROR(dev, "Do not understand BIT C table\n"); + return -EINVAL; + } + + bios->pll_limit_tbl_ptr = ROM16(bios->data[bitentry->offset + 8]); + + return 0; +} + +static int parse_bit_display_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) +{ + /* + * Parses the flat panel table segment that the bit entry points to. + * Starting at bitentry->offset: + * + * offset + 0 (16 bits): ??? table pointer - seems to have 18 byte + * records beginning with a freq. + * offset + 2 (16 bits): mode table pointer + */ + + if (bitentry->length != 4) { + NV_ERROR(dev, "Do not understand BIT display table\n"); + return -EINVAL; + } + + bios->fp.fptablepointer = ROM16(bios->data[bitentry->offset + 2]); + + return 0; +} + +static int parse_bit_init_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) +{ + /* + * Parses the init table segment that the bit entry points to. + * + * See parse_script_table_pointers for layout + */ + + if (bitentry->length < 14) { + NV_ERROR(dev, "Do not understand init table\n"); + return -EINVAL; + } + + parse_script_table_pointers(bios, bitentry->offset); + + if (bitentry->length >= 16) + bios->some_script_ptr = ROM16(bios->data[bitentry->offset + 14]); + if (bitentry->length >= 18) + bios->init96_tbl_ptr = ROM16(bios->data[bitentry->offset + 16]); + + return 0; +} + +static int parse_bit_i_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) +{ + /* + * BIT 'i' (info?) table + * + * offset + 0 (32 bits): BIOS version dword (as in B table) + * offset + 5 (8 bits): BIOS feature byte (same as for BMP?) + * offset + 13 (16 bits): pointer to table containing DAC load + * detection comparison values + * + * There's other things in the table, purpose unknown + */ + + uint16_t daccmpoffset; + uint8_t dacver, dacheaderlen; + + if (bitentry->length < 6) { + NV_ERROR(dev, "BIT i table too short for needed information\n"); + return -EINVAL; + } + + parse_bios_version(dev, bios, bitentry->offset); + + /* + * bit 4 seems to indicate a mobile bios (doesn't suffer from BMP's + * Quadro identity crisis), other bits possibly as for BMP feature byte + */ + bios->feature_byte = bios->data[bitentry->offset + 5]; + bios->is_mobile = bios->feature_byte & FEATURE_MOBILE; + + if (bitentry->length < 15) { + NV_WARN(dev, "BIT i table not long enough for DAC load " + "detection comparison table\n"); + return -EINVAL; + } + + daccmpoffset = ROM16(bios->data[bitentry->offset + 13]); + + /* doesn't exist on g80 */ + if (!daccmpoffset) + return 0; + + /* + * The first value in the table, following the header, is the + * comparison value, the second entry is a comparison value for + * TV load detection. + */ + + dacver = bios->data[daccmpoffset]; + dacheaderlen = bios->data[daccmpoffset + 1]; + + if (dacver != 0x00 && dacver != 0x10) { + NV_WARN(dev, "DAC load detection comparison table version " + "%d.%d not known\n", dacver >> 4, dacver & 0xf); + return -ENOSYS; + } + + bios->dactestval = ROM32(bios->data[daccmpoffset + dacheaderlen]); + bios->tvdactestval = ROM32(bios->data[daccmpoffset + dacheaderlen + 4]); + + return 0; +} + +static int parse_bit_lvds_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) +{ + /* + * Parses the LVDS table segment that the bit entry points to. + * Starting at bitentry->offset: + * + * offset + 0 (16 bits): LVDS strap xlate table pointer + */ + + if (bitentry->length != 2) { + NV_ERROR(dev, "Do not understand BIT LVDS table\n"); + return -EINVAL; + } + + /* + * No idea if it's still called the LVDS manufacturer table, but + * the concept's close enough. + */ + bios->fp.lvdsmanufacturerpointer = ROM16(bios->data[bitentry->offset]); + + return 0; +} + +static int +parse_bit_M_tbl_entry(struct drm_device *dev, struct nvbios *bios, + struct bit_entry *bitentry) +{ + /* + * offset + 2 (8 bits): number of options in an + * INIT_RAM_RESTRICT_ZM_REG_GROUP opcode option set + * offset + 3 (16 bits): pointer to strap xlate table for RAM + * restrict option selection + * + * There's a bunch of bits in this table other than the RAM restrict + * stuff that we don't use - their use currently unknown + */ + + /* + * Older bios versions don't have a sufficiently long table for + * what we want + */ + if (bitentry->length < 0x5) + return 0; + + if (bitentry->version < 2) { + bios->ram_restrict_group_count = bios->data[bitentry->offset + 2]; + bios->ram_restrict_tbl_ptr = ROM16(bios->data[bitentry->offset + 3]); + } else { + bios->ram_restrict_group_count = bios->data[bitentry->offset + 0]; + bios->ram_restrict_tbl_ptr = ROM16(bios->data[bitentry->offset + 1]); + } + + return 0; +} + +static int parse_bit_tmds_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry) +{ + /* + * Parses the pointer to the TMDS table + * + * Starting at bitentry->offset: + * + * offset + 0 (16 bits): TMDS table pointer + * + * The TMDS table is typically found just before the DCB table, with a + * characteristic signature of 0x11,0x13 (1.1 being version, 0x13 being + * length?) + * + * At offset +7 is a pointer to a script, which I don't know how to + * run yet. + * At offset +9 is a pointer to another script, likewise + * Offset +11 has a pointer to a table where the first word is a pxclk + * frequency and the second word a pointer to a script, which should be + * run if the comparison pxclk frequency is less than the pxclk desired. + * This repeats for decreasing comparison frequencies + * Offset +13 has a pointer to a similar table + * The selection of table (and possibly +7/+9 script) is dictated by + * "or" from the DCB. + */ + + uint16_t tmdstableptr, script1, script2; + + if (bitentry->length != 2) { + NV_ERROR(dev, "Do not understand BIT TMDS table\n"); + return -EINVAL; + } + + tmdstableptr = ROM16(bios->data[bitentry->offset]); + if (!tmdstableptr) { + NV_ERROR(dev, "Pointer to TMDS table invalid\n"); + return -EINVAL; + } + + NV_INFO(dev, "TMDS table version %d.%d\n", + bios->data[tmdstableptr] >> 4, bios->data[tmdstableptr] & 0xf); + + /* nv50+ has v2.0, but we don't parse it atm */ + if (bios->data[tmdstableptr] != 0x11) + return -ENOSYS; + + /* + * These two scripts are odd: they don't seem to get run even when + * they are not stubbed. + */ + script1 = ROM16(bios->data[tmdstableptr + 7]); + script2 = ROM16(bios->data[tmdstableptr + 9]); + if (bios->data[script1] != 'q' || bios->data[script2] != 'q') + NV_WARN(dev, "TMDS table script pointers not stubbed\n"); + + bios->tmds.output0_script_ptr = ROM16(bios->data[tmdstableptr + 11]); + bios->tmds.output1_script_ptr = ROM16(bios->data[tmdstableptr + 13]); + + return 0; +} + +static int +parse_bit_U_tbl_entry(struct drm_device *dev, struct nvbios *bios, + struct bit_entry *bitentry) +{ + /* + * Parses the pointer to the G80 output script tables + * + * Starting at bitentry->offset: + * + * offset + 0 (16 bits): output script table pointer + */ + + uint16_t outputscripttableptr; + + if (bitentry->length != 3) { + NV_ERROR(dev, "Do not understand BIT U table\n"); + return -EINVAL; + } + + outputscripttableptr = ROM16(bios->data[bitentry->offset]); + bios->display.script_table_ptr = outputscripttableptr; + return 0; +} + +struct bit_table { + const char id; + int (* const parse_fn)(struct drm_device *, struct nvbios *, struct bit_entry *); +}; + +#define BIT_TABLE(id, funcid) ((struct bit_table){ id, parse_bit_##funcid##_tbl_entry }) + +int +bit_table(struct drm_device *dev, u8 id, struct bit_entry *bit) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + u8 entries, *entry; + + if (bios->type != NVBIOS_BIT) + return -ENODEV; + + entries = bios->data[bios->offset + 10]; + entry = &bios->data[bios->offset + 12]; + while (entries--) { + if (entry[0] == id) { + bit->id = entry[0]; + bit->version = entry[1]; + bit->length = ROM16(entry[2]); + bit->offset = ROM16(entry[4]); + bit->data = ROMPTR(dev, entry[4]); + return 0; + } + + entry += bios->data[bios->offset + 9]; + } + + return -ENOENT; +} + +static int +parse_bit_table(struct nvbios *bios, const uint16_t bitoffset, + struct bit_table *table) +{ + struct drm_device *dev = bios->dev; + struct bit_entry bitentry; + + if (bit_table(dev, table->id, &bitentry) == 0) + return table->parse_fn(dev, bios, &bitentry); + + NV_INFO(dev, "BIT table '%c' not found\n", table->id); + return -ENOSYS; +} + +static int +parse_bit_structure(struct nvbios *bios, const uint16_t bitoffset) +{ + int ret; + + /* + * The only restriction on parsing order currently is having 'i' first + * for use of bios->*_version or bios->feature_byte while parsing; + * functions shouldn't be actually *doing* anything apart from pulling + * data from the image into the bios struct, thus no interdependencies + */ + ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('i', i)); + if (ret) /* info? */ + return ret; + if (bios->major_version >= 0x60) /* g80+ */ + parse_bit_table(bios, bitoffset, &BIT_TABLE('A', A)); + ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('C', C)); + if (ret) + return ret; + parse_bit_table(bios, bitoffset, &BIT_TABLE('D', display)); + ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('I', init)); + if (ret) + return ret; + parse_bit_table(bios, bitoffset, &BIT_TABLE('M', M)); /* memory? */ + parse_bit_table(bios, bitoffset, &BIT_TABLE('L', lvds)); + parse_bit_table(bios, bitoffset, &BIT_TABLE('T', tmds)); + parse_bit_table(bios, bitoffset, &BIT_TABLE('U', U)); + + return 0; +} + +static int parse_bmp_structure(struct drm_device *dev, struct nvbios *bios, unsigned int offset) +{ + /* + * Parses the BMP structure for useful things, but does not act on them + * + * offset + 5: BMP major version + * offset + 6: BMP minor version + * offset + 9: BMP feature byte + * offset + 10: BCD encoded BIOS version + * + * offset + 18: init script table pointer (for bios versions < 5.10h) + * offset + 20: extra init script table pointer (for bios + * versions < 5.10h) + * + * offset + 24: memory init table pointer (used on early bios versions) + * offset + 26: SDR memory sequencing setup data table + * offset + 28: DDR memory sequencing setup data table + * + * offset + 54: index of I2C CRTC pair to use for CRT output + * offset + 55: index of I2C CRTC pair to use for TV output + * offset + 56: index of I2C CRTC pair to use for flat panel output + * offset + 58: write CRTC index for I2C pair 0 + * offset + 59: read CRTC index for I2C pair 0 + * offset + 60: write CRTC index for I2C pair 1 + * offset + 61: read CRTC index for I2C pair 1 + * + * offset + 67: maximum internal PLL frequency (single stage PLL) + * offset + 71: minimum internal PLL frequency (single stage PLL) + * + * offset + 75: script table pointers, as described in + * parse_script_table_pointers + * + * offset + 89: TMDS single link output A table pointer + * offset + 91: TMDS single link output B table pointer + * offset + 95: LVDS single link output A table pointer + * offset + 105: flat panel timings table pointer + * offset + 107: flat panel strapping translation table pointer + * offset + 117: LVDS manufacturer panel config table pointer + * offset + 119: LVDS manufacturer strapping translation table pointer + * + * offset + 142: PLL limits table pointer + * + * offset + 156: minimum pixel clock for LVDS dual link + */ + + uint8_t *bmp = &bios->data[offset], bmp_version_major, bmp_version_minor; + uint16_t bmplength; + uint16_t legacy_scripts_offset, legacy_i2c_offset; + + /* load needed defaults in case we can't parse this info */ + bios->digital_min_front_porch = 0x4b; + bios->fmaxvco = 256000; + bios->fminvco = 128000; + bios->fp.duallink_transition_clk = 90000; + + bmp_version_major = bmp[5]; + bmp_version_minor = bmp[6]; + + NV_TRACE(dev, "BMP version %d.%d\n", + bmp_version_major, bmp_version_minor); + + /* + * Make sure that 0x36 is blank and can't be mistaken for a DCB + * pointer on early versions + */ + if (bmp_version_major < 5) + *(uint16_t *)&bios->data[0x36] = 0; + + /* + * Seems that the minor version was 1 for all major versions prior + * to 5. Version 6 could theoretically exist, but I suspect BIT + * happened instead. + */ + if ((bmp_version_major < 5 && bmp_version_minor != 1) || bmp_version_major > 5) { + NV_ERROR(dev, "You have an unsupported BMP version. " + "Please send in your bios\n"); + return -ENOSYS; + } + + if (bmp_version_major == 0) + /* nothing that's currently useful in this version */ + return 0; + else if (bmp_version_major == 1) + bmplength = 44; /* exact for 1.01 */ + else if (bmp_version_major == 2) + bmplength = 48; /* exact for 2.01 */ + else if (bmp_version_major == 3) + bmplength = 54; + /* guessed - mem init tables added in this version */ + else if (bmp_version_major == 4 || bmp_version_minor < 0x1) + /* don't know if 5.0 exists... */ + bmplength = 62; + /* guessed - BMP I2C indices added in version 4*/ + else if (bmp_version_minor < 0x6) + bmplength = 67; /* exact for 5.01 */ + else if (bmp_version_minor < 0x10) + bmplength = 75; /* exact for 5.06 */ + else if (bmp_version_minor == 0x10) + bmplength = 89; /* exact for 5.10h */ + else if (bmp_version_minor < 0x14) + bmplength = 118; /* exact for 5.11h */ + else if (bmp_version_minor < 0x24) + /* + * Not sure of version where pll limits came in; + * certainly exist by 0x24 though. + */ + /* length not exact: this is long enough to get lvds members */ + bmplength = 123; + else if (bmp_version_minor < 0x27) + /* + * Length not exact: this is long enough to get pll limit + * member + */ + bmplength = 144; + else + /* + * Length not exact: this is long enough to get dual link + * transition clock. + */ + bmplength = 158; + + /* checksum */ + if (nv_cksum(bmp, 8)) { + NV_ERROR(dev, "Bad BMP checksum\n"); + return -EINVAL; + } + + /* + * Bit 4 seems to indicate either a mobile bios or a quadro card -- + * mobile behaviour consistent (nv11+), quadro only seen nv18gl-nv36gl + * (not nv10gl), bit 5 that the flat panel tables are present, and + * bit 6 a tv bios. + */ + bios->feature_byte = bmp[9]; + + parse_bios_version(dev, bios, offset + 10); + + if (bmp_version_major < 5 || bmp_version_minor < 0x10) + bios->old_style_init = true; + legacy_scripts_offset = 18; + if (bmp_version_major < 2) + legacy_scripts_offset -= 4; + bios->init_script_tbls_ptr = ROM16(bmp[legacy_scripts_offset]); + bios->extra_init_script_tbl_ptr = ROM16(bmp[legacy_scripts_offset + 2]); + + if (bmp_version_major > 2) { /* appears in BMP 3 */ + bios->legacy.mem_init_tbl_ptr = ROM16(bmp[24]); + bios->legacy.sdr_seq_tbl_ptr = ROM16(bmp[26]); + bios->legacy.ddr_seq_tbl_ptr = ROM16(bmp[28]); + } + + legacy_i2c_offset = 0x48; /* BMP version 2 & 3 */ + if (bmplength > 61) + legacy_i2c_offset = offset + 54; + bios->legacy.i2c_indices.crt = bios->data[legacy_i2c_offset]; + bios->legacy.i2c_indices.tv = bios->data[legacy_i2c_offset + 1]; + bios->legacy.i2c_indices.panel = bios->data[legacy_i2c_offset + 2]; + + if (bmplength > 74) { + bios->fmaxvco = ROM32(bmp[67]); + bios->fminvco = ROM32(bmp[71]); + } + if (bmplength > 88) + parse_script_table_pointers(bios, offset + 75); + if (bmplength > 94) { + bios->tmds.output0_script_ptr = ROM16(bmp[89]); + bios->tmds.output1_script_ptr = ROM16(bmp[91]); + /* + * Never observed in use with lvds scripts, but is reused for + * 18/24 bit panel interface default for EDID equipped panels + * (if_is_24bit not set directly to avoid any oscillation). + */ + bios->legacy.lvds_single_a_script_ptr = ROM16(bmp[95]); + } + if (bmplength > 108) { + bios->fp.fptablepointer = ROM16(bmp[105]); + bios->fp.fpxlatetableptr = ROM16(bmp[107]); + bios->fp.xlatwidth = 1; + } + if (bmplength > 120) { + bios->fp.lvdsmanufacturerpointer = ROM16(bmp[117]); + bios->fp.fpxlatemanufacturertableptr = ROM16(bmp[119]); + } + if (bmplength > 143) + bios->pll_limit_tbl_ptr = ROM16(bmp[142]); + + if (bmplength > 157) + bios->fp.duallink_transition_clk = ROM16(bmp[156]) * 10; + + return 0; +} + +static uint16_t findstr(uint8_t *data, int n, const uint8_t *str, int len) +{ + int i, j; + + for (i = 0; i <= (n - len); i++) { + for (j = 0; j < len; j++) + if (data[i + j] != str[j]) + break; + if (j == len) + return i; + } + + return 0; +} + +void * +dcb_table(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u8 *dcb = NULL; + + if (dev_priv->card_type > NV_04) + dcb = ROMPTR(dev, dev_priv->vbios.data[0x36]); + if (!dcb) { + NV_WARNONCE(dev, "No DCB data found in VBIOS\n"); + return NULL; + } + + if (dcb[0] >= 0x41) { + NV_WARNONCE(dev, "DCB version 0x%02x unknown\n", dcb[0]); + return NULL; + } else + if (dcb[0] >= 0x30) { + if (ROM32(dcb[6]) == 0x4edcbdcb) + return dcb; + } else + if (dcb[0] >= 0x20) { + if (ROM32(dcb[4]) == 0x4edcbdcb) + return dcb; + } else + if (dcb[0] >= 0x15) { + if (!memcmp(&dcb[-7], "DEV_REC", 7)) + return dcb; + } else { + /* + * v1.4 (some NV15/16, NV11+) seems the same as v1.5, but + * always has the same single (crt) entry, even when tv-out + * present, so the conclusion is this version cannot really + * be used. + * + * v1.2 tables (some NV6/10, and NV15+) normally have the + * same 5 entries, which are not specific to the card and so + * no use. + * + * v1.2 does have an I2C table that read_dcb_i2c_table can + * handle, but cards exist (nv11 in #14821) with a bad i2c + * table pointer, so use the indices parsed in + * parse_bmp_structure. + * + * v1.1 (NV5+, maybe some NV4) is entirely unhelpful + */ + NV_WARNONCE(dev, "No useful DCB data in VBIOS\n"); + return NULL; + } + + NV_WARNONCE(dev, "DCB header validation failed\n"); + return NULL; +} + +void * +dcb_outp(struct drm_device *dev, u8 idx) +{ + u8 *dcb = dcb_table(dev); + if (dcb && dcb[0] >= 0x30) { + if (idx < dcb[2]) + return dcb + dcb[1] + (idx * dcb[3]); + } else + if (dcb && dcb[0] >= 0x20) { + u8 *i2c = ROMPTR(dev, dcb[2]); + u8 *ent = dcb + 8 + (idx * 8); + if (i2c && ent < i2c) + return ent; + } else + if (dcb && dcb[0] >= 0x15) { + u8 *i2c = ROMPTR(dev, dcb[2]); + u8 *ent = dcb + 4 + (idx * 10); + if (i2c && ent < i2c) + return ent; + } + + return NULL; +} + +int +dcb_outp_foreach(struct drm_device *dev, void *data, + int (*exec)(struct drm_device *, void *, int idx, u8 *outp)) +{ + int ret, idx = -1; + u8 *outp = NULL; + while ((outp = dcb_outp(dev, ++idx))) { + if (ROM32(outp[0]) == 0x00000000) + break; /* seen on an NV11 with DCB v1.5 */ + if (ROM32(outp[0]) == 0xffffffff) + break; /* seen on an NV17 with DCB v2.0 */ + + if ((outp[0] & 0x0f) == OUTPUT_UNUSED) + continue; + if ((outp[0] & 0x0f) == OUTPUT_EOL) + break; + + ret = exec(dev, data, idx, outp); + if (ret) + return ret; + } + + return 0; +} + +u8 * +dcb_conntab(struct drm_device *dev) +{ + u8 *dcb = dcb_table(dev); + if (dcb && dcb[0] >= 0x30 && dcb[1] >= 0x16) { + u8 *conntab = ROMPTR(dev, dcb[0x14]); + if (conntab && conntab[0] >= 0x30 && conntab[0] <= 0x40) + return conntab; + } + return NULL; +} + +u8 * +dcb_conn(struct drm_device *dev, u8 idx) +{ + u8 *conntab = dcb_conntab(dev); + if (conntab && idx < conntab[2]) + return conntab + conntab[1] + (idx * conntab[3]); + return NULL; +} + +static struct dcb_entry *new_dcb_entry(struct dcb_table *dcb) +{ + struct dcb_entry *entry = &dcb->entry[dcb->entries]; + + memset(entry, 0, sizeof(struct dcb_entry)); + entry->index = dcb->entries++; + + return entry; +} + +static void fabricate_dcb_output(struct dcb_table *dcb, int type, int i2c, + int heads, int or) +{ + struct dcb_entry *entry = new_dcb_entry(dcb); + + entry->type = type; + entry->i2c_index = i2c; + entry->heads = heads; + if (type != OUTPUT_ANALOG) + entry->location = !DCB_LOC_ON_CHIP; /* ie OFF CHIP */ + entry->or = or; +} + +static bool +parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb, + uint32_t conn, uint32_t conf, struct dcb_entry *entry) +{ + entry->type = conn & 0xf; + entry->i2c_index = (conn >> 4) & 0xf; + entry->heads = (conn >> 8) & 0xf; + entry->connector = (conn >> 12) & 0xf; + entry->bus = (conn >> 16) & 0xf; + entry->location = (conn >> 20) & 0x3; + entry->or = (conn >> 24) & 0xf; + + switch (entry->type) { + case OUTPUT_ANALOG: + /* + * Although the rest of a CRT conf dword is usually + * zeros, mac biosen have stuff there so we must mask + */ + entry->crtconf.maxfreq = (dcb->version < 0x30) ? + (conf & 0xffff) * 10 : + (conf & 0xff) * 10000; + break; + case OUTPUT_LVDS: + { + uint32_t mask; + if (conf & 0x1) + entry->lvdsconf.use_straps_for_mode = true; + if (dcb->version < 0x22) { + mask = ~0xd; + /* + * The laptop in bug 14567 lies and claims to not use + * straps when it does, so assume all DCB 2.0 laptops + * use straps, until a broken EDID using one is produced + */ + entry->lvdsconf.use_straps_for_mode = true; + /* + * Both 0x4 and 0x8 show up in v2.0 tables; assume they + * mean the same thing (probably wrong, but might work) + */ + if (conf & 0x4 || conf & 0x8) + entry->lvdsconf.use_power_scripts = true; + } else { + mask = ~0x7; + if (conf & 0x2) + entry->lvdsconf.use_acpi_for_edid = true; + if (conf & 0x4) + entry->lvdsconf.use_power_scripts = true; + entry->lvdsconf.sor.link = (conf & 0x00000030) >> 4; + } + if (conf & mask) { + /* + * Until we even try to use these on G8x, it's + * useless reporting unknown bits. They all are. + */ + if (dcb->version >= 0x40) + break; + + NV_ERROR(dev, "Unknown LVDS configuration bits, " + "please report\n"); + } + break; + } + case OUTPUT_TV: + { + if (dcb->version >= 0x30) + entry->tvconf.has_component_output = conf & (0x8 << 4); + else + entry->tvconf.has_component_output = false; + + break; + } + case OUTPUT_DP: + entry->dpconf.sor.link = (conf & 0x00000030) >> 4; + switch ((conf & 0x00e00000) >> 21) { + case 0: + entry->dpconf.link_bw = 162000; + break; + default: + entry->dpconf.link_bw = 270000; + break; + } + switch ((conf & 0x0f000000) >> 24) { + case 0xf: + entry->dpconf.link_nr = 4; + break; + case 0x3: + entry->dpconf.link_nr = 2; + break; + default: + entry->dpconf.link_nr = 1; + break; + } + break; + case OUTPUT_TMDS: + if (dcb->version >= 0x40) + entry->tmdsconf.sor.link = (conf & 0x00000030) >> 4; + else if (dcb->version >= 0x30) + entry->tmdsconf.slave_addr = (conf & 0x00000700) >> 8; + else if (dcb->version >= 0x22) + entry->tmdsconf.slave_addr = (conf & 0x00000070) >> 4; + + break; + case OUTPUT_EOL: + /* weird g80 mobile type that "nv" treats as a terminator */ + dcb->entries--; + return false; + default: + break; + } + + if (dcb->version < 0x40) { + /* Normal entries consist of a single bit, but dual link has + * the next most significant bit set too + */ + entry->duallink_possible = + ((1 << (ffs(entry->or) - 1)) * 3 == entry->or); + } else { + entry->duallink_possible = (entry->sorconf.link == 3); + } + + /* unsure what DCB version introduces this, 3.0? */ + if (conf & 0x100000) + entry->i2c_upper_default = true; + + return true; +} + +static bool +parse_dcb15_entry(struct drm_device *dev, struct dcb_table *dcb, + uint32_t conn, uint32_t conf, struct dcb_entry *entry) +{ + switch (conn & 0x0000000f) { + case 0: + entry->type = OUTPUT_ANALOG; + break; + case 1: + entry->type = OUTPUT_TV; + break; + case 2: + case 4: + if (conn & 0x10) + entry->type = OUTPUT_LVDS; + else + entry->type = OUTPUT_TMDS; + break; + case 3: + entry->type = OUTPUT_LVDS; + break; + default: + NV_ERROR(dev, "Unknown DCB type %d\n", conn & 0x0000000f); + return false; + } + + entry->i2c_index = (conn & 0x0003c000) >> 14; + entry->heads = ((conn & 0x001c0000) >> 18) + 1; + entry->or = entry->heads; /* same as heads, hopefully safe enough */ + entry->location = (conn & 0x01e00000) >> 21; + entry->bus = (conn & 0x0e000000) >> 25; + entry->duallink_possible = false; + + switch (entry->type) { + case OUTPUT_ANALOG: + entry->crtconf.maxfreq = (conf & 0xffff) * 10; + break; + case OUTPUT_TV: + entry->tvconf.has_component_output = false; + break; + case OUTPUT_LVDS: + if ((conn & 0x00003f00) >> 8 != 0x10) + entry->lvdsconf.use_straps_for_mode = true; + entry->lvdsconf.use_power_scripts = true; + break; + default: + break; + } + + return true; +} + +static +void merge_like_dcb_entries(struct drm_device *dev, struct dcb_table *dcb) +{ + /* + * DCB v2.0 lists each output combination separately. + * Here we merge compatible entries to have fewer outputs, with + * more options + */ + + int i, newentries = 0; + + for (i = 0; i < dcb->entries; i++) { + struct dcb_entry *ient = &dcb->entry[i]; + int j; + + for (j = i + 1; j < dcb->entries; j++) { + struct dcb_entry *jent = &dcb->entry[j]; + + if (jent->type == 100) /* already merged entry */ + continue; + + /* merge heads field when all other fields the same */ + if (jent->i2c_index == ient->i2c_index && + jent->type == ient->type && + jent->location == ient->location && + jent->or == ient->or) { + NV_TRACE(dev, "Merging DCB entries %d and %d\n", + i, j); + ient->heads |= jent->heads; + jent->type = 100; /* dummy value */ + } + } + } + + /* Compact entries merged into others out of dcb */ + for (i = 0; i < dcb->entries; i++) { + if (dcb->entry[i].type == 100) + continue; + + if (newentries != i) { + dcb->entry[newentries] = dcb->entry[i]; + dcb->entry[newentries].index = newentries; + } + newentries++; + } + + dcb->entries = newentries; +} + +static bool +apply_dcb_encoder_quirks(struct drm_device *dev, int idx, u32 *conn, u32 *conf) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_table *dcb = &dev_priv->vbios.dcb; + + /* Dell Precision M6300 + * DCB entry 2: 02025312 00000010 + * DCB entry 3: 02026312 00000020 + * + * Identical, except apparently a different connector on a + * different SOR link. Not a clue how we're supposed to know + * which one is in use if it even shares an i2c line... + * + * Ignore the connector on the second SOR link to prevent + * nasty problems until this is sorted (assuming it's not a + * VBIOS bug). + */ + if (nv_match_device(dev, 0x040d, 0x1028, 0x019b)) { + if (*conn == 0x02026312 && *conf == 0x00000020) + return false; + } + + /* GeForce3 Ti 200 + * + * DCB reports an LVDS output that should be TMDS: + * DCB entry 1: f2005014 ffffffff + */ + if (nv_match_device(dev, 0x0201, 0x1462, 0x8851)) { + if (*conn == 0xf2005014 && *conf == 0xffffffff) { + fabricate_dcb_output(dcb, OUTPUT_TMDS, 1, 1, 1); + return false; + } + } + + /* XFX GT-240X-YA + * + * So many things wrong here, replace the entire encoder table.. + */ + if (nv_match_device(dev, 0x0ca3, 0x1682, 0x3003)) { + if (idx == 0) { + *conn = 0x02001300; /* VGA, connector 1 */ + *conf = 0x00000028; + } else + if (idx == 1) { + *conn = 0x01010312; /* DVI, connector 0 */ + *conf = 0x00020030; + } else + if (idx == 2) { + *conn = 0x01010310; /* VGA, connector 0 */ + *conf = 0x00000028; + } else + if (idx == 3) { + *conn = 0x02022362; /* HDMI, connector 2 */ + *conf = 0x00020010; + } else { + *conn = 0x0000000e; /* EOL */ + *conf = 0x00000000; + } + } + + /* Some other twisted XFX board (rhbz#694914) + * + * The DVI/VGA encoder combo that's supposed to represent the + * DVI-I connector actually point at two different ones, and + * the HDMI connector ends up paired with the VGA instead. + * + * Connector table is missing anything for VGA at all, pointing it + * an invalid conntab entry 2 so we figure it out ourself. + */ + if (nv_match_device(dev, 0x0615, 0x1682, 0x2605)) { + if (idx == 0) { + *conn = 0x02002300; /* VGA, connector 2 */ + *conf = 0x00000028; + } else + if (idx == 1) { + *conn = 0x01010312; /* DVI, connector 0 */ + *conf = 0x00020030; + } else + if (idx == 2) { + *conn = 0x04020310; /* VGA, connector 0 */ + *conf = 0x00000028; + } else + if (idx == 3) { + *conn = 0x02021322; /* HDMI, connector 1 */ + *conf = 0x00020010; + } else { + *conn = 0x0000000e; /* EOL */ + *conf = 0x00000000; + } + } + + return true; +} + +static void +fabricate_dcb_encoder_table(struct drm_device *dev, struct nvbios *bios) +{ + struct dcb_table *dcb = &bios->dcb; + int all_heads = (nv_two_heads(dev) ? 3 : 1); + +#ifdef __powerpc__ + /* Apple iMac G4 NV17 */ + if (of_machine_is_compatible("PowerMac4,5")) { + fabricate_dcb_output(dcb, OUTPUT_TMDS, 0, all_heads, 1); + fabricate_dcb_output(dcb, OUTPUT_ANALOG, 1, all_heads, 2); + return; + } +#endif + + /* Make up some sane defaults */ + fabricate_dcb_output(dcb, OUTPUT_ANALOG, + bios->legacy.i2c_indices.crt, 1, 1); + + if (nv04_tv_identify(dev, bios->legacy.i2c_indices.tv) >= 0) + fabricate_dcb_output(dcb, OUTPUT_TV, + bios->legacy.i2c_indices.tv, + all_heads, 0); + + else if (bios->tmds.output0_script_ptr || + bios->tmds.output1_script_ptr) + fabricate_dcb_output(dcb, OUTPUT_TMDS, + bios->legacy.i2c_indices.panel, + all_heads, 1); +} + +static int +parse_dcb_entry(struct drm_device *dev, void *data, int idx, u8 *outp) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_table *dcb = &dev_priv->vbios.dcb; + u32 conf = (dcb->version >= 0x20) ? ROM32(outp[4]) : ROM32(outp[6]); + u32 conn = ROM32(outp[0]); + bool ret; + + if (apply_dcb_encoder_quirks(dev, idx, &conn, &conf)) { + struct dcb_entry *entry = new_dcb_entry(dcb); + + NV_TRACEWARN(dev, "DCB outp %02d: %08x %08x\n", idx, conn, conf); + + if (dcb->version >= 0x20) + ret = parse_dcb20_entry(dev, dcb, conn, conf, entry); + else + ret = parse_dcb15_entry(dev, dcb, conn, conf, entry); + if (!ret) + return 1; /* stop parsing */ + + /* Ignore the I2C index for on-chip TV-out, as there + * are cards with bogus values (nv31m in bug 23212), + * and it's otherwise useless. + */ + if (entry->type == OUTPUT_TV && + entry->location == DCB_LOC_ON_CHIP) + entry->i2c_index = 0x0f; + } + + return 0; +} + +static void +dcb_fake_connectors(struct nvbios *bios) +{ + struct dcb_table *dcbt = &bios->dcb; + u8 map[16] = { }; + int i, idx = 0; + + /* heuristic: if we ever get a non-zero connector field, assume + * that all the indices are valid and we don't need fake them. + * + * and, as usual, a blacklist of boards with bad bios data.. + */ + if (!nv_match_device(bios->dev, 0x0392, 0x107d, 0x20a2)) { + for (i = 0; i < dcbt->entries; i++) { + if (dcbt->entry[i].connector) + return; + } + } + + /* no useful connector info available, we need to make it up + * ourselves. the rule here is: anything on the same i2c bus + * is considered to be on the same connector. any output + * without an associated i2c bus is assigned its own unique + * connector index. + */ + for (i = 0; i < dcbt->entries; i++) { + u8 i2c = dcbt->entry[i].i2c_index; + if (i2c == 0x0f) { + dcbt->entry[i].connector = idx++; + } else { + if (!map[i2c]) + map[i2c] = ++idx; + dcbt->entry[i].connector = map[i2c] - 1; + } + } + + /* if we created more than one connector, destroy the connector + * table - just in case it has random, rather than stub, entries. + */ + if (i > 1) { + u8 *conntab = dcb_conntab(bios->dev); + if (conntab) + conntab[0] = 0x00; + } +} + +static int +parse_dcb_table(struct drm_device *dev, struct nvbios *bios) +{ + struct dcb_table *dcb = &bios->dcb; + u8 *dcbt, *conn; + int idx; + + dcbt = dcb_table(dev); + if (!dcbt) { + /* handle pre-DCB boards */ + if (bios->type == NVBIOS_BMP) { + fabricate_dcb_encoder_table(dev, bios); + return 0; + } + + return -EINVAL; + } + + NV_TRACE(dev, "DCB version %d.%d\n", dcbt[0] >> 4, dcbt[0] & 0xf); + + dcb->version = dcbt[0]; + dcb_outp_foreach(dev, NULL, parse_dcb_entry); + + /* + * apart for v2.1+ not being known for requiring merging, this + * guarantees dcbent->index is the index of the entry in the rom image + */ + if (dcb->version < 0x21) + merge_like_dcb_entries(dev, dcb); + + if (!dcb->entries) + return -ENXIO; + + /* dump connector table entries to log, if any exist */ + idx = -1; + while ((conn = dcb_conn(dev, ++idx))) { + if (conn[0] != 0xff) { + NV_TRACE(dev, "DCB conn %02d: ", idx); + if (dcb_conntab(dev)[3] < 4) + printk("%04x\n", ROM16(conn[0])); + else + printk("%08x\n", ROM32(conn[0])); + } + } + dcb_fake_connectors(bios); + return 0; +} + +static int load_nv17_hwsq_ucode_entry(struct drm_device *dev, struct nvbios *bios, uint16_t hwsq_offset, int entry) +{ + /* + * The header following the "HWSQ" signature has the number of entries, + * and the entry size + * + * An entry consists of a dword to write to the sequencer control reg + * (0x00001304), followed by the ucode bytes, written sequentially, + * starting at reg 0x00001400 + */ + + uint8_t bytes_to_write; + uint16_t hwsq_entry_offset; + int i; + + if (bios->data[hwsq_offset] <= entry) { + NV_ERROR(dev, "Too few entries in HW sequencer table for " + "requested entry\n"); + return -ENOENT; + } + + bytes_to_write = bios->data[hwsq_offset + 1]; + + if (bytes_to_write != 36) { + NV_ERROR(dev, "Unknown HW sequencer entry size\n"); + return -EINVAL; + } + + NV_TRACE(dev, "Loading NV17 power sequencing microcode\n"); + + hwsq_entry_offset = hwsq_offset + 2 + entry * bytes_to_write; + + /* set sequencer control */ + bios_wr32(bios, 0x00001304, ROM32(bios->data[hwsq_entry_offset])); + bytes_to_write -= 4; + + /* write ucode */ + for (i = 0; i < bytes_to_write; i += 4) + bios_wr32(bios, 0x00001400 + i, ROM32(bios->data[hwsq_entry_offset + i + 4])); + + /* twiddle NV_PBUS_DEBUG_4 */ + bios_wr32(bios, NV_PBUS_DEBUG_4, bios_rd32(bios, NV_PBUS_DEBUG_4) | 0x18); + + return 0; +} + +static int load_nv17_hw_sequencer_ucode(struct drm_device *dev, + struct nvbios *bios) +{ + /* + * BMP based cards, from NV17, need a microcode loading to correctly + * control the GPIO etc for LVDS panels + * + * BIT based cards seem to do this directly in the init scripts + * + * The microcode entries are found by the "HWSQ" signature. + */ + + const uint8_t hwsq_signature[] = { 'H', 'W', 'S', 'Q' }; + const int sz = sizeof(hwsq_signature); + int hwsq_offset; + + hwsq_offset = findstr(bios->data, bios->length, hwsq_signature, sz); + if (!hwsq_offset) + return 0; + + /* always use entry 0? */ + return load_nv17_hwsq_ucode_entry(dev, bios, hwsq_offset + sz, 0); +} + +uint8_t *nouveau_bios_embedded_edid(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + const uint8_t edid_sig[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; + uint16_t offset = 0; + uint16_t newoffset; + int searchlen = NV_PROM_SIZE; + + if (bios->fp.edid) + return bios->fp.edid; + + while (searchlen) { + newoffset = findstr(&bios->data[offset], searchlen, + edid_sig, 8); + if (!newoffset) + return NULL; + offset += newoffset; + if (!nv_cksum(&bios->data[offset], EDID1_LEN)) + break; + + searchlen -= offset; + offset++; + } + + NV_TRACE(dev, "Found EDID in BIOS\n"); + + return bios->fp.edid = &bios->data[offset]; +} + +void +nouveau_bios_run_init_table(struct drm_device *dev, uint16_t table, + struct dcb_entry *dcbent, int crtc) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct init_exec iexec = { true, false }; + + spin_lock_bh(&bios->lock); + bios->display.output = dcbent; + bios->display.crtc = crtc; + parse_init_table(bios, table, &iexec); + bios->display.output = NULL; + spin_unlock_bh(&bios->lock); +} + +void +nouveau_bios_init_exec(struct drm_device *dev, uint16_t table) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct init_exec iexec = { true, false }; + + parse_init_table(bios, table, &iexec); +} + +static bool NVInitVBIOS(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + + memset(bios, 0, sizeof(struct nvbios)); + spin_lock_init(&bios->lock); + bios->dev = dev; + + return bios_shadow(dev); +} + +static int nouveau_parse_vbios_struct(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + const uint8_t bit_signature[] = { 0xff, 0xb8, 'B', 'I', 'T' }; + const uint8_t bmp_signature[] = { 0xff, 0x7f, 'N', 'V', 0x0 }; + int offset; + + offset = findstr(bios->data, bios->length, + bit_signature, sizeof(bit_signature)); + if (offset) { + NV_TRACE(dev, "BIT BIOS found\n"); + bios->type = NVBIOS_BIT; + bios->offset = offset; + return parse_bit_structure(bios, offset + 6); + } + + offset = findstr(bios->data, bios->length, + bmp_signature, sizeof(bmp_signature)); + if (offset) { + NV_TRACE(dev, "BMP BIOS found\n"); + bios->type = NVBIOS_BMP; + bios->offset = offset; + return parse_bmp_structure(dev, bios, offset); + } + + NV_ERROR(dev, "No known BIOS signature found\n"); + return -ENODEV; +} + +int +nouveau_run_vbios_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + int i, ret = 0; + + /* Reset the BIOS head to 0. */ + bios->state.crtchead = 0; + + if (bios->major_version < 5) /* BMP only */ + load_nv17_hw_sequencer_ucode(dev, bios); + + if (bios->execute) { + bios->fp.last_script_invoc = 0; + bios->fp.lvds_init_run = false; + } + + parse_init_tables(bios); + + /* + * Runs some additional script seen on G8x VBIOSen. The VBIOS' + * parser will run this right after the init tables, the binary + * driver appears to run it at some point later. + */ + if (bios->some_script_ptr) { + struct init_exec iexec = {true, false}; + + NV_INFO(dev, "Parsing VBIOS init table at offset 0x%04X\n", + bios->some_script_ptr); + parse_init_table(bios, bios->some_script_ptr, &iexec); + } + + if (dev_priv->card_type >= NV_50) { + for (i = 0; i < bios->dcb.entries; i++) { + nouveau_bios_run_display_table(dev, 0, 0, + &bios->dcb.entry[i], -1); + } + } + + return ret; +} + +static bool +nouveau_bios_posted(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned htotal; + + if (dev_priv->card_type >= NV_50) { + if (NVReadVgaCrtc(dev, 0, 0x00) == 0 && + NVReadVgaCrtc(dev, 0, 0x1a) == 0) + return false; + return true; + } + + htotal = NVReadVgaCrtc(dev, 0, 0x06); + htotal |= (NVReadVgaCrtc(dev, 0, 0x07) & 0x01) << 8; + htotal |= (NVReadVgaCrtc(dev, 0, 0x07) & 0x20) << 4; + htotal |= (NVReadVgaCrtc(dev, 0, 0x25) & 0x01) << 10; + htotal |= (NVReadVgaCrtc(dev, 0, 0x41) & 0x01) << 11; + + return (htotal != 0); +} + +int +nouveau_bios_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + int ret; + + if (!NVInitVBIOS(dev)) + return -ENODEV; + + ret = nouveau_parse_vbios_struct(dev); + if (ret) + return ret; + + ret = nouveau_i2c_init(dev); + if (ret) + return ret; + + ret = nouveau_mxm_init(dev); + if (ret) + return ret; + + ret = parse_dcb_table(dev, bios); + if (ret) + return ret; + + if (!bios->major_version) /* we don't run version 0 bios */ + return 0; + + /* init script execution disabled */ + bios->execute = false; + + /* ... unless card isn't POSTed already */ + if (!nouveau_bios_posted(dev)) { + NV_INFO(dev, "Adaptor not initialised, " + "running VBIOS init tables.\n"); + bios->execute = true; + } + if (nouveau_force_post) + bios->execute = true; + + ret = nouveau_run_vbios_init(dev); + if (ret) + return ret; + + /* feature_byte on BMP is poor, but init always sets CR4B */ + if (bios->major_version < 5) + bios->is_mobile = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_4B) & 0x40; + + /* all BIT systems need p_f_m_t for digital_min_front_porch */ + if (bios->is_mobile || bios->major_version >= 5) + ret = parse_fp_mode_table(dev, bios); + + /* allow subsequent scripts to execute */ + bios->execute = true; + + return 0; +} + +void +nouveau_bios_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nouveau_mxm_fini(dev); + nouveau_i2c_fini(dev); + + kfree(dev_priv->vbios.data); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.h b/drivers/gpu/drm/nouveau/nouveau_bios.h new file mode 100644 index 00000000..298a3af4 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_bios.h @@ -0,0 +1,312 @@ +/* + * Copyright 2007-2008 Nouveau Project + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef __NOUVEAU_BIOS_H__ +#define __NOUVEAU_BIOS_H__ + +#include "nvreg.h" +#include "nouveau_i2c.h" + +#define DCB_MAX_NUM_ENTRIES 16 +#define DCB_MAX_NUM_I2C_ENTRIES 16 +#define DCB_MAX_NUM_GPIO_ENTRIES 32 +#define DCB_MAX_NUM_CONNECTOR_ENTRIES 16 + +#define DCB_LOC_ON_CHIP 0 + +#define ROM16(x) le16_to_cpu(*(u16 *)&(x)) +#define ROM32(x) le32_to_cpu(*(u32 *)&(x)) +#define ROM48(x) ({ u8 *p = &(x); (u64)ROM16(p[4]) << 32 | ROM32(p[0]); }) +#define ROM64(x) le64_to_cpu(*(u64 *)&(x)) +#define ROMPTR(d,x) ({ \ + struct drm_nouveau_private *dev_priv = (d)->dev_private; \ + ROM16(x) ? &dev_priv->vbios.data[ROM16(x)] : NULL; \ +}) + +struct bit_entry { + uint8_t id; + uint8_t version; + uint16_t length; + uint16_t offset; + uint8_t *data; +}; + +int bit_table(struct drm_device *, u8 id, struct bit_entry *); + +enum dcb_gpio_tag { + DCB_GPIO_PANEL_POWER = 0x01, + DCB_GPIO_TVDAC0 = 0x0c, + DCB_GPIO_TVDAC1 = 0x2d, + DCB_GPIO_PWM_FAN = 0x09, + DCB_GPIO_FAN_SENSE = 0x3d, + DCB_GPIO_UNUSED = 0xff +}; + +enum dcb_connector_type { + DCB_CONNECTOR_VGA = 0x00, + DCB_CONNECTOR_TV_0 = 0x10, + DCB_CONNECTOR_TV_1 = 0x11, + DCB_CONNECTOR_TV_3 = 0x13, + DCB_CONNECTOR_DVI_I = 0x30, + DCB_CONNECTOR_DVI_D = 0x31, + DCB_CONNECTOR_DMS59_0 = 0x38, + DCB_CONNECTOR_DMS59_1 = 0x39, + DCB_CONNECTOR_LVDS = 0x40, + DCB_CONNECTOR_LVDS_SPWG = 0x41, + DCB_CONNECTOR_DP = 0x46, + DCB_CONNECTOR_eDP = 0x47, + DCB_CONNECTOR_HDMI_0 = 0x60, + DCB_CONNECTOR_HDMI_1 = 0x61, + DCB_CONNECTOR_DMS59_DP0 = 0x64, + DCB_CONNECTOR_DMS59_DP1 = 0x65, + DCB_CONNECTOR_NONE = 0xff +}; + +enum dcb_type { + OUTPUT_ANALOG = 0, + OUTPUT_TV = 1, + OUTPUT_TMDS = 2, + OUTPUT_LVDS = 3, + OUTPUT_DP = 6, + OUTPUT_EOL = 14, /* DCB 4.0+, appears to be end-of-list */ + OUTPUT_UNUSED = 15, + OUTPUT_ANY = -1 +}; + +struct dcb_entry { + int index; /* may not be raw dcb index if merging has happened */ + enum dcb_type type; + uint8_t i2c_index; + uint8_t heads; + uint8_t connector; + uint8_t bus; + uint8_t location; + uint8_t or; + bool duallink_possible; + union { + struct sor_conf { + int link; + } sorconf; + struct { + int maxfreq; + } crtconf; + struct { + struct sor_conf sor; + bool use_straps_for_mode; + bool use_acpi_for_edid; + bool use_power_scripts; + } lvdsconf; + struct { + bool has_component_output; + } tvconf; + struct { + struct sor_conf sor; + int link_nr; + int link_bw; + } dpconf; + struct { + struct sor_conf sor; + int slave_addr; + } tmdsconf; + }; + bool i2c_upper_default; +}; + +struct dcb_table { + uint8_t version; + int entries; + struct dcb_entry entry[DCB_MAX_NUM_ENTRIES]; +}; + +enum nouveau_or { + OUTPUT_A = (1 << 0), + OUTPUT_B = (1 << 1), + OUTPUT_C = (1 << 2) +}; + +enum LVDS_script { + /* Order *does* matter here */ + LVDS_INIT = 1, + LVDS_RESET, + LVDS_BACKLIGHT_ON, + LVDS_BACKLIGHT_OFF, + LVDS_PANEL_ON, + LVDS_PANEL_OFF +}; + +/* these match types in pll limits table version 0x40, + * nouveau uses them on all chipsets internally where a + * specific pll needs to be referenced, but the exact + * register isn't known. + */ +enum pll_types { + PLL_CORE = 0x01, + PLL_SHADER = 0x02, + PLL_UNK03 = 0x03, + PLL_MEMORY = 0x04, + PLL_VDEC = 0x05, + PLL_UNK40 = 0x40, + PLL_UNK41 = 0x41, + PLL_UNK42 = 0x42, + PLL_VPLL0 = 0x80, + PLL_VPLL1 = 0x81, + PLL_MAX = 0xff +}; + +struct pll_lims { + u32 reg; + + struct { + int minfreq; + int maxfreq; + int min_inputfreq; + int max_inputfreq; + + uint8_t min_m; + uint8_t max_m; + uint8_t min_n; + uint8_t max_n; + } vco1, vco2; + + uint8_t max_log2p; + /* + * for most pre nv50 cards setting a log2P of 7 (the common max_log2p + * value) is no different to 6 (at least for vplls) so allowing the MNP + * calc to use 7 causes the generated clock to be out by a factor of 2. + * however, max_log2p cannot be fixed-up during parsing as the + * unmodified max_log2p value is still needed for setting mplls, hence + * an additional max_usable_log2p member + */ + uint8_t max_usable_log2p; + uint8_t log2p_bias; + + uint8_t min_p; + uint8_t max_p; + + int refclk; +}; + +struct nvbios { + struct drm_device *dev; + enum { + NVBIOS_BMP, + NVBIOS_BIT + } type; + uint16_t offset; + uint32_t length; + uint8_t *data; + + uint8_t chip_version; + + uint32_t dactestval; + uint32_t tvdactestval; + uint8_t digital_min_front_porch; + bool fp_no_ddc; + + spinlock_t lock; + + bool execute; + + uint8_t major_version; + uint8_t feature_byte; + bool is_mobile; + + uint32_t fmaxvco, fminvco; + + bool old_style_init; + uint16_t init_script_tbls_ptr; + uint16_t extra_init_script_tbl_ptr; + uint16_t macro_index_tbl_ptr; + uint16_t macro_tbl_ptr; + uint16_t condition_tbl_ptr; + uint16_t io_condition_tbl_ptr; + uint16_t io_flag_condition_tbl_ptr; + uint16_t init_function_tbl_ptr; + + uint16_t pll_limit_tbl_ptr; + uint16_t ram_restrict_tbl_ptr; + uint8_t ram_restrict_group_count; + + uint16_t some_script_ptr; /* BIT I + 14 */ + uint16_t init96_tbl_ptr; /* BIT I + 16 */ + + struct dcb_table dcb; + + struct { + int crtchead; + } state; + + struct { + struct dcb_entry *output; + int crtc; + uint16_t script_table_ptr; + } display; + + struct { + uint16_t fptablepointer; /* also used by tmds */ + uint16_t fpxlatetableptr; + int xlatwidth; + uint16_t lvdsmanufacturerpointer; + uint16_t fpxlatemanufacturertableptr; + uint16_t mode_ptr; + uint16_t xlated_entry; + bool power_off_for_reset; + bool reset_after_pclk_change; + bool dual_link; + bool link_c_increment; + bool if_is_24bit; + int duallink_transition_clk; + uint8_t strapless_is_24bit; + uint8_t *edid; + + /* will need resetting after suspend */ + int last_script_invoc; + bool lvds_init_run; + } fp; + + struct { + uint16_t output0_script_ptr; + uint16_t output1_script_ptr; + } tmds; + + struct { + uint16_t mem_init_tbl_ptr; + uint16_t sdr_seq_tbl_ptr; + uint16_t ddr_seq_tbl_ptr; + + struct { + uint8_t crt, tv, panel; + } i2c_indices; + + uint16_t lvds_single_a_script_ptr; + } legacy; +}; + +void *dcb_table(struct drm_device *); +void *dcb_outp(struct drm_device *, u8 idx); +int dcb_outp_foreach(struct drm_device *, void *data, + int (*)(struct drm_device *, void *, int idx, u8 *outp)); +u8 *dcb_conntab(struct drm_device *); +u8 *dcb_conn(struct drm_device *, u8 idx); + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c new file mode 100644 index 00000000..12ce044f --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c @@ -0,0 +1,1207 @@ +/* + * Copyright 2007 Dave Airlied + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +/* + * Authors: Dave Airlied <airlied@linux.ie> + * Ben Skeggs <darktama@iinet.net.au> + * Jeremy Kolb <jkolb@brandeis.edu> + */ + +#include "drmP.h" +#include "ttm/ttm_page_alloc.h" + +#include "nouveau_drm.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_mm.h" +#include "nouveau_vm.h" + +#include <linux/log2.h> +#include <linux/slab.h> + +static void +nouveau_bo_del_ttm(struct ttm_buffer_object *bo) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev); + struct drm_device *dev = dev_priv->dev; + struct nouveau_bo *nvbo = nouveau_bo(bo); + + if (unlikely(nvbo->gem)) + DRM_ERROR("bo %p still attached to GEM object\n", bo); + + nv10_mem_put_tile_region(dev, nvbo->tile, NULL); + kfree(nvbo); +} + +static void +nouveau_bo_fixup_align(struct nouveau_bo *nvbo, u32 flags, + int *align, int *size) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(nvbo->bo.bdev); + + if (dev_priv->card_type < NV_50) { + if (nvbo->tile_mode) { + if (dev_priv->chipset >= 0x40) { + *align = 65536; + *size = roundup(*size, 64 * nvbo->tile_mode); + + } else if (dev_priv->chipset >= 0x30) { + *align = 32768; + *size = roundup(*size, 64 * nvbo->tile_mode); + + } else if (dev_priv->chipset >= 0x20) { + *align = 16384; + *size = roundup(*size, 64 * nvbo->tile_mode); + + } else if (dev_priv->chipset >= 0x10) { + *align = 16384; + *size = roundup(*size, 32 * nvbo->tile_mode); + } + } + } else { + *size = roundup(*size, (1 << nvbo->page_shift)); + *align = max((1 << nvbo->page_shift), *align); + } + + *size = roundup(*size, PAGE_SIZE); +} + +int +nouveau_bo_new(struct drm_device *dev, int size, int align, + uint32_t flags, uint32_t tile_mode, uint32_t tile_flags, + struct nouveau_bo **pnvbo) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_bo *nvbo; + size_t acc_size; + int ret; + + nvbo = kzalloc(sizeof(struct nouveau_bo), GFP_KERNEL); + if (!nvbo) + return -ENOMEM; + INIT_LIST_HEAD(&nvbo->head); + INIT_LIST_HEAD(&nvbo->entry); + INIT_LIST_HEAD(&nvbo->vma_list); + nvbo->tile_mode = tile_mode; + nvbo->tile_flags = tile_flags; + nvbo->bo.bdev = &dev_priv->ttm.bdev; + + nvbo->page_shift = 12; + if (dev_priv->bar1_vm) { + if (!(flags & TTM_PL_FLAG_TT) && size > 256 * 1024) + nvbo->page_shift = dev_priv->bar1_vm->lpg_shift; + } + + nouveau_bo_fixup_align(nvbo, flags, &align, &size); + nvbo->bo.mem.num_pages = size >> PAGE_SHIFT; + nouveau_bo_placement_set(nvbo, flags, 0); + + acc_size = ttm_bo_dma_acc_size(&dev_priv->ttm.bdev, size, + sizeof(struct nouveau_bo)); + + ret = ttm_bo_init(&dev_priv->ttm.bdev, &nvbo->bo, size, + ttm_bo_type_device, &nvbo->placement, + align >> PAGE_SHIFT, 0, false, NULL, acc_size, + nouveau_bo_del_ttm); + if (ret) { + /* ttm will call nouveau_bo_del_ttm if it fails.. */ + return ret; + } + + *pnvbo = nvbo; + return 0; +} + +static void +set_placement_list(uint32_t *pl, unsigned *n, uint32_t type, uint32_t flags) +{ + *n = 0; + + if (type & TTM_PL_FLAG_VRAM) + pl[(*n)++] = TTM_PL_FLAG_VRAM | flags; + if (type & TTM_PL_FLAG_TT) + pl[(*n)++] = TTM_PL_FLAG_TT | flags; + if (type & TTM_PL_FLAG_SYSTEM) + pl[(*n)++] = TTM_PL_FLAG_SYSTEM | flags; +} + +static void +set_placement_range(struct nouveau_bo *nvbo, uint32_t type) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(nvbo->bo.bdev); + int vram_pages = dev_priv->vram_size >> PAGE_SHIFT; + + if (dev_priv->card_type == NV_10 && + nvbo->tile_mode && (type & TTM_PL_FLAG_VRAM) && + nvbo->bo.mem.num_pages < vram_pages / 4) { + /* + * Make sure that the color and depth buffers are handled + * by independent memory controller units. Up to a 9x + * speed up when alpha-blending and depth-test are enabled + * at the same time. + */ + if (nvbo->tile_flags & NOUVEAU_GEM_TILE_ZETA) { + nvbo->placement.fpfn = vram_pages / 2; + nvbo->placement.lpfn = ~0; + } else { + nvbo->placement.fpfn = 0; + nvbo->placement.lpfn = vram_pages / 2; + } + } +} + +void +nouveau_bo_placement_set(struct nouveau_bo *nvbo, uint32_t type, uint32_t busy) +{ + struct ttm_placement *pl = &nvbo->placement; + uint32_t flags = TTM_PL_MASK_CACHING | + (nvbo->pin_refcnt ? TTM_PL_FLAG_NO_EVICT : 0); + + pl->placement = nvbo->placements; + set_placement_list(nvbo->placements, &pl->num_placement, + type, flags); + + pl->busy_placement = nvbo->busy_placements; + set_placement_list(nvbo->busy_placements, &pl->num_busy_placement, + type | busy, flags); + + set_placement_range(nvbo, type); +} + +int +nouveau_bo_pin(struct nouveau_bo *nvbo, uint32_t memtype) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(nvbo->bo.bdev); + struct ttm_buffer_object *bo = &nvbo->bo; + int ret; + + if (nvbo->pin_refcnt && !(memtype & (1 << bo->mem.mem_type))) { + NV_ERROR(nouveau_bdev(bo->bdev)->dev, + "bo %p pinned elsewhere: 0x%08x vs 0x%08x\n", bo, + 1 << bo->mem.mem_type, memtype); + return -EINVAL; + } + + if (nvbo->pin_refcnt++) + return 0; + + ret = ttm_bo_reserve(bo, false, false, false, 0); + if (ret) + goto out; + + nouveau_bo_placement_set(nvbo, memtype, 0); + + ret = nouveau_bo_validate(nvbo, false, false, false); + if (ret == 0) { + switch (bo->mem.mem_type) { + case TTM_PL_VRAM: + dev_priv->fb_aper_free -= bo->mem.size; + break; + case TTM_PL_TT: + dev_priv->gart_info.aper_free -= bo->mem.size; + break; + default: + break; + } + } + ttm_bo_unreserve(bo); +out: + if (unlikely(ret)) + nvbo->pin_refcnt--; + return ret; +} + +int +nouveau_bo_unpin(struct nouveau_bo *nvbo) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(nvbo->bo.bdev); + struct ttm_buffer_object *bo = &nvbo->bo; + int ret; + + if (--nvbo->pin_refcnt) + return 0; + + ret = ttm_bo_reserve(bo, false, false, false, 0); + if (ret) + return ret; + + nouveau_bo_placement_set(nvbo, bo->mem.placement, 0); + + ret = nouveau_bo_validate(nvbo, false, false, false); + if (ret == 0) { + switch (bo->mem.mem_type) { + case TTM_PL_VRAM: + dev_priv->fb_aper_free += bo->mem.size; + break; + case TTM_PL_TT: + dev_priv->gart_info.aper_free += bo->mem.size; + break; + default: + break; + } + } + + ttm_bo_unreserve(bo); + return ret; +} + +int +nouveau_bo_map(struct nouveau_bo *nvbo) +{ + int ret; + + ret = ttm_bo_reserve(&nvbo->bo, false, false, false, 0); + if (ret) + return ret; + + ret = ttm_bo_kmap(&nvbo->bo, 0, nvbo->bo.mem.num_pages, &nvbo->kmap); + ttm_bo_unreserve(&nvbo->bo); + return ret; +} + +void +nouveau_bo_unmap(struct nouveau_bo *nvbo) +{ + if (nvbo) + ttm_bo_kunmap(&nvbo->kmap); +} + +int +nouveau_bo_validate(struct nouveau_bo *nvbo, bool interruptible, + bool no_wait_reserve, bool no_wait_gpu) +{ + int ret; + + ret = ttm_bo_validate(&nvbo->bo, &nvbo->placement, interruptible, + no_wait_reserve, no_wait_gpu); + if (ret) + return ret; + + return 0; +} + +u16 +nouveau_bo_rd16(struct nouveau_bo *nvbo, unsigned index) +{ + bool is_iomem; + u16 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem); + mem = &mem[index]; + if (is_iomem) + return ioread16_native((void __force __iomem *)mem); + else + return *mem; +} + +void +nouveau_bo_wr16(struct nouveau_bo *nvbo, unsigned index, u16 val) +{ + bool is_iomem; + u16 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem); + mem = &mem[index]; + if (is_iomem) + iowrite16_native(val, (void __force __iomem *)mem); + else + *mem = val; +} + +u32 +nouveau_bo_rd32(struct nouveau_bo *nvbo, unsigned index) +{ + bool is_iomem; + u32 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem); + mem = &mem[index]; + if (is_iomem) + return ioread32_native((void __force __iomem *)mem); + else + return *mem; +} + +void +nouveau_bo_wr32(struct nouveau_bo *nvbo, unsigned index, u32 val) +{ + bool is_iomem; + u32 *mem = ttm_kmap_obj_virtual(&nvbo->kmap, &is_iomem); + mem = &mem[index]; + if (is_iomem) + iowrite32_native(val, (void __force __iomem *)mem); + else + *mem = val; +} + +static struct ttm_tt * +nouveau_ttm_tt_create(struct ttm_bo_device *bdev, + unsigned long size, uint32_t page_flags, + struct page *dummy_read_page) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bdev); + struct drm_device *dev = dev_priv->dev; + + switch (dev_priv->gart_info.type) { +#if __OS_HAS_AGP + case NOUVEAU_GART_AGP: + return ttm_agp_tt_create(bdev, dev->agp->bridge, + size, page_flags, dummy_read_page); +#endif + case NOUVEAU_GART_PDMA: + case NOUVEAU_GART_HW: + return nouveau_sgdma_create_ttm(bdev, size, page_flags, + dummy_read_page); + default: + NV_ERROR(dev, "Unknown GART type %d\n", + dev_priv->gart_info.type); + break; + } + + return NULL; +} + +static int +nouveau_bo_invalidate_caches(struct ttm_bo_device *bdev, uint32_t flags) +{ + /* We'll do this from user space. */ + return 0; +} + +static int +nouveau_bo_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, + struct ttm_mem_type_manager *man) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bdev); + struct drm_device *dev = dev_priv->dev; + + switch (type) { + case TTM_PL_SYSTEM: + man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; + man->available_caching = TTM_PL_MASK_CACHING; + man->default_caching = TTM_PL_FLAG_CACHED; + break; + case TTM_PL_VRAM: + if (dev_priv->card_type >= NV_50) { + man->func = &nouveau_vram_manager; + man->io_reserve_fastpath = false; + man->use_io_reserve_lru = true; + } else { + man->func = &ttm_bo_manager_func; + } + man->flags = TTM_MEMTYPE_FLAG_FIXED | + TTM_MEMTYPE_FLAG_MAPPABLE; + man->available_caching = TTM_PL_FLAG_UNCACHED | + TTM_PL_FLAG_WC; + man->default_caching = TTM_PL_FLAG_WC; + break; + case TTM_PL_TT: + if (dev_priv->card_type >= NV_50) + man->func = &nouveau_gart_manager; + else + man->func = &ttm_bo_manager_func; + switch (dev_priv->gart_info.type) { + case NOUVEAU_GART_AGP: + man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; + man->available_caching = TTM_PL_FLAG_UNCACHED | + TTM_PL_FLAG_WC; + man->default_caching = TTM_PL_FLAG_WC; + break; + case NOUVEAU_GART_PDMA: + case NOUVEAU_GART_HW: + man->flags = TTM_MEMTYPE_FLAG_MAPPABLE | + TTM_MEMTYPE_FLAG_CMA; + man->available_caching = TTM_PL_MASK_CACHING; + man->default_caching = TTM_PL_FLAG_CACHED; + break; + default: + NV_ERROR(dev, "Unknown GART type: %d\n", + dev_priv->gart_info.type); + return -EINVAL; + } + break; + default: + NV_ERROR(dev, "Unsupported memory type %u\n", (unsigned)type); + return -EINVAL; + } + return 0; +} + +static void +nouveau_bo_evict_flags(struct ttm_buffer_object *bo, struct ttm_placement *pl) +{ + struct nouveau_bo *nvbo = nouveau_bo(bo); + + switch (bo->mem.mem_type) { + case TTM_PL_VRAM: + nouveau_bo_placement_set(nvbo, TTM_PL_FLAG_TT, + TTM_PL_FLAG_SYSTEM); + break; + default: + nouveau_bo_placement_set(nvbo, TTM_PL_FLAG_SYSTEM, 0); + break; + } + + *pl = nvbo->placement; +} + + +/* GPU-assisted copy using NV_MEMORY_TO_MEMORY_FORMAT, can access + * TTM_PL_{VRAM,TT} directly. + */ + +static int +nouveau_bo_move_accel_cleanup(struct nouveau_channel *chan, + struct nouveau_bo *nvbo, bool evict, + bool no_wait_reserve, bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + struct nouveau_fence *fence = NULL; + int ret; + + ret = nouveau_fence_new(chan, &fence, true); + if (ret) + return ret; + + ret = ttm_bo_move_accel_cleanup(&nvbo->bo, fence, NULL, evict, + no_wait_reserve, no_wait_gpu, new_mem); + nouveau_fence_unref(&fence); + return ret; +} + +static int +nvc0_bo_move_m2mf(struct nouveau_channel *chan, struct ttm_buffer_object *bo, + struct ttm_mem_reg *old_mem, struct ttm_mem_reg *new_mem) +{ + struct nouveau_mem *node = old_mem->mm_node; + u64 src_offset = node->vma[0].offset; + u64 dst_offset = node->vma[1].offset; + u32 page_count = new_mem->num_pages; + int ret; + + page_count = new_mem->num_pages; + while (page_count) { + int line_count = (page_count > 2047) ? 2047 : page_count; + + ret = RING_SPACE(chan, 12); + if (ret) + return ret; + + BEGIN_NVC0(chan, 2, NvSubM2MF, 0x0238, 2); + OUT_RING (chan, upper_32_bits(dst_offset)); + OUT_RING (chan, lower_32_bits(dst_offset)); + BEGIN_NVC0(chan, 2, NvSubM2MF, 0x030c, 6); + OUT_RING (chan, upper_32_bits(src_offset)); + OUT_RING (chan, lower_32_bits(src_offset)); + OUT_RING (chan, PAGE_SIZE); /* src_pitch */ + OUT_RING (chan, PAGE_SIZE); /* dst_pitch */ + OUT_RING (chan, PAGE_SIZE); /* line_length */ + OUT_RING (chan, line_count); + BEGIN_NVC0(chan, 2, NvSubM2MF, 0x0300, 1); + OUT_RING (chan, 0x00100110); + + page_count -= line_count; + src_offset += (PAGE_SIZE * line_count); + dst_offset += (PAGE_SIZE * line_count); + } + + return 0; +} + +static int +nv50_bo_move_m2mf(struct nouveau_channel *chan, struct ttm_buffer_object *bo, + struct ttm_mem_reg *old_mem, struct ttm_mem_reg *new_mem) +{ + struct nouveau_mem *node = old_mem->mm_node; + struct nouveau_bo *nvbo = nouveau_bo(bo); + u64 length = (new_mem->num_pages << PAGE_SHIFT); + u64 src_offset = node->vma[0].offset; + u64 dst_offset = node->vma[1].offset; + int ret; + + while (length) { + u32 amount, stride, height; + + amount = min(length, (u64)(4 * 1024 * 1024)); + stride = 16 * 4; + height = amount / stride; + + if (new_mem->mem_type == TTM_PL_VRAM && + nouveau_bo_tile_layout(nvbo)) { + ret = RING_SPACE(chan, 8); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubM2MF, 0x0200, 7); + OUT_RING (chan, 0); + OUT_RING (chan, 0); + OUT_RING (chan, stride); + OUT_RING (chan, height); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, 0); + } else { + ret = RING_SPACE(chan, 2); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubM2MF, 0x0200, 1); + OUT_RING (chan, 1); + } + if (old_mem->mem_type == TTM_PL_VRAM && + nouveau_bo_tile_layout(nvbo)) { + ret = RING_SPACE(chan, 8); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubM2MF, 0x021c, 7); + OUT_RING (chan, 0); + OUT_RING (chan, 0); + OUT_RING (chan, stride); + OUT_RING (chan, height); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, 0); + } else { + ret = RING_SPACE(chan, 2); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubM2MF, 0x021c, 1); + OUT_RING (chan, 1); + } + + ret = RING_SPACE(chan, 14); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubM2MF, 0x0238, 2); + OUT_RING (chan, upper_32_bits(src_offset)); + OUT_RING (chan, upper_32_bits(dst_offset)); + BEGIN_RING(chan, NvSubM2MF, 0x030c, 8); + OUT_RING (chan, lower_32_bits(src_offset)); + OUT_RING (chan, lower_32_bits(dst_offset)); + OUT_RING (chan, stride); + OUT_RING (chan, stride); + OUT_RING (chan, stride); + OUT_RING (chan, height); + OUT_RING (chan, 0x00000101); + OUT_RING (chan, 0x00000000); + BEGIN_RING(chan, NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_NOP, 1); + OUT_RING (chan, 0); + + length -= amount; + src_offset += amount; + dst_offset += amount; + } + + return 0; +} + +static inline uint32_t +nouveau_bo_mem_ctxdma(struct ttm_buffer_object *bo, + struct nouveau_channel *chan, struct ttm_mem_reg *mem) +{ + if (mem->mem_type == TTM_PL_TT) + return chan->gart_handle; + return chan->vram_handle; +} + +static int +nv04_bo_move_m2mf(struct nouveau_channel *chan, struct ttm_buffer_object *bo, + struct ttm_mem_reg *old_mem, struct ttm_mem_reg *new_mem) +{ + u32 src_offset = old_mem->start << PAGE_SHIFT; + u32 dst_offset = new_mem->start << PAGE_SHIFT; + u32 page_count = new_mem->num_pages; + int ret; + + ret = RING_SPACE(chan, 3); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_DMA_SOURCE, 2); + OUT_RING (chan, nouveau_bo_mem_ctxdma(bo, chan, old_mem)); + OUT_RING (chan, nouveau_bo_mem_ctxdma(bo, chan, new_mem)); + + page_count = new_mem->num_pages; + while (page_count) { + int line_count = (page_count > 2047) ? 2047 : page_count; + + ret = RING_SPACE(chan, 11); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubM2MF, + NV_MEMORY_TO_MEMORY_FORMAT_OFFSET_IN, 8); + OUT_RING (chan, src_offset); + OUT_RING (chan, dst_offset); + OUT_RING (chan, PAGE_SIZE); /* src_pitch */ + OUT_RING (chan, PAGE_SIZE); /* dst_pitch */ + OUT_RING (chan, PAGE_SIZE); /* line_length */ + OUT_RING (chan, line_count); + OUT_RING (chan, 0x00000101); + OUT_RING (chan, 0x00000000); + BEGIN_RING(chan, NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_NOP, 1); + OUT_RING (chan, 0); + + page_count -= line_count; + src_offset += (PAGE_SIZE * line_count); + dst_offset += (PAGE_SIZE * line_count); + } + + return 0; +} + +static int +nouveau_vma_getmap(struct nouveau_channel *chan, struct nouveau_bo *nvbo, + struct ttm_mem_reg *mem, struct nouveau_vma *vma) +{ + struct nouveau_mem *node = mem->mm_node; + int ret; + + ret = nouveau_vm_get(chan->vm, mem->num_pages << PAGE_SHIFT, + node->page_shift, NV_MEM_ACCESS_RO, vma); + if (ret) + return ret; + + if (mem->mem_type == TTM_PL_VRAM) + nouveau_vm_map(vma, node); + else + nouveau_vm_map_sg(vma, 0, mem->num_pages << PAGE_SHIFT, node); + + return 0; +} + +static int +nouveau_bo_move_m2mf(struct ttm_buffer_object *bo, int evict, bool intr, + bool no_wait_reserve, bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev); + struct nouveau_channel *chan = chan = dev_priv->channel; + struct nouveau_bo *nvbo = nouveau_bo(bo); + struct ttm_mem_reg *old_mem = &bo->mem; + int ret; + + mutex_lock_nested(&chan->mutex, NOUVEAU_KCHANNEL_MUTEX); + + /* create temporary vmas for the transfer and attach them to the + * old nouveau_mem node, these will get cleaned up after ttm has + * destroyed the ttm_mem_reg + */ + if (dev_priv->card_type >= NV_50) { + struct nouveau_mem *node = old_mem->mm_node; + + ret = nouveau_vma_getmap(chan, nvbo, old_mem, &node->vma[0]); + if (ret) + goto out; + + ret = nouveau_vma_getmap(chan, nvbo, new_mem, &node->vma[1]); + if (ret) + goto out; + } + + if (dev_priv->card_type < NV_50) + ret = nv04_bo_move_m2mf(chan, bo, &bo->mem, new_mem); + else + if (dev_priv->card_type < NV_C0) + ret = nv50_bo_move_m2mf(chan, bo, &bo->mem, new_mem); + else + ret = nvc0_bo_move_m2mf(chan, bo, &bo->mem, new_mem); + if (ret == 0) { + ret = nouveau_bo_move_accel_cleanup(chan, nvbo, evict, + no_wait_reserve, + no_wait_gpu, new_mem); + } + +out: + mutex_unlock(&chan->mutex); + return ret; +} + +static int +nouveau_bo_move_flipd(struct ttm_buffer_object *bo, bool evict, bool intr, + bool no_wait_reserve, bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + u32 placement_memtype = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING; + struct ttm_placement placement; + struct ttm_mem_reg tmp_mem; + int ret; + + placement.fpfn = placement.lpfn = 0; + placement.num_placement = placement.num_busy_placement = 1; + placement.placement = placement.busy_placement = &placement_memtype; + + tmp_mem = *new_mem; + tmp_mem.mm_node = NULL; + ret = ttm_bo_mem_space(bo, &placement, &tmp_mem, intr, no_wait_reserve, no_wait_gpu); + if (ret) + return ret; + + ret = ttm_tt_bind(bo->ttm, &tmp_mem); + if (ret) + goto out; + + ret = nouveau_bo_move_m2mf(bo, true, intr, no_wait_reserve, no_wait_gpu, &tmp_mem); + if (ret) + goto out; + + ret = ttm_bo_move_ttm(bo, true, no_wait_reserve, no_wait_gpu, new_mem); +out: + ttm_bo_mem_put(bo, &tmp_mem); + return ret; +} + +static int +nouveau_bo_move_flips(struct ttm_buffer_object *bo, bool evict, bool intr, + bool no_wait_reserve, bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + u32 placement_memtype = TTM_PL_FLAG_TT | TTM_PL_MASK_CACHING; + struct ttm_placement placement; + struct ttm_mem_reg tmp_mem; + int ret; + + placement.fpfn = placement.lpfn = 0; + placement.num_placement = placement.num_busy_placement = 1; + placement.placement = placement.busy_placement = &placement_memtype; + + tmp_mem = *new_mem; + tmp_mem.mm_node = NULL; + ret = ttm_bo_mem_space(bo, &placement, &tmp_mem, intr, no_wait_reserve, no_wait_gpu); + if (ret) + return ret; + + ret = ttm_bo_move_ttm(bo, true, no_wait_reserve, no_wait_gpu, &tmp_mem); + if (ret) + goto out; + + ret = nouveau_bo_move_m2mf(bo, true, intr, no_wait_reserve, no_wait_gpu, new_mem); + if (ret) + goto out; + +out: + ttm_bo_mem_put(bo, &tmp_mem); + return ret; +} + +static void +nouveau_bo_move_ntfy(struct ttm_buffer_object *bo, struct ttm_mem_reg *new_mem) +{ + struct nouveau_bo *nvbo = nouveau_bo(bo); + struct nouveau_vma *vma; + + /* ttm can now (stupidly) pass the driver bos it didn't create... */ + if (bo->destroy != nouveau_bo_del_ttm) + return; + + list_for_each_entry(vma, &nvbo->vma_list, head) { + if (new_mem && new_mem->mem_type == TTM_PL_VRAM) { + nouveau_vm_map(vma, new_mem->mm_node); + } else + if (new_mem && new_mem->mem_type == TTM_PL_TT && + nvbo->page_shift == vma->vm->spg_shift) { + nouveau_vm_map_sg(vma, 0, new_mem-> + num_pages << PAGE_SHIFT, + new_mem->mm_node); + } else { + nouveau_vm_unmap(vma); + } + } +} + +static int +nouveau_bo_vm_bind(struct ttm_buffer_object *bo, struct ttm_mem_reg *new_mem, + struct nouveau_tile_reg **new_tile) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev); + struct drm_device *dev = dev_priv->dev; + struct nouveau_bo *nvbo = nouveau_bo(bo); + u64 offset = new_mem->start << PAGE_SHIFT; + + *new_tile = NULL; + if (new_mem->mem_type != TTM_PL_VRAM) + return 0; + + if (dev_priv->card_type >= NV_10) { + *new_tile = nv10_mem_set_tiling(dev, offset, new_mem->size, + nvbo->tile_mode, + nvbo->tile_flags); + } + + return 0; +} + +static void +nouveau_bo_vm_cleanup(struct ttm_buffer_object *bo, + struct nouveau_tile_reg *new_tile, + struct nouveau_tile_reg **old_tile) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev); + struct drm_device *dev = dev_priv->dev; + + nv10_mem_put_tile_region(dev, *old_tile, bo->sync_obj); + *old_tile = new_tile; +} + +static int +nouveau_bo_move(struct ttm_buffer_object *bo, bool evict, bool intr, + bool no_wait_reserve, bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev); + struct nouveau_bo *nvbo = nouveau_bo(bo); + struct ttm_mem_reg *old_mem = &bo->mem; + struct nouveau_tile_reg *new_tile = NULL; + int ret = 0; + + if (dev_priv->card_type < NV_50) { + ret = nouveau_bo_vm_bind(bo, new_mem, &new_tile); + if (ret) + return ret; + } + + /* Fake bo copy. */ + if (old_mem->mem_type == TTM_PL_SYSTEM && !bo->ttm) { + BUG_ON(bo->mem.mm_node != NULL); + bo->mem = *new_mem; + new_mem->mm_node = NULL; + goto out; + } + + /* Software copy if the card isn't up and running yet. */ + if (!dev_priv->channel) { + ret = ttm_bo_move_memcpy(bo, evict, no_wait_reserve, no_wait_gpu, new_mem); + goto out; + } + + /* Hardware assisted copy. */ + if (new_mem->mem_type == TTM_PL_SYSTEM) + ret = nouveau_bo_move_flipd(bo, evict, intr, no_wait_reserve, no_wait_gpu, new_mem); + else if (old_mem->mem_type == TTM_PL_SYSTEM) + ret = nouveau_bo_move_flips(bo, evict, intr, no_wait_reserve, no_wait_gpu, new_mem); + else + ret = nouveau_bo_move_m2mf(bo, evict, intr, no_wait_reserve, no_wait_gpu, new_mem); + + if (!ret) + goto out; + + /* Fallback to software copy. */ + ret = ttm_bo_move_memcpy(bo, evict, no_wait_reserve, no_wait_gpu, new_mem); + +out: + if (dev_priv->card_type < NV_50) { + if (ret) + nouveau_bo_vm_cleanup(bo, NULL, &new_tile); + else + nouveau_bo_vm_cleanup(bo, new_tile, &nvbo->tile); + } + + return ret; +} + +static int +nouveau_bo_verify_access(struct ttm_buffer_object *bo, struct file *filp) +{ + return 0; +} + +static int +nouveau_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem) +{ + struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type]; + struct drm_nouveau_private *dev_priv = nouveau_bdev(bdev); + struct drm_device *dev = dev_priv->dev; + int ret; + + mem->bus.addr = NULL; + mem->bus.offset = 0; + mem->bus.size = mem->num_pages << PAGE_SHIFT; + mem->bus.base = 0; + mem->bus.is_iomem = false; + if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE)) + return -EINVAL; + switch (mem->mem_type) { + case TTM_PL_SYSTEM: + /* System memory */ + return 0; + case TTM_PL_TT: +#if __OS_HAS_AGP + if (dev_priv->gart_info.type == NOUVEAU_GART_AGP) { + mem->bus.offset = mem->start << PAGE_SHIFT; + mem->bus.base = dev_priv->gart_info.aper_base; + mem->bus.is_iomem = true; + } +#endif + break; + case TTM_PL_VRAM: + { + struct nouveau_mem *node = mem->mm_node; + u8 page_shift; + + if (!dev_priv->bar1_vm) { + mem->bus.offset = mem->start << PAGE_SHIFT; + mem->bus.base = pci_resource_start(dev->pdev, 1); + mem->bus.is_iomem = true; + break; + } + + if (dev_priv->card_type >= NV_C0) + page_shift = node->page_shift; + else + page_shift = 12; + + ret = nouveau_vm_get(dev_priv->bar1_vm, mem->bus.size, + page_shift, NV_MEM_ACCESS_RW, + &node->bar_vma); + if (ret) + return ret; + + nouveau_vm_map(&node->bar_vma, node); + if (ret) { + nouveau_vm_put(&node->bar_vma); + return ret; + } + + mem->bus.offset = node->bar_vma.offset; + if (dev_priv->card_type == NV_50) /*XXX*/ + mem->bus.offset -= 0x0020000000ULL; + mem->bus.base = pci_resource_start(dev->pdev, 1); + mem->bus.is_iomem = true; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static void +nouveau_ttm_io_mem_free(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bdev); + struct nouveau_mem *node = mem->mm_node; + + if (!dev_priv->bar1_vm || mem->mem_type != TTM_PL_VRAM) + return; + + if (!node->bar_vma.node) + return; + + nouveau_vm_unmap(&node->bar_vma); + nouveau_vm_put(&node->bar_vma); +} + +static int +nouveau_ttm_fault_reserve_notify(struct ttm_buffer_object *bo) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev); + struct nouveau_bo *nvbo = nouveau_bo(bo); + + /* as long as the bo isn't in vram, and isn't tiled, we've got + * nothing to do here. + */ + if (bo->mem.mem_type != TTM_PL_VRAM) { + if (dev_priv->card_type < NV_50 || + !nouveau_bo_tile_layout(nvbo)) + return 0; + } + + /* make sure bo is in mappable vram */ + if (bo->mem.start + bo->mem.num_pages < dev_priv->fb_mappable_pages) + return 0; + + + nvbo->placement.fpfn = 0; + nvbo->placement.lpfn = dev_priv->fb_mappable_pages; + nouveau_bo_placement_set(nvbo, TTM_PL_FLAG_VRAM, 0); + return nouveau_bo_validate(nvbo, false, true, false); +} + +void +nouveau_bo_fence(struct nouveau_bo *nvbo, struct nouveau_fence *fence) +{ + struct nouveau_fence *old_fence; + + if (likely(fence)) + nouveau_fence_ref(fence); + + spin_lock(&nvbo->bo.bdev->fence_lock); + old_fence = nvbo->bo.sync_obj; + nvbo->bo.sync_obj = fence; + spin_unlock(&nvbo->bo.bdev->fence_lock); + + nouveau_fence_unref(&old_fence); +} + +static int +nouveau_ttm_tt_populate(struct ttm_tt *ttm) +{ + struct ttm_dma_tt *ttm_dma = (void *)ttm; + struct drm_nouveau_private *dev_priv; + struct drm_device *dev; + unsigned i; + int r; + + if (ttm->state != tt_unpopulated) + return 0; + + dev_priv = nouveau_bdev(ttm->bdev); + dev = dev_priv->dev; + +#if __OS_HAS_AGP + if (dev_priv->gart_info.type == NOUVEAU_GART_AGP) { + return ttm_agp_tt_populate(ttm); + } +#endif + +#ifdef CONFIG_SWIOTLB + if (swiotlb_nr_tbl()) { + return ttm_dma_populate((void *)ttm, dev->dev); + } +#endif + + r = ttm_pool_populate(ttm); + if (r) { + return r; + } + + for (i = 0; i < ttm->num_pages; i++) { + ttm_dma->dma_address[i] = pci_map_page(dev->pdev, ttm->pages[i], + 0, PAGE_SIZE, + PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(dev->pdev, ttm_dma->dma_address[i])) { + while (--i) { + pci_unmap_page(dev->pdev, ttm_dma->dma_address[i], + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + ttm_dma->dma_address[i] = 0; + } + ttm_pool_unpopulate(ttm); + return -EFAULT; + } + } + return 0; +} + +static void +nouveau_ttm_tt_unpopulate(struct ttm_tt *ttm) +{ + struct ttm_dma_tt *ttm_dma = (void *)ttm; + struct drm_nouveau_private *dev_priv; + struct drm_device *dev; + unsigned i; + + dev_priv = nouveau_bdev(ttm->bdev); + dev = dev_priv->dev; + +#if __OS_HAS_AGP + if (dev_priv->gart_info.type == NOUVEAU_GART_AGP) { + ttm_agp_tt_unpopulate(ttm); + return; + } +#endif + +#ifdef CONFIG_SWIOTLB + if (swiotlb_nr_tbl()) { + ttm_dma_unpopulate((void *)ttm, dev->dev); + return; + } +#endif + + for (i = 0; i < ttm->num_pages; i++) { + if (ttm_dma->dma_address[i]) { + pci_unmap_page(dev->pdev, ttm_dma->dma_address[i], + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + } + } + + ttm_pool_unpopulate(ttm); +} + +struct ttm_bo_driver nouveau_bo_driver = { + .ttm_tt_create = &nouveau_ttm_tt_create, + .ttm_tt_populate = &nouveau_ttm_tt_populate, + .ttm_tt_unpopulate = &nouveau_ttm_tt_unpopulate, + .invalidate_caches = nouveau_bo_invalidate_caches, + .init_mem_type = nouveau_bo_init_mem_type, + .evict_flags = nouveau_bo_evict_flags, + .move_notify = nouveau_bo_move_ntfy, + .move = nouveau_bo_move, + .verify_access = nouveau_bo_verify_access, + .sync_obj_signaled = __nouveau_fence_signalled, + .sync_obj_wait = __nouveau_fence_wait, + .sync_obj_flush = __nouveau_fence_flush, + .sync_obj_unref = __nouveau_fence_unref, + .sync_obj_ref = __nouveau_fence_ref, + .fault_reserve_notify = &nouveau_ttm_fault_reserve_notify, + .io_mem_reserve = &nouveau_ttm_io_mem_reserve, + .io_mem_free = &nouveau_ttm_io_mem_free, +}; + +struct nouveau_vma * +nouveau_bo_vma_find(struct nouveau_bo *nvbo, struct nouveau_vm *vm) +{ + struct nouveau_vma *vma; + list_for_each_entry(vma, &nvbo->vma_list, head) { + if (vma->vm == vm) + return vma; + } + + return NULL; +} + +int +nouveau_bo_vma_add(struct nouveau_bo *nvbo, struct nouveau_vm *vm, + struct nouveau_vma *vma) +{ + const u32 size = nvbo->bo.mem.num_pages << PAGE_SHIFT; + struct nouveau_mem *node = nvbo->bo.mem.mm_node; + int ret; + + ret = nouveau_vm_get(vm, size, nvbo->page_shift, + NV_MEM_ACCESS_RW, vma); + if (ret) + return ret; + + if (nvbo->bo.mem.mem_type == TTM_PL_VRAM) + nouveau_vm_map(vma, nvbo->bo.mem.mm_node); + else + if (nvbo->bo.mem.mem_type == TTM_PL_TT) + nouveau_vm_map_sg(vma, 0, size, node); + + list_add_tail(&vma->head, &nvbo->vma_list); + vma->refcount = 1; + return 0; +} + +void +nouveau_bo_vma_del(struct nouveau_bo *nvbo, struct nouveau_vma *vma) +{ + if (vma->node) { + if (nvbo->bo.mem.mem_type != TTM_PL_SYSTEM) { + spin_lock(&nvbo->bo.bdev->fence_lock); + ttm_bo_wait(&nvbo->bo, false, false, false); + spin_unlock(&nvbo->bo.bdev->fence_lock); + nouveau_vm_unmap(vma); + } + + nouveau_vm_put(vma); + list_del(&vma->head); + } +} diff --git a/drivers/gpu/drm/nouveau/nouveau_calc.c b/drivers/gpu/drm/nouveau/nouveau_calc.c new file mode 100644 index 00000000..dad96cce --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_calc.c @@ -0,0 +1,478 @@ +/* + * Copyright 1993-2003 NVIDIA, Corporation + * Copyright 2007-2009 Stuart Bennett + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" + +/****************************************************************************\ +* * +* The video arbitration routines calculate some "magic" numbers. Fixes * +* the snow seen when accessing the framebuffer without it. * +* It just works (I hope). * +* * +\****************************************************************************/ + +struct nv_fifo_info { + int lwm; + int burst; +}; + +struct nv_sim_state { + int pclk_khz; + int mclk_khz; + int nvclk_khz; + int bpp; + int mem_page_miss; + int mem_latency; + int memory_type; + int memory_width; + int two_heads; +}; + +static void +nv04_calc_arb(struct nv_fifo_info *fifo, struct nv_sim_state *arb) +{ + int pagemiss, cas, width, bpp; + int nvclks, mclks, pclks, crtpagemiss; + int found, mclk_extra, mclk_loop, cbs, m1, p1; + int mclk_freq, pclk_freq, nvclk_freq; + int us_m, us_n, us_p, crtc_drain_rate; + int cpm_us, us_crt, clwm; + + pclk_freq = arb->pclk_khz; + mclk_freq = arb->mclk_khz; + nvclk_freq = arb->nvclk_khz; + pagemiss = arb->mem_page_miss; + cas = arb->mem_latency; + width = arb->memory_width >> 6; + bpp = arb->bpp; + cbs = 128; + + pclks = 2; + nvclks = 10; + mclks = 13 + cas; + mclk_extra = 3; + found = 0; + + while (!found) { + found = 1; + + mclk_loop = mclks + mclk_extra; + us_m = mclk_loop * 1000 * 1000 / mclk_freq; + us_n = nvclks * 1000 * 1000 / nvclk_freq; + us_p = nvclks * 1000 * 1000 / pclk_freq; + + crtc_drain_rate = pclk_freq * bpp / 8; + crtpagemiss = 2; + crtpagemiss += 1; + cpm_us = crtpagemiss * pagemiss * 1000 * 1000 / mclk_freq; + us_crt = cpm_us + us_m + us_n + us_p; + clwm = us_crt * crtc_drain_rate / (1000 * 1000); + clwm++; + + m1 = clwm + cbs - 512; + p1 = m1 * pclk_freq / mclk_freq; + p1 = p1 * bpp / 8; + if ((p1 < m1 && m1 > 0) || clwm > 519) { + found = !mclk_extra; + mclk_extra--; + } + if (clwm < 384) + clwm = 384; + + fifo->lwm = clwm; + fifo->burst = cbs; + } +} + +static void +nv10_calc_arb(struct nv_fifo_info *fifo, struct nv_sim_state *arb) +{ + int fill_rate, drain_rate; + int pclks, nvclks, mclks, xclks; + int pclk_freq, nvclk_freq, mclk_freq; + int fill_lat, extra_lat; + int max_burst_o, max_burst_l; + int fifo_len, min_lwm, max_lwm; + const int burst_lat = 80; /* Maximum allowable latency due + * to the CRTC FIFO burst. (ns) */ + + pclk_freq = arb->pclk_khz; + nvclk_freq = arb->nvclk_khz; + mclk_freq = arb->mclk_khz; + + fill_rate = mclk_freq * arb->memory_width / 8; /* kB/s */ + drain_rate = pclk_freq * arb->bpp / 8; /* kB/s */ + + fifo_len = arb->two_heads ? 1536 : 1024; /* B */ + + /* Fixed FIFO refill latency. */ + + pclks = 4; /* lwm detect. */ + + nvclks = 3 /* lwm -> sync. */ + + 2 /* fbi bus cycles (1 req + 1 busy) */ + + 1 /* 2 edge sync. may be very close to edge so + * just put one. */ + + 1 /* fbi_d_rdv_n */ + + 1 /* Fbi_d_rdata */ + + 1; /* crtfifo load */ + + mclks = 1 /* 2 edge sync. may be very close to edge so + * just put one. */ + + 1 /* arb_hp_req */ + + 5 /* tiling pipeline */ + + 2 /* latency fifo */ + + 2 /* memory request to fbio block */ + + 7; /* data returned from fbio block */ + + /* Need to accumulate 256 bits for read */ + mclks += (arb->memory_type == 0 ? 2 : 1) + * arb->memory_width / 32; + + fill_lat = mclks * 1000 * 1000 / mclk_freq /* minimum mclk latency */ + + nvclks * 1000 * 1000 / nvclk_freq /* nvclk latency */ + + pclks * 1000 * 1000 / pclk_freq; /* pclk latency */ + + /* Conditional FIFO refill latency. */ + + xclks = 2 * arb->mem_page_miss + mclks /* Extra latency due to + * the overlay. */ + + 2 * arb->mem_page_miss /* Extra pagemiss latency. */ + + (arb->bpp == 32 ? 8 : 4); /* Margin of error. */ + + extra_lat = xclks * 1000 * 1000 / mclk_freq; + + if (arb->two_heads) + /* Account for another CRTC. */ + extra_lat += fill_lat + extra_lat + burst_lat; + + /* FIFO burst */ + + /* Max burst not leading to overflows. */ + max_burst_o = (1 + fifo_len - extra_lat * drain_rate / (1000 * 1000)) + * (fill_rate / 1000) / ((fill_rate - drain_rate) / 1000); + fifo->burst = min(max_burst_o, 1024); + + /* Max burst value with an acceptable latency. */ + max_burst_l = burst_lat * fill_rate / (1000 * 1000); + fifo->burst = min(max_burst_l, fifo->burst); + + fifo->burst = rounddown_pow_of_two(fifo->burst); + + /* FIFO low watermark */ + + min_lwm = (fill_lat + extra_lat) * drain_rate / (1000 * 1000) + 1; + max_lwm = fifo_len - fifo->burst + + fill_lat * drain_rate / (1000 * 1000) + + fifo->burst * drain_rate / fill_rate; + + fifo->lwm = min_lwm + 10 * (max_lwm - min_lwm) / 100; /* Empirical. */ +} + +static void +nv04_update_arb(struct drm_device *dev, int VClk, int bpp, + int *burst, int *lwm) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv_fifo_info fifo_data; + struct nv_sim_state sim_data; + int MClk = nouveau_hw_get_clock(dev, PLL_MEMORY); + int NVClk = nouveau_hw_get_clock(dev, PLL_CORE); + uint32_t cfg1 = nvReadFB(dev, NV04_PFB_CFG1); + + sim_data.pclk_khz = VClk; + sim_data.mclk_khz = MClk; + sim_data.nvclk_khz = NVClk; + sim_data.bpp = bpp; + sim_data.two_heads = nv_two_heads(dev); + if ((dev->pci_device & 0xffff) == 0x01a0 /*CHIPSET_NFORCE*/ || + (dev->pci_device & 0xffff) == 0x01f0 /*CHIPSET_NFORCE2*/) { + uint32_t type; + + pci_read_config_dword(pci_get_bus_and_slot(0, 1), 0x7c, &type); + + sim_data.memory_type = (type >> 12) & 1; + sim_data.memory_width = 64; + sim_data.mem_latency = 3; + sim_data.mem_page_miss = 10; + } else { + sim_data.memory_type = nvReadFB(dev, NV04_PFB_CFG0) & 0x1; + sim_data.memory_width = (nvReadEXTDEV(dev, NV_PEXTDEV_BOOT_0) & 0x10) ? 128 : 64; + sim_data.mem_latency = cfg1 & 0xf; + sim_data.mem_page_miss = ((cfg1 >> 4) & 0xf) + ((cfg1 >> 31) & 0x1); + } + + if (dev_priv->card_type == NV_04) + nv04_calc_arb(&fifo_data, &sim_data); + else + nv10_calc_arb(&fifo_data, &sim_data); + + *burst = ilog2(fifo_data.burst >> 4); + *lwm = fifo_data.lwm >> 3; +} + +static void +nv20_update_arb(int *burst, int *lwm) +{ + unsigned int fifo_size, burst_size, graphics_lwm; + + fifo_size = 2048; + burst_size = 512; + graphics_lwm = fifo_size - burst_size; + + *burst = ilog2(burst_size >> 5); + *lwm = graphics_lwm >> 3; +} + +void +nouveau_calc_arb(struct drm_device *dev, int vclk, int bpp, int *burst, int *lwm) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->card_type < NV_20) + nv04_update_arb(dev, vclk, bpp, burst, lwm); + else if ((dev->pci_device & 0xfff0) == 0x0240 /*CHIPSET_C51*/ || + (dev->pci_device & 0xfff0) == 0x03d0 /*CHIPSET_C512*/) { + *burst = 128; + *lwm = 0x0480; + } else + nv20_update_arb(burst, lwm); +} + +static int +getMNP_single(struct drm_device *dev, struct pll_lims *pll_lim, int clk, + struct nouveau_pll_vals *bestpv) +{ + /* Find M, N and P for a single stage PLL + * + * Note that some bioses (NV3x) have lookup tables of precomputed MNP + * values, but we're too lazy to use those atm + * + * "clk" parameter in kHz + * returns calculated clock + */ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int cv = dev_priv->vbios.chip_version; + int minvco = pll_lim->vco1.minfreq, maxvco = pll_lim->vco1.maxfreq; + int minM = pll_lim->vco1.min_m, maxM = pll_lim->vco1.max_m; + int minN = pll_lim->vco1.min_n, maxN = pll_lim->vco1.max_n; + int minU = pll_lim->vco1.min_inputfreq; + int maxU = pll_lim->vco1.max_inputfreq; + int minP = pll_lim->max_p ? pll_lim->min_p : 0; + int maxP = pll_lim->max_p ? pll_lim->max_p : pll_lim->max_usable_log2p; + int crystal = pll_lim->refclk; + int M, N, thisP, P; + int clkP, calcclk; + int delta, bestdelta = INT_MAX; + int bestclk = 0; + + /* this division verified for nv20, nv18, nv28 (Haiku), and nv34 */ + /* possibly correlated with introduction of 27MHz crystal */ + if (dev_priv->card_type < NV_50) { + if (cv < 0x17 || cv == 0x1a || cv == 0x20) { + if (clk > 250000) + maxM = 6; + if (clk > 340000) + maxM = 2; + } else if (cv < 0x40) { + if (clk > 150000) + maxM = 6; + if (clk > 200000) + maxM = 4; + if (clk > 340000) + maxM = 2; + } + } + + P = pll_lim->max_p ? maxP : (1 << maxP); + if ((clk * P) < minvco) { + minvco = clk * maxP; + maxvco = minvco * 2; + } + + if (clk + clk/200 > maxvco) /* +0.5% */ + maxvco = clk + clk/200; + + /* NV34 goes maxlog2P->0, NV20 goes 0->maxlog2P */ + for (thisP = minP; thisP <= maxP; thisP++) { + P = pll_lim->max_p ? thisP : (1 << thisP); + clkP = clk * P; + + if (clkP < minvco) + continue; + if (clkP > maxvco) + return bestclk; + + for (M = minM; M <= maxM; M++) { + if (crystal/M < minU) + return bestclk; + if (crystal/M > maxU) + continue; + + /* add crystal/2 to round better */ + N = (clkP * M + crystal/2) / crystal; + + if (N < minN) + continue; + if (N > maxN) + break; + + /* more rounding additions */ + calcclk = ((N * crystal + P/2) / P + M/2) / M; + delta = abs(calcclk - clk); + /* we do an exhaustive search rather than terminating + * on an optimality condition... + */ + if (delta < bestdelta) { + bestdelta = delta; + bestclk = calcclk; + bestpv->N1 = N; + bestpv->M1 = M; + bestpv->log2P = thisP; + if (delta == 0) /* except this one */ + return bestclk; + } + } + } + + return bestclk; +} + +static int +getMNP_double(struct drm_device *dev, struct pll_lims *pll_lim, int clk, + struct nouveau_pll_vals *bestpv) +{ + /* Find M, N and P for a two stage PLL + * + * Note that some bioses (NV30+) have lookup tables of precomputed MNP + * values, but we're too lazy to use those atm + * + * "clk" parameter in kHz + * returns calculated clock + */ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chip_version = dev_priv->vbios.chip_version; + int minvco1 = pll_lim->vco1.minfreq, maxvco1 = pll_lim->vco1.maxfreq; + int minvco2 = pll_lim->vco2.minfreq, maxvco2 = pll_lim->vco2.maxfreq; + int minU1 = pll_lim->vco1.min_inputfreq, minU2 = pll_lim->vco2.min_inputfreq; + int maxU1 = pll_lim->vco1.max_inputfreq, maxU2 = pll_lim->vco2.max_inputfreq; + int minM1 = pll_lim->vco1.min_m, maxM1 = pll_lim->vco1.max_m; + int minN1 = pll_lim->vco1.min_n, maxN1 = pll_lim->vco1.max_n; + int minM2 = pll_lim->vco2.min_m, maxM2 = pll_lim->vco2.max_m; + int minN2 = pll_lim->vco2.min_n, maxN2 = pll_lim->vco2.max_n; + int maxlog2P = pll_lim->max_usable_log2p; + int crystal = pll_lim->refclk; + bool fixedgain2 = (minM2 == maxM2 && minN2 == maxN2); + int M1, N1, M2, N2, log2P; + int clkP, calcclk1, calcclk2, calcclkout; + int delta, bestdelta = INT_MAX; + int bestclk = 0; + + int vco2 = (maxvco2 - maxvco2/200) / 2; + for (log2P = 0; clk && log2P < maxlog2P && clk <= (vco2 >> log2P); log2P++) + ; + clkP = clk << log2P; + + if (maxvco2 < clk + clk/200) /* +0.5% */ + maxvco2 = clk + clk/200; + + for (M1 = minM1; M1 <= maxM1; M1++) { + if (crystal/M1 < minU1) + return bestclk; + if (crystal/M1 > maxU1) + continue; + + for (N1 = minN1; N1 <= maxN1; N1++) { + calcclk1 = crystal * N1 / M1; + if (calcclk1 < minvco1) + continue; + if (calcclk1 > maxvco1) + break; + + for (M2 = minM2; M2 <= maxM2; M2++) { + if (calcclk1/M2 < minU2) + break; + if (calcclk1/M2 > maxU2) + continue; + + /* add calcclk1/2 to round better */ + N2 = (clkP * M2 + calcclk1/2) / calcclk1; + if (N2 < minN2) + continue; + if (N2 > maxN2) + break; + + if (!fixedgain2) { + if (chip_version < 0x60) + if (N2/M2 < 4 || N2/M2 > 10) + continue; + + calcclk2 = calcclk1 * N2 / M2; + if (calcclk2 < minvco2) + break; + if (calcclk2 > maxvco2) + continue; + } else + calcclk2 = calcclk1; + + calcclkout = calcclk2 >> log2P; + delta = abs(calcclkout - clk); + /* we do an exhaustive search rather than terminating + * on an optimality condition... + */ + if (delta < bestdelta) { + bestdelta = delta; + bestclk = calcclkout; + bestpv->N1 = N1; + bestpv->M1 = M1; + bestpv->N2 = N2; + bestpv->M2 = M2; + bestpv->log2P = log2P; + if (delta == 0) /* except this one */ + return bestclk; + } + } + } + } + + return bestclk; +} + +int +nouveau_calc_pll_mnp(struct drm_device *dev, struct pll_lims *pll_lim, int clk, + struct nouveau_pll_vals *pv) +{ + int outclk; + + if (!pll_lim->vco2.maxfreq) + outclk = getMNP_single(dev, pll_lim, clk, pv); + else + outclk = getMNP_double(dev, pll_lim, clk, pv); + + if (!outclk) + NV_ERROR(dev, "Could not find a compatible set of PLL values\n"); + + return outclk; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_channel.c b/drivers/gpu/drm/nouveau/nouveau_channel.c new file mode 100644 index 00000000..846afb0b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_channel.c @@ -0,0 +1,492 @@ +/* + * Copyright 2005-2006 Stephane Marchesin + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_dma.h" +#include "nouveau_ramht.h" + +static int +nouveau_channel_pushbuf_init(struct nouveau_channel *chan) +{ + u32 mem = nouveau_vram_pushbuf ? TTM_PL_FLAG_VRAM : TTM_PL_FLAG_TT; + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int ret; + + /* allocate buffer object */ + ret = nouveau_bo_new(dev, 65536, 0, mem, 0, 0, &chan->pushbuf_bo); + if (ret) + goto out; + + ret = nouveau_bo_pin(chan->pushbuf_bo, mem); + if (ret) + goto out; + + ret = nouveau_bo_map(chan->pushbuf_bo); + if (ret) + goto out; + + /* create DMA object covering the entire memtype where the push + * buffer resides, userspace can submit its own push buffers from + * anywhere within the same memtype. + */ + chan->pushbuf_base = chan->pushbuf_bo->bo.offset; + if (dev_priv->card_type >= NV_50) { + ret = nouveau_bo_vma_add(chan->pushbuf_bo, chan->vm, + &chan->pushbuf_vma); + if (ret) + goto out; + + if (dev_priv->card_type < NV_C0) { + ret = nouveau_gpuobj_dma_new(chan, + NV_CLASS_DMA_IN_MEMORY, 0, + (1ULL << 40), + NV_MEM_ACCESS_RO, + NV_MEM_TARGET_VM, + &chan->pushbuf); + } + chan->pushbuf_base = chan->pushbuf_vma.offset; + } else + if (chan->pushbuf_bo->bo.mem.mem_type == TTM_PL_TT) { + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, 0, + dev_priv->gart_info.aper_size, + NV_MEM_ACCESS_RO, + NV_MEM_TARGET_GART, + &chan->pushbuf); + } else + if (dev_priv->card_type != NV_04) { + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, 0, + dev_priv->fb_available_size, + NV_MEM_ACCESS_RO, + NV_MEM_TARGET_VRAM, + &chan->pushbuf); + } else { + /* NV04 cmdbuf hack, from original ddx.. not sure of it's + * exact reason for existing :) PCI access to cmdbuf in + * VRAM. + */ + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, + pci_resource_start(dev->pdev, 1), + dev_priv->fb_available_size, + NV_MEM_ACCESS_RO, + NV_MEM_TARGET_PCI, + &chan->pushbuf); + } + +out: + if (ret) { + NV_ERROR(dev, "error initialising pushbuf: %d\n", ret); + nouveau_bo_vma_del(chan->pushbuf_bo, &chan->pushbuf_vma); + nouveau_gpuobj_ref(NULL, &chan->pushbuf); + if (chan->pushbuf_bo) { + nouveau_bo_unmap(chan->pushbuf_bo); + nouveau_bo_ref(NULL, &chan->pushbuf_bo); + } + } + + return 0; +} + +/* allocates and initializes a fifo for user space consumption */ +int +nouveau_channel_alloc(struct drm_device *dev, struct nouveau_channel **chan_ret, + struct drm_file *file_priv, + uint32_t vram_handle, uint32_t gart_handle) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_fpriv *fpriv = nouveau_fpriv(file_priv); + struct nouveau_channel *chan; + unsigned long flags; + int ret, i; + + /* allocate and lock channel structure */ + chan = kzalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + chan->dev = dev; + chan->file_priv = file_priv; + chan->vram_handle = vram_handle; + chan->gart_handle = gart_handle; + + kref_init(&chan->ref); + atomic_set(&chan->users, 1); + mutex_init(&chan->mutex); + mutex_lock(&chan->mutex); + + /* allocate hw channel id */ + spin_lock_irqsave(&dev_priv->channels.lock, flags); + for (chan->id = 0; chan->id < pfifo->channels; chan->id++) { + if (!dev_priv->channels.ptr[chan->id]) { + nouveau_channel_ref(chan, &dev_priv->channels.ptr[chan->id]); + break; + } + } + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + + if (chan->id == pfifo->channels) { + mutex_unlock(&chan->mutex); + kfree(chan); + return -ENODEV; + } + + NV_DEBUG(dev, "initialising channel %d\n", chan->id); + INIT_LIST_HEAD(&chan->nvsw.vbl_wait); + INIT_LIST_HEAD(&chan->nvsw.flip); + INIT_LIST_HEAD(&chan->fence.pending); + spin_lock_init(&chan->fence.lock); + + /* setup channel's memory and vm */ + ret = nouveau_gpuobj_channel_init(chan, vram_handle, gart_handle); + if (ret) { + NV_ERROR(dev, "gpuobj %d\n", ret); + nouveau_channel_put(&chan); + return ret; + } + + /* Allocate space for per-channel fixed notifier memory */ + ret = nouveau_notifier_init_channel(chan); + if (ret) { + NV_ERROR(dev, "ntfy %d\n", ret); + nouveau_channel_put(&chan); + return ret; + } + + /* Allocate DMA push buffer */ + ret = nouveau_channel_pushbuf_init(chan); + if (ret) { + NV_ERROR(dev, "pushbuf %d\n", ret); + nouveau_channel_put(&chan); + return ret; + } + + nouveau_dma_init(chan); + chan->user_put = 0x40; + chan->user_get = 0x44; + if (dev_priv->card_type >= NV_50) + chan->user_get_hi = 0x60; + + /* disable the fifo caches */ + pfifo->reassign(dev, false); + + /* Construct initial RAMFC for new channel */ + ret = pfifo->create_context(chan); + if (ret) { + nouveau_channel_put(&chan); + return ret; + } + + pfifo->reassign(dev, true); + + /* Insert NOPs for NOUVEAU_DMA_SKIPS */ + ret = RING_SPACE(chan, NOUVEAU_DMA_SKIPS); + if (ret) { + nouveau_channel_put(&chan); + return ret; + } + + for (i = 0; i < NOUVEAU_DMA_SKIPS; i++) + OUT_RING (chan, 0x00000000); + FIRE_RING(chan); + + ret = nouveau_fence_channel_init(chan); + if (ret) { + nouveau_channel_put(&chan); + return ret; + } + + nouveau_debugfs_channel_init(chan); + + NV_DEBUG(dev, "channel %d initialised\n", chan->id); + if (fpriv) { + spin_lock(&fpriv->lock); + list_add(&chan->list, &fpriv->channels); + spin_unlock(&fpriv->lock); + } + *chan_ret = chan; + return 0; +} + +struct nouveau_channel * +nouveau_channel_get_unlocked(struct nouveau_channel *ref) +{ + struct nouveau_channel *chan = NULL; + + if (likely(ref && atomic_inc_not_zero(&ref->users))) + nouveau_channel_ref(ref, &chan); + + return chan; +} + +struct nouveau_channel * +nouveau_channel_get(struct drm_file *file_priv, int id) +{ + struct nouveau_fpriv *fpriv = nouveau_fpriv(file_priv); + struct nouveau_channel *chan; + + spin_lock(&fpriv->lock); + list_for_each_entry(chan, &fpriv->channels, list) { + if (chan->id == id) { + chan = nouveau_channel_get_unlocked(chan); + spin_unlock(&fpriv->lock); + mutex_lock(&chan->mutex); + return chan; + } + } + spin_unlock(&fpriv->lock); + + return ERR_PTR(-EINVAL); +} + +void +nouveau_channel_put_unlocked(struct nouveau_channel **pchan) +{ + struct nouveau_channel *chan = *pchan; + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + unsigned long flags; + int i; + + /* decrement the refcount, and we're done if there's still refs */ + if (likely(!atomic_dec_and_test(&chan->users))) { + nouveau_channel_ref(NULL, pchan); + return; + } + + /* no one wants the channel anymore */ + NV_DEBUG(dev, "freeing channel %d\n", chan->id); + nouveau_debugfs_channel_fini(chan); + + /* give it chance to idle */ + nouveau_channel_idle(chan); + + /* ensure all outstanding fences are signaled. they should be if the + * above attempts at idling were OK, but if we failed this'll tell TTM + * we're done with the buffers. + */ + nouveau_fence_channel_fini(chan); + + /* boot it off the hardware */ + pfifo->reassign(dev, false); + + /* destroy the engine specific contexts */ + pfifo->destroy_context(chan); + for (i = 0; i < NVOBJ_ENGINE_NR; i++) { + if (chan->engctx[i]) + dev_priv->eng[i]->context_del(chan, i); + } + + pfifo->reassign(dev, true); + + /* aside from its resources, the channel should now be dead, + * remove it from the channel list + */ + spin_lock_irqsave(&dev_priv->channels.lock, flags); + nouveau_channel_ref(NULL, &dev_priv->channels.ptr[chan->id]); + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + + /* destroy any resources the channel owned */ + nouveau_gpuobj_ref(NULL, &chan->pushbuf); + if (chan->pushbuf_bo) { + nouveau_bo_vma_del(chan->pushbuf_bo, &chan->pushbuf_vma); + nouveau_bo_unmap(chan->pushbuf_bo); + nouveau_bo_unpin(chan->pushbuf_bo); + nouveau_bo_ref(NULL, &chan->pushbuf_bo); + } + nouveau_ramht_ref(NULL, &chan->ramht, chan); + nouveau_notifier_takedown_channel(chan); + nouveau_gpuobj_channel_takedown(chan); + + nouveau_channel_ref(NULL, pchan); +} + +void +nouveau_channel_put(struct nouveau_channel **pchan) +{ + mutex_unlock(&(*pchan)->mutex); + nouveau_channel_put_unlocked(pchan); +} + +static void +nouveau_channel_del(struct kref *ref) +{ + struct nouveau_channel *chan = + container_of(ref, struct nouveau_channel, ref); + + kfree(chan); +} + +void +nouveau_channel_ref(struct nouveau_channel *chan, + struct nouveau_channel **pchan) +{ + if (chan) + kref_get(&chan->ref); + + if (*pchan) + kref_put(&(*pchan)->ref, nouveau_channel_del); + + *pchan = chan; +} + +void +nouveau_channel_idle(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct nouveau_fence *fence = NULL; + int ret; + + nouveau_fence_update(chan); + + if (chan->fence.sequence != chan->fence.sequence_ack) { + ret = nouveau_fence_new(chan, &fence, true); + if (!ret) { + ret = nouveau_fence_wait(fence, false, false); + nouveau_fence_unref(&fence); + } + + if (ret) + NV_ERROR(dev, "Failed to idle channel %d.\n", chan->id); + } +} + +/* cleans up all the fifos from file_priv */ +void +nouveau_channel_cleanup(struct drm_device *dev, struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_engine *engine = &dev_priv->engine; + struct nouveau_channel *chan; + int i; + + NV_DEBUG(dev, "clearing FIFO enables from file_priv\n"); + for (i = 0; i < engine->fifo.channels; i++) { + chan = nouveau_channel_get(file_priv, i); + if (IS_ERR(chan)) + continue; + + list_del(&chan->list); + atomic_dec(&chan->users); + nouveau_channel_put(&chan); + } +} + + +/*********************************** + * ioctls wrapping the functions + ***********************************/ + +static int +nouveau_ioctl_fifo_alloc(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_nouveau_channel_alloc *init = data; + struct nouveau_channel *chan; + int ret; + + if (!dev_priv->eng[NVOBJ_ENGINE_GR]) + return -ENODEV; + + if (init->fb_ctxdma_handle == ~0 || init->tt_ctxdma_handle == ~0) + return -EINVAL; + + ret = nouveau_channel_alloc(dev, &chan, file_priv, + init->fb_ctxdma_handle, + init->tt_ctxdma_handle); + if (ret) + return ret; + init->channel = chan->id; + + if (nouveau_vram_pushbuf == 0) { + if (chan->dma.ib_max) + init->pushbuf_domains = NOUVEAU_GEM_DOMAIN_VRAM | + NOUVEAU_GEM_DOMAIN_GART; + else if (chan->pushbuf_bo->bo.mem.mem_type == TTM_PL_VRAM) + init->pushbuf_domains = NOUVEAU_GEM_DOMAIN_VRAM; + else + init->pushbuf_domains = NOUVEAU_GEM_DOMAIN_GART; + } else { + init->pushbuf_domains = NOUVEAU_GEM_DOMAIN_VRAM; + } + + if (dev_priv->card_type < NV_C0) { + init->subchan[0].handle = 0x00000000; + init->subchan[0].grclass = 0x0000; + init->subchan[1].handle = NvSw; + init->subchan[1].grclass = NV_SW; + init->nr_subchan = 2; + } + + /* Named memory object area */ + ret = drm_gem_handle_create(file_priv, chan->notifier_bo->gem, + &init->notifier_handle); + + if (ret == 0) + atomic_inc(&chan->users); /* userspace reference */ + nouveau_channel_put(&chan); + return ret; +} + +static int +nouveau_ioctl_fifo_free(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_channel_free *req = data; + struct nouveau_channel *chan; + + chan = nouveau_channel_get(file_priv, req->channel); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + list_del(&chan->list); + atomic_dec(&chan->users); + nouveau_channel_put(&chan); + return 0; +} + +/*********************************** + * finally, the ioctl table + ***********************************/ + +struct drm_ioctl_desc nouveau_ioctls[] = { + DRM_IOCTL_DEF_DRV(NOUVEAU_GETPARAM, nouveau_ioctl_getparam, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_SETPARAM, nouveau_ioctl_setparam, DRM_UNLOCKED|DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), + DRM_IOCTL_DEF_DRV(NOUVEAU_CHANNEL_ALLOC, nouveau_ioctl_fifo_alloc, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_CHANNEL_FREE, nouveau_ioctl_fifo_free, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_GROBJ_ALLOC, nouveau_ioctl_grobj_alloc, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_NOTIFIEROBJ_ALLOC, nouveau_ioctl_notifier_alloc, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_GPUOBJ_FREE, nouveau_ioctl_gpuobj_free, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_NEW, nouveau_gem_ioctl_new, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_PUSHBUF, nouveau_gem_ioctl_pushbuf, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_CPU_PREP, nouveau_gem_ioctl_cpu_prep, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_CPU_FINI, nouveau_gem_ioctl_cpu_fini, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_INFO, nouveau_gem_ioctl_info, DRM_UNLOCKED|DRM_AUTH), +}; + +int nouveau_max_ioctl = DRM_ARRAY_SIZE(nouveau_ioctls); diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c new file mode 100644 index 00000000..7b11edb0 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -0,0 +1,1114 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include <acpi/button.h> + +#include "drmP.h" +#include "drm_edid.h" +#include "drm_crtc_helper.h" + +#include "nouveau_reg.h" +#include "nouveau_drv.h" +#include "nouveau_encoder.h" +#include "nouveau_crtc.h" +#include "nouveau_connector.h" +#include "nouveau_gpio.h" +#include "nouveau_hw.h" + +static void nouveau_connector_hotplug(void *, int); + +struct nouveau_encoder * +find_encoder(struct drm_connector *connector, int type) +{ + struct drm_device *dev = connector->dev; + struct nouveau_encoder *nv_encoder; + struct drm_mode_object *obj; + int i, id; + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + id = connector->encoder_ids[i]; + if (!id) + break; + + obj = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER); + if (!obj) + continue; + nv_encoder = nouveau_encoder(obj_to_encoder(obj)); + + if (type == OUTPUT_ANY || nv_encoder->dcb->type == type) + return nv_encoder; + } + + return NULL; +} + +struct nouveau_connector * +nouveau_encoder_connector_get(struct nouveau_encoder *encoder) +{ + struct drm_device *dev = to_drm_encoder(encoder)->dev; + struct drm_connector *drm_connector; + + list_for_each_entry(drm_connector, &dev->mode_config.connector_list, head) { + if (drm_connector->encoder == to_drm_encoder(encoder)) + return nouveau_connector(drm_connector); + } + + return NULL; +} + +static void +nouveau_connector_destroy(struct drm_connector *connector) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct drm_nouveau_private *dev_priv; + struct drm_device *dev; + + if (!nv_connector) + return; + + dev = nv_connector->base.dev; + dev_priv = dev->dev_private; + NV_DEBUG_KMS(dev, "\n"); + + if (nv_connector->hpd != DCB_GPIO_UNUSED) { + nouveau_gpio_isr_del(dev, 0, nv_connector->hpd, 0xff, + nouveau_connector_hotplug, connector); + } + + kfree(nv_connector->edid); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(connector); +} + +static struct nouveau_i2c_chan * +nouveau_connector_ddc_detect(struct drm_connector *connector, + struct nouveau_encoder **pnv_encoder) +{ + struct drm_device *dev = connector->dev; + int i; + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + struct nouveau_i2c_chan *i2c = NULL; + struct nouveau_encoder *nv_encoder; + struct drm_mode_object *obj; + int id; + + id = connector->encoder_ids[i]; + if (!id) + break; + + obj = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER); + if (!obj) + continue; + nv_encoder = nouveau_encoder(obj_to_encoder(obj)); + + if (nv_encoder->dcb->i2c_index < 0xf) + i2c = nouveau_i2c_find(dev, nv_encoder->dcb->i2c_index); + + if (i2c && nouveau_probe_i2c_addr(i2c, 0x50)) { + *pnv_encoder = nv_encoder; + return i2c; + } + } + + return NULL; +} + +static struct nouveau_encoder * +nouveau_connector_of_detect(struct drm_connector *connector) +{ +#ifdef __powerpc__ + struct drm_device *dev = connector->dev; + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder; + struct device_node *cn, *dn = pci_device_to_OF_node(dev->pdev); + + if (!dn || + !((nv_encoder = find_encoder(connector, OUTPUT_TMDS)) || + (nv_encoder = find_encoder(connector, OUTPUT_ANALOG)))) + return NULL; + + for_each_child_of_node(dn, cn) { + const char *name = of_get_property(cn, "name", NULL); + const void *edid = of_get_property(cn, "EDID", NULL); + int idx = name ? name[strlen(name) - 1] - 'A' : 0; + + if (nv_encoder->dcb->i2c_index == idx && edid) { + nv_connector->edid = + kmemdup(edid, EDID_LENGTH, GFP_KERNEL); + of_node_put(cn); + return nv_encoder; + } + } +#endif + return NULL; +} + +static void +nouveau_connector_set_encoder(struct drm_connector *connector, + struct nouveau_encoder *nv_encoder) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct drm_nouveau_private *dev_priv = connector->dev->dev_private; + struct drm_device *dev = connector->dev; + + if (nv_connector->detected_encoder == nv_encoder) + return; + nv_connector->detected_encoder = nv_encoder; + + if (dev_priv->card_type >= NV_50) { + connector->interlace_allowed = true; + connector->doublescan_allowed = true; + } else + if (nv_encoder->dcb->type == OUTPUT_LVDS || + nv_encoder->dcb->type == OUTPUT_TMDS) { + connector->doublescan_allowed = false; + connector->interlace_allowed = false; + } else { + connector->doublescan_allowed = true; + if (dev_priv->card_type == NV_20 || + (dev_priv->card_type == NV_10 && + (dev->pci_device & 0x0ff0) != 0x0100 && + (dev->pci_device & 0x0ff0) != 0x0150)) + /* HW is broken */ + connector->interlace_allowed = false; + else + connector->interlace_allowed = true; + } + + if (nv_connector->type == DCB_CONNECTOR_DVI_I) { + drm_connector_property_set_value(connector, + dev->mode_config.dvi_i_subconnector_property, + nv_encoder->dcb->type == OUTPUT_TMDS ? + DRM_MODE_SUBCONNECTOR_DVID : + DRM_MODE_SUBCONNECTOR_DVIA); + } +} + +static enum drm_connector_status +nouveau_connector_detect(struct drm_connector *connector, bool force) +{ + struct drm_device *dev = connector->dev; + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder = NULL; + struct nouveau_encoder *nv_partner; + struct nouveau_i2c_chan *i2c; + int type; + + /* Cleanup the previous EDID block. */ + if (nv_connector->edid) { + drm_mode_connector_update_edid_property(connector, NULL); + kfree(nv_connector->edid); + nv_connector->edid = NULL; + } + + i2c = nouveau_connector_ddc_detect(connector, &nv_encoder); + if (i2c) { + nv_connector->edid = drm_get_edid(connector, &i2c->adapter); + drm_mode_connector_update_edid_property(connector, + nv_connector->edid); + if (!nv_connector->edid) { + NV_ERROR(dev, "DDC responded, but no EDID for %s\n", + drm_get_connector_name(connector)); + goto detect_analog; + } + + if (nv_encoder->dcb->type == OUTPUT_DP && + !nouveau_dp_detect(to_drm_encoder(nv_encoder))) { + NV_ERROR(dev, "Detected %s, but failed init\n", + drm_get_connector_name(connector)); + return connector_status_disconnected; + } + + /* Override encoder type for DVI-I based on whether EDID + * says the display is digital or analog, both use the + * same i2c channel so the value returned from ddc_detect + * isn't necessarily correct. + */ + nv_partner = NULL; + if (nv_encoder->dcb->type == OUTPUT_TMDS) + nv_partner = find_encoder(connector, OUTPUT_ANALOG); + if (nv_encoder->dcb->type == OUTPUT_ANALOG) + nv_partner = find_encoder(connector, OUTPUT_TMDS); + + if (nv_partner && ((nv_encoder->dcb->type == OUTPUT_ANALOG && + nv_partner->dcb->type == OUTPUT_TMDS) || + (nv_encoder->dcb->type == OUTPUT_TMDS && + nv_partner->dcb->type == OUTPUT_ANALOG))) { + if (nv_connector->edid->input & DRM_EDID_INPUT_DIGITAL) + type = OUTPUT_TMDS; + else + type = OUTPUT_ANALOG; + + nv_encoder = find_encoder(connector, type); + } + + nouveau_connector_set_encoder(connector, nv_encoder); + return connector_status_connected; + } + + nv_encoder = nouveau_connector_of_detect(connector); + if (nv_encoder) { + nouveau_connector_set_encoder(connector, nv_encoder); + return connector_status_connected; + } + +detect_analog: + nv_encoder = find_encoder(connector, OUTPUT_ANALOG); + if (!nv_encoder && !nouveau_tv_disable) + nv_encoder = find_encoder(connector, OUTPUT_TV); + if (nv_encoder && force) { + struct drm_encoder *encoder = to_drm_encoder(nv_encoder); + struct drm_encoder_helper_funcs *helper = + encoder->helper_private; + + if (helper->detect(encoder, connector) == + connector_status_connected) { + nouveau_connector_set_encoder(connector, nv_encoder); + return connector_status_connected; + } + + } + + return connector_status_disconnected; +} + +static enum drm_connector_status +nouveau_connector_detect_lvds(struct drm_connector *connector, bool force) +{ + struct drm_device *dev = connector->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder = NULL; + enum drm_connector_status status = connector_status_disconnected; + + /* Cleanup the previous EDID block. */ + if (nv_connector->edid) { + drm_mode_connector_update_edid_property(connector, NULL); + kfree(nv_connector->edid); + nv_connector->edid = NULL; + } + + nv_encoder = find_encoder(connector, OUTPUT_LVDS); + if (!nv_encoder) + return connector_status_disconnected; + + /* Try retrieving EDID via DDC */ + if (!dev_priv->vbios.fp_no_ddc) { + status = nouveau_connector_detect(connector, force); + if (status == connector_status_connected) + goto out; + } + + /* On some laptops (Sony, i'm looking at you) there appears to + * be no direct way of accessing the panel's EDID. The only + * option available to us appears to be to ask ACPI for help.. + * + * It's important this check's before trying straps, one of the + * said manufacturer's laptops are configured in such a way + * the nouveau decides an entry in the VBIOS FP mode table is + * valid - it's not (rh#613284) + */ + if (nv_encoder->dcb->lvdsconf.use_acpi_for_edid) { + if (!nouveau_acpi_edid(dev, connector)) { + status = connector_status_connected; + goto out; + } + } + + /* If no EDID found above, and the VBIOS indicates a hardcoded + * modeline is avalilable for the panel, set it as the panel's + * native mode and exit. + */ + if (nouveau_bios_fp_mode(dev, NULL) && (dev_priv->vbios.fp_no_ddc || + nv_encoder->dcb->lvdsconf.use_straps_for_mode)) { + status = connector_status_connected; + goto out; + } + + /* Still nothing, some VBIOS images have a hardcoded EDID block + * stored for the panel stored in them. + */ + if (!dev_priv->vbios.fp_no_ddc) { + struct edid *edid = + (struct edid *)nouveau_bios_embedded_edid(dev); + if (edid) { + nv_connector->edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + *(nv_connector->edid) = *edid; + status = connector_status_connected; + } + } + +out: +#if defined(CONFIG_ACPI_BUTTON) || \ + (defined(CONFIG_ACPI_BUTTON_MODULE) && defined(MODULE)) + if (status == connector_status_connected && + !nouveau_ignorelid && !acpi_lid_open()) + status = connector_status_unknown; +#endif + + drm_mode_connector_update_edid_property(connector, nv_connector->edid); + nouveau_connector_set_encoder(connector, nv_encoder); + return status; +} + +static void +nouveau_connector_force(struct drm_connector *connector) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder; + int type; + + if (nv_connector->type == DCB_CONNECTOR_DVI_I) { + if (connector->force == DRM_FORCE_ON_DIGITAL) + type = OUTPUT_TMDS; + else + type = OUTPUT_ANALOG; + } else + type = OUTPUT_ANY; + + nv_encoder = find_encoder(connector, type); + if (!nv_encoder) { + NV_ERROR(connector->dev, "can't find encoder to force %s on!\n", + drm_get_connector_name(connector)); + connector->status = connector_status_disconnected; + return; + } + + nouveau_connector_set_encoder(connector, nv_encoder); +} + +static int +nouveau_connector_set_property(struct drm_connector *connector, + struct drm_property *property, uint64_t value) +{ + struct drm_nouveau_private *dev_priv = connector->dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder; + struct drm_encoder *encoder = to_drm_encoder(nv_encoder); + struct drm_device *dev = connector->dev; + struct nouveau_crtc *nv_crtc; + int ret; + + nv_crtc = NULL; + if (connector->encoder && connector->encoder->crtc) + nv_crtc = nouveau_crtc(connector->encoder->crtc); + + /* Scaling mode */ + if (property == dev->mode_config.scaling_mode_property) { + bool modeset = false; + + switch (value) { + case DRM_MODE_SCALE_NONE: + case DRM_MODE_SCALE_FULLSCREEN: + case DRM_MODE_SCALE_CENTER: + case DRM_MODE_SCALE_ASPECT: + break; + default: + return -EINVAL; + } + + /* LVDS always needs gpu scaling */ + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS && + value == DRM_MODE_SCALE_NONE) + return -EINVAL; + + /* Changing between GPU and panel scaling requires a full + * modeset + */ + if ((nv_connector->scaling_mode == DRM_MODE_SCALE_NONE) || + (value == DRM_MODE_SCALE_NONE)) + modeset = true; + nv_connector->scaling_mode = value; + + if (!nv_crtc) + return 0; + + if (modeset || !nv_crtc->set_scale) { + ret = drm_crtc_helper_set_mode(&nv_crtc->base, + &nv_crtc->base.mode, + nv_crtc->base.x, + nv_crtc->base.y, NULL); + if (!ret) + return -EINVAL; + } else { + ret = nv_crtc->set_scale(nv_crtc, true); + if (ret) + return ret; + } + + return 0; + } + + /* Underscan */ + if (property == disp->underscan_property) { + if (nv_connector->underscan != value) { + nv_connector->underscan = value; + if (!nv_crtc || !nv_crtc->set_scale) + return 0; + + return nv_crtc->set_scale(nv_crtc, true); + } + + return 0; + } + + if (property == disp->underscan_hborder_property) { + if (nv_connector->underscan_hborder != value) { + nv_connector->underscan_hborder = value; + if (!nv_crtc || !nv_crtc->set_scale) + return 0; + + return nv_crtc->set_scale(nv_crtc, true); + } + + return 0; + } + + if (property == disp->underscan_vborder_property) { + if (nv_connector->underscan_vborder != value) { + nv_connector->underscan_vborder = value; + if (!nv_crtc || !nv_crtc->set_scale) + return 0; + + return nv_crtc->set_scale(nv_crtc, true); + } + + return 0; + } + + /* Dithering */ + if (property == disp->dithering_mode) { + nv_connector->dithering_mode = value; + if (!nv_crtc || !nv_crtc->set_dither) + return 0; + + return nv_crtc->set_dither(nv_crtc, true); + } + + if (property == disp->dithering_depth) { + nv_connector->dithering_depth = value; + if (!nv_crtc || !nv_crtc->set_dither) + return 0; + + return nv_crtc->set_dither(nv_crtc, true); + } + + if (nv_crtc && nv_crtc->set_color_vibrance) { + /* Hue */ + if (property == disp->vibrant_hue_property) { + nv_crtc->vibrant_hue = value - 90; + return nv_crtc->set_color_vibrance(nv_crtc, true); + } + /* Saturation */ + if (property == disp->color_vibrance_property) { + nv_crtc->color_vibrance = value - 100; + return nv_crtc->set_color_vibrance(nv_crtc, true); + } + } + + if (nv_encoder && nv_encoder->dcb->type == OUTPUT_TV) + return get_slave_funcs(encoder)->set_property( + encoder, connector, property, value); + + return -EINVAL; +} + +static struct drm_display_mode * +nouveau_connector_native_mode(struct drm_connector *connector) +{ + struct drm_connector_helper_funcs *helper = connector->helper_private; + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode, *largest = NULL; + int high_w = 0, high_h = 0, high_v = 0; + + list_for_each_entry(mode, &nv_connector->base.probed_modes, head) { + mode->vrefresh = drm_mode_vrefresh(mode); + if (helper->mode_valid(connector, mode) != MODE_OK || + (mode->flags & DRM_MODE_FLAG_INTERLACE)) + continue; + + /* Use preferred mode if there is one.. */ + if (mode->type & DRM_MODE_TYPE_PREFERRED) { + NV_DEBUG_KMS(dev, "native mode from preferred\n"); + return drm_mode_duplicate(dev, mode); + } + + /* Otherwise, take the resolution with the largest width, then + * height, then vertical refresh + */ + if (mode->hdisplay < high_w) + continue; + + if (mode->hdisplay == high_w && mode->vdisplay < high_h) + continue; + + if (mode->hdisplay == high_w && mode->vdisplay == high_h && + mode->vrefresh < high_v) + continue; + + high_w = mode->hdisplay; + high_h = mode->vdisplay; + high_v = mode->vrefresh; + largest = mode; + } + + NV_DEBUG_KMS(dev, "native mode from largest: %dx%d@%d\n", + high_w, high_h, high_v); + return largest ? drm_mode_duplicate(dev, largest) : NULL; +} + +struct moderec { + int hdisplay; + int vdisplay; +}; + +static struct moderec scaler_modes[] = { + { 1920, 1200 }, + { 1920, 1080 }, + { 1680, 1050 }, + { 1600, 1200 }, + { 1400, 1050 }, + { 1280, 1024 }, + { 1280, 960 }, + { 1152, 864 }, + { 1024, 768 }, + { 800, 600 }, + { 720, 400 }, + { 640, 480 }, + { 640, 400 }, + { 640, 350 }, + {} +}; + +static int +nouveau_connector_scaler_modes_add(struct drm_connector *connector) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct drm_display_mode *native = nv_connector->native_mode, *m; + struct drm_device *dev = connector->dev; + struct moderec *mode = &scaler_modes[0]; + int modes = 0; + + if (!native) + return 0; + + while (mode->hdisplay) { + if (mode->hdisplay <= native->hdisplay && + mode->vdisplay <= native->vdisplay) { + m = drm_cvt_mode(dev, mode->hdisplay, mode->vdisplay, + drm_mode_vrefresh(native), false, + false, false); + if (!m) + continue; + + m->type |= DRM_MODE_TYPE_DRIVER; + + drm_mode_probed_add(connector, m); + modes++; + } + + mode++; + } + + return modes; +} + +static void +nouveau_connector_detect_depth(struct drm_connector *connector) +{ + struct drm_nouveau_private *dev_priv = connector->dev->dev_private; + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder; + struct nvbios *bios = &dev_priv->vbios; + struct drm_display_mode *mode = nv_connector->native_mode; + bool duallink; + + /* if the edid is feeling nice enough to provide this info, use it */ + if (nv_connector->edid && connector->display_info.bpc) + return; + + /* EDID 1.4 is *supposed* to be supported on eDP, but, Apple... */ + if (nv_connector->type == DCB_CONNECTOR_eDP) { + connector->display_info.bpc = 6; + return; + } + + /* we're out of options unless we're LVDS, default to 8bpc */ + if (nv_encoder->dcb->type != OUTPUT_LVDS) { + connector->display_info.bpc = 8; + return; + } + + connector->display_info.bpc = 6; + + /* LVDS: panel straps */ + if (bios->fp_no_ddc) { + if (bios->fp.if_is_24bit) + connector->display_info.bpc = 8; + return; + } + + /* LVDS: DDC panel, need to first determine the number of links to + * know which if_is_24bit flag to check... + */ + if (nv_connector->edid && + nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) + duallink = ((u8 *)nv_connector->edid)[121] == 2; + else + duallink = mode->clock >= bios->fp.duallink_transition_clk; + + if ((!duallink && (bios->fp.strapless_is_24bit & 1)) || + ( duallink && (bios->fp.strapless_is_24bit & 2))) + connector->display_info.bpc = 8; +} + +static int +nouveau_connector_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder; + struct drm_encoder *encoder = to_drm_encoder(nv_encoder); + int ret = 0; + + /* destroy the native mode, the attached monitor could have changed. + */ + if (nv_connector->native_mode) { + drm_mode_destroy(dev, nv_connector->native_mode); + nv_connector->native_mode = NULL; + } + + if (nv_connector->edid) + ret = drm_add_edid_modes(connector, nv_connector->edid); + else + if (nv_encoder->dcb->type == OUTPUT_LVDS && + (nv_encoder->dcb->lvdsconf.use_straps_for_mode || + dev_priv->vbios.fp_no_ddc) && nouveau_bios_fp_mode(dev, NULL)) { + struct drm_display_mode mode; + + nouveau_bios_fp_mode(dev, &mode); + nv_connector->native_mode = drm_mode_duplicate(dev, &mode); + } + + /* Determine display colour depth for everything except LVDS now, + * DP requires this before mode_valid() is called. + */ + if (connector->connector_type != DRM_MODE_CONNECTOR_LVDS) + nouveau_connector_detect_depth(connector); + + /* Find the native mode if this is a digital panel, if we didn't + * find any modes through DDC previously add the native mode to + * the list of modes. + */ + if (!nv_connector->native_mode) + nv_connector->native_mode = + nouveau_connector_native_mode(connector); + if (ret == 0 && nv_connector->native_mode) { + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(dev, nv_connector->native_mode); + drm_mode_probed_add(connector, mode); + ret = 1; + } + + /* Determine LVDS colour depth, must happen after determining + * "native" mode as some VBIOS tables require us to use the + * pixel clock as part of the lookup... + */ + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) + nouveau_connector_detect_depth(connector); + + if (nv_encoder->dcb->type == OUTPUT_TV) + ret = get_slave_funcs(encoder)->get_modes(encoder, connector); + + if (nv_connector->type == DCB_CONNECTOR_LVDS || + nv_connector->type == DCB_CONNECTOR_LVDS_SPWG || + nv_connector->type == DCB_CONNECTOR_eDP) + ret += nouveau_connector_scaler_modes_add(connector); + + return ret; +} + +static unsigned +get_tmds_link_bandwidth(struct drm_connector *connector) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct drm_nouveau_private *dev_priv = connector->dev->dev_private; + struct dcb_entry *dcb = nv_connector->detected_encoder->dcb; + + if (dcb->location != DCB_LOC_ON_CHIP || + dev_priv->chipset >= 0x46) + return 165000; + else if (dev_priv->chipset >= 0x40) + return 155000; + else if (dev_priv->chipset >= 0x18) + return 135000; + else + return 112000; +} + +static int +nouveau_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder; + struct drm_encoder *encoder = to_drm_encoder(nv_encoder); + unsigned min_clock = 25000, max_clock = min_clock; + unsigned clock = mode->clock; + + switch (nv_encoder->dcb->type) { + case OUTPUT_LVDS: + if (nv_connector->native_mode && + (mode->hdisplay > nv_connector->native_mode->hdisplay || + mode->vdisplay > nv_connector->native_mode->vdisplay)) + return MODE_PANEL; + + min_clock = 0; + max_clock = 400000; + break; + case OUTPUT_TMDS: + max_clock = get_tmds_link_bandwidth(connector); + if (nouveau_duallink && nv_encoder->dcb->duallink_possible) + max_clock *= 2; + break; + case OUTPUT_ANALOG: + max_clock = nv_encoder->dcb->crtconf.maxfreq; + if (!max_clock) + max_clock = 350000; + break; + case OUTPUT_TV: + return get_slave_funcs(encoder)->mode_valid(encoder, mode); + case OUTPUT_DP: + max_clock = nv_encoder->dp.link_nr; + max_clock *= nv_encoder->dp.link_bw; + clock = clock * (connector->display_info.bpc * 3) / 10; + break; + default: + BUG_ON(1); + return MODE_BAD; + } + + if (clock < min_clock) + return MODE_CLOCK_LOW; + + if (clock > max_clock) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static struct drm_encoder * +nouveau_connector_best_encoder(struct drm_connector *connector) +{ + struct nouveau_connector *nv_connector = nouveau_connector(connector); + + if (nv_connector->detected_encoder) + return to_drm_encoder(nv_connector->detected_encoder); + + return NULL; +} + +static const struct drm_connector_helper_funcs +nouveau_connector_helper_funcs = { + .get_modes = nouveau_connector_get_modes, + .mode_valid = nouveau_connector_mode_valid, + .best_encoder = nouveau_connector_best_encoder, +}; + +static const struct drm_connector_funcs +nouveau_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .save = NULL, + .restore = NULL, + .detect = nouveau_connector_detect, + .destroy = nouveau_connector_destroy, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = nouveau_connector_set_property, + .force = nouveau_connector_force +}; + +static const struct drm_connector_funcs +nouveau_connector_funcs_lvds = { + .dpms = drm_helper_connector_dpms, + .save = NULL, + .restore = NULL, + .detect = nouveau_connector_detect_lvds, + .destroy = nouveau_connector_destroy, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = nouveau_connector_set_property, + .force = nouveau_connector_force +}; + +static int +drm_conntype_from_dcb(enum dcb_connector_type dcb) +{ + switch (dcb) { + case DCB_CONNECTOR_VGA : return DRM_MODE_CONNECTOR_VGA; + case DCB_CONNECTOR_TV_0 : + case DCB_CONNECTOR_TV_1 : + case DCB_CONNECTOR_TV_3 : return DRM_MODE_CONNECTOR_TV; + case DCB_CONNECTOR_DMS59_0 : + case DCB_CONNECTOR_DMS59_1 : + case DCB_CONNECTOR_DVI_I : return DRM_MODE_CONNECTOR_DVII; + case DCB_CONNECTOR_DVI_D : return DRM_MODE_CONNECTOR_DVID; + case DCB_CONNECTOR_LVDS : + case DCB_CONNECTOR_LVDS_SPWG: return DRM_MODE_CONNECTOR_LVDS; + case DCB_CONNECTOR_DMS59_DP0: + case DCB_CONNECTOR_DMS59_DP1: + case DCB_CONNECTOR_DP : return DRM_MODE_CONNECTOR_DisplayPort; + case DCB_CONNECTOR_eDP : return DRM_MODE_CONNECTOR_eDP; + case DCB_CONNECTOR_HDMI_0 : + case DCB_CONNECTOR_HDMI_1 : return DRM_MODE_CONNECTOR_HDMIA; + default: + break; + } + + return DRM_MODE_CONNECTOR_Unknown; +} + +struct drm_connector * +nouveau_connector_create(struct drm_device *dev, int index) +{ + const struct drm_connector_funcs *funcs = &nouveau_connector_funcs; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; + struct nouveau_connector *nv_connector = NULL; + struct drm_connector *connector; + int type, ret = 0; + bool dummy; + + NV_DEBUG_KMS(dev, "\n"); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + nv_connector = nouveau_connector(connector); + if (nv_connector->index == index) + return connector; + } + + nv_connector = kzalloc(sizeof(*nv_connector), GFP_KERNEL); + if (!nv_connector) + return ERR_PTR(-ENOMEM); + + connector = &nv_connector->base; + nv_connector->index = index; + + /* attempt to parse vbios connector type and hotplug gpio */ + nv_connector->dcb = dcb_conn(dev, index); + if (nv_connector->dcb) { + static const u8 hpd[16] = { + 0xff, 0x07, 0x08, 0xff, 0xff, 0x51, 0x52, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x5e, 0x5f, 0x60, + }; + + u32 entry = ROM16(nv_connector->dcb[0]); + if (dcb_conntab(dev)[3] >= 4) + entry |= (u32)ROM16(nv_connector->dcb[2]) << 16; + + nv_connector->hpd = ffs((entry & 0x07033000) >> 12); + nv_connector->hpd = hpd[nv_connector->hpd]; + + nv_connector->type = nv_connector->dcb[0]; + if (drm_conntype_from_dcb(nv_connector->type) == + DRM_MODE_CONNECTOR_Unknown) { + NV_WARN(dev, "unknown connector type %02x\n", + nv_connector->type); + nv_connector->type = DCB_CONNECTOR_NONE; + } + + /* Gigabyte NX85T */ + if (nv_match_device(dev, 0x0421, 0x1458, 0x344c)) { + if (nv_connector->type == DCB_CONNECTOR_HDMI_1) + nv_connector->type = DCB_CONNECTOR_DVI_I; + } + + /* Gigabyte GV-NX86T512H */ + if (nv_match_device(dev, 0x0402, 0x1458, 0x3455)) { + if (nv_connector->type == DCB_CONNECTOR_HDMI_1) + nv_connector->type = DCB_CONNECTOR_DVI_I; + } + } else { + nv_connector->type = DCB_CONNECTOR_NONE; + nv_connector->hpd = DCB_GPIO_UNUSED; + } + + /* no vbios data, or an unknown dcb connector type - attempt to + * figure out something suitable ourselves + */ + if (nv_connector->type == DCB_CONNECTOR_NONE) { + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_table *dcbt = &dev_priv->vbios.dcb; + u32 encoders = 0; + int i; + + for (i = 0; i < dcbt->entries; i++) { + if (dcbt->entry[i].connector == nv_connector->index) + encoders |= (1 << dcbt->entry[i].type); + } + + if (encoders & (1 << OUTPUT_DP)) { + if (encoders & (1 << OUTPUT_TMDS)) + nv_connector->type = DCB_CONNECTOR_DP; + else + nv_connector->type = DCB_CONNECTOR_eDP; + } else + if (encoders & (1 << OUTPUT_TMDS)) { + if (encoders & (1 << OUTPUT_ANALOG)) + nv_connector->type = DCB_CONNECTOR_DVI_I; + else + nv_connector->type = DCB_CONNECTOR_DVI_D; + } else + if (encoders & (1 << OUTPUT_ANALOG)) { + nv_connector->type = DCB_CONNECTOR_VGA; + } else + if (encoders & (1 << OUTPUT_LVDS)) { + nv_connector->type = DCB_CONNECTOR_LVDS; + } else + if (encoders & (1 << OUTPUT_TV)) { + nv_connector->type = DCB_CONNECTOR_TV_0; + } + } + + type = drm_conntype_from_dcb(nv_connector->type); + if (type == DRM_MODE_CONNECTOR_LVDS) { + ret = nouveau_bios_parse_lvds_table(dev, 0, &dummy, &dummy); + if (ret) { + NV_ERROR(dev, "Error parsing LVDS table, disabling\n"); + kfree(nv_connector); + return ERR_PTR(ret); + } + + funcs = &nouveau_connector_funcs_lvds; + } else { + funcs = &nouveau_connector_funcs; + } + + /* defaults, will get overridden in detect() */ + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + + drm_connector_init(dev, connector, funcs, type); + drm_connector_helper_add(connector, &nouveau_connector_helper_funcs); + + /* Init DVI-I specific properties */ + if (nv_connector->type == DCB_CONNECTOR_DVI_I) + drm_connector_attach_property(connector, dev->mode_config.dvi_i_subconnector_property, 0); + + /* Add overscan compensation options to digital outputs */ + if (disp->underscan_property && + (type == DRM_MODE_CONNECTOR_DVID || + type == DRM_MODE_CONNECTOR_DVII || + type == DRM_MODE_CONNECTOR_HDMIA || + type == DRM_MODE_CONNECTOR_DisplayPort)) { + drm_connector_attach_property(connector, + disp->underscan_property, + UNDERSCAN_OFF); + drm_connector_attach_property(connector, + disp->underscan_hborder_property, + 0); + drm_connector_attach_property(connector, + disp->underscan_vborder_property, + 0); + } + + /* Add hue and saturation options */ + if (disp->vibrant_hue_property) + drm_connector_attach_property(connector, + disp->vibrant_hue_property, + 90); + if (disp->color_vibrance_property) + drm_connector_attach_property(connector, + disp->color_vibrance_property, + 150); + + switch (nv_connector->type) { + case DCB_CONNECTOR_VGA: + if (dev_priv->card_type >= NV_50) { + drm_connector_attach_property(connector, + dev->mode_config.scaling_mode_property, + nv_connector->scaling_mode); + } + /* fall-through */ + case DCB_CONNECTOR_TV_0: + case DCB_CONNECTOR_TV_1: + case DCB_CONNECTOR_TV_3: + nv_connector->scaling_mode = DRM_MODE_SCALE_NONE; + break; + default: + nv_connector->scaling_mode = DRM_MODE_SCALE_FULLSCREEN; + + drm_connector_attach_property(connector, + dev->mode_config.scaling_mode_property, + nv_connector->scaling_mode); + if (disp->dithering_mode) { + nv_connector->dithering_mode = DITHERING_MODE_AUTO; + drm_connector_attach_property(connector, + disp->dithering_mode, + nv_connector->dithering_mode); + } + if (disp->dithering_depth) { + nv_connector->dithering_depth = DITHERING_DEPTH_AUTO; + drm_connector_attach_property(connector, + disp->dithering_depth, + nv_connector->dithering_depth); + } + break; + } + + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + if (nv_connector->hpd != DCB_GPIO_UNUSED) { + ret = nouveau_gpio_isr_add(dev, 0, nv_connector->hpd, 0xff, + nouveau_connector_hotplug, + connector); + if (ret == 0) + connector->polled = DRM_CONNECTOR_POLL_HPD; + } + + drm_sysfs_connector_add(connector); + return connector; +} + +static void +nouveau_connector_hotplug(void *data, int plugged) +{ + struct drm_connector *connector = data; + struct drm_device *dev = connector->dev; + + NV_DEBUG(dev, "%splugged %s\n", plugged ? "" : "un", + drm_get_connector_name(connector)); + + if (plugged) + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); + else + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + + drm_helper_hpd_irq_event(dev); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h new file mode 100644 index 00000000..e4857021 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_connector.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_CONNECTOR_H__ +#define __NOUVEAU_CONNECTOR_H__ + +#include "drm_edid.h" +#include "nouveau_i2c.h" + +enum nouveau_underscan_type { + UNDERSCAN_OFF, + UNDERSCAN_ON, + UNDERSCAN_AUTO, +}; + +/* the enum values specifically defined here match nv50/nvd0 hw values, and + * the code relies on this + */ +enum nouveau_dithering_mode { + DITHERING_MODE_OFF = 0x00, + DITHERING_MODE_ON = 0x01, + DITHERING_MODE_DYNAMIC2X2 = 0x10 | DITHERING_MODE_ON, + DITHERING_MODE_STATIC2X2 = 0x18 | DITHERING_MODE_ON, + DITHERING_MODE_TEMPORAL = 0x20 | DITHERING_MODE_ON, + DITHERING_MODE_AUTO +}; + +enum nouveau_dithering_depth { + DITHERING_DEPTH_6BPC = 0x00, + DITHERING_DEPTH_8BPC = 0x02, + DITHERING_DEPTH_AUTO +}; + +struct nouveau_connector { + struct drm_connector base; + enum dcb_connector_type type; + u8 index; + u8 *dcb; + u8 hpd; + + int dithering_mode; + int dithering_depth; + int scaling_mode; + enum nouveau_underscan_type underscan; + u32 underscan_hborder; + u32 underscan_vborder; + + struct nouveau_encoder *detected_encoder; + struct edid *edid; + struct drm_display_mode *native_mode; +}; + +static inline struct nouveau_connector *nouveau_connector( + struct drm_connector *con) +{ + return container_of(con, struct nouveau_connector, base); +} + +struct drm_connector * +nouveau_connector_create(struct drm_device *, int index); + +int +nouveau_connector_bpp(struct drm_connector *); + +#endif /* __NOUVEAU_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/nouveau/nouveau_crtc.h b/drivers/gpu/drm/nouveau/nouveau_crtc.h new file mode 100644 index 00000000..e6d0d1eb --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_crtc.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_CRTC_H__ +#define __NOUVEAU_CRTC_H__ + +struct nouveau_crtc { + struct drm_crtc base; + + int index; + + uint32_t dpms_saved_fp_control; + uint32_t fp_users; + int saturation; + int color_vibrance; + int vibrant_hue; + int sharpness; + int last_dpms; + + int cursor_saved_x, cursor_saved_y; + + struct { + int cpp; + bool blanked; + uint32_t offset; + uint32_t tile_flags; + } fb; + + struct { + struct nouveau_bo *nvbo; + bool visible; + uint32_t offset; + void (*set_offset)(struct nouveau_crtc *, uint32_t offset); + void (*set_pos)(struct nouveau_crtc *, int x, int y); + void (*hide)(struct nouveau_crtc *, bool update); + void (*show)(struct nouveau_crtc *, bool update); + } cursor; + + struct { + struct nouveau_bo *nvbo; + uint16_t r[256]; + uint16_t g[256]; + uint16_t b[256]; + int depth; + } lut; + + int (*set_dither)(struct nouveau_crtc *crtc, bool update); + int (*set_scale)(struct nouveau_crtc *crtc, bool update); + int (*set_color_vibrance)(struct nouveau_crtc *crtc, bool update); +}; + +static inline struct nouveau_crtc *nouveau_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct nouveau_crtc, base); +} + +static inline struct drm_crtc *to_drm_crtc(struct nouveau_crtc *crtc) +{ + return &crtc->base; +} + +int nv50_crtc_create(struct drm_device *dev, int index); +int nv50_crtc_cursor_set(struct drm_crtc *drm_crtc, struct drm_file *file_priv, + uint32_t buffer_handle, uint32_t width, + uint32_t height); +int nv50_crtc_cursor_move(struct drm_crtc *drm_crtc, int x, int y); + +int nv04_cursor_init(struct nouveau_crtc *); +int nv50_cursor_init(struct nouveau_crtc *); + +struct nouveau_connector * +nouveau_crtc_connector_get(struct nouveau_crtc *crtc); + +#endif /* __NOUVEAU_CRTC_H__ */ diff --git a/drivers/gpu/drm/nouveau/nouveau_debugfs.c b/drivers/gpu/drm/nouveau/nouveau_debugfs.c new file mode 100644 index 00000000..fa2ec491 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_debugfs.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2009 Red Hat <bskeggs@redhat.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* + * Authors: + * Ben Skeggs <bskeggs@redhat.com> + */ + +#include <linux/debugfs.h> + +#include "drmP.h" +#include "nouveau_drv.h" + +#include <ttm/ttm_page_alloc.h> + +static int +nouveau_debugfs_channel_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct nouveau_channel *chan = node->info_ent->data; + + seq_printf(m, "channel id : %d\n", chan->id); + + seq_printf(m, "cpu fifo state:\n"); + seq_printf(m, " base: 0x%10llx\n", chan->pushbuf_base); + seq_printf(m, " max: 0x%08x\n", chan->dma.max << 2); + seq_printf(m, " cur: 0x%08x\n", chan->dma.cur << 2); + seq_printf(m, " put: 0x%08x\n", chan->dma.put << 2); + seq_printf(m, " free: 0x%08x\n", chan->dma.free << 2); + if (chan->dma.ib_max) { + seq_printf(m, " ib max: 0x%08x\n", chan->dma.ib_max); + seq_printf(m, " ib put: 0x%08x\n", chan->dma.ib_put); + seq_printf(m, " ib free: 0x%08x\n", chan->dma.ib_free); + } + + seq_printf(m, "gpu fifo state:\n"); + seq_printf(m, " get: 0x%08x\n", + nvchan_rd32(chan, chan->user_get)); + seq_printf(m, " put: 0x%08x\n", + nvchan_rd32(chan, chan->user_put)); + if (chan->dma.ib_max) { + seq_printf(m, " ib get: 0x%08x\n", + nvchan_rd32(chan, 0x88)); + seq_printf(m, " ib put: 0x%08x\n", + nvchan_rd32(chan, 0x8c)); + } + + seq_printf(m, "last fence : %d\n", chan->fence.sequence); + seq_printf(m, "last signalled: %d\n", chan->fence.sequence_ack); + return 0; +} + +int +nouveau_debugfs_channel_init(struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct drm_minor *minor = chan->dev->primary; + int ret; + + if (!dev_priv->debugfs.channel_root) { + dev_priv->debugfs.channel_root = + debugfs_create_dir("channel", minor->debugfs_root); + if (!dev_priv->debugfs.channel_root) + return -ENOENT; + } + + snprintf(chan->debugfs.name, 32, "%d", chan->id); + chan->debugfs.info.name = chan->debugfs.name; + chan->debugfs.info.show = nouveau_debugfs_channel_info; + chan->debugfs.info.driver_features = 0; + chan->debugfs.info.data = chan; + + ret = drm_debugfs_create_files(&chan->debugfs.info, 1, + dev_priv->debugfs.channel_root, + chan->dev->primary); + if (ret == 0) + chan->debugfs.active = true; + return ret; +} + +void +nouveau_debugfs_channel_fini(struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + + if (!chan->debugfs.active) + return; + + drm_debugfs_remove_files(&chan->debugfs.info, 1, chan->dev->primary); + chan->debugfs.active = false; + + if (chan == dev_priv->channel) { + debugfs_remove(dev_priv->debugfs.channel_root); + dev_priv->debugfs.channel_root = NULL; + } +} + +static int +nouveau_debugfs_chipset_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_minor *minor = node->minor; + struct drm_device *dev = minor->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t ppci_0; + + ppci_0 = nv_rd32(dev, dev_priv->chipset >= 0x40 ? 0x88000 : 0x1800); + + seq_printf(m, "PMC_BOOT_0: 0x%08x\n", nv_rd32(dev, NV03_PMC_BOOT_0)); + seq_printf(m, "PCI ID : 0x%04x:0x%04x\n", + ppci_0 & 0xffff, ppci_0 >> 16); + return 0; +} + +static int +nouveau_debugfs_memory_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_minor *minor = node->minor; + struct drm_nouveau_private *dev_priv = minor->dev->dev_private; + + seq_printf(m, "VRAM total: %dKiB\n", (int)(dev_priv->vram_size >> 10)); + return 0; +} + +static int +nouveau_debugfs_vbios_image(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_nouveau_private *dev_priv = node->minor->dev->dev_private; + int i; + + for (i = 0; i < dev_priv->vbios.length; i++) + seq_printf(m, "%c", dev_priv->vbios.data[i]); + return 0; +} + +static int +nouveau_debugfs_evict_vram(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_nouveau_private *dev_priv = node->minor->dev->dev_private; + int ret; + + ret = ttm_bo_evict_mm(&dev_priv->ttm.bdev, TTM_PL_VRAM); + if (ret) + seq_printf(m, "failed: %d", ret); + else + seq_printf(m, "succeeded\n"); + return 0; +} + +static struct drm_info_list nouveau_debugfs_list[] = { + { "evict_vram", nouveau_debugfs_evict_vram, 0, NULL }, + { "chipset", nouveau_debugfs_chipset_info, 0, NULL }, + { "memory", nouveau_debugfs_memory_info, 0, NULL }, + { "vbios.rom", nouveau_debugfs_vbios_image, 0, NULL }, + { "ttm_page_pool", ttm_page_alloc_debugfs, 0, NULL }, + { "ttm_dma_page_pool", ttm_dma_page_alloc_debugfs, 0, NULL }, +}; +#define NOUVEAU_DEBUGFS_ENTRIES ARRAY_SIZE(nouveau_debugfs_list) + +int +nouveau_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES, + minor->debugfs_root, minor); + return 0; +} + +void +nouveau_debugfs_takedown(struct drm_minor *minor) +{ + drm_debugfs_remove_files(nouveau_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES, + minor); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c new file mode 100644 index 00000000..a85e1128 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -0,0 +1,621 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" +#include "nouveau_drv.h" +#include "nouveau_fb.h" +#include "nouveau_fbcon.h" +#include "nouveau_hw.h" +#include "nouveau_crtc.h" +#include "nouveau_dma.h" +#include "nouveau_connector.h" +#include "nouveau_gpio.h" +#include "nv50_display.h" + +static void +nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb) +{ + struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb); + + if (fb->nvbo) + drm_gem_object_unreference_unlocked(fb->nvbo->gem); + + drm_framebuffer_cleanup(drm_fb); + kfree(fb); +} + +static int +nouveau_user_framebuffer_create_handle(struct drm_framebuffer *drm_fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb); + + return drm_gem_handle_create(file_priv, fb->nvbo->gem, handle); +} + +static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = { + .destroy = nouveau_user_framebuffer_destroy, + .create_handle = nouveau_user_framebuffer_create_handle, +}; + +int +nouveau_framebuffer_init(struct drm_device *dev, + struct nouveau_framebuffer *nv_fb, + struct drm_mode_fb_cmd2 *mode_cmd, + struct nouveau_bo *nvbo) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_framebuffer *fb = &nv_fb->base; + int ret; + + ret = drm_framebuffer_init(dev, fb, &nouveau_framebuffer_funcs); + if (ret) { + return ret; + } + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + nv_fb->nvbo = nvbo; + + if (dev_priv->card_type >= NV_50) { + u32 tile_flags = nouveau_bo_tile_layout(nvbo); + if (tile_flags == 0x7a00 || + tile_flags == 0xfe00) + nv_fb->r_dma = NvEvoFB32; + else + if (tile_flags == 0x7000) + nv_fb->r_dma = NvEvoFB16; + else + nv_fb->r_dma = NvEvoVRAM_LP; + + switch (fb->depth) { + case 8: nv_fb->r_format = NV50_EVO_CRTC_FB_DEPTH_8; break; + case 15: nv_fb->r_format = NV50_EVO_CRTC_FB_DEPTH_15; break; + case 16: nv_fb->r_format = NV50_EVO_CRTC_FB_DEPTH_16; break; + case 24: + case 32: nv_fb->r_format = NV50_EVO_CRTC_FB_DEPTH_24; break; + case 30: nv_fb->r_format = NV50_EVO_CRTC_FB_DEPTH_30; break; + default: + NV_ERROR(dev, "unknown depth %d\n", fb->depth); + return -EINVAL; + } + + if (dev_priv->chipset == 0x50) + nv_fb->r_format |= (tile_flags << 8); + + if (!tile_flags) { + if (dev_priv->card_type < NV_D0) + nv_fb->r_pitch = 0x00100000 | fb->pitches[0]; + else + nv_fb->r_pitch = 0x01000000 | fb->pitches[0]; + } else { + u32 mode = nvbo->tile_mode; + if (dev_priv->card_type >= NV_C0) + mode >>= 4; + nv_fb->r_pitch = ((fb->pitches[0] / 4) << 4) | mode; + } + } + + return 0; +} + +static struct drm_framebuffer * +nouveau_user_framebuffer_create(struct drm_device *dev, + struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct nouveau_framebuffer *nouveau_fb; + struct drm_gem_object *gem; + int ret; + + gem = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); + if (!gem) + return ERR_PTR(-ENOENT); + + nouveau_fb = kzalloc(sizeof(struct nouveau_framebuffer), GFP_KERNEL); + if (!nouveau_fb) + return ERR_PTR(-ENOMEM); + + ret = nouveau_framebuffer_init(dev, nouveau_fb, mode_cmd, nouveau_gem_object(gem)); + if (ret) { + drm_gem_object_unreference(gem); + return ERR_PTR(ret); + } + + return &nouveau_fb->base; +} + +static const struct drm_mode_config_funcs nouveau_mode_config_funcs = { + .fb_create = nouveau_user_framebuffer_create, + .output_poll_changed = nouveau_fbcon_output_poll_changed, +}; + + +struct nouveau_drm_prop_enum_list { + u8 gen_mask; + int type; + char *name; +}; + +static struct nouveau_drm_prop_enum_list underscan[] = { + { 6, UNDERSCAN_AUTO, "auto" }, + { 6, UNDERSCAN_OFF, "off" }, + { 6, UNDERSCAN_ON, "on" }, + {} +}; + +static struct nouveau_drm_prop_enum_list dither_mode[] = { + { 7, DITHERING_MODE_AUTO, "auto" }, + { 7, DITHERING_MODE_OFF, "off" }, + { 1, DITHERING_MODE_ON, "on" }, + { 6, DITHERING_MODE_STATIC2X2, "static 2x2" }, + { 6, DITHERING_MODE_DYNAMIC2X2, "dynamic 2x2" }, + { 4, DITHERING_MODE_TEMPORAL, "temporal" }, + {} +}; + +static struct nouveau_drm_prop_enum_list dither_depth[] = { + { 6, DITHERING_DEPTH_AUTO, "auto" }, + { 6, DITHERING_DEPTH_6BPC, "6 bpc" }, + { 6, DITHERING_DEPTH_8BPC, "8 bpc" }, + {} +}; + +#define PROP_ENUM(p,gen,n,list) do { \ + struct nouveau_drm_prop_enum_list *l = (list); \ + int c = 0; \ + while (l->gen_mask) { \ + if (l->gen_mask & (1 << (gen))) \ + c++; \ + l++; \ + } \ + if (c) { \ + p = drm_property_create(dev, DRM_MODE_PROP_ENUM, n, c); \ + l = (list); \ + c = 0; \ + while (p && l->gen_mask) { \ + if (l->gen_mask & (1 << (gen))) { \ + drm_property_add_enum(p, c, l->type, l->name); \ + c++; \ + } \ + l++; \ + } \ + } \ +} while(0) + +int +nouveau_display_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; + struct drm_connector *connector; + int ret; + + ret = disp->init(dev); + if (ret) + return ret; + + /* power on internal panel if it's not already. the init tables of + * some vbios default this to off for some reason, causing the + * panel to not work after resume + */ + if (nouveau_gpio_func_get(dev, DCB_GPIO_PANEL_POWER) == 0) { + nouveau_gpio_func_set(dev, DCB_GPIO_PANEL_POWER, true); + msleep(300); + } + + /* enable polling for external displays */ + drm_kms_helper_poll_enable(dev); + + /* enable hotplug interrupts */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + struct nouveau_connector *conn = nouveau_connector(connector); + nouveau_gpio_irq(dev, 0, conn->hpd, 0xff, true); + } + + return ret; +} + +void +nouveau_display_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; + struct drm_connector *connector; + + /* disable hotplug interrupts */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + struct nouveau_connector *conn = nouveau_connector(connector); + nouveau_gpio_irq(dev, 0, conn->hpd, 0xff, false); + } + + drm_kms_helper_poll_disable(dev); + disp->fini(dev); +} + +int +nouveau_display_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; + int ret, gen; + + drm_mode_config_init(dev); + drm_mode_create_scaling_mode_property(dev); + drm_mode_create_dvi_i_properties(dev); + + if (dev_priv->card_type < NV_50) + gen = 0; + else + if (dev_priv->card_type < NV_D0) + gen = 1; + else + gen = 2; + + PROP_ENUM(disp->dithering_mode, gen, "dithering mode", dither_mode); + PROP_ENUM(disp->dithering_depth, gen, "dithering depth", dither_depth); + PROP_ENUM(disp->underscan_property, gen, "underscan", underscan); + + disp->underscan_hborder_property = + drm_property_create_range(dev, 0, "underscan hborder", 0, 128); + + disp->underscan_vborder_property = + drm_property_create_range(dev, 0, "underscan vborder", 0, 128); + + if (gen == 1) { + disp->vibrant_hue_property = + drm_property_create(dev, DRM_MODE_PROP_RANGE, + "vibrant hue", 2); + disp->vibrant_hue_property->values[0] = 0; + disp->vibrant_hue_property->values[1] = 180; /* -90..+90 */ + + disp->color_vibrance_property = + drm_property_create(dev, DRM_MODE_PROP_RANGE, + "color vibrance", 2); + disp->color_vibrance_property->values[0] = 0; + disp->color_vibrance_property->values[1] = 200; /* -100..+100 */ + } + + dev->mode_config.funcs = (void *)&nouveau_mode_config_funcs; + dev->mode_config.fb_base = pci_resource_start(dev->pdev, 1); + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + if (dev_priv->card_type < NV_10) { + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + } else + if (dev_priv->card_type < NV_50) { + dev->mode_config.max_width = 4096; + dev->mode_config.max_height = 4096; + } else { + dev->mode_config.max_width = 8192; + dev->mode_config.max_height = 8192; + } + + dev->mode_config.preferred_depth = 24; + dev->mode_config.prefer_shadow = 1; + + drm_kms_helper_poll_init(dev); + drm_kms_helper_poll_disable(dev); + + ret = disp->create(dev); + if (ret) + return ret; + + if (dev->mode_config.num_crtc) { + ret = drm_vblank_init(dev, dev->mode_config.num_crtc); + if (ret) + return ret; + } + + return ret; +} + +void +nouveau_display_destroy(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_display_engine *disp = &dev_priv->engine.display; + + drm_vblank_cleanup(dev); + + disp->destroy(dev); + + drm_kms_helper_poll_fini(dev); + drm_mode_config_cleanup(dev); +} + +int +nouveau_vblank_enable(struct drm_device *dev, int crtc) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->card_type >= NV_50) + nv_mask(dev, NV50_PDISPLAY_INTR_EN_1, 0, + NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_(crtc)); + else + NVWriteCRTC(dev, crtc, NV_PCRTC_INTR_EN_0, + NV_PCRTC_INTR_0_VBLANK); + + return 0; +} + +void +nouveau_vblank_disable(struct drm_device *dev, int crtc) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->card_type >= NV_50) + nv_mask(dev, NV50_PDISPLAY_INTR_EN_1, + NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_(crtc), 0); + else + NVWriteCRTC(dev, crtc, NV_PCRTC_INTR_EN_0, 0); +} + +static int +nouveau_page_flip_reserve(struct nouveau_bo *old_bo, + struct nouveau_bo *new_bo) +{ + int ret; + + ret = nouveau_bo_pin(new_bo, TTM_PL_FLAG_VRAM); + if (ret) + return ret; + + ret = ttm_bo_reserve(&new_bo->bo, false, false, false, 0); + if (ret) + goto fail; + + ret = ttm_bo_reserve(&old_bo->bo, false, false, false, 0); + if (ret) + goto fail_unreserve; + + return 0; + +fail_unreserve: + ttm_bo_unreserve(&new_bo->bo); +fail: + nouveau_bo_unpin(new_bo); + return ret; +} + +static void +nouveau_page_flip_unreserve(struct nouveau_bo *old_bo, + struct nouveau_bo *new_bo, + struct nouveau_fence *fence) +{ + nouveau_bo_fence(new_bo, fence); + ttm_bo_unreserve(&new_bo->bo); + + nouveau_bo_fence(old_bo, fence); + ttm_bo_unreserve(&old_bo->bo); + + nouveau_bo_unpin(old_bo); +} + +static int +nouveau_page_flip_emit(struct nouveau_channel *chan, + struct nouveau_bo *old_bo, + struct nouveau_bo *new_bo, + struct nouveau_page_flip_state *s, + struct nouveau_fence **pfence) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct drm_device *dev = chan->dev; + unsigned long flags; + int ret; + + /* Queue it to the pending list */ + spin_lock_irqsave(&dev->event_lock, flags); + list_add_tail(&s->head, &chan->nvsw.flip); + spin_unlock_irqrestore(&dev->event_lock, flags); + + /* Synchronize with the old framebuffer */ + ret = nouveau_fence_sync(old_bo->bo.sync_obj, chan); + if (ret) + goto fail; + + /* Emit the pageflip */ + ret = RING_SPACE(chan, 3); + if (ret) + goto fail; + + if (dev_priv->card_type < NV_C0) { + BEGIN_RING(chan, NvSubSw, NV_SW_PAGE_FLIP, 1); + OUT_RING (chan, 0x00000000); + OUT_RING (chan, 0x00000000); + } else { + BEGIN_NVC0(chan, 2, 0, NV10_SUBCHAN_REF_CNT, 1); + OUT_RING (chan, ++chan->fence.sequence); + BEGIN_NVC0(chan, 8, 0, NVSW_SUBCHAN_PAGE_FLIP, 0x0000); + } + FIRE_RING (chan); + + ret = nouveau_fence_new(chan, pfence, true); + if (ret) + goto fail; + + return 0; +fail: + spin_lock_irqsave(&dev->event_lock, flags); + list_del(&s->head); + spin_unlock_irqrestore(&dev->event_lock, flags); + return ret; +} + +int +nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct drm_device *dev = crtc->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_bo *old_bo = nouveau_framebuffer(crtc->fb)->nvbo; + struct nouveau_bo *new_bo = nouveau_framebuffer(fb)->nvbo; + struct nouveau_page_flip_state *s; + struct nouveau_channel *chan; + struct nouveau_fence *fence; + int ret; + + if (!dev_priv->channel) + return -ENODEV; + + s = kzalloc(sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + + /* Don't let the buffers go away while we flip */ + ret = nouveau_page_flip_reserve(old_bo, new_bo); + if (ret) + goto fail_free; + + /* Initialize a page flip struct */ + *s = (struct nouveau_page_flip_state) + { { }, event, nouveau_crtc(crtc)->index, + fb->bits_per_pixel, fb->pitches[0], crtc->x, crtc->y, + new_bo->bo.offset }; + + /* Choose the channel the flip will be handled in */ + chan = nouveau_fence_channel(new_bo->bo.sync_obj); + if (!chan) + chan = nouveau_channel_get_unlocked(dev_priv->channel); + mutex_lock(&chan->mutex); + + /* Emit a page flip */ + if (dev_priv->card_type >= NV_50) { + if (dev_priv->card_type >= NV_D0) + ret = nvd0_display_flip_next(crtc, fb, chan, 0); + else + ret = nv50_display_flip_next(crtc, fb, chan); + if (ret) { + nouveau_channel_put(&chan); + goto fail_unreserve; + } + } + + ret = nouveau_page_flip_emit(chan, old_bo, new_bo, s, &fence); + nouveau_channel_put(&chan); + if (ret) + goto fail_unreserve; + + /* Update the crtc struct and cleanup */ + crtc->fb = fb; + + nouveau_page_flip_unreserve(old_bo, new_bo, fence); + nouveau_fence_unref(&fence); + return 0; + +fail_unreserve: + nouveau_page_flip_unreserve(old_bo, new_bo, NULL); +fail_free: + kfree(s); + return ret; +} + +int +nouveau_finish_page_flip(struct nouveau_channel *chan, + struct nouveau_page_flip_state *ps) +{ + struct drm_device *dev = chan->dev; + struct nouveau_page_flip_state *s; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + + if (list_empty(&chan->nvsw.flip)) { + NV_ERROR(dev, "Unexpected pageflip in channel %d.\n", chan->id); + spin_unlock_irqrestore(&dev->event_lock, flags); + return -EINVAL; + } + + s = list_first_entry(&chan->nvsw.flip, + struct nouveau_page_flip_state, head); + if (s->event) { + struct drm_pending_vblank_event *e = s->event; + struct timeval now; + + do_gettimeofday(&now); + e->event.sequence = 0; + e->event.tv_sec = now.tv_sec; + e->event.tv_usec = now.tv_usec; + list_add_tail(&e->base.link, &e->base.file_priv->event_list); + wake_up_interruptible(&e->base.file_priv->event_wait); + } + + list_del(&s->head); + if (ps) + *ps = *s; + kfree(s); + + spin_unlock_irqrestore(&dev->event_lock, flags); + return 0; +} + +int +nouveau_display_dumb_create(struct drm_file *file_priv, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct nouveau_bo *bo; + int ret; + + args->pitch = roundup(args->width * (args->bpp / 8), 256); + args->size = args->pitch * args->height; + args->size = roundup(args->size, PAGE_SIZE); + + ret = nouveau_gem_new(dev, args->size, 0, TTM_PL_FLAG_VRAM, 0, 0, &bo); + if (ret) + return ret; + + ret = drm_gem_handle_create(file_priv, bo->gem, &args->handle); + drm_gem_object_unreference_unlocked(bo->gem); + return ret; +} + +int +nouveau_display_dumb_destroy(struct drm_file *file_priv, struct drm_device *dev, + uint32_t handle) +{ + return drm_gem_handle_delete(file_priv, handle); +} + +int +nouveau_display_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, + uint32_t handle, uint64_t *poffset) +{ + struct drm_gem_object *gem; + + gem = drm_gem_object_lookup(dev, file_priv, handle); + if (gem) { + struct nouveau_bo *bo = gem->driver_private; + *poffset = bo->bo.addr_space_offset; + drm_gem_object_unreference_unlocked(gem); + return 0; + } + + return -ENOENT; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_dma.c b/drivers/gpu/drm/nouveau/nouveau_dma.c new file mode 100644 index 00000000..295932e6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_dma.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_ramht.h" + +void +nouveau_dma_init(struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_bo *pushbuf = chan->pushbuf_bo; + + if (dev_priv->card_type >= NV_50) { + const int ib_size = pushbuf->bo.mem.size / 2; + + chan->dma.ib_base = (pushbuf->bo.mem.size - ib_size) >> 2; + chan->dma.ib_max = (ib_size / 8) - 1; + chan->dma.ib_put = 0; + chan->dma.ib_free = chan->dma.ib_max - chan->dma.ib_put; + + chan->dma.max = (pushbuf->bo.mem.size - ib_size) >> 2; + } else { + chan->dma.max = (pushbuf->bo.mem.size >> 2) - 2; + } + + chan->dma.put = 0; + chan->dma.cur = chan->dma.put; + chan->dma.free = chan->dma.max - chan->dma.cur; +} + +void +OUT_RINGp(struct nouveau_channel *chan, const void *data, unsigned nr_dwords) +{ + bool is_iomem; + u32 *mem = ttm_kmap_obj_virtual(&chan->pushbuf_bo->kmap, &is_iomem); + mem = &mem[chan->dma.cur]; + if (is_iomem) + memcpy_toio((void __force __iomem *)mem, data, nr_dwords * 4); + else + memcpy(mem, data, nr_dwords * 4); + chan->dma.cur += nr_dwords; +} + +/* Fetch and adjust GPU GET pointer + * + * Returns: + * value >= 0, the adjusted GET pointer + * -EINVAL if GET pointer currently outside main push buffer + * -EBUSY if timeout exceeded + */ +static inline int +READ_GET(struct nouveau_channel *chan, uint64_t *prev_get, int *timeout) +{ + uint64_t val; + + val = nvchan_rd32(chan, chan->user_get); + if (chan->user_get_hi) + val |= (uint64_t)nvchan_rd32(chan, chan->user_get_hi) << 32; + + /* reset counter as long as GET is still advancing, this is + * to avoid misdetecting a GPU lockup if the GPU happens to + * just be processing an operation that takes a long time + */ + if (val != *prev_get) { + *prev_get = val; + *timeout = 0; + } + + if ((++*timeout & 0xff) == 0) { + DRM_UDELAY(1); + if (*timeout > 100000) + return -EBUSY; + } + + if (val < chan->pushbuf_base || + val > chan->pushbuf_base + (chan->dma.max << 2)) + return -EINVAL; + + return (val - chan->pushbuf_base) >> 2; +} + +void +nv50_dma_push(struct nouveau_channel *chan, struct nouveau_bo *bo, + int delta, int length) +{ + struct nouveau_bo *pb = chan->pushbuf_bo; + struct nouveau_vma *vma; + int ip = (chan->dma.ib_put * 2) + chan->dma.ib_base; + u64 offset; + + vma = nouveau_bo_vma_find(bo, chan->vm); + BUG_ON(!vma); + offset = vma->offset + delta; + + BUG_ON(chan->dma.ib_free < 1); + nouveau_bo_wr32(pb, ip++, lower_32_bits(offset)); + nouveau_bo_wr32(pb, ip++, upper_32_bits(offset) | length << 8); + + chan->dma.ib_put = (chan->dma.ib_put + 1) & chan->dma.ib_max; + + DRM_MEMORYBARRIER(); + /* Flush writes. */ + nouveau_bo_rd32(pb, 0); + + nvchan_wr32(chan, 0x8c, chan->dma.ib_put); + chan->dma.ib_free--; +} + +static int +nv50_dma_push_wait(struct nouveau_channel *chan, int count) +{ + uint32_t cnt = 0, prev_get = 0; + + while (chan->dma.ib_free < count) { + uint32_t get = nvchan_rd32(chan, 0x88); + if (get != prev_get) { + prev_get = get; + cnt = 0; + } + + if ((++cnt & 0xff) == 0) { + DRM_UDELAY(1); + if (cnt > 100000) + return -EBUSY; + } + + chan->dma.ib_free = get - chan->dma.ib_put; + if (chan->dma.ib_free <= 0) + chan->dma.ib_free += chan->dma.ib_max; + } + + return 0; +} + +static int +nv50_dma_wait(struct nouveau_channel *chan, int slots, int count) +{ + uint64_t prev_get = 0; + int ret, cnt = 0; + + ret = nv50_dma_push_wait(chan, slots + 1); + if (unlikely(ret)) + return ret; + + while (chan->dma.free < count) { + int get = READ_GET(chan, &prev_get, &cnt); + if (unlikely(get < 0)) { + if (get == -EINVAL) + continue; + + return get; + } + + if (get <= chan->dma.cur) { + chan->dma.free = chan->dma.max - chan->dma.cur; + if (chan->dma.free >= count) + break; + + FIRE_RING(chan); + do { + get = READ_GET(chan, &prev_get, &cnt); + if (unlikely(get < 0)) { + if (get == -EINVAL) + continue; + return get; + } + } while (get == 0); + chan->dma.cur = 0; + chan->dma.put = 0; + } + + chan->dma.free = get - chan->dma.cur - 1; + } + + return 0; +} + +int +nouveau_dma_wait(struct nouveau_channel *chan, int slots, int size) +{ + uint64_t prev_get = 0; + int cnt = 0, get; + + if (chan->dma.ib_max) + return nv50_dma_wait(chan, slots, size); + + while (chan->dma.free < size) { + get = READ_GET(chan, &prev_get, &cnt); + if (unlikely(get == -EBUSY)) + return -EBUSY; + + /* loop until we have a usable GET pointer. the value + * we read from the GPU may be outside the main ring if + * PFIFO is processing a buffer called from the main ring, + * discard these values until something sensible is seen. + * + * the other case we discard GET is while the GPU is fetching + * from the SKIPS area, so the code below doesn't have to deal + * with some fun corner cases. + */ + if (unlikely(get == -EINVAL) || get < NOUVEAU_DMA_SKIPS) + continue; + + if (get <= chan->dma.cur) { + /* engine is fetching behind us, or is completely + * idle (GET == PUT) so we have free space up until + * the end of the push buffer + * + * we can only hit that path once per call due to + * looping back to the beginning of the push buffer, + * we'll hit the fetching-ahead-of-us path from that + * point on. + * + * the *one* exception to that rule is if we read + * GET==PUT, in which case the below conditional will + * always succeed and break us out of the wait loop. + */ + chan->dma.free = chan->dma.max - chan->dma.cur; + if (chan->dma.free >= size) + break; + + /* not enough space left at the end of the push buffer, + * instruct the GPU to jump back to the start right + * after processing the currently pending commands. + */ + OUT_RING(chan, chan->pushbuf_base | 0x20000000); + + /* wait for GET to depart from the skips area. + * prevents writing GET==PUT and causing a race + * condition that causes us to think the GPU is + * idle when it's not. + */ + do { + get = READ_GET(chan, &prev_get, &cnt); + if (unlikely(get == -EBUSY)) + return -EBUSY; + if (unlikely(get == -EINVAL)) + continue; + } while (get <= NOUVEAU_DMA_SKIPS); + WRITE_PUT(NOUVEAU_DMA_SKIPS); + + /* we're now submitting commands at the start of + * the push buffer. + */ + chan->dma.cur = + chan->dma.put = NOUVEAU_DMA_SKIPS; + } + + /* engine fetching ahead of us, we have space up until the + * current GET pointer. the "- 1" is to ensure there's + * space left to emit a jump back to the beginning of the + * push buffer if we require it. we can never get GET == PUT + * here, so this is safe. + */ + chan->dma.free = get - chan->dma.cur - 1; + } + + return 0; +} + diff --git a/drivers/gpu/drm/nouveau/nouveau_dma.h b/drivers/gpu/drm/nouveau/nouveau_dma.h new file mode 100644 index 00000000..23d4edf9 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_dma.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_DMA_H__ +#define __NOUVEAU_DMA_H__ + +#ifndef NOUVEAU_DMA_DEBUG +#define NOUVEAU_DMA_DEBUG 0 +#endif + +void nv50_dma_push(struct nouveau_channel *, struct nouveau_bo *, + int delta, int length); + +/* + * There's a hw race condition where you can't jump to your PUT offset, + * to avoid this we jump to offset + SKIPS and fill the difference with + * NOPs. + * + * xf86-video-nv configures the DMA fetch size to 32 bytes, and uses + * a SKIPS value of 8. Lets assume that the race condition is to do + * with writing into the fetch area, we configure a fetch size of 128 + * bytes so we need a larger SKIPS value. + */ +#define NOUVEAU_DMA_SKIPS (128 / 4) + +/* Hardcoded object assignments to subchannels (subchannel id). */ +enum { + NvSubM2MF = 0, + NvSubSw = 1, + NvSub2D = 2, + NvSubCtxSurf2D = 2, + NvSubGdiRect = 3, + NvSubImageBlit = 4 +}; + +/* Object handles. */ +enum { + NvM2MF = 0x80000001, + NvDmaFB = 0x80000002, + NvDmaTT = 0x80000003, + NvNotify0 = 0x80000006, + Nv2D = 0x80000007, + NvCtxSurf2D = 0x80000008, + NvRop = 0x80000009, + NvImagePatt = 0x8000000a, + NvClipRect = 0x8000000b, + NvGdiRect = 0x8000000c, + NvImageBlit = 0x8000000d, + NvSw = 0x8000000e, + NvSema = 0x8000000f, + NvEvoSema0 = 0x80000010, + NvEvoSema1 = 0x80000011, + + /* G80+ display objects */ + NvEvoVRAM = 0x01000000, + NvEvoFB16 = 0x01000001, + NvEvoFB32 = 0x01000002, + NvEvoVRAM_LP = 0x01000003, + NvEvoSync = 0xcafe0000 +}; + +#define NV_MEMORY_TO_MEMORY_FORMAT 0x00000039 +#define NV_MEMORY_TO_MEMORY_FORMAT_NAME 0x00000000 +#define NV_MEMORY_TO_MEMORY_FORMAT_SET_REF 0x00000050 +#define NV_MEMORY_TO_MEMORY_FORMAT_NOP 0x00000100 +#define NV_MEMORY_TO_MEMORY_FORMAT_NOTIFY 0x00000104 +#define NV_MEMORY_TO_MEMORY_FORMAT_NOTIFY_STYLE_WRITE 0x00000000 +#define NV_MEMORY_TO_MEMORY_FORMAT_NOTIFY_STYLE_WRITE_LE_AWAKEN 0x00000001 +#define NV_MEMORY_TO_MEMORY_FORMAT_DMA_NOTIFY 0x00000180 +#define NV_MEMORY_TO_MEMORY_FORMAT_DMA_SOURCE 0x00000184 +#define NV_MEMORY_TO_MEMORY_FORMAT_OFFSET_IN 0x0000030c + +#define NV50_MEMORY_TO_MEMORY_FORMAT 0x00005039 +#define NV50_MEMORY_TO_MEMORY_FORMAT_UNK200 0x00000200 +#define NV50_MEMORY_TO_MEMORY_FORMAT_UNK21C 0x0000021c +#define NV50_MEMORY_TO_MEMORY_FORMAT_OFFSET_IN_HIGH 0x00000238 +#define NV50_MEMORY_TO_MEMORY_FORMAT_OFFSET_OUT_HIGH 0x0000023c + +static __must_check inline int +RING_SPACE(struct nouveau_channel *chan, int size) +{ + int ret; + + ret = nouveau_dma_wait(chan, 1, size); + if (ret) + return ret; + + chan->dma.free -= size; + return 0; +} + +static inline void +OUT_RING(struct nouveau_channel *chan, int data) +{ + if (NOUVEAU_DMA_DEBUG) { + NV_INFO(chan->dev, "Ch%d/0x%08x: 0x%08x\n", + chan->id, chan->dma.cur << 2, data); + } + + nouveau_bo_wr32(chan->pushbuf_bo, chan->dma.cur++, data); +} + +extern void +OUT_RINGp(struct nouveau_channel *chan, const void *data, unsigned nr_dwords); + +static inline void +BEGIN_NVC0(struct nouveau_channel *chan, int op, int subc, int mthd, int size) +{ + OUT_RING(chan, (op << 28) | (size << 16) | (subc << 13) | (mthd >> 2)); +} + +static inline void +BEGIN_RING(struct nouveau_channel *chan, int subc, int mthd, int size) +{ + OUT_RING(chan, (subc << 13) | (size << 18) | mthd); +} + +#define WRITE_PUT(val) do { \ + DRM_MEMORYBARRIER(); \ + nouveau_bo_rd32(chan->pushbuf_bo, 0); \ + nvchan_wr32(chan, chan->user_put, ((val) << 2) + chan->pushbuf_base); \ +} while (0) + +static inline void +FIRE_RING(struct nouveau_channel *chan) +{ + if (NOUVEAU_DMA_DEBUG) { + NV_INFO(chan->dev, "Ch%d/0x%08x: PUSH!\n", + chan->id, chan->dma.cur << 2); + } + + if (chan->dma.cur == chan->dma.put) + return; + chan->accel_done = true; + + if (chan->dma.ib_max) { + nv50_dma_push(chan, chan->pushbuf_bo, chan->dma.put << 2, + (chan->dma.cur - chan->dma.put) << 2); + } else { + WRITE_PUT(chan->dma.cur); + } + + chan->dma.put = chan->dma.cur; +} + +static inline void +WIND_RING(struct nouveau_channel *chan) +{ + chan->dma.cur = chan->dma.put; +} + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c new file mode 100644 index 00000000..d996134b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -0,0 +1,603 @@ +/* + * Copyright 2009 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_i2c.h" +#include "nouveau_connector.h" +#include "nouveau_encoder.h" +#include "nouveau_crtc.h" +#include "nouveau_gpio.h" + +/****************************************************************************** + * aux channel util functions + *****************************************************************************/ +#define AUX_DBG(fmt, args...) do { \ + if (nouveau_reg_debug & NOUVEAU_REG_DEBUG_AUXCH) { \ + NV_PRINTK(KERN_DEBUG, dev, "AUXCH(%d): " fmt, ch, ##args); \ + } \ +} while (0) +#define AUX_ERR(fmt, args...) NV_ERROR(dev, "AUXCH(%d): " fmt, ch, ##args) + +static void +auxch_fini(struct drm_device *dev, int ch) +{ + nv_mask(dev, 0x00e4e4 + (ch * 0x50), 0x00310000, 0x00000000); +} + +static int +auxch_init(struct drm_device *dev, int ch) +{ + const u32 unksel = 1; /* nfi which to use, or if it matters.. */ + const u32 ureq = unksel ? 0x00100000 : 0x00200000; + const u32 urep = unksel ? 0x01000000 : 0x02000000; + u32 ctrl, timeout; + + /* wait up to 1ms for any previous transaction to be done... */ + timeout = 1000; + do { + ctrl = nv_rd32(dev, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("begin idle timeout 0x%08x", ctrl); + return -EBUSY; + } + } while (ctrl & 0x03010000); + + /* set some magic, and wait up to 1ms for it to appear */ + nv_mask(dev, 0x00e4e4 + (ch * 0x50), 0x00300000, ureq); + timeout = 1000; + do { + ctrl = nv_rd32(dev, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("magic wait 0x%08x\n", ctrl); + auxch_fini(dev, ch); + return -EBUSY; + } + } while ((ctrl & 0x03000000) != urep); + + return 0; +} + +static int +auxch_tx(struct drm_device *dev, int ch, u8 type, u32 addr, u8 *data, u8 size) +{ + u32 ctrl, stat, timeout, retries; + u32 xbuf[4] = {}; + int ret, i; + + AUX_DBG("%d: 0x%08x %d\n", type, addr, size); + + ret = auxch_init(dev, ch); + if (ret) + goto out; + + stat = nv_rd32(dev, 0x00e4e8 + (ch * 0x50)); + if (!(stat & 0x10000000)) { + AUX_DBG("sink not detected\n"); + ret = -ENXIO; + goto out; + } + + if (!(type & 1)) { + memcpy(xbuf, data, size); + for (i = 0; i < 16; i += 4) { + AUX_DBG("wr 0x%08x\n", xbuf[i / 4]); + nv_wr32(dev, 0x00e4c0 + (ch * 0x50) + i, xbuf[i / 4]); + } + } + + ctrl = nv_rd32(dev, 0x00e4e4 + (ch * 0x50)); + ctrl &= ~0x0001f0ff; + ctrl |= type << 12; + ctrl |= size - 1; + nv_wr32(dev, 0x00e4e0 + (ch * 0x50), addr); + + /* retry transaction a number of times on failure... */ + ret = -EREMOTEIO; + for (retries = 0; retries < 32; retries++) { + /* reset, and delay a while if this is a retry */ + nv_wr32(dev, 0x00e4e4 + (ch * 0x50), 0x80000000 | ctrl); + nv_wr32(dev, 0x00e4e4 + (ch * 0x50), 0x00000000 | ctrl); + if (retries) + udelay(400); + + /* transaction request, wait up to 1ms for it to complete */ + nv_wr32(dev, 0x00e4e4 + (ch * 0x50), 0x00010000 | ctrl); + + timeout = 1000; + do { + ctrl = nv_rd32(dev, 0x00e4e4 + (ch * 0x50)); + udelay(1); + if (!timeout--) { + AUX_ERR("tx req timeout 0x%08x\n", ctrl); + goto out; + } + } while (ctrl & 0x00010000); + + /* read status, and check if transaction completed ok */ + stat = nv_mask(dev, 0x00e4e8 + (ch * 0x50), 0, 0); + if (!(stat & 0x000f0f00)) { + ret = 0; + break; + } + + AUX_DBG("%02d 0x%08x 0x%08x\n", retries, ctrl, stat); + } + + if (type & 1) { + for (i = 0; i < 16; i += 4) { + xbuf[i / 4] = nv_rd32(dev, 0x00e4d0 + (ch * 0x50) + i); + AUX_DBG("rd 0x%08x\n", xbuf[i / 4]); + } + memcpy(data, xbuf, size); + } + +out: + auxch_fini(dev, ch); + return ret; +} + +u8 * +nouveau_dp_bios_data(struct drm_device *dev, struct dcb_entry *dcb, u8 **entry) +{ + struct bit_entry d; + u8 *table; + int i; + + if (bit_table(dev, 'd', &d)) { + NV_ERROR(dev, "BIT 'd' table not found\n"); + return NULL; + } + + if (d.version != 1) { + NV_ERROR(dev, "BIT 'd' table version %d unknown\n", d.version); + return NULL; + } + + table = ROMPTR(dev, d.data[0]); + if (!table) { + NV_ERROR(dev, "displayport table pointer invalid\n"); + return NULL; + } + + switch (table[0]) { + case 0x20: + case 0x21: + case 0x30: + case 0x40: + break; + default: + NV_ERROR(dev, "displayport table 0x%02x unknown\n", table[0]); + return NULL; + } + + for (i = 0; i < table[3]; i++) { + *entry = ROMPTR(dev, table[table[1] + (i * table[2])]); + if (*entry && bios_encoder_match(dcb, ROM32((*entry)[0]))) + return table; + } + + NV_ERROR(dev, "displayport encoder table not found\n"); + return NULL; +} + +/****************************************************************************** + * link training + *****************************************************************************/ +struct dp_state { + struct dp_train_func *func; + struct dcb_entry *dcb; + int auxch; + int crtc; + u8 *dpcd; + int link_nr; + u32 link_bw; + u8 stat[6]; + u8 conf[4]; +}; + +static void +dp_set_link_config(struct drm_device *dev, struct dp_state *dp) +{ + u8 sink[2]; + + NV_DEBUG_KMS(dev, "%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw); + + /* set desired link configuration on the source */ + dp->func->link_set(dev, dp->dcb, dp->crtc, dp->link_nr, dp->link_bw, + dp->dpcd[2] & DP_ENHANCED_FRAME_CAP); + + /* inform the sink of the new configuration */ + sink[0] = dp->link_bw / 27000; + sink[1] = dp->link_nr; + if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP) + sink[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + auxch_tx(dev, dp->auxch, 8, DP_LINK_BW_SET, sink, 2); +} + +static void +dp_set_training_pattern(struct drm_device *dev, struct dp_state *dp, u8 pattern) +{ + u8 sink_tp; + + NV_DEBUG_KMS(dev, "training pattern %d\n", pattern); + + dp->func->train_set(dev, dp->dcb, pattern); + + auxch_tx(dev, dp->auxch, 9, DP_TRAINING_PATTERN_SET, &sink_tp, 1); + sink_tp &= ~DP_TRAINING_PATTERN_MASK; + sink_tp |= pattern; + auxch_tx(dev, dp->auxch, 8, DP_TRAINING_PATTERN_SET, &sink_tp, 1); +} + +static int +dp_link_train_commit(struct drm_device *dev, struct dp_state *dp) +{ + int i; + + for (i = 0; i < dp->link_nr; i++) { + u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf; + u8 lpre = (lane & 0x0c) >> 2; + u8 lvsw = (lane & 0x03) >> 0; + + dp->conf[i] = (lpre << 3) | lvsw; + if (lvsw == DP_TRAIN_VOLTAGE_SWING_1200) + dp->conf[i] |= DP_TRAIN_MAX_SWING_REACHED; + if ((lpre << 3) == DP_TRAIN_PRE_EMPHASIS_9_5) + dp->conf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + NV_DEBUG_KMS(dev, "config lane %d %02x\n", i, dp->conf[i]); + dp->func->train_adj(dev, dp->dcb, i, lvsw, lpre); + } + + return auxch_tx(dev, dp->auxch, 8, DP_TRAINING_LANE0_SET, dp->conf, 4); +} + +static int +dp_link_train_update(struct drm_device *dev, struct dp_state *dp, u32 delay) +{ + int ret; + + udelay(delay); + + ret = auxch_tx(dev, dp->auxch, 9, DP_LANE0_1_STATUS, dp->stat, 6); + if (ret) + return ret; + + NV_DEBUG_KMS(dev, "status %02x %02x %02x %02x %02x %02x\n", + dp->stat[0], dp->stat[1], dp->stat[2], dp->stat[3], + dp->stat[4], dp->stat[5]); + return 0; +} + +static int +dp_link_train_cr(struct drm_device *dev, struct dp_state *dp) +{ + bool cr_done = false, abort = false; + int voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + int tries = 0, i; + + dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_1); + + do { + if (dp_link_train_commit(dev, dp) || + dp_link_train_update(dev, dp, 100)) + break; + + cr_done = true; + for (i = 0; i < dp->link_nr; i++) { + u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; + if (!(lane & DP_LANE_CR_DONE)) { + cr_done = false; + if (dp->conf[i] & DP_TRAIN_MAX_SWING_REACHED) + abort = true; + break; + } + } + + if ((dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK) != voltage) { + voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + tries = 0; + } + } while (!cr_done && !abort && ++tries < 5); + + return cr_done ? 0 : -1; +} + +static int +dp_link_train_eq(struct drm_device *dev, struct dp_state *dp) +{ + bool eq_done, cr_done = true; + int tries = 0, i; + + dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_2); + + do { + if (dp_link_train_update(dev, dp, 400)) + break; + + eq_done = !!(dp->stat[2] & DP_INTERLANE_ALIGN_DONE); + for (i = 0; i < dp->link_nr && eq_done; i++) { + u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf; + if (!(lane & DP_LANE_CR_DONE)) + cr_done = false; + if (!(lane & DP_LANE_CHANNEL_EQ_DONE) || + !(lane & DP_LANE_SYMBOL_LOCKED)) + eq_done = false; + } + + if (dp_link_train_commit(dev, dp)) + break; + } while (!eq_done && cr_done && ++tries <= 5); + + return eq_done ? 0 : -1; +} + +static void +dp_set_downspread(struct drm_device *dev, struct dp_state *dp, bool enable) +{ + u16 script = 0x0000; + u8 *entry, *table = nouveau_dp_bios_data(dev, dp->dcb, &entry); + if (table) { + if (table[0] >= 0x20 && table[0] <= 0x30) { + if (enable) script = ROM16(entry[12]); + else script = ROM16(entry[14]); + } else + if (table[0] == 0x40) { + if (enable) script = ROM16(entry[11]); + else script = ROM16(entry[13]); + } + } + + nouveau_bios_run_init_table(dev, script, dp->dcb, dp->crtc); +} + +static void +dp_link_train_init(struct drm_device *dev, struct dp_state *dp) +{ + u16 script = 0x0000; + u8 *entry, *table = nouveau_dp_bios_data(dev, dp->dcb, &entry); + if (table) { + if (table[0] >= 0x20 && table[0] <= 0x30) + script = ROM16(entry[6]); + else + if (table[0] == 0x40) + script = ROM16(entry[5]); + } + + nouveau_bios_run_init_table(dev, script, dp->dcb, dp->crtc); +} + +static void +dp_link_train_fini(struct drm_device *dev, struct dp_state *dp) +{ + u16 script = 0x0000; + u8 *entry, *table = nouveau_dp_bios_data(dev, dp->dcb, &entry); + if (table) { + if (table[0] >= 0x20 && table[0] <= 0x30) + script = ROM16(entry[8]); + else + if (table[0] == 0x40) + script = ROM16(entry[7]); + } + + nouveau_bios_run_init_table(dev, script, dp->dcb, dp->crtc); +} + +bool +nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate, + struct dp_train_func *func) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nouveau_connector *nv_connector = + nouveau_encoder_connector_get(nv_encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_i2c_chan *auxch; + const u32 bw_list[] = { 270000, 162000, 0 }; + const u32 *link_bw = bw_list; + struct dp_state dp; + + auxch = nouveau_i2c_find(dev, nv_encoder->dcb->i2c_index); + if (!auxch) + return false; + + dp.func = func; + dp.dcb = nv_encoder->dcb; + dp.crtc = nv_crtc->index; + dp.auxch = auxch->drive; + dp.dpcd = nv_encoder->dp.dpcd; + + /* adjust required bandwidth for 8B/10B coding overhead */ + datarate = (datarate / 8) * 10; + + /* some sinks toggle hotplug in response to some of the actions + * we take during link training (DP_SET_POWER is one), we need + * to ignore them for the moment to avoid races. + */ + nouveau_gpio_irq(dev, 0, nv_connector->hpd, 0xff, false); + + /* enable down-spreading, if possible */ + dp_set_downspread(dev, &dp, nv_encoder->dp.dpcd[3] & 1); + + /* execute pre-train script from vbios */ + dp_link_train_init(dev, &dp); + + /* start off at highest link rate supported by encoder and display */ + while (*link_bw > nv_encoder->dp.link_bw) + link_bw++; + + while (link_bw[0]) { + /* find minimum required lane count at this link rate */ + dp.link_nr = nv_encoder->dp.link_nr; + while ((dp.link_nr >> 1) * link_bw[0] > datarate) + dp.link_nr >>= 1; + + /* drop link rate to minimum with this lane count */ + while ((link_bw[1] * dp.link_nr) > datarate) + link_bw++; + dp.link_bw = link_bw[0]; + + /* program selected link configuration */ + dp_set_link_config(dev, &dp); + + /* attempt to train the link at this configuration */ + memset(dp.stat, 0x00, sizeof(dp.stat)); + if (!dp_link_train_cr(dev, &dp) && + !dp_link_train_eq(dev, &dp)) + break; + + /* retry at lower rate */ + link_bw++; + } + + /* finish link training */ + dp_set_training_pattern(dev, &dp, DP_TRAINING_PATTERN_DISABLE); + + /* execute post-train script from vbios */ + dp_link_train_fini(dev, &dp); + + /* re-enable hotplug detect */ + nouveau_gpio_irq(dev, 0, nv_connector->hpd, 0xff, true); + return true; +} + +void +nouveau_dp_dpms(struct drm_encoder *encoder, int mode, u32 datarate, + struct dp_train_func *func) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_i2c_chan *auxch; + u8 status; + + auxch = nouveau_i2c_find(encoder->dev, nv_encoder->dcb->i2c_index); + if (!auxch) + return; + + if (mode == DRM_MODE_DPMS_ON) + status = DP_SET_POWER_D0; + else + status = DP_SET_POWER_D3; + + nouveau_dp_auxch(auxch, 8, DP_SET_POWER, &status, 1); + + if (mode == DRM_MODE_DPMS_ON) + nouveau_dp_link_train(encoder, datarate, func); +} + +bool +nouveau_dp_detect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_i2c_chan *auxch; + u8 *dpcd = nv_encoder->dp.dpcd; + int ret; + + auxch = nouveau_i2c_find(dev, nv_encoder->dcb->i2c_index); + if (!auxch) + return false; + + ret = auxch_tx(dev, auxch->drive, 9, DP_DPCD_REV, dpcd, 8); + if (ret) + return false; + + nv_encoder->dp.link_bw = 27000 * dpcd[1]; + nv_encoder->dp.link_nr = dpcd[2] & DP_MAX_LANE_COUNT_MASK; + + NV_DEBUG_KMS(dev, "display: %dx%d dpcd 0x%02x\n", + nv_encoder->dp.link_nr, nv_encoder->dp.link_bw, dpcd[0]); + NV_DEBUG_KMS(dev, "encoder: %dx%d\n", + nv_encoder->dcb->dpconf.link_nr, + nv_encoder->dcb->dpconf.link_bw); + + if (nv_encoder->dcb->dpconf.link_nr < nv_encoder->dp.link_nr) + nv_encoder->dp.link_nr = nv_encoder->dcb->dpconf.link_nr; + if (nv_encoder->dcb->dpconf.link_bw < nv_encoder->dp.link_bw) + nv_encoder->dp.link_bw = nv_encoder->dcb->dpconf.link_bw; + + NV_DEBUG_KMS(dev, "maximum: %dx%d\n", + nv_encoder->dp.link_nr, nv_encoder->dp.link_bw); + + return true; +} + +int +nouveau_dp_auxch(struct nouveau_i2c_chan *auxch, int cmd, int addr, + uint8_t *data, int data_nr) +{ + return auxch_tx(auxch->dev, auxch->drive, cmd, addr, data, data_nr); +} + +static int +nouveau_dp_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct nouveau_i2c_chan *auxch = (struct nouveau_i2c_chan *)adap; + struct i2c_msg *msg = msgs; + int ret, mcnt = num; + + while (mcnt--) { + u8 remaining = msg->len; + u8 *ptr = msg->buf; + + while (remaining) { + u8 cnt = (remaining > 16) ? 16 : remaining; + u8 cmd; + + if (msg->flags & I2C_M_RD) + cmd = AUX_I2C_READ; + else + cmd = AUX_I2C_WRITE; + + if (mcnt || remaining > 16) + cmd |= AUX_I2C_MOT; + + ret = nouveau_dp_auxch(auxch, cmd, msg->addr, ptr, cnt); + if (ret < 0) + return ret; + + ptr += cnt; + remaining -= cnt; + } + + msg++; + } + + return num; +} + +static u32 +nouveau_dp_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +const struct i2c_algorithm nouveau_dp_i2c_algo = { + .master_xfer = nouveau_dp_i2c_xfer, + .functionality = nouveau_dp_i2c_func +}; diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.c b/drivers/gpu/drm/nouveau/nouveau_drv.c new file mode 100644 index 00000000..4f2030bd --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_drv.c @@ -0,0 +1,497 @@ +/* + * Copyright 2005 Stephane Marchesin. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <linux/console.h> +#include <linux/module.h> + +#include "drmP.h" +#include "drm.h" +#include "drm_crtc_helper.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" +#include "nouveau_fb.h" +#include "nouveau_fbcon.h" +#include "nouveau_pm.h" +#include "nv50_display.h" + +#include "drm_pciids.h" + +MODULE_PARM_DESC(agpmode, "AGP mode (0 to disable AGP)"); +int nouveau_agpmode = -1; +module_param_named(agpmode, nouveau_agpmode, int, 0400); + +MODULE_PARM_DESC(modeset, "Enable kernel modesetting"); +int nouveau_modeset = -1; +module_param_named(modeset, nouveau_modeset, int, 0400); + +MODULE_PARM_DESC(vbios, "Override default VBIOS location"); +char *nouveau_vbios; +module_param_named(vbios, nouveau_vbios, charp, 0400); + +MODULE_PARM_DESC(vram_pushbuf, "Force DMA push buffers to be in VRAM"); +int nouveau_vram_pushbuf; +module_param_named(vram_pushbuf, nouveau_vram_pushbuf, int, 0400); + +MODULE_PARM_DESC(vram_notify, "Force DMA notifiers to be in VRAM"); +int nouveau_vram_notify = 0; +module_param_named(vram_notify, nouveau_vram_notify, int, 0400); + +MODULE_PARM_DESC(vram_type, "Override detected VRAM type"); +char *nouveau_vram_type; +module_param_named(vram_type, nouveau_vram_type, charp, 0400); + +MODULE_PARM_DESC(duallink, "Allow dual-link TMDS (>=GeForce 8)"); +int nouveau_duallink = 1; +module_param_named(duallink, nouveau_duallink, int, 0400); + +MODULE_PARM_DESC(uscript_lvds, "LVDS output script table ID (>=GeForce 8)"); +int nouveau_uscript_lvds = -1; +module_param_named(uscript_lvds, nouveau_uscript_lvds, int, 0400); + +MODULE_PARM_DESC(uscript_tmds, "TMDS output script table ID (>=GeForce 8)"); +int nouveau_uscript_tmds = -1; +module_param_named(uscript_tmds, nouveau_uscript_tmds, int, 0400); + +MODULE_PARM_DESC(ignorelid, "Ignore ACPI lid status"); +int nouveau_ignorelid = 0; +module_param_named(ignorelid, nouveau_ignorelid, int, 0400); + +MODULE_PARM_DESC(noaccel, "Disable all acceleration"); +int nouveau_noaccel = -1; +module_param_named(noaccel, nouveau_noaccel, int, 0400); + +MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration"); +int nouveau_nofbaccel = 0; +module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400); + +MODULE_PARM_DESC(force_post, "Force POST"); +int nouveau_force_post = 0; +module_param_named(force_post, nouveau_force_post, int, 0400); + +MODULE_PARM_DESC(override_conntype, "Ignore DCB connector type"); +int nouveau_override_conntype = 0; +module_param_named(override_conntype, nouveau_override_conntype, int, 0400); + +MODULE_PARM_DESC(tv_disable, "Disable TV-out detection"); +int nouveau_tv_disable = 0; +module_param_named(tv_disable, nouveau_tv_disable, int, 0400); + +MODULE_PARM_DESC(tv_norm, "Default TV norm.\n" + "\t\tSupported: PAL, PAL-M, PAL-N, PAL-Nc, NTSC-M, NTSC-J,\n" + "\t\t\thd480i, hd480p, hd576i, hd576p, hd720p, hd1080i.\n" + "\t\tDefault: PAL\n" + "\t\t*NOTE* Ignored for cards with external TV encoders."); +char *nouveau_tv_norm; +module_param_named(tv_norm, nouveau_tv_norm, charp, 0400); + +MODULE_PARM_DESC(reg_debug, "Register access debug bitmask:\n" + "\t\t0x1 mc, 0x2 video, 0x4 fb, 0x8 extdev,\n" + "\t\t0x10 crtc, 0x20 ramdac, 0x40 vgacrtc, 0x80 rmvio,\n" + "\t\t0x100 vgaattr, 0x200 EVO (G80+)"); +int nouveau_reg_debug; +module_param_named(reg_debug, nouveau_reg_debug, int, 0600); + +MODULE_PARM_DESC(perflvl, "Performance level (default: boot)"); +char *nouveau_perflvl; +module_param_named(perflvl, nouveau_perflvl, charp, 0400); + +MODULE_PARM_DESC(perflvl_wr, "Allow perflvl changes (warning: dangerous!)"); +int nouveau_perflvl_wr; +module_param_named(perflvl_wr, nouveau_perflvl_wr, int, 0400); + +MODULE_PARM_DESC(msi, "Enable MSI (default: off)"); +int nouveau_msi; +module_param_named(msi, nouveau_msi, int, 0400); + +MODULE_PARM_DESC(ctxfw, "Use external HUB/GPC ucode (fermi)"); +int nouveau_ctxfw; +module_param_named(ctxfw, nouveau_ctxfw, int, 0400); + +MODULE_PARM_DESC(mxmdcb, "Santise DCB table according to MXM-SIS"); +int nouveau_mxmdcb = 1; +module_param_named(mxmdcb, nouveau_mxmdcb, int, 0400); + +int nouveau_fbpercrtc; +#if 0 +module_param_named(fbpercrtc, nouveau_fbpercrtc, int, 0400); +#endif + +static struct pci_device_id pciidlist[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID), + .class = PCI_BASE_CLASS_DISPLAY << 16, + .class_mask = 0xff << 16, + }, + { + PCI_DEVICE(PCI_VENDOR_ID_NVIDIA_SGS, PCI_ANY_ID), + .class = PCI_BASE_CLASS_DISPLAY << 16, + .class_mask = 0xff << 16, + }, + {} +}; + +MODULE_DEVICE_TABLE(pci, pciidlist); + +static struct drm_driver driver; + +static int __devinit +nouveau_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + return drm_get_pci_dev(pdev, ent, &driver); +} + +static void +nouveau_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + drm_put_dev(dev); +} + +int +nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_channel *chan; + struct drm_crtc *crtc; + int ret, i, e; + + if (pm_state.event == PM_EVENT_PRETHAW) + return 0; + + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + + NV_INFO(dev, "Disabling display...\n"); + nouveau_display_fini(dev); + + NV_INFO(dev, "Disabling fbcon...\n"); + nouveau_fbcon_set_suspend(dev, 1); + + NV_INFO(dev, "Unpinning framebuffer(s)...\n"); + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_framebuffer *nouveau_fb; + + nouveau_fb = nouveau_framebuffer(crtc->fb); + if (!nouveau_fb || !nouveau_fb->nvbo) + continue; + + nouveau_bo_unpin(nouveau_fb->nvbo); + } + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + nouveau_bo_unmap(nv_crtc->cursor.nvbo); + nouveau_bo_unpin(nv_crtc->cursor.nvbo); + } + + NV_INFO(dev, "Evicting buffers...\n"); + ttm_bo_evict_mm(&dev_priv->ttm.bdev, TTM_PL_VRAM); + + NV_INFO(dev, "Idling channels...\n"); + for (i = 0; i < pfifo->channels; i++) { + chan = dev_priv->channels.ptr[i]; + + if (chan && chan->pushbuf_bo) + nouveau_channel_idle(chan); + } + + pfifo->reassign(dev, false); + pfifo->disable(dev); + pfifo->unload_context(dev); + + for (e = NVOBJ_ENGINE_NR - 1; e >= 0; e--) { + if (!dev_priv->eng[e]) + continue; + + ret = dev_priv->eng[e]->fini(dev, e, true); + if (ret) { + NV_ERROR(dev, "... engine %d failed: %d\n", e, ret); + goto out_abort; + } + } + + ret = pinstmem->suspend(dev); + if (ret) { + NV_ERROR(dev, "... failed: %d\n", ret); + goto out_abort; + } + + NV_INFO(dev, "Suspending GPU objects...\n"); + ret = nouveau_gpuobj_suspend(dev); + if (ret) { + NV_ERROR(dev, "... failed: %d\n", ret); + pinstmem->resume(dev); + goto out_abort; + } + + NV_INFO(dev, "And we're gone!\n"); + pci_save_state(pdev); + if (pm_state.event == PM_EVENT_SUSPEND) { + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + } + + return 0; + +out_abort: + NV_INFO(dev, "Re-enabling acceleration..\n"); + for (e = e + 1; e < NVOBJ_ENGINE_NR; e++) { + if (dev_priv->eng[e]) + dev_priv->eng[e]->init(dev, e); + } + pfifo->enable(dev); + pfifo->reassign(dev, true); + return ret; +} + +int +nouveau_pci_resume(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_engine *engine = &dev_priv->engine; + struct drm_crtc *crtc; + int ret, i; + + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + + NV_INFO(dev, "We're back, enabling device...\n"); + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + if (pci_enable_device(pdev)) + return -1; + pci_set_master(dev->pdev); + + /* Make sure the AGP controller is in a consistent state */ + if (dev_priv->gart_info.type == NOUVEAU_GART_AGP) + nouveau_mem_reset_agp(dev); + + /* Make the CRTCs accessible */ + engine->display.early_init(dev); + + NV_INFO(dev, "POSTing device...\n"); + ret = nouveau_run_vbios_init(dev); + if (ret) + return ret; + + if (dev_priv->gart_info.type == NOUVEAU_GART_AGP) { + ret = nouveau_mem_init_agp(dev); + if (ret) { + NV_ERROR(dev, "error reinitialising AGP: %d\n", ret); + return ret; + } + } + + NV_INFO(dev, "Restoring GPU objects...\n"); + nouveau_gpuobj_resume(dev); + + NV_INFO(dev, "Reinitialising engines...\n"); + engine->instmem.resume(dev); + engine->mc.init(dev); + engine->timer.init(dev); + engine->fb.init(dev); + for (i = 0; i < NVOBJ_ENGINE_NR; i++) { + if (dev_priv->eng[i]) + dev_priv->eng[i]->init(dev, i); + } + engine->fifo.init(dev); + + nouveau_irq_postinstall(dev); + + /* Re-write SKIPS, they'll have been lost over the suspend */ + if (nouveau_vram_pushbuf) { + struct nouveau_channel *chan; + int j; + + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + chan = dev_priv->channels.ptr[i]; + if (!chan || !chan->pushbuf_bo) + continue; + + for (j = 0; j < NOUVEAU_DMA_SKIPS; j++) + nouveau_bo_wr32(chan->pushbuf_bo, i, 0); + } + } + + nouveau_pm_resume(dev); + + NV_INFO(dev, "Restoring mode...\n"); + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_framebuffer *nouveau_fb; + + nouveau_fb = nouveau_framebuffer(crtc->fb); + if (!nouveau_fb || !nouveau_fb->nvbo) + continue; + + nouveau_bo_pin(nouveau_fb->nvbo, TTM_PL_FLAG_VRAM); + } + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + ret = nouveau_bo_pin(nv_crtc->cursor.nvbo, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(nv_crtc->cursor.nvbo); + if (ret) + NV_ERROR(dev, "Could not pin/map cursor.\n"); + } + + nouveau_fbcon_set_suspend(dev, 0); + nouveau_fbcon_zfill_all(dev); + + nouveau_display_init(dev); + + /* Force CLUT to get re-loaded during modeset */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + nv_crtc->lut.depth = 0; + } + + drm_helper_resume_force_mode(dev); + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + u32 offset = nv_crtc->cursor.nvbo->bo.offset; + + nv_crtc->cursor.set_offset(nv_crtc, offset); + nv_crtc->cursor.set_pos(nv_crtc, nv_crtc->cursor_saved_x, + nv_crtc->cursor_saved_y); + } + + return 0; +} + +static const struct file_operations nouveau_driver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, + .mmap = nouveau_ttm_mmap, + .poll = drm_poll, + .fasync = drm_fasync, + .read = drm_read, +#if defined(CONFIG_COMPAT) + .compat_ioctl = nouveau_compat_ioctl, +#endif + .llseek = noop_llseek, +}; + +static struct drm_driver driver = { + .driver_features = + DRIVER_USE_AGP | DRIVER_PCI_DMA | DRIVER_SG | + DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | DRIVER_GEM | + DRIVER_MODESET, + .load = nouveau_load, + .firstopen = nouveau_firstopen, + .lastclose = nouveau_lastclose, + .unload = nouveau_unload, + .open = nouveau_open, + .preclose = nouveau_preclose, + .postclose = nouveau_postclose, +#if defined(CONFIG_DRM_NOUVEAU_DEBUG) + .debugfs_init = nouveau_debugfs_init, + .debugfs_cleanup = nouveau_debugfs_takedown, +#endif + .irq_preinstall = nouveau_irq_preinstall, + .irq_postinstall = nouveau_irq_postinstall, + .irq_uninstall = nouveau_irq_uninstall, + .irq_handler = nouveau_irq_handler, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = nouveau_vblank_enable, + .disable_vblank = nouveau_vblank_disable, + .reclaim_buffers = drm_core_reclaim_buffers, + .ioctls = nouveau_ioctls, + .fops = &nouveau_driver_fops, + .gem_init_object = nouveau_gem_object_new, + .gem_free_object = nouveau_gem_object_del, + .gem_open_object = nouveau_gem_object_open, + .gem_close_object = nouveau_gem_object_close, + + .dumb_create = nouveau_display_dumb_create, + .dumb_map_offset = nouveau_display_dumb_map_offset, + .dumb_destroy = nouveau_display_dumb_destroy, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, +#ifdef GIT_REVISION + .date = GIT_REVISION, +#else + .date = DRIVER_DATE, +#endif + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +static struct pci_driver nouveau_pci_driver = { + .name = DRIVER_NAME, + .id_table = pciidlist, + .probe = nouveau_pci_probe, + .remove = nouveau_pci_remove, + .suspend = nouveau_pci_suspend, + .resume = nouveau_pci_resume +}; + +static int __init nouveau_init(void) +{ + driver.num_ioctls = nouveau_max_ioctl; + + if (nouveau_modeset == -1) { +#ifdef CONFIG_VGA_CONSOLE + if (vgacon_text_force()) + nouveau_modeset = 0; + else +#endif + nouveau_modeset = 1; + } + + if (!nouveau_modeset) + return 0; + + nouveau_register_dsm_handler(); + return drm_pci_init(&driver, &nouveau_pci_driver); +} + +static void __exit nouveau_exit(void) +{ + if (!nouveau_modeset) + return; + + drm_pci_exit(&driver, &nouveau_pci_driver); + nouveau_unregister_dsm_handler(); +} + +module_init(nouveau_init); +module_exit(nouveau_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h new file mode 100644 index 00000000..3aef353a --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h @@ -0,0 +1,1793 @@ +/* + * Copyright 2005 Stephane Marchesin. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __NOUVEAU_DRV_H__ +#define __NOUVEAU_DRV_H__ + +#define DRIVER_AUTHOR "Stephane Marchesin" +#define DRIVER_EMAIL "nouveau@lists.freedesktop.org" + +#define DRIVER_NAME "nouveau" +#define DRIVER_DESC "nVidia Riva/TNT/GeForce" +#define DRIVER_DATE "20120316" + +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +#define NOUVEAU_FAMILY 0x0000FFFF +#define NOUVEAU_FLAGS 0xFFFF0000 + +#include "ttm/ttm_bo_api.h" +#include "ttm/ttm_bo_driver.h" +#include "ttm/ttm_placement.h" +#include "ttm/ttm_memory.h" +#include "ttm/ttm_module.h" + +struct nouveau_fpriv { + spinlock_t lock; + struct list_head channels; + struct nouveau_vm *vm; +}; + +static inline struct nouveau_fpriv * +nouveau_fpriv(struct drm_file *file_priv) +{ + return file_priv ? file_priv->driver_priv : NULL; +} + +#define DRM_FILE_PAGE_OFFSET (0x100000000ULL >> PAGE_SHIFT) + +#include "nouveau_drm.h" +#include "nouveau_reg.h" +#include "nouveau_bios.h" +#include "nouveau_util.h" + +struct nouveau_grctx; +struct nouveau_mem; +#include "nouveau_vm.h" + +#define MAX_NUM_DCB_ENTRIES 16 + +#define NOUVEAU_MAX_CHANNEL_NR 128 +#define NOUVEAU_MAX_TILE_NR 15 + +struct nouveau_mem { + struct drm_device *dev; + + struct nouveau_vma bar_vma; + struct nouveau_vma vma[2]; + u8 page_shift; + + struct drm_mm_node *tag; + struct list_head regions; + dma_addr_t *pages; + u32 memtype; + u64 offset; + u64 size; +}; + +struct nouveau_tile_reg { + bool used; + uint32_t addr; + uint32_t limit; + uint32_t pitch; + uint32_t zcomp; + struct drm_mm_node *tag_mem; + struct nouveau_fence *fence; +}; + +struct nouveau_bo { + struct ttm_buffer_object bo; + struct ttm_placement placement; + u32 valid_domains; + u32 placements[3]; + u32 busy_placements[3]; + struct ttm_bo_kmap_obj kmap; + struct list_head head; + + /* protected by ttm_bo_reserve() */ + struct drm_file *reserved_by; + struct list_head entry; + int pbbo_index; + bool validate_mapped; + + struct list_head vma_list; + unsigned page_shift; + + uint32_t tile_mode; + uint32_t tile_flags; + struct nouveau_tile_reg *tile; + + struct drm_gem_object *gem; + int pin_refcnt; +}; + +#define nouveau_bo_tile_layout(nvbo) \ + ((nvbo)->tile_flags & NOUVEAU_GEM_TILE_LAYOUT_MASK) + +static inline struct nouveau_bo * +nouveau_bo(struct ttm_buffer_object *bo) +{ + return container_of(bo, struct nouveau_bo, bo); +} + +static inline struct nouveau_bo * +nouveau_gem_object(struct drm_gem_object *gem) +{ + return gem ? gem->driver_private : NULL; +} + +/* TODO: submit equivalent to TTM generic API upstream? */ +static inline void __iomem * +nvbo_kmap_obj_iovirtual(struct nouveau_bo *nvbo) +{ + bool is_iomem; + void __iomem *ioptr = (void __force __iomem *)ttm_kmap_obj_virtual( + &nvbo->kmap, &is_iomem); + WARN_ON_ONCE(ioptr && !is_iomem); + return ioptr; +} + +enum nouveau_flags { + NV_NFORCE = 0x10000000, + NV_NFORCE2 = 0x20000000 +}; + +#define NVOBJ_ENGINE_SW 0 +#define NVOBJ_ENGINE_GR 1 +#define NVOBJ_ENGINE_CRYPT 2 +#define NVOBJ_ENGINE_COPY0 3 +#define NVOBJ_ENGINE_COPY1 4 +#define NVOBJ_ENGINE_MPEG 5 +#define NVOBJ_ENGINE_PPP NVOBJ_ENGINE_MPEG +#define NVOBJ_ENGINE_BSP 6 +#define NVOBJ_ENGINE_VP 7 +#define NVOBJ_ENGINE_DISPLAY 15 +#define NVOBJ_ENGINE_NR 16 + +#define NVOBJ_FLAG_DONT_MAP (1 << 0) +#define NVOBJ_FLAG_ZERO_ALLOC (1 << 1) +#define NVOBJ_FLAG_ZERO_FREE (1 << 2) +#define NVOBJ_FLAG_VM (1 << 3) +#define NVOBJ_FLAG_VM_USER (1 << 4) + +#define NVOBJ_CINST_GLOBAL 0xdeadbeef + +struct nouveau_gpuobj { + struct drm_device *dev; + struct kref refcount; + struct list_head list; + + void *node; + u32 *suspend; + + uint32_t flags; + + u32 size; + u32 pinst; /* PRAMIN BAR offset */ + u32 cinst; /* Channel offset */ + u64 vinst; /* VRAM address */ + u64 linst; /* VM address */ + + uint32_t engine; + uint32_t class; + + void (*dtor)(struct drm_device *, struct nouveau_gpuobj *); + void *priv; +}; + +struct nouveau_page_flip_state { + struct list_head head; + struct drm_pending_vblank_event *event; + int crtc, bpp, pitch, x, y; + uint64_t offset; +}; + +enum nouveau_channel_mutex_class { + NOUVEAU_UCHANNEL_MUTEX, + NOUVEAU_KCHANNEL_MUTEX +}; + +struct nouveau_channel { + struct drm_device *dev; + struct list_head list; + int id; + + /* references to the channel data structure */ + struct kref ref; + /* users of the hardware channel resources, the hardware + * context will be kicked off when it reaches zero. */ + atomic_t users; + struct mutex mutex; + + /* owner of this fifo */ + struct drm_file *file_priv; + /* mapping of the fifo itself */ + struct drm_local_map *map; + + /* mapping of the regs controlling the fifo */ + void __iomem *user; + uint32_t user_get; + uint32_t user_get_hi; + uint32_t user_put; + + /* Fencing */ + struct { + /* lock protects the pending list only */ + spinlock_t lock; + struct list_head pending; + uint32_t sequence; + uint32_t sequence_ack; + atomic_t last_sequence_irq; + struct nouveau_vma vma; + } fence; + + /* DMA push buffer */ + struct nouveau_gpuobj *pushbuf; + struct nouveau_bo *pushbuf_bo; + struct nouveau_vma pushbuf_vma; + uint64_t pushbuf_base; + + /* Notifier memory */ + struct nouveau_bo *notifier_bo; + struct nouveau_vma notifier_vma; + struct drm_mm notifier_heap; + + /* PFIFO context */ + struct nouveau_gpuobj *ramfc; + struct nouveau_gpuobj *cache; + void *fifo_priv; + + /* Execution engine contexts */ + void *engctx[NVOBJ_ENGINE_NR]; + + /* NV50 VM */ + struct nouveau_vm *vm; + struct nouveau_gpuobj *vm_pd; + + /* Objects */ + struct nouveau_gpuobj *ramin; /* Private instmem */ + struct drm_mm ramin_heap; /* Private PRAMIN heap */ + struct nouveau_ramht *ramht; /* Hash table */ + + /* GPU object info for stuff used in-kernel (mm_enabled) */ + uint32_t m2mf_ntfy; + uint32_t vram_handle; + uint32_t gart_handle; + bool accel_done; + + /* Push buffer state (only for drm's channel on !mm_enabled) */ + struct { + int max; + int free; + int cur; + int put; + /* access via pushbuf_bo */ + + int ib_base; + int ib_max; + int ib_free; + int ib_put; + } dma; + + uint32_t sw_subchannel[8]; + + struct nouveau_vma dispc_vma[4]; + struct { + struct nouveau_gpuobj *vblsem; + uint32_t vblsem_head; + uint32_t vblsem_offset; + uint32_t vblsem_rval; + struct list_head vbl_wait; + struct list_head flip; + } nvsw; + + struct { + bool active; + char name[32]; + struct drm_info_list info; + } debugfs; +}; + +struct nouveau_exec_engine { + void (*destroy)(struct drm_device *, int engine); + int (*init)(struct drm_device *, int engine); + int (*fini)(struct drm_device *, int engine, bool suspend); + int (*context_new)(struct nouveau_channel *, int engine); + void (*context_del)(struct nouveau_channel *, int engine); + int (*object_new)(struct nouveau_channel *, int engine, + u32 handle, u16 class); + void (*set_tile_region)(struct drm_device *dev, int i); + void (*tlb_flush)(struct drm_device *, int engine); +}; + +struct nouveau_instmem_engine { + void *priv; + + int (*init)(struct drm_device *dev); + void (*takedown)(struct drm_device *dev); + int (*suspend)(struct drm_device *dev); + void (*resume)(struct drm_device *dev); + + int (*get)(struct nouveau_gpuobj *, struct nouveau_channel *, + u32 size, u32 align); + void (*put)(struct nouveau_gpuobj *); + int (*map)(struct nouveau_gpuobj *); + void (*unmap)(struct nouveau_gpuobj *); + + void (*flush)(struct drm_device *); +}; + +struct nouveau_mc_engine { + int (*init)(struct drm_device *dev); + void (*takedown)(struct drm_device *dev); +}; + +struct nouveau_timer_engine { + int (*init)(struct drm_device *dev); + void (*takedown)(struct drm_device *dev); + uint64_t (*read)(struct drm_device *dev); +}; + +struct nouveau_fb_engine { + int num_tiles; + struct drm_mm tag_heap; + void *priv; + + int (*init)(struct drm_device *dev); + void (*takedown)(struct drm_device *dev); + + void (*init_tile_region)(struct drm_device *dev, int i, + uint32_t addr, uint32_t size, + uint32_t pitch, uint32_t flags); + void (*set_tile_region)(struct drm_device *dev, int i); + void (*free_tile_region)(struct drm_device *dev, int i); +}; + +struct nouveau_fifo_engine { + void *priv; + int channels; + + struct nouveau_gpuobj *playlist[2]; + int cur_playlist; + + int (*init)(struct drm_device *); + void (*takedown)(struct drm_device *); + + void (*disable)(struct drm_device *); + void (*enable)(struct drm_device *); + bool (*reassign)(struct drm_device *, bool enable); + bool (*cache_pull)(struct drm_device *dev, bool enable); + + int (*channel_id)(struct drm_device *); + + int (*create_context)(struct nouveau_channel *); + void (*destroy_context)(struct nouveau_channel *); + int (*load_context)(struct nouveau_channel *); + int (*unload_context)(struct drm_device *); + void (*tlb_flush)(struct drm_device *dev); +}; + +struct nouveau_display_engine { + void *priv; + int (*early_init)(struct drm_device *); + void (*late_takedown)(struct drm_device *); + int (*create)(struct drm_device *); + void (*destroy)(struct drm_device *); + int (*init)(struct drm_device *); + void (*fini)(struct drm_device *); + + struct drm_property *dithering_mode; + struct drm_property *dithering_depth; + struct drm_property *underscan_property; + struct drm_property *underscan_hborder_property; + struct drm_property *underscan_vborder_property; + /* not really hue and saturation: */ + struct drm_property *vibrant_hue_property; + struct drm_property *color_vibrance_property; +}; + +struct nouveau_gpio_engine { + spinlock_t lock; + struct list_head isr; + int (*init)(struct drm_device *); + void (*fini)(struct drm_device *); + int (*drive)(struct drm_device *, int line, int dir, int out); + int (*sense)(struct drm_device *, int line); + void (*irq_enable)(struct drm_device *, int line, bool); +}; + +struct nouveau_pm_voltage_level { + u32 voltage; /* microvolts */ + u8 vid; +}; + +struct nouveau_pm_voltage { + bool supported; + u8 version; + u8 vid_mask; + + struct nouveau_pm_voltage_level *level; + int nr_level; +}; + +/* Exclusive upper limits */ +#define NV_MEM_CL_DDR2_MAX 8 +#define NV_MEM_WR_DDR2_MAX 9 +#define NV_MEM_CL_DDR3_MAX 17 +#define NV_MEM_WR_DDR3_MAX 17 +#define NV_MEM_CL_GDDR3_MAX 16 +#define NV_MEM_WR_GDDR3_MAX 18 +#define NV_MEM_CL_GDDR5_MAX 21 +#define NV_MEM_WR_GDDR5_MAX 20 + +struct nouveau_pm_memtiming { + int id; + + u32 reg[9]; + u32 mr[4]; + + u8 tCWL; + + u8 odt; + u8 drive_strength; +}; + +struct nouveau_pm_tbl_header { + u8 version; + u8 header_len; + u8 entry_cnt; + u8 entry_len; +}; + +struct nouveau_pm_tbl_entry { + u8 tWR; + u8 tWTR; + u8 tCL; + u8 tRC; + u8 empty_4; + u8 tRFC; /* Byte 5 */ + u8 empty_6; + u8 tRAS; /* Byte 7 */ + u8 empty_8; + u8 tRP; /* Byte 9 */ + u8 tRCDRD; + u8 tRCDWR; + u8 tRRD; + u8 tUNK_13; + u8 RAM_FT1; /* 14, a bitmask of random RAM features */ + u8 empty_15; + u8 tUNK_16; + u8 empty_17; + u8 tUNK_18; + u8 tCWL; + u8 tUNK_20, tUNK_21; +}; + +struct nouveau_pm_profile; +struct nouveau_pm_profile_func { + void (*destroy)(struct nouveau_pm_profile *); + void (*init)(struct nouveau_pm_profile *); + void (*fini)(struct nouveau_pm_profile *); + struct nouveau_pm_level *(*select)(struct nouveau_pm_profile *); +}; + +struct nouveau_pm_profile { + const struct nouveau_pm_profile_func *func; + struct list_head head; + char name[8]; +}; + +#define NOUVEAU_PM_MAX_LEVEL 8 +struct nouveau_pm_level { + struct nouveau_pm_profile profile; + struct device_attribute dev_attr; + char name[32]; + int id; + + struct nouveau_pm_memtiming timing; + u32 memory; + u16 memscript; + + u32 core; + u32 shader; + u32 rop; + u32 copy; + u32 daemon; + u32 vdec; + u32 dom6; + u32 unka0; /* nva3:nvc0 */ + u32 hub01; /* nvc0- */ + u32 hub06; /* nvc0- */ + u32 hub07; /* nvc0- */ + + u32 volt_min; /* microvolts */ + u32 volt_max; + u8 fanspeed; +}; + +struct nouveau_pm_temp_sensor_constants { + u16 offset_constant; + s16 offset_mult; + s16 offset_div; + s16 slope_mult; + s16 slope_div; +}; + +struct nouveau_pm_threshold_temp { + s16 critical; + s16 down_clock; + s16 fan_boost; +}; + +struct nouveau_pm_fan { + u32 percent; + u32 min_duty; + u32 max_duty; + u32 pwm_freq; + u32 pwm_divisor; +}; + +struct nouveau_pm_engine { + struct nouveau_pm_voltage voltage; + struct nouveau_pm_level perflvl[NOUVEAU_PM_MAX_LEVEL]; + int nr_perflvl; + struct nouveau_pm_temp_sensor_constants sensor_constants; + struct nouveau_pm_threshold_temp threshold_temp; + struct nouveau_pm_fan fan; + + struct nouveau_pm_profile *profile_ac; + struct nouveau_pm_profile *profile_dc; + struct nouveau_pm_profile *profile; + struct list_head profiles; + + struct nouveau_pm_level boot; + struct nouveau_pm_level *cur; + + struct device *hwmon; + struct notifier_block acpi_nb; + + int (*clocks_get)(struct drm_device *, struct nouveau_pm_level *); + void *(*clocks_pre)(struct drm_device *, struct nouveau_pm_level *); + int (*clocks_set)(struct drm_device *, void *); + + int (*voltage_get)(struct drm_device *); + int (*voltage_set)(struct drm_device *, int voltage); + int (*pwm_get)(struct drm_device *, int line, u32*, u32*); + int (*pwm_set)(struct drm_device *, int line, u32, u32); + int (*temp_get)(struct drm_device *); +}; + +struct nouveau_vram_engine { + struct nouveau_mm mm; + + int (*init)(struct drm_device *); + void (*takedown)(struct drm_device *dev); + int (*get)(struct drm_device *, u64, u32 align, u32 size_nc, + u32 type, struct nouveau_mem **); + void (*put)(struct drm_device *, struct nouveau_mem **); + + bool (*flags_valid)(struct drm_device *, u32 tile_flags); +}; + +struct nouveau_engine { + struct nouveau_instmem_engine instmem; + struct nouveau_mc_engine mc; + struct nouveau_timer_engine timer; + struct nouveau_fb_engine fb; + struct nouveau_fifo_engine fifo; + struct nouveau_display_engine display; + struct nouveau_gpio_engine gpio; + struct nouveau_pm_engine pm; + struct nouveau_vram_engine vram; +}; + +struct nouveau_pll_vals { + union { + struct { +#ifdef __BIG_ENDIAN + uint8_t N1, M1, N2, M2; +#else + uint8_t M1, N1, M2, N2; +#endif + }; + struct { + uint16_t NM1, NM2; + } __attribute__((packed)); + }; + int log2P; + + int refclk; +}; + +enum nv04_fp_display_regs { + FP_DISPLAY_END, + FP_TOTAL, + FP_CRTC, + FP_SYNC_START, + FP_SYNC_END, + FP_VALID_START, + FP_VALID_END +}; + +struct nv04_crtc_reg { + unsigned char MiscOutReg; + uint8_t CRTC[0xa0]; + uint8_t CR58[0x10]; + uint8_t Sequencer[5]; + uint8_t Graphics[9]; + uint8_t Attribute[21]; + unsigned char DAC[768]; + + /* PCRTC regs */ + uint32_t fb_start; + uint32_t crtc_cfg; + uint32_t cursor_cfg; + uint32_t gpio_ext; + uint32_t crtc_830; + uint32_t crtc_834; + uint32_t crtc_850; + uint32_t crtc_eng_ctrl; + + /* PRAMDAC regs */ + uint32_t nv10_cursync; + struct nouveau_pll_vals pllvals; + uint32_t ramdac_gen_ctrl; + uint32_t ramdac_630; + uint32_t ramdac_634; + uint32_t tv_setup; + uint32_t tv_vtotal; + uint32_t tv_vskew; + uint32_t tv_vsync_delay; + uint32_t tv_htotal; + uint32_t tv_hskew; + uint32_t tv_hsync_delay; + uint32_t tv_hsync_delay2; + uint32_t fp_horiz_regs[7]; + uint32_t fp_vert_regs[7]; + uint32_t dither; + uint32_t fp_control; + uint32_t dither_regs[6]; + uint32_t fp_debug_0; + uint32_t fp_debug_1; + uint32_t fp_debug_2; + uint32_t fp_margin_color; + uint32_t ramdac_8c0; + uint32_t ramdac_a20; + uint32_t ramdac_a24; + uint32_t ramdac_a34; + uint32_t ctv_regs[38]; +}; + +struct nv04_output_reg { + uint32_t output; + int head; +}; + +struct nv04_mode_state { + struct nv04_crtc_reg crtc_reg[2]; + uint32_t pllsel; + uint32_t sel_clk; +}; + +enum nouveau_card_type { + NV_04 = 0x04, + NV_10 = 0x10, + NV_20 = 0x20, + NV_30 = 0x30, + NV_40 = 0x40, + NV_50 = 0x50, + NV_C0 = 0xc0, + NV_D0 = 0xd0, + NV_E0 = 0xe0, +}; + +struct drm_nouveau_private { + struct drm_device *dev; + bool noaccel; + + /* the card type, takes NV_* as values */ + enum nouveau_card_type card_type; + /* exact chipset, derived from NV_PMC_BOOT_0 */ + int chipset; + int flags; + u32 crystal; + + void __iomem *mmio; + + spinlock_t ramin_lock; + void __iomem *ramin; + u32 ramin_size; + u32 ramin_base; + bool ramin_available; + struct drm_mm ramin_heap; + struct nouveau_exec_engine *eng[NVOBJ_ENGINE_NR]; + struct list_head gpuobj_list; + struct list_head classes; + + struct nouveau_bo *vga_ram; + + /* interrupt handling */ + void (*irq_handler[32])(struct drm_device *); + bool msi_enabled; + + struct list_head vbl_waiting; + + struct { + struct drm_global_reference mem_global_ref; + struct ttm_bo_global_ref bo_global_ref; + struct ttm_bo_device bdev; + atomic_t validate_sequence; + } ttm; + + struct { + spinlock_t lock; + struct drm_mm heap; + struct nouveau_bo *bo; + } fence; + + struct { + spinlock_t lock; + struct nouveau_channel *ptr[NOUVEAU_MAX_CHANNEL_NR]; + } channels; + + struct nouveau_engine engine; + struct nouveau_channel *channel; + + /* For PFIFO and PGRAPH. */ + spinlock_t context_switch_lock; + + /* VM/PRAMIN flush, legacy PRAMIN aperture */ + spinlock_t vm_lock; + + /* RAMIN configuration, RAMFC, RAMHT and RAMRO offsets */ + struct nouveau_ramht *ramht; + struct nouveau_gpuobj *ramfc; + struct nouveau_gpuobj *ramro; + + uint32_t ramin_rsvd_vram; + + struct { + enum { + NOUVEAU_GART_NONE = 0, + NOUVEAU_GART_AGP, /* AGP */ + NOUVEAU_GART_PDMA, /* paged dma object */ + NOUVEAU_GART_HW /* on-chip gart/vm */ + } type; + uint64_t aper_base; + uint64_t aper_size; + uint64_t aper_free; + + struct ttm_backend_func *func; + + struct { + struct page *page; + dma_addr_t addr; + } dummy; + + struct nouveau_gpuobj *sg_ctxdma; + } gart_info; + + /* nv10-nv40 tiling regions */ + struct { + struct nouveau_tile_reg reg[NOUVEAU_MAX_TILE_NR]; + spinlock_t lock; + } tile; + + /* VRAM/fb configuration */ + enum { + NV_MEM_TYPE_UNKNOWN = 0, + NV_MEM_TYPE_STOLEN, + NV_MEM_TYPE_SGRAM, + NV_MEM_TYPE_SDRAM, + NV_MEM_TYPE_DDR1, + NV_MEM_TYPE_DDR2, + NV_MEM_TYPE_DDR3, + NV_MEM_TYPE_GDDR2, + NV_MEM_TYPE_GDDR3, + NV_MEM_TYPE_GDDR4, + NV_MEM_TYPE_GDDR5 + } vram_type; + uint64_t vram_size; + uint64_t vram_sys_base; + bool vram_rank_B; + + uint64_t fb_available_size; + uint64_t fb_mappable_pages; + uint64_t fb_aper_free; + int fb_mtrr; + + /* BAR control (NV50-) */ + struct nouveau_vm *bar1_vm; + struct nouveau_vm *bar3_vm; + + /* G8x/G9x virtual address space */ + struct nouveau_vm *chan_vm; + + struct nvbios vbios; + u8 *mxms; + struct list_head i2c_ports; + + struct nv04_mode_state mode_reg; + struct nv04_mode_state saved_reg; + uint32_t saved_vga_font[4][16384]; + uint32_t crtc_owner; + uint32_t dac_users[4]; + + struct backlight_device *backlight; + + struct { + struct dentry *channel_root; + } debugfs; + + struct nouveau_fbdev *nfbdev; + struct apertures_struct *apertures; +}; + +static inline struct drm_nouveau_private * +nouveau_private(struct drm_device *dev) +{ + return dev->dev_private; +} + +static inline struct drm_nouveau_private * +nouveau_bdev(struct ttm_bo_device *bd) +{ + return container_of(bd, struct drm_nouveau_private, ttm.bdev); +} + +static inline int +nouveau_bo_ref(struct nouveau_bo *ref, struct nouveau_bo **pnvbo) +{ + struct nouveau_bo *prev; + + if (!pnvbo) + return -EINVAL; + prev = *pnvbo; + + *pnvbo = ref ? nouveau_bo(ttm_bo_reference(&ref->bo)) : NULL; + if (prev) { + struct ttm_buffer_object *bo = &prev->bo; + + ttm_bo_unref(&bo); + } + + return 0; +} + +/* nouveau_drv.c */ +extern int nouveau_modeset; +extern int nouveau_agpmode; +extern int nouveau_duallink; +extern int nouveau_uscript_lvds; +extern int nouveau_uscript_tmds; +extern int nouveau_vram_pushbuf; +extern int nouveau_vram_notify; +extern char *nouveau_vram_type; +extern int nouveau_fbpercrtc; +extern int nouveau_tv_disable; +extern char *nouveau_tv_norm; +extern int nouveau_reg_debug; +extern char *nouveau_vbios; +extern int nouveau_ignorelid; +extern int nouveau_nofbaccel; +extern int nouveau_noaccel; +extern int nouveau_force_post; +extern int nouveau_override_conntype; +extern char *nouveau_perflvl; +extern int nouveau_perflvl_wr; +extern int nouveau_msi; +extern int nouveau_ctxfw; +extern int nouveau_mxmdcb; + +extern int nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state); +extern int nouveau_pci_resume(struct pci_dev *pdev); + +/* nouveau_state.c */ +extern int nouveau_open(struct drm_device *, struct drm_file *); +extern void nouveau_preclose(struct drm_device *dev, struct drm_file *); +extern void nouveau_postclose(struct drm_device *, struct drm_file *); +extern int nouveau_load(struct drm_device *, unsigned long flags); +extern int nouveau_firstopen(struct drm_device *); +extern void nouveau_lastclose(struct drm_device *); +extern int nouveau_unload(struct drm_device *); +extern int nouveau_ioctl_getparam(struct drm_device *, void *data, + struct drm_file *); +extern int nouveau_ioctl_setparam(struct drm_device *, void *data, + struct drm_file *); +extern bool nouveau_wait_eq(struct drm_device *, uint64_t timeout, + uint32_t reg, uint32_t mask, uint32_t val); +extern bool nouveau_wait_ne(struct drm_device *, uint64_t timeout, + uint32_t reg, uint32_t mask, uint32_t val); +extern bool nouveau_wait_cb(struct drm_device *, u64 timeout, + bool (*cond)(void *), void *); +extern bool nouveau_wait_for_idle(struct drm_device *); +extern int nouveau_card_init(struct drm_device *); + +/* nouveau_mem.c */ +extern int nouveau_mem_vram_init(struct drm_device *); +extern void nouveau_mem_vram_fini(struct drm_device *); +extern int nouveau_mem_gart_init(struct drm_device *); +extern void nouveau_mem_gart_fini(struct drm_device *); +extern int nouveau_mem_init_agp(struct drm_device *); +extern int nouveau_mem_reset_agp(struct drm_device *); +extern void nouveau_mem_close(struct drm_device *); +extern bool nouveau_mem_flags_valid(struct drm_device *, u32 tile_flags); +extern int nouveau_mem_timing_calc(struct drm_device *, u32 freq, + struct nouveau_pm_memtiming *); +extern void nouveau_mem_timing_read(struct drm_device *, + struct nouveau_pm_memtiming *); +extern int nouveau_mem_vbios_type(struct drm_device *); +extern struct nouveau_tile_reg *nv10_mem_set_tiling( + struct drm_device *dev, uint32_t addr, uint32_t size, + uint32_t pitch, uint32_t flags); +extern void nv10_mem_put_tile_region(struct drm_device *dev, + struct nouveau_tile_reg *tile, + struct nouveau_fence *fence); +extern const struct ttm_mem_type_manager_func nouveau_vram_manager; +extern const struct ttm_mem_type_manager_func nouveau_gart_manager; + +/* nouveau_notifier.c */ +extern int nouveau_notifier_init_channel(struct nouveau_channel *); +extern void nouveau_notifier_takedown_channel(struct nouveau_channel *); +extern int nouveau_notifier_alloc(struct nouveau_channel *, uint32_t handle, + int cout, uint32_t start, uint32_t end, + uint32_t *offset); +extern int nouveau_notifier_offset(struct nouveau_gpuobj *, uint32_t *); +extern int nouveau_ioctl_notifier_alloc(struct drm_device *, void *data, + struct drm_file *); +extern int nouveau_ioctl_notifier_free(struct drm_device *, void *data, + struct drm_file *); + +/* nouveau_channel.c */ +extern struct drm_ioctl_desc nouveau_ioctls[]; +extern int nouveau_max_ioctl; +extern void nouveau_channel_cleanup(struct drm_device *, struct drm_file *); +extern int nouveau_channel_alloc(struct drm_device *dev, + struct nouveau_channel **chan, + struct drm_file *file_priv, + uint32_t fb_ctxdma, uint32_t tt_ctxdma); +extern struct nouveau_channel * +nouveau_channel_get_unlocked(struct nouveau_channel *); +extern struct nouveau_channel * +nouveau_channel_get(struct drm_file *, int id); +extern void nouveau_channel_put_unlocked(struct nouveau_channel **); +extern void nouveau_channel_put(struct nouveau_channel **); +extern void nouveau_channel_ref(struct nouveau_channel *chan, + struct nouveau_channel **pchan); +extern void nouveau_channel_idle(struct nouveau_channel *chan); + +/* nouveau_object.c */ +#define NVOBJ_ENGINE_ADD(d, e, p) do { \ + struct drm_nouveau_private *dev_priv = (d)->dev_private; \ + dev_priv->eng[NVOBJ_ENGINE_##e] = (p); \ +} while (0) + +#define NVOBJ_ENGINE_DEL(d, e) do { \ + struct drm_nouveau_private *dev_priv = (d)->dev_private; \ + dev_priv->eng[NVOBJ_ENGINE_##e] = NULL; \ +} while (0) + +#define NVOBJ_CLASS(d, c, e) do { \ + int ret = nouveau_gpuobj_class_new((d), (c), NVOBJ_ENGINE_##e); \ + if (ret) \ + return ret; \ +} while (0) + +#define NVOBJ_MTHD(d, c, m, e) do { \ + int ret = nouveau_gpuobj_mthd_new((d), (c), (m), (e)); \ + if (ret) \ + return ret; \ +} while (0) + +extern int nouveau_gpuobj_early_init(struct drm_device *); +extern int nouveau_gpuobj_init(struct drm_device *); +extern void nouveau_gpuobj_takedown(struct drm_device *); +extern int nouveau_gpuobj_suspend(struct drm_device *dev); +extern void nouveau_gpuobj_resume(struct drm_device *dev); +extern int nouveau_gpuobj_class_new(struct drm_device *, u32 class, u32 eng); +extern int nouveau_gpuobj_mthd_new(struct drm_device *, u32 class, u32 mthd, + int (*exec)(struct nouveau_channel *, + u32 class, u32 mthd, u32 data)); +extern int nouveau_gpuobj_mthd_call(struct nouveau_channel *, u32, u32, u32); +extern int nouveau_gpuobj_mthd_call2(struct drm_device *, int, u32, u32, u32); +extern int nouveau_gpuobj_channel_init(struct nouveau_channel *, + uint32_t vram_h, uint32_t tt_h); +extern void nouveau_gpuobj_channel_takedown(struct nouveau_channel *); +extern int nouveau_gpuobj_new(struct drm_device *, struct nouveau_channel *, + uint32_t size, int align, uint32_t flags, + struct nouveau_gpuobj **); +extern void nouveau_gpuobj_ref(struct nouveau_gpuobj *, + struct nouveau_gpuobj **); +extern int nouveau_gpuobj_new_fake(struct drm_device *, u32 pinst, u64 vinst, + u32 size, u32 flags, + struct nouveau_gpuobj **); +extern int nouveau_gpuobj_dma_new(struct nouveau_channel *, int class, + uint64_t offset, uint64_t size, int access, + int target, struct nouveau_gpuobj **); +extern int nouveau_gpuobj_gr_new(struct nouveau_channel *, u32 handle, int class); +extern int nv50_gpuobj_dma_new(struct nouveau_channel *, int class, u64 base, + u64 size, int target, int access, u32 type, + u32 comp, struct nouveau_gpuobj **pobj); +extern void nv50_gpuobj_dma_init(struct nouveau_gpuobj *, u32 offset, + int class, u64 base, u64 size, int target, + int access, u32 type, u32 comp); +extern int nouveau_ioctl_grobj_alloc(struct drm_device *, void *data, + struct drm_file *); +extern int nouveau_ioctl_gpuobj_free(struct drm_device *, void *data, + struct drm_file *); + +/* nouveau_irq.c */ +extern int nouveau_irq_init(struct drm_device *); +extern void nouveau_irq_fini(struct drm_device *); +extern irqreturn_t nouveau_irq_handler(DRM_IRQ_ARGS); +extern void nouveau_irq_register(struct drm_device *, int status_bit, + void (*)(struct drm_device *)); +extern void nouveau_irq_unregister(struct drm_device *, int status_bit); +extern void nouveau_irq_preinstall(struct drm_device *); +extern int nouveau_irq_postinstall(struct drm_device *); +extern void nouveau_irq_uninstall(struct drm_device *); + +/* nouveau_sgdma.c */ +extern int nouveau_sgdma_init(struct drm_device *); +extern void nouveau_sgdma_takedown(struct drm_device *); +extern uint32_t nouveau_sgdma_get_physical(struct drm_device *, + uint32_t offset); +extern struct ttm_tt *nouveau_sgdma_create_ttm(struct ttm_bo_device *bdev, + unsigned long size, + uint32_t page_flags, + struct page *dummy_read_page); + +/* nouveau_debugfs.c */ +#if defined(CONFIG_DRM_NOUVEAU_DEBUG) +extern int nouveau_debugfs_init(struct drm_minor *); +extern void nouveau_debugfs_takedown(struct drm_minor *); +extern int nouveau_debugfs_channel_init(struct nouveau_channel *); +extern void nouveau_debugfs_channel_fini(struct nouveau_channel *); +#else +static inline int +nouveau_debugfs_init(struct drm_minor *minor) +{ + return 0; +} + +static inline void nouveau_debugfs_takedown(struct drm_minor *minor) +{ +} + +static inline int +nouveau_debugfs_channel_init(struct nouveau_channel *chan) +{ + return 0; +} + +static inline void +nouveau_debugfs_channel_fini(struct nouveau_channel *chan) +{ +} +#endif + +/* nouveau_dma.c */ +extern void nouveau_dma_init(struct nouveau_channel *); +extern int nouveau_dma_wait(struct nouveau_channel *, int slots, int size); + +/* nouveau_acpi.c */ +#define ROM_BIOS_PAGE 4096 +#if defined(CONFIG_ACPI) +void nouveau_register_dsm_handler(void); +void nouveau_unregister_dsm_handler(void); +void nouveau_switcheroo_optimus_dsm(void); +int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len); +bool nouveau_acpi_rom_supported(struct pci_dev *pdev); +int nouveau_acpi_edid(struct drm_device *, struct drm_connector *); +#else +static inline void nouveau_register_dsm_handler(void) {} +static inline void nouveau_unregister_dsm_handler(void) {} +static inline void nouveau_switcheroo_optimus_dsm(void) {} +static inline bool nouveau_acpi_rom_supported(struct pci_dev *pdev) { return false; } +static inline int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len) { return -EINVAL; } +static inline int nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector) { return -EINVAL; } +#endif + +/* nouveau_backlight.c */ +#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT +extern int nouveau_backlight_init(struct drm_device *); +extern void nouveau_backlight_exit(struct drm_device *); +#else +static inline int nouveau_backlight_init(struct drm_device *dev) +{ + return 0; +} + +static inline void nouveau_backlight_exit(struct drm_device *dev) { } +#endif + +/* nouveau_bios.c */ +extern int nouveau_bios_init(struct drm_device *); +extern void nouveau_bios_takedown(struct drm_device *dev); +extern int nouveau_run_vbios_init(struct drm_device *); +extern void nouveau_bios_run_init_table(struct drm_device *, uint16_t table, + struct dcb_entry *, int crtc); +extern void nouveau_bios_init_exec(struct drm_device *, uint16_t table); +extern struct dcb_connector_table_entry * +nouveau_bios_connector_entry(struct drm_device *, int index); +extern u32 get_pll_register(struct drm_device *, enum pll_types); +extern int get_pll_limits(struct drm_device *, uint32_t limit_match, + struct pll_lims *); +extern int nouveau_bios_run_display_table(struct drm_device *, u16 id, int clk, + struct dcb_entry *, int crtc); +extern bool nouveau_bios_fp_mode(struct drm_device *, struct drm_display_mode *); +extern uint8_t *nouveau_bios_embedded_edid(struct drm_device *); +extern int nouveau_bios_parse_lvds_table(struct drm_device *, int pxclk, + bool *dl, bool *if_is_24bit); +extern int run_tmds_table(struct drm_device *, struct dcb_entry *, + int head, int pxclk); +extern int call_lvds_script(struct drm_device *, struct dcb_entry *, int head, + enum LVDS_script, int pxclk); +bool bios_encoder_match(struct dcb_entry *, u32 hash); + +/* nouveau_mxm.c */ +int nouveau_mxm_init(struct drm_device *dev); +void nouveau_mxm_fini(struct drm_device *dev); + +/* nouveau_ttm.c */ +int nouveau_ttm_global_init(struct drm_nouveau_private *); +void nouveau_ttm_global_release(struct drm_nouveau_private *); +int nouveau_ttm_mmap(struct file *, struct vm_area_struct *); + +/* nouveau_hdmi.c */ +void nouveau_hdmi_mode_set(struct drm_encoder *, struct drm_display_mode *); + +/* nv04_fb.c */ +extern int nv04_fb_vram_init(struct drm_device *); +extern int nv04_fb_init(struct drm_device *); +extern void nv04_fb_takedown(struct drm_device *); + +/* nv10_fb.c */ +extern int nv10_fb_vram_init(struct drm_device *dev); +extern int nv1a_fb_vram_init(struct drm_device *dev); +extern int nv10_fb_init(struct drm_device *); +extern void nv10_fb_takedown(struct drm_device *); +extern void nv10_fb_init_tile_region(struct drm_device *dev, int i, + uint32_t addr, uint32_t size, + uint32_t pitch, uint32_t flags); +extern void nv10_fb_set_tile_region(struct drm_device *dev, int i); +extern void nv10_fb_free_tile_region(struct drm_device *dev, int i); + +/* nv20_fb.c */ +extern int nv20_fb_vram_init(struct drm_device *dev); +extern int nv20_fb_init(struct drm_device *); +extern void nv20_fb_takedown(struct drm_device *); +extern void nv20_fb_init_tile_region(struct drm_device *dev, int i, + uint32_t addr, uint32_t size, + uint32_t pitch, uint32_t flags); +extern void nv20_fb_set_tile_region(struct drm_device *dev, int i); +extern void nv20_fb_free_tile_region(struct drm_device *dev, int i); + +/* nv30_fb.c */ +extern int nv30_fb_init(struct drm_device *); +extern void nv30_fb_takedown(struct drm_device *); +extern void nv30_fb_init_tile_region(struct drm_device *dev, int i, + uint32_t addr, uint32_t size, + uint32_t pitch, uint32_t flags); +extern void nv30_fb_free_tile_region(struct drm_device *dev, int i); + +/* nv40_fb.c */ +extern int nv40_fb_vram_init(struct drm_device *dev); +extern int nv40_fb_init(struct drm_device *); +extern void nv40_fb_takedown(struct drm_device *); +extern void nv40_fb_set_tile_region(struct drm_device *dev, int i); + +/* nv50_fb.c */ +extern int nv50_fb_init(struct drm_device *); +extern void nv50_fb_takedown(struct drm_device *); +extern void nv50_fb_vm_trap(struct drm_device *, int display); + +/* nvc0_fb.c */ +extern int nvc0_fb_init(struct drm_device *); +extern void nvc0_fb_takedown(struct drm_device *); + +/* nv04_fifo.c */ +extern int nv04_fifo_init(struct drm_device *); +extern void nv04_fifo_fini(struct drm_device *); +extern void nv04_fifo_disable(struct drm_device *); +extern void nv04_fifo_enable(struct drm_device *); +extern bool nv04_fifo_reassign(struct drm_device *, bool); +extern bool nv04_fifo_cache_pull(struct drm_device *, bool); +extern int nv04_fifo_channel_id(struct drm_device *); +extern int nv04_fifo_create_context(struct nouveau_channel *); +extern void nv04_fifo_destroy_context(struct nouveau_channel *); +extern int nv04_fifo_load_context(struct nouveau_channel *); +extern int nv04_fifo_unload_context(struct drm_device *); +extern void nv04_fifo_isr(struct drm_device *); + +/* nv10_fifo.c */ +extern int nv10_fifo_init(struct drm_device *); +extern int nv10_fifo_channel_id(struct drm_device *); +extern int nv10_fifo_create_context(struct nouveau_channel *); +extern int nv10_fifo_load_context(struct nouveau_channel *); +extern int nv10_fifo_unload_context(struct drm_device *); + +/* nv40_fifo.c */ +extern int nv40_fifo_init(struct drm_device *); +extern int nv40_fifo_create_context(struct nouveau_channel *); +extern int nv40_fifo_load_context(struct nouveau_channel *); +extern int nv40_fifo_unload_context(struct drm_device *); + +/* nv50_fifo.c */ +extern int nv50_fifo_init(struct drm_device *); +extern void nv50_fifo_takedown(struct drm_device *); +extern int nv50_fifo_channel_id(struct drm_device *); +extern int nv50_fifo_create_context(struct nouveau_channel *); +extern void nv50_fifo_destroy_context(struct nouveau_channel *); +extern int nv50_fifo_load_context(struct nouveau_channel *); +extern int nv50_fifo_unload_context(struct drm_device *); +extern void nv50_fifo_tlb_flush(struct drm_device *dev); + +/* nvc0_fifo.c */ +extern int nvc0_fifo_init(struct drm_device *); +extern void nvc0_fifo_takedown(struct drm_device *); +extern void nvc0_fifo_disable(struct drm_device *); +extern void nvc0_fifo_enable(struct drm_device *); +extern bool nvc0_fifo_reassign(struct drm_device *, bool); +extern bool nvc0_fifo_cache_pull(struct drm_device *, bool); +extern int nvc0_fifo_channel_id(struct drm_device *); +extern int nvc0_fifo_create_context(struct nouveau_channel *); +extern void nvc0_fifo_destroy_context(struct nouveau_channel *); +extern int nvc0_fifo_load_context(struct nouveau_channel *); +extern int nvc0_fifo_unload_context(struct drm_device *); + +/* nv04_graph.c */ +extern int nv04_graph_create(struct drm_device *); +extern int nv04_graph_object_new(struct nouveau_channel *, int, u32, u16); +extern int nv04_graph_mthd_page_flip(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data); +extern struct nouveau_bitfield nv04_graph_nsource[]; + +/* nv10_graph.c */ +extern int nv10_graph_create(struct drm_device *); +extern struct nouveau_channel *nv10_graph_channel(struct drm_device *); +extern struct nouveau_bitfield nv10_graph_intr[]; +extern struct nouveau_bitfield nv10_graph_nstatus[]; + +/* nv20_graph.c */ +extern int nv20_graph_create(struct drm_device *); + +/* nv40_graph.c */ +extern int nv40_graph_create(struct drm_device *); +extern void nv40_grctx_init(struct nouveau_grctx *); + +/* nv50_graph.c */ +extern int nv50_graph_create(struct drm_device *); +extern int nv50_grctx_init(struct nouveau_grctx *); +extern struct nouveau_enum nv50_data_error_names[]; +extern int nv50_graph_isr_chid(struct drm_device *dev, u64 inst); + +/* nvc0_graph.c */ +extern int nvc0_graph_create(struct drm_device *); +extern int nvc0_graph_isr_chid(struct drm_device *dev, u64 inst); + +/* nv84_crypt.c */ +extern int nv84_crypt_create(struct drm_device *); + +/* nv98_crypt.c */ +extern int nv98_crypt_create(struct drm_device *dev); + +/* nva3_copy.c */ +extern int nva3_copy_create(struct drm_device *dev); + +/* nvc0_copy.c */ +extern int nvc0_copy_create(struct drm_device *dev, int engine); + +/* nv31_mpeg.c */ +extern int nv31_mpeg_create(struct drm_device *dev); + +/* nv50_mpeg.c */ +extern int nv50_mpeg_create(struct drm_device *dev); + +/* nv84_bsp.c */ +/* nv98_bsp.c */ +extern int nv84_bsp_create(struct drm_device *dev); + +/* nv84_vp.c */ +/* nv98_vp.c */ +extern int nv84_vp_create(struct drm_device *dev); + +/* nv98_ppp.c */ +extern int nv98_ppp_create(struct drm_device *dev); + +/* nv04_instmem.c */ +extern int nv04_instmem_init(struct drm_device *); +extern void nv04_instmem_takedown(struct drm_device *); +extern int nv04_instmem_suspend(struct drm_device *); +extern void nv04_instmem_resume(struct drm_device *); +extern int nv04_instmem_get(struct nouveau_gpuobj *, struct nouveau_channel *, + u32 size, u32 align); +extern void nv04_instmem_put(struct nouveau_gpuobj *); +extern int nv04_instmem_map(struct nouveau_gpuobj *); +extern void nv04_instmem_unmap(struct nouveau_gpuobj *); +extern void nv04_instmem_flush(struct drm_device *); + +/* nv50_instmem.c */ +extern int nv50_instmem_init(struct drm_device *); +extern void nv50_instmem_takedown(struct drm_device *); +extern int nv50_instmem_suspend(struct drm_device *); +extern void nv50_instmem_resume(struct drm_device *); +extern int nv50_instmem_get(struct nouveau_gpuobj *, struct nouveau_channel *, + u32 size, u32 align); +extern void nv50_instmem_put(struct nouveau_gpuobj *); +extern int nv50_instmem_map(struct nouveau_gpuobj *); +extern void nv50_instmem_unmap(struct nouveau_gpuobj *); +extern void nv50_instmem_flush(struct drm_device *); +extern void nv84_instmem_flush(struct drm_device *); + +/* nvc0_instmem.c */ +extern int nvc0_instmem_init(struct drm_device *); +extern void nvc0_instmem_takedown(struct drm_device *); +extern int nvc0_instmem_suspend(struct drm_device *); +extern void nvc0_instmem_resume(struct drm_device *); + +/* nv04_mc.c */ +extern int nv04_mc_init(struct drm_device *); +extern void nv04_mc_takedown(struct drm_device *); + +/* nv40_mc.c */ +extern int nv40_mc_init(struct drm_device *); +extern void nv40_mc_takedown(struct drm_device *); + +/* nv50_mc.c */ +extern int nv50_mc_init(struct drm_device *); +extern void nv50_mc_takedown(struct drm_device *); + +/* nv04_timer.c */ +extern int nv04_timer_init(struct drm_device *); +extern uint64_t nv04_timer_read(struct drm_device *); +extern void nv04_timer_takedown(struct drm_device *); + +extern long nouveau_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); + +/* nv04_dac.c */ +extern int nv04_dac_create(struct drm_connector *, struct dcb_entry *); +extern uint32_t nv17_dac_sample_load(struct drm_encoder *encoder); +extern int nv04_dac_output_offset(struct drm_encoder *encoder); +extern void nv04_dac_update_dacclk(struct drm_encoder *encoder, bool enable); +extern bool nv04_dac_in_use(struct drm_encoder *encoder); + +/* nv04_dfp.c */ +extern int nv04_dfp_create(struct drm_connector *, struct dcb_entry *); +extern int nv04_dfp_get_bound_head(struct drm_device *dev, struct dcb_entry *dcbent); +extern void nv04_dfp_bind_head(struct drm_device *dev, struct dcb_entry *dcbent, + int head, bool dl); +extern void nv04_dfp_disable(struct drm_device *dev, int head); +extern void nv04_dfp_update_fp_control(struct drm_encoder *encoder, int mode); + +/* nv04_tv.c */ +extern int nv04_tv_identify(struct drm_device *dev, int i2c_index); +extern int nv04_tv_create(struct drm_connector *, struct dcb_entry *); + +/* nv17_tv.c */ +extern int nv17_tv_create(struct drm_connector *, struct dcb_entry *); + +/* nv04_display.c */ +extern int nv04_display_early_init(struct drm_device *); +extern void nv04_display_late_takedown(struct drm_device *); +extern int nv04_display_create(struct drm_device *); +extern void nv04_display_destroy(struct drm_device *); +extern int nv04_display_init(struct drm_device *); +extern void nv04_display_fini(struct drm_device *); + +/* nvd0_display.c */ +extern int nvd0_display_create(struct drm_device *); +extern void nvd0_display_destroy(struct drm_device *); +extern int nvd0_display_init(struct drm_device *); +extern void nvd0_display_fini(struct drm_device *); +struct nouveau_bo *nvd0_display_crtc_sema(struct drm_device *, int crtc); +void nvd0_display_flip_stop(struct drm_crtc *); +int nvd0_display_flip_next(struct drm_crtc *, struct drm_framebuffer *, + struct nouveau_channel *, u32 swap_interval); + +/* nv04_crtc.c */ +extern int nv04_crtc_create(struct drm_device *, int index); + +/* nouveau_bo.c */ +extern struct ttm_bo_driver nouveau_bo_driver; +extern int nouveau_bo_new(struct drm_device *, int size, int align, + uint32_t flags, uint32_t tile_mode, + uint32_t tile_flags, struct nouveau_bo **); +extern int nouveau_bo_pin(struct nouveau_bo *, uint32_t flags); +extern int nouveau_bo_unpin(struct nouveau_bo *); +extern int nouveau_bo_map(struct nouveau_bo *); +extern void nouveau_bo_unmap(struct nouveau_bo *); +extern void nouveau_bo_placement_set(struct nouveau_bo *, uint32_t type, + uint32_t busy); +extern u16 nouveau_bo_rd16(struct nouveau_bo *nvbo, unsigned index); +extern void nouveau_bo_wr16(struct nouveau_bo *nvbo, unsigned index, u16 val); +extern u32 nouveau_bo_rd32(struct nouveau_bo *nvbo, unsigned index); +extern void nouveau_bo_wr32(struct nouveau_bo *nvbo, unsigned index, u32 val); +extern void nouveau_bo_fence(struct nouveau_bo *, struct nouveau_fence *); +extern int nouveau_bo_validate(struct nouveau_bo *, bool interruptible, + bool no_wait_reserve, bool no_wait_gpu); + +extern struct nouveau_vma * +nouveau_bo_vma_find(struct nouveau_bo *, struct nouveau_vm *); +extern int nouveau_bo_vma_add(struct nouveau_bo *, struct nouveau_vm *, + struct nouveau_vma *); +extern void nouveau_bo_vma_del(struct nouveau_bo *, struct nouveau_vma *); + +/* nouveau_fence.c */ +struct nouveau_fence; +extern int nouveau_fence_init(struct drm_device *); +extern void nouveau_fence_fini(struct drm_device *); +extern int nouveau_fence_channel_init(struct nouveau_channel *); +extern void nouveau_fence_channel_fini(struct nouveau_channel *); +extern void nouveau_fence_update(struct nouveau_channel *); +extern int nouveau_fence_new(struct nouveau_channel *, struct nouveau_fence **, + bool emit); +extern int nouveau_fence_emit(struct nouveau_fence *); +extern void nouveau_fence_work(struct nouveau_fence *fence, + void (*work)(void *priv, bool signalled), + void *priv); +struct nouveau_channel *nouveau_fence_channel(struct nouveau_fence *); + +extern bool __nouveau_fence_signalled(void *obj, void *arg); +extern int __nouveau_fence_wait(void *obj, void *arg, bool lazy, bool intr); +extern int __nouveau_fence_flush(void *obj, void *arg); +extern void __nouveau_fence_unref(void **obj); +extern void *__nouveau_fence_ref(void *obj); + +static inline bool nouveau_fence_signalled(struct nouveau_fence *obj) +{ + return __nouveau_fence_signalled(obj, NULL); +} +static inline int +nouveau_fence_wait(struct nouveau_fence *obj, bool lazy, bool intr) +{ + return __nouveau_fence_wait(obj, NULL, lazy, intr); +} +extern int nouveau_fence_sync(struct nouveau_fence *, struct nouveau_channel *); +static inline int nouveau_fence_flush(struct nouveau_fence *obj) +{ + return __nouveau_fence_flush(obj, NULL); +} +static inline void nouveau_fence_unref(struct nouveau_fence **obj) +{ + __nouveau_fence_unref((void **)obj); +} +static inline struct nouveau_fence *nouveau_fence_ref(struct nouveau_fence *obj) +{ + return __nouveau_fence_ref(obj); +} + +/* nouveau_gem.c */ +extern int nouveau_gem_new(struct drm_device *, int size, int align, + uint32_t domain, uint32_t tile_mode, + uint32_t tile_flags, struct nouveau_bo **); +extern int nouveau_gem_object_new(struct drm_gem_object *); +extern void nouveau_gem_object_del(struct drm_gem_object *); +extern int nouveau_gem_object_open(struct drm_gem_object *, struct drm_file *); +extern void nouveau_gem_object_close(struct drm_gem_object *, + struct drm_file *); +extern int nouveau_gem_ioctl_new(struct drm_device *, void *, + struct drm_file *); +extern int nouveau_gem_ioctl_pushbuf(struct drm_device *, void *, + struct drm_file *); +extern int nouveau_gem_ioctl_cpu_prep(struct drm_device *, void *, + struct drm_file *); +extern int nouveau_gem_ioctl_cpu_fini(struct drm_device *, void *, + struct drm_file *); +extern int nouveau_gem_ioctl_info(struct drm_device *, void *, + struct drm_file *); + +/* nouveau_display.c */ +int nouveau_display_create(struct drm_device *dev); +void nouveau_display_destroy(struct drm_device *dev); +int nouveau_display_init(struct drm_device *dev); +void nouveau_display_fini(struct drm_device *dev); +int nouveau_vblank_enable(struct drm_device *dev, int crtc); +void nouveau_vblank_disable(struct drm_device *dev, int crtc); +int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event); +int nouveau_finish_page_flip(struct nouveau_channel *, + struct nouveau_page_flip_state *); +int nouveau_display_dumb_create(struct drm_file *, struct drm_device *, + struct drm_mode_create_dumb *args); +int nouveau_display_dumb_map_offset(struct drm_file *, struct drm_device *, + uint32_t handle, uint64_t *offset); +int nouveau_display_dumb_destroy(struct drm_file *, struct drm_device *, + uint32_t handle); + +/* nv10_gpio.c */ +int nv10_gpio_init(struct drm_device *dev); +void nv10_gpio_fini(struct drm_device *dev); +int nv10_gpio_drive(struct drm_device *dev, int line, int dir, int out); +int nv10_gpio_sense(struct drm_device *dev, int line); +void nv10_gpio_irq_enable(struct drm_device *, int line, bool on); + +/* nv50_gpio.c */ +int nv50_gpio_init(struct drm_device *dev); +void nv50_gpio_fini(struct drm_device *dev); +int nv50_gpio_drive(struct drm_device *dev, int line, int dir, int out); +int nv50_gpio_sense(struct drm_device *dev, int line); +void nv50_gpio_irq_enable(struct drm_device *, int line, bool on); +int nvd0_gpio_drive(struct drm_device *dev, int line, int dir, int out); +int nvd0_gpio_sense(struct drm_device *dev, int line); + +/* nv50_calc.c */ +int nv50_calc_pll(struct drm_device *, struct pll_lims *, int clk, + int *N1, int *M1, int *N2, int *M2, int *P); +int nva3_calc_pll(struct drm_device *, struct pll_lims *, + int clk, int *N, int *fN, int *M, int *P); + +#ifndef ioread32_native +#ifdef __BIG_ENDIAN +#define ioread16_native ioread16be +#define iowrite16_native iowrite16be +#define ioread32_native ioread32be +#define iowrite32_native iowrite32be +#else /* def __BIG_ENDIAN */ +#define ioread16_native ioread16 +#define iowrite16_native iowrite16 +#define ioread32_native ioread32 +#define iowrite32_native iowrite32 +#endif /* def __BIG_ENDIAN else */ +#endif /* !ioread32_native */ + +/* channel control reg access */ +static inline u32 nvchan_rd32(struct nouveau_channel *chan, unsigned reg) +{ + return ioread32_native(chan->user + reg); +} + +static inline void nvchan_wr32(struct nouveau_channel *chan, + unsigned reg, u32 val) +{ + iowrite32_native(val, chan->user + reg); +} + +/* register access */ +static inline u32 nv_rd32(struct drm_device *dev, unsigned reg) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return ioread32_native(dev_priv->mmio + reg); +} + +static inline void nv_wr32(struct drm_device *dev, unsigned reg, u32 val) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + iowrite32_native(val, dev_priv->mmio + reg); +} + +static inline u32 nv_mask(struct drm_device *dev, u32 reg, u32 mask, u32 val) +{ + u32 tmp = nv_rd32(dev, reg); + nv_wr32(dev, reg, (tmp & ~mask) | val); + return tmp; +} + +static inline u8 nv_rd08(struct drm_device *dev, unsigned reg) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return ioread8(dev_priv->mmio + reg); +} + +static inline void nv_wr08(struct drm_device *dev, unsigned reg, u8 val) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + iowrite8(val, dev_priv->mmio + reg); +} + +#define nv_wait(dev, reg, mask, val) \ + nouveau_wait_eq(dev, 2000000000ULL, (reg), (mask), (val)) +#define nv_wait_ne(dev, reg, mask, val) \ + nouveau_wait_ne(dev, 2000000000ULL, (reg), (mask), (val)) +#define nv_wait_cb(dev, func, data) \ + nouveau_wait_cb(dev, 2000000000ULL, (func), (data)) + +/* PRAMIN access */ +static inline u32 nv_ri32(struct drm_device *dev, unsigned offset) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return ioread32_native(dev_priv->ramin + offset); +} + +static inline void nv_wi32(struct drm_device *dev, unsigned offset, u32 val) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + iowrite32_native(val, dev_priv->ramin + offset); +} + +/* object access */ +extern u32 nv_ro32(struct nouveau_gpuobj *, u32 offset); +extern void nv_wo32(struct nouveau_gpuobj *, u32 offset, u32 val); + +/* + * Logging + * Argument d is (struct drm_device *). + */ +#define NV_PRINTK(level, d, fmt, arg...) \ + printk(level "[" DRM_NAME "] " DRIVER_NAME " %s: " fmt, \ + pci_name(d->pdev), ##arg) +#ifndef NV_DEBUG_NOTRACE +#define NV_DEBUG(d, fmt, arg...) do { \ + if (drm_debug & DRM_UT_DRIVER) { \ + NV_PRINTK(KERN_DEBUG, d, "%s:%d - " fmt, __func__, \ + __LINE__, ##arg); \ + } \ +} while (0) +#define NV_DEBUG_KMS(d, fmt, arg...) do { \ + if (drm_debug & DRM_UT_KMS) { \ + NV_PRINTK(KERN_DEBUG, d, "%s:%d - " fmt, __func__, \ + __LINE__, ##arg); \ + } \ +} while (0) +#else +#define NV_DEBUG(d, fmt, arg...) do { \ + if (drm_debug & DRM_UT_DRIVER) \ + NV_PRINTK(KERN_DEBUG, d, fmt, ##arg); \ +} while (0) +#define NV_DEBUG_KMS(d, fmt, arg...) do { \ + if (drm_debug & DRM_UT_KMS) \ + NV_PRINTK(KERN_DEBUG, d, fmt, ##arg); \ +} while (0) +#endif +#define NV_ERROR(d, fmt, arg...) NV_PRINTK(KERN_ERR, d, fmt, ##arg) +#define NV_INFO(d, fmt, arg...) NV_PRINTK(KERN_INFO, d, fmt, ##arg) +#define NV_TRACEWARN(d, fmt, arg...) NV_PRINTK(KERN_NOTICE, d, fmt, ##arg) +#define NV_TRACE(d, fmt, arg...) NV_PRINTK(KERN_INFO, d, fmt, ##arg) +#define NV_WARN(d, fmt, arg...) NV_PRINTK(KERN_WARNING, d, fmt, ##arg) +#define NV_WARNONCE(d, fmt, arg...) do { \ + static int _warned = 0; \ + if (!_warned) { \ + NV_WARN(d, fmt, ##arg); \ + _warned = 1; \ + } \ +} while(0) + +/* nouveau_reg_debug bitmask */ +enum { + NOUVEAU_REG_DEBUG_MC = 0x1, + NOUVEAU_REG_DEBUG_VIDEO = 0x2, + NOUVEAU_REG_DEBUG_FB = 0x4, + NOUVEAU_REG_DEBUG_EXTDEV = 0x8, + NOUVEAU_REG_DEBUG_CRTC = 0x10, + NOUVEAU_REG_DEBUG_RAMDAC = 0x20, + NOUVEAU_REG_DEBUG_VGACRTC = 0x40, + NOUVEAU_REG_DEBUG_RMVIO = 0x80, + NOUVEAU_REG_DEBUG_VGAATTR = 0x100, + NOUVEAU_REG_DEBUG_EVO = 0x200, + NOUVEAU_REG_DEBUG_AUXCH = 0x400 +}; + +#define NV_REG_DEBUG(type, dev, fmt, arg...) do { \ + if (nouveau_reg_debug & NOUVEAU_REG_DEBUG_##type) \ + NV_PRINTK(KERN_DEBUG, dev, "%s: " fmt, __func__, ##arg); \ +} while (0) + +static inline bool +nv_two_heads(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + const int impl = dev->pci_device & 0x0ff0; + + if (dev_priv->card_type >= NV_10 && impl != 0x0100 && + impl != 0x0150 && impl != 0x01a0 && impl != 0x0200) + return true; + + return false; +} + +static inline bool +nv_gf4_disp_arch(struct drm_device *dev) +{ + return nv_two_heads(dev) && (dev->pci_device & 0x0ff0) != 0x0110; +} + +static inline bool +nv_two_reg_pll(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + const int impl = dev->pci_device & 0x0ff0; + + if (impl == 0x0310 || impl == 0x0340 || dev_priv->card_type >= NV_40) + return true; + return false; +} + +static inline bool +nv_match_device(struct drm_device *dev, unsigned device, + unsigned sub_vendor, unsigned sub_device) +{ + return dev->pdev->device == device && + dev->pdev->subsystem_vendor == sub_vendor && + dev->pdev->subsystem_device == sub_device; +} + +static inline void * +nv_engine(struct drm_device *dev, int engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return (void *)dev_priv->eng[engine]; +} + +/* returns 1 if device is one of the nv4x using the 0x4497 object class, + * helpful to determine a number of other hardware features + */ +static inline int +nv44_graph_class(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if ((dev_priv->chipset & 0xf0) == 0x60) + return 1; + + return !(0x0baf & (1 << (dev_priv->chipset & 0x0f))); +} + +/* memory type/access flags, do not match hardware values */ +#define NV_MEM_ACCESS_RO 1 +#define NV_MEM_ACCESS_WO 2 +#define NV_MEM_ACCESS_RW (NV_MEM_ACCESS_RO | NV_MEM_ACCESS_WO) +#define NV_MEM_ACCESS_SYS 4 +#define NV_MEM_ACCESS_VM 8 +#define NV_MEM_ACCESS_NOSNOOP 16 + +#define NV_MEM_TARGET_VRAM 0 +#define NV_MEM_TARGET_PCI 1 +#define NV_MEM_TARGET_PCI_NOSNOOP 2 +#define NV_MEM_TARGET_VM 3 +#define NV_MEM_TARGET_GART 4 + +#define NV_MEM_TYPE_VM 0x7f +#define NV_MEM_COMP_VM 0x03 + +/* FIFO methods */ +#define NV01_SUBCHAN_OBJECT 0x00000000 +#define NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH 0x00000010 +#define NV84_SUBCHAN_SEMAPHORE_ADDRESS_LOW 0x00000014 +#define NV84_SUBCHAN_SEMAPHORE_SEQUENCE 0x00000018 +#define NV84_SUBCHAN_SEMAPHORE_TRIGGER 0x0000001c +#define NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_EQUAL 0x00000001 +#define NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG 0x00000002 +#define NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_GEQUAL 0x00000004 +#define NV84_SUBCHAN_NOTIFY_INTR 0x00000020 +#define NV84_SUBCHAN_WRCACHE_FLUSH 0x00000024 +#define NV10_SUBCHAN_REF_CNT 0x00000050 +#define NVSW_SUBCHAN_PAGE_FLIP 0x00000054 +#define NV11_SUBCHAN_DMA_SEMAPHORE 0x00000060 +#define NV11_SUBCHAN_SEMAPHORE_OFFSET 0x00000064 +#define NV11_SUBCHAN_SEMAPHORE_ACQUIRE 0x00000068 +#define NV11_SUBCHAN_SEMAPHORE_RELEASE 0x0000006c +#define NV40_SUBCHAN_YIELD 0x00000080 + +/* NV_SW object class */ +#define NV_SW 0x0000506e +#define NV_SW_DMA_VBLSEM 0x0000018c +#define NV_SW_VBLSEM_OFFSET 0x00000400 +#define NV_SW_VBLSEM_RELEASE_VALUE 0x00000404 +#define NV_SW_VBLSEM_RELEASE 0x00000408 +#define NV_SW_PAGE_FLIP 0x00000500 + +#endif /* __NOUVEAU_DRV_H__ */ diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h new file mode 100644 index 00000000..3dc14a3d --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_ENCODER_H__ +#define __NOUVEAU_ENCODER_H__ + +#include "drm_encoder_slave.h" +#include "nouveau_drv.h" + +#define NV_DPMS_CLEARED 0x80 + +struct dp_train_func { + void (*link_set)(struct drm_device *, struct dcb_entry *, int crtc, + int nr, u32 bw, bool enhframe); + void (*train_set)(struct drm_device *, struct dcb_entry *, u8 pattern); + void (*train_adj)(struct drm_device *, struct dcb_entry *, + u8 lane, u8 swing, u8 preem); +}; + +struct nouveau_encoder { + struct drm_encoder_slave base; + + struct dcb_entry *dcb; + int or; + + /* different to drm_encoder.crtc, this reflects what's + * actually programmed on the hw, not the proposed crtc */ + struct drm_crtc *crtc; + + struct drm_display_mode mode; + int last_dpms; + + struct nv04_output_reg restore; + + union { + struct { + u8 dpcd[8]; + int link_nr; + int link_bw; + u32 datarate; + } dp; + }; +}; + +struct nouveau_encoder * +find_encoder(struct drm_connector *connector, int type); + +static inline struct nouveau_encoder *nouveau_encoder(struct drm_encoder *enc) +{ + struct drm_encoder_slave *slave = to_encoder_slave(enc); + + return container_of(slave, struct nouveau_encoder, base); +} + +static inline struct drm_encoder *to_drm_encoder(struct nouveau_encoder *enc) +{ + return &enc->base.base; +} + +static inline struct drm_encoder_slave_funcs * +get_slave_funcs(struct drm_encoder *enc) +{ + return to_encoder_slave(enc)->slave_funcs; +} + +/* nouveau_dp.c */ +int nouveau_dp_auxch(struct nouveau_i2c_chan *auxch, int cmd, int addr, + uint8_t *data, int data_nr); +bool nouveau_dp_detect(struct drm_encoder *); +void nouveau_dp_dpms(struct drm_encoder *, int mode, u32 datarate, + struct dp_train_func *); +u8 *nouveau_dp_bios_data(struct drm_device *, struct dcb_entry *, u8 **); + +struct nouveau_connector * +nouveau_encoder_connector_get(struct nouveau_encoder *encoder); +int nv50_sor_create(struct drm_connector *, struct dcb_entry *); +void nv50_sor_dp_calc_tu(struct drm_device *, int, int, u32, u32); +int nv50_dac_create(struct drm_connector *, struct dcb_entry *); + + +#endif /* __NOUVEAU_ENCODER_H__ */ diff --git a/drivers/gpu/drm/nouveau/nouveau_fb.h b/drivers/gpu/drm/nouveau/nouveau_fb.h new file mode 100644 index 00000000..f3fb649f --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_fb.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_FB_H__ +#define __NOUVEAU_FB_H__ + +struct nouveau_framebuffer { + struct drm_framebuffer base; + struct nouveau_bo *nvbo; + struct nouveau_vma vma; + u32 r_dma; + u32 r_format; + u32 r_pitch; +}; + +static inline struct nouveau_framebuffer * +nouveau_framebuffer(struct drm_framebuffer *fb) +{ + return container_of(fb, struct nouveau_framebuffer, base); +} + +int nouveau_framebuffer_init(struct drm_device *dev, struct nouveau_framebuffer *nouveau_fb, + struct drm_mode_fb_cmd2 *mode_cmd, struct nouveau_bo *nvbo); +#endif /* __NOUVEAU_FB_H__ */ diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c new file mode 100644 index 00000000..6fd22111 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c @@ -0,0 +1,561 @@ +/* + * Copyright © 2007 David Airlie + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * David Airlie + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/sysrq.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/screen_info.h> +#include <linux/vga_switcheroo.h> +#include <linux/console.h> + +#include "drmP.h" +#include "drm.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" +#include "drm_fb_helper.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_crtc.h" +#include "nouveau_fb.h" +#include "nouveau_fbcon.h" +#include "nouveau_dma.h" + +static void +nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int ret; + + if (info->state != FBINFO_STATE_RUNNING) + return; + + ret = -ENODEV; + if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && + mutex_trylock(&dev_priv->channel->mutex)) { + if (dev_priv->card_type < NV_50) + ret = nv04_fbcon_fillrect(info, rect); + else + if (dev_priv->card_type < NV_C0) + ret = nv50_fbcon_fillrect(info, rect); + else + ret = nvc0_fbcon_fillrect(info, rect); + mutex_unlock(&dev_priv->channel->mutex); + } + + if (ret == 0) + return; + + if (ret != -ENODEV) + nouveau_fbcon_gpu_lockup(info); + cfb_fillrect(info, rect); +} + +static void +nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int ret; + + if (info->state != FBINFO_STATE_RUNNING) + return; + + ret = -ENODEV; + if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && + mutex_trylock(&dev_priv->channel->mutex)) { + if (dev_priv->card_type < NV_50) + ret = nv04_fbcon_copyarea(info, image); + else + if (dev_priv->card_type < NV_C0) + ret = nv50_fbcon_copyarea(info, image); + else + ret = nvc0_fbcon_copyarea(info, image); + mutex_unlock(&dev_priv->channel->mutex); + } + + if (ret == 0) + return; + + if (ret != -ENODEV) + nouveau_fbcon_gpu_lockup(info); + cfb_copyarea(info, image); +} + +static void +nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int ret; + + if (info->state != FBINFO_STATE_RUNNING) + return; + + ret = -ENODEV; + if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && + mutex_trylock(&dev_priv->channel->mutex)) { + if (dev_priv->card_type < NV_50) + ret = nv04_fbcon_imageblit(info, image); + else + if (dev_priv->card_type < NV_C0) + ret = nv50_fbcon_imageblit(info, image); + else + ret = nvc0_fbcon_imageblit(info, image); + mutex_unlock(&dev_priv->channel->mutex); + } + + if (ret == 0) + return; + + if (ret != -ENODEV) + nouveau_fbcon_gpu_lockup(info); + cfb_imageblit(info, image); +} + +static int +nouveau_fbcon_sync(struct fb_info *info) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + int ret, i; + + if (!chan || !chan->accel_done || in_interrupt() || + info->state != FBINFO_STATE_RUNNING || + info->flags & FBINFO_HWACCEL_DISABLED) + return 0; + + if (!mutex_trylock(&chan->mutex)) + return 0; + + ret = RING_SPACE(chan, 4); + if (ret) { + mutex_unlock(&chan->mutex); + nouveau_fbcon_gpu_lockup(info); + return 0; + } + + if (dev_priv->card_type >= NV_C0) { + BEGIN_NVC0(chan, 2, NvSub2D, 0x010c, 1); + OUT_RING (chan, 0); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0100, 1); + OUT_RING (chan, 0); + } else { + BEGIN_RING(chan, 0, 0x0104, 1); + OUT_RING (chan, 0); + BEGIN_RING(chan, 0, 0x0100, 1); + OUT_RING (chan, 0); + } + + nouveau_bo_wr32(chan->notifier_bo, chan->m2mf_ntfy/4 + 3, 0xffffffff); + FIRE_RING(chan); + mutex_unlock(&chan->mutex); + + ret = -EBUSY; + for (i = 0; i < 100000; i++) { + if (!nouveau_bo_rd32(chan->notifier_bo, chan->m2mf_ntfy/4 + 3)) { + ret = 0; + break; + } + DRM_UDELAY(1); + } + + if (ret) { + nouveau_fbcon_gpu_lockup(info); + return 0; + } + + chan->accel_done = false; + return 0; +} + +static struct fb_ops nouveau_fbcon_ops = { + .owner = THIS_MODULE, + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_fillrect = nouveau_fbcon_fillrect, + .fb_copyarea = nouveau_fbcon_copyarea, + .fb_imageblit = nouveau_fbcon_imageblit, + .fb_sync = nouveau_fbcon_sync, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, + .fb_debug_enter = drm_fb_helper_debug_enter, + .fb_debug_leave = drm_fb_helper_debug_leave, +}; + +static struct fb_ops nouveau_fbcon_sw_ops = { + .owner = THIS_MODULE, + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, + .fb_debug_enter = drm_fb_helper_debug_enter, + .fb_debug_leave = drm_fb_helper_debug_leave, +}; + +static void nouveau_fbcon_gamma_set(struct drm_crtc *crtc, u16 red, u16 green, + u16 blue, int regno) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + nv_crtc->lut.r[regno] = red; + nv_crtc->lut.g[regno] = green; + nv_crtc->lut.b[regno] = blue; +} + +static void nouveau_fbcon_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green, + u16 *blue, int regno) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + *red = nv_crtc->lut.r[regno]; + *green = nv_crtc->lut.g[regno]; + *blue = nv_crtc->lut.b[regno]; +} + +static void +nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *nfbdev) +{ + struct fb_info *info = nfbdev->helper.fbdev; + struct fb_fillrect rect; + + /* Clear the entire fbcon. The drm will program every connector + * with it's preferred mode. If the sizes differ, one display will + * quite likely have garbage around the console. + */ + rect.dx = rect.dy = 0; + rect.width = info->var.xres_virtual; + rect.height = info->var.yres_virtual; + rect.color = 0; + rect.rop = ROP_COPY; + info->fbops->fb_fillrect(info, &rect); +} + +static int +nouveau_fbcon_create(struct nouveau_fbdev *nfbdev, + struct drm_fb_helper_surface_size *sizes) +{ + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct fb_info *info; + struct drm_framebuffer *fb; + struct nouveau_framebuffer *nouveau_fb; + struct nouveau_channel *chan; + struct nouveau_bo *nvbo; + struct drm_mode_fb_cmd2 mode_cmd; + struct pci_dev *pdev = dev->pdev; + struct device *device = &pdev->dev; + int size, ret; + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + + mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3); + mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256); + + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + size = mode_cmd.pitches[0] * mode_cmd.height; + size = roundup(size, PAGE_SIZE); + + ret = nouveau_gem_new(dev, size, 0, NOUVEAU_GEM_DOMAIN_VRAM, + 0, 0x0000, &nvbo); + if (ret) { + NV_ERROR(dev, "failed to allocate framebuffer\n"); + goto out; + } + + ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM); + if (ret) { + NV_ERROR(dev, "failed to pin fb: %d\n", ret); + nouveau_bo_ref(NULL, &nvbo); + goto out; + } + + ret = nouveau_bo_map(nvbo); + if (ret) { + NV_ERROR(dev, "failed to map fb: %d\n", ret); + nouveau_bo_unpin(nvbo); + nouveau_bo_ref(NULL, &nvbo); + goto out; + } + + chan = nouveau_nofbaccel ? NULL : dev_priv->channel; + if (chan && dev_priv->card_type >= NV_50) { + ret = nouveau_bo_vma_add(nvbo, chan->vm, &nfbdev->nouveau_fb.vma); + if (ret) { + NV_ERROR(dev, "failed to map fb into chan: %d\n", ret); + chan = NULL; + } + } + + mutex_lock(&dev->struct_mutex); + + info = framebuffer_alloc(0, device); + if (!info) { + ret = -ENOMEM; + goto out_unref; + } + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) { + ret = -ENOMEM; + goto out_unref; + } + + info->par = nfbdev; + + nouveau_framebuffer_init(dev, &nfbdev->nouveau_fb, &mode_cmd, nvbo); + + nouveau_fb = &nfbdev->nouveau_fb; + fb = &nouveau_fb->base; + + /* setup helper */ + nfbdev->helper.fb = fb; + nfbdev->helper.fbdev = info; + + strcpy(info->fix.id, "nouveaufb"); + if (nouveau_nofbaccel) + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED; + else + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT | + FBINFO_HWACCEL_IMAGEBLIT; + info->flags |= FBINFO_CAN_FORCE_OUTPUT; + info->fbops = &nouveau_fbcon_sw_ops; + info->fix.smem_start = nvbo->bo.mem.bus.base + + nvbo->bo.mem.bus.offset; + info->fix.smem_len = size; + + info->screen_base = nvbo_kmap_obj_iovirtual(nouveau_fb->nvbo); + info->screen_size = size; + + drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(info, &nfbdev->helper, sizes->fb_width, sizes->fb_height); + + /* Set aperture base/size for vesafb takeover */ + info->apertures = dev_priv->apertures; + if (!info->apertures) { + ret = -ENOMEM; + goto out_unref; + } + + /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ + + mutex_unlock(&dev->struct_mutex); + + if (dev_priv->channel && !nouveau_nofbaccel) { + ret = -ENODEV; + if (dev_priv->card_type < NV_50) + ret = nv04_fbcon_accel_init(info); + else + if (dev_priv->card_type < NV_C0) + ret = nv50_fbcon_accel_init(info); + else + ret = nvc0_fbcon_accel_init(info); + + if (ret == 0) + info->fbops = &nouveau_fbcon_ops; + } + + nouveau_fbcon_zfill(dev, nfbdev); + + /* To allow resizeing without swapping buffers */ + NV_INFO(dev, "allocated %dx%d fb: 0x%lx, bo %p\n", + nouveau_fb->base.width, + nouveau_fb->base.height, + nvbo->bo.offset, nvbo); + + vga_switcheroo_client_fb_set(dev->pdev, info); + return 0; + +out_unref: + mutex_unlock(&dev->struct_mutex); +out: + return ret; +} + +static int +nouveau_fbcon_find_or_create_single(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct nouveau_fbdev *nfbdev = (struct nouveau_fbdev *)helper; + int new_fb = 0; + int ret; + + if (!helper->fb) { + ret = nouveau_fbcon_create(nfbdev, sizes); + if (ret) + return ret; + new_fb = 1; + } + return new_fb; +} + +void +nouveau_fbcon_output_poll_changed(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + drm_fb_helper_hotplug_event(&dev_priv->nfbdev->helper); +} + +static int +nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *nfbdev) +{ + struct nouveau_framebuffer *nouveau_fb = &nfbdev->nouveau_fb; + struct fb_info *info; + + if (nfbdev->helper.fbdev) { + info = nfbdev->helper.fbdev; + unregister_framebuffer(info); + if (info->cmap.len) + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + + if (nouveau_fb->nvbo) { + nouveau_bo_unmap(nouveau_fb->nvbo); + nouveau_bo_vma_del(nouveau_fb->nvbo, &nouveau_fb->vma); + drm_gem_object_unreference_unlocked(nouveau_fb->nvbo->gem); + nouveau_fb->nvbo = NULL; + } + drm_fb_helper_fini(&nfbdev->helper); + drm_framebuffer_cleanup(&nouveau_fb->base); + return 0; +} + +void nouveau_fbcon_gpu_lockup(struct fb_info *info) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + + NV_ERROR(dev, "GPU lockup - switching to software fbcon\n"); + info->flags |= FBINFO_HWACCEL_DISABLED; +} + +static struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = { + .gamma_set = nouveau_fbcon_gamma_set, + .gamma_get = nouveau_fbcon_gamma_get, + .fb_probe = nouveau_fbcon_find_or_create_single, +}; + + +int nouveau_fbcon_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fbdev *nfbdev; + int preferred_bpp; + int ret; + + nfbdev = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL); + if (!nfbdev) + return -ENOMEM; + + nfbdev->dev = dev; + dev_priv->nfbdev = nfbdev; + nfbdev->helper.funcs = &nouveau_fbcon_helper_funcs; + + ret = drm_fb_helper_init(dev, &nfbdev->helper, + dev->mode_config.num_crtc, 4); + if (ret) { + kfree(nfbdev); + return ret; + } + + drm_fb_helper_single_add_all_connectors(&nfbdev->helper); + + if (dev_priv->vram_size <= 32 * 1024 * 1024) + preferred_bpp = 8; + else if (dev_priv->vram_size <= 64 * 1024 * 1024) + preferred_bpp = 16; + else + preferred_bpp = 32; + + drm_fb_helper_initial_config(&nfbdev->helper, preferred_bpp); + return 0; +} + +void nouveau_fbcon_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (!dev_priv->nfbdev) + return; + + nouveau_fbcon_destroy(dev, dev_priv->nfbdev); + kfree(dev_priv->nfbdev); + dev_priv->nfbdev = NULL; +} + +void nouveau_fbcon_save_disable_accel(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + dev_priv->nfbdev->saved_flags = dev_priv->nfbdev->helper.fbdev->flags; + dev_priv->nfbdev->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED; +} + +void nouveau_fbcon_restore_accel(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + dev_priv->nfbdev->helper.fbdev->flags = dev_priv->nfbdev->saved_flags; +} + +void nouveau_fbcon_set_suspend(struct drm_device *dev, int state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + console_lock(); + if (state == 0) + nouveau_fbcon_save_disable_accel(dev); + fb_set_suspend(dev_priv->nfbdev->helper.fbdev, state); + if (state == 1) + nouveau_fbcon_restore_accel(dev); + console_unlock(); +} + +void nouveau_fbcon_zfill_all(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + nouveau_fbcon_zfill(dev, dev_priv->nfbdev); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.h b/drivers/gpu/drm/nouveau/nouveau_fbcon.h new file mode 100644 index 00000000..b73c29f8 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_FBCON_H__ +#define __NOUVEAU_FBCON_H__ + +#include "drm_fb_helper.h" + +#include "nouveau_fb.h" +struct nouveau_fbdev { + struct drm_fb_helper helper; + struct nouveau_framebuffer nouveau_fb; + struct list_head fbdev_list; + struct drm_device *dev; + unsigned int saved_flags; +}; + +void nouveau_fbcon_restore(void); + +int nv04_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *region); +int nv04_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect); +int nv04_fbcon_imageblit(struct fb_info *info, const struct fb_image *image); +int nv04_fbcon_accel_init(struct fb_info *info); + +int nv50_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect); +int nv50_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *region); +int nv50_fbcon_imageblit(struct fb_info *info, const struct fb_image *image); +int nv50_fbcon_accel_init(struct fb_info *info); + +int nvc0_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect); +int nvc0_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *region); +int nvc0_fbcon_imageblit(struct fb_info *info, const struct fb_image *image); +int nvc0_fbcon_accel_init(struct fb_info *info); + +void nouveau_fbcon_gpu_lockup(struct fb_info *info); + +int nouveau_fbcon_init(struct drm_device *dev); +void nouveau_fbcon_fini(struct drm_device *dev); +void nouveau_fbcon_set_suspend(struct drm_device *dev, int state); +void nouveau_fbcon_zfill_all(struct drm_device *dev); +void nouveau_fbcon_save_disable_accel(struct drm_device *dev); +void nouveau_fbcon_restore_accel(struct drm_device *dev); + +void nouveau_fbcon_output_poll_changed(struct drm_device *dev); +#endif /* __NV50_FBCON_H__ */ + diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c new file mode 100644 index 00000000..c1dc20f6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_fence.c @@ -0,0 +1,614 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" + +#include <linux/ktime.h> +#include <linux/hrtimer.h> + +#include "nouveau_drv.h" +#include "nouveau_ramht.h" +#include "nouveau_dma.h" + +#define USE_REFCNT(dev) (nouveau_private(dev)->chipset >= 0x10) +#define USE_SEMA(dev) (nouveau_private(dev)->chipset >= 0x17) + +struct nouveau_fence { + struct nouveau_channel *channel; + struct kref refcount; + struct list_head entry; + + uint32_t sequence; + bool signalled; + + void (*work)(void *priv, bool signalled); + void *priv; +}; + +struct nouveau_semaphore { + struct kref ref; + struct drm_device *dev; + struct drm_mm_node *mem; +}; + +static inline struct nouveau_fence * +nouveau_fence(void *sync_obj) +{ + return (struct nouveau_fence *)sync_obj; +} + +static void +nouveau_fence_del(struct kref *ref) +{ + struct nouveau_fence *fence = + container_of(ref, struct nouveau_fence, refcount); + + nouveau_channel_ref(NULL, &fence->channel); + kfree(fence); +} + +void +nouveau_fence_update(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct nouveau_fence *tmp, *fence; + uint32_t sequence; + + spin_lock(&chan->fence.lock); + + /* Fetch the last sequence if the channel is still up and running */ + if (likely(!list_empty(&chan->fence.pending))) { + if (USE_REFCNT(dev)) + sequence = nvchan_rd32(chan, 0x48); + else + sequence = atomic_read(&chan->fence.last_sequence_irq); + + if (chan->fence.sequence_ack == sequence) + goto out; + chan->fence.sequence_ack = sequence; + } + + list_for_each_entry_safe(fence, tmp, &chan->fence.pending, entry) { + if (fence->sequence > chan->fence.sequence_ack) + break; + + fence->signalled = true; + list_del(&fence->entry); + if (fence->work) + fence->work(fence->priv, true); + + kref_put(&fence->refcount, nouveau_fence_del); + } + +out: + spin_unlock(&chan->fence.lock); +} + +int +nouveau_fence_new(struct nouveau_channel *chan, struct nouveau_fence **pfence, + bool emit) +{ + struct nouveau_fence *fence; + int ret = 0; + + fence = kzalloc(sizeof(*fence), GFP_KERNEL); + if (!fence) + return -ENOMEM; + kref_init(&fence->refcount); + nouveau_channel_ref(chan, &fence->channel); + + if (emit) + ret = nouveau_fence_emit(fence); + + if (ret) + nouveau_fence_unref(&fence); + *pfence = fence; + return ret; +} + +struct nouveau_channel * +nouveau_fence_channel(struct nouveau_fence *fence) +{ + return fence ? nouveau_channel_get_unlocked(fence->channel) : NULL; +} + +int +nouveau_fence_emit(struct nouveau_fence *fence) +{ + struct nouveau_channel *chan = fence->channel; + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int ret; + + ret = RING_SPACE(chan, 2); + if (ret) + return ret; + + if (unlikely(chan->fence.sequence == chan->fence.sequence_ack - 1)) { + nouveau_fence_update(chan); + + BUG_ON(chan->fence.sequence == + chan->fence.sequence_ack - 1); + } + + fence->sequence = ++chan->fence.sequence; + + kref_get(&fence->refcount); + spin_lock(&chan->fence.lock); + list_add_tail(&fence->entry, &chan->fence.pending); + spin_unlock(&chan->fence.lock); + + if (USE_REFCNT(dev)) { + if (dev_priv->card_type < NV_C0) + BEGIN_RING(chan, 0, NV10_SUBCHAN_REF_CNT, 1); + else + BEGIN_NVC0(chan, 2, 0, NV10_SUBCHAN_REF_CNT, 1); + } else { + BEGIN_RING(chan, NvSubSw, 0x0150, 1); + } + OUT_RING (chan, fence->sequence); + FIRE_RING(chan); + + return 0; +} + +void +nouveau_fence_work(struct nouveau_fence *fence, + void (*work)(void *priv, bool signalled), + void *priv) +{ + BUG_ON(fence->work); + + spin_lock(&fence->channel->fence.lock); + + if (fence->signalled) { + work(priv, true); + } else { + fence->work = work; + fence->priv = priv; + } + + spin_unlock(&fence->channel->fence.lock); +} + +void +__nouveau_fence_unref(void **sync_obj) +{ + struct nouveau_fence *fence = nouveau_fence(*sync_obj); + + if (fence) + kref_put(&fence->refcount, nouveau_fence_del); + *sync_obj = NULL; +} + +void * +__nouveau_fence_ref(void *sync_obj) +{ + struct nouveau_fence *fence = nouveau_fence(sync_obj); + + kref_get(&fence->refcount); + return sync_obj; +} + +bool +__nouveau_fence_signalled(void *sync_obj, void *sync_arg) +{ + struct nouveau_fence *fence = nouveau_fence(sync_obj); + struct nouveau_channel *chan = fence->channel; + + if (fence->signalled) + return true; + + nouveau_fence_update(chan); + return fence->signalled; +} + +int +__nouveau_fence_wait(void *sync_obj, void *sync_arg, bool lazy, bool intr) +{ + unsigned long timeout = jiffies + (3 * DRM_HZ); + unsigned long sleep_time = NSEC_PER_MSEC / 1000; + ktime_t t; + int ret = 0; + + while (1) { + if (__nouveau_fence_signalled(sync_obj, sync_arg)) + break; + + if (time_after_eq(jiffies, timeout)) { + ret = -EBUSY; + break; + } + + __set_current_state(intr ? TASK_INTERRUPTIBLE + : TASK_UNINTERRUPTIBLE); + if (lazy) { + t = ktime_set(0, sleep_time); + schedule_hrtimeout(&t, HRTIMER_MODE_REL); + sleep_time *= 2; + if (sleep_time > NSEC_PER_MSEC) + sleep_time = NSEC_PER_MSEC; + } + + if (intr && signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + } + + __set_current_state(TASK_RUNNING); + + return ret; +} + +static struct nouveau_semaphore * +semaphore_alloc(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_semaphore *sema; + int size = (dev_priv->chipset < 0x84) ? 4 : 16; + int ret, i; + + if (!USE_SEMA(dev)) + return NULL; + + sema = kmalloc(sizeof(*sema), GFP_KERNEL); + if (!sema) + goto fail; + + ret = drm_mm_pre_get(&dev_priv->fence.heap); + if (ret) + goto fail; + + spin_lock(&dev_priv->fence.lock); + sema->mem = drm_mm_search_free(&dev_priv->fence.heap, size, 0, 0); + if (sema->mem) + sema->mem = drm_mm_get_block_atomic(sema->mem, size, 0); + spin_unlock(&dev_priv->fence.lock); + + if (!sema->mem) + goto fail; + + kref_init(&sema->ref); + sema->dev = dev; + for (i = sema->mem->start; i < sema->mem->start + size; i += 4) + nouveau_bo_wr32(dev_priv->fence.bo, i / 4, 0); + + return sema; +fail: + kfree(sema); + return NULL; +} + +static void +semaphore_free(struct kref *ref) +{ + struct nouveau_semaphore *sema = + container_of(ref, struct nouveau_semaphore, ref); + struct drm_nouveau_private *dev_priv = sema->dev->dev_private; + + spin_lock(&dev_priv->fence.lock); + drm_mm_put_block(sema->mem); + spin_unlock(&dev_priv->fence.lock); + + kfree(sema); +} + +static void +semaphore_work(void *priv, bool signalled) +{ + struct nouveau_semaphore *sema = priv; + struct drm_nouveau_private *dev_priv = sema->dev->dev_private; + + if (unlikely(!signalled)) + nouveau_bo_wr32(dev_priv->fence.bo, sema->mem->start / 4, 1); + + kref_put(&sema->ref, semaphore_free); +} + +static int +semaphore_acquire(struct nouveau_channel *chan, struct nouveau_semaphore *sema) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_fence *fence = NULL; + u64 offset = chan->fence.vma.offset + sema->mem->start; + int ret; + + if (dev_priv->chipset < 0x84) { + ret = RING_SPACE(chan, 4); + if (ret) + return ret; + + BEGIN_RING(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 3); + OUT_RING (chan, NvSema); + OUT_RING (chan, offset); + OUT_RING (chan, 1); + } else + if (dev_priv->chipset < 0xc0) { + ret = RING_SPACE(chan, 7); + if (ret) + return ret; + + BEGIN_RING(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); + OUT_RING (chan, chan->vram_handle); + BEGIN_RING(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 1); + OUT_RING (chan, 1); /* ACQUIRE_EQ */ + } else { + ret = RING_SPACE(chan, 5); + if (ret) + return ret; + + BEGIN_NVC0(chan, 2, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 1); + OUT_RING (chan, 0x1001); /* ACQUIRE_EQ */ + } + + /* Delay semaphore destruction until its work is done */ + ret = nouveau_fence_new(chan, &fence, true); + if (ret) + return ret; + + kref_get(&sema->ref); + nouveau_fence_work(fence, semaphore_work, sema); + nouveau_fence_unref(&fence); + return 0; +} + +static int +semaphore_release(struct nouveau_channel *chan, struct nouveau_semaphore *sema) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_fence *fence = NULL; + u64 offset = chan->fence.vma.offset + sema->mem->start; + int ret; + + if (dev_priv->chipset < 0x84) { + ret = RING_SPACE(chan, 5); + if (ret) + return ret; + + BEGIN_RING(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 2); + OUT_RING (chan, NvSema); + OUT_RING (chan, offset); + BEGIN_RING(chan, 0, NV11_SUBCHAN_SEMAPHORE_RELEASE, 1); + OUT_RING (chan, 1); + } else + if (dev_priv->chipset < 0xc0) { + ret = RING_SPACE(chan, 7); + if (ret) + return ret; + + BEGIN_RING(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); + OUT_RING (chan, chan->vram_handle); + BEGIN_RING(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 1); + OUT_RING (chan, 2); /* RELEASE */ + } else { + ret = RING_SPACE(chan, 5); + if (ret) + return ret; + + BEGIN_NVC0(chan, 2, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 1); + OUT_RING (chan, 0x1002); /* RELEASE */ + } + + /* Delay semaphore destruction until its work is done */ + ret = nouveau_fence_new(chan, &fence, true); + if (ret) + return ret; + + kref_get(&sema->ref); + nouveau_fence_work(fence, semaphore_work, sema); + nouveau_fence_unref(&fence); + return 0; +} + +int +nouveau_fence_sync(struct nouveau_fence *fence, + struct nouveau_channel *wchan) +{ + struct nouveau_channel *chan = nouveau_fence_channel(fence); + struct drm_device *dev = wchan->dev; + struct nouveau_semaphore *sema; + int ret = 0; + + if (likely(!chan || chan == wchan || + nouveau_fence_signalled(fence))) + goto out; + + sema = semaphore_alloc(dev); + if (!sema) { + /* Early card or broken userspace, fall back to + * software sync. */ + ret = nouveau_fence_wait(fence, true, false); + goto out; + } + + /* try to take chan's mutex, if we can't take it right away + * we have to fallback to software sync to prevent locking + * order issues + */ + if (!mutex_trylock(&chan->mutex)) { + ret = nouveau_fence_wait(fence, true, false); + goto out_unref; + } + + /* Make wchan wait until it gets signalled */ + ret = semaphore_acquire(wchan, sema); + if (ret) + goto out_unlock; + + /* Signal the semaphore from chan */ + ret = semaphore_release(chan, sema); + +out_unlock: + mutex_unlock(&chan->mutex); +out_unref: + kref_put(&sema->ref, semaphore_free); +out: + if (chan) + nouveau_channel_put_unlocked(&chan); + return ret; +} + +int +__nouveau_fence_flush(void *sync_obj, void *sync_arg) +{ + return 0; +} + +int +nouveau_fence_channel_init(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *obj = NULL; + int ret; + + if (dev_priv->card_type < NV_C0) { + /* Create an NV_SW object for various sync purposes */ + ret = nouveau_gpuobj_gr_new(chan, NvSw, NV_SW); + if (ret) + return ret; + + ret = RING_SPACE(chan, 2); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubSw, NV01_SUBCHAN_OBJECT, 1); + OUT_RING (chan, NvSw); + FIRE_RING (chan); + } + + /* Setup area of memory shared between all channels for x-chan sync */ + if (USE_SEMA(dev) && dev_priv->chipset < 0x84) { + struct ttm_mem_reg *mem = &dev_priv->fence.bo->bo.mem; + + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_FROM_MEMORY, + mem->start << PAGE_SHIFT, + mem->size, NV_MEM_ACCESS_RW, + NV_MEM_TARGET_VRAM, &obj); + if (ret) + return ret; + + ret = nouveau_ramht_insert(chan, NvSema, obj); + nouveau_gpuobj_ref(NULL, &obj); + if (ret) + return ret; + } else + if (USE_SEMA(dev)) { + /* map fence bo into channel's vm */ + ret = nouveau_bo_vma_add(dev_priv->fence.bo, chan->vm, + &chan->fence.vma); + if (ret) + return ret; + } + + atomic_set(&chan->fence.last_sequence_irq, 0); + return 0; +} + +void +nouveau_fence_channel_fini(struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_fence *tmp, *fence; + + spin_lock(&chan->fence.lock); + list_for_each_entry_safe(fence, tmp, &chan->fence.pending, entry) { + fence->signalled = true; + list_del(&fence->entry); + + if (unlikely(fence->work)) + fence->work(fence->priv, false); + + kref_put(&fence->refcount, nouveau_fence_del); + } + spin_unlock(&chan->fence.lock); + + nouveau_bo_vma_del(dev_priv->fence.bo, &chan->fence.vma); +} + +int +nouveau_fence_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int size = (dev_priv->chipset < 0x84) ? 4096 : 16384; + int ret; + + /* Create a shared VRAM heap for cross-channel sync. */ + if (USE_SEMA(dev)) { + ret = nouveau_bo_new(dev, size, 0, TTM_PL_FLAG_VRAM, + 0, 0, &dev_priv->fence.bo); + if (ret) + return ret; + + ret = nouveau_bo_pin(dev_priv->fence.bo, TTM_PL_FLAG_VRAM); + if (ret) + goto fail; + + ret = nouveau_bo_map(dev_priv->fence.bo); + if (ret) + goto fail; + + ret = drm_mm_init(&dev_priv->fence.heap, 0, + dev_priv->fence.bo->bo.mem.size); + if (ret) + goto fail; + + spin_lock_init(&dev_priv->fence.lock); + } + + return 0; +fail: + nouveau_bo_unmap(dev_priv->fence.bo); + nouveau_bo_ref(NULL, &dev_priv->fence.bo); + return ret; +} + +void +nouveau_fence_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (USE_SEMA(dev)) { + drm_mm_takedown(&dev_priv->fence.heap); + nouveau_bo_unmap(dev_priv->fence.bo); + nouveau_bo_unpin(dev_priv->fence.bo); + nouveau_bo_ref(NULL, &dev_priv->fence.bo); + } +} diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c new file mode 100644 index 00000000..ed52a6f4 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_gem.c @@ -0,0 +1,868 @@ +/* + * Copyright (C) 2008 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include "drmP.h" +#include "drm.h" + +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_dma.h" + +#define nouveau_gem_pushbuf_sync(chan) 0 + +int +nouveau_gem_object_new(struct drm_gem_object *gem) +{ + return 0; +} + +void +nouveau_gem_object_del(struct drm_gem_object *gem) +{ + struct nouveau_bo *nvbo = gem->driver_private; + struct ttm_buffer_object *bo = &nvbo->bo; + + if (!nvbo) + return; + nvbo->gem = NULL; + + if (unlikely(nvbo->pin_refcnt)) { + nvbo->pin_refcnt = 1; + nouveau_bo_unpin(nvbo); + } + + ttm_bo_unref(&bo); + + drm_gem_object_release(gem); + kfree(gem); +} + +int +nouveau_gem_object_open(struct drm_gem_object *gem, struct drm_file *file_priv) +{ + struct nouveau_fpriv *fpriv = nouveau_fpriv(file_priv); + struct nouveau_bo *nvbo = nouveau_gem_object(gem); + struct nouveau_vma *vma; + int ret; + + if (!fpriv->vm) + return 0; + + ret = ttm_bo_reserve(&nvbo->bo, false, false, false, 0); + if (ret) + return ret; + + vma = nouveau_bo_vma_find(nvbo, fpriv->vm); + if (!vma) { + vma = kzalloc(sizeof(*vma), GFP_KERNEL); + if (!vma) { + ret = -ENOMEM; + goto out; + } + + ret = nouveau_bo_vma_add(nvbo, fpriv->vm, vma); + if (ret) { + kfree(vma); + goto out; + } + } else { + vma->refcount++; + } + +out: + ttm_bo_unreserve(&nvbo->bo); + return ret; +} + +void +nouveau_gem_object_close(struct drm_gem_object *gem, struct drm_file *file_priv) +{ + struct nouveau_fpriv *fpriv = nouveau_fpriv(file_priv); + struct nouveau_bo *nvbo = nouveau_gem_object(gem); + struct nouveau_vma *vma; + int ret; + + if (!fpriv->vm) + return; + + ret = ttm_bo_reserve(&nvbo->bo, false, false, false, 0); + if (ret) + return; + + vma = nouveau_bo_vma_find(nvbo, fpriv->vm); + if (vma) { + if (--vma->refcount == 0) { + nouveau_bo_vma_del(nvbo, vma); + kfree(vma); + } + } + ttm_bo_unreserve(&nvbo->bo); +} + +int +nouveau_gem_new(struct drm_device *dev, int size, int align, uint32_t domain, + uint32_t tile_mode, uint32_t tile_flags, + struct nouveau_bo **pnvbo) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_bo *nvbo; + u32 flags = 0; + int ret; + + if (domain & NOUVEAU_GEM_DOMAIN_VRAM) + flags |= TTM_PL_FLAG_VRAM; + if (domain & NOUVEAU_GEM_DOMAIN_GART) + flags |= TTM_PL_FLAG_TT; + if (!flags || domain & NOUVEAU_GEM_DOMAIN_CPU) + flags |= TTM_PL_FLAG_SYSTEM; + + ret = nouveau_bo_new(dev, size, align, flags, tile_mode, + tile_flags, pnvbo); + if (ret) + return ret; + nvbo = *pnvbo; + + /* we restrict allowed domains on nv50+ to only the types + * that were requested at creation time. not possibly on + * earlier chips without busting the ABI. + */ + nvbo->valid_domains = NOUVEAU_GEM_DOMAIN_VRAM | + NOUVEAU_GEM_DOMAIN_GART; + if (dev_priv->card_type >= NV_50) + nvbo->valid_domains &= domain; + + nvbo->gem = drm_gem_object_alloc(dev, nvbo->bo.mem.size); + if (!nvbo->gem) { + nouveau_bo_ref(NULL, pnvbo); + return -ENOMEM; + } + + nvbo->bo.persistent_swap_storage = nvbo->gem->filp; + nvbo->gem->driver_private = nvbo; + return 0; +} + +static int +nouveau_gem_info(struct drm_file *file_priv, struct drm_gem_object *gem, + struct drm_nouveau_gem_info *rep) +{ + struct nouveau_fpriv *fpriv = nouveau_fpriv(file_priv); + struct nouveau_bo *nvbo = nouveau_gem_object(gem); + struct nouveau_vma *vma; + + if (nvbo->bo.mem.mem_type == TTM_PL_TT) + rep->domain = NOUVEAU_GEM_DOMAIN_GART; + else + rep->domain = NOUVEAU_GEM_DOMAIN_VRAM; + + rep->offset = nvbo->bo.offset; + if (fpriv->vm) { + vma = nouveau_bo_vma_find(nvbo, fpriv->vm); + if (!vma) + return -EINVAL; + + rep->offset = vma->offset; + } + + rep->size = nvbo->bo.mem.num_pages << PAGE_SHIFT; + rep->map_handle = nvbo->bo.addr_space_offset; + rep->tile_mode = nvbo->tile_mode; + rep->tile_flags = nvbo->tile_flags; + return 0; +} + +int +nouveau_gem_ioctl_new(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_nouveau_gem_new *req = data; + struct nouveau_bo *nvbo = NULL; + int ret = 0; + + if (unlikely(dev_priv->ttm.bdev.dev_mapping == NULL)) + dev_priv->ttm.bdev.dev_mapping = dev_priv->dev->dev_mapping; + + if (!dev_priv->engine.vram.flags_valid(dev, req->info.tile_flags)) { + NV_ERROR(dev, "bad page flags: 0x%08x\n", req->info.tile_flags); + return -EINVAL; + } + + ret = nouveau_gem_new(dev, req->info.size, req->align, + req->info.domain, req->info.tile_mode, + req->info.tile_flags, &nvbo); + if (ret) + return ret; + + ret = drm_gem_handle_create(file_priv, nvbo->gem, &req->info.handle); + if (ret == 0) { + ret = nouveau_gem_info(file_priv, nvbo->gem, &req->info); + if (ret) + drm_gem_handle_delete(file_priv, req->info.handle); + } + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(nvbo->gem); + return ret; +} + +static int +nouveau_gem_set_domain(struct drm_gem_object *gem, uint32_t read_domains, + uint32_t write_domains, uint32_t valid_domains) +{ + struct nouveau_bo *nvbo = gem->driver_private; + struct ttm_buffer_object *bo = &nvbo->bo; + uint32_t domains = valid_domains & nvbo->valid_domains & + (write_domains ? write_domains : read_domains); + uint32_t pref_flags = 0, valid_flags = 0; + + if (!domains) + return -EINVAL; + + if (valid_domains & NOUVEAU_GEM_DOMAIN_VRAM) + valid_flags |= TTM_PL_FLAG_VRAM; + + if (valid_domains & NOUVEAU_GEM_DOMAIN_GART) + valid_flags |= TTM_PL_FLAG_TT; + + if ((domains & NOUVEAU_GEM_DOMAIN_VRAM) && + bo->mem.mem_type == TTM_PL_VRAM) + pref_flags |= TTM_PL_FLAG_VRAM; + + else if ((domains & NOUVEAU_GEM_DOMAIN_GART) && + bo->mem.mem_type == TTM_PL_TT) + pref_flags |= TTM_PL_FLAG_TT; + + else if (domains & NOUVEAU_GEM_DOMAIN_VRAM) + pref_flags |= TTM_PL_FLAG_VRAM; + + else + pref_flags |= TTM_PL_FLAG_TT; + + nouveau_bo_placement_set(nvbo, pref_flags, valid_flags); + + return 0; +} + +struct validate_op { + struct list_head vram_list; + struct list_head gart_list; + struct list_head both_list; +}; + +static void +validate_fini_list(struct list_head *list, struct nouveau_fence *fence) +{ + struct list_head *entry, *tmp; + struct nouveau_bo *nvbo; + + list_for_each_safe(entry, tmp, list) { + nvbo = list_entry(entry, struct nouveau_bo, entry); + + nouveau_bo_fence(nvbo, fence); + + if (unlikely(nvbo->validate_mapped)) { + ttm_bo_kunmap(&nvbo->kmap); + nvbo->validate_mapped = false; + } + + list_del(&nvbo->entry); + nvbo->reserved_by = NULL; + ttm_bo_unreserve(&nvbo->bo); + drm_gem_object_unreference_unlocked(nvbo->gem); + } +} + +static void +validate_fini(struct validate_op *op, struct nouveau_fence* fence) +{ + validate_fini_list(&op->vram_list, fence); + validate_fini_list(&op->gart_list, fence); + validate_fini_list(&op->both_list, fence); +} + +static int +validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, + struct drm_nouveau_gem_pushbuf_bo *pbbo, + int nr_buffers, struct validate_op *op) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t sequence; + int trycnt = 0; + int ret, i; + + sequence = atomic_add_return(1, &dev_priv->ttm.validate_sequence); +retry: + if (++trycnt > 100000) { + NV_ERROR(dev, "%s failed and gave up.\n", __func__); + return -EINVAL; + } + + for (i = 0; i < nr_buffers; i++) { + struct drm_nouveau_gem_pushbuf_bo *b = &pbbo[i]; + struct drm_gem_object *gem; + struct nouveau_bo *nvbo; + + gem = drm_gem_object_lookup(dev, file_priv, b->handle); + if (!gem) { + NV_ERROR(dev, "Unknown handle 0x%08x\n", b->handle); + validate_fini(op, NULL); + return -ENOENT; + } + nvbo = gem->driver_private; + + if (nvbo->reserved_by && nvbo->reserved_by == file_priv) { + NV_ERROR(dev, "multiple instances of buffer %d on " + "validation list\n", b->handle); + validate_fini(op, NULL); + return -EINVAL; + } + + ret = ttm_bo_reserve(&nvbo->bo, true, false, true, sequence); + if (ret) { + validate_fini(op, NULL); + if (unlikely(ret == -EAGAIN)) + ret = ttm_bo_wait_unreserved(&nvbo->bo, true); + drm_gem_object_unreference_unlocked(gem); + if (unlikely(ret)) { + if (ret != -ERESTARTSYS) + NV_ERROR(dev, "fail reserve\n"); + return ret; + } + goto retry; + } + + b->user_priv = (uint64_t)(unsigned long)nvbo; + nvbo->reserved_by = file_priv; + nvbo->pbbo_index = i; + if ((b->valid_domains & NOUVEAU_GEM_DOMAIN_VRAM) && + (b->valid_domains & NOUVEAU_GEM_DOMAIN_GART)) + list_add_tail(&nvbo->entry, &op->both_list); + else + if (b->valid_domains & NOUVEAU_GEM_DOMAIN_VRAM) + list_add_tail(&nvbo->entry, &op->vram_list); + else + if (b->valid_domains & NOUVEAU_GEM_DOMAIN_GART) + list_add_tail(&nvbo->entry, &op->gart_list); + else { + NV_ERROR(dev, "invalid valid domains: 0x%08x\n", + b->valid_domains); + list_add_tail(&nvbo->entry, &op->both_list); + validate_fini(op, NULL); + return -EINVAL; + } + } + + return 0; +} + +static int +validate_sync(struct nouveau_channel *chan, struct nouveau_bo *nvbo) +{ + struct nouveau_fence *fence = NULL; + int ret = 0; + + spin_lock(&nvbo->bo.bdev->fence_lock); + if (nvbo->bo.sync_obj) + fence = nouveau_fence_ref(nvbo->bo.sync_obj); + spin_unlock(&nvbo->bo.bdev->fence_lock); + + if (fence) { + ret = nouveau_fence_sync(fence, chan); + nouveau_fence_unref(&fence); + } + + return ret; +} + +static int +validate_list(struct nouveau_channel *chan, struct list_head *list, + struct drm_nouveau_gem_pushbuf_bo *pbbo, uint64_t user_pbbo_ptr) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct drm_nouveau_gem_pushbuf_bo __user *upbbo = + (void __force __user *)(uintptr_t)user_pbbo_ptr; + struct drm_device *dev = chan->dev; + struct nouveau_bo *nvbo; + int ret, relocs = 0; + + list_for_each_entry(nvbo, list, entry) { + struct drm_nouveau_gem_pushbuf_bo *b = &pbbo[nvbo->pbbo_index]; + + ret = validate_sync(chan, nvbo); + if (unlikely(ret)) { + NV_ERROR(dev, "fail pre-validate sync\n"); + return ret; + } + + ret = nouveau_gem_set_domain(nvbo->gem, b->read_domains, + b->write_domains, + b->valid_domains); + if (unlikely(ret)) { + NV_ERROR(dev, "fail set_domain\n"); + return ret; + } + + ret = nouveau_bo_validate(nvbo, true, false, false); + if (unlikely(ret)) { + if (ret != -ERESTARTSYS) + NV_ERROR(dev, "fail ttm_validate\n"); + return ret; + } + + ret = validate_sync(chan, nvbo); + if (unlikely(ret)) { + NV_ERROR(dev, "fail post-validate sync\n"); + return ret; + } + + if (dev_priv->card_type < NV_50) { + if (nvbo->bo.offset == b->presumed.offset && + ((nvbo->bo.mem.mem_type == TTM_PL_VRAM && + b->presumed.domain & NOUVEAU_GEM_DOMAIN_VRAM) || + (nvbo->bo.mem.mem_type == TTM_PL_TT && + b->presumed.domain & NOUVEAU_GEM_DOMAIN_GART))) + continue; + + if (nvbo->bo.mem.mem_type == TTM_PL_TT) + b->presumed.domain = NOUVEAU_GEM_DOMAIN_GART; + else + b->presumed.domain = NOUVEAU_GEM_DOMAIN_VRAM; + b->presumed.offset = nvbo->bo.offset; + b->presumed.valid = 0; + relocs++; + + if (DRM_COPY_TO_USER(&upbbo[nvbo->pbbo_index].presumed, + &b->presumed, sizeof(b->presumed))) + return -EFAULT; + } + } + + return relocs; +} + +static int +nouveau_gem_pushbuf_validate(struct nouveau_channel *chan, + struct drm_file *file_priv, + struct drm_nouveau_gem_pushbuf_bo *pbbo, + uint64_t user_buffers, int nr_buffers, + struct validate_op *op, int *apply_relocs) +{ + struct drm_device *dev = chan->dev; + int ret, relocs = 0; + + INIT_LIST_HEAD(&op->vram_list); + INIT_LIST_HEAD(&op->gart_list); + INIT_LIST_HEAD(&op->both_list); + + if (nr_buffers == 0) + return 0; + + ret = validate_init(chan, file_priv, pbbo, nr_buffers, op); + if (unlikely(ret)) { + if (ret != -ERESTARTSYS) + NV_ERROR(dev, "validate_init\n"); + return ret; + } + + ret = validate_list(chan, &op->vram_list, pbbo, user_buffers); + if (unlikely(ret < 0)) { + if (ret != -ERESTARTSYS) + NV_ERROR(dev, "validate vram_list\n"); + validate_fini(op, NULL); + return ret; + } + relocs += ret; + + ret = validate_list(chan, &op->gart_list, pbbo, user_buffers); + if (unlikely(ret < 0)) { + if (ret != -ERESTARTSYS) + NV_ERROR(dev, "validate gart_list\n"); + validate_fini(op, NULL); + return ret; + } + relocs += ret; + + ret = validate_list(chan, &op->both_list, pbbo, user_buffers); + if (unlikely(ret < 0)) { + if (ret != -ERESTARTSYS) + NV_ERROR(dev, "validate both_list\n"); + validate_fini(op, NULL); + return ret; + } + relocs += ret; + + *apply_relocs = relocs; + return 0; +} + +static inline void * +u_memcpya(uint64_t user, unsigned nmemb, unsigned size) +{ + void *mem; + void __user *userptr = (void __force __user *)(uintptr_t)user; + + mem = kmalloc(nmemb * size, GFP_KERNEL); + if (!mem) + return ERR_PTR(-ENOMEM); + + if (DRM_COPY_FROM_USER(mem, userptr, nmemb * size)) { + kfree(mem); + return ERR_PTR(-EFAULT); + } + + return mem; +} + +static int +nouveau_gem_pushbuf_reloc_apply(struct drm_device *dev, + struct drm_nouveau_gem_pushbuf *req, + struct drm_nouveau_gem_pushbuf_bo *bo) +{ + struct drm_nouveau_gem_pushbuf_reloc *reloc = NULL; + int ret = 0; + unsigned i; + + reloc = u_memcpya(req->relocs, req->nr_relocs, sizeof(*reloc)); + if (IS_ERR(reloc)) + return PTR_ERR(reloc); + + for (i = 0; i < req->nr_relocs; i++) { + struct drm_nouveau_gem_pushbuf_reloc *r = &reloc[i]; + struct drm_nouveau_gem_pushbuf_bo *b; + struct nouveau_bo *nvbo; + uint32_t data; + + if (unlikely(r->bo_index > req->nr_buffers)) { + NV_ERROR(dev, "reloc bo index invalid\n"); + ret = -EINVAL; + break; + } + + b = &bo[r->bo_index]; + if (b->presumed.valid) + continue; + + if (unlikely(r->reloc_bo_index > req->nr_buffers)) { + NV_ERROR(dev, "reloc container bo index invalid\n"); + ret = -EINVAL; + break; + } + nvbo = (void *)(unsigned long)bo[r->reloc_bo_index].user_priv; + + if (unlikely(r->reloc_bo_offset + 4 > + nvbo->bo.mem.num_pages << PAGE_SHIFT)) { + NV_ERROR(dev, "reloc outside of bo\n"); + ret = -EINVAL; + break; + } + + if (!nvbo->kmap.virtual) { + ret = ttm_bo_kmap(&nvbo->bo, 0, nvbo->bo.mem.num_pages, + &nvbo->kmap); + if (ret) { + NV_ERROR(dev, "failed kmap for reloc\n"); + break; + } + nvbo->validate_mapped = true; + } + + if (r->flags & NOUVEAU_GEM_RELOC_LOW) + data = b->presumed.offset + r->data; + else + if (r->flags & NOUVEAU_GEM_RELOC_HIGH) + data = (b->presumed.offset + r->data) >> 32; + else + data = r->data; + + if (r->flags & NOUVEAU_GEM_RELOC_OR) { + if (b->presumed.domain == NOUVEAU_GEM_DOMAIN_GART) + data |= r->tor; + else + data |= r->vor; + } + + spin_lock(&nvbo->bo.bdev->fence_lock); + ret = ttm_bo_wait(&nvbo->bo, false, false, false); + spin_unlock(&nvbo->bo.bdev->fence_lock); + if (ret) { + NV_ERROR(dev, "reloc wait_idle failed: %d\n", ret); + break; + } + + nouveau_bo_wr32(nvbo, r->reloc_bo_offset >> 2, data); + } + + kfree(reloc); + return ret; +} + +int +nouveau_gem_ioctl_pushbuf(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_nouveau_gem_pushbuf *req = data; + struct drm_nouveau_gem_pushbuf_push *push; + struct drm_nouveau_gem_pushbuf_bo *bo; + struct nouveau_channel *chan; + struct validate_op op; + struct nouveau_fence *fence = NULL; + int i, j, ret = 0, do_reloc = 0; + + chan = nouveau_channel_get(file_priv, req->channel); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + req->vram_available = dev_priv->fb_aper_free; + req->gart_available = dev_priv->gart_info.aper_free; + if (unlikely(req->nr_push == 0)) + goto out_next; + + if (unlikely(req->nr_push > NOUVEAU_GEM_MAX_PUSH)) { + NV_ERROR(dev, "pushbuf push count exceeds limit: %d max %d\n", + req->nr_push, NOUVEAU_GEM_MAX_PUSH); + nouveau_channel_put(&chan); + return -EINVAL; + } + + if (unlikely(req->nr_buffers > NOUVEAU_GEM_MAX_BUFFERS)) { + NV_ERROR(dev, "pushbuf bo count exceeds limit: %d max %d\n", + req->nr_buffers, NOUVEAU_GEM_MAX_BUFFERS); + nouveau_channel_put(&chan); + return -EINVAL; + } + + if (unlikely(req->nr_relocs > NOUVEAU_GEM_MAX_RELOCS)) { + NV_ERROR(dev, "pushbuf reloc count exceeds limit: %d max %d\n", + req->nr_relocs, NOUVEAU_GEM_MAX_RELOCS); + nouveau_channel_put(&chan); + return -EINVAL; + } + + push = u_memcpya(req->push, req->nr_push, sizeof(*push)); + if (IS_ERR(push)) { + nouveau_channel_put(&chan); + return PTR_ERR(push); + } + + bo = u_memcpya(req->buffers, req->nr_buffers, sizeof(*bo)); + if (IS_ERR(bo)) { + kfree(push); + nouveau_channel_put(&chan); + return PTR_ERR(bo); + } + + /* Ensure all push buffers are on validate list */ + for (i = 0; i < req->nr_push; i++) { + if (push[i].bo_index >= req->nr_buffers) { + NV_ERROR(dev, "push %d buffer not in list\n", i); + ret = -EINVAL; + goto out_prevalid; + } + } + + /* Validate buffer list */ + ret = nouveau_gem_pushbuf_validate(chan, file_priv, bo, req->buffers, + req->nr_buffers, &op, &do_reloc); + if (ret) { + if (ret != -ERESTARTSYS) + NV_ERROR(dev, "validate: %d\n", ret); + goto out_prevalid; + } + + /* Apply any relocations that are required */ + if (do_reloc) { + ret = nouveau_gem_pushbuf_reloc_apply(dev, req, bo); + if (ret) { + NV_ERROR(dev, "reloc apply: %d\n", ret); + goto out; + } + } + + if (chan->dma.ib_max) { + ret = nouveau_dma_wait(chan, req->nr_push + 1, 6); + if (ret) { + NV_INFO(dev, "nv50cal_space: %d\n", ret); + goto out; + } + + for (i = 0; i < req->nr_push; i++) { + struct nouveau_bo *nvbo = (void *)(unsigned long) + bo[push[i].bo_index].user_priv; + + nv50_dma_push(chan, nvbo, push[i].offset, + push[i].length); + } + } else + if (dev_priv->chipset >= 0x25) { + ret = RING_SPACE(chan, req->nr_push * 2); + if (ret) { + NV_ERROR(dev, "cal_space: %d\n", ret); + goto out; + } + + for (i = 0; i < req->nr_push; i++) { + struct nouveau_bo *nvbo = (void *)(unsigned long) + bo[push[i].bo_index].user_priv; + struct drm_mm_node *mem = nvbo->bo.mem.mm_node; + + OUT_RING(chan, ((mem->start << PAGE_SHIFT) + + push[i].offset) | 2); + OUT_RING(chan, 0); + } + } else { + ret = RING_SPACE(chan, req->nr_push * (2 + NOUVEAU_DMA_SKIPS)); + if (ret) { + NV_ERROR(dev, "jmp_space: %d\n", ret); + goto out; + } + + for (i = 0; i < req->nr_push; i++) { + struct nouveau_bo *nvbo = (void *)(unsigned long) + bo[push[i].bo_index].user_priv; + struct drm_mm_node *mem = nvbo->bo.mem.mm_node; + uint32_t cmd; + + cmd = chan->pushbuf_base + ((chan->dma.cur + 2) << 2); + cmd |= 0x20000000; + if (unlikely(cmd != req->suffix0)) { + if (!nvbo->kmap.virtual) { + ret = ttm_bo_kmap(&nvbo->bo, 0, + nvbo->bo.mem. + num_pages, + &nvbo->kmap); + if (ret) { + WIND_RING(chan); + goto out; + } + nvbo->validate_mapped = true; + } + + nouveau_bo_wr32(nvbo, (push[i].offset + + push[i].length - 8) / 4, cmd); + } + + OUT_RING(chan, ((mem->start << PAGE_SHIFT) + + push[i].offset) | 0x20000000); + OUT_RING(chan, 0); + for (j = 0; j < NOUVEAU_DMA_SKIPS; j++) + OUT_RING(chan, 0); + } + } + + ret = nouveau_fence_new(chan, &fence, true); + if (ret) { + NV_ERROR(dev, "error fencing pushbuf: %d\n", ret); + WIND_RING(chan); + goto out; + } + +out: + validate_fini(&op, fence); + nouveau_fence_unref(&fence); + +out_prevalid: + kfree(bo); + kfree(push); + +out_next: + if (chan->dma.ib_max) { + req->suffix0 = 0x00000000; + req->suffix1 = 0x00000000; + } else + if (dev_priv->chipset >= 0x25) { + req->suffix0 = 0x00020000; + req->suffix1 = 0x00000000; + } else { + req->suffix0 = 0x20000000 | + (chan->pushbuf_base + ((chan->dma.cur + 2) << 2)); + req->suffix1 = 0x00000000; + } + + nouveau_channel_put(&chan); + return ret; +} + +static inline uint32_t +domain_to_ttm(struct nouveau_bo *nvbo, uint32_t domain) +{ + uint32_t flags = 0; + + if (domain & NOUVEAU_GEM_DOMAIN_VRAM) + flags |= TTM_PL_FLAG_VRAM; + if (domain & NOUVEAU_GEM_DOMAIN_GART) + flags |= TTM_PL_FLAG_TT; + + return flags; +} + +int +nouveau_gem_ioctl_cpu_prep(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_gem_cpu_prep *req = data; + struct drm_gem_object *gem; + struct nouveau_bo *nvbo; + bool no_wait = !!(req->flags & NOUVEAU_GEM_CPU_PREP_NOWAIT); + int ret = -EINVAL; + + gem = drm_gem_object_lookup(dev, file_priv, req->handle); + if (!gem) + return -ENOENT; + nvbo = nouveau_gem_object(gem); + + spin_lock(&nvbo->bo.bdev->fence_lock); + ret = ttm_bo_wait(&nvbo->bo, true, true, no_wait); + spin_unlock(&nvbo->bo.bdev->fence_lock); + drm_gem_object_unreference_unlocked(gem); + return ret; +} + +int +nouveau_gem_ioctl_cpu_fini(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return 0; +} + +int +nouveau_gem_ioctl_info(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_gem_info *req = data; + struct drm_gem_object *gem; + int ret; + + gem = drm_gem_object_lookup(dev, file_priv, req->handle); + if (!gem) + return -ENOENT; + + ret = nouveau_gem_info(file_priv, gem, req); + drm_gem_object_unreference_unlocked(gem); + return ret; +} + diff --git a/drivers/gpu/drm/nouveau/nouveau_gpio.c b/drivers/gpu/drm/nouveau/nouveau_gpio.c new file mode 100644 index 00000000..a580cc62 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_gpio.c @@ -0,0 +1,400 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_i2c.h" +#include "nouveau_gpio.h" + +static u8 * +dcb_gpio_table(struct drm_device *dev) +{ + u8 *dcb = dcb_table(dev); + if (dcb) { + if (dcb[0] >= 0x30 && dcb[1] >= 0x0c) + return ROMPTR(dev, dcb[0x0a]); + if (dcb[0] >= 0x22 && dcb[-1] >= 0x13) + return ROMPTR(dev, dcb[-15]); + } + return NULL; +} + +static u8 * +dcb_gpio_entry(struct drm_device *dev, int idx, int ent, u8 *version) +{ + u8 *table = dcb_gpio_table(dev); + if (table) { + *version = table[0]; + if (*version < 0x30 && ent < table[2]) + return table + 3 + (ent * table[1]); + else if (ent < table[2]) + return table + table[1] + (ent * table[3]); + } + return NULL; +} + +int +nouveau_gpio_drive(struct drm_device *dev, int idx, int line, int dir, int out) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + + return pgpio->drive ? pgpio->drive(dev, line, dir, out) : -ENODEV; +} + +int +nouveau_gpio_sense(struct drm_device *dev, int idx, int line) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + + return pgpio->sense ? pgpio->sense(dev, line) : -ENODEV; +} + +int +nouveau_gpio_find(struct drm_device *dev, int idx, u8 func, u8 line, + struct gpio_func *gpio) +{ + u8 *table, *entry, version; + int i = -1; + + if (line == 0xff && func == 0xff) + return -EINVAL; + + while ((entry = dcb_gpio_entry(dev, idx, ++i, &version))) { + if (version < 0x40) { + u16 data = ROM16(entry[0]); + *gpio = (struct gpio_func) { + .line = (data & 0x001f) >> 0, + .func = (data & 0x07e0) >> 5, + .log[0] = (data & 0x1800) >> 11, + .log[1] = (data & 0x6000) >> 13, + }; + } else + if (version < 0x41) { + *gpio = (struct gpio_func) { + .line = entry[0] & 0x1f, + .func = entry[1], + .log[0] = (entry[3] & 0x18) >> 3, + .log[1] = (entry[3] & 0x60) >> 5, + }; + } else { + *gpio = (struct gpio_func) { + .line = entry[0] & 0x3f, + .func = entry[1], + .log[0] = (entry[4] & 0x30) >> 4, + .log[1] = (entry[4] & 0xc0) >> 6, + }; + } + + if ((line == 0xff || line == gpio->line) && + (func == 0xff || func == gpio->func)) + return 0; + } + + /* DCB 2.2, fixed TVDAC GPIO data */ + if ((table = dcb_table(dev)) && table[0] >= 0x22) { + if (func == DCB_GPIO_TVDAC0) { + *gpio = (struct gpio_func) { + .func = DCB_GPIO_TVDAC0, + .line = table[-4] >> 4, + .log[0] = !!(table[-5] & 2), + .log[1] = !(table[-5] & 2), + }; + return 0; + } + } + + /* Apple iMac G4 NV18 */ + if (nv_match_device(dev, 0x0189, 0x10de, 0x0010)) { + if (func == DCB_GPIO_TVDAC0) { + *gpio = (struct gpio_func) { + .func = DCB_GPIO_TVDAC0, + .line = 4, + .log[0] = 0, + .log[1] = 1, + }; + return 0; + } + } + + return -EINVAL; +} + +int +nouveau_gpio_set(struct drm_device *dev, int idx, u8 tag, u8 line, int state) +{ + struct gpio_func gpio; + int ret; + + ret = nouveau_gpio_find(dev, idx, tag, line, &gpio); + if (ret == 0) { + int dir = !!(gpio.log[state] & 0x02); + int out = !!(gpio.log[state] & 0x01); + ret = nouveau_gpio_drive(dev, idx, gpio.line, dir, out); + } + + return ret; +} + +int +nouveau_gpio_get(struct drm_device *dev, int idx, u8 tag, u8 line) +{ + struct gpio_func gpio; + int ret; + + ret = nouveau_gpio_find(dev, idx, tag, line, &gpio); + if (ret == 0) { + ret = nouveau_gpio_sense(dev, idx, gpio.line); + if (ret >= 0) + ret = (ret == (gpio.log[1] & 1)); + } + + return ret; +} + +int +nouveau_gpio_irq(struct drm_device *dev, int idx, u8 tag, u8 line, bool on) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + struct gpio_func gpio; + int ret; + + ret = nouveau_gpio_find(dev, idx, tag, line, &gpio); + if (ret == 0) { + if (idx == 0 && pgpio->irq_enable) + pgpio->irq_enable(dev, gpio.line, on); + else + ret = -ENODEV; + } + + return ret; +} + +struct gpio_isr { + struct drm_device *dev; + struct list_head head; + struct work_struct work; + int idx; + struct gpio_func func; + void (*handler)(void *, int); + void *data; + bool inhibit; +}; + +static void +nouveau_gpio_isr_bh(struct work_struct *work) +{ + struct gpio_isr *isr = container_of(work, struct gpio_isr, work); + struct drm_device *dev = isr->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + unsigned long flags; + int state; + + state = nouveau_gpio_get(dev, isr->idx, isr->func.func, isr->func.line); + if (state >= 0) + isr->handler(isr->data, state); + + spin_lock_irqsave(&pgpio->lock, flags); + isr->inhibit = false; + spin_unlock_irqrestore(&pgpio->lock, flags); +} + +void +nouveau_gpio_isr(struct drm_device *dev, int idx, u32 line_mask) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + struct gpio_isr *isr; + + if (idx != 0) + return; + + spin_lock(&pgpio->lock); + list_for_each_entry(isr, &pgpio->isr, head) { + if (line_mask & (1 << isr->func.line)) { + if (isr->inhibit) + continue; + isr->inhibit = true; + schedule_work(&isr->work); + } + } + spin_unlock(&pgpio->lock); +} + +int +nouveau_gpio_isr_add(struct drm_device *dev, int idx, u8 tag, u8 line, + void (*handler)(void *, int), void *data) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + struct gpio_isr *isr; + unsigned long flags; + int ret; + + isr = kzalloc(sizeof(*isr), GFP_KERNEL); + if (!isr) + return -ENOMEM; + + ret = nouveau_gpio_find(dev, idx, tag, line, &isr->func); + if (ret) { + kfree(isr); + return ret; + } + + INIT_WORK(&isr->work, nouveau_gpio_isr_bh); + isr->dev = dev; + isr->handler = handler; + isr->data = data; + isr->idx = idx; + + spin_lock_irqsave(&pgpio->lock, flags); + list_add(&isr->head, &pgpio->isr); + spin_unlock_irqrestore(&pgpio->lock, flags); + return 0; +} + +void +nouveau_gpio_isr_del(struct drm_device *dev, int idx, u8 tag, u8 line, + void (*handler)(void *, int), void *data) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + struct gpio_isr *isr, *tmp; + struct gpio_func func; + unsigned long flags; + LIST_HEAD(tofree); + int ret; + + ret = nouveau_gpio_find(dev, idx, tag, line, &func); + if (ret == 0) { + spin_lock_irqsave(&pgpio->lock, flags); + list_for_each_entry_safe(isr, tmp, &pgpio->isr, head) { + if (memcmp(&isr->func, &func, sizeof(func)) || + isr->idx != idx || + isr->handler != handler || isr->data != data) + continue; + list_move(&isr->head, &tofree); + } + spin_unlock_irqrestore(&pgpio->lock, flags); + + list_for_each_entry_safe(isr, tmp, &tofree, head) { + flush_work_sync(&isr->work); + kfree(isr); + } + } +} + +int +nouveau_gpio_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + + INIT_LIST_HEAD(&pgpio->isr); + spin_lock_init(&pgpio->lock); + + return nouveau_gpio_init(dev); +} + +void +nouveau_gpio_destroy(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + + nouveau_gpio_fini(dev); + BUG_ON(!list_empty(&pgpio->isr)); +} + +int +nouveau_gpio_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + int ret = 0; + + if (pgpio->init) + ret = pgpio->init(dev); + + return ret; +} + +void +nouveau_gpio_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; + + if (pgpio->fini) + pgpio->fini(dev); +} + +void +nouveau_gpio_reset(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u8 *entry, version; + int ent = -1; + + while ((entry = dcb_gpio_entry(dev, 0, ++ent, &version))) { + u8 func = 0xff, line, defs, unk0, unk1; + if (version >= 0x41) { + defs = !!(entry[0] & 0x80); + line = entry[0] & 0x3f; + func = entry[1]; + unk0 = entry[2]; + unk1 = entry[3] & 0x1f; + } else + if (version >= 0x40) { + line = entry[0] & 0x1f; + func = entry[1]; + defs = !!(entry[3] & 0x01); + unk0 = !!(entry[3] & 0x02); + unk1 = !!(entry[3] & 0x04); + } else { + break; + } + + if (func == 0xff) + continue; + + nouveau_gpio_func_set(dev, func, defs); + + if (dev_priv->card_type >= NV_D0) { + nv_mask(dev, 0x00d610 + (line * 4), 0xff, unk0); + if (unk1--) + nv_mask(dev, 0x00d640 + (unk1 * 4), 0xff, line); + } else + if (dev_priv->card_type >= NV_50) { + static const u32 regs[] = { 0xe100, 0xe28c }; + u32 val = (unk1 << 16) | unk0; + u32 reg = regs[line >> 4]; line &= 0x0f; + + nv_mask(dev, reg, 0x00010001 << line, val << line); + } + } +} diff --git a/drivers/gpu/drm/nouveau/nouveau_gpio.h b/drivers/gpu/drm/nouveau/nouveau_gpio.h new file mode 100644 index 00000000..64c5cb07 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_gpio.h @@ -0,0 +1,71 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __NOUVEAU_GPIO_H__ +#define __NOUVEAU_GPIO_H__ + +struct gpio_func { + u8 func; + u8 line; + u8 log[2]; +}; + +/* nouveau_gpio.c */ +int nouveau_gpio_create(struct drm_device *); +void nouveau_gpio_destroy(struct drm_device *); +int nouveau_gpio_init(struct drm_device *); +void nouveau_gpio_fini(struct drm_device *); +void nouveau_gpio_reset(struct drm_device *); +int nouveau_gpio_drive(struct drm_device *, int idx, int line, + int dir, int out); +int nouveau_gpio_sense(struct drm_device *, int idx, int line); +int nouveau_gpio_find(struct drm_device *, int idx, u8 tag, u8 line, + struct gpio_func *); +int nouveau_gpio_set(struct drm_device *, int idx, u8 tag, u8 line, int state); +int nouveau_gpio_get(struct drm_device *, int idx, u8 tag, u8 line); +int nouveau_gpio_irq(struct drm_device *, int idx, u8 tag, u8 line, bool on); +void nouveau_gpio_isr(struct drm_device *, int idx, u32 mask); +int nouveau_gpio_isr_add(struct drm_device *, int idx, u8 tag, u8 line, + void (*)(void *, int state), void *data); +void nouveau_gpio_isr_del(struct drm_device *, int idx, u8 tag, u8 line, + void (*)(void *, int state), void *data); + +static inline bool +nouveau_gpio_func_valid(struct drm_device *dev, u8 tag) +{ + struct gpio_func func; + return (nouveau_gpio_find(dev, 0, tag, 0xff, &func)) == 0; +} + +static inline int +nouveau_gpio_func_set(struct drm_device *dev, u8 tag, int state) +{ + return nouveau_gpio_set(dev, 0, tag, 0xff, state); +} + +static inline int +nouveau_gpio_func_get(struct drm_device *dev, u8 tag) +{ + return nouveau_gpio_get(dev, 0, tag, 0xff); +} + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_grctx.h b/drivers/gpu/drm/nouveau/nouveau_grctx.h new file mode 100644 index 00000000..86c2e374 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_grctx.h @@ -0,0 +1,133 @@ +#ifndef __NOUVEAU_GRCTX_H__ +#define __NOUVEAU_GRCTX_H__ + +struct nouveau_grctx { + struct drm_device *dev; + + enum { + NOUVEAU_GRCTX_PROG, + NOUVEAU_GRCTX_VALS + } mode; + void *data; + + uint32_t ctxprog_max; + uint32_t ctxprog_len; + uint32_t ctxprog_reg; + int ctxprog_label[32]; + uint32_t ctxvals_pos; + uint32_t ctxvals_base; +}; + +#ifdef CP_CTX +static inline void +cp_out(struct nouveau_grctx *ctx, uint32_t inst) +{ + uint32_t *ctxprog = ctx->data; + + if (ctx->mode != NOUVEAU_GRCTX_PROG) + return; + + BUG_ON(ctx->ctxprog_len == ctx->ctxprog_max); + ctxprog[ctx->ctxprog_len++] = inst; +} + +static inline void +cp_lsr(struct nouveau_grctx *ctx, uint32_t val) +{ + cp_out(ctx, CP_LOAD_SR | val); +} + +static inline void +cp_ctx(struct nouveau_grctx *ctx, uint32_t reg, uint32_t length) +{ + ctx->ctxprog_reg = (reg - 0x00400000) >> 2; + + ctx->ctxvals_base = ctx->ctxvals_pos; + ctx->ctxvals_pos = ctx->ctxvals_base + length; + + if (length > (CP_CTX_COUNT >> CP_CTX_COUNT_SHIFT)) { + cp_lsr(ctx, length); + length = 0; + } + + cp_out(ctx, CP_CTX | (length << CP_CTX_COUNT_SHIFT) | ctx->ctxprog_reg); +} + +static inline void +cp_name(struct nouveau_grctx *ctx, int name) +{ + uint32_t *ctxprog = ctx->data; + int i; + + if (ctx->mode != NOUVEAU_GRCTX_PROG) + return; + + ctx->ctxprog_label[name] = ctx->ctxprog_len; + for (i = 0; i < ctx->ctxprog_len; i++) { + if ((ctxprog[i] & 0xfff00000) != 0xff400000) + continue; + if ((ctxprog[i] & CP_BRA_IP) != ((name) << CP_BRA_IP_SHIFT)) + continue; + ctxprog[i] = (ctxprog[i] & 0x00ff00ff) | + (ctx->ctxprog_len << CP_BRA_IP_SHIFT); + } +} + +static inline void +_cp_bra(struct nouveau_grctx *ctx, u32 mod, int flag, int state, int name) +{ + int ip = 0; + + if (mod != 2) { + ip = ctx->ctxprog_label[name] << CP_BRA_IP_SHIFT; + if (ip == 0) + ip = 0xff000000 | (name << CP_BRA_IP_SHIFT); + } + + cp_out(ctx, CP_BRA | (mod << 18) | ip | flag | + (state ? 0 : CP_BRA_IF_CLEAR)); +} +#define cp_bra(c, f, s, n) _cp_bra((c), 0, CP_FLAG_##f, CP_FLAG_##f##_##s, n) +#ifdef CP_BRA_MOD +#define cp_cal(c, f, s, n) _cp_bra((c), 1, CP_FLAG_##f, CP_FLAG_##f##_##s, n) +#define cp_ret(c, f, s) _cp_bra((c), 2, CP_FLAG_##f, CP_FLAG_##f##_##s, 0) +#endif + +static inline void +_cp_wait(struct nouveau_grctx *ctx, int flag, int state) +{ + cp_out(ctx, CP_WAIT | flag | (state ? CP_WAIT_SET : 0)); +} +#define cp_wait(c, f, s) _cp_wait((c), CP_FLAG_##f, CP_FLAG_##f##_##s) + +static inline void +_cp_set(struct nouveau_grctx *ctx, int flag, int state) +{ + cp_out(ctx, CP_SET | flag | (state ? CP_SET_1 : 0)); +} +#define cp_set(c, f, s) _cp_set((c), CP_FLAG_##f, CP_FLAG_##f##_##s) + +static inline void +cp_pos(struct nouveau_grctx *ctx, int offset) +{ + ctx->ctxvals_pos = offset; + ctx->ctxvals_base = ctx->ctxvals_pos; + + cp_lsr(ctx, ctx->ctxvals_pos); + cp_out(ctx, CP_SET_CONTEXT_POINTER); +} + +static inline void +gr_def(struct nouveau_grctx *ctx, uint32_t reg, uint32_t val) +{ + if (ctx->mode != NOUVEAU_GRCTX_VALS) + return; + + reg = (reg - 0x00400000) / 4; + reg = (reg - ctx->ctxprog_reg) + ctx->ctxvals_base; + + nv_wo32(ctx->data, reg * 4, val); +} +#endif + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_hdmi.c b/drivers/gpu/drm/nouveau/nouveau_hdmi.c new file mode 100644 index 00000000..c3de3638 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_hdmi.c @@ -0,0 +1,260 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_connector.h" +#include "nouveau_encoder.h" +#include "nouveau_crtc.h" + +static bool +hdmi_sor(struct drm_encoder *encoder) +{ + struct drm_nouveau_private *dev_priv = encoder->dev->dev_private; + if (dev_priv->chipset < 0xa3 || + dev_priv->chipset == 0xaa || + dev_priv->chipset == 0xac) + return false; + return true; +} + +static inline u32 +hdmi_base(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(nv_encoder->crtc); + if (!hdmi_sor(encoder)) + return 0x616500 + (nv_crtc->index * 0x800); + return 0x61c500 + (nv_encoder->or * 0x800); +} + +static void +hdmi_wr32(struct drm_encoder *encoder, u32 reg, u32 val) +{ + nv_wr32(encoder->dev, hdmi_base(encoder) + reg, val); +} + +static u32 +hdmi_rd32(struct drm_encoder *encoder, u32 reg) +{ + return nv_rd32(encoder->dev, hdmi_base(encoder) + reg); +} + +static u32 +hdmi_mask(struct drm_encoder *encoder, u32 reg, u32 mask, u32 val) +{ + u32 tmp = hdmi_rd32(encoder, reg); + hdmi_wr32(encoder, reg, (tmp & ~mask) | val); + return tmp; +} + +static void +nouveau_audio_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + u32 or = nv_encoder->or * 0x800; + + if (hdmi_sor(encoder)) { + nv_mask(dev, 0x61c448 + or, 0x00000003, 0x00000000); + } +} + +static void +nouveau_audio_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector; + struct drm_device *dev = encoder->dev; + u32 or = nv_encoder->or * 0x800; + int i; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (!drm_detect_monitor_audio(nv_connector->edid)) { + nouveau_audio_disconnect(encoder); + return; + } + + if (hdmi_sor(encoder)) { + nv_mask(dev, 0x61c448 + or, 0x00000001, 0x00000001); + + drm_edid_to_eld(&nv_connector->base, nv_connector->edid); + if (nv_connector->base.eld[0]) { + u8 *eld = nv_connector->base.eld; + for (i = 0; i < eld[2] * 4; i++) + nv_wr32(dev, 0x61c440 + or, (i << 8) | eld[i]); + for (i = eld[2] * 4; i < 0x60; i++) + nv_wr32(dev, 0x61c440 + or, (i << 8) | 0x00); + nv_mask(dev, 0x61c448 + or, 0x00000002, 0x00000002); + } + } +} + +static void +nouveau_hdmi_infoframe(struct drm_encoder *encoder, u32 ctrl, u8 *frame) +{ + /* calculate checksum for the infoframe */ + u8 sum = 0, i; + for (i = 0; i < frame[2]; i++) + sum += frame[i]; + frame[3] = 256 - sum; + + /* disable infoframe, and write header */ + hdmi_mask(encoder, ctrl + 0x00, 0x00000001, 0x00000000); + hdmi_wr32(encoder, ctrl + 0x08, *(u32 *)frame & 0xffffff); + + /* register scans tell me the audio infoframe has only one set of + * subpack regs, according to tegra (gee nvidia, it'd be nice if we + * could get those docs too!), the hdmi block pads out the rest of + * the packet on its own. + */ + if (ctrl == 0x020) + frame[2] = 6; + + /* write out checksum and data, weird weird 7 byte register pairs */ + for (i = 0; i < frame[2] + 1; i += 7) { + u32 rsubpack = ctrl + 0x0c + ((i / 7) * 8); + u32 *subpack = (u32 *)&frame[3 + i]; + hdmi_wr32(encoder, rsubpack + 0, subpack[0]); + hdmi_wr32(encoder, rsubpack + 4, subpack[1] & 0xffffff); + } + + /* enable the infoframe */ + hdmi_mask(encoder, ctrl, 0x00000001, 0x00000001); +} + +static void +nouveau_hdmi_video_infoframe(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + const u8 Y = 0, A = 0, B = 0, S = 0, C = 0, M = 0, R = 0; + const u8 ITC = 0, EC = 0, Q = 0, SC = 0, VIC = 0, PR = 0; + const u8 bar_top = 0, bar_bottom = 0, bar_left = 0, bar_right = 0; + u8 frame[20]; + + frame[0x00] = 0x82; /* AVI infoframe */ + frame[0x01] = 0x02; /* version */ + frame[0x02] = 0x0d; /* length */ + frame[0x03] = 0x00; + frame[0x04] = (Y << 5) | (A << 4) | (B << 2) | S; + frame[0x05] = (C << 6) | (M << 4) | R; + frame[0x06] = (ITC << 7) | (EC << 4) | (Q << 2) | SC; + frame[0x07] = VIC; + frame[0x08] = PR; + frame[0x09] = bar_top & 0xff; + frame[0x0a] = bar_top >> 8; + frame[0x0b] = bar_bottom & 0xff; + frame[0x0c] = bar_bottom >> 8; + frame[0x0d] = bar_left & 0xff; + frame[0x0e] = bar_left >> 8; + frame[0x0f] = bar_right & 0xff; + frame[0x10] = bar_right >> 8; + frame[0x11] = 0x00; + frame[0x12] = 0x00; + frame[0x13] = 0x00; + + nouveau_hdmi_infoframe(encoder, 0x020, frame); +} + +static void +nouveau_hdmi_audio_infoframe(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + const u8 CT = 0x00, CC = 0x01, ceaSS = 0x00, SF = 0x00, FMT = 0x00; + const u8 CA = 0x00, DM_INH = 0, LSV = 0x00; + u8 frame[12]; + + frame[0x00] = 0x84; /* Audio infoframe */ + frame[0x01] = 0x01; /* version */ + frame[0x02] = 0x0a; /* length */ + frame[0x03] = 0x00; + frame[0x04] = (CT << 4) | CC; + frame[0x05] = (SF << 2) | ceaSS; + frame[0x06] = FMT; + frame[0x07] = CA; + frame[0x08] = (DM_INH << 7) | (LSV << 3); + frame[0x09] = 0x00; + frame[0x0a] = 0x00; + frame[0x0b] = 0x00; + + nouveau_hdmi_infoframe(encoder, 0x000, frame); +} + +static void +nouveau_hdmi_disconnect(struct drm_encoder *encoder) +{ + nouveau_audio_disconnect(encoder); + + /* disable audio and avi infoframes */ + hdmi_mask(encoder, 0x000, 0x00000001, 0x00000000); + hdmi_mask(encoder, 0x020, 0x00000001, 0x00000000); + + /* disable hdmi */ + hdmi_mask(encoder, 0x0a4, 0x40000000, 0x00000000); +} + +void +nouveau_hdmi_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector; + struct drm_device *dev = encoder->dev; + u32 max_ac_packet, rekey; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (!mode || !nv_connector || !nv_connector->edid || + !drm_detect_hdmi_monitor(nv_connector->edid)) { + nouveau_hdmi_disconnect(encoder); + return; + } + + nouveau_hdmi_video_infoframe(encoder, mode); + nouveau_hdmi_audio_infoframe(encoder, mode); + + hdmi_mask(encoder, 0x0d0, 0x00070001, 0x00010001); /* SPARE, HW_CTS */ + hdmi_mask(encoder, 0x068, 0x00010101, 0x00000000); /* ACR_CTRL, ?? */ + hdmi_mask(encoder, 0x078, 0x80000000, 0x80000000); /* ACR_0441_ENABLE */ + + nv_mask(dev, 0x61733c, 0x00100000, 0x00100000); /* RESETF */ + nv_mask(dev, 0x61733c, 0x10000000, 0x10000000); /* LOOKUP_EN */ + nv_mask(dev, 0x61733c, 0x00100000, 0x00000000); /* !RESETF */ + + /* value matches nvidia binary driver, and tegra constant */ + rekey = 56; + + max_ac_packet = mode->htotal - mode->hdisplay; + max_ac_packet -= rekey; + max_ac_packet -= 18; /* constant from tegra */ + max_ac_packet /= 32; + + /* enable hdmi */ + hdmi_mask(encoder, 0x0a4, 0x5f1f003f, 0x40000000 | /* enable */ + 0x1f000000 | /* unknown */ + max_ac_packet << 16 | + rekey); + + nouveau_audio_mode_set(encoder, mode); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_hw.c b/drivers/gpu/drm/nouveau/nouveau_hw.c new file mode 100644 index 00000000..ba896e54 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_hw.c @@ -0,0 +1,1087 @@ +/* + * Copyright 2006 Dave Airlie + * Copyright 2007 Maarten Maathuis + * Copyright 2007-2009 Stuart Bennett + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" + +#define CHIPSET_NFORCE 0x01a0 +#define CHIPSET_NFORCE2 0x01f0 + +/* + * misc hw access wrappers/control functions + */ + +void +NVWriteVgaSeq(struct drm_device *dev, int head, uint8_t index, uint8_t value) +{ + NVWritePRMVIO(dev, head, NV_PRMVIO_SRX, index); + NVWritePRMVIO(dev, head, NV_PRMVIO_SR, value); +} + +uint8_t +NVReadVgaSeq(struct drm_device *dev, int head, uint8_t index) +{ + NVWritePRMVIO(dev, head, NV_PRMVIO_SRX, index); + return NVReadPRMVIO(dev, head, NV_PRMVIO_SR); +} + +void +NVWriteVgaGr(struct drm_device *dev, int head, uint8_t index, uint8_t value) +{ + NVWritePRMVIO(dev, head, NV_PRMVIO_GRX, index); + NVWritePRMVIO(dev, head, NV_PRMVIO_GX, value); +} + +uint8_t +NVReadVgaGr(struct drm_device *dev, int head, uint8_t index) +{ + NVWritePRMVIO(dev, head, NV_PRMVIO_GRX, index); + return NVReadPRMVIO(dev, head, NV_PRMVIO_GX); +} + +/* CR44 takes values 0 (head A), 3 (head B) and 4 (heads tied) + * it affects only the 8 bit vga io regs, which we access using mmio at + * 0xc{0,2}3c*, 0x60{1,3}3*, and 0x68{1,3}3d* + * in general, the set value of cr44 does not matter: reg access works as + * expected and values can be set for the appropriate head by using a 0x2000 + * offset as required + * however: + * a) pre nv40, the head B range of PRMVIO regs at 0xc23c* was not exposed and + * cr44 must be set to 0 or 3 for accessing values on the correct head + * through the common 0xc03c* addresses + * b) in tied mode (4) head B is programmed to the values set on head A, and + * access using the head B addresses can have strange results, ergo we leave + * tied mode in init once we know to what cr44 should be restored on exit + * + * the owner parameter is slightly abused: + * 0 and 1 are treated as head values and so the set value is (owner * 3) + * other values are treated as literal values to set + */ +void +NVSetOwner(struct drm_device *dev, int owner) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (owner == 1) + owner *= 3; + + if (dev_priv->chipset == 0x11) { + /* This might seem stupid, but the blob does it and + * omitting it often locks the system up. + */ + NVReadVgaCrtc(dev, 0, NV_CIO_SR_LOCK_INDEX); + NVReadVgaCrtc(dev, 1, NV_CIO_SR_LOCK_INDEX); + } + + /* CR44 is always changed on CRTC0 */ + NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_44, owner); + + if (dev_priv->chipset == 0x11) { /* set me harder */ + NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_2E, owner); + NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_2E, owner); + } +} + +void +NVBlankScreen(struct drm_device *dev, int head, bool blank) +{ + unsigned char seq1; + + if (nv_two_heads(dev)) + NVSetOwner(dev, head); + + seq1 = NVReadVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX); + + NVVgaSeqReset(dev, head, true); + if (blank) + NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 | 0x20); + else + NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 & ~0x20); + NVVgaSeqReset(dev, head, false); +} + +/* + * PLL setting + */ + +static int +powerctrl_1_shift(int chip_version, int reg) +{ + int shift = -4; + + if (chip_version < 0x17 || chip_version == 0x1a || chip_version == 0x20) + return shift; + + switch (reg) { + case NV_RAMDAC_VPLL2: + shift += 4; + case NV_PRAMDAC_VPLL_COEFF: + shift += 4; + case NV_PRAMDAC_MPLL_COEFF: + shift += 4; + case NV_PRAMDAC_NVPLL_COEFF: + shift += 4; + } + + /* + * the shift for vpll regs is only used for nv3x chips with a single + * stage pll + */ + if (shift > 4 && (chip_version < 0x32 || chip_version == 0x35 || + chip_version == 0x36 || chip_version >= 0x40)) + shift = -4; + + return shift; +} + +static void +setPLL_single(struct drm_device *dev, uint32_t reg, struct nouveau_pll_vals *pv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chip_version = dev_priv->vbios.chip_version; + uint32_t oldpll = NVReadRAMDAC(dev, 0, reg); + int oldN = (oldpll >> 8) & 0xff, oldM = oldpll & 0xff; + uint32_t pll = (oldpll & 0xfff80000) | pv->log2P << 16 | pv->NM1; + uint32_t saved_powerctrl_1 = 0; + int shift_powerctrl_1 = powerctrl_1_shift(chip_version, reg); + + if (oldpll == pll) + return; /* already set */ + + if (shift_powerctrl_1 >= 0) { + saved_powerctrl_1 = nvReadMC(dev, NV_PBUS_POWERCTRL_1); + nvWriteMC(dev, NV_PBUS_POWERCTRL_1, + (saved_powerctrl_1 & ~(0xf << shift_powerctrl_1)) | + 1 << shift_powerctrl_1); + } + + if (oldM && pv->M1 && (oldN / oldM < pv->N1 / pv->M1)) + /* upclock -- write new post divider first */ + NVWriteRAMDAC(dev, 0, reg, pv->log2P << 16 | (oldpll & 0xffff)); + else + /* downclock -- write new NM first */ + NVWriteRAMDAC(dev, 0, reg, (oldpll & 0xffff0000) | pv->NM1); + + if (chip_version < 0x17 && chip_version != 0x11) + /* wait a bit on older chips */ + msleep(64); + NVReadRAMDAC(dev, 0, reg); + + /* then write the other half as well */ + NVWriteRAMDAC(dev, 0, reg, pll); + + if (shift_powerctrl_1 >= 0) + nvWriteMC(dev, NV_PBUS_POWERCTRL_1, saved_powerctrl_1); +} + +static uint32_t +new_ramdac580(uint32_t reg1, bool ss, uint32_t ramdac580) +{ + bool head_a = (reg1 == NV_PRAMDAC_VPLL_COEFF); + + if (ss) /* single stage pll mode */ + ramdac580 |= head_a ? NV_RAMDAC_580_VPLL1_ACTIVE : + NV_RAMDAC_580_VPLL2_ACTIVE; + else + ramdac580 &= head_a ? ~NV_RAMDAC_580_VPLL1_ACTIVE : + ~NV_RAMDAC_580_VPLL2_ACTIVE; + + return ramdac580; +} + +static void +setPLL_double_highregs(struct drm_device *dev, uint32_t reg1, + struct nouveau_pll_vals *pv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chip_version = dev_priv->vbios.chip_version; + bool nv3035 = chip_version == 0x30 || chip_version == 0x35; + uint32_t reg2 = reg1 + ((reg1 == NV_RAMDAC_VPLL2) ? 0x5c : 0x70); + uint32_t oldpll1 = NVReadRAMDAC(dev, 0, reg1); + uint32_t oldpll2 = !nv3035 ? NVReadRAMDAC(dev, 0, reg2) : 0; + uint32_t pll1 = (oldpll1 & 0xfff80000) | pv->log2P << 16 | pv->NM1; + uint32_t pll2 = (oldpll2 & 0x7fff0000) | 1 << 31 | pv->NM2; + uint32_t oldramdac580 = 0, ramdac580 = 0; + bool single_stage = !pv->NM2 || pv->N2 == pv->M2; /* nv41+ only */ + uint32_t saved_powerctrl_1 = 0, savedc040 = 0; + int shift_powerctrl_1 = powerctrl_1_shift(chip_version, reg1); + + /* model specific additions to generic pll1 and pll2 set up above */ + if (nv3035) { + pll1 = (pll1 & 0xfcc7ffff) | (pv->N2 & 0x18) << 21 | + (pv->N2 & 0x7) << 19 | 8 << 4 | (pv->M2 & 7) << 4; + pll2 = 0; + } + if (chip_version > 0x40 && reg1 >= NV_PRAMDAC_VPLL_COEFF) { /* !nv40 */ + oldramdac580 = NVReadRAMDAC(dev, 0, NV_PRAMDAC_580); + ramdac580 = new_ramdac580(reg1, single_stage, oldramdac580); + if (oldramdac580 != ramdac580) + oldpll1 = ~0; /* force mismatch */ + if (single_stage) + /* magic value used by nvidia in single stage mode */ + pll2 |= 0x011f; + } + if (chip_version > 0x70) + /* magic bits set by the blob (but not the bios) on g71-73 */ + pll1 = (pll1 & 0x7fffffff) | (single_stage ? 0x4 : 0xc) << 28; + + if (oldpll1 == pll1 && oldpll2 == pll2) + return; /* already set */ + + if (shift_powerctrl_1 >= 0) { + saved_powerctrl_1 = nvReadMC(dev, NV_PBUS_POWERCTRL_1); + nvWriteMC(dev, NV_PBUS_POWERCTRL_1, + (saved_powerctrl_1 & ~(0xf << shift_powerctrl_1)) | + 1 << shift_powerctrl_1); + } + + if (chip_version >= 0x40) { + int shift_c040 = 14; + + switch (reg1) { + case NV_PRAMDAC_MPLL_COEFF: + shift_c040 += 2; + case NV_PRAMDAC_NVPLL_COEFF: + shift_c040 += 2; + case NV_RAMDAC_VPLL2: + shift_c040 += 2; + case NV_PRAMDAC_VPLL_COEFF: + shift_c040 += 2; + } + + savedc040 = nvReadMC(dev, 0xc040); + if (shift_c040 != 14) + nvWriteMC(dev, 0xc040, savedc040 & ~(3 << shift_c040)); + } + + if (oldramdac580 != ramdac580) + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_580, ramdac580); + + if (!nv3035) + NVWriteRAMDAC(dev, 0, reg2, pll2); + NVWriteRAMDAC(dev, 0, reg1, pll1); + + if (shift_powerctrl_1 >= 0) + nvWriteMC(dev, NV_PBUS_POWERCTRL_1, saved_powerctrl_1); + if (chip_version >= 0x40) + nvWriteMC(dev, 0xc040, savedc040); +} + +static void +setPLL_double_lowregs(struct drm_device *dev, uint32_t NMNMreg, + struct nouveau_pll_vals *pv) +{ + /* When setting PLLs, there is a merry game of disabling and enabling + * various bits of hardware during the process. This function is a + * synthesis of six nv4x traces, nearly each card doing a subtly + * different thing. With luck all the necessary bits for each card are + * combined herein. Without luck it deviates from each card's formula + * so as to not work on any :) + */ + + uint32_t Preg = NMNMreg - 4; + bool mpll = Preg == 0x4020; + uint32_t oldPval = nvReadMC(dev, Preg); + uint32_t NMNM = pv->NM2 << 16 | pv->NM1; + uint32_t Pval = (oldPval & (mpll ? ~(0x77 << 16) : ~(7 << 16))) | + 0xc << 28 | pv->log2P << 16; + uint32_t saved4600 = 0; + /* some cards have different maskc040s */ + uint32_t maskc040 = ~(3 << 14), savedc040; + bool single_stage = !pv->NM2 || pv->N2 == pv->M2; + + if (nvReadMC(dev, NMNMreg) == NMNM && (oldPval & 0xc0070000) == Pval) + return; + + if (Preg == 0x4000) + maskc040 = ~0x333; + if (Preg == 0x4058) + maskc040 = ~(0xc << 24); + + if (mpll) { + struct pll_lims pll_lim; + uint8_t Pval2; + + if (get_pll_limits(dev, Preg, &pll_lim)) + return; + + Pval2 = pv->log2P + pll_lim.log2p_bias; + if (Pval2 > pll_lim.max_log2p) + Pval2 = pll_lim.max_log2p; + Pval |= 1 << 28 | Pval2 << 20; + + saved4600 = nvReadMC(dev, 0x4600); + nvWriteMC(dev, 0x4600, saved4600 | 8 << 28); + } + if (single_stage) + Pval |= mpll ? 1 << 12 : 1 << 8; + + nvWriteMC(dev, Preg, oldPval | 1 << 28); + nvWriteMC(dev, Preg, Pval & ~(4 << 28)); + if (mpll) { + Pval |= 8 << 20; + nvWriteMC(dev, 0x4020, Pval & ~(0xc << 28)); + nvWriteMC(dev, 0x4038, Pval & ~(0xc << 28)); + } + + savedc040 = nvReadMC(dev, 0xc040); + nvWriteMC(dev, 0xc040, savedc040 & maskc040); + + nvWriteMC(dev, NMNMreg, NMNM); + if (NMNMreg == 0x4024) + nvWriteMC(dev, 0x403c, NMNM); + + nvWriteMC(dev, Preg, Pval); + if (mpll) { + Pval &= ~(8 << 20); + nvWriteMC(dev, 0x4020, Pval); + nvWriteMC(dev, 0x4038, Pval); + nvWriteMC(dev, 0x4600, saved4600); + } + + nvWriteMC(dev, 0xc040, savedc040); + + if (mpll) { + nvWriteMC(dev, 0x4020, Pval & ~(1 << 28)); + nvWriteMC(dev, 0x4038, Pval & ~(1 << 28)); + } +} + +void +nouveau_hw_setpll(struct drm_device *dev, uint32_t reg1, + struct nouveau_pll_vals *pv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int cv = dev_priv->vbios.chip_version; + + if (cv == 0x30 || cv == 0x31 || cv == 0x35 || cv == 0x36 || + cv >= 0x40) { + if (reg1 > 0x405c) + setPLL_double_highregs(dev, reg1, pv); + else + setPLL_double_lowregs(dev, reg1, pv); + } else + setPLL_single(dev, reg1, pv); +} + +/* + * PLL getting + */ + +static void +nouveau_hw_decode_pll(struct drm_device *dev, uint32_t reg1, uint32_t pll1, + uint32_t pll2, struct nouveau_pll_vals *pllvals) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* to force parsing as single stage (i.e. nv40 vplls) pass pll2 as 0 */ + + /* log2P is & 0x7 as never more than 7, and nv30/35 only uses 3 bits */ + pllvals->log2P = (pll1 >> 16) & 0x7; + pllvals->N2 = pllvals->M2 = 1; + + if (reg1 <= 0x405c) { + pllvals->NM1 = pll2 & 0xffff; + /* single stage NVPLL and VPLLs use 1 << 8, MPLL uses 1 << 12 */ + if (!(pll1 & 0x1100)) + pllvals->NM2 = pll2 >> 16; + } else { + pllvals->NM1 = pll1 & 0xffff; + if (nv_two_reg_pll(dev) && pll2 & NV31_RAMDAC_ENABLE_VCO2) + pllvals->NM2 = pll2 & 0xffff; + else if (dev_priv->chipset == 0x30 || dev_priv->chipset == 0x35) { + pllvals->M1 &= 0xf; /* only 4 bits */ + if (pll1 & NV30_RAMDAC_ENABLE_VCO2) { + pllvals->M2 = (pll1 >> 4) & 0x7; + pllvals->N2 = ((pll1 >> 21) & 0x18) | + ((pll1 >> 19) & 0x7); + } + } + } +} + +int +nouveau_hw_get_pllvals(struct drm_device *dev, enum pll_types plltype, + struct nouveau_pll_vals *pllvals) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t reg1 = get_pll_register(dev, plltype), pll1, pll2 = 0; + struct pll_lims pll_lim; + int ret; + + if (reg1 == 0) + return -ENOENT; + + pll1 = nvReadMC(dev, reg1); + + if (reg1 <= 0x405c) + pll2 = nvReadMC(dev, reg1 + 4); + else if (nv_two_reg_pll(dev)) { + uint32_t reg2 = reg1 + (reg1 == NV_RAMDAC_VPLL2 ? 0x5c : 0x70); + + pll2 = nvReadMC(dev, reg2); + } + + if (dev_priv->card_type == 0x40 && reg1 >= NV_PRAMDAC_VPLL_COEFF) { + uint32_t ramdac580 = NVReadRAMDAC(dev, 0, NV_PRAMDAC_580); + + /* check whether vpll has been forced into single stage mode */ + if (reg1 == NV_PRAMDAC_VPLL_COEFF) { + if (ramdac580 & NV_RAMDAC_580_VPLL1_ACTIVE) + pll2 = 0; + } else + if (ramdac580 & NV_RAMDAC_580_VPLL2_ACTIVE) + pll2 = 0; + } + + nouveau_hw_decode_pll(dev, reg1, pll1, pll2, pllvals); + + ret = get_pll_limits(dev, plltype, &pll_lim); + if (ret) + return ret; + + pllvals->refclk = pll_lim.refclk; + + return 0; +} + +int +nouveau_hw_pllvals_to_clk(struct nouveau_pll_vals *pv) +{ + /* Avoid divide by zero if called at an inappropriate time */ + if (!pv->M1 || !pv->M2) + return 0; + + return pv->N1 * pv->N2 * pv->refclk / (pv->M1 * pv->M2) >> pv->log2P; +} + +int +nouveau_hw_get_clock(struct drm_device *dev, enum pll_types plltype) +{ + struct nouveau_pll_vals pllvals; + int ret; + + if (plltype == PLL_MEMORY && + (dev->pci_device & 0x0ff0) == CHIPSET_NFORCE) { + uint32_t mpllP; + + pci_read_config_dword(pci_get_bus_and_slot(0, 3), 0x6c, &mpllP); + if (!mpllP) + mpllP = 4; + + return 400000 / mpllP; + } else + if (plltype == PLL_MEMORY && + (dev->pci_device & 0xff0) == CHIPSET_NFORCE2) { + uint32_t clock; + + pci_read_config_dword(pci_get_bus_and_slot(0, 5), 0x4c, &clock); + return clock; + } + + ret = nouveau_hw_get_pllvals(dev, plltype, &pllvals); + if (ret) + return ret; + + return nouveau_hw_pllvals_to_clk(&pllvals); +} + +static void +nouveau_hw_fix_bad_vpll(struct drm_device *dev, int head) +{ + /* the vpll on an unused head can come up with a random value, way + * beyond the pll limits. for some reason this causes the chip to + * lock up when reading the dac palette regs, so set a valid pll here + * when such a condition detected. only seen on nv11 to date + */ + + struct pll_lims pll_lim; + struct nouveau_pll_vals pv; + enum pll_types pll = head ? PLL_VPLL1 : PLL_VPLL0; + + if (get_pll_limits(dev, pll, &pll_lim)) + return; + nouveau_hw_get_pllvals(dev, pll, &pv); + + if (pv.M1 >= pll_lim.vco1.min_m && pv.M1 <= pll_lim.vco1.max_m && + pv.N1 >= pll_lim.vco1.min_n && pv.N1 <= pll_lim.vco1.max_n && + pv.log2P <= pll_lim.max_log2p) + return; + + NV_WARN(dev, "VPLL %d outwith limits, attempting to fix\n", head + 1); + + /* set lowest clock within static limits */ + pv.M1 = pll_lim.vco1.max_m; + pv.N1 = pll_lim.vco1.min_n; + pv.log2P = pll_lim.max_usable_log2p; + nouveau_hw_setpll(dev, pll_lim.reg, &pv); +} + +/* + * vga font save/restore + */ + +static void nouveau_vga_font_io(struct drm_device *dev, + void __iomem *iovram, + bool save, unsigned plane) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned i; + + NVWriteVgaSeq(dev, 0, NV_VIO_SR_PLANE_MASK_INDEX, 1 << plane); + NVWriteVgaGr(dev, 0, NV_VIO_GX_READ_MAP_INDEX, plane); + for (i = 0; i < 16384; i++) { + if (save) { + dev_priv->saved_vga_font[plane][i] = + ioread32_native(iovram + i * 4); + } else { + iowrite32_native(dev_priv->saved_vga_font[plane][i], + iovram + i * 4); + } + } +} + +void +nouveau_hw_save_vga_fonts(struct drm_device *dev, bool save) +{ + uint8_t misc, gr4, gr5, gr6, seq2, seq4; + bool graphicsmode; + unsigned plane; + void __iomem *iovram; + + if (nv_two_heads(dev)) + NVSetOwner(dev, 0); + + NVSetEnablePalette(dev, 0, true); + graphicsmode = NVReadVgaAttr(dev, 0, NV_CIO_AR_MODE_INDEX) & 1; + NVSetEnablePalette(dev, 0, false); + + if (graphicsmode) /* graphics mode => framebuffer => no need to save */ + return; + + NV_INFO(dev, "%sing VGA fonts\n", save ? "Sav" : "Restor"); + + /* map first 64KiB of VRAM, holds VGA fonts etc */ + iovram = ioremap(pci_resource_start(dev->pdev, 1), 65536); + if (!iovram) { + NV_ERROR(dev, "Failed to map VRAM, " + "cannot save/restore VGA fonts.\n"); + return; + } + + if (nv_two_heads(dev)) + NVBlankScreen(dev, 1, true); + NVBlankScreen(dev, 0, true); + + /* save control regs */ + misc = NVReadPRMVIO(dev, 0, NV_PRMVIO_MISC__READ); + seq2 = NVReadVgaSeq(dev, 0, NV_VIO_SR_PLANE_MASK_INDEX); + seq4 = NVReadVgaSeq(dev, 0, NV_VIO_SR_MEM_MODE_INDEX); + gr4 = NVReadVgaGr(dev, 0, NV_VIO_GX_READ_MAP_INDEX); + gr5 = NVReadVgaGr(dev, 0, NV_VIO_GX_MODE_INDEX); + gr6 = NVReadVgaGr(dev, 0, NV_VIO_GX_MISC_INDEX); + + NVWritePRMVIO(dev, 0, NV_PRMVIO_MISC__WRITE, 0x67); + NVWriteVgaSeq(dev, 0, NV_VIO_SR_MEM_MODE_INDEX, 0x6); + NVWriteVgaGr(dev, 0, NV_VIO_GX_MODE_INDEX, 0x0); + NVWriteVgaGr(dev, 0, NV_VIO_GX_MISC_INDEX, 0x5); + + /* store font in planes 0..3 */ + for (plane = 0; plane < 4; plane++) + nouveau_vga_font_io(dev, iovram, save, plane); + + /* restore control regs */ + NVWritePRMVIO(dev, 0, NV_PRMVIO_MISC__WRITE, misc); + NVWriteVgaGr(dev, 0, NV_VIO_GX_READ_MAP_INDEX, gr4); + NVWriteVgaGr(dev, 0, NV_VIO_GX_MODE_INDEX, gr5); + NVWriteVgaGr(dev, 0, NV_VIO_GX_MISC_INDEX, gr6); + NVWriteVgaSeq(dev, 0, NV_VIO_SR_PLANE_MASK_INDEX, seq2); + NVWriteVgaSeq(dev, 0, NV_VIO_SR_MEM_MODE_INDEX, seq4); + + if (nv_two_heads(dev)) + NVBlankScreen(dev, 1, false); + NVBlankScreen(dev, 0, false); + + iounmap(iovram); +} + +/* + * mode state save/load + */ + +static void +rd_cio_state(struct drm_device *dev, int head, + struct nv04_crtc_reg *crtcstate, int index) +{ + crtcstate->CRTC[index] = NVReadVgaCrtc(dev, head, index); +} + +static void +wr_cio_state(struct drm_device *dev, int head, + struct nv04_crtc_reg *crtcstate, int index) +{ + NVWriteVgaCrtc(dev, head, index, crtcstate->CRTC[index]); +} + +static void +nv_save_state_ramdac(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *regp = &state->crtc_reg[head]; + int i; + + if (dev_priv->card_type >= NV_10) + regp->nv10_cursync = NVReadRAMDAC(dev, head, NV_RAMDAC_NV10_CURSYNC); + + nouveau_hw_get_pllvals(dev, head ? PLL_VPLL1 : PLL_VPLL0, ®p->pllvals); + state->pllsel = NVReadRAMDAC(dev, 0, NV_PRAMDAC_PLL_COEFF_SELECT); + if (nv_two_heads(dev)) + state->sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK); + if (dev_priv->chipset == 0x11) + regp->dither = NVReadRAMDAC(dev, head, NV_RAMDAC_DITHER_NV11); + + regp->ramdac_gen_ctrl = NVReadRAMDAC(dev, head, NV_PRAMDAC_GENERAL_CONTROL); + + if (nv_gf4_disp_arch(dev)) + regp->ramdac_630 = NVReadRAMDAC(dev, head, NV_PRAMDAC_630); + if (dev_priv->chipset >= 0x30) + regp->ramdac_634 = NVReadRAMDAC(dev, head, NV_PRAMDAC_634); + + regp->tv_setup = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_SETUP); + regp->tv_vtotal = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_VTOTAL); + regp->tv_vskew = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_VSKEW); + regp->tv_vsync_delay = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_VSYNC_DELAY); + regp->tv_htotal = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HTOTAL); + regp->tv_hskew = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HSKEW); + regp->tv_hsync_delay = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY); + regp->tv_hsync_delay2 = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY2); + + for (i = 0; i < 7; i++) { + uint32_t ramdac_reg = NV_PRAMDAC_FP_VDISPLAY_END + (i * 4); + regp->fp_vert_regs[i] = NVReadRAMDAC(dev, head, ramdac_reg); + regp->fp_horiz_regs[i] = NVReadRAMDAC(dev, head, ramdac_reg + 0x20); + } + + if (nv_gf4_disp_arch(dev)) { + regp->dither = NVReadRAMDAC(dev, head, NV_RAMDAC_FP_DITHER); + for (i = 0; i < 3; i++) { + regp->dither_regs[i] = NVReadRAMDAC(dev, head, NV_PRAMDAC_850 + i * 4); + regp->dither_regs[i + 3] = NVReadRAMDAC(dev, head, NV_PRAMDAC_85C + i * 4); + } + } + + regp->fp_control = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL); + regp->fp_debug_0 = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_0); + if (!nv_gf4_disp_arch(dev) && head == 0) { + /* early chips don't allow access to PRAMDAC_TMDS_* without + * the head A FPCLK on (nv11 even locks up) */ + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_FP_DEBUG_0, regp->fp_debug_0 & + ~NV_PRAMDAC_FP_DEBUG_0_PWRDOWN_FPCLK); + } + regp->fp_debug_1 = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_1); + regp->fp_debug_2 = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_2); + + regp->fp_margin_color = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_MARGIN_COLOR); + + if (nv_gf4_disp_arch(dev)) + regp->ramdac_8c0 = NVReadRAMDAC(dev, head, NV_PRAMDAC_8C0); + + if (dev_priv->card_type == NV_40) { + regp->ramdac_a20 = NVReadRAMDAC(dev, head, NV_PRAMDAC_A20); + regp->ramdac_a24 = NVReadRAMDAC(dev, head, NV_PRAMDAC_A24); + regp->ramdac_a34 = NVReadRAMDAC(dev, head, NV_PRAMDAC_A34); + + for (i = 0; i < 38; i++) + regp->ctv_regs[i] = NVReadRAMDAC(dev, head, + NV_PRAMDAC_CTV + 4*i); + } +} + +static void +nv_load_state_ramdac(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *regp = &state->crtc_reg[head]; + uint32_t pllreg = head ? NV_RAMDAC_VPLL2 : NV_PRAMDAC_VPLL_COEFF; + int i; + + if (dev_priv->card_type >= NV_10) + NVWriteRAMDAC(dev, head, NV_RAMDAC_NV10_CURSYNC, regp->nv10_cursync); + + nouveau_hw_setpll(dev, pllreg, ®p->pllvals); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_PLL_COEFF_SELECT, state->pllsel); + if (nv_two_heads(dev)) + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, state->sel_clk); + if (dev_priv->chipset == 0x11) + NVWriteRAMDAC(dev, head, NV_RAMDAC_DITHER_NV11, regp->dither); + + NVWriteRAMDAC(dev, head, NV_PRAMDAC_GENERAL_CONTROL, regp->ramdac_gen_ctrl); + + if (nv_gf4_disp_arch(dev)) + NVWriteRAMDAC(dev, head, NV_PRAMDAC_630, regp->ramdac_630); + if (dev_priv->chipset >= 0x30) + NVWriteRAMDAC(dev, head, NV_PRAMDAC_634, regp->ramdac_634); + + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_SETUP, regp->tv_setup); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_VTOTAL, regp->tv_vtotal); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_VSKEW, regp->tv_vskew); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_VSYNC_DELAY, regp->tv_vsync_delay); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HTOTAL, regp->tv_htotal); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HSKEW, regp->tv_hskew); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY, regp->tv_hsync_delay); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY2, regp->tv_hsync_delay2); + + for (i = 0; i < 7; i++) { + uint32_t ramdac_reg = NV_PRAMDAC_FP_VDISPLAY_END + (i * 4); + + NVWriteRAMDAC(dev, head, ramdac_reg, regp->fp_vert_regs[i]); + NVWriteRAMDAC(dev, head, ramdac_reg + 0x20, regp->fp_horiz_regs[i]); + } + + if (nv_gf4_disp_arch(dev)) { + NVWriteRAMDAC(dev, head, NV_RAMDAC_FP_DITHER, regp->dither); + for (i = 0; i < 3; i++) { + NVWriteRAMDAC(dev, head, NV_PRAMDAC_850 + i * 4, regp->dither_regs[i]); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_85C + i * 4, regp->dither_regs[i + 3]); + } + } + + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, regp->fp_control); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_0, regp->fp_debug_0); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_1, regp->fp_debug_1); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_2, regp->fp_debug_2); + + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_MARGIN_COLOR, regp->fp_margin_color); + + if (nv_gf4_disp_arch(dev)) + NVWriteRAMDAC(dev, head, NV_PRAMDAC_8C0, regp->ramdac_8c0); + + if (dev_priv->card_type == NV_40) { + NVWriteRAMDAC(dev, head, NV_PRAMDAC_A20, regp->ramdac_a20); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_A24, regp->ramdac_a24); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_A34, regp->ramdac_a34); + + for (i = 0; i < 38; i++) + NVWriteRAMDAC(dev, head, + NV_PRAMDAC_CTV + 4*i, regp->ctv_regs[i]); + } +} + +static void +nv_save_state_vga(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + struct nv04_crtc_reg *regp = &state->crtc_reg[head]; + int i; + + regp->MiscOutReg = NVReadPRMVIO(dev, head, NV_PRMVIO_MISC__READ); + + for (i = 0; i < 25; i++) + rd_cio_state(dev, head, regp, i); + + NVSetEnablePalette(dev, head, true); + for (i = 0; i < 21; i++) + regp->Attribute[i] = NVReadVgaAttr(dev, head, i); + NVSetEnablePalette(dev, head, false); + + for (i = 0; i < 9; i++) + regp->Graphics[i] = NVReadVgaGr(dev, head, i); + + for (i = 0; i < 5; i++) + regp->Sequencer[i] = NVReadVgaSeq(dev, head, i); +} + +static void +nv_load_state_vga(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + struct nv04_crtc_reg *regp = &state->crtc_reg[head]; + int i; + + NVWritePRMVIO(dev, head, NV_PRMVIO_MISC__WRITE, regp->MiscOutReg); + + for (i = 0; i < 5; i++) + NVWriteVgaSeq(dev, head, i, regp->Sequencer[i]); + + nv_lock_vga_crtc_base(dev, head, false); + for (i = 0; i < 25; i++) + wr_cio_state(dev, head, regp, i); + nv_lock_vga_crtc_base(dev, head, true); + + for (i = 0; i < 9; i++) + NVWriteVgaGr(dev, head, i, regp->Graphics[i]); + + NVSetEnablePalette(dev, head, true); + for (i = 0; i < 21; i++) + NVWriteVgaAttr(dev, head, i, regp->Attribute[i]); + NVSetEnablePalette(dev, head, false); +} + +static void +nv_save_state_ext(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *regp = &state->crtc_reg[head]; + int i; + + rd_cio_state(dev, head, regp, NV_CIO_CRE_LCD__INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_RPC0_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_RPC1_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_LSR_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_PIXEL_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_HEB__INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_ENH_INDEX); + + rd_cio_state(dev, head, regp, NV_CIO_CRE_FF_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_FFLWM__INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_21); + + if (dev_priv->card_type >= NV_20) + rd_cio_state(dev, head, regp, NV_CIO_CRE_47); + + if (dev_priv->card_type >= NV_30) + rd_cio_state(dev, head, regp, 0x9f); + + rd_cio_state(dev, head, regp, NV_CIO_CRE_49); + rd_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR0_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR1_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR2_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_ILACE__INDEX); + + if (dev_priv->card_type >= NV_10) { + regp->crtc_830 = NVReadCRTC(dev, head, NV_PCRTC_830); + regp->crtc_834 = NVReadCRTC(dev, head, NV_PCRTC_834); + + if (dev_priv->card_type >= NV_30) + regp->gpio_ext = NVReadCRTC(dev, head, NV_PCRTC_GPIO_EXT); + + if (dev_priv->card_type == NV_40) + regp->crtc_850 = NVReadCRTC(dev, head, NV_PCRTC_850); + + if (nv_two_heads(dev)) + regp->crtc_eng_ctrl = NVReadCRTC(dev, head, NV_PCRTC_ENGINE_CTRL); + regp->cursor_cfg = NVReadCRTC(dev, head, NV_PCRTC_CURSOR_CONFIG); + } + + regp->crtc_cfg = NVReadCRTC(dev, head, NV_PCRTC_CONFIG); + + rd_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH3__INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH4__INDEX); + if (dev_priv->card_type >= NV_10) { + rd_cio_state(dev, head, regp, NV_CIO_CRE_EBR_INDEX); + rd_cio_state(dev, head, regp, NV_CIO_CRE_CSB); + rd_cio_state(dev, head, regp, NV_CIO_CRE_4B); + rd_cio_state(dev, head, regp, NV_CIO_CRE_TVOUT_LATENCY); + } + /* NV11 and NV20 don't have this, they stop at 0x52. */ + if (nv_gf4_disp_arch(dev)) { + rd_cio_state(dev, head, regp, NV_CIO_CRE_42); + rd_cio_state(dev, head, regp, NV_CIO_CRE_53); + rd_cio_state(dev, head, regp, NV_CIO_CRE_54); + + for (i = 0; i < 0x10; i++) + regp->CR58[i] = NVReadVgaCrtc5758(dev, head, i); + rd_cio_state(dev, head, regp, NV_CIO_CRE_59); + rd_cio_state(dev, head, regp, NV_CIO_CRE_5B); + + rd_cio_state(dev, head, regp, NV_CIO_CRE_85); + rd_cio_state(dev, head, regp, NV_CIO_CRE_86); + } + + regp->fb_start = NVReadCRTC(dev, head, NV_PCRTC_START); +} + +static void +nv_load_state_ext(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *regp = &state->crtc_reg[head]; + uint32_t reg900; + int i; + + if (dev_priv->card_type >= NV_10) { + if (nv_two_heads(dev)) + /* setting ENGINE_CTRL (EC) *must* come before + * CIO_CRE_LCD, as writing CRE_LCD sets bits 16 & 17 in + * EC that should not be overwritten by writing stale EC + */ + NVWriteCRTC(dev, head, NV_PCRTC_ENGINE_CTRL, regp->crtc_eng_ctrl); + + nvWriteVIDEO(dev, NV_PVIDEO_STOP, 1); + nvWriteVIDEO(dev, NV_PVIDEO_INTR_EN, 0); + nvWriteVIDEO(dev, NV_PVIDEO_OFFSET_BUFF(0), 0); + nvWriteVIDEO(dev, NV_PVIDEO_OFFSET_BUFF(1), 0); + nvWriteVIDEO(dev, NV_PVIDEO_LIMIT(0), dev_priv->fb_available_size - 1); + nvWriteVIDEO(dev, NV_PVIDEO_LIMIT(1), dev_priv->fb_available_size - 1); + nvWriteVIDEO(dev, NV_PVIDEO_UVPLANE_LIMIT(0), dev_priv->fb_available_size - 1); + nvWriteVIDEO(dev, NV_PVIDEO_UVPLANE_LIMIT(1), dev_priv->fb_available_size - 1); + nvWriteMC(dev, NV_PBUS_POWERCTRL_2, 0); + + NVWriteCRTC(dev, head, NV_PCRTC_CURSOR_CONFIG, regp->cursor_cfg); + NVWriteCRTC(dev, head, NV_PCRTC_830, regp->crtc_830); + NVWriteCRTC(dev, head, NV_PCRTC_834, regp->crtc_834); + + if (dev_priv->card_type >= NV_30) + NVWriteCRTC(dev, head, NV_PCRTC_GPIO_EXT, regp->gpio_ext); + + if (dev_priv->card_type == NV_40) { + NVWriteCRTC(dev, head, NV_PCRTC_850, regp->crtc_850); + + reg900 = NVReadRAMDAC(dev, head, NV_PRAMDAC_900); + if (regp->crtc_cfg == NV10_PCRTC_CONFIG_START_ADDRESS_HSYNC) + NVWriteRAMDAC(dev, head, NV_PRAMDAC_900, reg900 | 0x10000); + else + NVWriteRAMDAC(dev, head, NV_PRAMDAC_900, reg900 & ~0x10000); + } + } + + NVWriteCRTC(dev, head, NV_PCRTC_CONFIG, regp->crtc_cfg); + + wr_cio_state(dev, head, regp, NV_CIO_CRE_RPC0_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_RPC1_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_LSR_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_PIXEL_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_LCD__INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_HEB__INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_ENH_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_FF_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_FFLWM__INDEX); + + if (dev_priv->card_type >= NV_20) + wr_cio_state(dev, head, regp, NV_CIO_CRE_47); + + if (dev_priv->card_type >= NV_30) + wr_cio_state(dev, head, regp, 0x9f); + + wr_cio_state(dev, head, regp, NV_CIO_CRE_49); + wr_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR0_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR1_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR2_INDEX); + if (dev_priv->card_type == NV_40) + nv_fix_nv40_hw_cursor(dev, head); + wr_cio_state(dev, head, regp, NV_CIO_CRE_ILACE__INDEX); + + wr_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH3__INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH4__INDEX); + if (dev_priv->card_type >= NV_10) { + wr_cio_state(dev, head, regp, NV_CIO_CRE_EBR_INDEX); + wr_cio_state(dev, head, regp, NV_CIO_CRE_CSB); + wr_cio_state(dev, head, regp, NV_CIO_CRE_4B); + wr_cio_state(dev, head, regp, NV_CIO_CRE_TVOUT_LATENCY); + } + /* NV11 and NV20 stop at 0x52. */ + if (nv_gf4_disp_arch(dev)) { + if (dev_priv->card_type == NV_10) { + /* Not waiting for vertical retrace before modifying + CRE_53/CRE_54 causes lockups. */ + nouveau_wait_eq(dev, 650000000, NV_PRMCIO_INP0__COLOR, 0x8, 0x8); + nouveau_wait_eq(dev, 650000000, NV_PRMCIO_INP0__COLOR, 0x8, 0x0); + } + + wr_cio_state(dev, head, regp, NV_CIO_CRE_42); + wr_cio_state(dev, head, regp, NV_CIO_CRE_53); + wr_cio_state(dev, head, regp, NV_CIO_CRE_54); + + for (i = 0; i < 0x10; i++) + NVWriteVgaCrtc5758(dev, head, i, regp->CR58[i]); + wr_cio_state(dev, head, regp, NV_CIO_CRE_59); + wr_cio_state(dev, head, regp, NV_CIO_CRE_5B); + + wr_cio_state(dev, head, regp, NV_CIO_CRE_85); + wr_cio_state(dev, head, regp, NV_CIO_CRE_86); + } + + NVWriteCRTC(dev, head, NV_PCRTC_START, regp->fb_start); + + /* Enable vblank interrupts. */ + NVWriteCRTC(dev, head, NV_PCRTC_INTR_EN_0, + (dev->vblank_enabled[head] ? 1 : 0)); + NVWriteCRTC(dev, head, NV_PCRTC_INTR_0, NV_PCRTC_INTR_0_VBLANK); +} + +static void +nv_save_state_palette(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + int head_offset = head * NV_PRMDIO_SIZE, i; + + nv_wr08(dev, NV_PRMDIO_PIXEL_MASK + head_offset, + NV_PRMDIO_PIXEL_MASK_MASK); + nv_wr08(dev, NV_PRMDIO_READ_MODE_ADDRESS + head_offset, 0x0); + + for (i = 0; i < 768; i++) { + state->crtc_reg[head].DAC[i] = nv_rd08(dev, + NV_PRMDIO_PALETTE_DATA + head_offset); + } + + NVSetEnablePalette(dev, head, false); +} + +void +nouveau_hw_load_state_palette(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + int head_offset = head * NV_PRMDIO_SIZE, i; + + nv_wr08(dev, NV_PRMDIO_PIXEL_MASK + head_offset, + NV_PRMDIO_PIXEL_MASK_MASK); + nv_wr08(dev, NV_PRMDIO_WRITE_MODE_ADDRESS + head_offset, 0x0); + + for (i = 0; i < 768; i++) { + nv_wr08(dev, NV_PRMDIO_PALETTE_DATA + head_offset, + state->crtc_reg[head].DAC[i]); + } + + NVSetEnablePalette(dev, head, false); +} + +void nouveau_hw_save_state(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset == 0x11) + /* NB: no attempt is made to restore the bad pll later on */ + nouveau_hw_fix_bad_vpll(dev, head); + nv_save_state_ramdac(dev, head, state); + nv_save_state_vga(dev, head, state); + nv_save_state_palette(dev, head, state); + nv_save_state_ext(dev, head, state); +} + +void nouveau_hw_load_state(struct drm_device *dev, int head, + struct nv04_mode_state *state) +{ + NVVgaProtect(dev, head, true); + nv_load_state_ramdac(dev, head, state); + nv_load_state_ext(dev, head, state); + nouveau_hw_load_state_palette(dev, head, state); + nv_load_state_vga(dev, head, state); + NVVgaProtect(dev, head, false); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_hw.h b/drivers/gpu/drm/nouveau/nouveau_hw.h new file mode 100644 index 00000000..2989090b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_hw.h @@ -0,0 +1,474 @@ +/* + * Copyright 2008 Stuart Bennett + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __NOUVEAU_HW_H__ +#define __NOUVEAU_HW_H__ + +#include "drmP.h" +#include "nouveau_drv.h" + +#define MASK(field) ( \ + (0xffffffff >> (31 - ((1 ? field) - (0 ? field)))) << (0 ? field)) + +#define XLATE(src, srclowbit, outfield) ( \ + (((src) >> (srclowbit)) << (0 ? outfield)) & MASK(outfield)) + +void NVWriteVgaSeq(struct drm_device *, int head, uint8_t index, uint8_t value); +uint8_t NVReadVgaSeq(struct drm_device *, int head, uint8_t index); +void NVWriteVgaGr(struct drm_device *, int head, uint8_t index, uint8_t value); +uint8_t NVReadVgaGr(struct drm_device *, int head, uint8_t index); +void NVSetOwner(struct drm_device *, int owner); +void NVBlankScreen(struct drm_device *, int head, bool blank); +void nouveau_hw_setpll(struct drm_device *, uint32_t reg1, + struct nouveau_pll_vals *pv); +int nouveau_hw_get_pllvals(struct drm_device *, enum pll_types plltype, + struct nouveau_pll_vals *pllvals); +int nouveau_hw_pllvals_to_clk(struct nouveau_pll_vals *pllvals); +int nouveau_hw_get_clock(struct drm_device *, enum pll_types plltype); +void nouveau_hw_save_vga_fonts(struct drm_device *, bool save); +void nouveau_hw_save_state(struct drm_device *, int head, + struct nv04_mode_state *state); +void nouveau_hw_load_state(struct drm_device *, int head, + struct nv04_mode_state *state); +void nouveau_hw_load_state_palette(struct drm_device *, int head, + struct nv04_mode_state *state); + +/* nouveau_calc.c */ +extern void nouveau_calc_arb(struct drm_device *, int vclk, int bpp, + int *burst, int *lwm); +extern int nouveau_calc_pll_mnp(struct drm_device *, struct pll_lims *pll_lim, + int clk, struct nouveau_pll_vals *pv); + +static inline uint32_t +nvReadMC(struct drm_device *dev, uint32_t reg) +{ + uint32_t val = nv_rd32(dev, reg); + NV_REG_DEBUG(MC, dev, "reg %08x val %08x\n", reg, val); + return val; +} + +static inline void +nvWriteMC(struct drm_device *dev, uint32_t reg, uint32_t val) +{ + NV_REG_DEBUG(MC, dev, "reg %08x val %08x\n", reg, val); + nv_wr32(dev, reg, val); +} + +static inline uint32_t +nvReadVIDEO(struct drm_device *dev, uint32_t reg) +{ + uint32_t val = nv_rd32(dev, reg); + NV_REG_DEBUG(VIDEO, dev, "reg %08x val %08x\n", reg, val); + return val; +} + +static inline void +nvWriteVIDEO(struct drm_device *dev, uint32_t reg, uint32_t val) +{ + NV_REG_DEBUG(VIDEO, dev, "reg %08x val %08x\n", reg, val); + nv_wr32(dev, reg, val); +} + +static inline uint32_t +nvReadFB(struct drm_device *dev, uint32_t reg) +{ + uint32_t val = nv_rd32(dev, reg); + NV_REG_DEBUG(FB, dev, "reg %08x val %08x\n", reg, val); + return val; +} + +static inline void +nvWriteFB(struct drm_device *dev, uint32_t reg, uint32_t val) +{ + NV_REG_DEBUG(FB, dev, "reg %08x val %08x\n", reg, val); + nv_wr32(dev, reg, val); +} + +static inline uint32_t +nvReadEXTDEV(struct drm_device *dev, uint32_t reg) +{ + uint32_t val = nv_rd32(dev, reg); + NV_REG_DEBUG(EXTDEV, dev, "reg %08x val %08x\n", reg, val); + return val; +} + +static inline void +nvWriteEXTDEV(struct drm_device *dev, uint32_t reg, uint32_t val) +{ + NV_REG_DEBUG(EXTDEV, dev, "reg %08x val %08x\n", reg, val); + nv_wr32(dev, reg, val); +} + +static inline uint32_t NVReadCRTC(struct drm_device *dev, + int head, uint32_t reg) +{ + uint32_t val; + if (head) + reg += NV_PCRTC0_SIZE; + val = nv_rd32(dev, reg); + NV_REG_DEBUG(CRTC, dev, "head %d reg %08x val %08x\n", head, reg, val); + return val; +} + +static inline void NVWriteCRTC(struct drm_device *dev, + int head, uint32_t reg, uint32_t val) +{ + if (head) + reg += NV_PCRTC0_SIZE; + NV_REG_DEBUG(CRTC, dev, "head %d reg %08x val %08x\n", head, reg, val); + nv_wr32(dev, reg, val); +} + +static inline uint32_t NVReadRAMDAC(struct drm_device *dev, + int head, uint32_t reg) +{ + uint32_t val; + if (head) + reg += NV_PRAMDAC0_SIZE; + val = nv_rd32(dev, reg); + NV_REG_DEBUG(RAMDAC, dev, "head %d reg %08x val %08x\n", + head, reg, val); + return val; +} + +static inline void NVWriteRAMDAC(struct drm_device *dev, + int head, uint32_t reg, uint32_t val) +{ + if (head) + reg += NV_PRAMDAC0_SIZE; + NV_REG_DEBUG(RAMDAC, dev, "head %d reg %08x val %08x\n", + head, reg, val); + nv_wr32(dev, reg, val); +} + +static inline uint8_t nv_read_tmds(struct drm_device *dev, + int or, int dl, uint8_t address) +{ + int ramdac = (or & OUTPUT_C) >> 2; + + NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_CONTROL + dl * 8, + NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE | address); + return NVReadRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_DATA + dl * 8); +} + +static inline void nv_write_tmds(struct drm_device *dev, + int or, int dl, uint8_t address, + uint8_t data) +{ + int ramdac = (or & OUTPUT_C) >> 2; + + NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_DATA + dl * 8, data); + NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_CONTROL + dl * 8, address); +} + +static inline void NVWriteVgaCrtc(struct drm_device *dev, + int head, uint8_t index, uint8_t value) +{ + NV_REG_DEBUG(VGACRTC, dev, "head %d index 0x%02x data 0x%02x\n", + head, index, value); + nv_wr08(dev, NV_PRMCIO_CRX__COLOR + head * NV_PRMCIO_SIZE, index); + nv_wr08(dev, NV_PRMCIO_CR__COLOR + head * NV_PRMCIO_SIZE, value); +} + +static inline uint8_t NVReadVgaCrtc(struct drm_device *dev, + int head, uint8_t index) +{ + uint8_t val; + nv_wr08(dev, NV_PRMCIO_CRX__COLOR + head * NV_PRMCIO_SIZE, index); + val = nv_rd08(dev, NV_PRMCIO_CR__COLOR + head * NV_PRMCIO_SIZE); + NV_REG_DEBUG(VGACRTC, dev, "head %d index 0x%02x data 0x%02x\n", + head, index, val); + return val; +} + +/* CR57 and CR58 are a fun pair of regs. CR57 provides an index (0-0xf) for CR58 + * I suspect they in fact do nothing, but are merely a way to carry useful + * per-head variables around + * + * Known uses: + * CR57 CR58 + * 0x00 index to the appropriate dcb entry (or 7f for inactive) + * 0x02 dcb entry's "or" value (or 00 for inactive) + * 0x03 bit0 set for dual link (LVDS, possibly elsewhere too) + * 0x08 or 0x09 pxclk in MHz + * 0x0f laptop panel info - low nibble for PEXTDEV_BOOT_0 strap + * high nibble for xlat strap value + */ + +static inline void +NVWriteVgaCrtc5758(struct drm_device *dev, int head, uint8_t index, uint8_t value) +{ + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_57, index); + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_58, value); +} + +static inline uint8_t NVReadVgaCrtc5758(struct drm_device *dev, int head, uint8_t index) +{ + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_57, index); + return NVReadVgaCrtc(dev, head, NV_CIO_CRE_58); +} + +static inline uint8_t NVReadPRMVIO(struct drm_device *dev, + int head, uint32_t reg) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint8_t val; + + /* Only NV4x have two pvio ranges; other twoHeads cards MUST call + * NVSetOwner for the relevant head to be programmed */ + if (head && dev_priv->card_type == NV_40) + reg += NV_PRMVIO_SIZE; + + val = nv_rd08(dev, reg); + NV_REG_DEBUG(RMVIO, dev, "head %d reg %08x val %02x\n", head, reg, val); + return val; +} + +static inline void NVWritePRMVIO(struct drm_device *dev, + int head, uint32_t reg, uint8_t value) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* Only NV4x have two pvio ranges; other twoHeads cards MUST call + * NVSetOwner for the relevant head to be programmed */ + if (head && dev_priv->card_type == NV_40) + reg += NV_PRMVIO_SIZE; + + NV_REG_DEBUG(RMVIO, dev, "head %d reg %08x val %02x\n", + head, reg, value); + nv_wr08(dev, reg, value); +} + +static inline void NVSetEnablePalette(struct drm_device *dev, int head, bool enable) +{ + nv_rd08(dev, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE); + nv_wr08(dev, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, enable ? 0 : 0x20); +} + +static inline bool NVGetEnablePalette(struct drm_device *dev, int head) +{ + nv_rd08(dev, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE); + return !(nv_rd08(dev, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE) & 0x20); +} + +static inline void NVWriteVgaAttr(struct drm_device *dev, + int head, uint8_t index, uint8_t value) +{ + if (NVGetEnablePalette(dev, head)) + index &= ~0x20; + else + index |= 0x20; + + nv_rd08(dev, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE); + NV_REG_DEBUG(VGAATTR, dev, "head %d index 0x%02x data 0x%02x\n", + head, index, value); + nv_wr08(dev, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, index); + nv_wr08(dev, NV_PRMCIO_AR__WRITE + head * NV_PRMCIO_SIZE, value); +} + +static inline uint8_t NVReadVgaAttr(struct drm_device *dev, + int head, uint8_t index) +{ + uint8_t val; + if (NVGetEnablePalette(dev, head)) + index &= ~0x20; + else + index |= 0x20; + + nv_rd08(dev, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE); + nv_wr08(dev, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, index); + val = nv_rd08(dev, NV_PRMCIO_AR__READ + head * NV_PRMCIO_SIZE); + NV_REG_DEBUG(VGAATTR, dev, "head %d index 0x%02x data 0x%02x\n", + head, index, val); + return val; +} + +static inline void NVVgaSeqReset(struct drm_device *dev, int head, bool start) +{ + NVWriteVgaSeq(dev, head, NV_VIO_SR_RESET_INDEX, start ? 0x1 : 0x3); +} + +static inline void NVVgaProtect(struct drm_device *dev, int head, bool protect) +{ + uint8_t seq1 = NVReadVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX); + + if (protect) { + NVVgaSeqReset(dev, head, true); + NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 | 0x20); + } else { + /* Reenable sequencer, then turn on screen */ + NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 & ~0x20); /* reenable display */ + NVVgaSeqReset(dev, head, false); + } + NVSetEnablePalette(dev, head, protect); +} + +static inline bool +nv_heads_tied(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset == 0x11) + return !!(nvReadMC(dev, NV_PBUS_DEBUG_1) & (1 << 28)); + + return NVReadVgaCrtc(dev, 0, NV_CIO_CRE_44) & 0x4; +} + +/* makes cr0-7 on the specified head read-only */ +static inline bool +nv_lock_vga_crtc_base(struct drm_device *dev, int head, bool lock) +{ + uint8_t cr11 = NVReadVgaCrtc(dev, head, NV_CIO_CR_VRE_INDEX); + bool waslocked = cr11 & 0x80; + + if (lock) + cr11 |= 0x80; + else + cr11 &= ~0x80; + NVWriteVgaCrtc(dev, head, NV_CIO_CR_VRE_INDEX, cr11); + + return waslocked; +} + +static inline void +nv_lock_vga_crtc_shadow(struct drm_device *dev, int head, int lock) +{ + /* shadow lock: connects 0x60?3d? regs to "real" 0x3d? regs + * bit7: unlocks HDT, HBS, HBE, HRS, HRE, HEB + * bit6: seems to have some effect on CR09 (double scan, VBS_9) + * bit5: unlocks HDE + * bit4: unlocks VDE + * bit3: unlocks VDT, OVL, VRS, ?VRE?, VBS, VBE, LSR, EBR + * bit2: same as bit 1 of 0x60?804 + * bit0: same as bit 0 of 0x60?804 + */ + + uint8_t cr21 = lock; + + if (lock < 0) + /* 0xfa is generic "unlock all" mask */ + cr21 = NVReadVgaCrtc(dev, head, NV_CIO_CRE_21) | 0xfa; + + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_21, cr21); +} + +/* renders the extended crtc regs (cr19+) on all crtcs impervious: + * immutable and unreadable + */ +static inline bool +NVLockVgaCrtcs(struct drm_device *dev, bool lock) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + bool waslocked = !NVReadVgaCrtc(dev, 0, NV_CIO_SR_LOCK_INDEX); + + NVWriteVgaCrtc(dev, 0, NV_CIO_SR_LOCK_INDEX, + lock ? NV_CIO_SR_LOCK_VALUE : NV_CIO_SR_UNLOCK_RW_VALUE); + /* NV11 has independently lockable extended crtcs, except when tied */ + if (dev_priv->chipset == 0x11 && !nv_heads_tied(dev)) + NVWriteVgaCrtc(dev, 1, NV_CIO_SR_LOCK_INDEX, + lock ? NV_CIO_SR_LOCK_VALUE : + NV_CIO_SR_UNLOCK_RW_VALUE); + + return waslocked; +} + +/* nv04 cursor max dimensions of 32x32 (A1R5G5B5) */ +#define NV04_CURSOR_SIZE 32 +/* limit nv10 cursors to 64x64 (ARGB8) (we could go to 64x255) */ +#define NV10_CURSOR_SIZE 64 + +static inline int nv_cursor_width(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + return dev_priv->card_type >= NV_10 ? NV10_CURSOR_SIZE : NV04_CURSOR_SIZE; +} + +static inline void +nv_fix_nv40_hw_cursor(struct drm_device *dev, int head) +{ + /* on some nv40 (such as the "true" (in the NV_PFB_BOOT_0 sense) nv40, + * the gf6800gt) a hardware bug requires a write to PRAMDAC_CURSOR_POS + * for changes to the CRTC CURCTL regs to take effect, whether changing + * the pixmap location, or just showing/hiding the cursor + */ + uint32_t curpos = NVReadRAMDAC(dev, head, NV_PRAMDAC_CU_START_POS); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_CU_START_POS, curpos); +} + +static inline void +nv_set_crtc_base(struct drm_device *dev, int head, uint32_t offset) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + NVWriteCRTC(dev, head, NV_PCRTC_START, offset); + + if (dev_priv->card_type == NV_04) { + /* + * Hilarious, the 24th bit doesn't want to stick to + * PCRTC_START... + */ + int cre_heb = NVReadVgaCrtc(dev, head, NV_CIO_CRE_HEB__INDEX); + + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_HEB__INDEX, + (cre_heb & ~0x40) | ((offset >> 18) & 0x40)); + } +} + +static inline void +nv_show_cursor(struct drm_device *dev, int head, bool show) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint8_t *curctl1 = + &dev_priv->mode_reg.crtc_reg[head].CRTC[NV_CIO_CRE_HCUR_ADDR1_INDEX]; + + if (show) + *curctl1 |= MASK(NV_CIO_CRE_HCUR_ADDR1_ENABLE); + else + *curctl1 &= ~MASK(NV_CIO_CRE_HCUR_ADDR1_ENABLE); + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_HCUR_ADDR1_INDEX, *curctl1); + + if (dev_priv->card_type == NV_40) + nv_fix_nv40_hw_cursor(dev, head); +} + +static inline uint32_t +nv_pitch_align(struct drm_device *dev, uint32_t width, int bpp) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int mask; + + if (bpp == 15) + bpp = 16; + if (bpp == 24) + bpp = 8; + + /* Alignment requirements taken from the Haiku driver */ + if (dev_priv->card_type == NV_04) + mask = 128 / bpp - 1; + else + mask = 512 / bpp - 1; + + return (width + mask) & ~mask; +} + +#endif /* __NOUVEAU_HW_H__ */ diff --git a/drivers/gpu/drm/nouveau/nouveau_hwsq.h b/drivers/gpu/drm/nouveau/nouveau_hwsq.h new file mode 100644 index 00000000..69768759 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_hwsq.h @@ -0,0 +1,115 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#ifndef __NOUVEAU_HWSQ_H__ +#define __NOUVEAU_HWSQ_H__ + +struct hwsq_ucode { + u8 data[0x200]; + union { + u8 *u08; + u16 *u16; + u32 *u32; + } ptr; + u16 len; + + u32 reg; + u32 val; +}; + +static inline void +hwsq_init(struct hwsq_ucode *hwsq) +{ + hwsq->ptr.u08 = hwsq->data; + hwsq->reg = 0xffffffff; + hwsq->val = 0xffffffff; +} + +static inline void +hwsq_fini(struct hwsq_ucode *hwsq) +{ + do { + *hwsq->ptr.u08++ = 0x7f; + hwsq->len = hwsq->ptr.u08 - hwsq->data; + } while (hwsq->len & 3); + hwsq->ptr.u08 = hwsq->data; +} + +static inline void +hwsq_usec(struct hwsq_ucode *hwsq, u8 usec) +{ + u32 shift = 0; + while (usec & ~3) { + usec >>= 2; + shift++; + } + + *hwsq->ptr.u08++ = (shift << 2) | usec; +} + +static inline void +hwsq_setf(struct hwsq_ucode *hwsq, u8 flag, int val) +{ + flag += 0x80; + if (val >= 0) + flag += 0x20; + if (val >= 1) + flag += 0x20; + *hwsq->ptr.u08++ = flag; +} + +static inline void +hwsq_op5f(struct hwsq_ucode *hwsq, u8 v0, u8 v1) +{ + *hwsq->ptr.u08++ = 0x5f; + *hwsq->ptr.u08++ = v0; + *hwsq->ptr.u08++ = v1; +} + +static inline void +hwsq_wr32(struct hwsq_ucode *hwsq, u32 reg, u32 val) +{ + if (val != hwsq->val) { + if ((val & 0xffff0000) == (hwsq->val & 0xffff0000)) { + *hwsq->ptr.u08++ = 0x42; + *hwsq->ptr.u16++ = (val & 0x0000ffff); + } else { + *hwsq->ptr.u08++ = 0xe2; + *hwsq->ptr.u32++ = val; + } + + hwsq->val = val; + } + + if ((reg & 0xffff0000) == (hwsq->reg & 0xffff0000)) { + *hwsq->ptr.u08++ = 0x40; + *hwsq->ptr.u16++ = (reg & 0x0000ffff); + } else { + *hwsq->ptr.u08++ = 0xe0; + *hwsq->ptr.u32++ = reg; + } + hwsq->reg = reg; +} + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_i2c.c b/drivers/gpu/drm/nouveau/nouveau_i2c.c new file mode 100644 index 00000000..77e56466 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_i2c.c @@ -0,0 +1,394 @@ +/* + * Copyright 2009 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <linux/module.h> + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_i2c.h" +#include "nouveau_hw.h" + +static void +i2c_drive_scl(void *data, int state) +{ + struct nouveau_i2c_chan *port = data; + if (port->type == 0) { + u8 val = NVReadVgaCrtc(port->dev, 0, port->drive); + if (state) val |= 0x20; + else val &= 0xdf; + NVWriteVgaCrtc(port->dev, 0, port->drive, val | 0x01); + } else + if (port->type == 4) { + nv_mask(port->dev, port->drive, 0x2f, state ? 0x21 : 0x01); + } else + if (port->type == 5) { + if (state) port->state |= 0x01; + else port->state &= 0xfe; + nv_wr32(port->dev, port->drive, 4 | port->state); + } +} + +static void +i2c_drive_sda(void *data, int state) +{ + struct nouveau_i2c_chan *port = data; + if (port->type == 0) { + u8 val = NVReadVgaCrtc(port->dev, 0, port->drive); + if (state) val |= 0x10; + else val &= 0xef; + NVWriteVgaCrtc(port->dev, 0, port->drive, val | 0x01); + } else + if (port->type == 4) { + nv_mask(port->dev, port->drive, 0x1f, state ? 0x11 : 0x01); + } else + if (port->type == 5) { + if (state) port->state |= 0x02; + else port->state &= 0xfd; + nv_wr32(port->dev, port->drive, 4 | port->state); + } +} + +static int +i2c_sense_scl(void *data) +{ + struct nouveau_i2c_chan *port = data; + struct drm_nouveau_private *dev_priv = port->dev->dev_private; + if (port->type == 0) { + return !!(NVReadVgaCrtc(port->dev, 0, port->sense) & 0x04); + } else + if (port->type == 4) { + return !!(nv_rd32(port->dev, port->sense) & 0x00040000); + } else + if (port->type == 5) { + if (dev_priv->card_type < NV_D0) + return !!(nv_rd32(port->dev, port->sense) & 0x01); + else + return !!(nv_rd32(port->dev, port->sense) & 0x10); + } + return 0; +} + +static int +i2c_sense_sda(void *data) +{ + struct nouveau_i2c_chan *port = data; + struct drm_nouveau_private *dev_priv = port->dev->dev_private; + if (port->type == 0) { + return !!(NVReadVgaCrtc(port->dev, 0, port->sense) & 0x08); + } else + if (port->type == 4) { + return !!(nv_rd32(port->dev, port->sense) & 0x00080000); + } else + if (port->type == 5) { + if (dev_priv->card_type < NV_D0) + return !!(nv_rd32(port->dev, port->sense) & 0x02); + else + return !!(nv_rd32(port->dev, port->sense) & 0x20); + } + return 0; +} + +static const uint32_t nv50_i2c_port[] = { + 0x00e138, 0x00e150, 0x00e168, 0x00e180, + 0x00e254, 0x00e274, 0x00e764, 0x00e780, + 0x00e79c, 0x00e7b8 +}; + +static u8 * +i2c_table(struct drm_device *dev, u8 *version) +{ + u8 *dcb = dcb_table(dev), *i2c = NULL; + if (dcb) { + if (dcb[0] >= 0x15) + i2c = ROMPTR(dev, dcb[2]); + if (dcb[0] >= 0x30) + i2c = ROMPTR(dev, dcb[4]); + } + + /* early revisions had no version number, use dcb version */ + if (i2c) { + *version = dcb[0]; + if (*version >= 0x30) + *version = i2c[0]; + } + + return i2c; +} + +int +nouveau_i2c_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct nouveau_i2c_chan *port; + u8 version = 0x00, entries, recordlen; + u8 *i2c, *entry, legacy[2][4] = {}; + int ret, i; + + INIT_LIST_HEAD(&dev_priv->i2c_ports); + + i2c = i2c_table(dev, &version); + if (!i2c) { + u8 *bmp = &bios->data[bios->offset]; + if (bios->type != NVBIOS_BMP) + return -ENODEV; + + legacy[0][0] = NV_CIO_CRE_DDC_WR__INDEX; + legacy[0][1] = NV_CIO_CRE_DDC_STATUS__INDEX; + legacy[1][0] = NV_CIO_CRE_DDC0_WR__INDEX; + legacy[1][1] = NV_CIO_CRE_DDC0_STATUS__INDEX; + + /* BMP (from v4.0) has i2c info in the structure, it's in a + * fixed location on earlier VBIOS + */ + if (bmp[5] < 4) + i2c = &bios->data[0x48]; + else + i2c = &bmp[0x36]; + + if (i2c[4]) legacy[0][0] = i2c[4]; + if (i2c[5]) legacy[0][1] = i2c[5]; + if (i2c[6]) legacy[1][0] = i2c[6]; + if (i2c[7]) legacy[1][1] = i2c[7]; + } + + if (version >= 0x30) { + entry = i2c[1] + i2c; + entries = i2c[2]; + recordlen = i2c[3]; + } else + if (version) { + entry = i2c; + entries = 16; + recordlen = 4; + } else { + entry = legacy[0]; + entries = 2; + recordlen = 4; + } + + for (i = 0; i < entries; i++, entry += recordlen) { + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (port == NULL) { + nouveau_i2c_fini(dev); + return -ENOMEM; + } + + port->type = entry[3]; + if (version < 0x30) { + port->type &= 0x07; + if (port->type == 0x07) + port->type = 0xff; + } + + if (port->type == 0xff) { + kfree(port); + continue; + } + + switch (port->type) { + case 0: /* NV04:NV50 */ + port->drive = entry[0]; + port->sense = entry[1]; + break; + case 4: /* NV4E */ + port->drive = 0x600800 + entry[1]; + port->sense = port->drive; + break; + case 5: /* NV50- */ + port->drive = entry[0] & 0x0f; + if (dev_priv->card_type < NV_D0) { + if (port->drive >= ARRAY_SIZE(nv50_i2c_port)) + break; + port->drive = nv50_i2c_port[port->drive]; + port->sense = port->drive; + } else { + port->drive = 0x00d014 + (port->drive * 0x20); + port->sense = port->drive; + } + break; + case 6: /* NV50- DP AUX */ + port->drive = entry[0]; + port->sense = port->drive; + port->adapter.algo = &nouveau_dp_i2c_algo; + break; + default: + break; + } + + if (!port->adapter.algo && !port->drive) { + NV_ERROR(dev, "I2C%d: type %d index %x/%x unknown\n", + i, port->type, port->drive, port->sense); + kfree(port); + continue; + } + + snprintf(port->adapter.name, sizeof(port->adapter.name), + "nouveau-%s-%d", pci_name(dev->pdev), i); + port->adapter.owner = THIS_MODULE; + port->adapter.dev.parent = &dev->pdev->dev; + port->dev = dev; + port->index = i; + port->dcb = ROM32(entry[0]); + i2c_set_adapdata(&port->adapter, i2c); + + if (port->adapter.algo != &nouveau_dp_i2c_algo) { + port->adapter.algo_data = &port->bit; + port->bit.udelay = 10; + port->bit.timeout = usecs_to_jiffies(2200); + port->bit.data = port; + port->bit.setsda = i2c_drive_sda; + port->bit.setscl = i2c_drive_scl; + port->bit.getsda = i2c_sense_sda; + port->bit.getscl = i2c_sense_scl; + + i2c_drive_scl(port, 0); + i2c_drive_sda(port, 1); + i2c_drive_scl(port, 1); + + ret = i2c_bit_add_bus(&port->adapter); + } else { + port->adapter.algo = &nouveau_dp_i2c_algo; + ret = i2c_add_adapter(&port->adapter); + } + + if (ret) { + NV_ERROR(dev, "I2C%d: failed register: %d\n", i, ret); + kfree(port); + continue; + } + + list_add_tail(&port->head, &dev_priv->i2c_ports); + } + + return 0; +} + +void +nouveau_i2c_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_i2c_chan *port, *tmp; + + list_for_each_entry_safe(port, tmp, &dev_priv->i2c_ports, head) { + i2c_del_adapter(&port->adapter); + kfree(port); + } +} + +struct nouveau_i2c_chan * +nouveau_i2c_find(struct drm_device *dev, u8 index) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_i2c_chan *port; + + if (index == NV_I2C_DEFAULT(0) || + index == NV_I2C_DEFAULT(1)) { + u8 version, *i2c = i2c_table(dev, &version); + if (i2c && version >= 0x30) { + if (index == NV_I2C_DEFAULT(0)) + index = (i2c[4] & 0x0f); + else + index = (i2c[4] & 0xf0) >> 4; + } else { + index = 2; + } + } + + list_for_each_entry(port, &dev_priv->i2c_ports, head) { + if (port->index == index) + break; + } + + if (&port->head == &dev_priv->i2c_ports) + return NULL; + + if (dev_priv->card_type >= NV_50 && (port->dcb & 0x00000100)) { + u32 reg = 0x00e500, val; + if (port->type == 6) { + reg += port->drive * 0x50; + val = 0x2002; + } else { + reg += ((port->dcb & 0x1e00) >> 9) * 0x50; + val = 0xe001; + } + + /* nfi, but neither auxch or i2c work if it's 1 */ + nv_mask(dev, reg + 0x0c, 0x00000001, 0x00000000); + /* nfi, but switches auxch vs normal i2c */ + nv_mask(dev, reg + 0x00, 0x0000f003, val); + } + + return port; +} + +bool +nouveau_probe_i2c_addr(struct nouveau_i2c_chan *i2c, int addr) +{ + uint8_t buf[] = { 0 }; + struct i2c_msg msgs[] = { + { + .addr = addr, + .flags = 0, + .len = 1, + .buf = buf, + }, + { + .addr = addr, + .flags = I2C_M_RD, + .len = 1, + .buf = buf, + } + }; + + return i2c_transfer(&i2c->adapter, msgs, 2) == 2; +} + +int +nouveau_i2c_identify(struct drm_device *dev, const char *what, + struct i2c_board_info *info, + bool (*match)(struct nouveau_i2c_chan *, + struct i2c_board_info *), + int index) +{ + struct nouveau_i2c_chan *i2c = nouveau_i2c_find(dev, index); + int i; + + if (!i2c) { + NV_DEBUG(dev, "No bus when probing %s on %d\n", what, index); + return -ENODEV; + } + + NV_DEBUG(dev, "Probing %ss on I2C bus: %d\n", what, i2c->index); + for (i = 0; info[i].addr; i++) { + if (nouveau_probe_i2c_addr(i2c, info[i].addr) && + (!match || match(i2c, &info[i]))) { + NV_INFO(dev, "Detected %s: %s\n", what, info[i].type); + return i; + } + } + + NV_DEBUG(dev, "No devices found.\n"); + return -ENODEV; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_i2c.h b/drivers/gpu/drm/nouveau/nouveau_i2c.h new file mode 100644 index 00000000..1d083893 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_i2c.h @@ -0,0 +1,59 @@ +/* + * Copyright 2009 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __NOUVEAU_I2C_H__ +#define __NOUVEAU_I2C_H__ + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include "drm_dp_helper.h" + +#define NV_I2C_PORT(n) (0x00 + (n)) +#define NV_I2C_PORT_NUM 0x10 +#define NV_I2C_DEFAULT(n) (0x80 + (n)) + +struct nouveau_i2c_chan { + struct i2c_adapter adapter; + struct drm_device *dev; + struct i2c_algo_bit_data bit; + struct list_head head; + u8 index; + u8 type; + u32 dcb; + u32 drive; + u32 sense; + u32 state; +}; + +int nouveau_i2c_init(struct drm_device *); +void nouveau_i2c_fini(struct drm_device *); +struct nouveau_i2c_chan *nouveau_i2c_find(struct drm_device *, u8 index); +bool nouveau_probe_i2c_addr(struct nouveau_i2c_chan *i2c, int addr); +int nouveau_i2c_identify(struct drm_device *dev, const char *what, + struct i2c_board_info *info, + bool (*match)(struct nouveau_i2c_chan *, + struct i2c_board_info *), + int index); + +extern const struct i2c_algorithm nouveau_dp_i2c_algo; + +#endif /* __NOUVEAU_I2C_H__ */ diff --git a/drivers/gpu/drm/nouveau/nouveau_ioc32.c b/drivers/gpu/drm/nouveau/nouveau_ioc32.c new file mode 100644 index 00000000..475ba810 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_ioc32.c @@ -0,0 +1,70 @@ +/** + * \file mga_ioc32.c + * + * 32-bit ioctl compatibility routines for the MGA DRM. + * + * \author Dave Airlie <airlied@linux.ie> with code from patches by Egbert Eich + * + * + * Copyright (C) Paul Mackerras 2005 + * Copyright (C) Egbert Eich 2003,2004 + * Copyright (C) Dave Airlie 2005 + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <linux/compat.h> + +#include "drmP.h" +#include "drm.h" + +#include "nouveau_drv.h" + +/** + * Called whenever a 32-bit process running under a 64-bit kernel + * performs an ioctl on /dev/dri/card<n>. + * + * \param filp file pointer. + * \param cmd command. + * \param arg user argument. + * \return zero on success or negative number on failure. + */ +long nouveau_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + unsigned int nr = DRM_IOCTL_NR(cmd); + drm_ioctl_compat_t *fn = NULL; + int ret; + + if (nr < DRM_COMMAND_BASE) + return drm_compat_ioctl(filp, cmd, arg); + +#if 0 + if (nr < DRM_COMMAND_BASE + DRM_ARRAY_SIZE(mga_compat_ioctls)) + fn = nouveau_compat_ioctls[nr - DRM_COMMAND_BASE]; +#endif + if (fn != NULL) + ret = (*fn)(filp, cmd, arg); + else + ret = drm_ioctl(filp, cmd, arg); + + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_irq.c b/drivers/gpu/drm/nouveau/nouveau_irq.c new file mode 100644 index 00000000..868c7fd7 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_irq.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2006 Ben Skeggs. + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* + * Authors: + * Ben Skeggs <darktama@iinet.net.au> + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drm.h" +#include "nouveau_drv.h" +#include "nouveau_reg.h" +#include "nouveau_ramht.h" +#include "nouveau_util.h" + +void +nouveau_irq_preinstall(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* Master disable */ + nv_wr32(dev, NV03_PMC_INTR_EN_0, 0); + + INIT_LIST_HEAD(&dev_priv->vbl_waiting); +} + +int +nouveau_irq_postinstall(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* Master enable */ + nv_wr32(dev, NV03_PMC_INTR_EN_0, NV_PMC_INTR_EN_0_MASTER_ENABLE); + if (dev_priv->msi_enabled) + nv_wr08(dev, 0x00088068, 0xff); + + return 0; +} + +void +nouveau_irq_uninstall(struct drm_device *dev) +{ + /* Master disable */ + nv_wr32(dev, NV03_PMC_INTR_EN_0, 0); +} + +irqreturn_t +nouveau_irq_handler(DRM_IRQ_ARGS) +{ + struct drm_device *dev = (struct drm_device *)arg; + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + u32 stat; + int i; + + stat = nv_rd32(dev, NV03_PMC_INTR_0); + if (stat == 0 || stat == ~0) + return IRQ_NONE; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + for (i = 0; i < 32 && stat; i++) { + if (!(stat & (1 << i)) || !dev_priv->irq_handler[i]) + continue; + + dev_priv->irq_handler[i](dev); + stat &= ~(1 << i); + } + + if (dev_priv->msi_enabled) + nv_wr08(dev, 0x00088068, 0xff); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + if (stat && nouveau_ratelimit()) + NV_ERROR(dev, "PMC - unhandled INTR 0x%08x\n", stat); + return IRQ_HANDLED; +} + +int +nouveau_irq_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int ret; + + if (nouveau_msi != 0 && dev_priv->card_type >= NV_50) { + ret = pci_enable_msi(dev->pdev); + if (ret == 0) { + NV_INFO(dev, "enabled MSI\n"); + dev_priv->msi_enabled = true; + } + } + + return drm_irq_install(dev); +} + +void +nouveau_irq_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + drm_irq_uninstall(dev); + if (dev_priv->msi_enabled) + pci_disable_msi(dev->pdev); +} + +void +nouveau_irq_register(struct drm_device *dev, int status_bit, + void (*handler)(struct drm_device *)) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + dev_priv->irq_handler[status_bit] = handler; + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); +} + +void +nouveau_irq_unregister(struct drm_device *dev, int status_bit) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + dev_priv->irq_handler[status_bit] = NULL; + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_mem.c b/drivers/gpu/drm/nouveau/nouveau_mem.c new file mode 100644 index 00000000..b08065f9 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_mem.c @@ -0,0 +1,1248 @@ +/* + * Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved. + * Copyright 2005 Stephane Marchesin + * + * The Weather Channel (TM) funded Tungsten Graphics to develop the + * initial release of the Radeon 8500 driver under the XFree86 license. + * This notice must be preserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Ben Skeggs <bskeggs@redhat.com> + * Roy Spliet <r.spliet@student.tudelft.nl> + */ + + +#include "drmP.h" +#include "drm.h" +#include "drm_sarea.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" +#include "nouveau_mm.h" +#include "nouveau_vm.h" + +/* + * NV10-NV40 tiling helpers + */ + +static void +nv10_mem_update_tile_region(struct drm_device *dev, + struct nouveau_tile_reg *tile, uint32_t addr, + uint32_t size, uint32_t pitch, uint32_t flags) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + int i = tile - dev_priv->tile.reg, j; + unsigned long save; + + nouveau_fence_unref(&tile->fence); + + if (tile->pitch) + pfb->free_tile_region(dev, i); + + if (pitch) + pfb->init_tile_region(dev, i, addr, size, pitch, flags); + + spin_lock_irqsave(&dev_priv->context_switch_lock, save); + pfifo->reassign(dev, false); + pfifo->cache_pull(dev, false); + + nouveau_wait_for_idle(dev); + + pfb->set_tile_region(dev, i); + for (j = 0; j < NVOBJ_ENGINE_NR; j++) { + if (dev_priv->eng[j] && dev_priv->eng[j]->set_tile_region) + dev_priv->eng[j]->set_tile_region(dev, i); + } + + pfifo->cache_pull(dev, true); + pfifo->reassign(dev, true); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, save); +} + +static struct nouveau_tile_reg * +nv10_mem_get_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + spin_lock(&dev_priv->tile.lock); + + if (!tile->used && + (!tile->fence || nouveau_fence_signalled(tile->fence))) + tile->used = true; + else + tile = NULL; + + spin_unlock(&dev_priv->tile.lock); + return tile; +} + +void +nv10_mem_put_tile_region(struct drm_device *dev, struct nouveau_tile_reg *tile, + struct nouveau_fence *fence) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (tile) { + spin_lock(&dev_priv->tile.lock); + if (fence) { + /* Mark it as pending. */ + tile->fence = fence; + nouveau_fence_ref(fence); + } + + tile->used = false; + spin_unlock(&dev_priv->tile.lock); + } +} + +struct nouveau_tile_reg * +nv10_mem_set_tiling(struct drm_device *dev, uint32_t addr, uint32_t size, + uint32_t pitch, uint32_t flags) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + struct nouveau_tile_reg *tile, *found = NULL; + int i; + + for (i = 0; i < pfb->num_tiles; i++) { + tile = nv10_mem_get_tile_region(dev, i); + + if (pitch && !found) { + found = tile; + continue; + + } else if (tile && tile->pitch) { + /* Kill an unused tile region. */ + nv10_mem_update_tile_region(dev, tile, 0, 0, 0, 0); + } + + nv10_mem_put_tile_region(dev, tile, NULL); + } + + if (found) + nv10_mem_update_tile_region(dev, found, addr, size, + pitch, flags); + return found; +} + +/* + * Cleanup everything + */ +void +nouveau_mem_vram_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + ttm_bo_device_release(&dev_priv->ttm.bdev); + + nouveau_ttm_global_release(dev_priv); + + if (dev_priv->fb_mtrr >= 0) { + drm_mtrr_del(dev_priv->fb_mtrr, + pci_resource_start(dev->pdev, 1), + pci_resource_len(dev->pdev, 1), DRM_MTRR_WC); + dev_priv->fb_mtrr = -1; + } +} + +void +nouveau_mem_gart_fini(struct drm_device *dev) +{ + nouveau_sgdma_takedown(dev); + + if (drm_core_has_AGP(dev) && dev->agp) { + struct drm_agp_mem *entry, *tempe; + + /* Remove AGP resources, but leave dev->agp + intact until drv_cleanup is called. */ + list_for_each_entry_safe(entry, tempe, &dev->agp->memory, head) { + if (entry->bound) + drm_unbind_agp(entry->memory); + drm_free_agp(entry->memory, entry->pages); + kfree(entry); + } + INIT_LIST_HEAD(&dev->agp->memory); + + if (dev->agp->acquired) + drm_agp_release(dev); + + dev->agp->acquired = 0; + dev->agp->enabled = 0; + } +} + +bool +nouveau_mem_flags_valid(struct drm_device *dev, u32 tile_flags) +{ + if (!(tile_flags & NOUVEAU_GEM_TILE_LAYOUT_MASK)) + return true; + + return false; +} + +#if __OS_HAS_AGP +static unsigned long +get_agp_mode(struct drm_device *dev, unsigned long mode) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* + * FW seems to be broken on nv18, it makes the card lock up + * randomly. + */ + if (dev_priv->chipset == 0x18) + mode &= ~PCI_AGP_COMMAND_FW; + + /* + * AGP mode set in the command line. + */ + if (nouveau_agpmode > 0) { + bool agpv3 = mode & 0x8; + int rate = agpv3 ? nouveau_agpmode / 4 : nouveau_agpmode; + + mode = (mode & ~0x7) | (rate & 0x7); + } + + return mode; +} +#endif + +int +nouveau_mem_reset_agp(struct drm_device *dev) +{ +#if __OS_HAS_AGP + uint32_t saved_pci_nv_1, pmc_enable; + int ret; + + /* First of all, disable fast writes, otherwise if it's + * already enabled in the AGP bridge and we disable the card's + * AGP controller we might be locking ourselves out of it. */ + if ((nv_rd32(dev, NV04_PBUS_PCI_NV_19) | + dev->agp->mode) & PCI_AGP_COMMAND_FW) { + struct drm_agp_info info; + struct drm_agp_mode mode; + + ret = drm_agp_info(dev, &info); + if (ret) + return ret; + + mode.mode = get_agp_mode(dev, info.mode) & ~PCI_AGP_COMMAND_FW; + ret = drm_agp_enable(dev, mode); + if (ret) + return ret; + } + + saved_pci_nv_1 = nv_rd32(dev, NV04_PBUS_PCI_NV_1); + + /* clear busmaster bit */ + nv_wr32(dev, NV04_PBUS_PCI_NV_1, saved_pci_nv_1 & ~0x4); + /* disable AGP */ + nv_wr32(dev, NV04_PBUS_PCI_NV_19, 0); + + /* power cycle pgraph, if enabled */ + pmc_enable = nv_rd32(dev, NV03_PMC_ENABLE); + if (pmc_enable & NV_PMC_ENABLE_PGRAPH) { + nv_wr32(dev, NV03_PMC_ENABLE, + pmc_enable & ~NV_PMC_ENABLE_PGRAPH); + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) | + NV_PMC_ENABLE_PGRAPH); + } + + /* and restore (gives effect of resetting AGP) */ + nv_wr32(dev, NV04_PBUS_PCI_NV_1, saved_pci_nv_1); +#endif + + return 0; +} + +int +nouveau_mem_init_agp(struct drm_device *dev) +{ +#if __OS_HAS_AGP + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_agp_info info; + struct drm_agp_mode mode; + int ret; + + if (!dev->agp->acquired) { + ret = drm_agp_acquire(dev); + if (ret) { + NV_ERROR(dev, "Unable to acquire AGP: %d\n", ret); + return ret; + } + } + + nouveau_mem_reset_agp(dev); + + ret = drm_agp_info(dev, &info); + if (ret) { + NV_ERROR(dev, "Unable to get AGP info: %d\n", ret); + return ret; + } + + /* see agp.h for the AGPSTAT_* modes available */ + mode.mode = get_agp_mode(dev, info.mode); + ret = drm_agp_enable(dev, mode); + if (ret) { + NV_ERROR(dev, "Unable to enable AGP: %d\n", ret); + return ret; + } + + dev_priv->gart_info.type = NOUVEAU_GART_AGP; + dev_priv->gart_info.aper_base = info.aperture_base; + dev_priv->gart_info.aper_size = info.aperture_size; +#endif + return 0; +} + +static const struct vram_types { + int value; + const char *name; +} vram_type_map[] = { + { NV_MEM_TYPE_STOLEN , "stolen system memory" }, + { NV_MEM_TYPE_SGRAM , "SGRAM" }, + { NV_MEM_TYPE_SDRAM , "SDRAM" }, + { NV_MEM_TYPE_DDR1 , "DDR1" }, + { NV_MEM_TYPE_DDR2 , "DDR2" }, + { NV_MEM_TYPE_DDR3 , "DDR3" }, + { NV_MEM_TYPE_GDDR2 , "GDDR2" }, + { NV_MEM_TYPE_GDDR3 , "GDDR3" }, + { NV_MEM_TYPE_GDDR4 , "GDDR4" }, + { NV_MEM_TYPE_GDDR5 , "GDDR5" }, + { NV_MEM_TYPE_UNKNOWN, "unknown type" } +}; + +int +nouveau_mem_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct ttm_bo_device *bdev = &dev_priv->ttm.bdev; + const struct vram_types *vram_type; + int ret, dma_bits; + + dma_bits = 32; + if (dev_priv->card_type >= NV_50) { + if (pci_dma_supported(dev->pdev, DMA_BIT_MASK(40))) + dma_bits = 40; + } else + if (0 && pci_is_pcie(dev->pdev) && + dev_priv->chipset > 0x40 && + dev_priv->chipset != 0x45) { + if (pci_dma_supported(dev->pdev, DMA_BIT_MASK(39))) + dma_bits = 39; + } + + ret = pci_set_dma_mask(dev->pdev, DMA_BIT_MASK(dma_bits)); + if (ret) + return ret; + ret = pci_set_consistent_dma_mask(dev->pdev, DMA_BIT_MASK(dma_bits)); + if (ret) { + /* Reset to default value. */ + pci_set_consistent_dma_mask(dev->pdev, DMA_BIT_MASK(32)); + } + + + ret = nouveau_ttm_global_init(dev_priv); + if (ret) + return ret; + + ret = ttm_bo_device_init(&dev_priv->ttm.bdev, + dev_priv->ttm.bo_global_ref.ref.object, + &nouveau_bo_driver, DRM_FILE_PAGE_OFFSET, + dma_bits <= 32 ? true : false); + if (ret) { + NV_ERROR(dev, "Error initialising bo driver: %d\n", ret); + return ret; + } + + vram_type = vram_type_map; + while (vram_type->value != NV_MEM_TYPE_UNKNOWN) { + if (nouveau_vram_type) { + if (!strcasecmp(nouveau_vram_type, vram_type->name)) + break; + dev_priv->vram_type = vram_type->value; + } else { + if (vram_type->value == dev_priv->vram_type) + break; + } + vram_type++; + } + + NV_INFO(dev, "Detected %dMiB VRAM (%s)\n", + (int)(dev_priv->vram_size >> 20), vram_type->name); + if (dev_priv->vram_sys_base) { + NV_INFO(dev, "Stolen system memory at: 0x%010llx\n", + dev_priv->vram_sys_base); + } + + dev_priv->fb_available_size = dev_priv->vram_size; + dev_priv->fb_mappable_pages = dev_priv->fb_available_size; + if (dev_priv->fb_mappable_pages > pci_resource_len(dev->pdev, 1)) + dev_priv->fb_mappable_pages = pci_resource_len(dev->pdev, 1); + dev_priv->fb_mappable_pages >>= PAGE_SHIFT; + + dev_priv->fb_available_size -= dev_priv->ramin_rsvd_vram; + dev_priv->fb_aper_free = dev_priv->fb_available_size; + + /* mappable vram */ + ret = ttm_bo_init_mm(bdev, TTM_PL_VRAM, + dev_priv->fb_available_size >> PAGE_SHIFT); + if (ret) { + NV_ERROR(dev, "Failed VRAM mm init: %d\n", ret); + return ret; + } + + if (dev_priv->card_type < NV_50) { + ret = nouveau_bo_new(dev, 256*1024, 0, TTM_PL_FLAG_VRAM, + 0, 0, &dev_priv->vga_ram); + if (ret == 0) + ret = nouveau_bo_pin(dev_priv->vga_ram, + TTM_PL_FLAG_VRAM); + + if (ret) { + NV_WARN(dev, "failed to reserve VGA memory\n"); + nouveau_bo_ref(NULL, &dev_priv->vga_ram); + } + } + + dev_priv->fb_mtrr = drm_mtrr_add(pci_resource_start(dev->pdev, 1), + pci_resource_len(dev->pdev, 1), + DRM_MTRR_WC); + return 0; +} + +int +nouveau_mem_gart_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct ttm_bo_device *bdev = &dev_priv->ttm.bdev; + int ret; + + dev_priv->gart_info.type = NOUVEAU_GART_NONE; + +#if !defined(__powerpc__) && !defined(__ia64__) + if (drm_pci_device_is_agp(dev) && dev->agp && nouveau_agpmode) { + ret = nouveau_mem_init_agp(dev); + if (ret) + NV_ERROR(dev, "Error initialising AGP: %d\n", ret); + } +#endif + + if (dev_priv->gart_info.type == NOUVEAU_GART_NONE) { + ret = nouveau_sgdma_init(dev); + if (ret) { + NV_ERROR(dev, "Error initialising PCI(E): %d\n", ret); + return ret; + } + } + + NV_INFO(dev, "%d MiB GART (aperture)\n", + (int)(dev_priv->gart_info.aper_size >> 20)); + dev_priv->gart_info.aper_free = dev_priv->gart_info.aper_size; + + ret = ttm_bo_init_mm(bdev, TTM_PL_TT, + dev_priv->gart_info.aper_size >> PAGE_SHIFT); + if (ret) { + NV_ERROR(dev, "Failed TT mm init: %d\n", ret); + return ret; + } + + return 0; +} + +static int +nv40_mem_timing_calc(struct drm_device *dev, u32 freq, + struct nouveau_pm_tbl_entry *e, u8 len, + struct nouveau_pm_memtiming *boot, + struct nouveau_pm_memtiming *t) +{ + t->reg[0] = (e->tRP << 24 | e->tRAS << 16 | e->tRFC << 8 | e->tRC); + + /* XXX: I don't trust the -1's and +1's... they must come + * from somewhere! */ + t->reg[1] = (e->tWR + 2 + (t->tCWL - 1)) << 24 | + 1 << 16 | + (e->tWTR + 2 + (t->tCWL - 1)) << 8 | + (e->tCL + 2 - (t->tCWL - 1)); + + t->reg[2] = 0x20200000 | + ((t->tCWL - 1) << 24 | + e->tRRD << 16 | + e->tRCDWR << 8 | + e->tRCDRD); + + NV_DEBUG(dev, "Entry %d: 220: %08x %08x %08x\n", t->id, + t->reg[0], t->reg[1], t->reg[2]); + return 0; +} + +static int +nv50_mem_timing_calc(struct drm_device *dev, u32 freq, + struct nouveau_pm_tbl_entry *e, u8 len, + struct nouveau_pm_memtiming *boot, + struct nouveau_pm_memtiming *t) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct bit_entry P; + uint8_t unk18 = 1, unk20 = 0, unk21 = 0, tmp7_3; + + if (bit_table(dev, 'P', &P)) + return -EINVAL; + + switch (min(len, (u8) 22)) { + case 22: + unk21 = e->tUNK_21; + case 21: + unk20 = e->tUNK_20; + case 20: + if (e->tCWL > 0) + t->tCWL = e->tCWL; + case 19: + unk18 = e->tUNK_18; + break; + } + + t->reg[0] = (e->tRP << 24 | e->tRAS << 16 | e->tRFC << 8 | e->tRC); + + t->reg[1] = (e->tWR + 2 + (t->tCWL - 1)) << 24 | + max(unk18, (u8) 1) << 16 | + (e->tWTR + 2 + (t->tCWL - 1)) << 8; + + t->reg[2] = ((t->tCWL - 1) << 24 | + e->tRRD << 16 | + e->tRCDWR << 8 | + e->tRCDRD); + + t->reg[4] = e->tUNK_13 << 8 | e->tUNK_13; + + t->reg[5] = (e->tRFC << 24 | max(e->tRCDRD, e->tRCDWR) << 16 | e->tRP); + + t->reg[8] = boot->reg[8] & 0xffffff00; + + if (P.version == 1) { + t->reg[1] |= (e->tCL + 2 - (t->tCWL - 1)); + + t->reg[3] = (0x14 + e->tCL) << 24 | + 0x16 << 16 | + (e->tCL - 1) << 8 | + (e->tCL - 1); + + t->reg[4] |= boot->reg[4] & 0xffff0000; + + t->reg[6] = (0x33 - t->tCWL) << 16 | + t->tCWL << 8 | + (0x2e + e->tCL - t->tCWL); + + t->reg[7] = 0x4000202 | (e->tCL - 1) << 16; + + /* XXX: P.version == 1 only has DDR2 and GDDR3? */ + if (dev_priv->vram_type == NV_MEM_TYPE_DDR2) { + t->reg[5] |= (e->tCL + 3) << 8; + t->reg[6] |= (t->tCWL - 2) << 8; + t->reg[8] |= (e->tCL - 4); + } else { + t->reg[5] |= (e->tCL + 2) << 8; + t->reg[6] |= t->tCWL << 8; + t->reg[8] |= (e->tCL - 2); + } + } else { + t->reg[1] |= (5 + e->tCL - (t->tCWL)); + + /* XXX: 0xb? 0x30? */ + t->reg[3] = (0x30 + e->tCL) << 24 | + (boot->reg[3] & 0x00ff0000)| + (0xb + e->tCL) << 8 | + (e->tCL - 1); + + t->reg[4] |= (unk20 << 24 | unk21 << 16); + + /* XXX: +6? */ + t->reg[5] |= (t->tCWL + 6) << 8; + + t->reg[6] = (0x5a + e->tCL) << 16 | + (6 - e->tCL + t->tCWL) << 8 | + (0x50 + e->tCL - t->tCWL); + + tmp7_3 = (boot->reg[7] & 0xff000000) >> 24; + t->reg[7] = (tmp7_3 << 24) | + ((tmp7_3 - 6 + e->tCL) << 16) | + 0x202; + } + + NV_DEBUG(dev, "Entry %d: 220: %08x %08x %08x %08x\n", t->id, + t->reg[0], t->reg[1], t->reg[2], t->reg[3]); + NV_DEBUG(dev, " 230: %08x %08x %08x %08x\n", + t->reg[4], t->reg[5], t->reg[6], t->reg[7]); + NV_DEBUG(dev, " 240: %08x\n", t->reg[8]); + return 0; +} + +static int +nvc0_mem_timing_calc(struct drm_device *dev, u32 freq, + struct nouveau_pm_tbl_entry *e, u8 len, + struct nouveau_pm_memtiming *boot, + struct nouveau_pm_memtiming *t) +{ + if (e->tCWL > 0) + t->tCWL = e->tCWL; + + t->reg[0] = (e->tRP << 24 | (e->tRAS & 0x7f) << 17 | + e->tRFC << 8 | e->tRC); + + t->reg[1] = (boot->reg[1] & 0xff000000) | + (e->tRCDWR & 0x0f) << 20 | + (e->tRCDRD & 0x0f) << 14 | + (t->tCWL << 7) | + (e->tCL & 0x0f); + + t->reg[2] = (boot->reg[2] & 0xff0000ff) | + e->tWR << 16 | e->tWTR << 8; + + t->reg[3] = (e->tUNK_20 & 0x1f) << 9 | + (e->tUNK_21 & 0xf) << 5 | + (e->tUNK_13 & 0x1f); + + t->reg[4] = (boot->reg[4] & 0xfff00fff) | + (e->tRRD&0x1f) << 15; + + NV_DEBUG(dev, "Entry %d: 290: %08x %08x %08x %08x\n", t->id, + t->reg[0], t->reg[1], t->reg[2], t->reg[3]); + NV_DEBUG(dev, " 2a0: %08x\n", t->reg[4]); + return 0; +} + +/** + * MR generation methods + */ + +static int +nouveau_mem_ddr2_mr(struct drm_device *dev, u32 freq, + struct nouveau_pm_tbl_entry *e, u8 len, + struct nouveau_pm_memtiming *boot, + struct nouveau_pm_memtiming *t) +{ + t->drive_strength = 0; + if (len < 15) { + t->odt = boot->odt; + } else { + t->odt = e->RAM_FT1 & 0x07; + } + + if (e->tCL >= NV_MEM_CL_DDR2_MAX) { + NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL); + return -ERANGE; + } + + if (e->tWR >= NV_MEM_WR_DDR2_MAX) { + NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR); + return -ERANGE; + } + + if (t->odt > 3) { + NV_WARN(dev, "(%u) Invalid odt value, assuming disabled: %x", + t->id, t->odt); + t->odt = 0; + } + + t->mr[0] = (boot->mr[0] & 0x100f) | + (e->tCL) << 4 | + (e->tWR - 1) << 9; + t->mr[1] = (boot->mr[1] & 0x101fbb) | + (t->odt & 0x1) << 2 | + (t->odt & 0x2) << 5; + + NV_DEBUG(dev, "(%u) MR: %08x", t->id, t->mr[0]); + return 0; +} + +uint8_t nv_mem_wr_lut_ddr3[NV_MEM_WR_DDR3_MAX] = { + 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 0, 0}; + +static int +nouveau_mem_ddr3_mr(struct drm_device *dev, u32 freq, + struct nouveau_pm_tbl_entry *e, u8 len, + struct nouveau_pm_memtiming *boot, + struct nouveau_pm_memtiming *t) +{ + u8 cl = e->tCL - 4; + + t->drive_strength = 0; + if (len < 15) { + t->odt = boot->odt; + } else { + t->odt = e->RAM_FT1 & 0x07; + } + + if (e->tCL >= NV_MEM_CL_DDR3_MAX || e->tCL < 4) { + NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL); + return -ERANGE; + } + + if (e->tWR >= NV_MEM_WR_DDR3_MAX || e->tWR < 4) { + NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR); + return -ERANGE; + } + + if (e->tCWL < 5) { + NV_WARN(dev, "(%u) Invalid tCWL: %u", t->id, e->tCWL); + return -ERANGE; + } + + t->mr[0] = (boot->mr[0] & 0x180b) | + /* CAS */ + (cl & 0x7) << 4 | + (cl & 0x8) >> 1 | + (nv_mem_wr_lut_ddr3[e->tWR]) << 9; + t->mr[1] = (boot->mr[1] & 0x101dbb) | + (t->odt & 0x1) << 2 | + (t->odt & 0x2) << 5 | + (t->odt & 0x4) << 7; + t->mr[2] = (boot->mr[2] & 0x20ffb7) | (e->tCWL - 5) << 3; + + NV_DEBUG(dev, "(%u) MR: %08x %08x", t->id, t->mr[0], t->mr[2]); + return 0; +} + +uint8_t nv_mem_cl_lut_gddr3[NV_MEM_CL_GDDR3_MAX] = { + 0, 0, 0, 0, 4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11}; +uint8_t nv_mem_wr_lut_gddr3[NV_MEM_WR_GDDR3_MAX] = { + 0, 0, 0, 0, 0, 2, 3, 8, 9, 10, 11, 0, 0, 1, 1, 0, 3}; + +static int +nouveau_mem_gddr3_mr(struct drm_device *dev, u32 freq, + struct nouveau_pm_tbl_entry *e, u8 len, + struct nouveau_pm_memtiming *boot, + struct nouveau_pm_memtiming *t) +{ + if (len < 15) { + t->drive_strength = boot->drive_strength; + t->odt = boot->odt; + } else { + t->drive_strength = (e->RAM_FT1 & 0x30) >> 4; + t->odt = e->RAM_FT1 & 0x07; + } + + if (e->tCL >= NV_MEM_CL_GDDR3_MAX) { + NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL); + return -ERANGE; + } + + if (e->tWR >= NV_MEM_WR_GDDR3_MAX) { + NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR); + return -ERANGE; + } + + if (t->odt > 3) { + NV_WARN(dev, "(%u) Invalid odt value, assuming autocal: %x", + t->id, t->odt); + t->odt = 0; + } + + t->mr[0] = (boot->mr[0] & 0xe0b) | + /* CAS */ + ((nv_mem_cl_lut_gddr3[e->tCL] & 0x7) << 4) | + ((nv_mem_cl_lut_gddr3[e->tCL] & 0x8) >> 2); + t->mr[1] = (boot->mr[1] & 0x100f40) | t->drive_strength | + (t->odt << 2) | + (nv_mem_wr_lut_gddr3[e->tWR] & 0xf) << 4; + t->mr[2] = boot->mr[2]; + + NV_DEBUG(dev, "(%u) MR: %08x %08x %08x", t->id, + t->mr[0], t->mr[1], t->mr[2]); + return 0; +} + +static int +nouveau_mem_gddr5_mr(struct drm_device *dev, u32 freq, + struct nouveau_pm_tbl_entry *e, u8 len, + struct nouveau_pm_memtiming *boot, + struct nouveau_pm_memtiming *t) +{ + if (len < 15) { + t->drive_strength = boot->drive_strength; + t->odt = boot->odt; + } else { + t->drive_strength = (e->RAM_FT1 & 0x30) >> 4; + t->odt = e->RAM_FT1 & 0x03; + } + + if (e->tCL >= NV_MEM_CL_GDDR5_MAX) { + NV_WARN(dev, "(%u) Invalid tCL: %u", t->id, e->tCL); + return -ERANGE; + } + + if (e->tWR >= NV_MEM_WR_GDDR5_MAX) { + NV_WARN(dev, "(%u) Invalid tWR: %u", t->id, e->tWR); + return -ERANGE; + } + + if (t->odt > 3) { + NV_WARN(dev, "(%u) Invalid odt value, assuming autocal: %x", + t->id, t->odt); + t->odt = 0; + } + + t->mr[0] = (boot->mr[0] & 0x007) | + ((e->tCL - 5) << 3) | + ((e->tWR - 4) << 8); + t->mr[1] = (boot->mr[1] & 0x1007f0) | + t->drive_strength | + (t->odt << 2); + + NV_DEBUG(dev, "(%u) MR: %08x %08x", t->id, t->mr[0], t->mr[1]); + return 0; +} + +int +nouveau_mem_timing_calc(struct drm_device *dev, u32 freq, + struct nouveau_pm_memtiming *t) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_memtiming *boot = &pm->boot.timing; + struct nouveau_pm_tbl_entry *e; + u8 ver, len, *ptr, *ramcfg; + int ret; + + ptr = nouveau_perf_timing(dev, freq, &ver, &len); + if (!ptr || ptr[0] == 0x00) { + *t = *boot; + return 0; + } + e = (struct nouveau_pm_tbl_entry *)ptr; + + t->tCWL = boot->tCWL; + + switch (dev_priv->card_type) { + case NV_40: + ret = nv40_mem_timing_calc(dev, freq, e, len, boot, t); + break; + case NV_50: + ret = nv50_mem_timing_calc(dev, freq, e, len, boot, t); + break; + case NV_C0: + ret = nvc0_mem_timing_calc(dev, freq, e, len, boot, t); + break; + default: + ret = -ENODEV; + break; + } + + switch (dev_priv->vram_type * !ret) { + case NV_MEM_TYPE_GDDR3: + ret = nouveau_mem_gddr3_mr(dev, freq, e, len, boot, t); + break; + case NV_MEM_TYPE_GDDR5: + ret = nouveau_mem_gddr5_mr(dev, freq, e, len, boot, t); + break; + case NV_MEM_TYPE_DDR2: + ret = nouveau_mem_ddr2_mr(dev, freq, e, len, boot, t); + break; + case NV_MEM_TYPE_DDR3: + ret = nouveau_mem_ddr3_mr(dev, freq, e, len, boot, t); + break; + default: + ret = -EINVAL; + break; + } + + ramcfg = nouveau_perf_ramcfg(dev, freq, &ver, &len); + if (ramcfg) { + int dll_off; + + if (ver == 0x00) + dll_off = !!(ramcfg[3] & 0x04); + else + dll_off = !!(ramcfg[2] & 0x40); + + switch (dev_priv->vram_type) { + case NV_MEM_TYPE_GDDR3: + t->mr[1] &= ~0x00000040; + t->mr[1] |= 0x00000040 * dll_off; + break; + default: + t->mr[1] &= ~0x00000001; + t->mr[1] |= 0x00000001 * dll_off; + break; + } + } + + return ret; +} + +void +nouveau_mem_timing_read(struct drm_device *dev, struct nouveau_pm_memtiming *t) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 timing_base, timing_regs, mr_base; + int i; + + if (dev_priv->card_type >= 0xC0) { + timing_base = 0x10f290; + mr_base = 0x10f300; + } else { + timing_base = 0x100220; + mr_base = 0x1002c0; + } + + t->id = -1; + + switch (dev_priv->card_type) { + case NV_50: + timing_regs = 9; + break; + case NV_C0: + case NV_D0: + timing_regs = 5; + break; + case NV_30: + case NV_40: + timing_regs = 3; + break; + default: + timing_regs = 0; + return; + } + for(i = 0; i < timing_regs; i++) + t->reg[i] = nv_rd32(dev, timing_base + (0x04 * i)); + + t->tCWL = 0; + if (dev_priv->card_type < NV_C0) { + t->tCWL = ((nv_rd32(dev, 0x100228) & 0x0f000000) >> 24) + 1; + } else if (dev_priv->card_type <= NV_D0) { + t->tCWL = ((nv_rd32(dev, 0x10f294) & 0x00000f80) >> 7); + } + + t->mr[0] = nv_rd32(dev, mr_base); + t->mr[1] = nv_rd32(dev, mr_base + 0x04); + t->mr[2] = nv_rd32(dev, mr_base + 0x20); + t->mr[3] = nv_rd32(dev, mr_base + 0x24); + + t->odt = 0; + t->drive_strength = 0; + + switch (dev_priv->vram_type) { + case NV_MEM_TYPE_DDR3: + t->odt |= (t->mr[1] & 0x200) >> 7; + case NV_MEM_TYPE_DDR2: + t->odt |= (t->mr[1] & 0x04) >> 2 | + (t->mr[1] & 0x40) >> 5; + break; + case NV_MEM_TYPE_GDDR3: + case NV_MEM_TYPE_GDDR5: + t->drive_strength = t->mr[1] & 0x03; + t->odt = (t->mr[1] & 0x0c) >> 2; + break; + default: + break; + } +} + +int +nouveau_mem_exec(struct nouveau_mem_exec_func *exec, + struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = exec->dev->dev_private; + struct nouveau_pm_memtiming *info = &perflvl->timing; + u32 tMRD = 1000, tCKSRE = 0, tCKSRX = 0, tXS = 0, tDLLK = 0; + u32 mr[3] = { info->mr[0], info->mr[1], info->mr[2] }; + u32 mr1_dlloff; + + switch (dev_priv->vram_type) { + case NV_MEM_TYPE_DDR2: + tDLLK = 2000; + mr1_dlloff = 0x00000001; + break; + case NV_MEM_TYPE_DDR3: + tDLLK = 12000; + mr1_dlloff = 0x00000001; + break; + case NV_MEM_TYPE_GDDR3: + tDLLK = 40000; + mr1_dlloff = 0x00000040; + break; + default: + NV_ERROR(exec->dev, "cannot reclock unsupported memtype\n"); + return -ENODEV; + } + + /* fetch current MRs */ + switch (dev_priv->vram_type) { + case NV_MEM_TYPE_GDDR3: + case NV_MEM_TYPE_DDR3: + mr[2] = exec->mrg(exec, 2); + default: + mr[1] = exec->mrg(exec, 1); + mr[0] = exec->mrg(exec, 0); + break; + } + + /* DLL 'on' -> DLL 'off' mode, disable before entering self-refresh */ + if (!(mr[1] & mr1_dlloff) && (info->mr[1] & mr1_dlloff)) { + exec->precharge(exec); + exec->mrs (exec, 1, mr[1] | mr1_dlloff); + exec->wait(exec, tMRD); + } + + /* enter self-refresh mode */ + exec->precharge(exec); + exec->refresh(exec); + exec->refresh(exec); + exec->refresh_auto(exec, false); + exec->refresh_self(exec, true); + exec->wait(exec, tCKSRE); + + /* modify input clock frequency */ + exec->clock_set(exec); + + /* exit self-refresh mode */ + exec->wait(exec, tCKSRX); + exec->precharge(exec); + exec->refresh_self(exec, false); + exec->refresh_auto(exec, true); + exec->wait(exec, tXS); + + /* update MRs */ + if (mr[2] != info->mr[2]) { + exec->mrs (exec, 2, info->mr[2]); + exec->wait(exec, tMRD); + } + + if (mr[1] != info->mr[1]) { + /* need to keep DLL off until later, at least on GDDR3 */ + exec->mrs (exec, 1, info->mr[1] | (mr[1] & mr1_dlloff)); + exec->wait(exec, tMRD); + } + + if (mr[0] != info->mr[0]) { + exec->mrs (exec, 0, info->mr[0]); + exec->wait(exec, tMRD); + } + + /* update PFB timing registers */ + exec->timing_set(exec); + + /* DLL (enable + ) reset */ + if (!(info->mr[1] & mr1_dlloff)) { + if (mr[1] & mr1_dlloff) { + exec->mrs (exec, 1, info->mr[1]); + exec->wait(exec, tMRD); + } + exec->mrs (exec, 0, info->mr[0] | 0x00000100); + exec->wait(exec, tMRD); + exec->mrs (exec, 0, info->mr[0] | 0x00000000); + exec->wait(exec, tMRD); + exec->wait(exec, tDLLK); + if (dev_priv->vram_type == NV_MEM_TYPE_GDDR3) + exec->precharge(exec); + } + + return 0; +} + +int +nouveau_mem_vbios_type(struct drm_device *dev) +{ + struct bit_entry M; + u8 ramcfg = (nv_rd32(dev, 0x101000) & 0x0000003c) >> 2; + if (!bit_table(dev, 'M', &M) || M.version != 2 || M.length < 5) { + u8 *table = ROMPTR(dev, M.data[3]); + if (table && table[0] == 0x10 && ramcfg < table[3]) { + u8 *entry = table + table[1] + (ramcfg * table[2]); + switch (entry[0] & 0x0f) { + case 0: return NV_MEM_TYPE_DDR2; + case 1: return NV_MEM_TYPE_DDR3; + case 2: return NV_MEM_TYPE_GDDR3; + case 3: return NV_MEM_TYPE_GDDR5; + default: + break; + } + + } + } + return NV_MEM_TYPE_UNKNOWN; +} + +static int +nouveau_vram_manager_init(struct ttm_mem_type_manager *man, unsigned long psize) +{ + /* nothing to do */ + return 0; +} + +static int +nouveau_vram_manager_fini(struct ttm_mem_type_manager *man) +{ + /* nothing to do */ + return 0; +} + +static inline void +nouveau_mem_node_cleanup(struct nouveau_mem *node) +{ + if (node->vma[0].node) { + nouveau_vm_unmap(&node->vma[0]); + nouveau_vm_put(&node->vma[0]); + } + + if (node->vma[1].node) { + nouveau_vm_unmap(&node->vma[1]); + nouveau_vm_put(&node->vma[1]); + } +} + +static void +nouveau_vram_manager_del(struct ttm_mem_type_manager *man, + struct ttm_mem_reg *mem) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(man->bdev); + struct nouveau_vram_engine *vram = &dev_priv->engine.vram; + struct drm_device *dev = dev_priv->dev; + + nouveau_mem_node_cleanup(mem->mm_node); + vram->put(dev, (struct nouveau_mem **)&mem->mm_node); +} + +static int +nouveau_vram_manager_new(struct ttm_mem_type_manager *man, + struct ttm_buffer_object *bo, + struct ttm_placement *placement, + struct ttm_mem_reg *mem) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(man->bdev); + struct nouveau_vram_engine *vram = &dev_priv->engine.vram; + struct drm_device *dev = dev_priv->dev; + struct nouveau_bo *nvbo = nouveau_bo(bo); + struct nouveau_mem *node; + u32 size_nc = 0; + int ret; + + if (nvbo->tile_flags & NOUVEAU_GEM_TILE_NONCONTIG) + size_nc = 1 << nvbo->page_shift; + + ret = vram->get(dev, mem->num_pages << PAGE_SHIFT, + mem->page_alignment << PAGE_SHIFT, size_nc, + (nvbo->tile_flags >> 8) & 0x3ff, &node); + if (ret) { + mem->mm_node = NULL; + return (ret == -ENOSPC) ? 0 : ret; + } + + node->page_shift = nvbo->page_shift; + + mem->mm_node = node; + mem->start = node->offset >> PAGE_SHIFT; + return 0; +} + +void +nouveau_vram_manager_debug(struct ttm_mem_type_manager *man, const char *prefix) +{ + struct nouveau_mm *mm = man->priv; + struct nouveau_mm_node *r; + u32 total = 0, free = 0; + + mutex_lock(&mm->mutex); + list_for_each_entry(r, &mm->nodes, nl_entry) { + printk(KERN_DEBUG "%s %d: 0x%010llx 0x%010llx\n", + prefix, r->type, ((u64)r->offset << 12), + (((u64)r->offset + r->length) << 12)); + + total += r->length; + if (!r->type) + free += r->length; + } + mutex_unlock(&mm->mutex); + + printk(KERN_DEBUG "%s total: 0x%010llx free: 0x%010llx\n", + prefix, (u64)total << 12, (u64)free << 12); + printk(KERN_DEBUG "%s block: 0x%08x\n", + prefix, mm->block_size << 12); +} + +const struct ttm_mem_type_manager_func nouveau_vram_manager = { + nouveau_vram_manager_init, + nouveau_vram_manager_fini, + nouveau_vram_manager_new, + nouveau_vram_manager_del, + nouveau_vram_manager_debug +}; + +static int +nouveau_gart_manager_init(struct ttm_mem_type_manager *man, unsigned long psize) +{ + return 0; +} + +static int +nouveau_gart_manager_fini(struct ttm_mem_type_manager *man) +{ + return 0; +} + +static void +nouveau_gart_manager_del(struct ttm_mem_type_manager *man, + struct ttm_mem_reg *mem) +{ + nouveau_mem_node_cleanup(mem->mm_node); + kfree(mem->mm_node); + mem->mm_node = NULL; +} + +static int +nouveau_gart_manager_new(struct ttm_mem_type_manager *man, + struct ttm_buffer_object *bo, + struct ttm_placement *placement, + struct ttm_mem_reg *mem) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bo->bdev); + struct nouveau_mem *node; + + if (unlikely((mem->num_pages << PAGE_SHIFT) >= + dev_priv->gart_info.aper_size)) + return -ENOMEM; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return -ENOMEM; + node->page_shift = 12; + + mem->mm_node = node; + mem->start = 0; + return 0; +} + +void +nouveau_gart_manager_debug(struct ttm_mem_type_manager *man, const char *prefix) +{ +} + +const struct ttm_mem_type_manager_func nouveau_gart_manager = { + nouveau_gart_manager_init, + nouveau_gart_manager_fini, + nouveau_gart_manager_new, + nouveau_gart_manager_del, + nouveau_gart_manager_debug +}; diff --git a/drivers/gpu/drm/nouveau/nouveau_mm.c b/drivers/gpu/drm/nouveau/nouveau_mm.c new file mode 100644 index 00000000..b29ffb3d --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_mm.c @@ -0,0 +1,175 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_mm.h" + +static inline void +region_put(struct nouveau_mm *mm, struct nouveau_mm_node *a) +{ + list_del(&a->nl_entry); + list_del(&a->fl_entry); + kfree(a); +} + +static struct nouveau_mm_node * +region_split(struct nouveau_mm *mm, struct nouveau_mm_node *a, u32 size) +{ + struct nouveau_mm_node *b; + + if (a->length == size) + return a; + + b = kmalloc(sizeof(*b), GFP_KERNEL); + if (unlikely(b == NULL)) + return NULL; + + b->offset = a->offset; + b->length = size; + b->type = a->type; + a->offset += size; + a->length -= size; + list_add_tail(&b->nl_entry, &a->nl_entry); + if (b->type == 0) + list_add_tail(&b->fl_entry, &a->fl_entry); + return b; +} + +#define node(root, dir) ((root)->nl_entry.dir == &mm->nodes) ? NULL : \ + list_entry((root)->nl_entry.dir, struct nouveau_mm_node, nl_entry) + +void +nouveau_mm_put(struct nouveau_mm *mm, struct nouveau_mm_node *this) +{ + struct nouveau_mm_node *prev = node(this, prev); + struct nouveau_mm_node *next = node(this, next); + + list_add(&this->fl_entry, &mm->free); + this->type = 0; + + if (prev && prev->type == 0) { + prev->length += this->length; + region_put(mm, this); + this = prev; + } + + if (next && next->type == 0) { + next->offset = this->offset; + next->length += this->length; + region_put(mm, this); + } +} + +int +nouveau_mm_get(struct nouveau_mm *mm, int type, u32 size, u32 size_nc, + u32 align, struct nouveau_mm_node **pnode) +{ + struct nouveau_mm_node *prev, *this, *next; + u32 min = size_nc ? size_nc : size; + u32 align_mask = align - 1; + u32 splitoff; + u32 s, e; + + list_for_each_entry(this, &mm->free, fl_entry) { + e = this->offset + this->length; + s = this->offset; + + prev = node(this, prev); + if (prev && prev->type != type) + s = roundup(s, mm->block_size); + + next = node(this, next); + if (next && next->type != type) + e = rounddown(e, mm->block_size); + + s = (s + align_mask) & ~align_mask; + e &= ~align_mask; + if (s > e || e - s < min) + continue; + + splitoff = s - this->offset; + if (splitoff && !region_split(mm, this, splitoff)) + return -ENOMEM; + + this = region_split(mm, this, min(size, e - s)); + if (!this) + return -ENOMEM; + + this->type = type; + list_del(&this->fl_entry); + *pnode = this; + return 0; + } + + return -ENOSPC; +} + +int +nouveau_mm_init(struct nouveau_mm *mm, u32 offset, u32 length, u32 block) +{ + struct nouveau_mm_node *node; + + if (block) { + mutex_init(&mm->mutex); + INIT_LIST_HEAD(&mm->nodes); + INIT_LIST_HEAD(&mm->free); + mm->block_size = block; + mm->heap_nodes = 0; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return -ENOMEM; + node->offset = roundup(offset, mm->block_size); + node->length = rounddown(offset + length, mm->block_size) - node->offset; + + list_add_tail(&node->nl_entry, &mm->nodes); + list_add_tail(&node->fl_entry, &mm->free); + mm->heap_nodes++; + return 0; +} + +int +nouveau_mm_fini(struct nouveau_mm *mm) +{ + struct nouveau_mm_node *node, *heap = + list_first_entry(&mm->nodes, struct nouveau_mm_node, nl_entry); + int nodes = 0; + + list_for_each_entry(node, &mm->nodes, nl_entry) { + if (nodes++ == mm->heap_nodes) { + printk(KERN_ERR "nouveau_mm in use at destroy time!\n"); + list_for_each_entry(node, &mm->nodes, nl_entry) { + printk(KERN_ERR "0x%02x: 0x%08x 0x%08x\n", + node->type, node->offset, node->length); + } + WARN_ON(1); + return -EBUSY; + } + } + + kfree(heap); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_mm.h b/drivers/gpu/drm/nouveau/nouveau_mm.h new file mode 100644 index 00000000..57a600c3 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_mm.h @@ -0,0 +1,67 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#ifndef __NOUVEAU_REGION_H__ +#define __NOUVEAU_REGION_H__ + +struct nouveau_mm_node { + struct list_head nl_entry; + struct list_head fl_entry; + struct list_head rl_entry; + + u8 type; + u32 offset; + u32 length; +}; + +struct nouveau_mm { + struct list_head nodes; + struct list_head free; + + struct mutex mutex; + + u32 block_size; + int heap_nodes; +}; + +int nouveau_mm_init(struct nouveau_mm *, u32 offset, u32 length, u32 block); +int nouveau_mm_fini(struct nouveau_mm *); +int nouveau_mm_pre(struct nouveau_mm *); +int nouveau_mm_get(struct nouveau_mm *, int type, u32 size, u32 size_nc, + u32 align, struct nouveau_mm_node **); +void nouveau_mm_put(struct nouveau_mm *, struct nouveau_mm_node *); + +int nv50_vram_init(struct drm_device *); +void nv50_vram_fini(struct drm_device *); +int nv50_vram_new(struct drm_device *, u64 size, u32 align, u32 size_nc, + u32 memtype, struct nouveau_mem **); +void nv50_vram_del(struct drm_device *, struct nouveau_mem **); +bool nv50_vram_flags_valid(struct drm_device *, u32 tile_flags); + +int nvc0_vram_init(struct drm_device *); +int nvc0_vram_new(struct drm_device *, u64 size, u32 align, u32 ncmin, + u32 memtype, struct nouveau_mem **); +bool nvc0_vram_flags_valid(struct drm_device *, u32 tile_flags); + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_mxm.c b/drivers/gpu/drm/nouveau/nouveau_mxm.c new file mode 100644 index 00000000..07d0d1e0 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_mxm.c @@ -0,0 +1,723 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <linux/acpi.h> + +#include "drmP.h" +#include "nouveau_drv.h" + +#define MXM_DBG(dev, fmt, args...) NV_DEBUG((dev), "MXM: " fmt, ##args) +#define MXM_MSG(dev, fmt, args...) NV_INFO((dev), "MXM: " fmt, ##args) + +static u8 * +mxms_data(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return dev_priv->mxms; + +} + +static u16 +mxms_version(struct drm_device *dev) +{ + u8 *mxms = mxms_data(dev); + u16 version = (mxms[4] << 8) | mxms[5]; + switch (version ) { + case 0x0200: + case 0x0201: + case 0x0300: + return version; + default: + break; + } + + MXM_DBG(dev, "unknown version %d.%d\n", mxms[4], mxms[5]); + return 0x0000; +} + +static u16 +mxms_headerlen(struct drm_device *dev) +{ + return 8; +} + +static u16 +mxms_structlen(struct drm_device *dev) +{ + return *(u16 *)&mxms_data(dev)[6]; +} + +static bool +mxms_checksum(struct drm_device *dev) +{ + u16 size = mxms_headerlen(dev) + mxms_structlen(dev); + u8 *mxms = mxms_data(dev), sum = 0; + while (size--) + sum += *mxms++; + if (sum) { + MXM_DBG(dev, "checksum invalid\n"); + return false; + } + return true; +} + +static bool +mxms_valid(struct drm_device *dev) +{ + u8 *mxms = mxms_data(dev); + if (*(u32 *)mxms != 0x5f4d584d) { + MXM_DBG(dev, "signature invalid\n"); + return false; + } + + if (!mxms_version(dev) || !mxms_checksum(dev)) + return false; + + return true; +} + +static bool +mxms_foreach(struct drm_device *dev, u8 types, + bool (*exec)(struct drm_device *, u8 *, void *), void *info) +{ + u8 *mxms = mxms_data(dev); + u8 *desc = mxms + mxms_headerlen(dev); + u8 *fini = desc + mxms_structlen(dev) - 1; + while (desc < fini) { + u8 type = desc[0] & 0x0f; + u8 headerlen = 0; + u8 recordlen = 0; + u8 entries = 0; + + switch (type) { + case 0: /* Output Device Structure */ + if (mxms_version(dev) >= 0x0300) + headerlen = 8; + else + headerlen = 6; + break; + case 1: /* System Cooling Capability Structure */ + case 2: /* Thermal Structure */ + case 3: /* Input Power Structure */ + headerlen = 4; + break; + case 4: /* GPIO Device Structure */ + headerlen = 4; + recordlen = 2; + entries = (ROM32(desc[0]) & 0x01f00000) >> 20; + break; + case 5: /* Vendor Specific Structure */ + headerlen = 8; + break; + case 6: /* Backlight Control Structure */ + if (mxms_version(dev) >= 0x0300) { + headerlen = 4; + recordlen = 8; + entries = (desc[1] & 0xf0) >> 4; + } else { + headerlen = 8; + } + break; + case 7: /* Fan Control Structure */ + headerlen = 8; + recordlen = 4; + entries = desc[1] & 0x07; + break; + default: + MXM_DBG(dev, "unknown descriptor type %d\n", type); + return false; + } + + if ((drm_debug & DRM_UT_DRIVER) && (exec == NULL)) { + static const char * mxms_desc_name[] = { + "ODS", "SCCS", "TS", "IPS", + "GSD", "VSS", "BCS", "FCS", + }; + u8 *dump = desc; + int i, j; + + MXM_DBG(dev, "%4s: ", mxms_desc_name[type]); + for (j = headerlen - 1; j >= 0; j--) + printk("%02x", dump[j]); + printk("\n"); + dump += headerlen; + + for (i = 0; i < entries; i++, dump += recordlen) { + MXM_DBG(dev, " "); + for (j = recordlen - 1; j >= 0; j--) + printk("%02x", dump[j]); + printk("\n"); + } + } + + if (types & (1 << type)) { + if (!exec(dev, desc, info)) + return false; + } + + desc += headerlen + (entries * recordlen); + } + + return true; +} + +static u8 * +mxm_table(struct drm_device *dev, u8 *size) +{ + struct bit_entry x; + + if (bit_table(dev, 'x', &x)) { + MXM_DBG(dev, "BIT 'x' table not present\n"); + return NULL; + } + + if (x.version != 1 || x.length < 3) { + MXM_MSG(dev, "BIT x table %d/%d unknown\n", + x.version, x.length); + return NULL; + } + + *size = x.length; + return x.data; +} + +/* These map MXM v2.x digital connection values to the appropriate SOR/link, + * hopefully they're correct for all boards within the same chipset... + * + * MXM v3.x VBIOS are nicer and provide pointers to these tables. + */ +static u8 nv84_sor_map[16] = { + 0x00, 0x12, 0x22, 0x11, 0x32, 0x31, 0x11, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static u8 nv92_sor_map[16] = { + 0x00, 0x12, 0x22, 0x11, 0x32, 0x31, 0x11, 0x31, + 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static u8 nv94_sor_map[16] = { + 0x00, 0x14, 0x24, 0x11, 0x34, 0x31, 0x11, 0x31, + 0x11, 0x31, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static u8 nv96_sor_map[16] = { + 0x00, 0x14, 0x24, 0x00, 0x34, 0x00, 0x11, 0x31, + 0x11, 0x31, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static u8 nv98_sor_map[16] = { + 0x00, 0x14, 0x12, 0x11, 0x00, 0x31, 0x11, 0x31, + 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static u8 +mxm_sor_map(struct drm_device *dev, u8 conn) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u8 len, *mxm = mxm_table(dev, &len); + if (mxm && len >= 6) { + u8 *map = ROMPTR(dev, mxm[4]); + if (map) { + if (map[0] == 0x10) { + if (conn < map[3]) + return map[map[1] + conn]; + return 0x00; + } + + MXM_MSG(dev, "unknown sor map 0x%02x\n", map[0]); + } + } + + if (dev_priv->chipset == 0x84 || dev_priv->chipset == 0x86) + return nv84_sor_map[conn]; + if (dev_priv->chipset == 0x92) + return nv92_sor_map[conn]; + if (dev_priv->chipset == 0x94) + return nv94_sor_map[conn]; + if (dev_priv->chipset == 0x96) + return nv96_sor_map[conn]; + if (dev_priv->chipset == 0x98) + return nv98_sor_map[conn]; + + MXM_MSG(dev, "missing sor map\n"); + return 0x00; +} + +static u8 +mxm_ddc_map(struct drm_device *dev, u8 port) +{ + u8 len, *mxm = mxm_table(dev, &len); + if (mxm && len >= 8) { + u8 *map = ROMPTR(dev, mxm[6]); + if (map) { + if (map[0] == 0x10) { + if (port < map[3]) + return map[map[1] + port]; + return 0x00; + } + + MXM_MSG(dev, "unknown ddc map 0x%02x\n", map[0]); + } + } + + /* v2.x: directly write port as dcb i2cidx */ + return (port << 4) | port; +} + +struct mxms_odev { + u8 outp_type; + u8 conn_type; + u8 ddc_port; + u8 dig_conn; +}; + +static void +mxms_output_device(struct drm_device *dev, u8 *pdata, struct mxms_odev *desc) +{ + u64 data = ROM32(pdata[0]); + if (mxms_version(dev) >= 0x0300) + data |= (u64)ROM16(pdata[4]) << 32; + + desc->outp_type = (data & 0x00000000000000f0ULL) >> 4; + desc->ddc_port = (data & 0x0000000000000f00ULL) >> 8; + desc->conn_type = (data & 0x000000000001f000ULL) >> 12; + desc->dig_conn = (data & 0x0000000000780000ULL) >> 19; +} + +struct context { + u32 *outp; + struct mxms_odev desc; +}; + +static bool +mxm_match_tmds_partner(struct drm_device *dev, u8 *data, void *info) +{ + struct context *ctx = info; + struct mxms_odev desc; + + mxms_output_device(dev, data, &desc); + if (desc.outp_type == 2 && + desc.dig_conn == ctx->desc.dig_conn) + return false; + return true; +} + +static bool +mxm_match_dcb(struct drm_device *dev, u8 *data, void *info) +{ + struct context *ctx = info; + u64 desc = *(u64 *)data; + + mxms_output_device(dev, data, &ctx->desc); + + /* match dcb encoder type to mxm-ods device type */ + if ((ctx->outp[0] & 0x0000000f) != ctx->desc.outp_type) + return true; + + /* digital output, have some extra stuff to match here, there's a + * table in the vbios that provides a mapping from the mxm digital + * connection enum values to SOR/link + */ + if ((desc & 0x00000000000000f0) >= 0x20) { + /* check against sor index */ + u8 link = mxm_sor_map(dev, ctx->desc.dig_conn); + if ((ctx->outp[0] & 0x0f000000) != (link & 0x0f) << 24) + return true; + + /* check dcb entry has a compatible link field */ + link = (link & 0x30) >> 4; + if ((link & ((ctx->outp[1] & 0x00000030) >> 4)) != link) + return true; + } + + /* mark this descriptor accounted for by setting invalid device type, + * except of course some manufactures don't follow specs properly and + * we need to avoid killing off the TMDS function on DP connectors + * if MXM-SIS is missing an entry for it. + */ + data[0] &= ~0xf0; + if (ctx->desc.outp_type == 6 && ctx->desc.conn_type == 6 && + mxms_foreach(dev, 0x01, mxm_match_tmds_partner, ctx)) { + data[0] |= 0x20; /* modify descriptor to match TMDS now */ + } else { + data[0] |= 0xf0; + } + + return false; +} + +static int +mxm_dcb_sanitise_entry(struct drm_device *dev, void *data, int idx, u8 *dcbe) +{ + struct context ctx = { .outp = (u32 *)dcbe }; + u8 type, i2cidx, link; + u8 *conn; + + /* look for an output device structure that matches this dcb entry. + * if one isn't found, disable it. + */ + if (mxms_foreach(dev, 0x01, mxm_match_dcb, &ctx)) { + MXM_DBG(dev, "disable %d: 0x%08x 0x%08x\n", + idx, ctx.outp[0], ctx.outp[1]); + ctx.outp[0] |= 0x0000000f; + return 0; + } + + /* modify the output's ddc/aux port, there's a pointer to a table + * with the mapping from mxm ddc/aux port to dcb i2c_index in the + * vbios mxm table + */ + i2cidx = mxm_ddc_map(dev, ctx.desc.ddc_port); + if ((ctx.outp[0] & 0x0000000f) != OUTPUT_DP) + i2cidx = (i2cidx & 0x0f) << 4; + else + i2cidx = (i2cidx & 0xf0); + + if (i2cidx != 0xf0) { + ctx.outp[0] &= ~0x000000f0; + ctx.outp[0] |= i2cidx; + } + + /* override dcb sorconf.link, based on what mxm data says */ + switch (ctx.desc.outp_type) { + case 0x00: /* Analog CRT */ + case 0x01: /* Analog TV/HDTV */ + break; + default: + link = mxm_sor_map(dev, ctx.desc.dig_conn) & 0x30; + ctx.outp[1] &= ~0x00000030; + ctx.outp[1] |= link; + break; + } + + /* we may need to fixup various other vbios tables based on what + * the descriptor says the connector type should be. + * + * in a lot of cases, the vbios tables will claim DVI-I is possible, + * and the mxm data says the connector is really HDMI. another + * common example is DP->eDP. + */ + conn = dcb_conn(dev, (ctx.outp[0] & 0x0000f000) >> 12); + type = conn[0]; + switch (ctx.desc.conn_type) { + case 0x01: /* LVDS */ + ctx.outp[1] |= 0x00000004; /* use_power_scripts */ + /* XXX: modify default link width in LVDS table */ + break; + case 0x02: /* HDMI */ + type = DCB_CONNECTOR_HDMI_1; + break; + case 0x03: /* DVI-D */ + type = DCB_CONNECTOR_DVI_D; + break; + case 0x0e: /* eDP, falls through to DPint */ + ctx.outp[1] |= 0x00010000; + case 0x07: /* DP internal, wtf is this?? HP8670w */ + ctx.outp[1] |= 0x00000004; /* use_power_scripts? */ + type = DCB_CONNECTOR_eDP; + break; + default: + break; + } + + if (mxms_version(dev) >= 0x0300) + conn[0] = type; + + return 0; +} + +static bool +mxm_show_unmatched(struct drm_device *dev, u8 *data, void *info) +{ + u64 desc = *(u64 *)data; + if ((desc & 0xf0) != 0xf0) + MXM_MSG(dev, "unmatched output device 0x%016llx\n", desc); + return true; +} + +static void +mxm_dcb_sanitise(struct drm_device *dev) +{ + u8 *dcb = dcb_table(dev); + if (!dcb || dcb[0] != 0x40) { + MXM_DBG(dev, "unsupported DCB version\n"); + return; + } + + dcb_outp_foreach(dev, NULL, mxm_dcb_sanitise_entry); + mxms_foreach(dev, 0x01, mxm_show_unmatched, NULL); +} + +static bool +mxm_shadow_rom_fetch(struct nouveau_i2c_chan *i2c, u8 addr, + u8 offset, u8 size, u8 *data) +{ + struct i2c_msg msgs[] = { + { .addr = addr, .flags = 0, .len = 1, .buf = &offset }, + { .addr = addr, .flags = I2C_M_RD, .len = size, .buf = data, }, + }; + + return i2c_transfer(&i2c->adapter, msgs, 2) == 2; +} + +static bool +mxm_shadow_rom(struct drm_device *dev, u8 version) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_i2c_chan *i2c = NULL; + u8 i2cidx, mxms[6], addr, size; + + i2cidx = mxm_ddc_map(dev, 1 /* LVDS_DDC */) & 0x0f; + if (i2cidx < 0x0f) + i2c = nouveau_i2c_find(dev, i2cidx); + if (!i2c) + return false; + + addr = 0x54; + if (!mxm_shadow_rom_fetch(i2c, addr, 0, 6, mxms)) { + addr = 0x56; + if (!mxm_shadow_rom_fetch(i2c, addr, 0, 6, mxms)) + return false; + } + + dev_priv->mxms = mxms; + size = mxms_headerlen(dev) + mxms_structlen(dev); + dev_priv->mxms = kmalloc(size, GFP_KERNEL); + + if (dev_priv->mxms && + mxm_shadow_rom_fetch(i2c, addr, 0, size, dev_priv->mxms)) + return true; + + kfree(dev_priv->mxms); + dev_priv->mxms = NULL; + return false; +} + +#if defined(CONFIG_ACPI) +static bool +mxm_shadow_dsm(struct drm_device *dev, u8 version) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + static char muid[] = { + 0x00, 0xA4, 0x04, 0x40, 0x7D, 0x91, 0xF2, 0x4C, + 0xB8, 0x9C, 0x79, 0xB6, 0x2F, 0xD5, 0x56, 0x65 + }; + u32 mxms_args[] = { 0x00000000 }; + union acpi_object args[4] = { + /* _DSM MUID */ + { .buffer.type = 3, + .buffer.length = sizeof(muid), + .buffer.pointer = muid, + }, + /* spec says this can be zero to mean "highest revision", but + * of course there's at least one bios out there which fails + * unless you pass in exactly the version it supports.. + */ + { .integer.type = ACPI_TYPE_INTEGER, + .integer.value = (version & 0xf0) << 4 | (version & 0x0f), + }, + /* MXMS function */ + { .integer.type = ACPI_TYPE_INTEGER, + .integer.value = 0x00000010, + }, + /* Pointer to MXMS arguments */ + { .buffer.type = ACPI_TYPE_BUFFER, + .buffer.length = sizeof(mxms_args), + .buffer.pointer = (char *)mxms_args, + }, + }; + struct acpi_object_list list = { ARRAY_SIZE(args), args }; + struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_handle handle; + int ret; + + handle = DEVICE_ACPI_HANDLE(&dev->pdev->dev); + if (!handle) + return false; + + ret = acpi_evaluate_object(handle, "_DSM", &list, &retn); + if (ret) { + MXM_DBG(dev, "DSM MXMS failed: %d\n", ret); + return false; + } + + obj = retn.pointer; + if (obj->type == ACPI_TYPE_BUFFER) { + dev_priv->mxms = kmemdup(obj->buffer.pointer, + obj->buffer.length, GFP_KERNEL); + } else + if (obj->type == ACPI_TYPE_INTEGER) { + MXM_DBG(dev, "DSM MXMS returned 0x%llx\n", obj->integer.value); + } + + kfree(obj); + return dev_priv->mxms != NULL; +} +#endif + +#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE) + +#define WMI_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0" + +static u8 +wmi_wmmx_mxmi(struct drm_device *dev, u8 version) +{ + u32 mxmi_args[] = { 0x494D584D /* MXMI */, version, 0 }; + struct acpi_buffer args = { sizeof(mxmi_args), mxmi_args }; + struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn); + if (ACPI_FAILURE(status)) { + MXM_DBG(dev, "WMMX MXMI returned %d\n", status); + return 0x00; + } + + obj = retn.pointer; + if (obj->type == ACPI_TYPE_INTEGER) { + version = obj->integer.value; + MXM_DBG(dev, "WMMX MXMI version %d.%d\n", + (version >> 4), version & 0x0f); + } else { + version = 0; + MXM_DBG(dev, "WMMX MXMI returned non-integer\n"); + } + + kfree(obj); + return version; +} + +static bool +mxm_shadow_wmi(struct drm_device *dev, u8 version) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 mxms_args[] = { 0x534D584D /* MXMS */, version, 0 }; + struct acpi_buffer args = { sizeof(mxms_args), mxms_args }; + struct acpi_buffer retn = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + if (!wmi_has_guid(WMI_WMMX_GUID)) { + MXM_DBG(dev, "WMMX GUID not found\n"); + return false; + } + + mxms_args[1] = wmi_wmmx_mxmi(dev, 0x00); + if (!mxms_args[1]) + mxms_args[1] = wmi_wmmx_mxmi(dev, version); + if (!mxms_args[1]) + return false; + + status = wmi_evaluate_method(WMI_WMMX_GUID, 0, 0, &args, &retn); + if (ACPI_FAILURE(status)) { + MXM_DBG(dev, "WMMX MXMS returned %d\n", status); + return false; + } + + obj = retn.pointer; + if (obj->type == ACPI_TYPE_BUFFER) { + dev_priv->mxms = kmemdup(obj->buffer.pointer, + obj->buffer.length, GFP_KERNEL); + } + + kfree(obj); + return dev_priv->mxms != NULL; +} +#endif + +struct mxm_shadow_h { + const char *name; + bool (*exec)(struct drm_device *, u8 version); +} _mxm_shadow[] = { + { "ROM", mxm_shadow_rom }, +#if defined(CONFIG_ACPI) + { "DSM", mxm_shadow_dsm }, +#endif +#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE) + { "WMI", mxm_shadow_wmi }, +#endif + {} +}; + +static int +mxm_shadow(struct drm_device *dev, u8 version) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct mxm_shadow_h *shadow = _mxm_shadow; + do { + MXM_DBG(dev, "checking %s\n", shadow->name); + if (shadow->exec(dev, version)) { + if (mxms_valid(dev)) + return 0; + kfree(dev_priv->mxms); + dev_priv->mxms = NULL; + } + } while ((++shadow)->name); + return -ENOENT; +} + +int +nouveau_mxm_init(struct drm_device *dev) +{ + u8 mxm_size, *mxm = mxm_table(dev, &mxm_size); + if (!mxm || !mxm[0]) { + MXM_MSG(dev, "no VBIOS data, nothing to do\n"); + return 0; + } + + MXM_MSG(dev, "BIOS version %d.%d\n", mxm[0] >> 4, mxm[0] & 0x0f); + + if (mxm_shadow(dev, mxm[0])) { + MXM_MSG(dev, "failed to locate valid SIS\n"); +#if 0 + /* we should, perhaps, fall back to some kind of limited + * mode here if the x86 vbios hasn't already done the + * work for us (so we prevent loading with completely + * whacked vbios tables). + */ + return -EINVAL; +#else + return 0; +#endif + } + + MXM_MSG(dev, "MXMS Version %d.%d\n", + mxms_version(dev) >> 8, mxms_version(dev) & 0xff); + mxms_foreach(dev, 0, NULL, NULL); + + if (nouveau_mxmdcb) + mxm_dcb_sanitise(dev); + return 0; +} + +void +nouveau_mxm_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + kfree(dev_priv->mxms); + dev_priv->mxms = NULL; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_notifier.c b/drivers/gpu/drm/nouveau/nouveau_notifier.c new file mode 100644 index 00000000..2ef883c4 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_notifier.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" + +int +nouveau_notifier_init_channel(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_bo *ntfy = NULL; + uint32_t flags, ttmpl; + int ret; + + if (nouveau_vram_notify) { + flags = NOUVEAU_GEM_DOMAIN_VRAM; + ttmpl = TTM_PL_FLAG_VRAM; + } else { + flags = NOUVEAU_GEM_DOMAIN_GART; + ttmpl = TTM_PL_FLAG_TT; + } + + ret = nouveau_gem_new(dev, PAGE_SIZE, 0, flags, 0, 0, &ntfy); + if (ret) + return ret; + + ret = nouveau_bo_pin(ntfy, ttmpl); + if (ret) + goto out_err; + + ret = nouveau_bo_map(ntfy); + if (ret) + goto out_err; + + if (dev_priv->card_type >= NV_50) { + ret = nouveau_bo_vma_add(ntfy, chan->vm, &chan->notifier_vma); + if (ret) + goto out_err; + } + + ret = drm_mm_init(&chan->notifier_heap, 0, ntfy->bo.mem.size); + if (ret) + goto out_err; + + chan->notifier_bo = ntfy; +out_err: + if (ret) { + nouveau_bo_vma_del(ntfy, &chan->notifier_vma); + drm_gem_object_unreference_unlocked(ntfy->gem); + } + + return ret; +} + +void +nouveau_notifier_takedown_channel(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + + if (!chan->notifier_bo) + return; + + nouveau_bo_vma_del(chan->notifier_bo, &chan->notifier_vma); + nouveau_bo_unmap(chan->notifier_bo); + mutex_lock(&dev->struct_mutex); + nouveau_bo_unpin(chan->notifier_bo); + mutex_unlock(&dev->struct_mutex); + drm_gem_object_unreference_unlocked(chan->notifier_bo->gem); + drm_mm_takedown(&chan->notifier_heap); +} + +static void +nouveau_notifier_gpuobj_dtor(struct drm_device *dev, + struct nouveau_gpuobj *gpuobj) +{ + NV_DEBUG(dev, "\n"); + + if (gpuobj->priv) + drm_mm_put_block(gpuobj->priv); +} + +int +nouveau_notifier_alloc(struct nouveau_channel *chan, uint32_t handle, + int size, uint32_t start, uint32_t end, + uint32_t *b_offset) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *nobj = NULL; + struct drm_mm_node *mem; + uint64_t offset; + int target, ret; + + mem = drm_mm_search_free_in_range(&chan->notifier_heap, size, 0, + start, end, 0); + if (mem) + mem = drm_mm_get_block_range(mem, size, 0, start, end); + if (!mem) { + NV_ERROR(dev, "Channel %d notifier block full\n", chan->id); + return -ENOMEM; + } + + if (dev_priv->card_type < NV_50) { + if (chan->notifier_bo->bo.mem.mem_type == TTM_PL_VRAM) + target = NV_MEM_TARGET_VRAM; + else + target = NV_MEM_TARGET_GART; + offset = chan->notifier_bo->bo.offset; + } else { + target = NV_MEM_TARGET_VM; + offset = chan->notifier_vma.offset; + } + offset += mem->start; + + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, offset, + mem->size, NV_MEM_ACCESS_RW, target, + &nobj); + if (ret) { + drm_mm_put_block(mem); + NV_ERROR(dev, "Error creating notifier ctxdma: %d\n", ret); + return ret; + } + nobj->dtor = nouveau_notifier_gpuobj_dtor; + nobj->priv = mem; + + ret = nouveau_ramht_insert(chan, handle, nobj); + nouveau_gpuobj_ref(NULL, &nobj); + if (ret) { + drm_mm_put_block(mem); + NV_ERROR(dev, "Error adding notifier to ramht: %d\n", ret); + return ret; + } + + *b_offset = mem->start; + return 0; +} + +int +nouveau_notifier_offset(struct nouveau_gpuobj *nobj, uint32_t *poffset) +{ + if (!nobj || nobj->dtor != nouveau_notifier_gpuobj_dtor) + return -EINVAL; + + if (poffset) { + struct drm_mm_node *mem = nobj->priv; + + if (*poffset >= mem->size) + return false; + + *poffset += mem->start; + } + + return 0; +} + +int +nouveau_ioctl_notifier_alloc(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_nouveau_notifierobj_alloc *na = data; + struct nouveau_channel *chan; + int ret; + + /* completely unnecessary for these chipsets... */ + if (unlikely(dev_priv->card_type >= NV_C0)) + return -EINVAL; + + chan = nouveau_channel_get(file_priv, na->channel); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + ret = nouveau_notifier_alloc(chan, na->handle, na->size, 0, 0x1000, + &na->offset); + nouveau_channel_put(&chan); + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_object.c b/drivers/gpu/drm/nouveau/nouveau_object.c new file mode 100644 index 00000000..cc419fae --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_object.c @@ -0,0 +1,1045 @@ +/* + * Copyright (C) 2006 Ben Skeggs. + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* + * Authors: + * Ben Skeggs <darktama@iinet.net.au> + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_ramht.h" +#include "nouveau_vm.h" +#include "nv50_display.h" + +struct nouveau_gpuobj_method { + struct list_head head; + u32 mthd; + int (*exec)(struct nouveau_channel *, u32 class, u32 mthd, u32 data); +}; + +struct nouveau_gpuobj_class { + struct list_head head; + struct list_head methods; + u32 id; + u32 engine; +}; + +int +nouveau_gpuobj_class_new(struct drm_device *dev, u32 class, u32 engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj_class *oc; + + oc = kzalloc(sizeof(*oc), GFP_KERNEL); + if (!oc) + return -ENOMEM; + + INIT_LIST_HEAD(&oc->methods); + oc->id = class; + oc->engine = engine; + list_add(&oc->head, &dev_priv->classes); + return 0; +} + +int +nouveau_gpuobj_mthd_new(struct drm_device *dev, u32 class, u32 mthd, + int (*exec)(struct nouveau_channel *, u32, u32, u32)) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj_method *om; + struct nouveau_gpuobj_class *oc; + + list_for_each_entry(oc, &dev_priv->classes, head) { + if (oc->id == class) + goto found; + } + + return -EINVAL; + +found: + om = kzalloc(sizeof(*om), GFP_KERNEL); + if (!om) + return -ENOMEM; + + om->mthd = mthd; + om->exec = exec; + list_add(&om->head, &oc->methods); + return 0; +} + +int +nouveau_gpuobj_mthd_call(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_gpuobj_method *om; + struct nouveau_gpuobj_class *oc; + + list_for_each_entry(oc, &dev_priv->classes, head) { + if (oc->id != class) + continue; + + list_for_each_entry(om, &oc->methods, head) { + if (om->mthd == mthd) + return om->exec(chan, class, mthd, data); + } + } + + return -ENOENT; +} + +int +nouveau_gpuobj_mthd_call2(struct drm_device *dev, int chid, + u32 class, u32 mthd, u32 data) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = NULL; + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&dev_priv->channels.lock, flags); + if (chid >= 0 && chid < dev_priv->engine.fifo.channels) + chan = dev_priv->channels.ptr[chid]; + if (chan) + ret = nouveau_gpuobj_mthd_call(chan, class, mthd, data); + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + return ret; +} + +/* NVidia uses context objects to drive drawing operations. + + Context objects can be selected into 8 subchannels in the FIFO, + and then used via DMA command buffers. + + A context object is referenced by a user defined handle (CARD32). The HW + looks up graphics objects in a hash table in the instance RAM. + + An entry in the hash table consists of 2 CARD32. The first CARD32 contains + the handle, the second one a bitfield, that contains the address of the + object in instance RAM. + + The format of the second CARD32 seems to be: + + NV4 to NV30: + + 15: 0 instance_addr >> 4 + 17:16 engine (here uses 1 = graphics) + 28:24 channel id (here uses 0) + 31 valid (use 1) + + NV40: + + 15: 0 instance_addr >> 4 (maybe 19-0) + 21:20 engine (here uses 1 = graphics) + I'm unsure about the other bits, but using 0 seems to work. + + The key into the hash table depends on the object handle and channel id and + is given as: +*/ + +int +nouveau_gpuobj_new(struct drm_device *dev, struct nouveau_channel *chan, + uint32_t size, int align, uint32_t flags, + struct nouveau_gpuobj **gpuobj_ret) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *instmem = &dev_priv->engine.instmem; + struct nouveau_gpuobj *gpuobj; + struct drm_mm_node *ramin = NULL; + int ret, i; + + NV_DEBUG(dev, "ch%d size=%u align=%d flags=0x%08x\n", + chan ? chan->id : -1, size, align, flags); + + gpuobj = kzalloc(sizeof(*gpuobj), GFP_KERNEL); + if (!gpuobj) + return -ENOMEM; + NV_DEBUG(dev, "gpuobj %p\n", gpuobj); + gpuobj->dev = dev; + gpuobj->flags = flags; + kref_init(&gpuobj->refcount); + gpuobj->size = size; + + spin_lock(&dev_priv->ramin_lock); + list_add_tail(&gpuobj->list, &dev_priv->gpuobj_list); + spin_unlock(&dev_priv->ramin_lock); + + if (!(flags & NVOBJ_FLAG_VM) && chan) { + ramin = drm_mm_search_free(&chan->ramin_heap, size, align, 0); + if (ramin) + ramin = drm_mm_get_block(ramin, size, align); + if (!ramin) { + nouveau_gpuobj_ref(NULL, &gpuobj); + return -ENOMEM; + } + + gpuobj->pinst = chan->ramin->pinst; + if (gpuobj->pinst != ~0) + gpuobj->pinst += ramin->start; + + gpuobj->cinst = ramin->start; + gpuobj->vinst = ramin->start + chan->ramin->vinst; + gpuobj->node = ramin; + } else { + ret = instmem->get(gpuobj, chan, size, align); + if (ret) { + nouveau_gpuobj_ref(NULL, &gpuobj); + return ret; + } + + ret = -ENOSYS; + if (!(flags & NVOBJ_FLAG_DONT_MAP)) + ret = instmem->map(gpuobj); + if (ret) + gpuobj->pinst = ~0; + + gpuobj->cinst = NVOBJ_CINST_GLOBAL; + } + + if (gpuobj->flags & NVOBJ_FLAG_ZERO_ALLOC) { + for (i = 0; i < gpuobj->size; i += 4) + nv_wo32(gpuobj, i, 0); + instmem->flush(dev); + } + + + *gpuobj_ret = gpuobj; + return 0; +} + +int +nouveau_gpuobj_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + NV_DEBUG(dev, "\n"); + + INIT_LIST_HEAD(&dev_priv->gpuobj_list); + INIT_LIST_HEAD(&dev_priv->classes); + spin_lock_init(&dev_priv->ramin_lock); + dev_priv->ramin_base = ~0; + + return 0; +} + +void +nouveau_gpuobj_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj_method *om, *tm; + struct nouveau_gpuobj_class *oc, *tc; + + NV_DEBUG(dev, "\n"); + + list_for_each_entry_safe(oc, tc, &dev_priv->classes, head) { + list_for_each_entry_safe(om, tm, &oc->methods, head) { + list_del(&om->head); + kfree(om); + } + list_del(&oc->head); + kfree(oc); + } + + BUG_ON(!list_empty(&dev_priv->gpuobj_list)); +} + + +static void +nouveau_gpuobj_del(struct kref *ref) +{ + struct nouveau_gpuobj *gpuobj = + container_of(ref, struct nouveau_gpuobj, refcount); + struct drm_device *dev = gpuobj->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *instmem = &dev_priv->engine.instmem; + int i; + + NV_DEBUG(dev, "gpuobj %p\n", gpuobj); + + if (gpuobj->node && (gpuobj->flags & NVOBJ_FLAG_ZERO_FREE)) { + for (i = 0; i < gpuobj->size; i += 4) + nv_wo32(gpuobj, i, 0); + instmem->flush(dev); + } + + if (gpuobj->dtor) + gpuobj->dtor(dev, gpuobj); + + if (gpuobj->cinst == NVOBJ_CINST_GLOBAL) { + if (gpuobj->node) { + instmem->unmap(gpuobj); + instmem->put(gpuobj); + } + } else { + if (gpuobj->node) { + spin_lock(&dev_priv->ramin_lock); + drm_mm_put_block(gpuobj->node); + spin_unlock(&dev_priv->ramin_lock); + } + } + + spin_lock(&dev_priv->ramin_lock); + list_del(&gpuobj->list); + spin_unlock(&dev_priv->ramin_lock); + + kfree(gpuobj); +} + +void +nouveau_gpuobj_ref(struct nouveau_gpuobj *ref, struct nouveau_gpuobj **ptr) +{ + if (ref) + kref_get(&ref->refcount); + + if (*ptr) + kref_put(&(*ptr)->refcount, nouveau_gpuobj_del); + + *ptr = ref; +} + +int +nouveau_gpuobj_new_fake(struct drm_device *dev, u32 pinst, u64 vinst, + u32 size, u32 flags, struct nouveau_gpuobj **pgpuobj) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gpuobj = NULL; + int i; + + NV_DEBUG(dev, + "pinst=0x%08x vinst=0x%010llx size=0x%08x flags=0x%08x\n", + pinst, vinst, size, flags); + + gpuobj = kzalloc(sizeof(*gpuobj), GFP_KERNEL); + if (!gpuobj) + return -ENOMEM; + NV_DEBUG(dev, "gpuobj %p\n", gpuobj); + gpuobj->dev = dev; + gpuobj->flags = flags; + kref_init(&gpuobj->refcount); + gpuobj->size = size; + gpuobj->pinst = pinst; + gpuobj->cinst = NVOBJ_CINST_GLOBAL; + gpuobj->vinst = vinst; + + if (gpuobj->flags & NVOBJ_FLAG_ZERO_ALLOC) { + for (i = 0; i < gpuobj->size; i += 4) + nv_wo32(gpuobj, i, 0); + dev_priv->engine.instmem.flush(dev); + } + + spin_lock(&dev_priv->ramin_lock); + list_add_tail(&gpuobj->list, &dev_priv->gpuobj_list); + spin_unlock(&dev_priv->ramin_lock); + *pgpuobj = gpuobj; + return 0; +} + +/* + DMA objects are used to reference a piece of memory in the + framebuffer, PCI or AGP address space. Each object is 16 bytes big + and looks as follows: + + entry[0] + 11:0 class (seems like I can always use 0 here) + 12 page table present? + 13 page entry linear? + 15:14 access: 0 rw, 1 ro, 2 wo + 17:16 target: 0 NV memory, 1 NV memory tiled, 2 PCI, 3 AGP + 31:20 dma adjust (bits 0-11 of the address) + entry[1] + dma limit (size of transfer) + entry[X] + 1 0 readonly, 1 readwrite + 31:12 dma frame address of the page (bits 12-31 of the address) + entry[N] + page table terminator, same value as the first pte, as does nvidia + rivatv uses 0xffffffff + + Non linear page tables need a list of frame addresses afterwards, + the rivatv project has some info on this. + + The method below creates a DMA object in instance RAM and returns a handle + to it that can be used to set up context objects. +*/ + +void +nv50_gpuobj_dma_init(struct nouveau_gpuobj *obj, u32 offset, int class, + u64 base, u64 size, int target, int access, + u32 type, u32 comp) +{ + struct drm_nouveau_private *dev_priv = obj->dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + u32 flags0; + + flags0 = (comp << 29) | (type << 22) | class; + flags0 |= 0x00100000; + + switch (access) { + case NV_MEM_ACCESS_RO: flags0 |= 0x00040000; break; + case NV_MEM_ACCESS_RW: + case NV_MEM_ACCESS_WO: flags0 |= 0x00080000; break; + default: + break; + } + + switch (target) { + case NV_MEM_TARGET_VRAM: + flags0 |= 0x00010000; + break; + case NV_MEM_TARGET_PCI: + flags0 |= 0x00020000; + break; + case NV_MEM_TARGET_PCI_NOSNOOP: + flags0 |= 0x00030000; + break; + case NV_MEM_TARGET_GART: + base += dev_priv->gart_info.aper_base; + default: + flags0 &= ~0x00100000; + break; + } + + /* convert to base + limit */ + size = (base + size) - 1; + + nv_wo32(obj, offset + 0x00, flags0); + nv_wo32(obj, offset + 0x04, lower_32_bits(size)); + nv_wo32(obj, offset + 0x08, lower_32_bits(base)); + nv_wo32(obj, offset + 0x0c, upper_32_bits(size) << 24 | + upper_32_bits(base)); + nv_wo32(obj, offset + 0x10, 0x00000000); + nv_wo32(obj, offset + 0x14, 0x00000000); + + pinstmem->flush(obj->dev); +} + +int +nv50_gpuobj_dma_new(struct nouveau_channel *chan, int class, u64 base, u64 size, + int target, int access, u32 type, u32 comp, + struct nouveau_gpuobj **pobj) +{ + struct drm_device *dev = chan->dev; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 24, 16, NVOBJ_FLAG_ZERO_FREE, pobj); + if (ret) + return ret; + + nv50_gpuobj_dma_init(*pobj, 0, class, base, size, target, + access, type, comp); + return 0; +} + +int +nouveau_gpuobj_dma_new(struct nouveau_channel *chan, int class, u64 base, + u64 size, int access, int target, + struct nouveau_gpuobj **pobj) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct drm_device *dev = chan->dev; + struct nouveau_gpuobj *obj; + u32 flags0, flags2; + int ret; + + if (dev_priv->card_type >= NV_50) { + u32 comp = (target == NV_MEM_TARGET_VM) ? NV_MEM_COMP_VM : 0; + u32 type = (target == NV_MEM_TARGET_VM) ? NV_MEM_TYPE_VM : 0; + + return nv50_gpuobj_dma_new(chan, class, base, size, + target, access, type, comp, pobj); + } + + if (target == NV_MEM_TARGET_GART) { + struct nouveau_gpuobj *gart = dev_priv->gart_info.sg_ctxdma; + + if (dev_priv->gart_info.type == NOUVEAU_GART_PDMA) { + if (base == 0) { + nouveau_gpuobj_ref(gart, pobj); + return 0; + } + + base = nouveau_sgdma_get_physical(dev, base); + target = NV_MEM_TARGET_PCI; + } else { + base += dev_priv->gart_info.aper_base; + if (dev_priv->gart_info.type == NOUVEAU_GART_AGP) + target = NV_MEM_TARGET_PCI_NOSNOOP; + else + target = NV_MEM_TARGET_PCI; + } + } + + flags0 = class; + flags0 |= 0x00003000; /* PT present, PT linear */ + flags2 = 0; + + switch (target) { + case NV_MEM_TARGET_PCI: + flags0 |= 0x00020000; + break; + case NV_MEM_TARGET_PCI_NOSNOOP: + flags0 |= 0x00030000; + break; + default: + break; + } + + switch (access) { + case NV_MEM_ACCESS_RO: + flags0 |= 0x00004000; + break; + case NV_MEM_ACCESS_WO: + flags0 |= 0x00008000; + default: + flags2 |= 0x00000002; + break; + } + + flags0 |= (base & 0x00000fff) << 20; + flags2 |= (base & 0xfffff000); + + ret = nouveau_gpuobj_new(dev, chan, 16, 16, NVOBJ_FLAG_ZERO_FREE, &obj); + if (ret) + return ret; + + nv_wo32(obj, 0x00, flags0); + nv_wo32(obj, 0x04, size - 1); + nv_wo32(obj, 0x08, flags2); + nv_wo32(obj, 0x0c, flags2); + + obj->engine = NVOBJ_ENGINE_SW; + obj->class = class; + *pobj = obj; + return 0; +} + +/* Context objects in the instance RAM have the following structure. + * On NV40 they are 32 byte long, on NV30 and smaller 16 bytes. + + NV4 - NV30: + + entry[0] + 11:0 class + 12 chroma key enable + 13 user clip enable + 14 swizzle enable + 17:15 patch config: + scrcopy_and, rop_and, blend_and, scrcopy, srccopy_pre, blend_pre + 18 synchronize enable + 19 endian: 1 big, 0 little + 21:20 dither mode + 23 single step enable + 24 patch status: 0 invalid, 1 valid + 25 context_surface 0: 1 valid + 26 context surface 1: 1 valid + 27 context pattern: 1 valid + 28 context rop: 1 valid + 29,30 context beta, beta4 + entry[1] + 7:0 mono format + 15:8 color format + 31:16 notify instance address + entry[2] + 15:0 dma 0 instance address + 31:16 dma 1 instance address + entry[3] + dma method traps + + NV40: + No idea what the exact format is. Here's what can be deducted: + + entry[0]: + 11:0 class (maybe uses more bits here?) + 17 user clip enable + 21:19 patch config + 25 patch status valid ? + entry[1]: + 15:0 DMA notifier (maybe 20:0) + entry[2]: + 15:0 DMA 0 instance (maybe 20:0) + 24 big endian + entry[3]: + 15:0 DMA 1 instance (maybe 20:0) + entry[4]: + entry[5]: + set to 0? +*/ +static int +nouveau_gpuobj_sw_new(struct nouveau_channel *chan, u32 handle, u16 class) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_gpuobj *gpuobj; + int ret; + + gpuobj = kzalloc(sizeof(*gpuobj), GFP_KERNEL); + if (!gpuobj) + return -ENOMEM; + gpuobj->dev = chan->dev; + gpuobj->engine = NVOBJ_ENGINE_SW; + gpuobj->class = class; + kref_init(&gpuobj->refcount); + gpuobj->cinst = 0x40; + + spin_lock(&dev_priv->ramin_lock); + list_add_tail(&gpuobj->list, &dev_priv->gpuobj_list); + spin_unlock(&dev_priv->ramin_lock); + + ret = nouveau_ramht_insert(chan, handle, gpuobj); + nouveau_gpuobj_ref(NULL, &gpuobj); + return ret; +} + +int +nouveau_gpuobj_gr_new(struct nouveau_channel *chan, u32 handle, int class) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct drm_device *dev = chan->dev; + struct nouveau_gpuobj_class *oc; + int ret; + + NV_DEBUG(dev, "ch%d class=0x%04x\n", chan->id, class); + + list_for_each_entry(oc, &dev_priv->classes, head) { + struct nouveau_exec_engine *eng = dev_priv->eng[oc->engine]; + + if (oc->id != class) + continue; + + if (oc->engine == NVOBJ_ENGINE_SW) + return nouveau_gpuobj_sw_new(chan, handle, class); + + if (!chan->engctx[oc->engine]) { + ret = eng->context_new(chan, oc->engine); + if (ret) + return ret; + } + + return eng->object_new(chan, oc->engine, handle, class); + } + + NV_ERROR(dev, "illegal object class: 0x%x\n", class); + return -EINVAL; +} + +static int +nouveau_gpuobj_channel_init_pramin(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t size; + uint32_t base; + int ret; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + /* Base amount for object storage (4KiB enough?) */ + size = 0x2000; + base = 0; + + if (dev_priv->card_type == NV_50) { + /* Various fixed table thingos */ + size += 0x1400; /* mostly unknown stuff */ + size += 0x4000; /* vm pd */ + base = 0x6000; + /* RAMHT, not sure about setting size yet, 32KiB to be safe */ + size += 0x8000; + /* RAMFC */ + size += 0x1000; + } + + ret = nouveau_gpuobj_new(dev, NULL, size, 0x1000, 0, &chan->ramin); + if (ret) { + NV_ERROR(dev, "Error allocating channel PRAMIN: %d\n", ret); + return ret; + } + + ret = drm_mm_init(&chan->ramin_heap, base, size - base); + if (ret) { + NV_ERROR(dev, "Error creating PRAMIN heap: %d\n", ret); + nouveau_gpuobj_ref(NULL, &chan->ramin); + return ret; + } + + return 0; +} + +static int +nvc0_gpuobj_channel_init(struct nouveau_channel *chan, struct nouveau_vm *vm) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct drm_device *dev = chan->dev; + struct nouveau_gpuobj *pgd = NULL; + struct nouveau_vm_pgd *vpgd; + int ret, i; + + ret = nouveau_gpuobj_new(dev, NULL, 4096, 0x1000, 0, &chan->ramin); + if (ret) + return ret; + + /* create page directory for this vm if none currently exists, + * will be destroyed automagically when last reference to the + * vm is removed + */ + if (list_empty(&vm->pgd_list)) { + ret = nouveau_gpuobj_new(dev, NULL, 65536, 0x1000, 0, &pgd); + if (ret) + return ret; + } + nouveau_vm_ref(vm, &chan->vm, pgd); + nouveau_gpuobj_ref(NULL, &pgd); + + /* point channel at vm's page directory */ + vpgd = list_first_entry(&vm->pgd_list, struct nouveau_vm_pgd, head); + nv_wo32(chan->ramin, 0x0200, lower_32_bits(vpgd->obj->vinst)); + nv_wo32(chan->ramin, 0x0204, upper_32_bits(vpgd->obj->vinst)); + nv_wo32(chan->ramin, 0x0208, 0xffffffff); + nv_wo32(chan->ramin, 0x020c, 0x000000ff); + + /* map display semaphore buffers into channel's vm */ + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct nouveau_bo *bo; + if (dev_priv->card_type >= NV_D0) + bo = nvd0_display_crtc_sema(dev, i); + else + bo = nv50_display(dev)->crtc[i].sem.bo; + + ret = nouveau_bo_vma_add(bo, chan->vm, &chan->dispc_vma[i]); + if (ret) + return ret; + } + + return 0; +} + +int +nouveau_gpuobj_channel_init(struct nouveau_channel *chan, + uint32_t vram_h, uint32_t tt_h) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fpriv *fpriv = nouveau_fpriv(chan->file_priv); + struct nouveau_vm *vm = fpriv ? fpriv->vm : dev_priv->chan_vm; + struct nouveau_gpuobj *vram = NULL, *tt = NULL; + int ret, i; + + NV_DEBUG(dev, "ch%d vram=0x%08x tt=0x%08x\n", chan->id, vram_h, tt_h); + if (dev_priv->card_type >= NV_C0) + return nvc0_gpuobj_channel_init(chan, vm); + + /* Allocate a chunk of memory for per-channel object storage */ + ret = nouveau_gpuobj_channel_init_pramin(chan); + if (ret) { + NV_ERROR(dev, "init pramin\n"); + return ret; + } + + /* NV50 VM + * - Allocate per-channel page-directory + * - Link with shared channel VM + */ + if (vm) { + u32 pgd_offs = (dev_priv->chipset == 0x50) ? 0x1400 : 0x0200; + u64 vm_vinst = chan->ramin->vinst + pgd_offs; + u32 vm_pinst = chan->ramin->pinst; + + if (vm_pinst != ~0) + vm_pinst += pgd_offs; + + ret = nouveau_gpuobj_new_fake(dev, vm_pinst, vm_vinst, 0x4000, + 0, &chan->vm_pd); + if (ret) + return ret; + + nouveau_vm_ref(vm, &chan->vm, chan->vm_pd); + } + + /* RAMHT */ + if (dev_priv->card_type < NV_50) { + nouveau_ramht_ref(dev_priv->ramht, &chan->ramht, NULL); + } else { + struct nouveau_gpuobj *ramht = NULL; + + ret = nouveau_gpuobj_new(dev, chan, 0x8000, 16, + NVOBJ_FLAG_ZERO_ALLOC, &ramht); + if (ret) + return ret; + + ret = nouveau_ramht_new(dev, ramht, &chan->ramht); + nouveau_gpuobj_ref(NULL, &ramht); + if (ret) + return ret; + + /* dma objects for display sync channel semaphore blocks */ + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct nouveau_gpuobj *sem = NULL; + struct nv50_display_crtc *dispc = + &nv50_display(dev)->crtc[i]; + u64 offset = dispc->sem.bo->bo.offset; + + ret = nouveau_gpuobj_dma_new(chan, 0x3d, offset, 0xfff, + NV_MEM_ACCESS_RW, + NV_MEM_TARGET_VRAM, &sem); + if (ret) + return ret; + + ret = nouveau_ramht_insert(chan, NvEvoSema0 + i, sem); + nouveau_gpuobj_ref(NULL, &sem); + if (ret) + return ret; + } + } + + /* VRAM ctxdma */ + if (dev_priv->card_type >= NV_50) { + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, + 0, (1ULL << 40), NV_MEM_ACCESS_RW, + NV_MEM_TARGET_VM, &vram); + if (ret) { + NV_ERROR(dev, "Error creating VRAM ctxdma: %d\n", ret); + return ret; + } + } else { + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, + 0, dev_priv->fb_available_size, + NV_MEM_ACCESS_RW, + NV_MEM_TARGET_VRAM, &vram); + if (ret) { + NV_ERROR(dev, "Error creating VRAM ctxdma: %d\n", ret); + return ret; + } + } + + ret = nouveau_ramht_insert(chan, vram_h, vram); + nouveau_gpuobj_ref(NULL, &vram); + if (ret) { + NV_ERROR(dev, "Error adding VRAM ctxdma to RAMHT: %d\n", ret); + return ret; + } + + /* TT memory ctxdma */ + if (dev_priv->card_type >= NV_50) { + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, + 0, (1ULL << 40), NV_MEM_ACCESS_RW, + NV_MEM_TARGET_VM, &tt); + } else { + ret = nouveau_gpuobj_dma_new(chan, NV_CLASS_DMA_IN_MEMORY, + 0, dev_priv->gart_info.aper_size, + NV_MEM_ACCESS_RW, + NV_MEM_TARGET_GART, &tt); + } + + if (ret) { + NV_ERROR(dev, "Error creating TT ctxdma: %d\n", ret); + return ret; + } + + ret = nouveau_ramht_insert(chan, tt_h, tt); + nouveau_gpuobj_ref(NULL, &tt); + if (ret) { + NV_ERROR(dev, "Error adding TT ctxdma to RAMHT: %d\n", ret); + return ret; + } + + return 0; +} + +void +nouveau_gpuobj_channel_takedown(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + if (dev_priv->card_type >= NV_D0) { + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct nouveau_bo *bo = nvd0_display_crtc_sema(dev, i); + nouveau_bo_vma_del(bo, &chan->dispc_vma[i]); + } + } else + if (dev_priv->card_type >= NV_50) { + struct nv50_display *disp = nv50_display(dev); + for (i = 0; i < dev->mode_config.num_crtc; i++) { + struct nv50_display_crtc *dispc = &disp->crtc[i]; + nouveau_bo_vma_del(dispc->sem.bo, &chan->dispc_vma[i]); + } + } + + nouveau_vm_ref(NULL, &chan->vm, chan->vm_pd); + nouveau_gpuobj_ref(NULL, &chan->vm_pd); + + if (drm_mm_initialized(&chan->ramin_heap)) + drm_mm_takedown(&chan->ramin_heap); + nouveau_gpuobj_ref(NULL, &chan->ramin); +} + +int +nouveau_gpuobj_suspend(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gpuobj; + int i; + + list_for_each_entry(gpuobj, &dev_priv->gpuobj_list, list) { + if (gpuobj->cinst != NVOBJ_CINST_GLOBAL) + continue; + + gpuobj->suspend = vmalloc(gpuobj->size); + if (!gpuobj->suspend) { + nouveau_gpuobj_resume(dev); + return -ENOMEM; + } + + for (i = 0; i < gpuobj->size; i += 4) + gpuobj->suspend[i/4] = nv_ro32(gpuobj, i); + } + + return 0; +} + +void +nouveau_gpuobj_resume(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gpuobj; + int i; + + list_for_each_entry(gpuobj, &dev_priv->gpuobj_list, list) { + if (!gpuobj->suspend) + continue; + + for (i = 0; i < gpuobj->size; i += 4) + nv_wo32(gpuobj, i, gpuobj->suspend[i/4]); + + vfree(gpuobj->suspend); + gpuobj->suspend = NULL; + } + + dev_priv->engine.instmem.flush(dev); +} + +int nouveau_ioctl_grobj_alloc(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_grobj_alloc *init = data; + struct nouveau_channel *chan; + int ret; + + if (init->handle == ~0) + return -EINVAL; + + chan = nouveau_channel_get(file_priv, init->channel); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + if (nouveau_ramht_find(chan, init->handle)) { + ret = -EEXIST; + goto out; + } + + ret = nouveau_gpuobj_gr_new(chan, init->handle, init->class); + if (ret) { + NV_ERROR(dev, "Error creating object: %d (%d/0x%08x)\n", + ret, init->channel, init->handle); + } + +out: + nouveau_channel_put(&chan); + return ret; +} + +int nouveau_ioctl_gpuobj_free(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_gpuobj_free *objfree = data; + struct nouveau_channel *chan; + int ret; + + chan = nouveau_channel_get(file_priv, objfree->channel); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + /* Synchronize with the user channel */ + nouveau_channel_idle(chan); + + ret = nouveau_ramht_remove(chan, objfree->handle); + nouveau_channel_put(&chan); + return ret; +} + +u32 +nv_ro32(struct nouveau_gpuobj *gpuobj, u32 offset) +{ + struct drm_nouveau_private *dev_priv = gpuobj->dev->dev_private; + struct drm_device *dev = gpuobj->dev; + unsigned long flags; + + if (gpuobj->pinst == ~0 || !dev_priv->ramin_available) { + u64 ptr = gpuobj->vinst + offset; + u32 base = ptr >> 16; + u32 val; + + spin_lock_irqsave(&dev_priv->vm_lock, flags); + if (dev_priv->ramin_base != base) { + dev_priv->ramin_base = base; + nv_wr32(dev, 0x001700, dev_priv->ramin_base); + } + val = nv_rd32(dev, 0x700000 + (ptr & 0xffff)); + spin_unlock_irqrestore(&dev_priv->vm_lock, flags); + return val; + } + + return nv_ri32(dev, gpuobj->pinst + offset); +} + +void +nv_wo32(struct nouveau_gpuobj *gpuobj, u32 offset, u32 val) +{ + struct drm_nouveau_private *dev_priv = gpuobj->dev->dev_private; + struct drm_device *dev = gpuobj->dev; + unsigned long flags; + + if (gpuobj->pinst == ~0 || !dev_priv->ramin_available) { + u64 ptr = gpuobj->vinst + offset; + u32 base = ptr >> 16; + + spin_lock_irqsave(&dev_priv->vm_lock, flags); + if (dev_priv->ramin_base != base) { + dev_priv->ramin_base = base; + nv_wr32(dev, 0x001700, dev_priv->ramin_base); + } + nv_wr32(dev, 0x700000 + (ptr & 0xffff), val); + spin_unlock_irqrestore(&dev_priv->vm_lock, flags); + return; + } + + nv_wi32(dev, gpuobj->pinst + offset, val); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_perf.c b/drivers/gpu/drm/nouveau/nouveau_perf.c new file mode 100644 index 00000000..69a528d1 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_perf.c @@ -0,0 +1,415 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" + +static u8 * +nouveau_perf_table(struct drm_device *dev, u8 *ver) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct bit_entry P; + + if (!bit_table(dev, 'P', &P) && P.version && P.version <= 2) { + u8 *perf = ROMPTR(dev, P.data[0]); + if (perf) { + *ver = perf[0]; + return perf; + } + } + + if (bios->type == NVBIOS_BMP) { + if (bios->data[bios->offset + 6] >= 0x25) { + u8 *perf = ROMPTR(dev, bios->data[bios->offset + 0x94]); + if (perf) { + *ver = perf[1]; + return perf; + } + } + } + + return NULL; +} + +static u8 * +nouveau_perf_entry(struct drm_device *dev, int idx, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len) +{ + u8 *perf = nouveau_perf_table(dev, ver); + if (perf) { + if (*ver >= 0x12 && *ver < 0x20 && idx < perf[2]) { + *hdr = perf[3]; + *cnt = 0; + *len = 0; + return perf + perf[0] + idx * perf[3]; + } else + if (*ver >= 0x20 && *ver < 0x40 && idx < perf[2]) { + *hdr = perf[3]; + *cnt = perf[4]; + *len = perf[5]; + return perf + perf[1] + idx * (*hdr + (*cnt * *len)); + } else + if (*ver >= 0x40 && *ver < 0x41 && idx < perf[5]) { + *hdr = perf[2]; + *cnt = perf[4]; + *len = perf[3]; + return perf + perf[1] + idx * (*hdr + (*cnt * *len)); + } + } + return NULL; +} + +static u8 * +nouveau_perf_rammap(struct drm_device *dev, u32 freq, + u8 *ver, u8 *hdr, u8 *cnt, u8 *len) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct bit_entry P; + u8 *perf, i = 0; + + if (!bit_table(dev, 'P', &P) && P.version == 2) { + u8 *rammap = ROMPTR(dev, P.data[4]); + if (rammap) { + u8 *ramcfg = rammap + rammap[1]; + + *ver = rammap[0]; + *hdr = rammap[2]; + *cnt = rammap[4]; + *len = rammap[3]; + + freq /= 1000; + for (i = 0; i < rammap[5]; i++) { + if (freq >= ROM16(ramcfg[0]) && + freq <= ROM16(ramcfg[2])) + return ramcfg; + + ramcfg += *hdr + (*cnt * *len); + } + } + + return NULL; + } + + if (dev_priv->chipset == 0x49 || + dev_priv->chipset == 0x4b) + freq /= 2; + + while ((perf = nouveau_perf_entry(dev, i++, ver, hdr, cnt, len))) { + if (*ver >= 0x20 && *ver < 0x25) { + if (perf[0] != 0xff && freq <= ROM16(perf[11]) * 1000) + break; + } else + if (*ver >= 0x25 && *ver < 0x40) { + if (perf[0] != 0xff && freq <= ROM16(perf[12]) * 1000) + break; + } + } + + if (perf) { + u8 *ramcfg = perf + *hdr; + *ver = 0x00; + *hdr = 0; + return ramcfg; + } + + return NULL; +} + +u8 * +nouveau_perf_ramcfg(struct drm_device *dev, u32 freq, u8 *ver, u8 *len) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + u8 strap, hdr, cnt; + u8 *rammap; + + strap = (nv_rd32(dev, 0x101000) & 0x0000003c) >> 2; + if (bios->ram_restrict_tbl_ptr) + strap = bios->data[bios->ram_restrict_tbl_ptr + strap]; + + rammap = nouveau_perf_rammap(dev, freq, ver, &hdr, &cnt, len); + if (rammap && strap < cnt) + return rammap + hdr + (strap * *len); + + return NULL; +} + +u8 * +nouveau_perf_timing(struct drm_device *dev, u32 freq, u8 *ver, u8 *len) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct bit_entry P; + u8 *perf, *timing = NULL; + u8 i = 0, hdr, cnt; + + if (bios->type == NVBIOS_BMP) { + while ((perf = nouveau_perf_entry(dev, i++, ver, &hdr, &cnt, + len)) && *ver == 0x15) { + if (freq <= ROM32(perf[5]) * 20) { + *ver = 0x00; + *len = 14; + return perf + 41; + } + } + return NULL; + } + + if (!bit_table(dev, 'P', &P)) { + if (P.version == 1) + timing = ROMPTR(dev, P.data[4]); + else + if (P.version == 2) + timing = ROMPTR(dev, P.data[8]); + } + + if (timing && timing[0] == 0x10) { + u8 *ramcfg = nouveau_perf_ramcfg(dev, freq, ver, len); + if (ramcfg && ramcfg[1] < timing[2]) { + *ver = timing[0]; + *len = timing[3]; + return timing + timing[1] + (ramcfg[1] * timing[3]); + } + } + + return NULL; +} + +static void +legacy_perf_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + char *perf, *entry, *bmp = &bios->data[bios->offset]; + int headerlen, use_straps; + + if (bmp[5] < 0x5 || bmp[6] < 0x14) { + NV_DEBUG(dev, "BMP version too old for perf\n"); + return; + } + + perf = ROMPTR(dev, bmp[0x73]); + if (!perf) { + NV_DEBUG(dev, "No memclock table pointer found.\n"); + return; + } + + switch (perf[0]) { + case 0x12: + case 0x14: + case 0x18: + use_straps = 0; + headerlen = 1; + break; + case 0x01: + use_straps = perf[1] & 1; + headerlen = (use_straps ? 8 : 2); + break; + default: + NV_WARN(dev, "Unknown memclock table version %x.\n", perf[0]); + return; + } + + entry = perf + headerlen; + if (use_straps) + entry += (nv_rd32(dev, NV_PEXTDEV_BOOT_0) & 0x3c) >> 1; + + sprintf(pm->perflvl[0].name, "performance_level_0"); + pm->perflvl[0].memory = ROM16(entry[0]) * 20; + pm->nr_perflvl = 1; +} + +static void +nouveau_perf_voltage(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct bit_entry P; + u8 *vmap; + int id; + + id = perflvl->volt_min; + perflvl->volt_min = 0; + + /* boards using voltage table version <0x40 store the voltage + * level directly in the perflvl entry as a multiple of 10mV + */ + if (dev_priv->engine.pm.voltage.version < 0x40) { + perflvl->volt_min = id * 10000; + perflvl->volt_max = perflvl->volt_min; + return; + } + + /* on newer ones, the perflvl stores an index into yet another + * vbios table containing a min/max voltage value for the perflvl + */ + if (bit_table(dev, 'P', &P) || P.version != 2 || P.length < 34) { + NV_DEBUG(dev, "where's our volt map table ptr? %d %d\n", + P.version, P.length); + return; + } + + vmap = ROMPTR(dev, P.data[32]); + if (!vmap) { + NV_DEBUG(dev, "volt map table pointer invalid\n"); + return; + } + + if (id < vmap[3]) { + vmap += vmap[1] + (vmap[2] * id); + perflvl->volt_min = ROM32(vmap[0]); + perflvl->volt_max = ROM32(vmap[4]); + } +} + +void +nouveau_perf_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nvbios *bios = &dev_priv->vbios; + u8 *perf, ver, hdr, cnt, len; + int ret, vid, i = -1; + + if (bios->type == NVBIOS_BMP && bios->data[bios->offset + 6] < 0x25) { + legacy_perf_init(dev); + return; + } + + perf = nouveau_perf_table(dev, &ver); + if (ver >= 0x20 && ver < 0x40) + pm->fan.pwm_divisor = ROM16(perf[6]); + + while ((perf = nouveau_perf_entry(dev, ++i, &ver, &hdr, &cnt, &len))) { + struct nouveau_pm_level *perflvl = &pm->perflvl[pm->nr_perflvl]; + + if (perf[0] == 0xff) + continue; + + switch (ver) { + case 0x12: + case 0x13: + case 0x15: + perflvl->fanspeed = perf[55]; + if (hdr > 56) + perflvl->volt_min = perf[56]; + perflvl->core = ROM32(perf[1]) * 10; + perflvl->memory = ROM32(perf[5]) * 20; + break; + case 0x21: + case 0x23: + case 0x24: + perflvl->fanspeed = perf[4]; + perflvl->volt_min = perf[5]; + perflvl->shader = ROM16(perf[6]) * 1000; + perflvl->core = perflvl->shader; + perflvl->core += (signed char)perf[8] * 1000; + if (dev_priv->chipset == 0x49 || + dev_priv->chipset == 0x4b) + perflvl->memory = ROM16(perf[11]) * 1000; + else + perflvl->memory = ROM16(perf[11]) * 2000; + break; + case 0x25: + perflvl->fanspeed = perf[4]; + perflvl->volt_min = perf[5]; + perflvl->core = ROM16(perf[6]) * 1000; + perflvl->shader = ROM16(perf[10]) * 1000; + perflvl->memory = ROM16(perf[12]) * 1000; + break; + case 0x30: + perflvl->memscript = ROM16(perf[2]); + case 0x35: + perflvl->fanspeed = perf[6]; + perflvl->volt_min = perf[7]; + perflvl->core = ROM16(perf[8]) * 1000; + perflvl->shader = ROM16(perf[10]) * 1000; + perflvl->memory = ROM16(perf[12]) * 1000; + perflvl->vdec = ROM16(perf[16]) * 1000; + perflvl->dom6 = ROM16(perf[20]) * 1000; + break; + case 0x40: +#define subent(n) ((ROM16(perf[hdr + (n) * len]) & 0xfff) * 1000) + perflvl->fanspeed = 0; /*XXX*/ + perflvl->volt_min = perf[2]; + if (dev_priv->card_type == NV_50) { + perflvl->core = subent(0); + perflvl->shader = subent(1); + perflvl->memory = subent(2); + perflvl->vdec = subent(3); + perflvl->unka0 = subent(4); + } else { + perflvl->hub06 = subent(0); + perflvl->hub01 = subent(1); + perflvl->copy = subent(2); + perflvl->shader = subent(3); + perflvl->rop = subent(4); + perflvl->memory = subent(5); + perflvl->vdec = subent(6); + perflvl->daemon = subent(10); + perflvl->hub07 = subent(11); + perflvl->core = perflvl->shader / 2; + } + break; + } + + /* make sure vid is valid */ + nouveau_perf_voltage(dev, perflvl); + if (pm->voltage.supported && perflvl->volt_min) { + vid = nouveau_volt_vid_lookup(dev, perflvl->volt_min); + if (vid < 0) { + NV_DEBUG(dev, "perflvl %d, bad vid\n", i); + continue; + } + } + + /* get the corresponding memory timings */ + ret = nouveau_mem_timing_calc(dev, perflvl->memory, + &perflvl->timing); + if (ret) { + NV_DEBUG(dev, "perflvl %d, bad timing: %d\n", i, ret); + continue; + } + + snprintf(perflvl->name, sizeof(perflvl->name), + "performance_level_%d", i); + perflvl->id = i; + + snprintf(perflvl->profile.name, sizeof(perflvl->profile.name), + "%d", perflvl->id); + perflvl->profile.func = &nouveau_pm_static_profile_func; + list_add_tail(&perflvl->profile.head, &pm->profiles); + + + pm->nr_perflvl++; + } +} + +void +nouveau_perf_fini(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nouveau_pm.c b/drivers/gpu/drm/nouveau/nouveau_pm.c new file mode 100644 index 00000000..da3e7c3a --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_pm.c @@ -0,0 +1,949 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" +#include "nouveau_gpio.h" + +#ifdef CONFIG_ACPI +#include <linux/acpi.h> +#endif +#include <linux/power_supply.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +static int +nouveau_pwmfan_get(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct gpio_func gpio; + u32 divs, duty; + int ret; + + if (!pm->pwm_get) + return -ENODEV; + + ret = nouveau_gpio_find(dev, 0, DCB_GPIO_PWM_FAN, 0xff, &gpio); + if (ret == 0) { + ret = pm->pwm_get(dev, gpio.line, &divs, &duty); + if (ret == 0 && divs) { + divs = max(divs, duty); + if (dev_priv->card_type <= NV_40 || (gpio.log[0] & 1)) + duty = divs - duty; + return (duty * 100) / divs; + } + + return nouveau_gpio_func_get(dev, gpio.func) * 100; + } + + return -ENODEV; +} + +static int +nouveau_pwmfan_set(struct drm_device *dev, int percent) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct gpio_func gpio; + u32 divs, duty; + int ret; + + if (!pm->pwm_set) + return -ENODEV; + + ret = nouveau_gpio_find(dev, 0, DCB_GPIO_PWM_FAN, 0xff, &gpio); + if (ret == 0) { + divs = pm->fan.pwm_divisor; + if (pm->fan.pwm_freq) { + /*XXX: PNVIO clock more than likely... */ + divs = 135000 / pm->fan.pwm_freq; + if (dev_priv->chipset < 0xa3) + divs /= 4; + } + + duty = ((divs * percent) + 99) / 100; + if (dev_priv->card_type <= NV_40 || (gpio.log[0] & 1)) + duty = divs - duty; + + ret = pm->pwm_set(dev, gpio.line, divs, duty); + if (!ret) + pm->fan.percent = percent; + return ret; + } + + return -ENODEV; +} + +static int +nouveau_pm_perflvl_aux(struct drm_device *dev, struct nouveau_pm_level *perflvl, + struct nouveau_pm_level *a, struct nouveau_pm_level *b) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + int ret; + + /*XXX: not on all boards, we should control based on temperature + * on recent boards.. or maybe on some other factor we don't + * know about? + */ + if (a->fanspeed && b->fanspeed && b->fanspeed > a->fanspeed) { + ret = nouveau_pwmfan_set(dev, perflvl->fanspeed); + if (ret && ret != -ENODEV) { + NV_ERROR(dev, "fanspeed set failed: %d\n", ret); + return ret; + } + } + + if (pm->voltage.supported && pm->voltage_set) { + if (perflvl->volt_min && b->volt_min > a->volt_min) { + ret = pm->voltage_set(dev, perflvl->volt_min); + if (ret) { + NV_ERROR(dev, "voltage set failed: %d\n", ret); + return ret; + } + } + } + + return 0; +} + +static int +nouveau_pm_perflvl_set(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + void *state; + int ret; + + if (perflvl == pm->cur) + return 0; + + ret = nouveau_pm_perflvl_aux(dev, perflvl, pm->cur, perflvl); + if (ret) + return ret; + + state = pm->clocks_pre(dev, perflvl); + if (IS_ERR(state)) { + ret = PTR_ERR(state); + goto error; + } + ret = pm->clocks_set(dev, state); + if (ret) + goto error; + + ret = nouveau_pm_perflvl_aux(dev, perflvl, perflvl, pm->cur); + if (ret) + return ret; + + pm->cur = perflvl; + return 0; + +error: + /* restore the fan speed and voltage before leaving */ + nouveau_pm_perflvl_aux(dev, perflvl, perflvl, pm->cur); + return ret; +} + +void +nouveau_pm_trigger(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_profile *profile = NULL; + struct nouveau_pm_level *perflvl = NULL; + int ret; + + /* select power profile based on current power source */ + if (power_supply_is_system_supplied()) + profile = pm->profile_ac; + else + profile = pm->profile_dc; + + if (profile != pm->profile) { + pm->profile->func->fini(pm->profile); + pm->profile = profile; + pm->profile->func->init(pm->profile); + } + + /* select performance level based on profile */ + perflvl = profile->func->select(profile); + + /* change perflvl, if necessary */ + if (perflvl != pm->cur) { + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + u64 time0 = ptimer->read(dev); + + NV_INFO(dev, "setting performance level: %d", perflvl->id); + ret = nouveau_pm_perflvl_set(dev, perflvl); + if (ret) + NV_INFO(dev, "> reclocking failed: %d\n\n", ret); + + NV_INFO(dev, "> reclocking took %lluns\n\n", + ptimer->read(dev) - time0); + } +} + +static struct nouveau_pm_profile * +profile_find(struct drm_device *dev, const char *string) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_profile *profile; + + list_for_each_entry(profile, &pm->profiles, head) { + if (!strncmp(profile->name, string, sizeof(profile->name))) + return profile; + } + + return NULL; +} + +static int +nouveau_pm_profile_set(struct drm_device *dev, const char *profile) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_profile *ac = NULL, *dc = NULL; + char string[16], *cur = string, *ptr; + + /* safety precaution, for now */ + if (nouveau_perflvl_wr != 7777) + return -EPERM; + + strncpy(string, profile, sizeof(string)); + string[sizeof(string) - 1] = 0; + if ((ptr = strchr(string, '\n'))) + *ptr = '\0'; + + ptr = strsep(&cur, ","); + if (ptr) + ac = profile_find(dev, ptr); + + ptr = strsep(&cur, ","); + if (ptr) + dc = profile_find(dev, ptr); + else + dc = ac; + + if (ac == NULL || dc == NULL) + return -EINVAL; + + pm->profile_ac = ac; + pm->profile_dc = dc; + nouveau_pm_trigger(dev); + return 0; +} + +static void +nouveau_pm_static_dummy(struct nouveau_pm_profile *profile) +{ +} + +static struct nouveau_pm_level * +nouveau_pm_static_select(struct nouveau_pm_profile *profile) +{ + return container_of(profile, struct nouveau_pm_level, profile); +} + +const struct nouveau_pm_profile_func nouveau_pm_static_profile_func = { + .destroy = nouveau_pm_static_dummy, + .init = nouveau_pm_static_dummy, + .fini = nouveau_pm_static_dummy, + .select = nouveau_pm_static_select, +}; + +static int +nouveau_pm_perflvl_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + int ret; + + memset(perflvl, 0, sizeof(*perflvl)); + + if (pm->clocks_get) { + ret = pm->clocks_get(dev, perflvl); + if (ret) + return ret; + } + + if (pm->voltage.supported && pm->voltage_get) { + ret = pm->voltage_get(dev); + if (ret > 0) { + perflvl->volt_min = ret; + perflvl->volt_max = ret; + } + } + + ret = nouveau_pwmfan_get(dev); + if (ret > 0) + perflvl->fanspeed = ret; + + nouveau_mem_timing_read(dev, &perflvl->timing); + return 0; +} + +static void +nouveau_pm_perflvl_info(struct nouveau_pm_level *perflvl, char *ptr, int len) +{ + char c[16], s[16], v[32], f[16], m[16]; + + c[0] = '\0'; + if (perflvl->core) + snprintf(c, sizeof(c), " core %dMHz", perflvl->core / 1000); + + s[0] = '\0'; + if (perflvl->shader) + snprintf(s, sizeof(s), " shader %dMHz", perflvl->shader / 1000); + + m[0] = '\0'; + if (perflvl->memory) + snprintf(m, sizeof(m), " memory %dMHz", perflvl->memory / 1000); + + v[0] = '\0'; + if (perflvl->volt_min && perflvl->volt_min != perflvl->volt_max) { + snprintf(v, sizeof(v), " voltage %dmV-%dmV", + perflvl->volt_min / 1000, perflvl->volt_max / 1000); + } else + if (perflvl->volt_min) { + snprintf(v, sizeof(v), " voltage %dmV", + perflvl->volt_min / 1000); + } + + f[0] = '\0'; + if (perflvl->fanspeed) + snprintf(f, sizeof(f), " fanspeed %d%%", perflvl->fanspeed); + + snprintf(ptr, len, "%s%s%s%s%s\n", c, s, m, v, f); +} + +static ssize_t +nouveau_pm_get_perflvl_info(struct device *d, + struct device_attribute *a, char *buf) +{ + struct nouveau_pm_level *perflvl = + container_of(a, struct nouveau_pm_level, dev_attr); + char *ptr = buf; + int len = PAGE_SIZE; + + snprintf(ptr, len, "%d:", perflvl->id); + ptr += strlen(buf); + len -= strlen(buf); + + nouveau_pm_perflvl_info(perflvl, ptr, len); + return strlen(buf); +} + +static ssize_t +nouveau_pm_get_perflvl(struct device *d, struct device_attribute *a, char *buf) +{ + struct drm_device *dev = pci_get_drvdata(to_pci_dev(d)); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_level cur; + int len = PAGE_SIZE, ret; + char *ptr = buf; + + snprintf(ptr, len, "profile: %s, %s\nc:", + pm->profile_ac->name, pm->profile_dc->name); + ptr += strlen(buf); + len -= strlen(buf); + + ret = nouveau_pm_perflvl_get(dev, &cur); + if (ret == 0) + nouveau_pm_perflvl_info(&cur, ptr, len); + return strlen(buf); +} + +static ssize_t +nouveau_pm_set_perflvl(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = pci_get_drvdata(to_pci_dev(d)); + int ret; + + ret = nouveau_pm_profile_set(dev, buf); + if (ret) + return ret; + return strlen(buf); +} + +static DEVICE_ATTR(performance_level, S_IRUGO | S_IWUSR, + nouveau_pm_get_perflvl, nouveau_pm_set_perflvl); + +static int +nouveau_sysfs_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct device *d = &dev->pdev->dev; + int ret, i; + + ret = device_create_file(d, &dev_attr_performance_level); + if (ret) + return ret; + + for (i = 0; i < pm->nr_perflvl; i++) { + struct nouveau_pm_level *perflvl = &pm->perflvl[i]; + + perflvl->dev_attr.attr.name = perflvl->name; + perflvl->dev_attr.attr.mode = S_IRUGO; + perflvl->dev_attr.show = nouveau_pm_get_perflvl_info; + perflvl->dev_attr.store = NULL; + sysfs_attr_init(&perflvl->dev_attr.attr); + + ret = device_create_file(d, &perflvl->dev_attr); + if (ret) { + NV_ERROR(dev, "failed pervlvl %d sysfs: %d\n", + perflvl->id, i); + perflvl->dev_attr.attr.name = NULL; + nouveau_pm_fini(dev); + return ret; + } + } + + return 0; +} + +static void +nouveau_sysfs_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct device *d = &dev->pdev->dev; + int i; + + device_remove_file(d, &dev_attr_performance_level); + for (i = 0; i < pm->nr_perflvl; i++) { + struct nouveau_pm_level *pl = &pm->perflvl[i]; + + if (!pl->dev_attr.attr.name) + break; + + device_remove_file(d, &pl->dev_attr); + } +} + +#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE)) +static ssize_t +nouveau_hwmon_show_temp(struct device *d, struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + + return snprintf(buf, PAGE_SIZE, "%d\n", pm->temp_get(dev)*1000); +} +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, nouveau_hwmon_show_temp, + NULL, 0); + +static ssize_t +nouveau_hwmon_max_temp(struct device *d, struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_threshold_temp *temp = &pm->threshold_temp; + + return snprintf(buf, PAGE_SIZE, "%d\n", temp->down_clock*1000); +} +static ssize_t +nouveau_hwmon_set_max_temp(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_threshold_temp *temp = &pm->threshold_temp; + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + temp->down_clock = value/1000; + + nouveau_temp_safety_checks(dev); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, nouveau_hwmon_max_temp, + nouveau_hwmon_set_max_temp, + 0); + +static ssize_t +nouveau_hwmon_critical_temp(struct device *d, struct device_attribute *a, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_threshold_temp *temp = &pm->threshold_temp; + + return snprintf(buf, PAGE_SIZE, "%d\n", temp->critical*1000); +} +static ssize_t +nouveau_hwmon_set_critical_temp(struct device *d, struct device_attribute *a, + const char *buf, + size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_threshold_temp *temp = &pm->threshold_temp; + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return count; + + temp->critical = value/1000; + + nouveau_temp_safety_checks(dev); + + return count; +} +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, + nouveau_hwmon_critical_temp, + nouveau_hwmon_set_critical_temp, + 0); + +static ssize_t nouveau_hwmon_show_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "nouveau\n"); +} +static SENSOR_DEVICE_ATTR(name, S_IRUGO, nouveau_hwmon_show_name, NULL, 0); + +static ssize_t nouveau_hwmon_show_update_rate(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "1000\n"); +} +static SENSOR_DEVICE_ATTR(update_rate, S_IRUGO, + nouveau_hwmon_show_update_rate, + NULL, 0); + +static ssize_t +nouveau_hwmon_show_fan0_input(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + struct gpio_func gpio; + u32 cycles, cur, prev; + u64 start; + int ret; + + ret = nouveau_gpio_find(dev, 0, DCB_GPIO_FAN_SENSE, 0xff, &gpio); + if (ret) + return ret; + + /* Monitor the GPIO input 0x3b for 250ms. + * When the fan spins, it changes the value of GPIO FAN_SENSE. + * We get 4 changes (0 -> 1 -> 0 -> 1 -> [...]) per complete rotation. + */ + start = ptimer->read(dev); + prev = nouveau_gpio_sense(dev, 0, gpio.line); + cycles = 0; + do { + cur = nouveau_gpio_sense(dev, 0, gpio.line); + if (prev != cur) { + cycles++; + prev = cur; + } + + usleep_range(500, 1000); /* supports 0 < rpm < 7500 */ + } while (ptimer->read(dev) - start < 250000000); + + /* interpolate to get rpm */ + return sprintf(buf, "%i\n", cycles / 4 * 4 * 60); +} +static SENSOR_DEVICE_ATTR(fan0_input, S_IRUGO, nouveau_hwmon_show_fan0_input, + NULL, 0); + +static ssize_t +nouveau_hwmon_get_pwm0(struct device *d, struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + int ret; + + ret = nouveau_pwmfan_get(dev); + if (ret < 0) + return ret; + + return sprintf(buf, "%i\n", ret); +} + +static ssize_t +nouveau_hwmon_set_pwm0(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + int ret = -ENODEV; + long value; + + if (nouveau_perflvl_wr != 7777) + return -EPERM; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return -EINVAL; + + if (value < pm->fan.min_duty) + value = pm->fan.min_duty; + if (value > pm->fan.max_duty) + value = pm->fan.max_duty; + + ret = nouveau_pwmfan_set(dev, value); + if (ret) + return ret; + + return count; +} + +static SENSOR_DEVICE_ATTR(pwm0, S_IRUGO | S_IWUSR, + nouveau_hwmon_get_pwm0, + nouveau_hwmon_set_pwm0, 0); + +static ssize_t +nouveau_hwmon_get_pwm0_min(struct device *d, + struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + + return sprintf(buf, "%i\n", pm->fan.min_duty); +} + +static ssize_t +nouveau_hwmon_set_pwm0_min(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return -EINVAL; + + if (value < 0) + value = 0; + + if (pm->fan.max_duty - value < 10) + value = pm->fan.max_duty - 10; + + if (value < 10) + pm->fan.min_duty = 10; + else + pm->fan.min_duty = value; + + return count; +} + +static SENSOR_DEVICE_ATTR(pwm0_min, S_IRUGO | S_IWUSR, + nouveau_hwmon_get_pwm0_min, + nouveau_hwmon_set_pwm0_min, 0); + +static ssize_t +nouveau_hwmon_get_pwm0_max(struct device *d, + struct device_attribute *a, char *buf) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + + return sprintf(buf, "%i\n", pm->fan.max_duty); +} + +static ssize_t +nouveau_hwmon_set_pwm0_max(struct device *d, struct device_attribute *a, + const char *buf, size_t count) +{ + struct drm_device *dev = dev_get_drvdata(d); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + long value; + + if (kstrtol(buf, 10, &value) == -EINVAL) + return -EINVAL; + + if (value < 0) + value = 0; + + if (value - pm->fan.min_duty < 10) + value = pm->fan.min_duty + 10; + + if (value > 100) + pm->fan.max_duty = 100; + else + pm->fan.max_duty = value; + + return count; +} + +static SENSOR_DEVICE_ATTR(pwm0_max, S_IRUGO | S_IWUSR, + nouveau_hwmon_get_pwm0_max, + nouveau_hwmon_set_pwm0_max, 0); + +static struct attribute *hwmon_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_update_rate.dev_attr.attr, + NULL +}; +static struct attribute *hwmon_fan_rpm_attributes[] = { + &sensor_dev_attr_fan0_input.dev_attr.attr, + NULL +}; +static struct attribute *hwmon_pwm_fan_attributes[] = { + &sensor_dev_attr_pwm0.dev_attr.attr, + &sensor_dev_attr_pwm0_min.dev_attr.attr, + &sensor_dev_attr_pwm0_max.dev_attr.attr, + NULL +}; + +static const struct attribute_group hwmon_attrgroup = { + .attrs = hwmon_attributes, +}; +static const struct attribute_group hwmon_fan_rpm_attrgroup = { + .attrs = hwmon_fan_rpm_attributes, +}; +static const struct attribute_group hwmon_pwm_fan_attrgroup = { + .attrs = hwmon_pwm_fan_attributes, +}; +#endif + +static int +nouveau_hwmon_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; +#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE)) + struct device *hwmon_dev; + int ret = 0; + + if (!pm->temp_get) + return -ENODEV; + + hwmon_dev = hwmon_device_register(&dev->pdev->dev); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + NV_ERROR(dev, + "Unable to register hwmon device: %d\n", ret); + return ret; + } + dev_set_drvdata(hwmon_dev, dev); + + /* default sysfs entries */ + ret = sysfs_create_group(&dev->pdev->dev.kobj, &hwmon_attrgroup); + if (ret) { + if (ret) + goto error; + } + + /* if the card has a pwm fan */ + /*XXX: incorrect, need better detection for this, some boards have + * the gpio entries for pwm fan control even when there's no + * actual fan connected to it... therm table? */ + if (nouveau_pwmfan_get(dev) >= 0) { + ret = sysfs_create_group(&dev->pdev->dev.kobj, + &hwmon_pwm_fan_attrgroup); + if (ret) + goto error; + } + + /* if the card can read the fan rpm */ + if (nouveau_gpio_func_valid(dev, DCB_GPIO_FAN_SENSE)) { + ret = sysfs_create_group(&dev->pdev->dev.kobj, + &hwmon_fan_rpm_attrgroup); + if (ret) + goto error; + } + + pm->hwmon = hwmon_dev; + + return 0; + +error: + NV_ERROR(dev, "Unable to create some hwmon sysfs files: %d\n", ret); + hwmon_device_unregister(hwmon_dev); + pm->hwmon = NULL; + return ret; +#else + pm->hwmon = NULL; + return 0; +#endif +} + +static void +nouveau_hwmon_fini(struct drm_device *dev) +{ +#if defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE)) + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + + if (pm->hwmon) { + sysfs_remove_group(&dev->pdev->dev.kobj, &hwmon_attrgroup); + sysfs_remove_group(&dev->pdev->dev.kobj, + &hwmon_pwm_fan_attrgroup); + sysfs_remove_group(&dev->pdev->dev.kobj, + &hwmon_fan_rpm_attrgroup); + + hwmon_device_unregister(pm->hwmon); + } +#endif +} + +#if defined(CONFIG_ACPI) && defined(CONFIG_POWER_SUPPLY) +static int +nouveau_pm_acpi_event(struct notifier_block *nb, unsigned long val, void *data) +{ + struct drm_nouveau_private *dev_priv = + container_of(nb, struct drm_nouveau_private, engine.pm.acpi_nb); + struct drm_device *dev = dev_priv->dev; + struct acpi_bus_event *entry = (struct acpi_bus_event *)data; + + if (strcmp(entry->device_class, "ac_adapter") == 0) { + bool ac = power_supply_is_system_supplied(); + + NV_DEBUG(dev, "power supply changed: %s\n", ac ? "AC" : "DC"); + nouveau_pm_trigger(dev); + } + + return NOTIFY_OK; +} +#endif + +int +nouveau_pm_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + char info[256]; + int ret, i; + + /* parse aux tables from vbios */ + nouveau_volt_init(dev); + nouveau_temp_init(dev); + + /* determine current ("boot") performance level */ + ret = nouveau_pm_perflvl_get(dev, &pm->boot); + if (ret) { + NV_ERROR(dev, "failed to determine boot perflvl\n"); + return ret; + } + + strncpy(pm->boot.name, "boot", 4); + strncpy(pm->boot.profile.name, "boot", 4); + pm->boot.profile.func = &nouveau_pm_static_profile_func; + + INIT_LIST_HEAD(&pm->profiles); + list_add(&pm->boot.profile.head, &pm->profiles); + + pm->profile_ac = &pm->boot.profile; + pm->profile_dc = &pm->boot.profile; + pm->profile = &pm->boot.profile; + pm->cur = &pm->boot; + + /* add performance levels from vbios */ + nouveau_perf_init(dev); + + /* display available performance levels */ + NV_INFO(dev, "%d available performance level(s)\n", pm->nr_perflvl); + for (i = 0; i < pm->nr_perflvl; i++) { + nouveau_pm_perflvl_info(&pm->perflvl[i], info, sizeof(info)); + NV_INFO(dev, "%d:%s", pm->perflvl[i].id, info); + } + + nouveau_pm_perflvl_info(&pm->boot, info, sizeof(info)); + NV_INFO(dev, "c:%s", info); + + /* switch performance levels now if requested */ + if (nouveau_perflvl != NULL) + nouveau_pm_profile_set(dev, nouveau_perflvl); + + /* determine the current fan speed */ + pm->fan.percent = nouveau_pwmfan_get(dev); + + nouveau_sysfs_init(dev); + nouveau_hwmon_init(dev); +#if defined(CONFIG_ACPI) && defined(CONFIG_POWER_SUPPLY) + pm->acpi_nb.notifier_call = nouveau_pm_acpi_event; + register_acpi_notifier(&pm->acpi_nb); +#endif + + return 0; +} + +void +nouveau_pm_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_profile *profile, *tmp; + + list_for_each_entry_safe(profile, tmp, &pm->profiles, head) { + list_del(&profile->head); + profile->func->destroy(profile); + } + + if (pm->cur != &pm->boot) + nouveau_pm_perflvl_set(dev, &pm->boot); + + nouveau_temp_fini(dev); + nouveau_perf_fini(dev); + nouveau_volt_fini(dev); + +#if defined(CONFIG_ACPI) && defined(CONFIG_POWER_SUPPLY) + unregister_acpi_notifier(&pm->acpi_nb); +#endif + nouveau_hwmon_fini(dev); + nouveau_sysfs_fini(dev); +} + +void +nouveau_pm_resume(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_level *perflvl; + + if (!pm->cur || pm->cur == &pm->boot) + return; + + perflvl = pm->cur; + pm->cur = &pm->boot; + nouveau_pm_perflvl_set(dev, perflvl); + nouveau_pwmfan_set(dev, pm->fan.percent); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_pm.h b/drivers/gpu/drm/nouveau/nouveau_pm.h new file mode 100644 index 00000000..3f82dfea --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_pm.h @@ -0,0 +1,107 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#ifndef __NOUVEAU_PM_H__ +#define __NOUVEAU_PM_H__ + +struct nouveau_mem_exec_func { + struct drm_device *dev; + void (*precharge)(struct nouveau_mem_exec_func *); + void (*refresh)(struct nouveau_mem_exec_func *); + void (*refresh_auto)(struct nouveau_mem_exec_func *, bool); + void (*refresh_self)(struct nouveau_mem_exec_func *, bool); + void (*wait)(struct nouveau_mem_exec_func *, u32 nsec); + u32 (*mrg)(struct nouveau_mem_exec_func *, int mr); + void (*mrs)(struct nouveau_mem_exec_func *, int mr, u32 data); + void (*clock_set)(struct nouveau_mem_exec_func *); + void (*timing_set)(struct nouveau_mem_exec_func *); + void *priv; +}; + +/* nouveau_mem.c */ +int nouveau_mem_exec(struct nouveau_mem_exec_func *, + struct nouveau_pm_level *); + +/* nouveau_pm.c */ +int nouveau_pm_init(struct drm_device *dev); +void nouveau_pm_fini(struct drm_device *dev); +void nouveau_pm_resume(struct drm_device *dev); +extern const struct nouveau_pm_profile_func nouveau_pm_static_profile_func; +void nouveau_pm_trigger(struct drm_device *dev); + +/* nouveau_volt.c */ +void nouveau_volt_init(struct drm_device *); +void nouveau_volt_fini(struct drm_device *); +int nouveau_volt_vid_lookup(struct drm_device *, int voltage); +int nouveau_volt_lvl_lookup(struct drm_device *, int vid); +int nouveau_voltage_gpio_get(struct drm_device *); +int nouveau_voltage_gpio_set(struct drm_device *, int voltage); + +/* nouveau_perf.c */ +void nouveau_perf_init(struct drm_device *); +void nouveau_perf_fini(struct drm_device *); +u8 *nouveau_perf_timing(struct drm_device *, u32 freq, u8 *ver, u8 *len); +u8 *nouveau_perf_ramcfg(struct drm_device *, u32 freq, u8 *ver, u8 *len); + +/* nouveau_mem.c */ +void nouveau_mem_timing_init(struct drm_device *); +void nouveau_mem_timing_fini(struct drm_device *); + +/* nv04_pm.c */ +int nv04_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *); +void *nv04_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *); +int nv04_pm_clocks_set(struct drm_device *, void *); + +/* nv40_pm.c */ +int nv40_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *); +void *nv40_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *); +int nv40_pm_clocks_set(struct drm_device *, void *); +int nv40_pm_pwm_get(struct drm_device *, int, u32 *, u32 *); +int nv40_pm_pwm_set(struct drm_device *, int, u32, u32); + +/* nv50_pm.c */ +int nv50_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *); +void *nv50_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *); +int nv50_pm_clocks_set(struct drm_device *, void *); +int nv50_pm_pwm_get(struct drm_device *, int, u32 *, u32 *); +int nv50_pm_pwm_set(struct drm_device *, int, u32, u32); + +/* nva3_pm.c */ +int nva3_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *); +void *nva3_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *); +int nva3_pm_clocks_set(struct drm_device *, void *); + +/* nvc0_pm.c */ +int nvc0_pm_clocks_get(struct drm_device *, struct nouveau_pm_level *); +void *nvc0_pm_clocks_pre(struct drm_device *, struct nouveau_pm_level *); +int nvc0_pm_clocks_set(struct drm_device *, void *); + +/* nouveau_temp.c */ +void nouveau_temp_init(struct drm_device *dev); +void nouveau_temp_fini(struct drm_device *dev); +void nouveau_temp_safety_checks(struct drm_device *dev); +int nv40_temp_get(struct drm_device *dev); +int nv84_temp_get(struct drm_device *dev); + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_ramht.c b/drivers/gpu/drm/nouveau/nouveau_ramht.c new file mode 100644 index 00000000..a24a81f5 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_ramht.c @@ -0,0 +1,309 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_ramht.h" + +static u32 +nouveau_ramht_hash_handle(struct nouveau_channel *chan, u32 handle) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_ramht *ramht = chan->ramht; + u32 hash = 0; + int i; + + NV_DEBUG(dev, "ch%d handle=0x%08x\n", chan->id, handle); + + for (i = 32; i > 0; i -= ramht->bits) { + hash ^= (handle & ((1 << ramht->bits) - 1)); + handle >>= ramht->bits; + } + + if (dev_priv->card_type < NV_50) + hash ^= chan->id << (ramht->bits - 4); + hash <<= 3; + + NV_DEBUG(dev, "hash=0x%08x\n", hash); + return hash; +} + +static int +nouveau_ramht_entry_valid(struct drm_device *dev, struct nouveau_gpuobj *ramht, + u32 offset) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 ctx = nv_ro32(ramht, offset + 4); + + if (dev_priv->card_type < NV_40) + return ((ctx & NV_RAMHT_CONTEXT_VALID) != 0); + return (ctx != 0); +} + +static int +nouveau_ramht_entry_same_channel(struct nouveau_channel *chan, + struct nouveau_gpuobj *ramht, u32 offset) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + u32 ctx = nv_ro32(ramht, offset + 4); + + if (dev_priv->card_type >= NV_50) + return true; + else if (dev_priv->card_type >= NV_40) + return chan->id == + ((ctx >> NV40_RAMHT_CONTEXT_CHANNEL_SHIFT) & 0x1f); + else + return chan->id == + ((ctx >> NV_RAMHT_CONTEXT_CHANNEL_SHIFT) & 0x1f); +} + +int +nouveau_ramht_insert(struct nouveau_channel *chan, u32 handle, + struct nouveau_gpuobj *gpuobj) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *instmem = &dev_priv->engine.instmem; + struct nouveau_ramht_entry *entry; + struct nouveau_gpuobj *ramht = chan->ramht->gpuobj; + unsigned long flags; + u32 ctx, co, ho; + + if (nouveau_ramht_find(chan, handle)) + return -EEXIST; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + entry->channel = chan; + entry->gpuobj = NULL; + entry->handle = handle; + nouveau_gpuobj_ref(gpuobj, &entry->gpuobj); + + if (dev_priv->card_type < NV_40) { + ctx = NV_RAMHT_CONTEXT_VALID | (gpuobj->pinst >> 4) | + (chan->id << NV_RAMHT_CONTEXT_CHANNEL_SHIFT) | + (gpuobj->engine << NV_RAMHT_CONTEXT_ENGINE_SHIFT); + } else + if (dev_priv->card_type < NV_50) { + ctx = (gpuobj->pinst >> 4) | + (chan->id << NV40_RAMHT_CONTEXT_CHANNEL_SHIFT) | + (gpuobj->engine << NV40_RAMHT_CONTEXT_ENGINE_SHIFT); + } else { + if (gpuobj->engine == NVOBJ_ENGINE_DISPLAY) { + ctx = (gpuobj->cinst << 10) | + (chan->id << 28) | + chan->id; /* HASH_TAG */ + } else { + ctx = (gpuobj->cinst >> 4) | + ((gpuobj->engine << + NV40_RAMHT_CONTEXT_ENGINE_SHIFT)); + } + } + + spin_lock_irqsave(&chan->ramht->lock, flags); + list_add(&entry->head, &chan->ramht->entries); + + co = ho = nouveau_ramht_hash_handle(chan, handle); + do { + if (!nouveau_ramht_entry_valid(dev, ramht, co)) { + NV_DEBUG(dev, + "insert ch%d 0x%08x: h=0x%08x, c=0x%08x\n", + chan->id, co, handle, ctx); + nv_wo32(ramht, co + 0, handle); + nv_wo32(ramht, co + 4, ctx); + + spin_unlock_irqrestore(&chan->ramht->lock, flags); + instmem->flush(dev); + return 0; + } + NV_DEBUG(dev, "collision ch%d 0x%08x: h=0x%08x\n", + chan->id, co, nv_ro32(ramht, co)); + + co += 8; + if (co >= ramht->size) + co = 0; + } while (co != ho); + + NV_ERROR(dev, "RAMHT space exhausted. ch=%d\n", chan->id); + list_del(&entry->head); + spin_unlock_irqrestore(&chan->ramht->lock, flags); + kfree(entry); + return -ENOMEM; +} + +static struct nouveau_ramht_entry * +nouveau_ramht_remove_entry(struct nouveau_channel *chan, u32 handle) +{ + struct nouveau_ramht *ramht = chan ? chan->ramht : NULL; + struct nouveau_ramht_entry *entry; + unsigned long flags; + + if (!ramht) + return NULL; + + spin_lock_irqsave(&ramht->lock, flags); + list_for_each_entry(entry, &ramht->entries, head) { + if (entry->channel == chan && + (!handle || entry->handle == handle)) { + list_del(&entry->head); + spin_unlock_irqrestore(&ramht->lock, flags); + + return entry; + } + } + spin_unlock_irqrestore(&ramht->lock, flags); + + return NULL; +} + +static void +nouveau_ramht_remove_hash(struct nouveau_channel *chan, u32 handle) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *instmem = &dev_priv->engine.instmem; + struct nouveau_gpuobj *ramht = chan->ramht->gpuobj; + unsigned long flags; + u32 co, ho; + + spin_lock_irqsave(&chan->ramht->lock, flags); + co = ho = nouveau_ramht_hash_handle(chan, handle); + do { + if (nouveau_ramht_entry_valid(dev, ramht, co) && + nouveau_ramht_entry_same_channel(chan, ramht, co) && + (handle == nv_ro32(ramht, co))) { + NV_DEBUG(dev, + "remove ch%d 0x%08x: h=0x%08x, c=0x%08x\n", + chan->id, co, handle, nv_ro32(ramht, co + 4)); + nv_wo32(ramht, co + 0, 0x00000000); + nv_wo32(ramht, co + 4, 0x00000000); + instmem->flush(dev); + goto out; + } + + co += 8; + if (co >= ramht->size) + co = 0; + } while (co != ho); + + NV_ERROR(dev, "RAMHT entry not found. ch=%d, handle=0x%08x\n", + chan->id, handle); +out: + spin_unlock_irqrestore(&chan->ramht->lock, flags); +} + +int +nouveau_ramht_remove(struct nouveau_channel *chan, u32 handle) +{ + struct nouveau_ramht_entry *entry; + + entry = nouveau_ramht_remove_entry(chan, handle); + if (!entry) + return -ENOENT; + + nouveau_ramht_remove_hash(chan, entry->handle); + nouveau_gpuobj_ref(NULL, &entry->gpuobj); + kfree(entry); + return 0; +} + +struct nouveau_gpuobj * +nouveau_ramht_find(struct nouveau_channel *chan, u32 handle) +{ + struct nouveau_ramht *ramht = chan->ramht; + struct nouveau_ramht_entry *entry; + struct nouveau_gpuobj *gpuobj = NULL; + unsigned long flags; + + if (unlikely(!chan->ramht)) + return NULL; + + spin_lock_irqsave(&ramht->lock, flags); + list_for_each_entry(entry, &chan->ramht->entries, head) { + if (entry->channel == chan && entry->handle == handle) { + gpuobj = entry->gpuobj; + break; + } + } + spin_unlock_irqrestore(&ramht->lock, flags); + + return gpuobj; +} + +int +nouveau_ramht_new(struct drm_device *dev, struct nouveau_gpuobj *gpuobj, + struct nouveau_ramht **pramht) +{ + struct nouveau_ramht *ramht; + + ramht = kzalloc(sizeof(*ramht), GFP_KERNEL); + if (!ramht) + return -ENOMEM; + + ramht->dev = dev; + kref_init(&ramht->refcount); + ramht->bits = drm_order(gpuobj->size / 8); + INIT_LIST_HEAD(&ramht->entries); + spin_lock_init(&ramht->lock); + nouveau_gpuobj_ref(gpuobj, &ramht->gpuobj); + + *pramht = ramht; + return 0; +} + +static void +nouveau_ramht_del(struct kref *ref) +{ + struct nouveau_ramht *ramht = + container_of(ref, struct nouveau_ramht, refcount); + + nouveau_gpuobj_ref(NULL, &ramht->gpuobj); + kfree(ramht); +} + +void +nouveau_ramht_ref(struct nouveau_ramht *ref, struct nouveau_ramht **ptr, + struct nouveau_channel *chan) +{ + struct nouveau_ramht_entry *entry; + struct nouveau_ramht *ramht; + + if (ref) + kref_get(&ref->refcount); + + ramht = *ptr; + if (ramht) { + while ((entry = nouveau_ramht_remove_entry(chan, 0))) { + nouveau_ramht_remove_hash(chan, entry->handle); + nouveau_gpuobj_ref(NULL, &entry->gpuobj); + kfree(entry); + } + + kref_put(&ramht->refcount, nouveau_ramht_del); + } + *ptr = ref; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_ramht.h b/drivers/gpu/drm/nouveau/nouveau_ramht.h new file mode 100644 index 00000000..c82de98f --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_ramht.h @@ -0,0 +1,55 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#ifndef __NOUVEAU_RAMHT_H__ +#define __NOUVEAU_RAMHT_H__ + +struct nouveau_ramht_entry { + struct list_head head; + struct nouveau_channel *channel; + struct nouveau_gpuobj *gpuobj; + u32 handle; +}; + +struct nouveau_ramht { + struct drm_device *dev; + struct kref refcount; + spinlock_t lock; + struct nouveau_gpuobj *gpuobj; + struct list_head entries; + int bits; +}; + +extern int nouveau_ramht_new(struct drm_device *, struct nouveau_gpuobj *, + struct nouveau_ramht **); +extern void nouveau_ramht_ref(struct nouveau_ramht *, struct nouveau_ramht **, + struct nouveau_channel *unref_channel); + +extern int nouveau_ramht_insert(struct nouveau_channel *, u32 handle, + struct nouveau_gpuobj *); +extern int nouveau_ramht_remove(struct nouveau_channel *, u32 handle); +extern struct nouveau_gpuobj * +nouveau_ramht_find(struct nouveau_channel *chan, u32 handle); + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_reg.h b/drivers/gpu/drm/nouveau/nouveau_reg.h new file mode 100644 index 00000000..43a96b99 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_reg.h @@ -0,0 +1,858 @@ + +#define NV04_PFB_BOOT_0 0x00100000 +# define NV04_PFB_BOOT_0_RAM_AMOUNT 0x00000003 +# define NV04_PFB_BOOT_0_RAM_AMOUNT_32MB 0x00000000 +# define NV04_PFB_BOOT_0_RAM_AMOUNT_4MB 0x00000001 +# define NV04_PFB_BOOT_0_RAM_AMOUNT_8MB 0x00000002 +# define NV04_PFB_BOOT_0_RAM_AMOUNT_16MB 0x00000003 +# define NV04_PFB_BOOT_0_RAM_WIDTH_128 0x00000004 +# define NV04_PFB_BOOT_0_RAM_TYPE 0x00000028 +# define NV04_PFB_BOOT_0_RAM_TYPE_SGRAM_8MBIT 0x00000000 +# define NV04_PFB_BOOT_0_RAM_TYPE_SGRAM_16MBIT 0x00000008 +# define NV04_PFB_BOOT_0_RAM_TYPE_SGRAM_16MBIT_4BANK 0x00000010 +# define NV04_PFB_BOOT_0_RAM_TYPE_SDRAM_16MBIT 0x00000018 +# define NV04_PFB_BOOT_0_RAM_TYPE_SDRAM_64MBIT 0x00000020 +# define NV04_PFB_BOOT_0_RAM_TYPE_SDRAM_64MBITX16 0x00000028 +# define NV04_PFB_BOOT_0_UMA_ENABLE 0x00000100 +# define NV04_PFB_BOOT_0_UMA_SIZE 0x0000f000 +#define NV04_PFB_DEBUG_0 0x00100080 +# define NV04_PFB_DEBUG_0_PAGE_MODE 0x00000001 +# define NV04_PFB_DEBUG_0_REFRESH_OFF 0x00000010 +# define NV04_PFB_DEBUG_0_REFRESH_COUNTX64 0x00003f00 +# define NV04_PFB_DEBUG_0_REFRESH_SLOW_CLK 0x00004000 +# define NV04_PFB_DEBUG_0_SAFE_MODE 0x00008000 +# define NV04_PFB_DEBUG_0_ALOM_ENABLE 0x00010000 +# define NV04_PFB_DEBUG_0_CASOE 0x00100000 +# define NV04_PFB_DEBUG_0_CKE_INVERT 0x10000000 +# define NV04_PFB_DEBUG_0_REFINC 0x20000000 +# define NV04_PFB_DEBUG_0_SAVE_POWER_OFF 0x40000000 +#define NV04_PFB_CFG0 0x00100200 +# define NV04_PFB_CFG0_SCRAMBLE 0x20000000 +#define NV04_PFB_CFG1 0x00100204 +#define NV04_PFB_FIFO_DATA 0x0010020c +# define NV10_PFB_FIFO_DATA_RAM_AMOUNT_MB_MASK 0xfff00000 +# define NV10_PFB_FIFO_DATA_RAM_AMOUNT_MB_SHIFT 20 +#define NV10_PFB_REFCTRL 0x00100210 +# define NV10_PFB_REFCTRL_VALID_1 (1 << 31) +#define NV04_PFB_PAD 0x0010021c +# define NV04_PFB_PAD_CKE_NORMAL (1 << 0) +#define NV10_PFB_TILE(i) (0x00100240 + (i*16)) +#define NV10_PFB_TILE__SIZE 8 +#define NV10_PFB_TLIMIT(i) (0x00100244 + (i*16)) +#define NV10_PFB_TSIZE(i) (0x00100248 + (i*16)) +#define NV10_PFB_TSTATUS(i) (0x0010024c + (i*16)) +#define NV04_PFB_REF 0x001002d0 +# define NV04_PFB_REF_CMD_REFRESH (1 << 0) +#define NV04_PFB_PRE 0x001002d4 +# define NV04_PFB_PRE_CMD_PRECHARGE (1 << 0) +#define NV20_PFB_ZCOMP(i) (0x00100300 + 4*(i)) +# define NV20_PFB_ZCOMP_MODE_32 (4 << 24) +# define NV20_PFB_ZCOMP_EN (1 << 31) +# define NV25_PFB_ZCOMP_MODE_16 (1 << 20) +# define NV25_PFB_ZCOMP_MODE_32 (2 << 20) +#define NV10_PFB_CLOSE_PAGE2 0x0010033c +#define NV04_PFB_SCRAMBLE(i) (0x00100400 + 4 * (i)) +#define NV40_PFB_TILE(i) (0x00100600 + (i*16)) +#define NV40_PFB_TILE__SIZE_0 12 +#define NV40_PFB_TILE__SIZE_1 15 +#define NV40_PFB_TLIMIT(i) (0x00100604 + (i*16)) +#define NV40_PFB_TSIZE(i) (0x00100608 + (i*16)) +#define NV40_PFB_TSTATUS(i) (0x0010060c + (i*16)) +#define NV40_PFB_UNK_800 0x00100800 + +#define NV_PEXTDEV_BOOT_0 0x00101000 +#define NV_PEXTDEV_BOOT_0_RAMCFG 0x0000003c +# define NV_PEXTDEV_BOOT_0_STRAP_FP_IFACE_12BIT (8 << 12) +#define NV_PEXTDEV_BOOT_3 0x0010100c + +#define NV_RAMIN 0x00700000 + +#define NV_RAMHT_HANDLE_OFFSET 0 +#define NV_RAMHT_CONTEXT_OFFSET 4 +# define NV_RAMHT_CONTEXT_VALID (1<<31) +# define NV_RAMHT_CONTEXT_CHANNEL_SHIFT 24 +# define NV_RAMHT_CONTEXT_ENGINE_SHIFT 16 +# define NV_RAMHT_CONTEXT_ENGINE_SOFTWARE 0 +# define NV_RAMHT_CONTEXT_ENGINE_GRAPHICS 1 +# define NV_RAMHT_CONTEXT_INSTANCE_SHIFT 0 +# define NV40_RAMHT_CONTEXT_CHANNEL_SHIFT 23 +# define NV40_RAMHT_CONTEXT_ENGINE_SHIFT 20 +# define NV40_RAMHT_CONTEXT_INSTANCE_SHIFT 0 + +/* Some object classes we care about in the drm */ +#define NV_CLASS_DMA_FROM_MEMORY 0x00000002 +#define NV_CLASS_DMA_TO_MEMORY 0x00000003 +#define NV_CLASS_NULL 0x00000030 +#define NV_CLASS_DMA_IN_MEMORY 0x0000003D + +#define NV03_USER(i) (0x00800000+(i*NV03_USER_SIZE)) +#define NV03_USER__SIZE 16 +#define NV10_USER__SIZE 32 +#define NV03_USER_SIZE 0x00010000 +#define NV03_USER_DMA_PUT(i) (0x00800040+(i*NV03_USER_SIZE)) +#define NV03_USER_DMA_PUT__SIZE 16 +#define NV10_USER_DMA_PUT__SIZE 32 +#define NV03_USER_DMA_GET(i) (0x00800044+(i*NV03_USER_SIZE)) +#define NV03_USER_DMA_GET__SIZE 16 +#define NV10_USER_DMA_GET__SIZE 32 +#define NV03_USER_REF_CNT(i) (0x00800048+(i*NV03_USER_SIZE)) +#define NV03_USER_REF_CNT__SIZE 16 +#define NV10_USER_REF_CNT__SIZE 32 + +#define NV40_USER(i) (0x00c00000+(i*NV40_USER_SIZE)) +#define NV40_USER_SIZE 0x00001000 +#define NV40_USER_DMA_PUT(i) (0x00c00040+(i*NV40_USER_SIZE)) +#define NV40_USER_DMA_PUT__SIZE 32 +#define NV40_USER_DMA_GET(i) (0x00c00044+(i*NV40_USER_SIZE)) +#define NV40_USER_DMA_GET__SIZE 32 +#define NV40_USER_REF_CNT(i) (0x00c00048+(i*NV40_USER_SIZE)) +#define NV40_USER_REF_CNT__SIZE 32 + +#define NV50_USER(i) (0x00c00000+(i*NV50_USER_SIZE)) +#define NV50_USER_SIZE 0x00002000 +#define NV50_USER_DMA_PUT(i) (0x00c00040+(i*NV50_USER_SIZE)) +#define NV50_USER_DMA_PUT__SIZE 128 +#define NV50_USER_DMA_GET(i) (0x00c00044+(i*NV50_USER_SIZE)) +#define NV50_USER_DMA_GET__SIZE 128 +#define NV50_USER_REF_CNT(i) (0x00c00048+(i*NV50_USER_SIZE)) +#define NV50_USER_REF_CNT__SIZE 128 + +#define NV03_FIFO_SIZE 0x8000UL + +#define NV03_PMC_BOOT_0 0x00000000 +#define NV03_PMC_BOOT_1 0x00000004 +#define NV03_PMC_INTR_0 0x00000100 +# define NV_PMC_INTR_0_PFIFO_PENDING (1<<8) +# define NV_PMC_INTR_0_PGRAPH_PENDING (1<<12) +# define NV_PMC_INTR_0_NV50_I2C_PENDING (1<<21) +# define NV_PMC_INTR_0_CRTC0_PENDING (1<<24) +# define NV_PMC_INTR_0_CRTC1_PENDING (1<<25) +# define NV_PMC_INTR_0_NV50_DISPLAY_PENDING (1<<26) +# define NV_PMC_INTR_0_CRTCn_PENDING (3<<24) +#define NV03_PMC_INTR_EN_0 0x00000140 +# define NV_PMC_INTR_EN_0_MASTER_ENABLE (1<<0) +#define NV03_PMC_ENABLE 0x00000200 +# define NV_PMC_ENABLE_PFIFO (1<<8) +# define NV_PMC_ENABLE_PGRAPH (1<<12) +/* Disabling the below bit breaks newer (G7X only?) mobile chipsets, + * the card will hang early on in the X init process. + */ +# define NV_PMC_ENABLE_UNK13 (1<<13) +#define NV40_PMC_GRAPH_UNITS 0x00001540 +#define NV40_PMC_BACKLIGHT 0x000015f0 +# define NV40_PMC_BACKLIGHT_MASK 0x001f0000 +#define NV40_PMC_1700 0x00001700 +#define NV40_PMC_1704 0x00001704 +#define NV40_PMC_1708 0x00001708 +#define NV40_PMC_170C 0x0000170C + +/* probably PMC ? */ +#define NV50_PUNK_BAR0_PRAMIN 0x00001700 +#define NV50_PUNK_BAR_CFG_BASE 0x00001704 +#define NV50_PUNK_BAR_CFG_BASE_VALID (1<<30) +#define NV50_PUNK_BAR1_CTXDMA 0x00001708 +#define NV50_PUNK_BAR1_CTXDMA_VALID (1<<31) +#define NV50_PUNK_BAR3_CTXDMA 0x0000170C +#define NV50_PUNK_BAR3_CTXDMA_VALID (1<<31) +#define NV50_PUNK_UNK1710 0x00001710 + +#define NV04_PBUS_PCI_NV_1 0x00001804 +#define NV04_PBUS_PCI_NV_19 0x0000184C +#define NV04_PBUS_PCI_NV_20 0x00001850 +# define NV04_PBUS_PCI_NV_20_ROM_SHADOW_DISABLED (0 << 0) +# define NV04_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED (1 << 0) + +#define NV04_PTIMER_INTR_0 0x00009100 +#define NV04_PTIMER_INTR_EN_0 0x00009140 +#define NV04_PTIMER_NUMERATOR 0x00009200 +#define NV04_PTIMER_DENOMINATOR 0x00009210 +#define NV04_PTIMER_TIME_0 0x00009400 +#define NV04_PTIMER_TIME_1 0x00009410 +#define NV04_PTIMER_ALARM_0 0x00009420 + +#define NV04_PGRAPH_DEBUG_0 0x00400080 +#define NV04_PGRAPH_DEBUG_1 0x00400084 +#define NV04_PGRAPH_DEBUG_2 0x00400088 +#define NV04_PGRAPH_DEBUG_3 0x0040008c +#define NV10_PGRAPH_DEBUG_4 0x00400090 +#define NV03_PGRAPH_INTR 0x00400100 +#define NV03_PGRAPH_NSTATUS 0x00400104 +# define NV04_PGRAPH_NSTATUS_STATE_IN_USE (1<<11) +# define NV04_PGRAPH_NSTATUS_INVALID_STATE (1<<12) +# define NV04_PGRAPH_NSTATUS_BAD_ARGUMENT (1<<13) +# define NV04_PGRAPH_NSTATUS_PROTECTION_FAULT (1<<14) +# define NV10_PGRAPH_NSTATUS_STATE_IN_USE (1<<23) +# define NV10_PGRAPH_NSTATUS_INVALID_STATE (1<<24) +# define NV10_PGRAPH_NSTATUS_BAD_ARGUMENT (1<<25) +# define NV10_PGRAPH_NSTATUS_PROTECTION_FAULT (1<<26) +#define NV03_PGRAPH_NSOURCE 0x00400108 +# define NV03_PGRAPH_NSOURCE_NOTIFICATION (1<<0) +# define NV03_PGRAPH_NSOURCE_DATA_ERROR (1<<1) +# define NV03_PGRAPH_NSOURCE_PROTECTION_ERROR (1<<2) +# define NV03_PGRAPH_NSOURCE_RANGE_EXCEPTION (1<<3) +# define NV03_PGRAPH_NSOURCE_LIMIT_COLOR (1<<4) +# define NV03_PGRAPH_NSOURCE_LIMIT_ZETA (1<<5) +# define NV03_PGRAPH_NSOURCE_ILLEGAL_MTHD (1<<6) +# define NV03_PGRAPH_NSOURCE_DMA_R_PROTECTION (1<<7) +# define NV03_PGRAPH_NSOURCE_DMA_W_PROTECTION (1<<8) +# define NV03_PGRAPH_NSOURCE_FORMAT_EXCEPTION (1<<9) +# define NV03_PGRAPH_NSOURCE_PATCH_EXCEPTION (1<<10) +# define NV03_PGRAPH_NSOURCE_STATE_INVALID (1<<11) +# define NV03_PGRAPH_NSOURCE_DOUBLE_NOTIFY (1<<12) +# define NV03_PGRAPH_NSOURCE_NOTIFY_IN_USE (1<<13) +# define NV03_PGRAPH_NSOURCE_METHOD_CNT (1<<14) +# define NV03_PGRAPH_NSOURCE_BFR_NOTIFICATION (1<<15) +# define NV03_PGRAPH_NSOURCE_DMA_VTX_PROTECTION (1<<16) +# define NV03_PGRAPH_NSOURCE_DMA_WIDTH_A (1<<17) +# define NV03_PGRAPH_NSOURCE_DMA_WIDTH_B (1<<18) +#define NV03_PGRAPH_INTR_EN 0x00400140 +#define NV40_PGRAPH_INTR_EN 0x0040013C +# define NV_PGRAPH_INTR_NOTIFY (1<<0) +# define NV_PGRAPH_INTR_MISSING_HW (1<<4) +# define NV_PGRAPH_INTR_CONTEXT_SWITCH (1<<12) +# define NV_PGRAPH_INTR_BUFFER_NOTIFY (1<<16) +# define NV_PGRAPH_INTR_ERROR (1<<20) +#define NV10_PGRAPH_CTX_CONTROL 0x00400144 +#define NV10_PGRAPH_CTX_USER 0x00400148 +#define NV10_PGRAPH_CTX_SWITCH(i) (0x0040014C + 0x4*(i)) +#define NV04_PGRAPH_CTX_SWITCH1 0x00400160 +#define NV10_PGRAPH_CTX_CACHE(i, j) (0x00400160 \ + + 0x4*(i) + 0x20*(j)) +#define NV04_PGRAPH_CTX_SWITCH2 0x00400164 +#define NV04_PGRAPH_CTX_SWITCH3 0x00400168 +#define NV04_PGRAPH_CTX_SWITCH4 0x0040016C +#define NV04_PGRAPH_CTX_CONTROL 0x00400170 +#define NV04_PGRAPH_CTX_USER 0x00400174 +#define NV04_PGRAPH_CTX_CACHE1 0x00400180 +#define NV03_PGRAPH_CTX_CONTROL 0x00400190 +#define NV03_PGRAPH_CTX_USER 0x00400194 +#define NV04_PGRAPH_CTX_CACHE2 0x004001A0 +#define NV04_PGRAPH_CTX_CACHE3 0x004001C0 +#define NV04_PGRAPH_CTX_CACHE4 0x004001E0 +#define NV40_PGRAPH_CTXCTL_0304 0x00400304 +#define NV40_PGRAPH_CTXCTL_0304_XFER_CTX 0x00000001 +#define NV40_PGRAPH_CTXCTL_UCODE_STAT 0x00400308 +#define NV40_PGRAPH_CTXCTL_UCODE_STAT_IP_MASK 0xff000000 +#define NV40_PGRAPH_CTXCTL_UCODE_STAT_IP_SHIFT 24 +#define NV40_PGRAPH_CTXCTL_UCODE_STAT_OP_MASK 0x00ffffff +#define NV40_PGRAPH_CTXCTL_0310 0x00400310 +#define NV40_PGRAPH_CTXCTL_0310_XFER_SAVE 0x00000020 +#define NV40_PGRAPH_CTXCTL_0310_XFER_LOAD 0x00000040 +#define NV40_PGRAPH_CTXCTL_030C 0x0040030c +#define NV40_PGRAPH_CTXCTL_UCODE_INDEX 0x00400324 +#define NV40_PGRAPH_CTXCTL_UCODE_DATA 0x00400328 +#define NV40_PGRAPH_CTXCTL_CUR 0x0040032c +#define NV40_PGRAPH_CTXCTL_CUR_LOADED 0x01000000 +#define NV40_PGRAPH_CTXCTL_CUR_INSTANCE 0x000FFFFF +#define NV40_PGRAPH_CTXCTL_NEXT 0x00400330 +#define NV40_PGRAPH_CTXCTL_NEXT_INSTANCE 0x000fffff +#define NV50_PGRAPH_CTXCTL_CUR 0x0040032c +#define NV50_PGRAPH_CTXCTL_CUR_LOADED 0x80000000 +#define NV50_PGRAPH_CTXCTL_CUR_INSTANCE 0x00ffffff +#define NV50_PGRAPH_CTXCTL_NEXT 0x00400330 +#define NV50_PGRAPH_CTXCTL_NEXT_INSTANCE 0x00ffffff +#define NV03_PGRAPH_ABS_X_RAM 0x00400400 +#define NV03_PGRAPH_ABS_Y_RAM 0x00400480 +#define NV03_PGRAPH_X_MISC 0x00400500 +#define NV03_PGRAPH_Y_MISC 0x00400504 +#define NV04_PGRAPH_VALID1 0x00400508 +#define NV04_PGRAPH_SOURCE_COLOR 0x0040050C +#define NV04_PGRAPH_MISC24_0 0x00400510 +#define NV03_PGRAPH_XY_LOGIC_MISC0 0x00400514 +#define NV03_PGRAPH_XY_LOGIC_MISC1 0x00400518 +#define NV03_PGRAPH_XY_LOGIC_MISC2 0x0040051C +#define NV03_PGRAPH_XY_LOGIC_MISC3 0x00400520 +#define NV03_PGRAPH_CLIPX_0 0x00400524 +#define NV03_PGRAPH_CLIPX_1 0x00400528 +#define NV03_PGRAPH_CLIPY_0 0x0040052C +#define NV03_PGRAPH_CLIPY_1 0x00400530 +#define NV03_PGRAPH_ABS_ICLIP_XMAX 0x00400534 +#define NV03_PGRAPH_ABS_ICLIP_YMAX 0x00400538 +#define NV03_PGRAPH_ABS_UCLIP_XMIN 0x0040053C +#define NV03_PGRAPH_ABS_UCLIP_YMIN 0x00400540 +#define NV03_PGRAPH_ABS_UCLIP_XMAX 0x00400544 +#define NV03_PGRAPH_ABS_UCLIP_YMAX 0x00400548 +#define NV03_PGRAPH_ABS_UCLIPA_XMIN 0x00400560 +#define NV03_PGRAPH_ABS_UCLIPA_YMIN 0x00400564 +#define NV03_PGRAPH_ABS_UCLIPA_XMAX 0x00400568 +#define NV03_PGRAPH_ABS_UCLIPA_YMAX 0x0040056C +#define NV04_PGRAPH_MISC24_1 0x00400570 +#define NV04_PGRAPH_MISC24_2 0x00400574 +#define NV04_PGRAPH_VALID2 0x00400578 +#define NV04_PGRAPH_PASSTHRU_0 0x0040057C +#define NV04_PGRAPH_PASSTHRU_1 0x00400580 +#define NV04_PGRAPH_PASSTHRU_2 0x00400584 +#define NV10_PGRAPH_DIMX_TEXTURE 0x00400588 +#define NV10_PGRAPH_WDIMX_TEXTURE 0x0040058C +#define NV04_PGRAPH_COMBINE_0_ALPHA 0x00400590 +#define NV04_PGRAPH_COMBINE_0_COLOR 0x00400594 +#define NV04_PGRAPH_COMBINE_1_ALPHA 0x00400598 +#define NV04_PGRAPH_COMBINE_1_COLOR 0x0040059C +#define NV04_PGRAPH_FORMAT_0 0x004005A8 +#define NV04_PGRAPH_FORMAT_1 0x004005AC +#define NV04_PGRAPH_FILTER_0 0x004005B0 +#define NV04_PGRAPH_FILTER_1 0x004005B4 +#define NV03_PGRAPH_MONO_COLOR0 0x00400600 +#define NV04_PGRAPH_ROP3 0x00400604 +#define NV04_PGRAPH_BETA_AND 0x00400608 +#define NV04_PGRAPH_BETA_PREMULT 0x0040060C +#define NV04_PGRAPH_LIMIT_VIOL_PIX 0x00400610 +#define NV04_PGRAPH_FORMATS 0x00400618 +#define NV10_PGRAPH_DEBUG_2 0x00400620 +#define NV04_PGRAPH_BOFFSET0 0x00400640 +#define NV04_PGRAPH_BOFFSET1 0x00400644 +#define NV04_PGRAPH_BOFFSET2 0x00400648 +#define NV04_PGRAPH_BOFFSET3 0x0040064C +#define NV04_PGRAPH_BOFFSET4 0x00400650 +#define NV04_PGRAPH_BOFFSET5 0x00400654 +#define NV04_PGRAPH_BBASE0 0x00400658 +#define NV04_PGRAPH_BBASE1 0x0040065C +#define NV04_PGRAPH_BBASE2 0x00400660 +#define NV04_PGRAPH_BBASE3 0x00400664 +#define NV04_PGRAPH_BBASE4 0x00400668 +#define NV04_PGRAPH_BBASE5 0x0040066C +#define NV04_PGRAPH_BPITCH0 0x00400670 +#define NV04_PGRAPH_BPITCH1 0x00400674 +#define NV04_PGRAPH_BPITCH2 0x00400678 +#define NV04_PGRAPH_BPITCH3 0x0040067C +#define NV04_PGRAPH_BPITCH4 0x00400680 +#define NV04_PGRAPH_BLIMIT0 0x00400684 +#define NV04_PGRAPH_BLIMIT1 0x00400688 +#define NV04_PGRAPH_BLIMIT2 0x0040068C +#define NV04_PGRAPH_BLIMIT3 0x00400690 +#define NV04_PGRAPH_BLIMIT4 0x00400694 +#define NV04_PGRAPH_BLIMIT5 0x00400698 +#define NV04_PGRAPH_BSWIZZLE2 0x0040069C +#define NV04_PGRAPH_BSWIZZLE5 0x004006A0 +#define NV03_PGRAPH_STATUS 0x004006B0 +#define NV04_PGRAPH_STATUS 0x00400700 +# define NV40_PGRAPH_STATUS_SYNC_STALL 0x00004000 +#define NV04_PGRAPH_TRAPPED_ADDR 0x00400704 +#define NV04_PGRAPH_TRAPPED_DATA 0x00400708 +#define NV04_PGRAPH_SURFACE 0x0040070C +#define NV10_PGRAPH_TRAPPED_DATA_HIGH 0x0040070C +#define NV04_PGRAPH_STATE 0x00400710 +#define NV10_PGRAPH_SURFACE 0x00400710 +#define NV04_PGRAPH_NOTIFY 0x00400714 +#define NV10_PGRAPH_STATE 0x00400714 +#define NV10_PGRAPH_NOTIFY 0x00400718 + +#define NV04_PGRAPH_FIFO 0x00400720 + +#define NV04_PGRAPH_BPIXEL 0x00400724 +#define NV10_PGRAPH_RDI_INDEX 0x00400750 +#define NV04_PGRAPH_FFINTFC_ST2 0x00400754 +#define NV10_PGRAPH_RDI_DATA 0x00400754 +#define NV04_PGRAPH_DMA_PITCH 0x00400760 +#define NV10_PGRAPH_FFINTFC_FIFO_PTR 0x00400760 +#define NV04_PGRAPH_DVD_COLORFMT 0x00400764 +#define NV10_PGRAPH_FFINTFC_ST2 0x00400764 +#define NV04_PGRAPH_SCALED_FORMAT 0x00400768 +#define NV10_PGRAPH_FFINTFC_ST2_DL 0x00400768 +#define NV10_PGRAPH_FFINTFC_ST2_DH 0x0040076c +#define NV10_PGRAPH_DMA_PITCH 0x00400770 +#define NV10_PGRAPH_DVD_COLORFMT 0x00400774 +#define NV10_PGRAPH_SCALED_FORMAT 0x00400778 +#define NV20_PGRAPH_CHANNEL_CTX_TABLE 0x00400780 +#define NV20_PGRAPH_CHANNEL_CTX_POINTER 0x00400784 +#define NV20_PGRAPH_CHANNEL_CTX_XFER 0x00400788 +#define NV20_PGRAPH_CHANNEL_CTX_XFER_LOAD 0x00000001 +#define NV20_PGRAPH_CHANNEL_CTX_XFER_SAVE 0x00000002 +#define NV04_PGRAPH_PATT_COLOR0 0x00400800 +#define NV04_PGRAPH_PATT_COLOR1 0x00400804 +#define NV04_PGRAPH_PATTERN 0x00400808 +#define NV04_PGRAPH_PATTERN_SHAPE 0x00400810 +#define NV04_PGRAPH_CHROMA 0x00400814 +#define NV04_PGRAPH_CONTROL0 0x00400818 +#define NV04_PGRAPH_CONTROL1 0x0040081C +#define NV04_PGRAPH_CONTROL2 0x00400820 +#define NV04_PGRAPH_BLEND 0x00400824 +#define NV04_PGRAPH_STORED_FMT 0x00400830 +#define NV04_PGRAPH_PATT_COLORRAM 0x00400900 +#define NV20_PGRAPH_TILE(i) (0x00400900 + (i*16)) +#define NV20_PGRAPH_TLIMIT(i) (0x00400904 + (i*16)) +#define NV20_PGRAPH_TSIZE(i) (0x00400908 + (i*16)) +#define NV20_PGRAPH_TSTATUS(i) (0x0040090C + (i*16)) +#define NV20_PGRAPH_ZCOMP(i) (0x00400980 + 4*(i)) +#define NV10_PGRAPH_TILE(i) (0x00400B00 + (i*16)) +#define NV10_PGRAPH_TLIMIT(i) (0x00400B04 + (i*16)) +#define NV10_PGRAPH_TSIZE(i) (0x00400B08 + (i*16)) +#define NV10_PGRAPH_TSTATUS(i) (0x00400B0C + (i*16)) +#define NV04_PGRAPH_U_RAM 0x00400D00 +#define NV47_PGRAPH_TILE(i) (0x00400D00 + (i*16)) +#define NV47_PGRAPH_TLIMIT(i) (0x00400D04 + (i*16)) +#define NV47_PGRAPH_TSIZE(i) (0x00400D08 + (i*16)) +#define NV47_PGRAPH_TSTATUS(i) (0x00400D0C + (i*16)) +#define NV04_PGRAPH_V_RAM 0x00400D40 +#define NV04_PGRAPH_W_RAM 0x00400D80 +#define NV10_PGRAPH_COMBINER0_IN_ALPHA 0x00400E40 +#define NV10_PGRAPH_COMBINER1_IN_ALPHA 0x00400E44 +#define NV10_PGRAPH_COMBINER0_IN_RGB 0x00400E48 +#define NV10_PGRAPH_COMBINER1_IN_RGB 0x00400E4C +#define NV10_PGRAPH_COMBINER_COLOR0 0x00400E50 +#define NV10_PGRAPH_COMBINER_COLOR1 0x00400E54 +#define NV10_PGRAPH_COMBINER0_OUT_ALPHA 0x00400E58 +#define NV10_PGRAPH_COMBINER1_OUT_ALPHA 0x00400E5C +#define NV10_PGRAPH_COMBINER0_OUT_RGB 0x00400E60 +#define NV10_PGRAPH_COMBINER1_OUT_RGB 0x00400E64 +#define NV10_PGRAPH_COMBINER_FINAL0 0x00400E68 +#define NV10_PGRAPH_COMBINER_FINAL1 0x00400E6C +#define NV10_PGRAPH_WINDOWCLIP_HORIZONTAL 0x00400F00 +#define NV10_PGRAPH_WINDOWCLIP_VERTICAL 0x00400F20 +#define NV10_PGRAPH_XFMODE0 0x00400F40 +#define NV10_PGRAPH_XFMODE1 0x00400F44 +#define NV10_PGRAPH_GLOBALSTATE0 0x00400F48 +#define NV10_PGRAPH_GLOBALSTATE1 0x00400F4C +#define NV10_PGRAPH_PIPE_ADDRESS 0x00400F50 +#define NV10_PGRAPH_PIPE_DATA 0x00400F54 +#define NV04_PGRAPH_DMA_START_0 0x00401000 +#define NV04_PGRAPH_DMA_START_1 0x00401004 +#define NV04_PGRAPH_DMA_LENGTH 0x00401008 +#define NV04_PGRAPH_DMA_MISC 0x0040100C +#define NV04_PGRAPH_DMA_DATA_0 0x00401020 +#define NV04_PGRAPH_DMA_DATA_1 0x00401024 +#define NV04_PGRAPH_DMA_RM 0x00401030 +#define NV04_PGRAPH_DMA_A_XLATE_INST 0x00401040 +#define NV04_PGRAPH_DMA_A_CONTROL 0x00401044 +#define NV04_PGRAPH_DMA_A_LIMIT 0x00401048 +#define NV04_PGRAPH_DMA_A_TLB_PTE 0x0040104C +#define NV04_PGRAPH_DMA_A_TLB_TAG 0x00401050 +#define NV04_PGRAPH_DMA_A_ADJ_OFFSET 0x00401054 +#define NV04_PGRAPH_DMA_A_OFFSET 0x00401058 +#define NV04_PGRAPH_DMA_A_SIZE 0x0040105C +#define NV04_PGRAPH_DMA_A_Y_SIZE 0x00401060 +#define NV04_PGRAPH_DMA_B_XLATE_INST 0x00401080 +#define NV04_PGRAPH_DMA_B_CONTROL 0x00401084 +#define NV04_PGRAPH_DMA_B_LIMIT 0x00401088 +#define NV04_PGRAPH_DMA_B_TLB_PTE 0x0040108C +#define NV04_PGRAPH_DMA_B_TLB_TAG 0x00401090 +#define NV04_PGRAPH_DMA_B_ADJ_OFFSET 0x00401094 +#define NV04_PGRAPH_DMA_B_OFFSET 0x00401098 +#define NV04_PGRAPH_DMA_B_SIZE 0x0040109C +#define NV04_PGRAPH_DMA_B_Y_SIZE 0x004010A0 +#define NV40_PGRAPH_TILE1(i) (0x00406900 + (i*16)) +#define NV40_PGRAPH_TLIMIT1(i) (0x00406904 + (i*16)) +#define NV40_PGRAPH_TSIZE1(i) (0x00406908 + (i*16)) +#define NV40_PGRAPH_TSTATUS1(i) (0x0040690C + (i*16)) + + +/* It's a guess that this works on NV03. Confirmed on NV04, though */ +#define NV04_PFIFO_DELAY_0 0x00002040 +#define NV04_PFIFO_DMA_TIMESLICE 0x00002044 +#define NV04_PFIFO_NEXT_CHANNEL 0x00002050 +#define NV03_PFIFO_INTR_0 0x00002100 +#define NV03_PFIFO_INTR_EN_0 0x00002140 +# define NV_PFIFO_INTR_CACHE_ERROR (1<<0) +# define NV_PFIFO_INTR_RUNOUT (1<<4) +# define NV_PFIFO_INTR_RUNOUT_OVERFLOW (1<<8) +# define NV_PFIFO_INTR_DMA_PUSHER (1<<12) +# define NV_PFIFO_INTR_DMA_PT (1<<16) +# define NV_PFIFO_INTR_SEMAPHORE (1<<20) +# define NV_PFIFO_INTR_ACQUIRE_TIMEOUT (1<<24) +#define NV03_PFIFO_RAMHT 0x00002210 +#define NV03_PFIFO_RAMFC 0x00002214 +#define NV03_PFIFO_RAMRO 0x00002218 +#define NV40_PFIFO_RAMFC 0x00002220 +#define NV03_PFIFO_CACHES 0x00002500 +#define NV04_PFIFO_MODE 0x00002504 +#define NV04_PFIFO_DMA 0x00002508 +#define NV04_PFIFO_SIZE 0x0000250c +#define NV50_PFIFO_CTX_TABLE(c) (0x2600+(c)*4) +#define NV50_PFIFO_CTX_TABLE__SIZE 128 +#define NV50_PFIFO_CTX_TABLE_CHANNEL_ENABLED (1<<31) +#define NV50_PFIFO_CTX_TABLE_UNK30_BAD (1<<30) +#define NV50_PFIFO_CTX_TABLE_INSTANCE_MASK_G80 0x0FFFFFFF +#define NV50_PFIFO_CTX_TABLE_INSTANCE_MASK_G84 0x00FFFFFF +#define NV03_PFIFO_CACHE0_PUSH0 0x00003000 +#define NV03_PFIFO_CACHE0_PULL0 0x00003040 +#define NV04_PFIFO_CACHE0_PULL0 0x00003050 +#define NV04_PFIFO_CACHE0_PULL1 0x00003054 +#define NV03_PFIFO_CACHE1_PUSH0 0x00003200 +#define NV03_PFIFO_CACHE1_PUSH1 0x00003204 +#define NV03_PFIFO_CACHE1_PUSH1_DMA (1<<8) +#define NV40_PFIFO_CACHE1_PUSH1_DMA (1<<16) +#define NV03_PFIFO_CACHE1_PUSH1_CHID_MASK 0x0000000f +#define NV10_PFIFO_CACHE1_PUSH1_CHID_MASK 0x0000001f +#define NV50_PFIFO_CACHE1_PUSH1_CHID_MASK 0x0000007f +#define NV03_PFIFO_CACHE1_PUT 0x00003210 +#define NV04_PFIFO_CACHE1_DMA_PUSH 0x00003220 +#define NV04_PFIFO_CACHE1_DMA_FETCH 0x00003224 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_8_BYTES 0x00000000 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_16_BYTES 0x00000008 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_24_BYTES 0x00000010 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_32_BYTES 0x00000018 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_40_BYTES 0x00000020 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_48_BYTES 0x00000028 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_56_BYTES 0x00000030 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_64_BYTES 0x00000038 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_72_BYTES 0x00000040 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_80_BYTES 0x00000048 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_88_BYTES 0x00000050 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_96_BYTES 0x00000058 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_104_BYTES 0x00000060 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_112_BYTES 0x00000068 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_120_BYTES 0x00000070 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_128_BYTES 0x00000078 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_136_BYTES 0x00000080 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_144_BYTES 0x00000088 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_152_BYTES 0x00000090 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_160_BYTES 0x00000098 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_168_BYTES 0x000000A0 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_176_BYTES 0x000000A8 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_184_BYTES 0x000000B0 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_192_BYTES 0x000000B8 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_200_BYTES 0x000000C0 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_208_BYTES 0x000000C8 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_216_BYTES 0x000000D0 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_224_BYTES 0x000000D8 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_232_BYTES 0x000000E0 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_240_BYTES 0x000000E8 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_248_BYTES 0x000000F0 +# define NV_PFIFO_CACHE1_DMA_FETCH_TRIG_256_BYTES 0x000000F8 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE 0x0000E000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_32_BYTES 0x00000000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_64_BYTES 0x00002000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_96_BYTES 0x00004000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_128_BYTES 0x00006000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_160_BYTES 0x00008000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_192_BYTES 0x0000A000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_224_BYTES 0x0000C000 +# define NV_PFIFO_CACHE1_DMA_FETCH_SIZE_256_BYTES 0x0000E000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS 0x001F0000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_0 0x00000000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_1 0x00010000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_2 0x00020000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_3 0x00030000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_4 0x00040000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_5 0x00050000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_6 0x00060000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_7 0x00070000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_8 0x00080000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_9 0x00090000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_10 0x000A0000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_11 0x000B0000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_12 0x000C0000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_13 0x000D0000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_14 0x000E0000 +# define NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_15 0x000F0000 +# define NV_PFIFO_CACHE1_ENDIAN 0x80000000 +# define NV_PFIFO_CACHE1_LITTLE_ENDIAN 0x7FFFFFFF +# define NV_PFIFO_CACHE1_BIG_ENDIAN 0x80000000 +#define NV04_PFIFO_CACHE1_DMA_STATE 0x00003228 +#define NV04_PFIFO_CACHE1_DMA_INSTANCE 0x0000322c +#define NV04_PFIFO_CACHE1_DMA_CTL 0x00003230 +#define NV04_PFIFO_CACHE1_DMA_PUT 0x00003240 +#define NV04_PFIFO_CACHE1_DMA_GET 0x00003244 +#define NV10_PFIFO_CACHE1_REF_CNT 0x00003248 +#define NV10_PFIFO_CACHE1_DMA_SUBROUTINE 0x0000324C +#define NV03_PFIFO_CACHE1_PULL0 0x00003240 +#define NV04_PFIFO_CACHE1_PULL0 0x00003250 +# define NV04_PFIFO_CACHE1_PULL0_HASH_FAILED 0x00000010 +# define NV04_PFIFO_CACHE1_PULL0_HASH_BUSY 0x00001000 +#define NV03_PFIFO_CACHE1_PULL1 0x00003250 +#define NV04_PFIFO_CACHE1_PULL1 0x00003254 +#define NV04_PFIFO_CACHE1_HASH 0x00003258 +#define NV10_PFIFO_CACHE1_ACQUIRE_TIMEOUT 0x00003260 +#define NV10_PFIFO_CACHE1_ACQUIRE_TIMESTAMP 0x00003264 +#define NV10_PFIFO_CACHE1_ACQUIRE_VALUE 0x00003268 +#define NV10_PFIFO_CACHE1_SEMAPHORE 0x0000326C +#define NV03_PFIFO_CACHE1_GET 0x00003270 +#define NV04_PFIFO_CACHE1_ENGINE 0x00003280 +#define NV04_PFIFO_CACHE1_DMA_DCOUNT 0x000032A0 +#define NV40_PFIFO_GRCTX_INSTANCE 0x000032E0 +#define NV40_PFIFO_UNK32E4 0x000032E4 +#define NV04_PFIFO_CACHE1_METHOD(i) (0x00003800+(i*8)) +#define NV04_PFIFO_CACHE1_DATA(i) (0x00003804+(i*8)) +#define NV40_PFIFO_CACHE1_METHOD(i) (0x00090000+(i*8)) +#define NV40_PFIFO_CACHE1_DATA(i) (0x00090004+(i*8)) + +#define NV_CRTC0_INTSTAT 0x00600100 +#define NV_CRTC0_INTEN 0x00600140 +#define NV_CRTC1_INTSTAT 0x00602100 +#define NV_CRTC1_INTEN 0x00602140 +# define NV_CRTC_INTR_VBLANK (1<<0) + +#define NV04_PRAMIN 0x00700000 + +/* Fifo commands. These are not regs, neither masks */ +#define NV03_FIFO_CMD_JUMP 0x20000000 +#define NV03_FIFO_CMD_JUMP_OFFSET_MASK 0x1ffffffc +#define NV03_FIFO_CMD_REWIND (NV03_FIFO_CMD_JUMP | (0 & NV03_FIFO_CMD_JUMP_OFFSET_MASK)) + +/* This is a partial import from rules-ng, a few things may be duplicated. + * Eventually we should completely import everything from rules-ng. + * For the moment check rules-ng for docs. + */ + +#define NV50_PMC 0x00000000 +#define NV50_PMC__LEN 0x1 +#define NV50_PMC__ESIZE 0x2000 +# define NV50_PMC_BOOT_0 0x00000000 +# define NV50_PMC_BOOT_0_REVISION 0x000000ff +# define NV50_PMC_BOOT_0_REVISION__SHIFT 0 +# define NV50_PMC_BOOT_0_ARCH 0x0ff00000 +# define NV50_PMC_BOOT_0_ARCH__SHIFT 20 +# define NV50_PMC_INTR_0 0x00000100 +# define NV50_PMC_INTR_0_PFIFO (1<<8) +# define NV50_PMC_INTR_0_PGRAPH (1<<12) +# define NV50_PMC_INTR_0_PTIMER (1<<20) +# define NV50_PMC_INTR_0_HOTPLUG (1<<21) +# define NV50_PMC_INTR_0_DISPLAY (1<<26) +# define NV50_PMC_INTR_EN_0 0x00000140 +# define NV50_PMC_INTR_EN_0_MASTER (1<<0) +# define NV50_PMC_INTR_EN_0_MASTER_DISABLED (0<<0) +# define NV50_PMC_INTR_EN_0_MASTER_ENABLED (1<<0) +# define NV50_PMC_ENABLE 0x00000200 +# define NV50_PMC_ENABLE_PFIFO (1<<8) +# define NV50_PMC_ENABLE_PGRAPH (1<<12) + +#define NV50_PCONNECTOR 0x0000e000 +#define NV50_PCONNECTOR__LEN 0x1 +#define NV50_PCONNECTOR__ESIZE 0x1000 +# define NV50_PCONNECTOR_HOTPLUG_INTR 0x0000e050 +# define NV50_PCONNECTOR_HOTPLUG_INTR_PLUG_I2C0 (1<<0) +# define NV50_PCONNECTOR_HOTPLUG_INTR_PLUG_I2C1 (1<<1) +# define NV50_PCONNECTOR_HOTPLUG_INTR_PLUG_I2C2 (1<<2) +# define NV50_PCONNECTOR_HOTPLUG_INTR_PLUG_I2C3 (1<<3) +# define NV50_PCONNECTOR_HOTPLUG_INTR_UNPLUG_I2C0 (1<<16) +# define NV50_PCONNECTOR_HOTPLUG_INTR_UNPLUG_I2C1 (1<<17) +# define NV50_PCONNECTOR_HOTPLUG_INTR_UNPLUG_I2C2 (1<<18) +# define NV50_PCONNECTOR_HOTPLUG_INTR_UNPLUG_I2C3 (1<<19) +# define NV50_PCONNECTOR_HOTPLUG_CTRL 0x0000e054 +# define NV50_PCONNECTOR_HOTPLUG_CTRL_PLUG_I2C0 (1<<0) +# define NV50_PCONNECTOR_HOTPLUG_CTRL_PLUG_I2C1 (1<<1) +# define NV50_PCONNECTOR_HOTPLUG_CTRL_PLUG_I2C2 (1<<2) +# define NV50_PCONNECTOR_HOTPLUG_CTRL_PLUG_I2C3 (1<<3) +# define NV50_PCONNECTOR_HOTPLUG_CTRL_UNPLUG_I2C0 (1<<16) +# define NV50_PCONNECTOR_HOTPLUG_CTRL_UNPLUG_I2C1 (1<<17) +# define NV50_PCONNECTOR_HOTPLUG_CTRL_UNPLUG_I2C2 (1<<18) +# define NV50_PCONNECTOR_HOTPLUG_CTRL_UNPLUG_I2C3 (1<<19) +# define NV50_PCONNECTOR_HOTPLUG_STATE 0x0000e104 +# define NV50_PCONNECTOR_HOTPLUG_STATE_PIN_CONNECTED_I2C0 (1<<2) +# define NV50_PCONNECTOR_HOTPLUG_STATE_PIN_CONNECTED_I2C1 (1<<6) +# define NV50_PCONNECTOR_HOTPLUG_STATE_PIN_CONNECTED_I2C2 (1<<10) +# define NV50_PCONNECTOR_HOTPLUG_STATE_PIN_CONNECTED_I2C3 (1<<14) +# define NV50_PCONNECTOR_I2C_PORT_0 0x0000e138 +# define NV50_PCONNECTOR_I2C_PORT_1 0x0000e150 +# define NV50_PCONNECTOR_I2C_PORT_2 0x0000e168 +# define NV50_PCONNECTOR_I2C_PORT_3 0x0000e180 +# define NV50_PCONNECTOR_I2C_PORT_4 0x0000e240 +# define NV50_PCONNECTOR_I2C_PORT_5 0x0000e258 + +#define NV50_AUXCH_DATA_OUT(i, n) ((n) * 4 + (i) * 0x50 + 0x0000e4c0) +#define NV50_AUXCH_DATA_OUT__SIZE 4 +#define NV50_AUXCH_DATA_IN(i, n) ((n) * 4 + (i) * 0x50 + 0x0000e4d0) +#define NV50_AUXCH_DATA_IN__SIZE 4 +#define NV50_AUXCH_ADDR(i) ((i) * 0x50 + 0x0000e4e0) +#define NV50_AUXCH_CTRL(i) ((i) * 0x50 + 0x0000e4e4) +#define NV50_AUXCH_CTRL_LINKSTAT 0x01000000 +#define NV50_AUXCH_CTRL_LINKSTAT_NOT_READY 0x00000000 +#define NV50_AUXCH_CTRL_LINKSTAT_READY 0x01000000 +#define NV50_AUXCH_CTRL_LINKEN 0x00100000 +#define NV50_AUXCH_CTRL_LINKEN_DISABLED 0x00000000 +#define NV50_AUXCH_CTRL_LINKEN_ENABLED 0x00100000 +#define NV50_AUXCH_CTRL_EXEC 0x00010000 +#define NV50_AUXCH_CTRL_EXEC_COMPLETE 0x00000000 +#define NV50_AUXCH_CTRL_EXEC_IN_PROCESS 0x00010000 +#define NV50_AUXCH_CTRL_CMD 0x0000f000 +#define NV50_AUXCH_CTRL_CMD_SHIFT 12 +#define NV50_AUXCH_CTRL_LEN 0x0000000f +#define NV50_AUXCH_CTRL_LEN_SHIFT 0 +#define NV50_AUXCH_STAT(i) ((i) * 0x50 + 0x0000e4e8) +#define NV50_AUXCH_STAT_STATE 0x10000000 +#define NV50_AUXCH_STAT_STATE_NOT_READY 0x00000000 +#define NV50_AUXCH_STAT_STATE_READY 0x10000000 +#define NV50_AUXCH_STAT_REPLY 0x000f0000 +#define NV50_AUXCH_STAT_REPLY_AUX 0x00030000 +#define NV50_AUXCH_STAT_REPLY_AUX_ACK 0x00000000 +#define NV50_AUXCH_STAT_REPLY_AUX_NACK 0x00010000 +#define NV50_AUXCH_STAT_REPLY_AUX_DEFER 0x00020000 +#define NV50_AUXCH_STAT_REPLY_I2C 0x000c0000 +#define NV50_AUXCH_STAT_REPLY_I2C_ACK 0x00000000 +#define NV50_AUXCH_STAT_REPLY_I2C_NACK 0x00040000 +#define NV50_AUXCH_STAT_REPLY_I2C_DEFER 0x00080000 +#define NV50_AUXCH_STAT_COUNT 0x0000001f + +#define NV50_PBUS 0x00088000 +#define NV50_PBUS__LEN 0x1 +#define NV50_PBUS__ESIZE 0x1000 +# define NV50_PBUS_PCI_ID 0x00088000 +# define NV50_PBUS_PCI_ID_VENDOR_ID 0x0000ffff +# define NV50_PBUS_PCI_ID_VENDOR_ID__SHIFT 0 +# define NV50_PBUS_PCI_ID_DEVICE_ID 0xffff0000 +# define NV50_PBUS_PCI_ID_DEVICE_ID__SHIFT 16 + +#define NV50_PFB 0x00100000 +#define NV50_PFB__LEN 0x1 +#define NV50_PFB__ESIZE 0x1000 + +#define NV50_PEXTDEV 0x00101000 +#define NV50_PEXTDEV__LEN 0x1 +#define NV50_PEXTDEV__ESIZE 0x1000 + +#define NV50_PROM 0x00300000 +#define NV50_PROM__LEN 0x1 +#define NV50_PROM__ESIZE 0x10000 + +#define NV50_PGRAPH 0x00400000 +#define NV50_PGRAPH__LEN 0x1 +#define NV50_PGRAPH__ESIZE 0x10000 + +#define NV50_PDISPLAY 0x00610000 +#define NV50_PDISPLAY_OBJECTS 0x00610010 +#define NV50_PDISPLAY_INTR_0 0x00610020 +#define NV50_PDISPLAY_INTR_1 0x00610024 +#define NV50_PDISPLAY_INTR_1_VBLANK_CRTC 0x0000000c +#define NV50_PDISPLAY_INTR_1_VBLANK_CRTC_SHIFT 2 +#define NV50_PDISPLAY_INTR_1_VBLANK_CRTC_(n) (1 << ((n) + 2)) +#define NV50_PDISPLAY_INTR_1_VBLANK_CRTC_0 0x00000004 +#define NV50_PDISPLAY_INTR_1_VBLANK_CRTC_1 0x00000008 +#define NV50_PDISPLAY_INTR_1_CLK_UNK10 0x00000010 +#define NV50_PDISPLAY_INTR_1_CLK_UNK20 0x00000020 +#define NV50_PDISPLAY_INTR_1_CLK_UNK40 0x00000040 +#define NV50_PDISPLAY_INTR_EN_0 0x00610028 +#define NV50_PDISPLAY_INTR_EN_1 0x0061002c +#define NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC 0x0000000c +#define NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_(n) (1 << ((n) + 2)) +#define NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_0 0x00000004 +#define NV50_PDISPLAY_INTR_EN_1_VBLANK_CRTC_1 0x00000008 +#define NV50_PDISPLAY_INTR_EN_1_CLK_UNK10 0x00000010 +#define NV50_PDISPLAY_INTR_EN_1_CLK_UNK20 0x00000020 +#define NV50_PDISPLAY_INTR_EN_1_CLK_UNK40 0x00000040 +#define NV50_PDISPLAY_UNK30_CTRL 0x00610030 +#define NV50_PDISPLAY_UNK30_CTRL_UPDATE_VCLK0 0x00000200 +#define NV50_PDISPLAY_UNK30_CTRL_UPDATE_VCLK1 0x00000400 +#define NV50_PDISPLAY_UNK30_CTRL_PENDING 0x80000000 +#define NV50_PDISPLAY_TRAPPED_ADDR(i) ((i) * 0x08 + 0x00610080) +#define NV50_PDISPLAY_TRAPPED_DATA(i) ((i) * 0x08 + 0x00610084) +#define NV50_PDISPLAY_EVO_CTRL(i) ((i) * 0x10 + 0x00610200) +#define NV50_PDISPLAY_EVO_CTRL_DMA 0x00000010 +#define NV50_PDISPLAY_EVO_CTRL_DMA_DISABLED 0x00000000 +#define NV50_PDISPLAY_EVO_CTRL_DMA_ENABLED 0x00000010 +#define NV50_PDISPLAY_EVO_DMA_CB(i) ((i) * 0x10 + 0x00610204) +#define NV50_PDISPLAY_EVO_DMA_CB_LOCATION 0x00000002 +#define NV50_PDISPLAY_EVO_DMA_CB_LOCATION_VRAM 0x00000000 +#define NV50_PDISPLAY_EVO_DMA_CB_LOCATION_SYSTEM 0x00000002 +#define NV50_PDISPLAY_EVO_DMA_CB_VALID 0x00000001 +#define NV50_PDISPLAY_EVO_UNK2(i) ((i) * 0x10 + 0x00610208) +#define NV50_PDISPLAY_EVO_HASH_TAG(i) ((i) * 0x10 + 0x0061020c) + +#define NV50_PDISPLAY_CURSOR 0x00610270 +#define NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i) ((i) * 0x10 + 0x00610270) +#define NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_ON 0x00000001 +#define NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS 0x00030000 +#define NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_ACTIVE 0x00010000 + +#define NV50_PDISPLAY_PIO_CTRL 0x00610300 +#define NV50_PDISPLAY_PIO_CTRL_PENDING 0x80000000 +#define NV50_PDISPLAY_PIO_CTRL_MTHD 0x00001ffc +#define NV50_PDISPLAY_PIO_CTRL_ENABLED 0x00000001 +#define NV50_PDISPLAY_PIO_DATA 0x00610304 + +#define NV50_PDISPLAY_CRTC_P(i, r) ((i) * 0x540 + NV50_PDISPLAY_CRTC_##r) +#define NV50_PDISPLAY_CRTC_C(i, r) (4 + (i) * 0x540 + NV50_PDISPLAY_CRTC_##r) +#define NV50_PDISPLAY_CRTC_UNK_0A18 /* mthd 0x0900 */ 0x00610a18 +#define NV50_PDISPLAY_CRTC_CLUT_MODE 0x00610a24 +#define NV50_PDISPLAY_CRTC_INTERLACE 0x00610a48 +#define NV50_PDISPLAY_CRTC_SCALE_CTRL 0x00610a50 +#define NV50_PDISPLAY_CRTC_CURSOR_CTRL 0x00610a58 +#define NV50_PDISPLAY_CRTC_UNK0A78 /* mthd 0x0904 */ 0x00610a78 +#define NV50_PDISPLAY_CRTC_UNK0AB8 0x00610ab8 +#define NV50_PDISPLAY_CRTC_DEPTH 0x00610ac8 +#define NV50_PDISPLAY_CRTC_CLOCK 0x00610ad0 +#define NV50_PDISPLAY_CRTC_COLOR_CTRL 0x00610ae0 +#define NV50_PDISPLAY_CRTC_SYNC_START_TO_BLANK_END 0x00610ae8 +#define NV50_PDISPLAY_CRTC_MODE_UNK1 0x00610af0 +#define NV50_PDISPLAY_CRTC_DISPLAY_TOTAL 0x00610af8 +#define NV50_PDISPLAY_CRTC_SYNC_DURATION 0x00610b00 +#define NV50_PDISPLAY_CRTC_MODE_UNK2 0x00610b08 +#define NV50_PDISPLAY_CRTC_UNK_0B10 /* mthd 0x0828 */ 0x00610b10 +#define NV50_PDISPLAY_CRTC_FB_SIZE 0x00610b18 +#define NV50_PDISPLAY_CRTC_FB_PITCH 0x00610b20 +#define NV50_PDISPLAY_CRTC_FB_PITCH_LINEAR 0x00100000 +#define NV50_PDISPLAY_CRTC_FB_POS 0x00610b28 +#define NV50_PDISPLAY_CRTC_SCALE_CENTER_OFFSET 0x00610b38 +#define NV50_PDISPLAY_CRTC_REAL_RES 0x00610b40 +#define NV50_PDISPLAY_CRTC_SCALE_RES1 0x00610b48 +#define NV50_PDISPLAY_CRTC_SCALE_RES2 0x00610b50 + +#define NV50_PDISPLAY_DAC_MODE_CTRL_P(i) (0x00610b58 + (i) * 0x8) +#define NV50_PDISPLAY_DAC_MODE_CTRL_C(i) (0x00610b5c + (i) * 0x8) +#define NV50_PDISPLAY_SOR_MODE_CTRL_P(i) (0x00610b70 + (i) * 0x8) +#define NV50_PDISPLAY_SOR_MODE_CTRL_C(i) (0x00610b74 + (i) * 0x8) +#define NV50_PDISPLAY_EXT_MODE_CTRL_P(i) (0x00610b80 + (i) * 0x8) +#define NV50_PDISPLAY_EXT_MODE_CTRL_C(i) (0x00610b84 + (i) * 0x8) +#define NV50_PDISPLAY_DAC_MODE_CTRL2_P(i) (0x00610bdc + (i) * 0x8) +#define NV50_PDISPLAY_DAC_MODE_CTRL2_C(i) (0x00610be0 + (i) * 0x8) +#define NV90_PDISPLAY_SOR_MODE_CTRL_P(i) (0x00610794 + (i) * 0x8) +#define NV90_PDISPLAY_SOR_MODE_CTRL_C(i) (0x00610798 + (i) * 0x8) + +#define NV50_PDISPLAY_CRTC_CLK 0x00614000 +#define NV50_PDISPLAY_CRTC_CLK_CTRL1(i) ((i) * 0x800 + 0x614100) +#define NV50_PDISPLAY_CRTC_CLK_CTRL1_CONNECTED 0x00000600 +#define NV50_PDISPLAY_CRTC_CLK_VPLL_A(i) ((i) * 0x800 + 0x614104) +#define NV50_PDISPLAY_CRTC_CLK_VPLL_B(i) ((i) * 0x800 + 0x614108) +#define NV50_PDISPLAY_CRTC_CLK_CTRL2(i) ((i) * 0x800 + 0x614200) + +#define NV50_PDISPLAY_DAC_CLK 0x00614000 +#define NV50_PDISPLAY_DAC_CLK_CTRL2(i) ((i) * 0x800 + 0x614280) + +#define NV50_PDISPLAY_SOR_CLK 0x00614000 +#define NV50_PDISPLAY_SOR_CLK_CTRL2(i) ((i) * 0x800 + 0x614300) + +#define NV50_PDISPLAY_VGACRTC(r) ((r) + 0x619400) + +#define NV50_PDISPLAY_DAC 0x0061a000 +#define NV50_PDISPLAY_DAC_DPMS_CTRL(i) (0x0061a004 + (i) * 0x800) +#define NV50_PDISPLAY_DAC_DPMS_CTRL_HSYNC_OFF 0x00000001 +#define NV50_PDISPLAY_DAC_DPMS_CTRL_VSYNC_OFF 0x00000004 +#define NV50_PDISPLAY_DAC_DPMS_CTRL_BLANKED 0x00000010 +#define NV50_PDISPLAY_DAC_DPMS_CTRL_OFF 0x00000040 +#define NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING 0x80000000 +#define NV50_PDISPLAY_DAC_LOAD_CTRL(i) (0x0061a00c + (i) * 0x800) +#define NV50_PDISPLAY_DAC_LOAD_CTRL_ACTIVE 0x00100000 +#define NV50_PDISPLAY_DAC_LOAD_CTRL_PRESENT 0x38000000 +#define NV50_PDISPLAY_DAC_LOAD_CTRL_DONE 0x80000000 +#define NV50_PDISPLAY_DAC_CLK_CTRL1(i) (0x0061a010 + (i) * 0x800) +#define NV50_PDISPLAY_DAC_CLK_CTRL1_CONNECTED 0x00000600 + +#define NV50_PDISPLAY_SOR 0x0061c000 +#define NV50_PDISPLAY_SOR_DPMS_CTRL(i) (0x0061c004 + (i) * 0x800) +#define NV50_PDISPLAY_SOR_DPMS_CTRL_PENDING 0x80000000 +#define NV50_PDISPLAY_SOR_DPMS_CTRL_ON 0x00000001 +#define NV50_PDISPLAY_SOR_CLK_CTRL1(i) (0x0061c008 + (i) * 0x800) +#define NV50_PDISPLAY_SOR_CLK_CTRL1_CONNECTED 0x00000600 +#define NV50_PDISPLAY_SOR_DPMS_STATE(i) (0x0061c030 + (i) * 0x800) +#define NV50_PDISPLAY_SOR_DPMS_STATE_ACTIVE 0x00030000 +#define NV50_PDISPLAY_SOR_DPMS_STATE_BLANKED 0x00080000 +#define NV50_PDISPLAY_SOR_DPMS_STATE_WAIT 0x10000000 +#define NV50_PDISP_SOR_PWM_DIV(i) (0x0061c080 + (i) * 0x800) +#define NV50_PDISP_SOR_PWM_CTL(i) (0x0061c084 + (i) * 0x800) +#define NV50_PDISP_SOR_PWM_CTL_NEW 0x80000000 +#define NVA3_PDISP_SOR_PWM_CTL_UNK 0x40000000 +#define NV50_PDISP_SOR_PWM_CTL_VAL 0x000007ff +#define NVA3_PDISP_SOR_PWM_CTL_VAL 0x00ffffff +#define NV50_SOR_DP_CTRL(i, l) (0x0061c10c + (i) * 0x800 + (l) * 0x80) +#define NV50_SOR_DP_CTRL_ENABLED 0x00000001 +#define NV50_SOR_DP_CTRL_ENHANCED_FRAME_ENABLED 0x00004000 +#define NV50_SOR_DP_CTRL_LANE_MASK 0x001f0000 +#define NV50_SOR_DP_CTRL_LANE_0_ENABLED 0x00010000 +#define NV50_SOR_DP_CTRL_LANE_1_ENABLED 0x00020000 +#define NV50_SOR_DP_CTRL_LANE_2_ENABLED 0x00040000 +#define NV50_SOR_DP_CTRL_LANE_3_ENABLED 0x00080000 +#define NV50_SOR_DP_CTRL_TRAINING_PATTERN 0x0f000000 +#define NV50_SOR_DP_CTRL_TRAINING_PATTERN_DISABLED 0x00000000 +#define NV50_SOR_DP_CTRL_TRAINING_PATTERN_1 0x01000000 +#define NV50_SOR_DP_CTRL_TRAINING_PATTERN_2 0x02000000 +#define NV50_SOR_DP_UNK118(i, l) (0x0061c118 + (i) * 0x800 + (l) * 0x80) +#define NV50_SOR_DP_UNK120(i, l) (0x0061c120 + (i) * 0x800 + (l) * 0x80) +#define NV50_SOR_DP_SCFG(i, l) (0x0061c128 + (i) * 0x800 + (l) * 0x80) +#define NV50_SOR_DP_UNK130(i, l) (0x0061c130 + (i) * 0x800 + (l) * 0x80) + +#define NV50_PDISPLAY_USER(i) ((i) * 0x1000 + 0x00640000) +#define NV50_PDISPLAY_USER_PUT(i) ((i) * 0x1000 + 0x00640000) +#define NV50_PDISPLAY_USER_GET(i) ((i) * 0x1000 + 0x00640004) + +#define NV50_PDISPLAY_CURSOR_USER 0x00647000 +#define NV50_PDISPLAY_CURSOR_USER_POS_CTRL(i) ((i) * 0x1000 + 0x00647080) +#define NV50_PDISPLAY_CURSOR_USER_POS(i) ((i) * 0x1000 + 0x00647084) diff --git a/drivers/gpu/drm/nouveau/nouveau_sgdma.c b/drivers/gpu/drm/nouveau/nouveau_sgdma.c new file mode 100644 index 00000000..47f245ed --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_sgdma.c @@ -0,0 +1,444 @@ +#include "drmP.h" +#include "nouveau_drv.h" +#include <linux/pagemap.h> +#include <linux/slab.h> + +#define NV_CTXDMA_PAGE_SHIFT 12 +#define NV_CTXDMA_PAGE_SIZE (1 << NV_CTXDMA_PAGE_SHIFT) +#define NV_CTXDMA_PAGE_MASK (NV_CTXDMA_PAGE_SIZE - 1) + +struct nouveau_sgdma_be { + /* this has to be the first field so populate/unpopulated in + * nouve_bo.c works properly, otherwise have to move them here + */ + struct ttm_dma_tt ttm; + struct drm_device *dev; + u64 offset; +}; + +static void +nouveau_sgdma_destroy(struct ttm_tt *ttm) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + + if (ttm) { + NV_DEBUG(nvbe->dev, "\n"); + ttm_dma_tt_fini(&nvbe->ttm); + kfree(nvbe); + } +} + +static int +nv04_sgdma_bind(struct ttm_tt *ttm, struct ttm_mem_reg *mem) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct drm_device *dev = nvbe->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gpuobj = dev_priv->gart_info.sg_ctxdma; + unsigned i, j, pte; + + NV_DEBUG(dev, "pg=0x%lx\n", mem->start); + + nvbe->offset = mem->start << PAGE_SHIFT; + pte = (nvbe->offset >> NV_CTXDMA_PAGE_SHIFT) + 2; + for (i = 0; i < ttm->num_pages; i++) { + dma_addr_t dma_offset = nvbe->ttm.dma_address[i]; + uint32_t offset_l = lower_32_bits(dma_offset); + + for (j = 0; j < PAGE_SIZE / NV_CTXDMA_PAGE_SIZE; j++, pte++) { + nv_wo32(gpuobj, (pte * 4) + 0, offset_l | 3); + offset_l += NV_CTXDMA_PAGE_SIZE; + } + } + + return 0; +} + +static int +nv04_sgdma_unbind(struct ttm_tt *ttm) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct drm_device *dev = nvbe->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gpuobj = dev_priv->gart_info.sg_ctxdma; + unsigned i, j, pte; + + NV_DEBUG(dev, "\n"); + + if (ttm->state != tt_bound) + return 0; + + pte = (nvbe->offset >> NV_CTXDMA_PAGE_SHIFT) + 2; + for (i = 0; i < ttm->num_pages; i++) { + for (j = 0; j < PAGE_SIZE / NV_CTXDMA_PAGE_SIZE; j++, pte++) + nv_wo32(gpuobj, (pte * 4) + 0, 0x00000000); + } + + return 0; +} + +static struct ttm_backend_func nv04_sgdma_backend = { + .bind = nv04_sgdma_bind, + .unbind = nv04_sgdma_unbind, + .destroy = nouveau_sgdma_destroy +}; + +static void +nv41_sgdma_flush(struct nouveau_sgdma_be *nvbe) +{ + struct drm_device *dev = nvbe->dev; + + nv_wr32(dev, 0x100810, 0x00000022); + if (!nv_wait(dev, 0x100810, 0x00000100, 0x00000100)) + NV_ERROR(dev, "vm flush timeout: 0x%08x\n", + nv_rd32(dev, 0x100810)); + nv_wr32(dev, 0x100810, 0x00000000); +} + +static int +nv41_sgdma_bind(struct ttm_tt *ttm, struct ttm_mem_reg *mem) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct drm_nouveau_private *dev_priv = nvbe->dev->dev_private; + struct nouveau_gpuobj *pgt = dev_priv->gart_info.sg_ctxdma; + dma_addr_t *list = nvbe->ttm.dma_address; + u32 pte = mem->start << 2; + u32 cnt = ttm->num_pages; + + nvbe->offset = mem->start << PAGE_SHIFT; + + while (cnt--) { + nv_wo32(pgt, pte, (*list++ >> 7) | 1); + pte += 4; + } + + nv41_sgdma_flush(nvbe); + return 0; +} + +static int +nv41_sgdma_unbind(struct ttm_tt *ttm) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct drm_nouveau_private *dev_priv = nvbe->dev->dev_private; + struct nouveau_gpuobj *pgt = dev_priv->gart_info.sg_ctxdma; + u32 pte = (nvbe->offset >> 12) << 2; + u32 cnt = ttm->num_pages; + + while (cnt--) { + nv_wo32(pgt, pte, 0x00000000); + pte += 4; + } + + nv41_sgdma_flush(nvbe); + return 0; +} + +static struct ttm_backend_func nv41_sgdma_backend = { + .bind = nv41_sgdma_bind, + .unbind = nv41_sgdma_unbind, + .destroy = nouveau_sgdma_destroy +}; + +static void +nv44_sgdma_flush(struct ttm_tt *ttm) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct drm_device *dev = nvbe->dev; + + nv_wr32(dev, 0x100814, (ttm->num_pages - 1) << 12); + nv_wr32(dev, 0x100808, nvbe->offset | 0x20); + if (!nv_wait(dev, 0x100808, 0x00000001, 0x00000001)) + NV_ERROR(dev, "gart flush timeout: 0x%08x\n", + nv_rd32(dev, 0x100808)); + nv_wr32(dev, 0x100808, 0x00000000); +} + +static void +nv44_sgdma_fill(struct nouveau_gpuobj *pgt, dma_addr_t *list, u32 base, u32 cnt) +{ + struct drm_nouveau_private *dev_priv = pgt->dev->dev_private; + dma_addr_t dummy = dev_priv->gart_info.dummy.addr; + u32 pte, tmp[4]; + + pte = base >> 2; + base &= ~0x0000000f; + + tmp[0] = nv_ro32(pgt, base + 0x0); + tmp[1] = nv_ro32(pgt, base + 0x4); + tmp[2] = nv_ro32(pgt, base + 0x8); + tmp[3] = nv_ro32(pgt, base + 0xc); + while (cnt--) { + u32 addr = list ? (*list++ >> 12) : (dummy >> 12); + switch (pte++ & 0x3) { + case 0: + tmp[0] &= ~0x07ffffff; + tmp[0] |= addr; + break; + case 1: + tmp[0] &= ~0xf8000000; + tmp[0] |= addr << 27; + tmp[1] &= ~0x003fffff; + tmp[1] |= addr >> 5; + break; + case 2: + tmp[1] &= ~0xffc00000; + tmp[1] |= addr << 22; + tmp[2] &= ~0x0001ffff; + tmp[2] |= addr >> 10; + break; + case 3: + tmp[2] &= ~0xfffe0000; + tmp[2] |= addr << 17; + tmp[3] &= ~0x00000fff; + tmp[3] |= addr >> 15; + break; + } + } + + tmp[3] |= 0x40000000; + + nv_wo32(pgt, base + 0x0, tmp[0]); + nv_wo32(pgt, base + 0x4, tmp[1]); + nv_wo32(pgt, base + 0x8, tmp[2]); + nv_wo32(pgt, base + 0xc, tmp[3]); +} + +static int +nv44_sgdma_bind(struct ttm_tt *ttm, struct ttm_mem_reg *mem) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct drm_nouveau_private *dev_priv = nvbe->dev->dev_private; + struct nouveau_gpuobj *pgt = dev_priv->gart_info.sg_ctxdma; + dma_addr_t *list = nvbe->ttm.dma_address; + u32 pte = mem->start << 2, tmp[4]; + u32 cnt = ttm->num_pages; + int i; + + nvbe->offset = mem->start << PAGE_SHIFT; + + if (pte & 0x0000000c) { + u32 max = 4 - ((pte >> 2) & 0x3); + u32 part = (cnt > max) ? max : cnt; + nv44_sgdma_fill(pgt, list, pte, part); + pte += (part << 2); + list += part; + cnt -= part; + } + + while (cnt >= 4) { + for (i = 0; i < 4; i++) + tmp[i] = *list++ >> 12; + nv_wo32(pgt, pte + 0x0, tmp[0] >> 0 | tmp[1] << 27); + nv_wo32(pgt, pte + 0x4, tmp[1] >> 5 | tmp[2] << 22); + nv_wo32(pgt, pte + 0x8, tmp[2] >> 10 | tmp[3] << 17); + nv_wo32(pgt, pte + 0xc, tmp[3] >> 15 | 0x40000000); + pte += 0x10; + cnt -= 4; + } + + if (cnt) + nv44_sgdma_fill(pgt, list, pte, cnt); + + nv44_sgdma_flush(ttm); + return 0; +} + +static int +nv44_sgdma_unbind(struct ttm_tt *ttm) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct drm_nouveau_private *dev_priv = nvbe->dev->dev_private; + struct nouveau_gpuobj *pgt = dev_priv->gart_info.sg_ctxdma; + u32 pte = (nvbe->offset >> 12) << 2; + u32 cnt = ttm->num_pages; + + if (pte & 0x0000000c) { + u32 max = 4 - ((pte >> 2) & 0x3); + u32 part = (cnt > max) ? max : cnt; + nv44_sgdma_fill(pgt, NULL, pte, part); + pte += (part << 2); + cnt -= part; + } + + while (cnt >= 4) { + nv_wo32(pgt, pte + 0x0, 0x00000000); + nv_wo32(pgt, pte + 0x4, 0x00000000); + nv_wo32(pgt, pte + 0x8, 0x00000000); + nv_wo32(pgt, pte + 0xc, 0x00000000); + pte += 0x10; + cnt -= 4; + } + + if (cnt) + nv44_sgdma_fill(pgt, NULL, pte, cnt); + + nv44_sgdma_flush(ttm); + return 0; +} + +static struct ttm_backend_func nv44_sgdma_backend = { + .bind = nv44_sgdma_bind, + .unbind = nv44_sgdma_unbind, + .destroy = nouveau_sgdma_destroy +}; + +static int +nv50_sgdma_bind(struct ttm_tt *ttm, struct ttm_mem_reg *mem) +{ + struct nouveau_sgdma_be *nvbe = (struct nouveau_sgdma_be *)ttm; + struct nouveau_mem *node = mem->mm_node; + + /* noop: bound in move_notify() */ + node->pages = nvbe->ttm.dma_address; + return 0; +} + +static int +nv50_sgdma_unbind(struct ttm_tt *ttm) +{ + /* noop: unbound in move_notify() */ + return 0; +} + +static struct ttm_backend_func nv50_sgdma_backend = { + .bind = nv50_sgdma_bind, + .unbind = nv50_sgdma_unbind, + .destroy = nouveau_sgdma_destroy +}; + +struct ttm_tt * +nouveau_sgdma_create_ttm(struct ttm_bo_device *bdev, + unsigned long size, uint32_t page_flags, + struct page *dummy_read_page) +{ + struct drm_nouveau_private *dev_priv = nouveau_bdev(bdev); + struct drm_device *dev = dev_priv->dev; + struct nouveau_sgdma_be *nvbe; + + nvbe = kzalloc(sizeof(*nvbe), GFP_KERNEL); + if (!nvbe) + return NULL; + + nvbe->dev = dev; + nvbe->ttm.ttm.func = dev_priv->gart_info.func; + + if (ttm_dma_tt_init(&nvbe->ttm, bdev, size, page_flags, dummy_read_page)) { + kfree(nvbe); + return NULL; + } + return &nvbe->ttm.ttm; +} + +int +nouveau_sgdma_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gpuobj = NULL; + u32 aper_size, align; + int ret; + + if (dev_priv->card_type >= NV_40 && pci_is_pcie(dev->pdev)) + aper_size = 512 * 1024 * 1024; + else + aper_size = 64 * 1024 * 1024; + + /* Dear NVIDIA, NV44+ would like proper present bits in PTEs for + * christmas. The cards before it have them, the cards after + * it have them, why is NV44 so unloved? + */ + dev_priv->gart_info.dummy.page = alloc_page(GFP_DMA32 | GFP_KERNEL); + if (!dev_priv->gart_info.dummy.page) + return -ENOMEM; + + dev_priv->gart_info.dummy.addr = + pci_map_page(dev->pdev, dev_priv->gart_info.dummy.page, + 0, PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(dev->pdev, dev_priv->gart_info.dummy.addr)) { + NV_ERROR(dev, "error mapping dummy page\n"); + __free_page(dev_priv->gart_info.dummy.page); + dev_priv->gart_info.dummy.page = NULL; + return -ENOMEM; + } + + if (dev_priv->card_type >= NV_50) { + dev_priv->gart_info.aper_base = 0; + dev_priv->gart_info.aper_size = aper_size; + dev_priv->gart_info.type = NOUVEAU_GART_HW; + dev_priv->gart_info.func = &nv50_sgdma_backend; + } else + if (0 && pci_is_pcie(dev->pdev) && + dev_priv->chipset > 0x40 && dev_priv->chipset != 0x45) { + if (nv44_graph_class(dev)) { + dev_priv->gart_info.func = &nv44_sgdma_backend; + align = 512 * 1024; + } else { + dev_priv->gart_info.func = &nv41_sgdma_backend; + align = 16; + } + + ret = nouveau_gpuobj_new(dev, NULL, aper_size / 1024, align, + NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &gpuobj); + if (ret) { + NV_ERROR(dev, "Error creating sgdma object: %d\n", ret); + return ret; + } + + dev_priv->gart_info.sg_ctxdma = gpuobj; + dev_priv->gart_info.aper_base = 0; + dev_priv->gart_info.aper_size = aper_size; + dev_priv->gart_info.type = NOUVEAU_GART_HW; + } else { + ret = nouveau_gpuobj_new(dev, NULL, (aper_size / 1024) + 8, 16, + NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &gpuobj); + if (ret) { + NV_ERROR(dev, "Error creating sgdma object: %d\n", ret); + return ret; + } + + nv_wo32(gpuobj, 0, NV_CLASS_DMA_IN_MEMORY | + (1 << 12) /* PT present */ | + (0 << 13) /* PT *not* linear */ | + (0 << 14) /* RW */ | + (2 << 16) /* PCI */); + nv_wo32(gpuobj, 4, aper_size - 1); + + dev_priv->gart_info.sg_ctxdma = gpuobj; + dev_priv->gart_info.aper_base = 0; + dev_priv->gart_info.aper_size = aper_size; + dev_priv->gart_info.type = NOUVEAU_GART_PDMA; + dev_priv->gart_info.func = &nv04_sgdma_backend; + } + + return 0; +} + +void +nouveau_sgdma_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nouveau_gpuobj_ref(NULL, &dev_priv->gart_info.sg_ctxdma); + + if (dev_priv->gart_info.dummy.page) { + pci_unmap_page(dev->pdev, dev_priv->gart_info.dummy.addr, + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + __free_page(dev_priv->gart_info.dummy.page); + dev_priv->gart_info.dummy.page = NULL; + } +} + +uint32_t +nouveau_sgdma_get_physical(struct drm_device *dev, uint32_t offset) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gpuobj = dev_priv->gart_info.sg_ctxdma; + int pte = (offset >> NV_CTXDMA_PAGE_SHIFT) + 2; + + BUG_ON(dev_priv->card_type >= NV_50); + + return (nv_ro32(gpuobj, 4 * pte) & ~NV_CTXDMA_PAGE_MASK) | + (offset & NV_CTXDMA_PAGE_MASK); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_state.c b/drivers/gpu/drm/nouveau/nouveau_state.c new file mode 100644 index 00000000..c2a8511e --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_state.c @@ -0,0 +1,1449 @@ +/* + * Copyright 2005 Stephane Marchesin + * Copyright 2008 Stuart Bennett + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <linux/swab.h> +#include <linux/slab.h> +#include "drmP.h" +#include "drm.h" +#include "drm_sarea.h" +#include "drm_crtc_helper.h" +#include <linux/vgaarb.h> +#include <linux/vga_switcheroo.h> + +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_fbcon.h" +#include "nouveau_ramht.h" +#include "nouveau_gpio.h" +#include "nouveau_pm.h" +#include "nv50_display.h" + +static void nouveau_stub_takedown(struct drm_device *dev) {} +static int nouveau_stub_init(struct drm_device *dev) { return 0; } + +static int nouveau_init_engine_ptrs(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_engine *engine = &dev_priv->engine; + + switch (dev_priv->chipset & 0xf0) { + case 0x00: + engine->instmem.init = nv04_instmem_init; + engine->instmem.takedown = nv04_instmem_takedown; + engine->instmem.suspend = nv04_instmem_suspend; + engine->instmem.resume = nv04_instmem_resume; + engine->instmem.get = nv04_instmem_get; + engine->instmem.put = nv04_instmem_put; + engine->instmem.map = nv04_instmem_map; + engine->instmem.unmap = nv04_instmem_unmap; + engine->instmem.flush = nv04_instmem_flush; + engine->mc.init = nv04_mc_init; + engine->mc.takedown = nv04_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nv04_fb_init; + engine->fb.takedown = nv04_fb_takedown; + engine->fifo.channels = 16; + engine->fifo.init = nv04_fifo_init; + engine->fifo.takedown = nv04_fifo_fini; + engine->fifo.disable = nv04_fifo_disable; + engine->fifo.enable = nv04_fifo_enable; + engine->fifo.reassign = nv04_fifo_reassign; + engine->fifo.cache_pull = nv04_fifo_cache_pull; + engine->fifo.channel_id = nv04_fifo_channel_id; + engine->fifo.create_context = nv04_fifo_create_context; + engine->fifo.destroy_context = nv04_fifo_destroy_context; + engine->fifo.load_context = nv04_fifo_load_context; + engine->fifo.unload_context = nv04_fifo_unload_context; + engine->display.early_init = nv04_display_early_init; + engine->display.late_takedown = nv04_display_late_takedown; + engine->display.create = nv04_display_create; + engine->display.destroy = nv04_display_destroy; + engine->display.init = nv04_display_init; + engine->display.fini = nv04_display_fini; + engine->pm.clocks_get = nv04_pm_clocks_get; + engine->pm.clocks_pre = nv04_pm_clocks_pre; + engine->pm.clocks_set = nv04_pm_clocks_set; + engine->vram.init = nv04_fb_vram_init; + engine->vram.takedown = nouveau_stub_takedown; + engine->vram.flags_valid = nouveau_mem_flags_valid; + break; + case 0x10: + engine->instmem.init = nv04_instmem_init; + engine->instmem.takedown = nv04_instmem_takedown; + engine->instmem.suspend = nv04_instmem_suspend; + engine->instmem.resume = nv04_instmem_resume; + engine->instmem.get = nv04_instmem_get; + engine->instmem.put = nv04_instmem_put; + engine->instmem.map = nv04_instmem_map; + engine->instmem.unmap = nv04_instmem_unmap; + engine->instmem.flush = nv04_instmem_flush; + engine->mc.init = nv04_mc_init; + engine->mc.takedown = nv04_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nv10_fb_init; + engine->fb.takedown = nv10_fb_takedown; + engine->fb.init_tile_region = nv10_fb_init_tile_region; + engine->fb.set_tile_region = nv10_fb_set_tile_region; + engine->fb.free_tile_region = nv10_fb_free_tile_region; + engine->fifo.channels = 32; + engine->fifo.init = nv10_fifo_init; + engine->fifo.takedown = nv04_fifo_fini; + engine->fifo.disable = nv04_fifo_disable; + engine->fifo.enable = nv04_fifo_enable; + engine->fifo.reassign = nv04_fifo_reassign; + engine->fifo.cache_pull = nv04_fifo_cache_pull; + engine->fifo.channel_id = nv10_fifo_channel_id; + engine->fifo.create_context = nv10_fifo_create_context; + engine->fifo.destroy_context = nv04_fifo_destroy_context; + engine->fifo.load_context = nv10_fifo_load_context; + engine->fifo.unload_context = nv10_fifo_unload_context; + engine->display.early_init = nv04_display_early_init; + engine->display.late_takedown = nv04_display_late_takedown; + engine->display.create = nv04_display_create; + engine->display.destroy = nv04_display_destroy; + engine->display.init = nv04_display_init; + engine->display.fini = nv04_display_fini; + engine->gpio.drive = nv10_gpio_drive; + engine->gpio.sense = nv10_gpio_sense; + engine->pm.clocks_get = nv04_pm_clocks_get; + engine->pm.clocks_pre = nv04_pm_clocks_pre; + engine->pm.clocks_set = nv04_pm_clocks_set; + if (dev_priv->chipset == 0x1a || + dev_priv->chipset == 0x1f) + engine->vram.init = nv1a_fb_vram_init; + else + engine->vram.init = nv10_fb_vram_init; + engine->vram.takedown = nouveau_stub_takedown; + engine->vram.flags_valid = nouveau_mem_flags_valid; + break; + case 0x20: + engine->instmem.init = nv04_instmem_init; + engine->instmem.takedown = nv04_instmem_takedown; + engine->instmem.suspend = nv04_instmem_suspend; + engine->instmem.resume = nv04_instmem_resume; + engine->instmem.get = nv04_instmem_get; + engine->instmem.put = nv04_instmem_put; + engine->instmem.map = nv04_instmem_map; + engine->instmem.unmap = nv04_instmem_unmap; + engine->instmem.flush = nv04_instmem_flush; + engine->mc.init = nv04_mc_init; + engine->mc.takedown = nv04_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nv20_fb_init; + engine->fb.takedown = nv20_fb_takedown; + engine->fb.init_tile_region = nv20_fb_init_tile_region; + engine->fb.set_tile_region = nv20_fb_set_tile_region; + engine->fb.free_tile_region = nv20_fb_free_tile_region; + engine->fifo.channels = 32; + engine->fifo.init = nv10_fifo_init; + engine->fifo.takedown = nv04_fifo_fini; + engine->fifo.disable = nv04_fifo_disable; + engine->fifo.enable = nv04_fifo_enable; + engine->fifo.reassign = nv04_fifo_reassign; + engine->fifo.cache_pull = nv04_fifo_cache_pull; + engine->fifo.channel_id = nv10_fifo_channel_id; + engine->fifo.create_context = nv10_fifo_create_context; + engine->fifo.destroy_context = nv04_fifo_destroy_context; + engine->fifo.load_context = nv10_fifo_load_context; + engine->fifo.unload_context = nv10_fifo_unload_context; + engine->display.early_init = nv04_display_early_init; + engine->display.late_takedown = nv04_display_late_takedown; + engine->display.create = nv04_display_create; + engine->display.destroy = nv04_display_destroy; + engine->display.init = nv04_display_init; + engine->display.fini = nv04_display_fini; + engine->gpio.drive = nv10_gpio_drive; + engine->gpio.sense = nv10_gpio_sense; + engine->pm.clocks_get = nv04_pm_clocks_get; + engine->pm.clocks_pre = nv04_pm_clocks_pre; + engine->pm.clocks_set = nv04_pm_clocks_set; + engine->vram.init = nv20_fb_vram_init; + engine->vram.takedown = nouveau_stub_takedown; + engine->vram.flags_valid = nouveau_mem_flags_valid; + break; + case 0x30: + engine->instmem.init = nv04_instmem_init; + engine->instmem.takedown = nv04_instmem_takedown; + engine->instmem.suspend = nv04_instmem_suspend; + engine->instmem.resume = nv04_instmem_resume; + engine->instmem.get = nv04_instmem_get; + engine->instmem.put = nv04_instmem_put; + engine->instmem.map = nv04_instmem_map; + engine->instmem.unmap = nv04_instmem_unmap; + engine->instmem.flush = nv04_instmem_flush; + engine->mc.init = nv04_mc_init; + engine->mc.takedown = nv04_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nv30_fb_init; + engine->fb.takedown = nv30_fb_takedown; + engine->fb.init_tile_region = nv30_fb_init_tile_region; + engine->fb.set_tile_region = nv10_fb_set_tile_region; + engine->fb.free_tile_region = nv30_fb_free_tile_region; + engine->fifo.channels = 32; + engine->fifo.init = nv10_fifo_init; + engine->fifo.takedown = nv04_fifo_fini; + engine->fifo.disable = nv04_fifo_disable; + engine->fifo.enable = nv04_fifo_enable; + engine->fifo.reassign = nv04_fifo_reassign; + engine->fifo.cache_pull = nv04_fifo_cache_pull; + engine->fifo.channel_id = nv10_fifo_channel_id; + engine->fifo.create_context = nv10_fifo_create_context; + engine->fifo.destroy_context = nv04_fifo_destroy_context; + engine->fifo.load_context = nv10_fifo_load_context; + engine->fifo.unload_context = nv10_fifo_unload_context; + engine->display.early_init = nv04_display_early_init; + engine->display.late_takedown = nv04_display_late_takedown; + engine->display.create = nv04_display_create; + engine->display.destroy = nv04_display_destroy; + engine->display.init = nv04_display_init; + engine->display.fini = nv04_display_fini; + engine->gpio.drive = nv10_gpio_drive; + engine->gpio.sense = nv10_gpio_sense; + engine->pm.clocks_get = nv04_pm_clocks_get; + engine->pm.clocks_pre = nv04_pm_clocks_pre; + engine->pm.clocks_set = nv04_pm_clocks_set; + engine->pm.voltage_get = nouveau_voltage_gpio_get; + engine->pm.voltage_set = nouveau_voltage_gpio_set; + engine->vram.init = nv20_fb_vram_init; + engine->vram.takedown = nouveau_stub_takedown; + engine->vram.flags_valid = nouveau_mem_flags_valid; + break; + case 0x40: + case 0x60: + engine->instmem.init = nv04_instmem_init; + engine->instmem.takedown = nv04_instmem_takedown; + engine->instmem.suspend = nv04_instmem_suspend; + engine->instmem.resume = nv04_instmem_resume; + engine->instmem.get = nv04_instmem_get; + engine->instmem.put = nv04_instmem_put; + engine->instmem.map = nv04_instmem_map; + engine->instmem.unmap = nv04_instmem_unmap; + engine->instmem.flush = nv04_instmem_flush; + engine->mc.init = nv40_mc_init; + engine->mc.takedown = nv40_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nv40_fb_init; + engine->fb.takedown = nv40_fb_takedown; + engine->fb.init_tile_region = nv30_fb_init_tile_region; + engine->fb.set_tile_region = nv40_fb_set_tile_region; + engine->fb.free_tile_region = nv30_fb_free_tile_region; + engine->fifo.channels = 32; + engine->fifo.init = nv40_fifo_init; + engine->fifo.takedown = nv04_fifo_fini; + engine->fifo.disable = nv04_fifo_disable; + engine->fifo.enable = nv04_fifo_enable; + engine->fifo.reassign = nv04_fifo_reassign; + engine->fifo.cache_pull = nv04_fifo_cache_pull; + engine->fifo.channel_id = nv10_fifo_channel_id; + engine->fifo.create_context = nv40_fifo_create_context; + engine->fifo.destroy_context = nv04_fifo_destroy_context; + engine->fifo.load_context = nv40_fifo_load_context; + engine->fifo.unload_context = nv40_fifo_unload_context; + engine->display.early_init = nv04_display_early_init; + engine->display.late_takedown = nv04_display_late_takedown; + engine->display.create = nv04_display_create; + engine->display.destroy = nv04_display_destroy; + engine->display.init = nv04_display_init; + engine->display.fini = nv04_display_fini; + engine->gpio.init = nv10_gpio_init; + engine->gpio.fini = nv10_gpio_fini; + engine->gpio.drive = nv10_gpio_drive; + engine->gpio.sense = nv10_gpio_sense; + engine->gpio.irq_enable = nv10_gpio_irq_enable; + engine->pm.clocks_get = nv40_pm_clocks_get; + engine->pm.clocks_pre = nv40_pm_clocks_pre; + engine->pm.clocks_set = nv40_pm_clocks_set; + engine->pm.voltage_get = nouveau_voltage_gpio_get; + engine->pm.voltage_set = nouveau_voltage_gpio_set; + engine->pm.temp_get = nv40_temp_get; + engine->pm.pwm_get = nv40_pm_pwm_get; + engine->pm.pwm_set = nv40_pm_pwm_set; + engine->vram.init = nv40_fb_vram_init; + engine->vram.takedown = nouveau_stub_takedown; + engine->vram.flags_valid = nouveau_mem_flags_valid; + break; + case 0x50: + case 0x80: /* gotta love NVIDIA's consistency.. */ + case 0x90: + case 0xa0: + engine->instmem.init = nv50_instmem_init; + engine->instmem.takedown = nv50_instmem_takedown; + engine->instmem.suspend = nv50_instmem_suspend; + engine->instmem.resume = nv50_instmem_resume; + engine->instmem.get = nv50_instmem_get; + engine->instmem.put = nv50_instmem_put; + engine->instmem.map = nv50_instmem_map; + engine->instmem.unmap = nv50_instmem_unmap; + if (dev_priv->chipset == 0x50) + engine->instmem.flush = nv50_instmem_flush; + else + engine->instmem.flush = nv84_instmem_flush; + engine->mc.init = nv50_mc_init; + engine->mc.takedown = nv50_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nv50_fb_init; + engine->fb.takedown = nv50_fb_takedown; + engine->fifo.channels = 128; + engine->fifo.init = nv50_fifo_init; + engine->fifo.takedown = nv50_fifo_takedown; + engine->fifo.disable = nv04_fifo_disable; + engine->fifo.enable = nv04_fifo_enable; + engine->fifo.reassign = nv04_fifo_reassign; + engine->fifo.channel_id = nv50_fifo_channel_id; + engine->fifo.create_context = nv50_fifo_create_context; + engine->fifo.destroy_context = nv50_fifo_destroy_context; + engine->fifo.load_context = nv50_fifo_load_context; + engine->fifo.unload_context = nv50_fifo_unload_context; + engine->fifo.tlb_flush = nv50_fifo_tlb_flush; + engine->display.early_init = nv50_display_early_init; + engine->display.late_takedown = nv50_display_late_takedown; + engine->display.create = nv50_display_create; + engine->display.destroy = nv50_display_destroy; + engine->display.init = nv50_display_init; + engine->display.fini = nv50_display_fini; + engine->gpio.init = nv50_gpio_init; + engine->gpio.fini = nv50_gpio_fini; + engine->gpio.drive = nv50_gpio_drive; + engine->gpio.sense = nv50_gpio_sense; + engine->gpio.irq_enable = nv50_gpio_irq_enable; + switch (dev_priv->chipset) { + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0x98: + case 0xa0: + case 0xaa: + case 0xac: + case 0x50: + engine->pm.clocks_get = nv50_pm_clocks_get; + engine->pm.clocks_pre = nv50_pm_clocks_pre; + engine->pm.clocks_set = nv50_pm_clocks_set; + break; + default: + engine->pm.clocks_get = nva3_pm_clocks_get; + engine->pm.clocks_pre = nva3_pm_clocks_pre; + engine->pm.clocks_set = nva3_pm_clocks_set; + break; + } + engine->pm.voltage_get = nouveau_voltage_gpio_get; + engine->pm.voltage_set = nouveau_voltage_gpio_set; + if (dev_priv->chipset >= 0x84) + engine->pm.temp_get = nv84_temp_get; + else + engine->pm.temp_get = nv40_temp_get; + engine->pm.pwm_get = nv50_pm_pwm_get; + engine->pm.pwm_set = nv50_pm_pwm_set; + engine->vram.init = nv50_vram_init; + engine->vram.takedown = nv50_vram_fini; + engine->vram.get = nv50_vram_new; + engine->vram.put = nv50_vram_del; + engine->vram.flags_valid = nv50_vram_flags_valid; + break; + case 0xc0: + engine->instmem.init = nvc0_instmem_init; + engine->instmem.takedown = nvc0_instmem_takedown; + engine->instmem.suspend = nvc0_instmem_suspend; + engine->instmem.resume = nvc0_instmem_resume; + engine->instmem.get = nv50_instmem_get; + engine->instmem.put = nv50_instmem_put; + engine->instmem.map = nv50_instmem_map; + engine->instmem.unmap = nv50_instmem_unmap; + engine->instmem.flush = nv84_instmem_flush; + engine->mc.init = nv50_mc_init; + engine->mc.takedown = nv50_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nvc0_fb_init; + engine->fb.takedown = nvc0_fb_takedown; + engine->fifo.channels = 128; + engine->fifo.init = nvc0_fifo_init; + engine->fifo.takedown = nvc0_fifo_takedown; + engine->fifo.disable = nvc0_fifo_disable; + engine->fifo.enable = nvc0_fifo_enable; + engine->fifo.reassign = nvc0_fifo_reassign; + engine->fifo.channel_id = nvc0_fifo_channel_id; + engine->fifo.create_context = nvc0_fifo_create_context; + engine->fifo.destroy_context = nvc0_fifo_destroy_context; + engine->fifo.load_context = nvc0_fifo_load_context; + engine->fifo.unload_context = nvc0_fifo_unload_context; + engine->display.early_init = nv50_display_early_init; + engine->display.late_takedown = nv50_display_late_takedown; + engine->display.create = nv50_display_create; + engine->display.destroy = nv50_display_destroy; + engine->display.init = nv50_display_init; + engine->display.fini = nv50_display_fini; + engine->gpio.init = nv50_gpio_init; + engine->gpio.fini = nv50_gpio_fini; + engine->gpio.drive = nv50_gpio_drive; + engine->gpio.sense = nv50_gpio_sense; + engine->gpio.irq_enable = nv50_gpio_irq_enable; + engine->vram.init = nvc0_vram_init; + engine->vram.takedown = nv50_vram_fini; + engine->vram.get = nvc0_vram_new; + engine->vram.put = nv50_vram_del; + engine->vram.flags_valid = nvc0_vram_flags_valid; + engine->pm.temp_get = nv84_temp_get; + engine->pm.clocks_get = nvc0_pm_clocks_get; + engine->pm.clocks_pre = nvc0_pm_clocks_pre; + engine->pm.clocks_set = nvc0_pm_clocks_set; + engine->pm.voltage_get = nouveau_voltage_gpio_get; + engine->pm.voltage_set = nouveau_voltage_gpio_set; + engine->pm.pwm_get = nv50_pm_pwm_get; + engine->pm.pwm_set = nv50_pm_pwm_set; + break; + case 0xd0: + engine->instmem.init = nvc0_instmem_init; + engine->instmem.takedown = nvc0_instmem_takedown; + engine->instmem.suspend = nvc0_instmem_suspend; + engine->instmem.resume = nvc0_instmem_resume; + engine->instmem.get = nv50_instmem_get; + engine->instmem.put = nv50_instmem_put; + engine->instmem.map = nv50_instmem_map; + engine->instmem.unmap = nv50_instmem_unmap; + engine->instmem.flush = nv84_instmem_flush; + engine->mc.init = nv50_mc_init; + engine->mc.takedown = nv50_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nvc0_fb_init; + engine->fb.takedown = nvc0_fb_takedown; + engine->fifo.channels = 128; + engine->fifo.init = nvc0_fifo_init; + engine->fifo.takedown = nvc0_fifo_takedown; + engine->fifo.disable = nvc0_fifo_disable; + engine->fifo.enable = nvc0_fifo_enable; + engine->fifo.reassign = nvc0_fifo_reassign; + engine->fifo.channel_id = nvc0_fifo_channel_id; + engine->fifo.create_context = nvc0_fifo_create_context; + engine->fifo.destroy_context = nvc0_fifo_destroy_context; + engine->fifo.load_context = nvc0_fifo_load_context; + engine->fifo.unload_context = nvc0_fifo_unload_context; + engine->display.early_init = nouveau_stub_init; + engine->display.late_takedown = nouveau_stub_takedown; + engine->display.create = nvd0_display_create; + engine->display.destroy = nvd0_display_destroy; + engine->display.init = nvd0_display_init; + engine->display.fini = nvd0_display_fini; + engine->gpio.init = nv50_gpio_init; + engine->gpio.fini = nv50_gpio_fini; + engine->gpio.drive = nvd0_gpio_drive; + engine->gpio.sense = nvd0_gpio_sense; + engine->gpio.irq_enable = nv50_gpio_irq_enable; + engine->vram.init = nvc0_vram_init; + engine->vram.takedown = nv50_vram_fini; + engine->vram.get = nvc0_vram_new; + engine->vram.put = nv50_vram_del; + engine->vram.flags_valid = nvc0_vram_flags_valid; + engine->pm.temp_get = nv84_temp_get; + engine->pm.clocks_get = nvc0_pm_clocks_get; + engine->pm.clocks_pre = nvc0_pm_clocks_pre; + engine->pm.clocks_set = nvc0_pm_clocks_set; + engine->pm.voltage_get = nouveau_voltage_gpio_get; + engine->pm.voltage_set = nouveau_voltage_gpio_set; + break; + case 0xe0: + engine->instmem.init = nvc0_instmem_init; + engine->instmem.takedown = nvc0_instmem_takedown; + engine->instmem.suspend = nvc0_instmem_suspend; + engine->instmem.resume = nvc0_instmem_resume; + engine->instmem.get = nv50_instmem_get; + engine->instmem.put = nv50_instmem_put; + engine->instmem.map = nv50_instmem_map; + engine->instmem.unmap = nv50_instmem_unmap; + engine->instmem.flush = nv84_instmem_flush; + engine->mc.init = nv50_mc_init; + engine->mc.takedown = nv50_mc_takedown; + engine->timer.init = nv04_timer_init; + engine->timer.read = nv04_timer_read; + engine->timer.takedown = nv04_timer_takedown; + engine->fb.init = nvc0_fb_init; + engine->fb.takedown = nvc0_fb_takedown; + engine->fifo.channels = 0; + engine->fifo.init = nouveau_stub_init; + engine->fifo.takedown = nouveau_stub_takedown; + engine->fifo.disable = nvc0_fifo_disable; + engine->fifo.enable = nvc0_fifo_enable; + engine->fifo.reassign = nvc0_fifo_reassign; + engine->fifo.unload_context = nouveau_stub_init; + engine->display.early_init = nouveau_stub_init; + engine->display.late_takedown = nouveau_stub_takedown; + engine->display.create = nvd0_display_create; + engine->display.destroy = nvd0_display_destroy; + engine->display.init = nvd0_display_init; + engine->display.fini = nvd0_display_fini; + engine->gpio.init = nv50_gpio_init; + engine->gpio.fini = nv50_gpio_fini; + engine->gpio.drive = nvd0_gpio_drive; + engine->gpio.sense = nvd0_gpio_sense; + engine->gpio.irq_enable = nv50_gpio_irq_enable; + engine->vram.init = nvc0_vram_init; + engine->vram.takedown = nv50_vram_fini; + engine->vram.get = nvc0_vram_new; + engine->vram.put = nv50_vram_del; + engine->vram.flags_valid = nvc0_vram_flags_valid; + break; + default: + NV_ERROR(dev, "NV%02x unsupported\n", dev_priv->chipset); + return 1; + } + + /* headless mode */ + if (nouveau_modeset == 2) { + engine->display.early_init = nouveau_stub_init; + engine->display.late_takedown = nouveau_stub_takedown; + engine->display.create = nouveau_stub_init; + engine->display.init = nouveau_stub_init; + engine->display.destroy = nouveau_stub_takedown; + } + + return 0; +} + +static unsigned int +nouveau_vga_set_decode(void *priv, bool state) +{ + struct drm_device *dev = priv; + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset >= 0x40) + nv_wr32(dev, 0x88054, state); + else + nv_wr32(dev, 0x1854, state); + + if (state) + return VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM | + VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; + else + return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; +} + +static void nouveau_switcheroo_set_state(struct pci_dev *pdev, + enum vga_switcheroo_state state) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + pm_message_t pmm = { .event = PM_EVENT_SUSPEND }; + if (state == VGA_SWITCHEROO_ON) { + printk(KERN_ERR "VGA switcheroo: switched nouveau on\n"); + dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; + nouveau_pci_resume(pdev); + drm_kms_helper_poll_enable(dev); + dev->switch_power_state = DRM_SWITCH_POWER_ON; + } else { + printk(KERN_ERR "VGA switcheroo: switched nouveau off\n"); + dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; + drm_kms_helper_poll_disable(dev); + nouveau_switcheroo_optimus_dsm(); + nouveau_pci_suspend(pdev, pmm); + dev->switch_power_state = DRM_SWITCH_POWER_OFF; + } +} + +static void nouveau_switcheroo_reprobe(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + nouveau_fbcon_output_poll_changed(dev); +} + +static bool nouveau_switcheroo_can_switch(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + bool can_switch; + + spin_lock(&dev->count_lock); + can_switch = (dev->open_count == 0); + spin_unlock(&dev->count_lock); + return can_switch; +} + +static void +nouveau_card_channel_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->channel) + nouveau_channel_put_unlocked(&dev_priv->channel); +} + +static int +nouveau_card_channel_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan; + int ret, oclass; + + ret = nouveau_channel_alloc(dev, &chan, NULL, NvDmaFB, NvDmaTT); + dev_priv->channel = chan; + if (ret) + return ret; + + mutex_unlock(&dev_priv->channel->mutex); + + if (dev_priv->card_type <= NV_50) { + if (dev_priv->card_type < NV_50) + oclass = 0x0039; + else + oclass = 0x5039; + + ret = nouveau_gpuobj_gr_new(chan, NvM2MF, oclass); + if (ret) + goto error; + + ret = nouveau_notifier_alloc(chan, NvNotify0, 32, 0xfe0, 0x1000, + &chan->m2mf_ntfy); + if (ret) + goto error; + + ret = RING_SPACE(chan, 6); + if (ret) + goto error; + + BEGIN_RING(chan, NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_NAME, 1); + OUT_RING (chan, NvM2MF); + BEGIN_RING(chan, NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_DMA_NOTIFY, 3); + OUT_RING (chan, NvNotify0); + OUT_RING (chan, chan->vram_handle); + OUT_RING (chan, chan->gart_handle); + } else + if (dev_priv->card_type <= NV_D0) { + ret = nouveau_gpuobj_gr_new(chan, 0x9039, 0x9039); + if (ret) + goto error; + + ret = RING_SPACE(chan, 2); + if (ret) + goto error; + + BEGIN_NVC0(chan, 2, NvSubM2MF, 0x0000, 1); + OUT_RING (chan, 0x00009039); + } + + FIRE_RING (chan); +error: + if (ret) + nouveau_card_channel_fini(dev); + return ret; +} + +int +nouveau_card_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_engine *engine; + int ret, e = 0; + + vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); + vga_switcheroo_register_client(dev->pdev, nouveau_switcheroo_set_state, + nouveau_switcheroo_reprobe, + nouveau_switcheroo_can_switch); + + /* Initialise internal driver API hooks */ + ret = nouveau_init_engine_ptrs(dev); + if (ret) + goto out; + engine = &dev_priv->engine; + spin_lock_init(&dev_priv->channels.lock); + spin_lock_init(&dev_priv->tile.lock); + spin_lock_init(&dev_priv->context_switch_lock); + spin_lock_init(&dev_priv->vm_lock); + + /* Make the CRTCs and I2C buses accessible */ + ret = engine->display.early_init(dev); + if (ret) + goto out; + + /* Parse BIOS tables / Run init tables if card not POSTed */ + ret = nouveau_bios_init(dev); + if (ret) + goto out_display_early; + + /* workaround an odd issue on nvc1 by disabling the device's + * nosnoop capability. hopefully won't cause issues until a + * better fix is found - assuming there is one... + */ + if (dev_priv->chipset == 0xc1) { + nv_mask(dev, 0x00088080, 0x00000800, 0x00000000); + } + + /* PMC */ + ret = engine->mc.init(dev); + if (ret) + goto out_bios; + + /* PTIMER */ + ret = engine->timer.init(dev); + if (ret) + goto out_mc; + + /* PFB */ + ret = engine->fb.init(dev); + if (ret) + goto out_timer; + + ret = engine->vram.init(dev); + if (ret) + goto out_fb; + + /* PGPIO */ + ret = nouveau_gpio_create(dev); + if (ret) + goto out_vram; + + ret = nouveau_gpuobj_init(dev); + if (ret) + goto out_gpio; + + ret = engine->instmem.init(dev); + if (ret) + goto out_gpuobj; + + ret = nouveau_mem_vram_init(dev); + if (ret) + goto out_instmem; + + ret = nouveau_mem_gart_init(dev); + if (ret) + goto out_ttmvram; + + if (!dev_priv->noaccel) { + switch (dev_priv->card_type) { + case NV_04: + nv04_graph_create(dev); + break; + case NV_10: + nv10_graph_create(dev); + break; + case NV_20: + case NV_30: + nv20_graph_create(dev); + break; + case NV_40: + nv40_graph_create(dev); + break; + case NV_50: + nv50_graph_create(dev); + break; + case NV_C0: + case NV_D0: + nvc0_graph_create(dev); + break; + default: + break; + } + + switch (dev_priv->chipset) { + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0xa0: + nv84_crypt_create(dev); + break; + case 0x98: + case 0xaa: + case 0xac: + nv98_crypt_create(dev); + break; + } + + switch (dev_priv->card_type) { + case NV_50: + switch (dev_priv->chipset) { + case 0xa3: + case 0xa5: + case 0xa8: + case 0xaf: + nva3_copy_create(dev); + break; + } + break; + case NV_C0: + nvc0_copy_create(dev, 0); + nvc0_copy_create(dev, 1); + break; + default: + break; + } + + if (dev_priv->chipset >= 0xa3 || dev_priv->chipset == 0x98) { + nv84_bsp_create(dev); + nv84_vp_create(dev); + nv98_ppp_create(dev); + } else + if (dev_priv->chipset >= 0x84) { + nv50_mpeg_create(dev); + nv84_bsp_create(dev); + nv84_vp_create(dev); + } else + if (dev_priv->chipset >= 0x50) { + nv50_mpeg_create(dev); + } else + if (dev_priv->card_type == NV_40 || + dev_priv->chipset == 0x31 || + dev_priv->chipset == 0x34 || + dev_priv->chipset == 0x36) { + nv31_mpeg_create(dev); + } + + for (e = 0; e < NVOBJ_ENGINE_NR; e++) { + if (dev_priv->eng[e]) { + ret = dev_priv->eng[e]->init(dev, e); + if (ret) + goto out_engine; + } + } + + /* PFIFO */ + ret = engine->fifo.init(dev); + if (ret) + goto out_engine; + } + + ret = nouveau_irq_init(dev); + if (ret) + goto out_fifo; + + ret = nouveau_display_create(dev); + if (ret) + goto out_irq; + + nouveau_backlight_init(dev); + nouveau_pm_init(dev); + + ret = nouveau_fence_init(dev); + if (ret) + goto out_pm; + + if (dev_priv->eng[NVOBJ_ENGINE_GR]) { + ret = nouveau_card_channel_init(dev); + if (ret) + goto out_fence; + } + + if (dev->mode_config.num_crtc) { + ret = nouveau_display_init(dev); + if (ret) + goto out_chan; + + nouveau_fbcon_init(dev); + } + + return 0; + +out_chan: + nouveau_card_channel_fini(dev); +out_fence: + nouveau_fence_fini(dev); +out_pm: + nouveau_pm_fini(dev); + nouveau_backlight_exit(dev); + nouveau_display_destroy(dev); +out_irq: + nouveau_irq_fini(dev); +out_fifo: + if (!dev_priv->noaccel) + engine->fifo.takedown(dev); +out_engine: + if (!dev_priv->noaccel) { + for (e = e - 1; e >= 0; e--) { + if (!dev_priv->eng[e]) + continue; + dev_priv->eng[e]->fini(dev, e, false); + dev_priv->eng[e]->destroy(dev,e ); + } + } + nouveau_mem_gart_fini(dev); +out_ttmvram: + nouveau_mem_vram_fini(dev); +out_instmem: + engine->instmem.takedown(dev); +out_gpuobj: + nouveau_gpuobj_takedown(dev); +out_gpio: + nouveau_gpio_destroy(dev); +out_vram: + engine->vram.takedown(dev); +out_fb: + engine->fb.takedown(dev); +out_timer: + engine->timer.takedown(dev); +out_mc: + engine->mc.takedown(dev); +out_bios: + nouveau_bios_takedown(dev); +out_display_early: + engine->display.late_takedown(dev); +out: + vga_client_register(dev->pdev, NULL, NULL, NULL); + return ret; +} + +static void nouveau_card_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_engine *engine = &dev_priv->engine; + int e; + + if (dev->mode_config.num_crtc) { + nouveau_fbcon_fini(dev); + nouveau_display_fini(dev); + } + + nouveau_card_channel_fini(dev); + nouveau_fence_fini(dev); + nouveau_pm_fini(dev); + nouveau_backlight_exit(dev); + nouveau_display_destroy(dev); + + if (!dev_priv->noaccel) { + engine->fifo.takedown(dev); + for (e = NVOBJ_ENGINE_NR - 1; e >= 0; e--) { + if (dev_priv->eng[e]) { + dev_priv->eng[e]->fini(dev, e, false); + dev_priv->eng[e]->destroy(dev,e ); + } + } + } + + if (dev_priv->vga_ram) { + nouveau_bo_unpin(dev_priv->vga_ram); + nouveau_bo_ref(NULL, &dev_priv->vga_ram); + } + + mutex_lock(&dev->struct_mutex); + ttm_bo_clean_mm(&dev_priv->ttm.bdev, TTM_PL_VRAM); + ttm_bo_clean_mm(&dev_priv->ttm.bdev, TTM_PL_TT); + mutex_unlock(&dev->struct_mutex); + nouveau_mem_gart_fini(dev); + nouveau_mem_vram_fini(dev); + + engine->instmem.takedown(dev); + nouveau_gpuobj_takedown(dev); + + nouveau_gpio_destroy(dev); + engine->vram.takedown(dev); + engine->fb.takedown(dev); + engine->timer.takedown(dev); + engine->mc.takedown(dev); + + nouveau_bios_takedown(dev); + engine->display.late_takedown(dev); + + nouveau_irq_fini(dev); + + vga_client_register(dev->pdev, NULL, NULL, NULL); +} + +int +nouveau_open(struct drm_device *dev, struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fpriv *fpriv; + int ret; + + fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL); + if (unlikely(!fpriv)) + return -ENOMEM; + + spin_lock_init(&fpriv->lock); + INIT_LIST_HEAD(&fpriv->channels); + + if (dev_priv->card_type == NV_50) { + ret = nouveau_vm_new(dev, 0, (1ULL << 40), 0x0020000000ULL, + &fpriv->vm); + if (ret) { + kfree(fpriv); + return ret; + } + } else + if (dev_priv->card_type >= NV_C0) { + ret = nouveau_vm_new(dev, 0, (1ULL << 40), 0x0008000000ULL, + &fpriv->vm); + if (ret) { + kfree(fpriv); + return ret; + } + } + + file_priv->driver_priv = fpriv; + return 0; +} + +/* here a client dies, release the stuff that was allocated for its + * file_priv */ +void nouveau_preclose(struct drm_device *dev, struct drm_file *file_priv) +{ + nouveau_channel_cleanup(dev, file_priv); +} + +void +nouveau_postclose(struct drm_device *dev, struct drm_file *file_priv) +{ + struct nouveau_fpriv *fpriv = nouveau_fpriv(file_priv); + nouveau_vm_ref(NULL, &fpriv->vm, NULL); + kfree(fpriv); +} + +/* first module load, setup the mmio/fb mapping */ +/* KMS: we need mmio at load time, not when the first drm client opens. */ +int nouveau_firstopen(struct drm_device *dev) +{ + return 0; +} + +/* if we have an OF card, copy vbios to RAMIN */ +static void nouveau_OF_copy_vbios_to_ramin(struct drm_device *dev) +{ +#if defined(__powerpc__) + int size, i; + const uint32_t *bios; + struct device_node *dn = pci_device_to_OF_node(dev->pdev); + if (!dn) { + NV_INFO(dev, "Unable to get the OF node\n"); + return; + } + + bios = of_get_property(dn, "NVDA,BMP", &size); + if (bios) { + for (i = 0; i < size; i += 4) + nv_wi32(dev, i, bios[i/4]); + NV_INFO(dev, "OF bios successfully copied (%d bytes)\n", size); + } else { + NV_INFO(dev, "Unable to get the OF bios\n"); + } +#endif +} + +static struct apertures_struct *nouveau_get_apertures(struct drm_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + struct apertures_struct *aper = alloc_apertures(3); + if (!aper) + return NULL; + + aper->ranges[0].base = pci_resource_start(pdev, 1); + aper->ranges[0].size = pci_resource_len(pdev, 1); + aper->count = 1; + + if (pci_resource_len(pdev, 2)) { + aper->ranges[aper->count].base = pci_resource_start(pdev, 2); + aper->ranges[aper->count].size = pci_resource_len(pdev, 2); + aper->count++; + } + + if (pci_resource_len(pdev, 3)) { + aper->ranges[aper->count].base = pci_resource_start(pdev, 3); + aper->ranges[aper->count].size = pci_resource_len(pdev, 3); + aper->count++; + } + + return aper; +} + +static int nouveau_remove_conflicting_drivers(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + bool primary = false; + dev_priv->apertures = nouveau_get_apertures(dev); + if (!dev_priv->apertures) + return -ENOMEM; + +#ifdef CONFIG_X86 + primary = dev->pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; +#endif + + remove_conflicting_framebuffers(dev_priv->apertures, "nouveaufb", primary); + return 0; +} + +int nouveau_load(struct drm_device *dev, unsigned long flags) +{ + struct drm_nouveau_private *dev_priv; + unsigned long long offset, length; + uint32_t reg0 = ~0, strap; + int ret; + + dev_priv = kzalloc(sizeof(*dev_priv), GFP_KERNEL); + if (!dev_priv) { + ret = -ENOMEM; + goto err_out; + } + dev->dev_private = dev_priv; + dev_priv->dev = dev; + + pci_set_master(dev->pdev); + + dev_priv->flags = flags & NOUVEAU_FLAGS; + + NV_DEBUG(dev, "vendor: 0x%X device: 0x%X class: 0x%X\n", + dev->pci_vendor, dev->pci_device, dev->pdev->class); + + /* first up, map the start of mmio and determine the chipset */ + dev_priv->mmio = ioremap(pci_resource_start(dev->pdev, 0), PAGE_SIZE); + if (dev_priv->mmio) { +#ifdef __BIG_ENDIAN + /* put the card into big-endian mode if it's not */ + if (nv_rd32(dev, NV03_PMC_BOOT_1) != 0x01000001) + nv_wr32(dev, NV03_PMC_BOOT_1, 0x01000001); + DRM_MEMORYBARRIER(); +#endif + + /* determine chipset and derive architecture from it */ + reg0 = nv_rd32(dev, NV03_PMC_BOOT_0); + if ((reg0 & 0x0f000000) > 0) { + dev_priv->chipset = (reg0 & 0xff00000) >> 20; + switch (dev_priv->chipset & 0xf0) { + case 0x10: + case 0x20: + case 0x30: + dev_priv->card_type = dev_priv->chipset & 0xf0; + break; + case 0x40: + case 0x60: + dev_priv->card_type = NV_40; + break; + case 0x50: + case 0x80: + case 0x90: + case 0xa0: + dev_priv->card_type = NV_50; + break; + case 0xc0: + dev_priv->card_type = NV_C0; + break; + case 0xd0: + dev_priv->card_type = NV_D0; + break; + case 0xe0: + dev_priv->card_type = NV_E0; + break; + default: + break; + } + } else + if ((reg0 & 0xff00fff0) == 0x20004000) { + if (reg0 & 0x00f00000) + dev_priv->chipset = 0x05; + else + dev_priv->chipset = 0x04; + dev_priv->card_type = NV_04; + } + + iounmap(dev_priv->mmio); + } + + if (!dev_priv->card_type) { + NV_ERROR(dev, "unsupported chipset 0x%08x\n", reg0); + ret = -EINVAL; + goto err_priv; + } + + NV_INFO(dev, "Detected an NV%2x generation card (0x%08x)\n", + dev_priv->card_type, reg0); + + /* map the mmio regs, limiting the amount to preserve vmap space */ + offset = pci_resource_start(dev->pdev, 0); + length = pci_resource_len(dev->pdev, 0); + if (dev_priv->card_type < NV_E0) + length = min(length, (unsigned long long)0x00800000); + + dev_priv->mmio = ioremap(offset, length); + if (!dev_priv->mmio) { + NV_ERROR(dev, "Unable to initialize the mmio mapping. " + "Please report your setup to " DRIVER_EMAIL "\n"); + ret = -EINVAL; + goto err_priv; + } + NV_DEBUG(dev, "regs mapped ok at 0x%llx\n", offset); + + /* determine frequency of timing crystal */ + strap = nv_rd32(dev, 0x101000); + if ( dev_priv->chipset < 0x17 || + (dev_priv->chipset >= 0x20 && dev_priv->chipset <= 0x25)) + strap &= 0x00000040; + else + strap &= 0x00400040; + + switch (strap) { + case 0x00000000: dev_priv->crystal = 13500; break; + case 0x00000040: dev_priv->crystal = 14318; break; + case 0x00400000: dev_priv->crystal = 27000; break; + case 0x00400040: dev_priv->crystal = 25000; break; + } + + NV_DEBUG(dev, "crystal freq: %dKHz\n", dev_priv->crystal); + + /* Determine whether we'll attempt acceleration or not, some + * cards are disabled by default here due to them being known + * non-functional, or never been tested due to lack of hw. + */ + dev_priv->noaccel = !!nouveau_noaccel; + if (nouveau_noaccel == -1) { + switch (dev_priv->chipset) { + case 0xd9: /* known broken */ + NV_INFO(dev, "acceleration disabled by default, pass " + "noaccel=0 to force enable\n"); + dev_priv->noaccel = true; + break; + default: + dev_priv->noaccel = false; + break; + } + } + + ret = nouveau_remove_conflicting_drivers(dev); + if (ret) + goto err_mmio; + + /* Map PRAMIN BAR, or on older cards, the aperture within BAR0 */ + if (dev_priv->card_type >= NV_40) { + int ramin_bar = 2; + if (pci_resource_len(dev->pdev, ramin_bar) == 0) + ramin_bar = 3; + + dev_priv->ramin_size = pci_resource_len(dev->pdev, ramin_bar); + dev_priv->ramin = + ioremap(pci_resource_start(dev->pdev, ramin_bar), + dev_priv->ramin_size); + if (!dev_priv->ramin) { + NV_ERROR(dev, "Failed to map PRAMIN BAR\n"); + ret = -ENOMEM; + goto err_mmio; + } + } else { + dev_priv->ramin_size = 1 * 1024 * 1024; + dev_priv->ramin = ioremap(offset + NV_RAMIN, + dev_priv->ramin_size); + if (!dev_priv->ramin) { + NV_ERROR(dev, "Failed to map BAR0 PRAMIN.\n"); + ret = -ENOMEM; + goto err_mmio; + } + } + + nouveau_OF_copy_vbios_to_ramin(dev); + + /* Special flags */ + if (dev->pci_device == 0x01a0) + dev_priv->flags |= NV_NFORCE; + else if (dev->pci_device == 0x01f0) + dev_priv->flags |= NV_NFORCE2; + + /* For kernel modesetting, init card now and bring up fbcon */ + ret = nouveau_card_init(dev); + if (ret) + goto err_ramin; + + return 0; + +err_ramin: + iounmap(dev_priv->ramin); +err_mmio: + iounmap(dev_priv->mmio); +err_priv: + kfree(dev_priv); + dev->dev_private = NULL; +err_out: + return ret; +} + +void nouveau_lastclose(struct drm_device *dev) +{ + vga_switcheroo_process_delayed_switch(); +} + +int nouveau_unload(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nouveau_card_takedown(dev); + + iounmap(dev_priv->mmio); + iounmap(dev_priv->ramin); + + kfree(dev_priv); + dev->dev_private = NULL; + return 0; +} + +int nouveau_ioctl_getparam(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_nouveau_getparam *getparam = data; + + switch (getparam->param) { + case NOUVEAU_GETPARAM_CHIPSET_ID: + getparam->value = dev_priv->chipset; + break; + case NOUVEAU_GETPARAM_PCI_VENDOR: + getparam->value = dev->pci_vendor; + break; + case NOUVEAU_GETPARAM_PCI_DEVICE: + getparam->value = dev->pci_device; + break; + case NOUVEAU_GETPARAM_BUS_TYPE: + if (drm_pci_device_is_agp(dev)) + getparam->value = NV_AGP; + else if (pci_is_pcie(dev->pdev)) + getparam->value = NV_PCIE; + else + getparam->value = NV_PCI; + break; + case NOUVEAU_GETPARAM_FB_SIZE: + getparam->value = dev_priv->fb_available_size; + break; + case NOUVEAU_GETPARAM_AGP_SIZE: + getparam->value = dev_priv->gart_info.aper_size; + break; + case NOUVEAU_GETPARAM_VM_VRAM_BASE: + getparam->value = 0; /* deprecated */ + break; + case NOUVEAU_GETPARAM_PTIMER_TIME: + getparam->value = dev_priv->engine.timer.read(dev); + break; + case NOUVEAU_GETPARAM_HAS_BO_USAGE: + getparam->value = 1; + break; + case NOUVEAU_GETPARAM_HAS_PAGEFLIP: + getparam->value = 1; + break; + case NOUVEAU_GETPARAM_GRAPH_UNITS: + /* NV40 and NV50 versions are quite different, but register + * address is the same. User is supposed to know the card + * family anyway... */ + if (dev_priv->chipset >= 0x40) { + getparam->value = nv_rd32(dev, NV40_PMC_GRAPH_UNITS); + break; + } + /* FALLTHRU */ + default: + NV_DEBUG(dev, "unknown parameter %lld\n", getparam->param); + return -EINVAL; + } + + return 0; +} + +int +nouveau_ioctl_setparam(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_nouveau_setparam *setparam = data; + + switch (setparam->param) { + default: + NV_DEBUG(dev, "unknown parameter %lld\n", setparam->param); + return -EINVAL; + } + + return 0; +} + +/* Wait until (value(reg) & mask) == val, up until timeout has hit */ +bool +nouveau_wait_eq(struct drm_device *dev, uint64_t timeout, + uint32_t reg, uint32_t mask, uint32_t val) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + uint64_t start = ptimer->read(dev); + + do { + if ((nv_rd32(dev, reg) & mask) == val) + return true; + } while (ptimer->read(dev) - start < timeout); + + return false; +} + +/* Wait until (value(reg) & mask) != val, up until timeout has hit */ +bool +nouveau_wait_ne(struct drm_device *dev, uint64_t timeout, + uint32_t reg, uint32_t mask, uint32_t val) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + uint64_t start = ptimer->read(dev); + + do { + if ((nv_rd32(dev, reg) & mask) != val) + return true; + } while (ptimer->read(dev) - start < timeout); + + return false; +} + +/* Wait until cond(data) == true, up until timeout has hit */ +bool +nouveau_wait_cb(struct drm_device *dev, u64 timeout, + bool (*cond)(void *), void *data) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + u64 start = ptimer->read(dev); + + do { + if (cond(data) == true) + return true; + } while (ptimer->read(dev) - start < timeout); + + return false; +} + +/* Waits for PGRAPH to go completely idle */ +bool nouveau_wait_for_idle(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t mask = ~0; + + if (dev_priv->card_type == NV_40) + mask &= ~NV40_PGRAPH_STATUS_SYNC_STALL; + + if (!nv_wait(dev, NV04_PGRAPH_STATUS, mask, 0)) { + NV_ERROR(dev, "PGRAPH idle timed out with status 0x%08x\n", + nv_rd32(dev, NV04_PGRAPH_STATUS)); + return false; + } + + return true; +} + diff --git a/drivers/gpu/drm/nouveau/nouveau_temp.c b/drivers/gpu/drm/nouveau/nouveau_temp.c new file mode 100644 index 00000000..0f5a3016 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_temp.c @@ -0,0 +1,331 @@ +/* + * Copyright 2010 PathScale inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ + +#include <linux/module.h> + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" + +static void +nouveau_temp_vbios_parse(struct drm_device *dev, u8 *temp) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_temp_sensor_constants *sensor = &pm->sensor_constants; + struct nouveau_pm_threshold_temp *temps = &pm->threshold_temp; + int i, headerlen, recordlen, entries; + + if (!temp) { + NV_DEBUG(dev, "temperature table pointer invalid\n"); + return; + } + + /* Set the default sensor's contants */ + sensor->offset_constant = 0; + sensor->offset_mult = 0; + sensor->offset_div = 1; + sensor->slope_mult = 1; + sensor->slope_div = 1; + + /* Set the default temperature thresholds */ + temps->critical = 110; + temps->down_clock = 100; + temps->fan_boost = 90; + + /* Set the default range for the pwm fan */ + pm->fan.min_duty = 30; + pm->fan.max_duty = 100; + + /* Set the known default values to setup the temperature sensor */ + if (dev_priv->card_type >= NV_40) { + switch (dev_priv->chipset) { + case 0x43: + sensor->offset_mult = 32060; + sensor->offset_div = 1000; + sensor->slope_mult = 792; + sensor->slope_div = 1000; + break; + + case 0x44: + case 0x47: + case 0x4a: + sensor->offset_mult = 27839; + sensor->offset_div = 1000; + sensor->slope_mult = 780; + sensor->slope_div = 1000; + break; + + case 0x46: + sensor->offset_mult = -24775; + sensor->offset_div = 100; + sensor->slope_mult = 467; + sensor->slope_div = 10000; + break; + + case 0x49: + sensor->offset_mult = -25051; + sensor->offset_div = 100; + sensor->slope_mult = 458; + sensor->slope_div = 10000; + break; + + case 0x4b: + sensor->offset_mult = -24088; + sensor->offset_div = 100; + sensor->slope_mult = 442; + sensor->slope_div = 10000; + break; + + case 0x50: + sensor->offset_mult = -22749; + sensor->offset_div = 100; + sensor->slope_mult = 431; + sensor->slope_div = 10000; + break; + + case 0x67: + sensor->offset_mult = -26149; + sensor->offset_div = 100; + sensor->slope_mult = 484; + sensor->slope_div = 10000; + break; + } + } + + headerlen = temp[1]; + recordlen = temp[2]; + entries = temp[3]; + temp = temp + headerlen; + + /* Read the entries from the table */ + for (i = 0; i < entries; i++) { + s16 value = ROM16(temp[1]); + + switch (temp[0]) { + case 0x01: + if ((value & 0x8f) == 0) + sensor->offset_constant = (value >> 9) & 0x7f; + break; + + case 0x04: + if ((value & 0xf00f) == 0xa000) /* core */ + temps->critical = (value&0x0ff0) >> 4; + break; + + case 0x07: + if ((value & 0xf00f) == 0xa000) /* core */ + temps->down_clock = (value&0x0ff0) >> 4; + break; + + case 0x08: + if ((value & 0xf00f) == 0xa000) /* core */ + temps->fan_boost = (value&0x0ff0) >> 4; + break; + + case 0x10: + sensor->offset_mult = value; + break; + + case 0x11: + sensor->offset_div = value; + break; + + case 0x12: + sensor->slope_mult = value; + break; + + case 0x13: + sensor->slope_div = value; + break; + case 0x22: + pm->fan.min_duty = value & 0xff; + pm->fan.max_duty = (value & 0xff00) >> 8; + break; + case 0x26: + pm->fan.pwm_freq = value; + break; + } + temp += recordlen; + } + + nouveau_temp_safety_checks(dev); + + /* check the fan min/max settings */ + if (pm->fan.min_duty < 10) + pm->fan.min_duty = 10; + if (pm->fan.max_duty > 100) + pm->fan.max_duty = 100; + if (pm->fan.max_duty < pm->fan.min_duty) + pm->fan.max_duty = pm->fan.min_duty; +} + +static int +nv40_sensor_setup(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_temp_sensor_constants *sensor = &pm->sensor_constants; + s32 offset = sensor->offset_mult / sensor->offset_div; + s32 sensor_calibration; + + /* set up the sensors */ + sensor_calibration = 120 - offset - sensor->offset_constant; + sensor_calibration = sensor_calibration * sensor->slope_div / + sensor->slope_mult; + + if (dev_priv->chipset >= 0x46) + sensor_calibration |= 0x80000000; + else + sensor_calibration |= 0x10000000; + + nv_wr32(dev, 0x0015b0, sensor_calibration); + + /* Wait for the sensor to update */ + msleep(5); + + /* read */ + return nv_rd32(dev, 0x0015b4) & 0x1fff; +} + +int +nv40_temp_get(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_temp_sensor_constants *sensor = &pm->sensor_constants; + int offset = sensor->offset_mult / sensor->offset_div; + int core_temp; + + if (dev_priv->card_type >= NV_50) { + core_temp = nv_rd32(dev, 0x20008); + } else { + core_temp = nv_rd32(dev, 0x0015b4) & 0x1fff; + /* Setup the sensor if the temperature is 0 */ + if (core_temp == 0) + core_temp = nv40_sensor_setup(dev); + } + + core_temp = core_temp * sensor->slope_mult / sensor->slope_div; + core_temp = core_temp + offset + sensor->offset_constant; + + return core_temp; +} + +int +nv84_temp_get(struct drm_device *dev) +{ + return nv_rd32(dev, 0x20400); +} + +void +nouveau_temp_safety_checks(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_threshold_temp *temps = &pm->threshold_temp; + + if (temps->critical > 120) + temps->critical = 120; + else if (temps->critical < 80) + temps->critical = 80; + + if (temps->down_clock > 110) + temps->down_clock = 110; + else if (temps->down_clock < 60) + temps->down_clock = 60; + + if (temps->fan_boost > 100) + temps->fan_boost = 100; + else if (temps->fan_boost < 40) + temps->fan_boost = 40; +} + +static bool +probe_monitoring_device(struct nouveau_i2c_chan *i2c, + struct i2c_board_info *info) +{ + struct i2c_client *client; + + request_module("%s%s", I2C_MODULE_PREFIX, info->type); + + client = i2c_new_device(&i2c->adapter, info); + if (!client) + return false; + + if (!client->driver || client->driver->detect(client, info)) { + i2c_unregister_device(client); + return false; + } + + return true; +} + +static void +nouveau_temp_probe_i2c(struct drm_device *dev) +{ + struct i2c_board_info info[] = { + { I2C_BOARD_INFO("w83l785ts", 0x2d) }, + { I2C_BOARD_INFO("w83781d", 0x2d) }, + { I2C_BOARD_INFO("adt7473", 0x2e) }, + { I2C_BOARD_INFO("f75375", 0x2e) }, + { I2C_BOARD_INFO("lm99", 0x4c) }, + { } + }; + + nouveau_i2c_identify(dev, "monitoring device", info, + probe_monitoring_device, NV_I2C_DEFAULT(0)); +} + +void +nouveau_temp_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvbios *bios = &dev_priv->vbios; + struct bit_entry P; + u8 *temp = NULL; + + if (bios->type == NVBIOS_BIT) { + if (bit_table(dev, 'P', &P)) + return; + + if (P.version == 1) + temp = ROMPTR(dev, P.data[12]); + else if (P.version == 2) + temp = ROMPTR(dev, P.data[16]); + else + NV_WARN(dev, "unknown temp for BIT P %d\n", P.version); + + nouveau_temp_vbios_parse(dev, temp); + } + + nouveau_temp_probe_i2c(dev); +} + +void +nouveau_temp_fini(struct drm_device *dev) +{ + +} diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c new file mode 100644 index 00000000..bd35f930 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2007-2008 Tungsten Graphics, Inc., Cedar Park, TX., USA, + * All Rights Reserved. + * Copyright (c) 2009 VMware, Inc., Palo Alto, CA., USA, + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" + +#include "nouveau_drv.h" + +int +nouveau_ttm_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_nouveau_private *dev_priv = + file_priv->minor->dev->dev_private; + + if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET)) + return drm_mmap(filp, vma); + + return ttm_bo_mmap(filp, vma, &dev_priv->ttm.bdev); +} + +static int +nouveau_ttm_mem_global_init(struct drm_global_reference *ref) +{ + return ttm_mem_global_init(ref->object); +} + +static void +nouveau_ttm_mem_global_release(struct drm_global_reference *ref) +{ + ttm_mem_global_release(ref->object); +} + +int +nouveau_ttm_global_init(struct drm_nouveau_private *dev_priv) +{ + struct drm_global_reference *global_ref; + int ret; + + global_ref = &dev_priv->ttm.mem_global_ref; + global_ref->global_type = DRM_GLOBAL_TTM_MEM; + global_ref->size = sizeof(struct ttm_mem_global); + global_ref->init = &nouveau_ttm_mem_global_init; + global_ref->release = &nouveau_ttm_mem_global_release; + + ret = drm_global_item_ref(global_ref); + if (unlikely(ret != 0)) { + DRM_ERROR("Failed setting up TTM memory accounting\n"); + dev_priv->ttm.mem_global_ref.release = NULL; + return ret; + } + + dev_priv->ttm.bo_global_ref.mem_glob = global_ref->object; + global_ref = &dev_priv->ttm.bo_global_ref.ref; + global_ref->global_type = DRM_GLOBAL_TTM_BO; + global_ref->size = sizeof(struct ttm_bo_global); + global_ref->init = &ttm_bo_global_init; + global_ref->release = &ttm_bo_global_release; + + ret = drm_global_item_ref(global_ref); + if (unlikely(ret != 0)) { + DRM_ERROR("Failed setting up TTM BO subsystem\n"); + drm_global_item_unref(&dev_priv->ttm.mem_global_ref); + dev_priv->ttm.mem_global_ref.release = NULL; + return ret; + } + + return 0; +} + +void +nouveau_ttm_global_release(struct drm_nouveau_private *dev_priv) +{ + if (dev_priv->ttm.mem_global_ref.release == NULL) + return; + + drm_global_item_unref(&dev_priv->ttm.bo_global_ref.ref); + drm_global_item_unref(&dev_priv->ttm.mem_global_ref); + dev_priv->ttm.mem_global_ref.release = NULL; +} + diff --git a/drivers/gpu/drm/nouveau/nouveau_util.c b/drivers/gpu/drm/nouveau/nouveau_util.c new file mode 100644 index 00000000..e51b5150 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_util.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 Nouveau Project + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include <linux/ratelimit.h> + +#include "nouveau_util.h" + +static DEFINE_RATELIMIT_STATE(nouveau_ratelimit_state, 3 * HZ, 20); + +void +nouveau_bitfield_print(const struct nouveau_bitfield *bf, u32 value) +{ + while (bf->name) { + if (value & bf->mask) { + printk(" %s", bf->name); + value &= ~bf->mask; + } + + bf++; + } + + if (value) + printk(" (unknown bits 0x%08x)", value); +} + +const struct nouveau_enum * +nouveau_enum_find(const struct nouveau_enum *en, u32 value) +{ + while (en->name) { + if (en->value == value) + return en; + en++; + } + + return NULL; +} + +void +nouveau_enum_print(const struct nouveau_enum *en, u32 value) +{ + en = nouveau_enum_find(en, value); + if (en) { + printk("%s", en->name); + return; + } + + printk("(unknown enum 0x%08x)", value); +} + +int +nouveau_ratelimit(void) +{ + return __ratelimit(&nouveau_ratelimit_state); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_util.h b/drivers/gpu/drm/nouveau/nouveau_util.h new file mode 100644 index 00000000..b97719fb --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_util.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 Nouveau Project + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NOUVEAU_UTIL_H__ +#define __NOUVEAU_UTIL_H__ + +struct nouveau_bitfield { + u32 mask; + const char *name; +}; + +struct nouveau_enum { + u32 value; + const char *name; + void *data; +}; + +void nouveau_bitfield_print(const struct nouveau_bitfield *, u32 value); +void nouveau_enum_print(const struct nouveau_enum *, u32 value); +const struct nouveau_enum * +nouveau_enum_find(const struct nouveau_enum *, u32 value); + +int nouveau_ratelimit(void); + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_vm.c b/drivers/gpu/drm/nouveau/nouveau_vm.c new file mode 100644 index 00000000..2bf6c035 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_vm.c @@ -0,0 +1,436 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_mm.h" +#include "nouveau_vm.h" + +void +nouveau_vm_map_at(struct nouveau_vma *vma, u64 delta, struct nouveau_mem *node) +{ + struct nouveau_vm *vm = vma->vm; + struct nouveau_mm_node *r; + int big = vma->node->type != vm->spg_shift; + u32 offset = vma->node->offset + (delta >> 12); + u32 bits = vma->node->type - 12; + u32 pde = (offset >> vm->pgt_bits) - vm->fpde; + u32 pte = (offset & ((1 << vm->pgt_bits) - 1)) >> bits; + u32 max = 1 << (vm->pgt_bits - bits); + u32 end, len; + + delta = 0; + list_for_each_entry(r, &node->regions, rl_entry) { + u64 phys = (u64)r->offset << 12; + u32 num = r->length >> bits; + + while (num) { + struct nouveau_gpuobj *pgt = vm->pgt[pde].obj[big]; + + end = (pte + num); + if (unlikely(end >= max)) + end = max; + len = end - pte; + + vm->map(vma, pgt, node, pte, len, phys, delta); + + num -= len; + pte += len; + if (unlikely(end >= max)) { + phys += len << (bits + 12); + pde++; + pte = 0; + } + + delta += (u64)len << vma->node->type; + } + } + + vm->flush(vm); +} + +void +nouveau_vm_map(struct nouveau_vma *vma, struct nouveau_mem *node) +{ + nouveau_vm_map_at(vma, 0, node); +} + +void +nouveau_vm_map_sg(struct nouveau_vma *vma, u64 delta, u64 length, + struct nouveau_mem *mem) +{ + struct nouveau_vm *vm = vma->vm; + dma_addr_t *list = mem->pages; + int big = vma->node->type != vm->spg_shift; + u32 offset = vma->node->offset + (delta >> 12); + u32 bits = vma->node->type - 12; + u32 num = length >> vma->node->type; + u32 pde = (offset >> vm->pgt_bits) - vm->fpde; + u32 pte = (offset & ((1 << vm->pgt_bits) - 1)) >> bits; + u32 max = 1 << (vm->pgt_bits - bits); + u32 end, len; + + while (num) { + struct nouveau_gpuobj *pgt = vm->pgt[pde].obj[big]; + + end = (pte + num); + if (unlikely(end >= max)) + end = max; + len = end - pte; + + vm->map_sg(vma, pgt, mem, pte, len, list); + + num -= len; + pte += len; + list += len; + if (unlikely(end >= max)) { + pde++; + pte = 0; + } + } + + vm->flush(vm); +} + +void +nouveau_vm_unmap_at(struct nouveau_vma *vma, u64 delta, u64 length) +{ + struct nouveau_vm *vm = vma->vm; + int big = vma->node->type != vm->spg_shift; + u32 offset = vma->node->offset + (delta >> 12); + u32 bits = vma->node->type - 12; + u32 num = length >> vma->node->type; + u32 pde = (offset >> vm->pgt_bits) - vm->fpde; + u32 pte = (offset & ((1 << vm->pgt_bits) - 1)) >> bits; + u32 max = 1 << (vm->pgt_bits - bits); + u32 end, len; + + while (num) { + struct nouveau_gpuobj *pgt = vm->pgt[pde].obj[big]; + + end = (pte + num); + if (unlikely(end >= max)) + end = max; + len = end - pte; + + vm->unmap(pgt, pte, len); + + num -= len; + pte += len; + if (unlikely(end >= max)) { + pde++; + pte = 0; + } + } + + vm->flush(vm); +} + +void +nouveau_vm_unmap(struct nouveau_vma *vma) +{ + nouveau_vm_unmap_at(vma, 0, (u64)vma->node->length << 12); +} + +static void +nouveau_vm_unmap_pgt(struct nouveau_vm *vm, int big, u32 fpde, u32 lpde) +{ + struct nouveau_vm_pgd *vpgd; + struct nouveau_vm_pgt *vpgt; + struct nouveau_gpuobj *pgt; + u32 pde; + + for (pde = fpde; pde <= lpde; pde++) { + vpgt = &vm->pgt[pde - vm->fpde]; + if (--vpgt->refcount[big]) + continue; + + pgt = vpgt->obj[big]; + vpgt->obj[big] = NULL; + + list_for_each_entry(vpgd, &vm->pgd_list, head) { + vm->map_pgt(vpgd->obj, pde, vpgt->obj); + } + + mutex_unlock(&vm->mm.mutex); + nouveau_gpuobj_ref(NULL, &pgt); + mutex_lock(&vm->mm.mutex); + } +} + +static int +nouveau_vm_map_pgt(struct nouveau_vm *vm, u32 pde, u32 type) +{ + struct nouveau_vm_pgt *vpgt = &vm->pgt[pde - vm->fpde]; + struct nouveau_vm_pgd *vpgd; + struct nouveau_gpuobj *pgt; + int big = (type != vm->spg_shift); + u32 pgt_size; + int ret; + + pgt_size = (1 << (vm->pgt_bits + 12)) >> type; + pgt_size *= 8; + + mutex_unlock(&vm->mm.mutex); + ret = nouveau_gpuobj_new(vm->dev, NULL, pgt_size, 0x1000, + NVOBJ_FLAG_ZERO_ALLOC, &pgt); + mutex_lock(&vm->mm.mutex); + if (unlikely(ret)) + return ret; + + /* someone beat us to filling the PDE while we didn't have the lock */ + if (unlikely(vpgt->refcount[big]++)) { + mutex_unlock(&vm->mm.mutex); + nouveau_gpuobj_ref(NULL, &pgt); + mutex_lock(&vm->mm.mutex); + return 0; + } + + vpgt->obj[big] = pgt; + list_for_each_entry(vpgd, &vm->pgd_list, head) { + vm->map_pgt(vpgd->obj, pde, vpgt->obj); + } + + return 0; +} + +int +nouveau_vm_get(struct nouveau_vm *vm, u64 size, u32 page_shift, + u32 access, struct nouveau_vma *vma) +{ + u32 align = (1 << page_shift) >> 12; + u32 msize = size >> 12; + u32 fpde, lpde, pde; + int ret; + + mutex_lock(&vm->mm.mutex); + ret = nouveau_mm_get(&vm->mm, page_shift, msize, 0, align, &vma->node); + if (unlikely(ret != 0)) { + mutex_unlock(&vm->mm.mutex); + return ret; + } + + fpde = (vma->node->offset >> vm->pgt_bits); + lpde = (vma->node->offset + vma->node->length - 1) >> vm->pgt_bits; + for (pde = fpde; pde <= lpde; pde++) { + struct nouveau_vm_pgt *vpgt = &vm->pgt[pde - vm->fpde]; + int big = (vma->node->type != vm->spg_shift); + + if (likely(vpgt->refcount[big])) { + vpgt->refcount[big]++; + continue; + } + + ret = nouveau_vm_map_pgt(vm, pde, vma->node->type); + if (ret) { + if (pde != fpde) + nouveau_vm_unmap_pgt(vm, big, fpde, pde - 1); + nouveau_mm_put(&vm->mm, vma->node); + mutex_unlock(&vm->mm.mutex); + vma->node = NULL; + return ret; + } + } + mutex_unlock(&vm->mm.mutex); + + vma->vm = vm; + vma->offset = (u64)vma->node->offset << 12; + vma->access = access; + return 0; +} + +void +nouveau_vm_put(struct nouveau_vma *vma) +{ + struct nouveau_vm *vm = vma->vm; + u32 fpde, lpde; + + if (unlikely(vma->node == NULL)) + return; + fpde = (vma->node->offset >> vm->pgt_bits); + lpde = (vma->node->offset + vma->node->length - 1) >> vm->pgt_bits; + + mutex_lock(&vm->mm.mutex); + nouveau_vm_unmap_pgt(vm, vma->node->type != vm->spg_shift, fpde, lpde); + nouveau_mm_put(&vm->mm, vma->node); + vma->node = NULL; + mutex_unlock(&vm->mm.mutex); +} + +int +nouveau_vm_new(struct drm_device *dev, u64 offset, u64 length, u64 mm_offset, + struct nouveau_vm **pvm) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_vm *vm; + u64 mm_length = (offset + length) - mm_offset; + u32 block, pgt_bits; + int ret; + + vm = kzalloc(sizeof(*vm), GFP_KERNEL); + if (!vm) + return -ENOMEM; + + if (dev_priv->card_type == NV_50) { + vm->map_pgt = nv50_vm_map_pgt; + vm->map = nv50_vm_map; + vm->map_sg = nv50_vm_map_sg; + vm->unmap = nv50_vm_unmap; + vm->flush = nv50_vm_flush; + vm->spg_shift = 12; + vm->lpg_shift = 16; + + pgt_bits = 29; + block = (1 << pgt_bits); + if (length < block) + block = length; + + } else + if (dev_priv->card_type >= NV_C0) { + vm->map_pgt = nvc0_vm_map_pgt; + vm->map = nvc0_vm_map; + vm->map_sg = nvc0_vm_map_sg; + vm->unmap = nvc0_vm_unmap; + vm->flush = nvc0_vm_flush; + vm->spg_shift = 12; + vm->lpg_shift = 17; + pgt_bits = 27; + block = 4096; + } else { + kfree(vm); + return -ENOSYS; + } + + vm->fpde = offset >> pgt_bits; + vm->lpde = (offset + length - 1) >> pgt_bits; + vm->pgt = kcalloc(vm->lpde - vm->fpde + 1, sizeof(*vm->pgt), GFP_KERNEL); + if (!vm->pgt) { + kfree(vm); + return -ENOMEM; + } + + INIT_LIST_HEAD(&vm->pgd_list); + vm->dev = dev; + vm->refcount = 1; + vm->pgt_bits = pgt_bits - 12; + + ret = nouveau_mm_init(&vm->mm, mm_offset >> 12, mm_length >> 12, + block >> 12); + if (ret) { + kfree(vm); + return ret; + } + + *pvm = vm; + return 0; +} + +static int +nouveau_vm_link(struct nouveau_vm *vm, struct nouveau_gpuobj *pgd) +{ + struct nouveau_vm_pgd *vpgd; + int i; + + if (!pgd) + return 0; + + vpgd = kzalloc(sizeof(*vpgd), GFP_KERNEL); + if (!vpgd) + return -ENOMEM; + + nouveau_gpuobj_ref(pgd, &vpgd->obj); + + mutex_lock(&vm->mm.mutex); + for (i = vm->fpde; i <= vm->lpde; i++) + vm->map_pgt(pgd, i, vm->pgt[i - vm->fpde].obj); + list_add(&vpgd->head, &vm->pgd_list); + mutex_unlock(&vm->mm.mutex); + return 0; +} + +static void +nouveau_vm_unlink(struct nouveau_vm *vm, struct nouveau_gpuobj *mpgd) +{ + struct nouveau_vm_pgd *vpgd, *tmp; + struct nouveau_gpuobj *pgd = NULL; + + if (!mpgd) + return; + + mutex_lock(&vm->mm.mutex); + list_for_each_entry_safe(vpgd, tmp, &vm->pgd_list, head) { + if (vpgd->obj == mpgd) { + pgd = vpgd->obj; + list_del(&vpgd->head); + kfree(vpgd); + break; + } + } + mutex_unlock(&vm->mm.mutex); + + nouveau_gpuobj_ref(NULL, &pgd); +} + +static void +nouveau_vm_del(struct nouveau_vm *vm) +{ + struct nouveau_vm_pgd *vpgd, *tmp; + + list_for_each_entry_safe(vpgd, tmp, &vm->pgd_list, head) { + nouveau_vm_unlink(vm, vpgd->obj); + } + + nouveau_mm_fini(&vm->mm); + kfree(vm->pgt); + kfree(vm); +} + +int +nouveau_vm_ref(struct nouveau_vm *ref, struct nouveau_vm **ptr, + struct nouveau_gpuobj *pgd) +{ + struct nouveau_vm *vm; + int ret; + + vm = ref; + if (vm) { + ret = nouveau_vm_link(vm, pgd); + if (ret) + return ret; + + vm->refcount++; + } + + vm = *ptr; + *ptr = ref; + + if (vm) { + nouveau_vm_unlink(vm, pgd); + + if (--vm->refcount == 0) + nouveau_vm_del(vm); + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nouveau_vm.h b/drivers/gpu/drm/nouveau/nouveau_vm.h new file mode 100644 index 00000000..4fb6e728 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_vm.h @@ -0,0 +1,115 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#ifndef __NOUVEAU_VM_H__ +#define __NOUVEAU_VM_H__ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_mm.h" + +struct nouveau_vm_pgt { + struct nouveau_gpuobj *obj[2]; + u32 refcount[2]; +}; + +struct nouveau_vm_pgd { + struct list_head head; + struct nouveau_gpuobj *obj; +}; + +struct nouveau_vma { + struct list_head head; + int refcount; + struct nouveau_vm *vm; + struct nouveau_mm_node *node; + u64 offset; + u32 access; +}; + +struct nouveau_vm { + struct drm_device *dev; + struct nouveau_mm mm; + int refcount; + + struct list_head pgd_list; + atomic_t engref[16]; + + struct nouveau_vm_pgt *pgt; + u32 fpde; + u32 lpde; + + u32 pgt_bits; + u8 spg_shift; + u8 lpg_shift; + + void (*map_pgt)(struct nouveau_gpuobj *pgd, u32 pde, + struct nouveau_gpuobj *pgt[2]); + void (*map)(struct nouveau_vma *, struct nouveau_gpuobj *, + struct nouveau_mem *, u32 pte, u32 cnt, + u64 phys, u64 delta); + void (*map_sg)(struct nouveau_vma *, struct nouveau_gpuobj *, + struct nouveau_mem *, u32 pte, u32 cnt, dma_addr_t *); + void (*unmap)(struct nouveau_gpuobj *pgt, u32 pte, u32 cnt); + void (*flush)(struct nouveau_vm *); +}; + +/* nouveau_vm.c */ +int nouveau_vm_new(struct drm_device *, u64 offset, u64 length, u64 mm_offset, + struct nouveau_vm **); +int nouveau_vm_ref(struct nouveau_vm *, struct nouveau_vm **, + struct nouveau_gpuobj *pgd); +int nouveau_vm_get(struct nouveau_vm *, u64 size, u32 page_shift, + u32 access, struct nouveau_vma *); +void nouveau_vm_put(struct nouveau_vma *); +void nouveau_vm_map(struct nouveau_vma *, struct nouveau_mem *); +void nouveau_vm_map_at(struct nouveau_vma *, u64 offset, struct nouveau_mem *); +void nouveau_vm_unmap(struct nouveau_vma *); +void nouveau_vm_unmap_at(struct nouveau_vma *, u64 offset, u64 length); +void nouveau_vm_map_sg(struct nouveau_vma *, u64 offset, u64 length, + struct nouveau_mem *); + +/* nv50_vm.c */ +void nv50_vm_map_pgt(struct nouveau_gpuobj *pgd, u32 pde, + struct nouveau_gpuobj *pgt[2]); +void nv50_vm_map(struct nouveau_vma *, struct nouveau_gpuobj *, + struct nouveau_mem *, u32 pte, u32 cnt, u64 phys, u64 delta); +void nv50_vm_map_sg(struct nouveau_vma *, struct nouveau_gpuobj *, + struct nouveau_mem *, u32 pte, u32 cnt, dma_addr_t *); +void nv50_vm_unmap(struct nouveau_gpuobj *, u32 pte, u32 cnt); +void nv50_vm_flush(struct nouveau_vm *); +void nv50_vm_flush_engine(struct drm_device *, int engine); + +/* nvc0_vm.c */ +void nvc0_vm_map_pgt(struct nouveau_gpuobj *pgd, u32 pde, + struct nouveau_gpuobj *pgt[2]); +void nvc0_vm_map(struct nouveau_vma *, struct nouveau_gpuobj *, + struct nouveau_mem *, u32 pte, u32 cnt, u64 phys, u64 delta); +void nvc0_vm_map_sg(struct nouveau_vma *, struct nouveau_gpuobj *, + struct nouveau_mem *, u32 pte, u32 cnt, dma_addr_t *); +void nvc0_vm_unmap(struct nouveau_gpuobj *, u32 pte, u32 cnt); +void nvc0_vm_flush(struct nouveau_vm *); + +#endif diff --git a/drivers/gpu/drm/nouveau/nouveau_volt.c b/drivers/gpu/drm/nouveau/nouveau_volt.c new file mode 100644 index 00000000..b010cb99 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nouveau_volt.c @@ -0,0 +1,247 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_pm.h" +#include "nouveau_gpio.h" + +static const enum dcb_gpio_tag vidtag[] = { 0x04, 0x05, 0x06, 0x1a, 0x73 }; +static int nr_vidtag = sizeof(vidtag) / sizeof(vidtag[0]); + +int +nouveau_voltage_gpio_get(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + u8 vid = 0; + int i; + + for (i = 0; i < nr_vidtag; i++) { + if (!(volt->vid_mask & (1 << i))) + continue; + + vid |= nouveau_gpio_func_get(dev, vidtag[i]) << i; + } + + return nouveau_volt_lvl_lookup(dev, vid); +} + +int +nouveau_voltage_gpio_set(struct drm_device *dev, int voltage) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + int vid, i; + + vid = nouveau_volt_vid_lookup(dev, voltage); + if (vid < 0) + return vid; + + for (i = 0; i < nr_vidtag; i++) { + if (!(volt->vid_mask & (1 << i))) + continue; + + nouveau_gpio_func_set(dev, vidtag[i], !!(vid & (1 << i))); + } + + return 0; +} + +int +nouveau_volt_vid_lookup(struct drm_device *dev, int voltage) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + int i; + + for (i = 0; i < volt->nr_level; i++) { + if (volt->level[i].voltage == voltage) + return volt->level[i].vid; + } + + return -ENOENT; +} + +int +nouveau_volt_lvl_lookup(struct drm_device *dev, int vid) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + int i; + + for (i = 0; i < volt->nr_level; i++) { + if (volt->level[i].vid == vid) + return volt->level[i].voltage; + } + + return -ENOENT; +} + +void +nouveau_volt_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_engine *pm = &dev_priv->engine.pm; + struct nouveau_pm_voltage *voltage = &pm->voltage; + struct nvbios *bios = &dev_priv->vbios; + struct bit_entry P; + u8 *volt = NULL, *entry; + int i, headerlen, recordlen, entries, vidmask, vidshift; + + if (bios->type == NVBIOS_BIT) { + if (bit_table(dev, 'P', &P)) + return; + + if (P.version == 1) + volt = ROMPTR(dev, P.data[16]); + else + if (P.version == 2) + volt = ROMPTR(dev, P.data[12]); + else { + NV_WARN(dev, "unknown volt for BIT P %d\n", P.version); + } + } else { + if (bios->data[bios->offset + 6] < 0x27) { + NV_DEBUG(dev, "BMP version too old for voltage\n"); + return; + } + + volt = ROMPTR(dev, bios->data[bios->offset + 0x98]); + } + + if (!volt) { + NV_DEBUG(dev, "voltage table pointer invalid\n"); + return; + } + + switch (volt[0]) { + case 0x10: + case 0x11: + case 0x12: + headerlen = 5; + recordlen = volt[1]; + entries = volt[2]; + vidshift = 0; + vidmask = volt[4]; + break; + case 0x20: + headerlen = volt[1]; + recordlen = volt[3]; + entries = volt[2]; + vidshift = 0; /* could be vidshift like 0x30? */ + vidmask = volt[5]; + break; + case 0x30: + headerlen = volt[1]; + recordlen = volt[2]; + entries = volt[3]; + vidmask = volt[4]; + /* no longer certain what volt[5] is, if it's related to + * the vid shift then it's definitely not a function of + * how many bits are set. + * + * after looking at a number of nva3+ vbios images, they + * all seem likely to have a static shift of 2.. lets + * go with that for now until proven otherwise. + */ + vidshift = 2; + break; + case 0x40: + headerlen = volt[1]; + recordlen = volt[2]; + entries = volt[3]; /* not a clue what the entries are for.. */ + vidmask = volt[11]; /* guess.. */ + vidshift = 0; + break; + default: + NV_WARN(dev, "voltage table 0x%02x unknown\n", volt[0]); + return; + } + + /* validate vid mask */ + voltage->vid_mask = vidmask; + if (!voltage->vid_mask) + return; + + i = 0; + while (vidmask) { + if (i > nr_vidtag) { + NV_DEBUG(dev, "vid bit %d unknown\n", i); + return; + } + + if (!nouveau_gpio_func_valid(dev, vidtag[i])) { + NV_DEBUG(dev, "vid bit %d has no gpio tag\n", i); + return; + } + + vidmask >>= 1; + i++; + } + + /* parse vbios entries into common format */ + voltage->version = volt[0]; + if (voltage->version < 0x40) { + voltage->nr_level = entries; + voltage->level = + kcalloc(entries, sizeof(*voltage->level), GFP_KERNEL); + if (!voltage->level) + return; + + entry = volt + headerlen; + for (i = 0; i < entries; i++, entry += recordlen) { + voltage->level[i].voltage = entry[0] * 10000; + voltage->level[i].vid = entry[1] >> vidshift; + } + } else { + u32 volt_uv = ROM32(volt[4]); + s16 step_uv = ROM16(volt[8]); + u8 vid; + + voltage->nr_level = voltage->vid_mask + 1; + voltage->level = kcalloc(voltage->nr_level, + sizeof(*voltage->level), GFP_KERNEL); + if (!voltage->level) + return; + + for (vid = 0; vid <= voltage->vid_mask; vid++) { + voltage->level[vid].voltage = volt_uv; + voltage->level[vid].vid = vid; + volt_uv += step_uv; + } + } + + voltage->supported = true; +} + +void +nouveau_volt_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_pm_voltage *volt = &dev_priv->engine.pm.voltage; + + kfree(volt->level); +} diff --git a/drivers/gpu/drm/nouveau/nv04_crtc.c b/drivers/gpu/drm/nouveau/nv04_crtc.c new file mode 100644 index 00000000..728d0758 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_crtc.c @@ -0,0 +1,1063 @@ +/* + * Copyright 1993-2003 NVIDIA, Corporation + * Copyright 2006 Dave Airlie + * Copyright 2007 Maarten Maathuis + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" + +#include "nouveau_drv.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_crtc.h" +#include "nouveau_fb.h" +#include "nouveau_hw.h" +#include "nvreg.h" +#include "nouveau_fbcon.h" + +static int +nv04_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb); + +static void +crtc_wr_cio_state(struct drm_crtc *crtc, struct nv04_crtc_reg *crtcstate, int index) +{ + NVWriteVgaCrtc(crtc->dev, nouveau_crtc(crtc)->index, index, + crtcstate->CRTC[index]); +} + +static void nv_crtc_set_digital_vibrance(struct drm_crtc *crtc, int level) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + + regp->CRTC[NV_CIO_CRE_CSB] = nv_crtc->saturation = level; + if (nv_crtc->saturation && nv_gf4_disp_arch(crtc->dev)) { + regp->CRTC[NV_CIO_CRE_CSB] = 0x80; + regp->CRTC[NV_CIO_CRE_5B] = nv_crtc->saturation << 2; + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_5B); + } + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_CSB); +} + +static void nv_crtc_set_image_sharpening(struct drm_crtc *crtc, int level) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + + nv_crtc->sharpness = level; + if (level < 0) /* blur is in hw range 0x3f -> 0x20 */ + level += 0x40; + regp->ramdac_634 = level; + NVWriteRAMDAC(crtc->dev, nv_crtc->index, NV_PRAMDAC_634, regp->ramdac_634); +} + +#define PLLSEL_VPLL1_MASK \ + (NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_VPLL \ + | NV_PRAMDAC_PLL_COEFF_SELECT_VCLK_RATIO_DB2) +#define PLLSEL_VPLL2_MASK \ + (NV_PRAMDAC_PLL_COEFF_SELECT_PLL_SOURCE_VPLL2 \ + | NV_PRAMDAC_PLL_COEFF_SELECT_VCLK2_RATIO_DB2) +#define PLLSEL_TV_MASK \ + (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1 \ + | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1 \ + | NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2 \ + | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2) + +/* NV4x 0x40.. pll notes: + * gpu pll: 0x4000 + 0x4004 + * ?gpu? pll: 0x4008 + 0x400c + * vpll1: 0x4010 + 0x4014 + * vpll2: 0x4018 + 0x401c + * mpll: 0x4020 + 0x4024 + * mpll: 0x4038 + 0x403c + * + * the first register of each pair has some unknown details: + * bits 0-7: redirected values from elsewhere? (similar to PLL_SETUP_CONTROL?) + * bits 20-23: (mpll) something to do with post divider? + * bits 28-31: related to single stage mode? (bit 8/12) + */ + +static void nv_crtc_calc_state_ext(struct drm_crtc *crtc, struct drm_display_mode * mode, int dot_clock) +{ + struct drm_device *dev = crtc->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nv04_mode_state *state = &dev_priv->mode_reg; + struct nv04_crtc_reg *regp = &state->crtc_reg[nv_crtc->index]; + struct nouveau_pll_vals *pv = ®p->pllvals; + struct pll_lims pll_lim; + + if (get_pll_limits(dev, nv_crtc->index ? PLL_VPLL1 : PLL_VPLL0, &pll_lim)) + return; + + /* NM2 == 0 is used to determine single stage mode on two stage plls */ + pv->NM2 = 0; + + /* for newer nv4x the blob uses only the first stage of the vpll below a + * certain clock. for a certain nv4b this is 150MHz. since the max + * output frequency of the first stage for this card is 300MHz, it is + * assumed the threshold is given by vco1 maxfreq/2 + */ + /* for early nv4x, specifically nv40 and *some* nv43 (devids 0 and 6, + * not 8, others unknown), the blob always uses both plls. no problem + * has yet been observed in allowing the use a single stage pll on all + * nv43 however. the behaviour of single stage use is untested on nv40 + */ + if (dev_priv->chipset > 0x40 && dot_clock <= (pll_lim.vco1.maxfreq / 2)) + memset(&pll_lim.vco2, 0, sizeof(pll_lim.vco2)); + + if (!nouveau_calc_pll_mnp(dev, &pll_lim, dot_clock, pv)) + return; + + state->pllsel &= PLLSEL_VPLL1_MASK | PLLSEL_VPLL2_MASK | PLLSEL_TV_MASK; + + /* The blob uses this always, so let's do the same */ + if (dev_priv->card_type == NV_40) + state->pllsel |= NV_PRAMDAC_PLL_COEFF_SELECT_USE_VPLL2_TRUE; + /* again nv40 and some nv43 act more like nv3x as described above */ + if (dev_priv->chipset < 0x41) + state->pllsel |= NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_MPLL | + NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_NVPLL; + state->pllsel |= nv_crtc->index ? PLLSEL_VPLL2_MASK : PLLSEL_VPLL1_MASK; + + if (pv->NM2) + NV_DEBUG_KMS(dev, "vpll: n1 %d n2 %d m1 %d m2 %d log2p %d\n", + pv->N1, pv->N2, pv->M1, pv->M2, pv->log2P); + else + NV_DEBUG_KMS(dev, "vpll: n %d m %d log2p %d\n", + pv->N1, pv->M1, pv->log2P); + + nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.offset); +} + +static void +nv_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_device *dev = crtc->dev; + unsigned char seq1 = 0, crtc17 = 0; + unsigned char crtc1A; + + NV_DEBUG_KMS(dev, "Setting dpms mode %d on CRTC %d\n", mode, + nv_crtc->index); + + if (nv_crtc->last_dpms == mode) /* Don't do unnecessary mode changes. */ + return; + + nv_crtc->last_dpms = mode; + + if (nv_two_heads(dev)) + NVSetOwner(dev, nv_crtc->index); + + /* nv4ref indicates these two RPC1 bits inhibit h/v sync */ + crtc1A = NVReadVgaCrtc(dev, nv_crtc->index, + NV_CIO_CRE_RPC1_INDEX) & ~0xC0; + switch (mode) { + case DRM_MODE_DPMS_STANDBY: + /* Screen: Off; HSync: Off, VSync: On -- Not Supported */ + seq1 = 0x20; + crtc17 = 0x80; + crtc1A |= 0x80; + break; + case DRM_MODE_DPMS_SUSPEND: + /* Screen: Off; HSync: On, VSync: Off -- Not Supported */ + seq1 = 0x20; + crtc17 = 0x80; + crtc1A |= 0x40; + break; + case DRM_MODE_DPMS_OFF: + /* Screen: Off; HSync: Off, VSync: Off */ + seq1 = 0x20; + crtc17 = 0x00; + crtc1A |= 0xC0; + break; + case DRM_MODE_DPMS_ON: + default: + /* Screen: On; HSync: On, VSync: On */ + seq1 = 0x00; + crtc17 = 0x80; + break; + } + + NVVgaSeqReset(dev, nv_crtc->index, true); + /* Each head has it's own sequencer, so we can turn it off when we want */ + seq1 |= (NVReadVgaSeq(dev, nv_crtc->index, NV_VIO_SR_CLOCK_INDEX) & ~0x20); + NVWriteVgaSeq(dev, nv_crtc->index, NV_VIO_SR_CLOCK_INDEX, seq1); + crtc17 |= (NVReadVgaCrtc(dev, nv_crtc->index, NV_CIO_CR_MODE_INDEX) & ~0x80); + mdelay(10); + NVWriteVgaCrtc(dev, nv_crtc->index, NV_CIO_CR_MODE_INDEX, crtc17); + NVVgaSeqReset(dev, nv_crtc->index, false); + + NVWriteVgaCrtc(dev, nv_crtc->index, NV_CIO_CRE_RPC1_INDEX, crtc1A); +} + +static bool +nv_crtc_mode_fixup(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void +nv_crtc_mode_set_vga(struct drm_crtc *crtc, struct drm_display_mode *mode) +{ + struct drm_device *dev = crtc->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + struct drm_framebuffer *fb = crtc->fb; + + /* Calculate our timings */ + int horizDisplay = (mode->crtc_hdisplay >> 3) - 1; + int horizStart = (mode->crtc_hsync_start >> 3) + 1; + int horizEnd = (mode->crtc_hsync_end >> 3) + 1; + int horizTotal = (mode->crtc_htotal >> 3) - 5; + int horizBlankStart = (mode->crtc_hdisplay >> 3) - 1; + int horizBlankEnd = (mode->crtc_htotal >> 3) - 1; + int vertDisplay = mode->crtc_vdisplay - 1; + int vertStart = mode->crtc_vsync_start - 1; + int vertEnd = mode->crtc_vsync_end - 1; + int vertTotal = mode->crtc_vtotal - 2; + int vertBlankStart = mode->crtc_vdisplay - 1; + int vertBlankEnd = mode->crtc_vtotal - 1; + + struct drm_encoder *encoder; + bool fp_output = false; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + if (encoder->crtc == crtc && + (nv_encoder->dcb->type == OUTPUT_LVDS || + nv_encoder->dcb->type == OUTPUT_TMDS)) + fp_output = true; + } + + if (fp_output) { + vertStart = vertTotal - 3; + vertEnd = vertTotal - 2; + vertBlankStart = vertStart; + horizStart = horizTotal - 5; + horizEnd = horizTotal - 2; + horizBlankEnd = horizTotal + 4; +#if 0 + if (dev->overlayAdaptor && dev_priv->card_type >= NV_10) + /* This reportedly works around some video overlay bandwidth problems */ + horizTotal += 2; +#endif + } + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + vertTotal |= 1; + +#if 0 + ErrorF("horizDisplay: 0x%X \n", horizDisplay); + ErrorF("horizStart: 0x%X \n", horizStart); + ErrorF("horizEnd: 0x%X \n", horizEnd); + ErrorF("horizTotal: 0x%X \n", horizTotal); + ErrorF("horizBlankStart: 0x%X \n", horizBlankStart); + ErrorF("horizBlankEnd: 0x%X \n", horizBlankEnd); + ErrorF("vertDisplay: 0x%X \n", vertDisplay); + ErrorF("vertStart: 0x%X \n", vertStart); + ErrorF("vertEnd: 0x%X \n", vertEnd); + ErrorF("vertTotal: 0x%X \n", vertTotal); + ErrorF("vertBlankStart: 0x%X \n", vertBlankStart); + ErrorF("vertBlankEnd: 0x%X \n", vertBlankEnd); +#endif + + /* + * compute correct Hsync & Vsync polarity + */ + if ((mode->flags & (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC)) + && (mode->flags & (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC))) { + + regp->MiscOutReg = 0x23; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + regp->MiscOutReg |= 0x40; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + regp->MiscOutReg |= 0x80; + } else { + int vdisplay = mode->vdisplay; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + vdisplay *= 2; + if (mode->vscan > 1) + vdisplay *= mode->vscan; + if (vdisplay < 400) + regp->MiscOutReg = 0xA3; /* +hsync -vsync */ + else if (vdisplay < 480) + regp->MiscOutReg = 0x63; /* -hsync +vsync */ + else if (vdisplay < 768) + regp->MiscOutReg = 0xE3; /* -hsync -vsync */ + else + regp->MiscOutReg = 0x23; /* +hsync +vsync */ + } + + regp->MiscOutReg |= (mode->clock_index & 0x03) << 2; + + /* + * Time Sequencer + */ + regp->Sequencer[NV_VIO_SR_RESET_INDEX] = 0x00; + /* 0x20 disables the sequencer */ + if (mode->flags & DRM_MODE_FLAG_CLKDIV2) + regp->Sequencer[NV_VIO_SR_CLOCK_INDEX] = 0x29; + else + regp->Sequencer[NV_VIO_SR_CLOCK_INDEX] = 0x21; + regp->Sequencer[NV_VIO_SR_PLANE_MASK_INDEX] = 0x0F; + regp->Sequencer[NV_VIO_SR_CHAR_MAP_INDEX] = 0x00; + regp->Sequencer[NV_VIO_SR_MEM_MODE_INDEX] = 0x0E; + + /* + * CRTC + */ + regp->CRTC[NV_CIO_CR_HDT_INDEX] = horizTotal; + regp->CRTC[NV_CIO_CR_HDE_INDEX] = horizDisplay; + regp->CRTC[NV_CIO_CR_HBS_INDEX] = horizBlankStart; + regp->CRTC[NV_CIO_CR_HBE_INDEX] = (1 << 7) | + XLATE(horizBlankEnd, 0, NV_CIO_CR_HBE_4_0); + regp->CRTC[NV_CIO_CR_HRS_INDEX] = horizStart; + regp->CRTC[NV_CIO_CR_HRE_INDEX] = XLATE(horizBlankEnd, 5, NV_CIO_CR_HRE_HBE_5) | + XLATE(horizEnd, 0, NV_CIO_CR_HRE_4_0); + regp->CRTC[NV_CIO_CR_VDT_INDEX] = vertTotal; + regp->CRTC[NV_CIO_CR_OVL_INDEX] = XLATE(vertStart, 9, NV_CIO_CR_OVL_VRS_9) | + XLATE(vertDisplay, 9, NV_CIO_CR_OVL_VDE_9) | + XLATE(vertTotal, 9, NV_CIO_CR_OVL_VDT_9) | + (1 << 4) | + XLATE(vertBlankStart, 8, NV_CIO_CR_OVL_VBS_8) | + XLATE(vertStart, 8, NV_CIO_CR_OVL_VRS_8) | + XLATE(vertDisplay, 8, NV_CIO_CR_OVL_VDE_8) | + XLATE(vertTotal, 8, NV_CIO_CR_OVL_VDT_8); + regp->CRTC[NV_CIO_CR_RSAL_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_CELL_HT_INDEX] = ((mode->flags & DRM_MODE_FLAG_DBLSCAN) ? MASK(NV_CIO_CR_CELL_HT_SCANDBL) : 0) | + 1 << 6 | + XLATE(vertBlankStart, 9, NV_CIO_CR_CELL_HT_VBS_9); + regp->CRTC[NV_CIO_CR_CURS_ST_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_CURS_END_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_SA_HI_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_SA_LO_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_TCOFF_HI_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_TCOFF_LO_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_VRS_INDEX] = vertStart; + regp->CRTC[NV_CIO_CR_VRE_INDEX] = 1 << 5 | XLATE(vertEnd, 0, NV_CIO_CR_VRE_3_0); + regp->CRTC[NV_CIO_CR_VDE_INDEX] = vertDisplay; + /* framebuffer can be larger than crtc scanout area. */ + regp->CRTC[NV_CIO_CR_OFFSET_INDEX] = fb->pitches[0] / 8; + regp->CRTC[NV_CIO_CR_ULINE_INDEX] = 0x00; + regp->CRTC[NV_CIO_CR_VBS_INDEX] = vertBlankStart; + regp->CRTC[NV_CIO_CR_VBE_INDEX] = vertBlankEnd; + regp->CRTC[NV_CIO_CR_MODE_INDEX] = 0x43; + regp->CRTC[NV_CIO_CR_LCOMP_INDEX] = 0xff; + + /* + * Some extended CRTC registers (they are not saved with the rest of the vga regs). + */ + + /* framebuffer can be larger than crtc scanout area. */ + regp->CRTC[NV_CIO_CRE_RPC0_INDEX] = + XLATE(fb->pitches[0] / 8, 8, NV_CIO_CRE_RPC0_OFFSET_10_8); + regp->CRTC[NV_CIO_CRE_42] = + XLATE(fb->pitches[0] / 8, 11, NV_CIO_CRE_42_OFFSET_11); + regp->CRTC[NV_CIO_CRE_RPC1_INDEX] = mode->crtc_hdisplay < 1280 ? + MASK(NV_CIO_CRE_RPC1_LARGE) : 0x00; + regp->CRTC[NV_CIO_CRE_LSR_INDEX] = XLATE(horizBlankEnd, 6, NV_CIO_CRE_LSR_HBE_6) | + XLATE(vertBlankStart, 10, NV_CIO_CRE_LSR_VBS_10) | + XLATE(vertStart, 10, NV_CIO_CRE_LSR_VRS_10) | + XLATE(vertDisplay, 10, NV_CIO_CRE_LSR_VDE_10) | + XLATE(vertTotal, 10, NV_CIO_CRE_LSR_VDT_10); + regp->CRTC[NV_CIO_CRE_HEB__INDEX] = XLATE(horizStart, 8, NV_CIO_CRE_HEB_HRS_8) | + XLATE(horizBlankStart, 8, NV_CIO_CRE_HEB_HBS_8) | + XLATE(horizDisplay, 8, NV_CIO_CRE_HEB_HDE_8) | + XLATE(horizTotal, 8, NV_CIO_CRE_HEB_HDT_8); + regp->CRTC[NV_CIO_CRE_EBR_INDEX] = XLATE(vertBlankStart, 11, NV_CIO_CRE_EBR_VBS_11) | + XLATE(vertStart, 11, NV_CIO_CRE_EBR_VRS_11) | + XLATE(vertDisplay, 11, NV_CIO_CRE_EBR_VDE_11) | + XLATE(vertTotal, 11, NV_CIO_CRE_EBR_VDT_11); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + horizTotal = (horizTotal >> 1) & ~1; + regp->CRTC[NV_CIO_CRE_ILACE__INDEX] = horizTotal; + regp->CRTC[NV_CIO_CRE_HEB__INDEX] |= XLATE(horizTotal, 8, NV_CIO_CRE_HEB_ILC_8); + } else + regp->CRTC[NV_CIO_CRE_ILACE__INDEX] = 0xff; /* interlace off */ + + /* + * Graphics Display Controller + */ + regp->Graphics[NV_VIO_GX_SR_INDEX] = 0x00; + regp->Graphics[NV_VIO_GX_SREN_INDEX] = 0x00; + regp->Graphics[NV_VIO_GX_CCOMP_INDEX] = 0x00; + regp->Graphics[NV_VIO_GX_ROP_INDEX] = 0x00; + regp->Graphics[NV_VIO_GX_READ_MAP_INDEX] = 0x00; + regp->Graphics[NV_VIO_GX_MODE_INDEX] = 0x40; /* 256 color mode */ + regp->Graphics[NV_VIO_GX_MISC_INDEX] = 0x05; /* map 64k mem + graphic mode */ + regp->Graphics[NV_VIO_GX_DONT_CARE_INDEX] = 0x0F; + regp->Graphics[NV_VIO_GX_BIT_MASK_INDEX] = 0xFF; + + regp->Attribute[0] = 0x00; /* standard colormap translation */ + regp->Attribute[1] = 0x01; + regp->Attribute[2] = 0x02; + regp->Attribute[3] = 0x03; + regp->Attribute[4] = 0x04; + regp->Attribute[5] = 0x05; + regp->Attribute[6] = 0x06; + regp->Attribute[7] = 0x07; + regp->Attribute[8] = 0x08; + regp->Attribute[9] = 0x09; + regp->Attribute[10] = 0x0A; + regp->Attribute[11] = 0x0B; + regp->Attribute[12] = 0x0C; + regp->Attribute[13] = 0x0D; + regp->Attribute[14] = 0x0E; + regp->Attribute[15] = 0x0F; + regp->Attribute[NV_CIO_AR_MODE_INDEX] = 0x01; /* Enable graphic mode */ + /* Non-vga */ + regp->Attribute[NV_CIO_AR_OSCAN_INDEX] = 0x00; + regp->Attribute[NV_CIO_AR_PLANE_INDEX] = 0x0F; /* enable all color planes */ + regp->Attribute[NV_CIO_AR_HPP_INDEX] = 0x00; + regp->Attribute[NV_CIO_AR_CSEL_INDEX] = 0x00; +} + +/** + * Sets up registers for the given mode/adjusted_mode pair. + * + * The clocks, CRTCs and outputs attached to this CRTC must be off. + * + * This shouldn't enable any clocks, CRTCs, or outputs, but they should + * be easily turned on/off after this. + */ +static void +nv_crtc_mode_set_regs(struct drm_crtc *crtc, struct drm_display_mode * mode) +{ + struct drm_device *dev = crtc->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + struct nv04_crtc_reg *savep = &dev_priv->saved_reg.crtc_reg[nv_crtc->index]; + struct drm_encoder *encoder; + bool lvds_output = false, tmds_output = false, tv_output = false, + off_chip_digital = false; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + bool digital = false; + + if (encoder->crtc != crtc) + continue; + + if (nv_encoder->dcb->type == OUTPUT_LVDS) + digital = lvds_output = true; + if (nv_encoder->dcb->type == OUTPUT_TV) + tv_output = true; + if (nv_encoder->dcb->type == OUTPUT_TMDS) + digital = tmds_output = true; + if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP && digital) + off_chip_digital = true; + } + + /* Registers not directly related to the (s)vga mode */ + + /* What is the meaning of this register? */ + /* A few popular values are 0x18, 0x1c, 0x38, 0x3c */ + regp->CRTC[NV_CIO_CRE_ENH_INDEX] = savep->CRTC[NV_CIO_CRE_ENH_INDEX] & ~(1<<5); + + regp->crtc_eng_ctrl = 0; + /* Except for rare conditions I2C is enabled on the primary crtc */ + if (nv_crtc->index == 0) + regp->crtc_eng_ctrl |= NV_CRTC_FSEL_I2C; +#if 0 + /* Set overlay to desired crtc. */ + if (dev->overlayAdaptor) { + NVPortPrivPtr pPriv = GET_OVERLAY_PRIVATE(dev); + if (pPriv->overlayCRTC == nv_crtc->index) + regp->crtc_eng_ctrl |= NV_CRTC_FSEL_OVERLAY; + } +#endif + + /* ADDRESS_SPACE_PNVM is the same as setting HCUR_ASI */ + regp->cursor_cfg = NV_PCRTC_CURSOR_CONFIG_CUR_LINES_64 | + NV_PCRTC_CURSOR_CONFIG_CUR_PIXELS_64 | + NV_PCRTC_CURSOR_CONFIG_ADDRESS_SPACE_PNVM; + if (dev_priv->chipset >= 0x11) + regp->cursor_cfg |= NV_PCRTC_CURSOR_CONFIG_CUR_BPP_32; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + regp->cursor_cfg |= NV_PCRTC_CURSOR_CONFIG_DOUBLE_SCAN_ENABLE; + + /* Unblock some timings */ + regp->CRTC[NV_CIO_CRE_53] = 0; + regp->CRTC[NV_CIO_CRE_54] = 0; + + /* 0x00 is disabled, 0x11 is lvds, 0x22 crt and 0x88 tmds */ + if (lvds_output) + regp->CRTC[NV_CIO_CRE_SCRATCH3__INDEX] = 0x11; + else if (tmds_output) + regp->CRTC[NV_CIO_CRE_SCRATCH3__INDEX] = 0x88; + else + regp->CRTC[NV_CIO_CRE_SCRATCH3__INDEX] = 0x22; + + /* These values seem to vary */ + /* This register seems to be used by the bios to make certain decisions on some G70 cards? */ + regp->CRTC[NV_CIO_CRE_SCRATCH4__INDEX] = savep->CRTC[NV_CIO_CRE_SCRATCH4__INDEX]; + + nv_crtc_set_digital_vibrance(crtc, nv_crtc->saturation); + + /* probably a scratch reg, but kept for cargo-cult purposes: + * bit0: crtc0?, head A + * bit6: lvds, head A + * bit7: (only in X), head A + */ + if (nv_crtc->index == 0) + regp->CRTC[NV_CIO_CRE_4B] = savep->CRTC[NV_CIO_CRE_4B] | 0x80; + + /* The blob seems to take the current value from crtc 0, add 4 to that + * and reuse the old value for crtc 1 */ + regp->CRTC[NV_CIO_CRE_TVOUT_LATENCY] = dev_priv->saved_reg.crtc_reg[0].CRTC[NV_CIO_CRE_TVOUT_LATENCY]; + if (!nv_crtc->index) + regp->CRTC[NV_CIO_CRE_TVOUT_LATENCY] += 4; + + /* the blob sometimes sets |= 0x10 (which is the same as setting |= + * 1 << 30 on 0x60.830), for no apparent reason */ + regp->CRTC[NV_CIO_CRE_59] = off_chip_digital; + + if (dev_priv->card_type >= NV_30) + regp->CRTC[0x9f] = off_chip_digital ? 0x11 : 0x1; + + regp->crtc_830 = mode->crtc_vdisplay - 3; + regp->crtc_834 = mode->crtc_vdisplay - 1; + + if (dev_priv->card_type == NV_40) + /* This is what the blob does */ + regp->crtc_850 = NVReadCRTC(dev, 0, NV_PCRTC_850); + + if (dev_priv->card_type >= NV_30) + regp->gpio_ext = NVReadCRTC(dev, 0, NV_PCRTC_GPIO_EXT); + + if (dev_priv->card_type >= NV_10) + regp->crtc_cfg = NV10_PCRTC_CONFIG_START_ADDRESS_HSYNC; + else + regp->crtc_cfg = NV04_PCRTC_CONFIG_START_ADDRESS_HSYNC; + + /* Some misc regs */ + if (dev_priv->card_type == NV_40) { + regp->CRTC[NV_CIO_CRE_85] = 0xFF; + regp->CRTC[NV_CIO_CRE_86] = 0x1; + } + + regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] = (crtc->fb->depth + 1) / 8; + /* Enable slaved mode (called MODE_TV in nv4ref.h) */ + if (lvds_output || tmds_output || tv_output) + regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] |= (1 << 7); + + /* Generic PRAMDAC regs */ + + if (dev_priv->card_type >= NV_10) + /* Only bit that bios and blob set. */ + regp->nv10_cursync = (1 << 25); + + regp->ramdac_gen_ctrl = NV_PRAMDAC_GENERAL_CONTROL_BPC_8BITS | + NV_PRAMDAC_GENERAL_CONTROL_VGA_STATE_SEL | + NV_PRAMDAC_GENERAL_CONTROL_PIXMIX_ON; + if (crtc->fb->depth == 16) + regp->ramdac_gen_ctrl |= NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL; + if (dev_priv->chipset >= 0x11) + regp->ramdac_gen_ctrl |= NV_PRAMDAC_GENERAL_CONTROL_PIPE_LONG; + + regp->ramdac_630 = 0; /* turn off green mode (tv test pattern?) */ + regp->tv_setup = 0; + + nv_crtc_set_image_sharpening(crtc, nv_crtc->sharpness); + + /* Some values the blob sets */ + regp->ramdac_8c0 = 0x100; + regp->ramdac_a20 = 0x0; + regp->ramdac_a24 = 0xfffff; + regp->ramdac_a34 = 0x1; +} + +/** + * Sets up registers for the given mode/adjusted_mode pair. + * + * The clocks, CRTCs and outputs attached to this CRTC must be off. + * + * This shouldn't enable any clocks, CRTCs, or outputs, but they should + * be easily turned on/off after this. + */ +static int +nv_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, struct drm_framebuffer *old_fb) +{ + struct drm_device *dev = crtc->dev; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_nouveau_private *dev_priv = dev->dev_private; + + NV_DEBUG_KMS(dev, "CTRC mode on CRTC %d:\n", nv_crtc->index); + drm_mode_debug_printmodeline(adjusted_mode); + + /* unlock must come after turning off FP_TG_CONTROL in output_prepare */ + nv_lock_vga_crtc_shadow(dev, nv_crtc->index, -1); + + nv_crtc_mode_set_vga(crtc, adjusted_mode); + /* calculated in nv04_dfp_prepare, nv40 needs it written before calculating PLLs */ + if (dev_priv->card_type == NV_40) + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, dev_priv->mode_reg.sel_clk); + nv_crtc_mode_set_regs(crtc, adjusted_mode); + nv_crtc_calc_state_ext(crtc, mode, adjusted_mode->clock); + return 0; +} + +static void nv_crtc_save(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct nv04_mode_state *state = &dev_priv->mode_reg; + struct nv04_crtc_reg *crtc_state = &state->crtc_reg[nv_crtc->index]; + struct nv04_mode_state *saved = &dev_priv->saved_reg; + struct nv04_crtc_reg *crtc_saved = &saved->crtc_reg[nv_crtc->index]; + + if (nv_two_heads(crtc->dev)) + NVSetOwner(crtc->dev, nv_crtc->index); + + nouveau_hw_save_state(crtc->dev, nv_crtc->index, saved); + + /* init some state to saved value */ + state->sel_clk = saved->sel_clk & ~(0x5 << 16); + crtc_state->CRTC[NV_CIO_CRE_LCD__INDEX] = crtc_saved->CRTC[NV_CIO_CRE_LCD__INDEX]; + state->pllsel = saved->pllsel & ~(PLLSEL_VPLL1_MASK | PLLSEL_VPLL2_MASK | PLLSEL_TV_MASK); + crtc_state->gpio_ext = crtc_saved->gpio_ext; +} + +static void nv_crtc_restore(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + int head = nv_crtc->index; + uint8_t saved_cr21 = dev_priv->saved_reg.crtc_reg[head].CRTC[NV_CIO_CRE_21]; + + if (nv_two_heads(crtc->dev)) + NVSetOwner(crtc->dev, head); + + nouveau_hw_load_state(crtc->dev, head, &dev_priv->saved_reg); + nv_lock_vga_crtc_shadow(crtc->dev, head, saved_cr21); + + nv_crtc->last_dpms = NV_DPMS_CLEARED; +} + +static void nv_crtc_prepare(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_crtc_helper_funcs *funcs = crtc->helper_private; + + if (nv_two_heads(dev)) + NVSetOwner(dev, nv_crtc->index); + + drm_vblank_pre_modeset(dev, nv_crtc->index); + funcs->dpms(crtc, DRM_MODE_DPMS_OFF); + + NVBlankScreen(dev, nv_crtc->index, true); + + /* Some more preparation. */ + NVWriteCRTC(dev, nv_crtc->index, NV_PCRTC_CONFIG, NV_PCRTC_CONFIG_START_ADDRESS_NON_VGA); + if (dev_priv->card_type == NV_40) { + uint32_t reg900 = NVReadRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_900); + NVWriteRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_900, reg900 & ~0x10000); + } +} + +static void nv_crtc_commit(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_crtc_helper_funcs *funcs = crtc->helper_private; + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + nouveau_hw_load_state(dev, nv_crtc->index, &dev_priv->mode_reg); + nv04_crtc_mode_set_base(crtc, crtc->x, crtc->y, NULL); + +#ifdef __BIG_ENDIAN + /* turn on LFB swapping */ + { + uint8_t tmp = NVReadVgaCrtc(dev, nv_crtc->index, NV_CIO_CRE_RCR); + tmp |= MASK(NV_CIO_CRE_RCR_ENDIAN_BIG); + NVWriteVgaCrtc(dev, nv_crtc->index, NV_CIO_CRE_RCR, tmp); + } +#endif + + funcs->dpms(crtc, DRM_MODE_DPMS_ON); + drm_vblank_post_modeset(dev, nv_crtc->index); +} + +static void nv_crtc_destroy(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + NV_DEBUG_KMS(crtc->dev, "\n"); + + if (!nv_crtc) + return; + + drm_crtc_cleanup(crtc); + + nouveau_bo_unmap(nv_crtc->cursor.nvbo); + nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo); + kfree(nv_crtc); +} + +static void +nv_crtc_gamma_load(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_device *dev = nv_crtc->base.dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct rgb { uint8_t r, g, b; } __attribute__((packed)) *rgbs; + int i; + + rgbs = (struct rgb *)dev_priv->mode_reg.crtc_reg[nv_crtc->index].DAC; + for (i = 0; i < 256; i++) { + rgbs[i].r = nv_crtc->lut.r[i] >> 8; + rgbs[i].g = nv_crtc->lut.g[i] >> 8; + rgbs[i].b = nv_crtc->lut.b[i] >> 8; + } + + nouveau_hw_load_state_palette(dev, nv_crtc->index, &dev_priv->mode_reg); +} + +static void +nv_crtc_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, uint32_t start, + uint32_t size) +{ + int end = (start + size > 256) ? 256 : start + size, i; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + for (i = start; i < end; i++) { + nv_crtc->lut.r[i] = r[i]; + nv_crtc->lut.g[i] = g[i]; + nv_crtc->lut.b[i] = b[i]; + } + + /* We need to know the depth before we upload, but it's possible to + * get called before a framebuffer is bound. If this is the case, + * mark the lut values as dirty by setting depth==0, and it'll be + * uploaded on the first mode_set_base() + */ + if (!nv_crtc->base.fb) { + nv_crtc->lut.depth = 0; + return; + } + + nv_crtc_gamma_load(crtc); +} + +static int +nv04_crtc_do_mode_set_base(struct drm_crtc *crtc, + struct drm_framebuffer *passed_fb, + int x, int y, bool atomic) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + struct drm_framebuffer *drm_fb; + struct nouveau_framebuffer *fb; + int arb_burst, arb_lwm; + int ret; + + NV_DEBUG_KMS(dev, "index %d\n", nv_crtc->index); + + /* no fb bound */ + if (!atomic && !crtc->fb) { + NV_DEBUG_KMS(dev, "No FB bound\n"); + return 0; + } + + + /* If atomic, we want to switch to the fb we were passed, so + * now we update pointers to do that. (We don't pin; just + * assume we're already pinned and update the base address.) + */ + if (atomic) { + drm_fb = passed_fb; + fb = nouveau_framebuffer(passed_fb); + } else { + drm_fb = crtc->fb; + fb = nouveau_framebuffer(crtc->fb); + /* If not atomic, we can go ahead and pin, and unpin the + * old fb we were passed. + */ + ret = nouveau_bo_pin(fb->nvbo, TTM_PL_FLAG_VRAM); + if (ret) + return ret; + + if (passed_fb) { + struct nouveau_framebuffer *ofb = nouveau_framebuffer(passed_fb); + nouveau_bo_unpin(ofb->nvbo); + } + } + + nv_crtc->fb.offset = fb->nvbo->bo.offset; + + if (nv_crtc->lut.depth != drm_fb->depth) { + nv_crtc->lut.depth = drm_fb->depth; + nv_crtc_gamma_load(crtc); + } + + /* Update the framebuffer format. */ + regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] &= ~3; + regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] |= (crtc->fb->depth + 1) / 8; + regp->ramdac_gen_ctrl &= ~NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL; + if (crtc->fb->depth == 16) + regp->ramdac_gen_ctrl |= NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL; + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_PIXEL_INDEX); + NVWriteRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_GENERAL_CONTROL, + regp->ramdac_gen_ctrl); + + regp->CRTC[NV_CIO_CR_OFFSET_INDEX] = drm_fb->pitches[0] >> 3; + regp->CRTC[NV_CIO_CRE_RPC0_INDEX] = + XLATE(drm_fb->pitches[0] >> 3, 8, NV_CIO_CRE_RPC0_OFFSET_10_8); + regp->CRTC[NV_CIO_CRE_42] = + XLATE(drm_fb->pitches[0] / 8, 11, NV_CIO_CRE_42_OFFSET_11); + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_RPC0_INDEX); + crtc_wr_cio_state(crtc, regp, NV_CIO_CR_OFFSET_INDEX); + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_42); + + /* Update the framebuffer location. */ + regp->fb_start = nv_crtc->fb.offset & ~3; + regp->fb_start += (y * drm_fb->pitches[0]) + (x * drm_fb->bits_per_pixel / 8); + nv_set_crtc_base(dev, nv_crtc->index, regp->fb_start); + + /* Update the arbitration parameters. */ + nouveau_calc_arb(dev, crtc->mode.clock, drm_fb->bits_per_pixel, + &arb_burst, &arb_lwm); + + regp->CRTC[NV_CIO_CRE_FF_INDEX] = arb_burst; + regp->CRTC[NV_CIO_CRE_FFLWM__INDEX] = arb_lwm & 0xff; + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_FF_INDEX); + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_FFLWM__INDEX); + + if (dev_priv->card_type >= NV_20) { + regp->CRTC[NV_CIO_CRE_47] = arb_lwm >> 8; + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_47); + } + + return 0; +} + +static int +nv04_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + return nv04_crtc_do_mode_set_base(crtc, old_fb, x, y, false); +} + +static int +nv04_crtc_mode_set_base_atomic(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + int x, int y, enum mode_set_atomic state) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct drm_device *dev = dev_priv->dev; + + if (state == ENTER_ATOMIC_MODE_SET) + nouveau_fbcon_save_disable_accel(dev); + else + nouveau_fbcon_restore_accel(dev); + + return nv04_crtc_do_mode_set_base(crtc, fb, x, y, true); +} + +static void nv04_cursor_upload(struct drm_device *dev, struct nouveau_bo *src, + struct nouveau_bo *dst) +{ + int width = nv_cursor_width(dev); + uint32_t pixel; + int i, j; + + for (i = 0; i < width; i++) { + for (j = 0; j < width; j++) { + pixel = nouveau_bo_rd32(src, i*64 + j); + + nouveau_bo_wr16(dst, i*width + j, (pixel & 0x80000000) >> 16 + | (pixel & 0xf80000) >> 9 + | (pixel & 0xf800) >> 6 + | (pixel & 0xf8) >> 3); + } + } +} + +static void nv11_cursor_upload(struct drm_device *dev, struct nouveau_bo *src, + struct nouveau_bo *dst) +{ + uint32_t pixel; + int alpha, i; + + /* nv11+ supports premultiplied (PM), or non-premultiplied (NPM) alpha + * cursors (though NPM in combination with fp dithering may not work on + * nv11, from "nv" driver history) + * NPM mode needs NV_PCRTC_CURSOR_CONFIG_ALPHA_BLEND set and is what the + * blob uses, however we get given PM cursors so we use PM mode + */ + for (i = 0; i < 64 * 64; i++) { + pixel = nouveau_bo_rd32(src, i); + + /* hw gets unhappy if alpha <= rgb values. for a PM image "less + * than" shouldn't happen; fix "equal to" case by adding one to + * alpha channel (slightly inaccurate, but so is attempting to + * get back to NPM images, due to limits of integer precision) + */ + alpha = pixel >> 24; + if (alpha > 0 && alpha < 255) + pixel = (pixel & 0x00ffffff) | ((alpha + 1) << 24); + +#ifdef __BIG_ENDIAN + { + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset == 0x11) { + pixel = ((pixel & 0x000000ff) << 24) | + ((pixel & 0x0000ff00) << 8) | + ((pixel & 0x00ff0000) >> 8) | + ((pixel & 0xff000000) >> 24); + } + } +#endif + + nouveau_bo_wr32(dst, i, pixel); + } +} + +static int +nv04_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv, + uint32_t buffer_handle, uint32_t width, uint32_t height) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct drm_device *dev = dev_priv->dev; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nouveau_bo *cursor = NULL; + struct drm_gem_object *gem; + int ret = 0; + + if (!buffer_handle) { + nv_crtc->cursor.hide(nv_crtc, true); + return 0; + } + + if (width != 64 || height != 64) + return -EINVAL; + + gem = drm_gem_object_lookup(dev, file_priv, buffer_handle); + if (!gem) + return -ENOENT; + cursor = nouveau_gem_object(gem); + + ret = nouveau_bo_map(cursor); + if (ret) + goto out; + + if (dev_priv->chipset >= 0x11) + nv11_cursor_upload(dev, cursor, nv_crtc->cursor.nvbo); + else + nv04_cursor_upload(dev, cursor, nv_crtc->cursor.nvbo); + + nouveau_bo_unmap(cursor); + nv_crtc->cursor.offset = nv_crtc->cursor.nvbo->bo.offset; + nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.offset); + nv_crtc->cursor.show(nv_crtc, true); +out: + drm_gem_object_unreference_unlocked(gem); + return ret; +} + +static int +nv04_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + nv_crtc->cursor.set_pos(nv_crtc, x, y); + return 0; +} + +static const struct drm_crtc_funcs nv04_crtc_funcs = { + .save = nv_crtc_save, + .restore = nv_crtc_restore, + .cursor_set = nv04_crtc_cursor_set, + .cursor_move = nv04_crtc_cursor_move, + .gamma_set = nv_crtc_gamma_set, + .set_config = drm_crtc_helper_set_config, + .page_flip = nouveau_crtc_page_flip, + .destroy = nv_crtc_destroy, +}; + +static const struct drm_crtc_helper_funcs nv04_crtc_helper_funcs = { + .dpms = nv_crtc_dpms, + .prepare = nv_crtc_prepare, + .commit = nv_crtc_commit, + .mode_fixup = nv_crtc_mode_fixup, + .mode_set = nv_crtc_mode_set, + .mode_set_base = nv04_crtc_mode_set_base, + .mode_set_base_atomic = nv04_crtc_mode_set_base_atomic, + .load_lut = nv_crtc_gamma_load, +}; + +int +nv04_crtc_create(struct drm_device *dev, int crtc_num) +{ + struct nouveau_crtc *nv_crtc; + int ret, i; + + nv_crtc = kzalloc(sizeof(*nv_crtc), GFP_KERNEL); + if (!nv_crtc) + return -ENOMEM; + + for (i = 0; i < 256; i++) { + nv_crtc->lut.r[i] = i << 8; + nv_crtc->lut.g[i] = i << 8; + nv_crtc->lut.b[i] = i << 8; + } + nv_crtc->lut.depth = 0; + + nv_crtc->index = crtc_num; + nv_crtc->last_dpms = NV_DPMS_CLEARED; + + drm_crtc_init(dev, &nv_crtc->base, &nv04_crtc_funcs); + drm_crtc_helper_add(&nv_crtc->base, &nv04_crtc_helper_funcs); + drm_mode_crtc_set_gamma_size(&nv_crtc->base, 256); + + ret = nouveau_bo_new(dev, 64*64*4, 0x100, TTM_PL_FLAG_VRAM, + 0, 0x0000, &nv_crtc->cursor.nvbo); + if (!ret) { + ret = nouveau_bo_pin(nv_crtc->cursor.nvbo, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(nv_crtc->cursor.nvbo); + if (ret) + nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo); + } + + nv04_cursor_init(nv_crtc); + + return 0; +} + diff --git a/drivers/gpu/drm/nouveau/nv04_cursor.c b/drivers/gpu/drm/nouveau/nv04_cursor.c new file mode 100644 index 00000000..aaf3de3b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_cursor.c @@ -0,0 +1,71 @@ +#include "drmP.h" +#include "drm_mode.h" +#include "nouveau_reg.h" +#include "nouveau_drv.h" +#include "nouveau_crtc.h" +#include "nouveau_hw.h" + +static void +nv04_cursor_show(struct nouveau_crtc *nv_crtc, bool update) +{ + nv_show_cursor(nv_crtc->base.dev, nv_crtc->index, true); +} + +static void +nv04_cursor_hide(struct nouveau_crtc *nv_crtc, bool update) +{ + nv_show_cursor(nv_crtc->base.dev, nv_crtc->index, false); +} + +static void +nv04_cursor_set_pos(struct nouveau_crtc *nv_crtc, int x, int y) +{ + nv_crtc->cursor_saved_x = x; nv_crtc->cursor_saved_y = y; + NVWriteRAMDAC(nv_crtc->base.dev, nv_crtc->index, + NV_PRAMDAC_CU_START_POS, + XLATE(y, 0, NV_PRAMDAC_CU_START_POS_Y) | + XLATE(x, 0, NV_PRAMDAC_CU_START_POS_X)); +} + +static void +crtc_wr_cio_state(struct drm_crtc *crtc, struct nv04_crtc_reg *crtcstate, int index) +{ + NVWriteVgaCrtc(crtc->dev, nouveau_crtc(crtc)->index, index, + crtcstate->CRTC[index]); +} + +static void +nv04_cursor_set_offset(struct nouveau_crtc *nv_crtc, uint32_t offset) +{ + struct drm_device *dev = nv_crtc->base.dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + struct drm_crtc *crtc = &nv_crtc->base; + + regp->CRTC[NV_CIO_CRE_HCUR_ADDR0_INDEX] = + MASK(NV_CIO_CRE_HCUR_ASI) | + XLATE(offset, 17, NV_CIO_CRE_HCUR_ADDR0_ADR); + regp->CRTC[NV_CIO_CRE_HCUR_ADDR1_INDEX] = + XLATE(offset, 11, NV_CIO_CRE_HCUR_ADDR1_ADR); + if (crtc->mode.flags & DRM_MODE_FLAG_DBLSCAN) + regp->CRTC[NV_CIO_CRE_HCUR_ADDR1_INDEX] |= + MASK(NV_CIO_CRE_HCUR_ADDR1_CUR_DBL); + regp->CRTC[NV_CIO_CRE_HCUR_ADDR2_INDEX] = offset >> 24; + + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_HCUR_ADDR0_INDEX); + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_HCUR_ADDR1_INDEX); + crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_HCUR_ADDR2_INDEX); + if (dev_priv->card_type == NV_40) + nv_fix_nv40_hw_cursor(dev, nv_crtc->index); +} + +int +nv04_cursor_init(struct nouveau_crtc *crtc) +{ + crtc->cursor.set_offset = nv04_cursor_set_offset; + crtc->cursor.set_pos = nv04_cursor_set_pos; + crtc->cursor.hide = nv04_cursor_hide; + crtc->cursor.show = nv04_cursor_show; + return 0; +} + diff --git a/drivers/gpu/drm/nouveau/nv04_dac.c b/drivers/gpu/drm/nouveau/nv04_dac.c new file mode 100644 index 00000000..8300266f --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_dac.c @@ -0,0 +1,544 @@ +/* + * Copyright 2003 NVIDIA, Corporation + * Copyright 2006 Dave Airlie + * Copyright 2007 Maarten Maathuis + * Copyright 2007-2009 Stuart Bennett + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" + +#include "nouveau_drv.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_crtc.h" +#include "nouveau_hw.h" +#include "nouveau_gpio.h" +#include "nvreg.h" + +int nv04_dac_output_offset(struct drm_encoder *encoder) +{ + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + int offset = 0; + + if (dcb->or & (8 | OUTPUT_C)) + offset += 0x68; + if (dcb->or & (8 | OUTPUT_B)) + offset += 0x2000; + + return offset; +} + +/* + * arbitrary limit to number of sense oscillations tolerated in one sample + * period (observed to be at least 13 in "nvidia") + */ +#define MAX_HBLANK_OSC 20 + +/* + * arbitrary limit to number of conflicting sample pairs to tolerate at a + * voltage step (observed to be at least 5 in "nvidia") + */ +#define MAX_SAMPLE_PAIRS 10 + +static int sample_load_twice(struct drm_device *dev, bool sense[2]) +{ + int i; + + for (i = 0; i < 2; i++) { + bool sense_a, sense_b, sense_b_prime; + int j = 0; + + /* + * wait for bit 0 clear -- out of hblank -- (say reg value 0x4), + * then wait for transition 0x4->0x5->0x4: enter hblank, leave + * hblank again + * use a 10ms timeout (guards against crtc being inactive, in + * which case blank state would never change) + */ + if (!nouveau_wait_eq(dev, 10000000, NV_PRMCIO_INP0__COLOR, + 0x00000001, 0x00000000)) + return -EBUSY; + if (!nouveau_wait_eq(dev, 10000000, NV_PRMCIO_INP0__COLOR, + 0x00000001, 0x00000001)) + return -EBUSY; + if (!nouveau_wait_eq(dev, 10000000, NV_PRMCIO_INP0__COLOR, + 0x00000001, 0x00000000)) + return -EBUSY; + + udelay(100); + /* when level triggers, sense is _LO_ */ + sense_a = nv_rd08(dev, NV_PRMCIO_INP0) & 0x10; + + /* take another reading until it agrees with sense_a... */ + do { + udelay(100); + sense_b = nv_rd08(dev, NV_PRMCIO_INP0) & 0x10; + if (sense_a != sense_b) { + sense_b_prime = + nv_rd08(dev, NV_PRMCIO_INP0) & 0x10; + if (sense_b == sense_b_prime) { + /* ... unless two consecutive subsequent + * samples agree; sense_a is replaced */ + sense_a = sense_b; + /* force mis-match so we loop */ + sense_b = !sense_a; + } + } + } while ((sense_a != sense_b) && ++j < MAX_HBLANK_OSC); + + if (j == MAX_HBLANK_OSC) + /* with so much oscillation, default to sense:LO */ + sense[i] = false; + else + sense[i] = sense_a; + } + + return 0; +} + +static enum drm_connector_status nv04_dac_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + uint8_t saved_seq1, saved_pi, saved_rpc1, saved_cr_mode; + uint8_t saved_palette0[3], saved_palette_mask; + uint32_t saved_rtest_ctrl, saved_rgen_ctrl; + int i; + uint8_t blue; + bool sense = true; + + /* + * for this detection to work, there needs to be a mode set up on the + * CRTC. this is presumed to be the case + */ + + if (nv_two_heads(dev)) + /* only implemented for head A for now */ + NVSetOwner(dev, 0); + + saved_cr_mode = NVReadVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX); + NVWriteVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX, saved_cr_mode | 0x80); + + saved_seq1 = NVReadVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX); + NVWriteVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX, saved_seq1 & ~0x20); + + saved_rtest_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL, + saved_rtest_ctrl & ~NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF); + + msleep(10); + + saved_pi = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX); + NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX, + saved_pi & ~(0x80 | MASK(NV_CIO_CRE_PIXEL_FORMAT))); + saved_rpc1 = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX); + NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX, saved_rpc1 & ~0xc0); + + nv_wr08(dev, NV_PRMDIO_READ_MODE_ADDRESS, 0x0); + for (i = 0; i < 3; i++) + saved_palette0[i] = nv_rd08(dev, NV_PRMDIO_PALETTE_DATA); + saved_palette_mask = nv_rd08(dev, NV_PRMDIO_PIXEL_MASK); + nv_wr08(dev, NV_PRMDIO_PIXEL_MASK, 0); + + saved_rgen_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL, + (saved_rgen_ctrl & ~(NV_PRAMDAC_GENERAL_CONTROL_BPC_8BITS | + NV_PRAMDAC_GENERAL_CONTROL_TERMINATION_75OHM)) | + NV_PRAMDAC_GENERAL_CONTROL_PIXMIX_ON); + + blue = 8; /* start of test range */ + + do { + bool sense_pair[2]; + + nv_wr08(dev, NV_PRMDIO_WRITE_MODE_ADDRESS, 0); + nv_wr08(dev, NV_PRMDIO_PALETTE_DATA, 0); + nv_wr08(dev, NV_PRMDIO_PALETTE_DATA, 0); + /* testing blue won't find monochrome monitors. I don't care */ + nv_wr08(dev, NV_PRMDIO_PALETTE_DATA, blue); + + i = 0; + /* take sample pairs until both samples in the pair agree */ + do { + if (sample_load_twice(dev, sense_pair)) + goto out; + } while ((sense_pair[0] != sense_pair[1]) && + ++i < MAX_SAMPLE_PAIRS); + + if (i == MAX_SAMPLE_PAIRS) + /* too much oscillation defaults to LO */ + sense = false; + else + sense = sense_pair[0]; + + /* + * if sense goes LO before blue ramps to 0x18, monitor is not connected. + * ergo, if blue gets to 0x18, monitor must be connected + */ + } while (++blue < 0x18 && sense); + +out: + nv_wr08(dev, NV_PRMDIO_PIXEL_MASK, saved_palette_mask); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL, saved_rgen_ctrl); + nv_wr08(dev, NV_PRMDIO_WRITE_MODE_ADDRESS, 0); + for (i = 0; i < 3; i++) + nv_wr08(dev, NV_PRMDIO_PALETTE_DATA, saved_palette0[i]); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL, saved_rtest_ctrl); + NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX, saved_pi); + NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX, saved_rpc1); + NVWriteVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX, saved_seq1); + NVWriteVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX, saved_cr_mode); + + if (blue == 0x18) { + NV_INFO(dev, "Load detected on head A\n"); + return connector_status_connected; + } + + return connector_status_disconnected; +} + +uint32_t nv17_dac_sample_load(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + uint32_t sample, testval, regoffset = nv04_dac_output_offset(encoder); + uint32_t saved_powerctrl_2 = 0, saved_powerctrl_4 = 0, saved_routput, + saved_rtest_ctrl, saved_gpio0, saved_gpio1, temp, routput; + int head; + +#define RGB_TEST_DATA(r, g, b) (r << 0 | g << 10 | b << 20) + if (dcb->type == OUTPUT_TV) { + testval = RGB_TEST_DATA(0xa0, 0xa0, 0xa0); + + if (dev_priv->vbios.tvdactestval) + testval = dev_priv->vbios.tvdactestval; + } else { + testval = RGB_TEST_DATA(0x140, 0x140, 0x140); /* 0x94050140 */ + + if (dev_priv->vbios.dactestval) + testval = dev_priv->vbios.dactestval; + } + + saved_rtest_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, + saved_rtest_ctrl & ~NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF); + + saved_powerctrl_2 = nvReadMC(dev, NV_PBUS_POWERCTRL_2); + + nvWriteMC(dev, NV_PBUS_POWERCTRL_2, saved_powerctrl_2 & 0xd7ffffff); + if (regoffset == 0x68) { + saved_powerctrl_4 = nvReadMC(dev, NV_PBUS_POWERCTRL_4); + nvWriteMC(dev, NV_PBUS_POWERCTRL_4, saved_powerctrl_4 & 0xffffffcf); + } + + saved_gpio1 = nouveau_gpio_func_get(dev, DCB_GPIO_TVDAC1); + saved_gpio0 = nouveau_gpio_func_get(dev, DCB_GPIO_TVDAC0); + + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC1, dcb->type == OUTPUT_TV); + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC0, dcb->type == OUTPUT_TV); + + msleep(4); + + saved_routput = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset); + head = (saved_routput & 0x100) >> 8; + + /* if there's a spare crtc, using it will minimise flicker */ + if (!(NVReadVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX) & 0xC0)) + head ^= 1; + + /* nv driver and nv31 use 0xfffffeee, nv34 and 6600 use 0xfffffece */ + routput = (saved_routput & 0xfffffece) | head << 8; + + if (dev_priv->card_type >= NV_40) { + if (dcb->type == OUTPUT_TV) + routput |= 0x1a << 16; + else + routput &= ~(0x1a << 16); + } + + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, routput); + msleep(1); + + temp = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, temp | 1); + + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TESTPOINT_DATA, + NV_PRAMDAC_TESTPOINT_DATA_NOTBLANK | testval); + temp = NVReadRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL, + temp | NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED); + msleep(5); + + sample = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset); + /* do it again just in case it's a residual current */ + sample &= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset); + + temp = NVReadRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL, + temp & ~NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TESTPOINT_DATA, 0); + + /* bios does something more complex for restoring, but I think this is good enough */ + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, saved_routput); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, saved_rtest_ctrl); + if (regoffset == 0x68) + nvWriteMC(dev, NV_PBUS_POWERCTRL_4, saved_powerctrl_4); + nvWriteMC(dev, NV_PBUS_POWERCTRL_2, saved_powerctrl_2); + + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC1, saved_gpio1); + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC0, saved_gpio0); + + return sample; +} + +static enum drm_connector_status +nv17_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + + if (nv04_dac_in_use(encoder)) + return connector_status_disconnected; + + if (nv17_dac_sample_load(encoder) & + NV_PRAMDAC_TEST_CONTROL_SENSEB_ALLHI) { + NV_INFO(dev, "Load detected on output %c\n", + '@' + ffs(dcb->or)); + return connector_status_connected; + } else { + return connector_status_disconnected; + } +} + +static bool nv04_dac_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + if (nv04_dac_in_use(encoder)) + return false; + + return true; +} + +static void nv04_dac_prepare(struct drm_encoder *encoder) +{ + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + struct drm_device *dev = encoder->dev; + int head = nouveau_crtc(encoder->crtc)->index; + + helper->dpms(encoder, DRM_MODE_DPMS_OFF); + + nv04_dfp_disable(dev, head); +} + +static void nv04_dac_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int head = nouveau_crtc(encoder->crtc)->index; + + if (nv_gf4_disp_arch(dev)) { + struct drm_encoder *rebind; + uint32_t dac_offset = nv04_dac_output_offset(encoder); + uint32_t otherdac; + + /* bit 16-19 are bits that are set on some G70 cards, + * but don't seem to have much effect */ + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset, + head << 8 | NV_PRAMDAC_DACCLK_SEL_DACCLK); + /* force any other vga encoders to bind to the other crtc */ + list_for_each_entry(rebind, &dev->mode_config.encoder_list, head) { + if (rebind == encoder + || nouveau_encoder(rebind)->dcb->type != OUTPUT_ANALOG) + continue; + + dac_offset = nv04_dac_output_offset(rebind); + otherdac = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset, + (otherdac & ~0x0100) | (head ^ 1) << 8); + } + } + + /* This could use refinement for flatpanels, but it should work this way */ + if (dev_priv->chipset < 0x44) + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0xf0000000); + else + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000); +} + +static void nv04_dac_commit(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + + helper->dpms(encoder, DRM_MODE_DPMS_ON); + + NV_INFO(dev, "Output %s is running on CRTC %d using output %c\n", + drm_get_connector_name(&nouveau_encoder_connector_get(nv_encoder)->base), + nv_crtc->index, '@' + ffs(nv_encoder->dcb->or)); +} + +void nv04_dac_update_dacclk(struct drm_encoder *encoder, bool enable) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + + if (nv_gf4_disp_arch(dev)) { + uint32_t *dac_users = &dev_priv->dac_users[ffs(dcb->or) - 1]; + int dacclk_off = NV_PRAMDAC_DACCLK + nv04_dac_output_offset(encoder); + uint32_t dacclk = NVReadRAMDAC(dev, 0, dacclk_off); + + if (enable) { + *dac_users |= 1 << dcb->index; + NVWriteRAMDAC(dev, 0, dacclk_off, dacclk | NV_PRAMDAC_DACCLK_SEL_DACCLK); + + } else { + *dac_users &= ~(1 << dcb->index); + if (!*dac_users) + NVWriteRAMDAC(dev, 0, dacclk_off, + dacclk & ~NV_PRAMDAC_DACCLK_SEL_DACCLK); + } + } +} + +/* Check if the DAC corresponding to 'encoder' is being used by + * someone else. */ +bool nv04_dac_in_use(struct drm_encoder *encoder) +{ + struct drm_nouveau_private *dev_priv = encoder->dev->dev_private; + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + + return nv_gf4_disp_arch(encoder->dev) && + (dev_priv->dac_users[ffs(dcb->or) - 1] & ~(1 << dcb->index)); +} + +static void nv04_dac_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + if (nv_encoder->last_dpms == mode) + return; + nv_encoder->last_dpms = mode; + + NV_INFO(dev, "Setting dpms mode %d on vga encoder (output %d)\n", + mode, nv_encoder->dcb->index); + + nv04_dac_update_dacclk(encoder, mode == DRM_MODE_DPMS_ON); +} + +static void nv04_dac_save(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + + if (nv_gf4_disp_arch(dev)) + nv_encoder->restore.output = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + + nv04_dac_output_offset(encoder)); +} + +static void nv04_dac_restore(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + + if (nv_gf4_disp_arch(dev)) + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + nv04_dac_output_offset(encoder), + nv_encoder->restore.output); + + nv_encoder->last_dpms = NV_DPMS_CLEARED; +} + +static void nv04_dac_destroy(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + NV_DEBUG_KMS(encoder->dev, "\n"); + + drm_encoder_cleanup(encoder); + kfree(nv_encoder); +} + +static const struct drm_encoder_helper_funcs nv04_dac_helper_funcs = { + .dpms = nv04_dac_dpms, + .save = nv04_dac_save, + .restore = nv04_dac_restore, + .mode_fixup = nv04_dac_mode_fixup, + .prepare = nv04_dac_prepare, + .commit = nv04_dac_commit, + .mode_set = nv04_dac_mode_set, + .detect = nv04_dac_detect +}; + +static const struct drm_encoder_helper_funcs nv17_dac_helper_funcs = { + .dpms = nv04_dac_dpms, + .save = nv04_dac_save, + .restore = nv04_dac_restore, + .mode_fixup = nv04_dac_mode_fixup, + .prepare = nv04_dac_prepare, + .commit = nv04_dac_commit, + .mode_set = nv04_dac_mode_set, + .detect = nv17_dac_detect +}; + +static const struct drm_encoder_funcs nv04_dac_funcs = { + .destroy = nv04_dac_destroy, +}; + +int +nv04_dac_create(struct drm_connector *connector, struct dcb_entry *entry) +{ + const struct drm_encoder_helper_funcs *helper; + struct nouveau_encoder *nv_encoder = NULL; + struct drm_device *dev = connector->dev; + struct drm_encoder *encoder; + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + + encoder = to_drm_encoder(nv_encoder); + + nv_encoder->dcb = entry; + nv_encoder->or = ffs(entry->or) - 1; + + if (nv_gf4_disp_arch(dev)) + helper = &nv17_dac_helper_funcs; + else + helper = &nv04_dac_helper_funcs; + + drm_encoder_init(dev, encoder, &nv04_dac_funcs, DRM_MODE_ENCODER_DAC); + drm_encoder_helper_add(encoder, helper); + + encoder->possible_crtcs = entry->heads; + encoder->possible_clones = 0; + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv04_dfp.c b/drivers/gpu/drm/nouveau/nv04_dfp.c new file mode 100644 index 00000000..22587460 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_dfp.c @@ -0,0 +1,720 @@ +/* + * Copyright 2003 NVIDIA, Corporation + * Copyright 2006 Dave Airlie + * Copyright 2007 Maarten Maathuis + * Copyright 2007-2009 Stuart Bennett + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" + +#include "nouveau_drv.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_crtc.h" +#include "nouveau_hw.h" +#include "nvreg.h" + +#include "i2c/sil164.h" + +#define FP_TG_CONTROL_ON (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | \ + NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS | \ + NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS) +#define FP_TG_CONTROL_OFF (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_DISABLE | \ + NV_PRAMDAC_FP_TG_CONTROL_HSYNC_DISABLE | \ + NV_PRAMDAC_FP_TG_CONTROL_VSYNC_DISABLE) + +static inline bool is_fpc_off(uint32_t fpc) +{ + return ((fpc & (FP_TG_CONTROL_ON | FP_TG_CONTROL_OFF)) == + FP_TG_CONTROL_OFF); +} + +int nv04_dfp_get_bound_head(struct drm_device *dev, struct dcb_entry *dcbent) +{ + /* special case of nv_read_tmds to find crtc associated with an output. + * this does not give a correct answer for off-chip dvi, but there's no + * use for such an answer anyway + */ + int ramdac = (dcbent->or & OUTPUT_C) >> 2; + + NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_CONTROL, + NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE | 0x4); + return ((NVReadRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_DATA) & 0x8) >> 3) ^ ramdac; +} + +void nv04_dfp_bind_head(struct drm_device *dev, struct dcb_entry *dcbent, + int head, bool dl) +{ + /* The BIOS scripts don't do this for us, sadly + * Luckily we do know the values ;-) + * + * head < 0 indicates we wish to force a setting with the overrideval + * (for VT restore etc.) + */ + + int ramdac = (dcbent->or & OUTPUT_C) >> 2; + uint8_t tmds04 = 0x80; + + if (head != ramdac) + tmds04 = 0x88; + + if (dcbent->type == OUTPUT_LVDS) + tmds04 |= 0x01; + + nv_write_tmds(dev, dcbent->or, 0, 0x04, tmds04); + + if (dl) /* dual link */ + nv_write_tmds(dev, dcbent->or, 1, 0x04, tmds04 ^ 0x08); +} + +void nv04_dfp_disable(struct drm_device *dev, int head) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *crtcstate = dev_priv->mode_reg.crtc_reg; + + if (NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL) & + FP_TG_CONTROL_ON) { + /* digital remnants must be cleaned before new crtc + * values programmed. delay is time for the vga stuff + * to realise it's in control again + */ + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, + FP_TG_CONTROL_OFF); + msleep(50); + } + /* don't inadvertently turn it on when state written later */ + crtcstate[head].fp_control = FP_TG_CONTROL_OFF; + crtcstate[head].CRTC[NV_CIO_CRE_LCD__INDEX] &= + ~NV_CIO_CRE_LCD_ROUTE_MASK; +} + +void nv04_dfp_update_fp_control(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_crtc *crtc; + struct nouveau_crtc *nv_crtc; + uint32_t *fpc; + + if (mode == DRM_MODE_DPMS_ON) { + nv_crtc = nouveau_crtc(encoder->crtc); + fpc = &dev_priv->mode_reg.crtc_reg[nv_crtc->index].fp_control; + + if (is_fpc_off(*fpc)) { + /* using saved value is ok, as (is_digital && dpms_on && + * fp_control==OFF) is (at present) *only* true when + * fpc's most recent change was by below "off" code + */ + *fpc = nv_crtc->dpms_saved_fp_control; + } + + nv_crtc->fp_users |= 1 << nouveau_encoder(encoder)->dcb->index; + NVWriteRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_FP_TG_CONTROL, *fpc); + } else { + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + nv_crtc = nouveau_crtc(crtc); + fpc = &dev_priv->mode_reg.crtc_reg[nv_crtc->index].fp_control; + + nv_crtc->fp_users &= ~(1 << nouveau_encoder(encoder)->dcb->index); + if (!is_fpc_off(*fpc) && !nv_crtc->fp_users) { + nv_crtc->dpms_saved_fp_control = *fpc; + /* cut the FP output */ + *fpc &= ~FP_TG_CONTROL_ON; + *fpc |= FP_TG_CONTROL_OFF; + NVWriteRAMDAC(dev, nv_crtc->index, + NV_PRAMDAC_FP_TG_CONTROL, *fpc); + } + } + } +} + +static struct drm_encoder *get_tmds_slave(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + struct drm_encoder *slave; + + if (dcb->type != OUTPUT_TMDS || dcb->location == DCB_LOC_ON_CHIP) + return NULL; + + /* Some BIOSes (e.g. the one in a Quadro FX1000) report several + * TMDS transmitters at the same I2C address, in the same I2C + * bus. This can still work because in that case one of them is + * always hard-wired to a reasonable configuration using straps, + * and the other one needs to be programmed. + * + * I don't think there's a way to know which is which, even the + * blob programs the one exposed via I2C for *both* heads, so + * let's do the same. + */ + list_for_each_entry(slave, &dev->mode_config.encoder_list, head) { + struct dcb_entry *slave_dcb = nouveau_encoder(slave)->dcb; + + if (slave_dcb->type == OUTPUT_TMDS && get_slave_funcs(slave) && + slave_dcb->tmdsconf.slave_addr == dcb->tmdsconf.slave_addr) + return slave; + } + + return NULL; +} + +static bool nv04_dfp_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector = nouveau_encoder_connector_get(nv_encoder); + + if (!nv_connector->native_mode || + nv_connector->scaling_mode == DRM_MODE_SCALE_NONE || + mode->hdisplay > nv_connector->native_mode->hdisplay || + mode->vdisplay > nv_connector->native_mode->vdisplay) { + nv_encoder->mode = *adjusted_mode; + + } else { + nv_encoder->mode = *nv_connector->native_mode; + adjusted_mode->clock = nv_connector->native_mode->clock; + } + + return true; +} + +static void nv04_dfp_prepare_sel_clk(struct drm_device *dev, + struct nouveau_encoder *nv_encoder, int head) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_mode_state *state = &dev_priv->mode_reg; + uint32_t bits1618 = nv_encoder->dcb->or & OUTPUT_A ? 0x10000 : 0x40000; + + if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP) + return; + + /* SEL_CLK is only used on the primary ramdac + * It toggles spread spectrum PLL output and sets the bindings of PLLs + * to heads on digital outputs + */ + if (head) + state->sel_clk |= bits1618; + else + state->sel_clk &= ~bits1618; + + /* nv30: + * bit 0 NVClk spread spectrum on/off + * bit 2 MemClk spread spectrum on/off + * bit 4 PixClk1 spread spectrum on/off toggle + * bit 6 PixClk2 spread spectrum on/off toggle + * + * nv40 (observations from bios behaviour and mmio traces): + * bits 4&6 as for nv30 + * bits 5&7 head dependent as for bits 4&6, but do not appear with 4&6; + * maybe a different spread mode + * bits 8&10 seen on dual-link dvi outputs, purpose unknown (set by POST scripts) + * The logic behind turning spread spectrum on/off in the first place, + * and which bit-pair to use, is unclear on nv40 (for earlier cards, the fp table + * entry has the necessary info) + */ + if (nv_encoder->dcb->type == OUTPUT_LVDS && dev_priv->saved_reg.sel_clk & 0xf0) { + int shift = (dev_priv->saved_reg.sel_clk & 0x50) ? 0 : 1; + + state->sel_clk &= ~0xf0; + state->sel_clk |= (head ? 0x40 : 0x10) << shift; + } +} + +static void nv04_dfp_prepare(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int head = nouveau_crtc(encoder->crtc)->index; + struct nv04_crtc_reg *crtcstate = dev_priv->mode_reg.crtc_reg; + uint8_t *cr_lcd = &crtcstate[head].CRTC[NV_CIO_CRE_LCD__INDEX]; + uint8_t *cr_lcd_oth = &crtcstate[head ^ 1].CRTC[NV_CIO_CRE_LCD__INDEX]; + + helper->dpms(encoder, DRM_MODE_DPMS_OFF); + + nv04_dfp_prepare_sel_clk(dev, nv_encoder, head); + + *cr_lcd = (*cr_lcd & ~NV_CIO_CRE_LCD_ROUTE_MASK) | 0x3; + + if (nv_two_heads(dev)) { + if (nv_encoder->dcb->location == DCB_LOC_ON_CHIP) + *cr_lcd |= head ? 0x0 : 0x8; + else { + *cr_lcd |= (nv_encoder->dcb->or << 4) & 0x30; + if (nv_encoder->dcb->type == OUTPUT_LVDS) + *cr_lcd |= 0x30; + if ((*cr_lcd & 0x30) == (*cr_lcd_oth & 0x30)) { + /* avoid being connected to both crtcs */ + *cr_lcd_oth &= ~0x30; + NVWriteVgaCrtc(dev, head ^ 1, + NV_CIO_CRE_LCD__INDEX, + *cr_lcd_oth); + } + } + } +} + + +static void nv04_dfp_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + struct nv04_crtc_reg *savep = &dev_priv->saved_reg.crtc_reg[nv_crtc->index]; + struct nouveau_connector *nv_connector = nouveau_crtc_connector_get(nv_crtc); + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_display_mode *output_mode = &nv_encoder->mode; + struct drm_connector *connector = &nv_connector->base; + uint32_t mode_ratio, panel_ratio; + + NV_DEBUG_KMS(dev, "Output mode on CRTC %d:\n", nv_crtc->index); + drm_mode_debug_printmodeline(output_mode); + + /* Initialize the FP registers in this CRTC. */ + regp->fp_horiz_regs[FP_DISPLAY_END] = output_mode->hdisplay - 1; + regp->fp_horiz_regs[FP_TOTAL] = output_mode->htotal - 1; + if (!nv_gf4_disp_arch(dev) || + (output_mode->hsync_start - output_mode->hdisplay) >= + dev_priv->vbios.digital_min_front_porch) + regp->fp_horiz_regs[FP_CRTC] = output_mode->hdisplay; + else + regp->fp_horiz_regs[FP_CRTC] = output_mode->hsync_start - dev_priv->vbios.digital_min_front_porch - 1; + regp->fp_horiz_regs[FP_SYNC_START] = output_mode->hsync_start - 1; + regp->fp_horiz_regs[FP_SYNC_END] = output_mode->hsync_end - 1; + regp->fp_horiz_regs[FP_VALID_START] = output_mode->hskew; + regp->fp_horiz_regs[FP_VALID_END] = output_mode->hdisplay - 1; + + regp->fp_vert_regs[FP_DISPLAY_END] = output_mode->vdisplay - 1; + regp->fp_vert_regs[FP_TOTAL] = output_mode->vtotal - 1; + regp->fp_vert_regs[FP_CRTC] = output_mode->vtotal - 5 - 1; + regp->fp_vert_regs[FP_SYNC_START] = output_mode->vsync_start - 1; + regp->fp_vert_regs[FP_SYNC_END] = output_mode->vsync_end - 1; + regp->fp_vert_regs[FP_VALID_START] = 0; + regp->fp_vert_regs[FP_VALID_END] = output_mode->vdisplay - 1; + + /* bit26: a bit seen on some g7x, no as yet discernable purpose */ + regp->fp_control = NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | + (savep->fp_control & (1 << 26 | NV_PRAMDAC_FP_TG_CONTROL_READ_PROG)); + /* Deal with vsync/hsync polarity */ + /* LVDS screens do set this, but modes with +ve syncs are very rare */ + if (output_mode->flags & DRM_MODE_FLAG_PVSYNC) + regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS; + if (output_mode->flags & DRM_MODE_FLAG_PHSYNC) + regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS; + /* panel scaling first, as native would get set otherwise */ + if (nv_connector->scaling_mode == DRM_MODE_SCALE_NONE || + nv_connector->scaling_mode == DRM_MODE_SCALE_CENTER) /* panel handles it */ + regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_CENTER; + else if (adjusted_mode->hdisplay == output_mode->hdisplay && + adjusted_mode->vdisplay == output_mode->vdisplay) /* native mode */ + regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_NATIVE; + else /* gpu needs to scale */ + regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_SCALE; + if (nvReadEXTDEV(dev, NV_PEXTDEV_BOOT_0) & NV_PEXTDEV_BOOT_0_STRAP_FP_IFACE_12BIT) + regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12; + if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP && + output_mode->clock > 165000) + regp->fp_control |= (2 << 24); + if (nv_encoder->dcb->type == OUTPUT_LVDS) { + bool duallink = false, dummy; + if (nv_connector->edid && + nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) { + duallink = (((u8 *)nv_connector->edid)[121] == 2); + } else { + nouveau_bios_parse_lvds_table(dev, output_mode->clock, + &duallink, &dummy); + } + + if (duallink) + regp->fp_control |= (8 << 28); + } else + if (output_mode->clock > 165000) + regp->fp_control |= (8 << 28); + + regp->fp_debug_0 = NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND | + NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND | + NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR | + NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR | + NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED | + NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE | + NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE; + + /* We want automatic scaling */ + regp->fp_debug_1 = 0; + /* This can override HTOTAL and VTOTAL */ + regp->fp_debug_2 = 0; + + /* Use 20.12 fixed point format to avoid floats */ + mode_ratio = (1 << 12) * adjusted_mode->hdisplay / adjusted_mode->vdisplay; + panel_ratio = (1 << 12) * output_mode->hdisplay / output_mode->vdisplay; + /* if ratios are equal, SCALE_ASPECT will automatically (and correctly) + * get treated the same as SCALE_FULLSCREEN */ + if (nv_connector->scaling_mode == DRM_MODE_SCALE_ASPECT && + mode_ratio != panel_ratio) { + uint32_t diff, scale; + bool divide_by_2 = nv_gf4_disp_arch(dev); + + if (mode_ratio < panel_ratio) { + /* vertical needs to expand to glass size (automatic) + * horizontal needs to be scaled at vertical scale factor + * to maintain aspect */ + + scale = (1 << 12) * adjusted_mode->vdisplay / output_mode->vdisplay; + regp->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_XSCALE_TESTMODE_ENABLE | + XLATE(scale, divide_by_2, NV_PRAMDAC_FP_DEBUG_1_XSCALE_VALUE); + + /* restrict area of screen used, horizontally */ + diff = output_mode->hdisplay - + output_mode->vdisplay * mode_ratio / (1 << 12); + regp->fp_horiz_regs[FP_VALID_START] += diff / 2; + regp->fp_horiz_regs[FP_VALID_END] -= diff / 2; + } + + if (mode_ratio > panel_ratio) { + /* horizontal needs to expand to glass size (automatic) + * vertical needs to be scaled at horizontal scale factor + * to maintain aspect */ + + scale = (1 << 12) * adjusted_mode->hdisplay / output_mode->hdisplay; + regp->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_YSCALE_TESTMODE_ENABLE | + XLATE(scale, divide_by_2, NV_PRAMDAC_FP_DEBUG_1_YSCALE_VALUE); + + /* restrict area of screen used, vertically */ + diff = output_mode->vdisplay - + (1 << 12) * output_mode->hdisplay / mode_ratio; + regp->fp_vert_regs[FP_VALID_START] += diff / 2; + regp->fp_vert_regs[FP_VALID_END] -= diff / 2; + } + } + + /* Output property. */ + if ((nv_connector->dithering_mode == DITHERING_MODE_ON) || + (nv_connector->dithering_mode == DITHERING_MODE_AUTO && + encoder->crtc->fb->depth > connector->display_info.bpc * 3)) { + if (dev_priv->chipset == 0x11) + regp->dither = savep->dither | 0x00010000; + else { + int i; + regp->dither = savep->dither | 0x00000001; + for (i = 0; i < 3; i++) { + regp->dither_regs[i] = 0xe4e4e4e4; + regp->dither_regs[i + 3] = 0x44444444; + } + } + } else { + if (dev_priv->chipset != 0x11) { + /* reset them */ + int i; + for (i = 0; i < 3; i++) { + regp->dither_regs[i] = savep->dither_regs[i]; + regp->dither_regs[i + 3] = savep->dither_regs[i + 3]; + } + } + regp->dither = savep->dither; + } + + regp->fp_margin_color = 0; +} + +static void nv04_dfp_commit(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct dcb_entry *dcbe = nv_encoder->dcb; + int head = nouveau_crtc(encoder->crtc)->index; + struct drm_encoder *slave_encoder; + + if (dcbe->type == OUTPUT_TMDS) + run_tmds_table(dev, dcbe, head, nv_encoder->mode.clock); + else if (dcbe->type == OUTPUT_LVDS) + call_lvds_script(dev, dcbe, head, LVDS_RESET, nv_encoder->mode.clock); + + /* update fp_control state for any changes made by scripts, + * so correct value is written at DPMS on */ + dev_priv->mode_reg.crtc_reg[head].fp_control = + NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL); + + /* This could use refinement for flatpanels, but it should work this way */ + if (dev_priv->chipset < 0x44) + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0xf0000000); + else + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000); + + /* Init external transmitters */ + slave_encoder = get_tmds_slave(encoder); + if (slave_encoder) + get_slave_funcs(slave_encoder)->mode_set( + slave_encoder, &nv_encoder->mode, &nv_encoder->mode); + + helper->dpms(encoder, DRM_MODE_DPMS_ON); + + NV_INFO(dev, "Output %s is running on CRTC %d using output %c\n", + drm_get_connector_name(&nouveau_encoder_connector_get(nv_encoder)->base), + nv_crtc->index, '@' + ffs(nv_encoder->dcb->or)); +} + +static void nv04_dfp_update_backlight(struct drm_encoder *encoder, int mode) +{ +#ifdef __powerpc__ + struct drm_device *dev = encoder->dev; + + /* BIOS scripts usually take care of the backlight, thanks + * Apple for your consistency. + */ + if (dev->pci_device == 0x0179 || dev->pci_device == 0x0189 || + dev->pci_device == 0x0329) { + if (mode == DRM_MODE_DPMS_ON) { + nv_mask(dev, NV_PBUS_DEBUG_DUALHEAD_CTL, 0, 1 << 31); + nv_mask(dev, NV_PCRTC_GPIO_EXT, 3, 1); + } else { + nv_mask(dev, NV_PBUS_DEBUG_DUALHEAD_CTL, 1 << 31, 0); + nv_mask(dev, NV_PCRTC_GPIO_EXT, 3, 0); + } + } +#endif +} + +static inline bool is_powersaving_dpms(int mode) +{ + return (mode != DRM_MODE_DPMS_ON); +} + +static void nv04_lvds_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_crtc *crtc = encoder->crtc; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + bool was_powersaving = is_powersaving_dpms(nv_encoder->last_dpms); + + if (nv_encoder->last_dpms == mode) + return; + nv_encoder->last_dpms = mode; + + NV_INFO(dev, "Setting dpms mode %d on lvds encoder (output %d)\n", + mode, nv_encoder->dcb->index); + + if (was_powersaving && is_powersaving_dpms(mode)) + return; + + if (nv_encoder->dcb->lvdsconf.use_power_scripts) { + /* when removing an output, crtc may not be set, but PANEL_OFF + * must still be run + */ + int head = crtc ? nouveau_crtc(crtc)->index : + nv04_dfp_get_bound_head(dev, nv_encoder->dcb); + + if (mode == DRM_MODE_DPMS_ON) { + call_lvds_script(dev, nv_encoder->dcb, head, + LVDS_PANEL_ON, nv_encoder->mode.clock); + } else + /* pxclk of 0 is fine for PANEL_OFF, and for a + * disconnected LVDS encoder there is no native_mode + */ + call_lvds_script(dev, nv_encoder->dcb, head, + LVDS_PANEL_OFF, 0); + } + + nv04_dfp_update_backlight(encoder, mode); + nv04_dfp_update_fp_control(encoder, mode); + + if (mode == DRM_MODE_DPMS_ON) + nv04_dfp_prepare_sel_clk(dev, nv_encoder, nouveau_crtc(crtc)->index); + else { + dev_priv->mode_reg.sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK); + dev_priv->mode_reg.sel_clk &= ~0xf0; + } + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, dev_priv->mode_reg.sel_clk); +} + +static void nv04_tmds_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + if (nv_encoder->last_dpms == mode) + return; + nv_encoder->last_dpms = mode; + + NV_INFO(dev, "Setting dpms mode %d on tmds encoder (output %d)\n", + mode, nv_encoder->dcb->index); + + nv04_dfp_update_backlight(encoder, mode); + nv04_dfp_update_fp_control(encoder, mode); +} + +static void nv04_dfp_save(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + + if (nv_two_heads(dev)) + nv_encoder->restore.head = + nv04_dfp_get_bound_head(dev, nv_encoder->dcb); +} + +static void nv04_dfp_restore(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int head = nv_encoder->restore.head; + + if (nv_encoder->dcb->type == OUTPUT_LVDS) { + struct nouveau_connector *connector = + nouveau_encoder_connector_get(nv_encoder); + + if (connector && connector->native_mode) + call_lvds_script(dev, nv_encoder->dcb, head, + LVDS_PANEL_ON, + connector->native_mode->clock); + + } else if (nv_encoder->dcb->type == OUTPUT_TMDS) { + int clock = nouveau_hw_pllvals_to_clk + (&dev_priv->saved_reg.crtc_reg[head].pllvals); + + run_tmds_table(dev, nv_encoder->dcb, head, clock); + } + + nv_encoder->last_dpms = NV_DPMS_CLEARED; +} + +static void nv04_dfp_destroy(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + NV_DEBUG_KMS(encoder->dev, "\n"); + + if (get_slave_funcs(encoder)) + get_slave_funcs(encoder)->destroy(encoder); + + drm_encoder_cleanup(encoder); + kfree(nv_encoder); +} + +static void nv04_tmds_slave_init(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + struct nouveau_i2c_chan *i2c = nouveau_i2c_find(dev, 2); + struct i2c_board_info info[] = { + { + .type = "sil164", + .addr = (dcb->tmdsconf.slave_addr == 0x7 ? 0x3a : 0x38), + .platform_data = &(struct sil164_encoder_params) { + SIL164_INPUT_EDGE_RISING + } + }, + { } + }; + int type; + + if (!nv_gf4_disp_arch(dev) || !i2c || + get_tmds_slave(encoder)) + return; + + type = nouveau_i2c_identify(dev, "TMDS transmitter", info, NULL, 2); + if (type < 0) + return; + + drm_i2c_encoder_init(dev, to_encoder_slave(encoder), + &i2c->adapter, &info[type]); +} + +static const struct drm_encoder_helper_funcs nv04_lvds_helper_funcs = { + .dpms = nv04_lvds_dpms, + .save = nv04_dfp_save, + .restore = nv04_dfp_restore, + .mode_fixup = nv04_dfp_mode_fixup, + .prepare = nv04_dfp_prepare, + .commit = nv04_dfp_commit, + .mode_set = nv04_dfp_mode_set, + .detect = NULL, +}; + +static const struct drm_encoder_helper_funcs nv04_tmds_helper_funcs = { + .dpms = nv04_tmds_dpms, + .save = nv04_dfp_save, + .restore = nv04_dfp_restore, + .mode_fixup = nv04_dfp_mode_fixup, + .prepare = nv04_dfp_prepare, + .commit = nv04_dfp_commit, + .mode_set = nv04_dfp_mode_set, + .detect = NULL, +}; + +static const struct drm_encoder_funcs nv04_dfp_funcs = { + .destroy = nv04_dfp_destroy, +}; + +int +nv04_dfp_create(struct drm_connector *connector, struct dcb_entry *entry) +{ + const struct drm_encoder_helper_funcs *helper; + struct nouveau_encoder *nv_encoder = NULL; + struct drm_encoder *encoder; + int type; + + switch (entry->type) { + case OUTPUT_TMDS: + type = DRM_MODE_ENCODER_TMDS; + helper = &nv04_tmds_helper_funcs; + break; + case OUTPUT_LVDS: + type = DRM_MODE_ENCODER_LVDS; + helper = &nv04_lvds_helper_funcs; + break; + default: + return -EINVAL; + } + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + + encoder = to_drm_encoder(nv_encoder); + + nv_encoder->dcb = entry; + nv_encoder->or = ffs(entry->or) - 1; + + drm_encoder_init(connector->dev, encoder, &nv04_dfp_funcs, type); + drm_encoder_helper_add(encoder, helper); + + encoder->possible_crtcs = entry->heads; + encoder->possible_clones = 0; + + if (entry->type == OUTPUT_TMDS && + entry->location != DCB_LOC_ON_CHIP) + nv04_tmds_slave_init(encoder); + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv04_display.c b/drivers/gpu/drm/nouveau/nv04_display.c new file mode 100644 index 00000000..7047d37e --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_display.c @@ -0,0 +1,263 @@ +/* + * Copyright 2009 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Author: Ben Skeggs + */ + +#include "drmP.h" +#include "drm.h" +#include "drm_crtc_helper.h" + +#include "nouveau_drv.h" +#include "nouveau_fb.h" +#include "nouveau_hw.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" + +static void nv04_vblank_crtc0_isr(struct drm_device *); +static void nv04_vblank_crtc1_isr(struct drm_device *); + +static void +nv04_display_store_initial_head_owner(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset != 0x11) { + dev_priv->crtc_owner = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_44); + return; + } + + /* reading CR44 is broken on nv11, so we attempt to infer it */ + if (nvReadMC(dev, NV_PBUS_DEBUG_1) & (1 << 28)) /* heads tied, restore both */ + dev_priv->crtc_owner = 0x4; + else { + uint8_t slaved_on_A, slaved_on_B; + bool tvA = false; + bool tvB = false; + + slaved_on_B = NVReadVgaCrtc(dev, 1, NV_CIO_CRE_PIXEL_INDEX) & + 0x80; + if (slaved_on_B) + tvB = !(NVReadVgaCrtc(dev, 1, NV_CIO_CRE_LCD__INDEX) & + MASK(NV_CIO_CRE_LCD_LCD_SELECT)); + + slaved_on_A = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX) & + 0x80; + if (slaved_on_A) + tvA = !(NVReadVgaCrtc(dev, 0, NV_CIO_CRE_LCD__INDEX) & + MASK(NV_CIO_CRE_LCD_LCD_SELECT)); + + if (slaved_on_A && !tvA) + dev_priv->crtc_owner = 0x0; + else if (slaved_on_B && !tvB) + dev_priv->crtc_owner = 0x3; + else if (slaved_on_A) + dev_priv->crtc_owner = 0x0; + else if (slaved_on_B) + dev_priv->crtc_owner = 0x3; + else + dev_priv->crtc_owner = 0x0; + } +} + +int +nv04_display_early_init(struct drm_device *dev) +{ + /* Make the I2C buses accessible. */ + if (!nv_gf4_disp_arch(dev)) { + uint32_t pmc_enable = nv_rd32(dev, NV03_PMC_ENABLE); + + if (!(pmc_enable & 1)) + nv_wr32(dev, NV03_PMC_ENABLE, pmc_enable | 1); + } + + /* Unlock the VGA CRTCs. */ + NVLockVgaCrtcs(dev, false); + + /* Make sure the CRTCs aren't in slaved mode. */ + if (nv_two_heads(dev)) { + nv04_display_store_initial_head_owner(dev); + NVSetOwner(dev, 0); + } + + return 0; +} + +void +nv04_display_late_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (nv_two_heads(dev)) + NVSetOwner(dev, dev_priv->crtc_owner); + + NVLockVgaCrtcs(dev, true); +} + +int +nv04_display_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_table *dcb = &dev_priv->vbios.dcb; + struct drm_connector *connector, *ct; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + int i, ret; + + NV_DEBUG_KMS(dev, "\n"); + + nouveau_hw_save_vga_fonts(dev, 1); + + nv04_crtc_create(dev, 0); + if (nv_two_heads(dev)) + nv04_crtc_create(dev, 1); + + for (i = 0; i < dcb->entries; i++) { + struct dcb_entry *dcbent = &dcb->entry[i]; + + connector = nouveau_connector_create(dev, dcbent->connector); + if (IS_ERR(connector)) + continue; + + switch (dcbent->type) { + case OUTPUT_ANALOG: + ret = nv04_dac_create(connector, dcbent); + break; + case OUTPUT_LVDS: + case OUTPUT_TMDS: + ret = nv04_dfp_create(connector, dcbent); + break; + case OUTPUT_TV: + if (dcbent->location == DCB_LOC_ON_CHIP) + ret = nv17_tv_create(connector, dcbent); + else + ret = nv04_tv_create(connector, dcbent); + break; + default: + NV_WARN(dev, "DCB type %d not known\n", dcbent->type); + continue; + } + + if (ret) + continue; + } + + list_for_each_entry_safe(connector, ct, + &dev->mode_config.connector_list, head) { + if (!connector->encoder_ids[0]) { + NV_WARN(dev, "%s has no encoders, removing\n", + drm_get_connector_name(connector)); + connector->funcs->destroy(connector); + } + } + + /* Save previous state */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + crtc->funcs->save(crtc); + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct drm_encoder_helper_funcs *func = encoder->helper_private; + + func->save(encoder); + } + + nouveau_irq_register(dev, 24, nv04_vblank_crtc0_isr); + nouveau_irq_register(dev, 25, nv04_vblank_crtc1_isr); + return 0; +} + +void +nv04_display_destroy(struct drm_device *dev) +{ + struct drm_encoder *encoder; + struct drm_crtc *crtc; + + NV_DEBUG_KMS(dev, "\n"); + + nouveau_irq_unregister(dev, 24); + nouveau_irq_unregister(dev, 25); + + /* Turn every CRTC off. */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct drm_mode_set modeset = { + .crtc = crtc, + }; + + crtc->funcs->set_config(&modeset); + } + + /* Restore state */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct drm_encoder_helper_funcs *func = encoder->helper_private; + + func->restore(encoder); + } + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + crtc->funcs->restore(crtc); + + nouveau_hw_save_vga_fonts(dev, 0); +} + +int +nv04_display_init(struct drm_device *dev) +{ + struct drm_encoder *encoder; + struct drm_crtc *crtc; + + /* meh.. modeset apparently doesn't setup all the regs and depends + * on pre-existing state, for now load the state of the card *before* + * nouveau was loaded, and then do a modeset. + * + * best thing to do probably is to make save/restore routines not + * save/restore "pre-load" state, but more general so we can save + * on suspend too. + */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct drm_encoder_helper_funcs *func = encoder->helper_private; + + func->restore(encoder); + } + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + crtc->funcs->restore(crtc); + + return 0; +} + +void +nv04_display_fini(struct drm_device *dev) +{ +} + +static void +nv04_vblank_crtc0_isr(struct drm_device *dev) +{ + nv_wr32(dev, NV_CRTC0_INTSTAT, NV_CRTC_INTR_VBLANK); + drm_handle_vblank(dev, 0); +} + +static void +nv04_vblank_crtc1_isr(struct drm_device *dev) +{ + nv_wr32(dev, NV_CRTC1_INTSTAT, NV_CRTC_INTR_VBLANK); + drm_handle_vblank(dev, 1); +} diff --git a/drivers/gpu/drm/nouveau/nv04_fb.c b/drivers/gpu/drm/nouveau/nv04_fb.c new file mode 100644 index 00000000..d5eedd67 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_fb.c @@ -0,0 +1,55 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +int +nv04_fb_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 boot0 = nv_rd32(dev, NV04_PFB_BOOT_0); + + if (boot0 & 0x00000100) { + dev_priv->vram_size = ((boot0 >> 12) & 0xf) * 2 + 2; + dev_priv->vram_size *= 1024 * 1024; + } else { + switch (boot0 & NV04_PFB_BOOT_0_RAM_AMOUNT) { + case NV04_PFB_BOOT_0_RAM_AMOUNT_32MB: + dev_priv->vram_size = 32 * 1024 * 1024; + break; + case NV04_PFB_BOOT_0_RAM_AMOUNT_16MB: + dev_priv->vram_size = 16 * 1024 * 1024; + break; + case NV04_PFB_BOOT_0_RAM_AMOUNT_8MB: + dev_priv->vram_size = 8 * 1024 * 1024; + break; + case NV04_PFB_BOOT_0_RAM_AMOUNT_4MB: + dev_priv->vram_size = 4 * 1024 * 1024; + break; + } + } + + if ((boot0 & 0x00000038) <= 0x10) + dev_priv->vram_type = NV_MEM_TYPE_SGRAM; + else + dev_priv->vram_type = NV_MEM_TYPE_SDRAM; + + return 0; +} + +int +nv04_fb_init(struct drm_device *dev) +{ + /* This is what the DDX did for NV_ARCH_04, but a mmio-trace shows + * nvidia reading PFB_CFG_0, then writing back its original value. + * (which was 0x701114 in this case) + */ + + nv_wr32(dev, NV04_PFB_CFG0, 0x1114); + return 0; +} + +void +nv04_fb_takedown(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv04_fbcon.c b/drivers/gpu/drm/nouveau/nv04_fbcon.c new file mode 100644 index 00000000..7a118937 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_fbcon.c @@ -0,0 +1,275 @@ +/* + * Copyright 2009 Ben Skeggs + * Copyright 2008 Stuart Bennett + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_ramht.h" +#include "nouveau_fbcon.h" + +int +nv04_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + int ret; + + ret = RING_SPACE(chan, 4); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubImageBlit, 0x0300, 3); + OUT_RING(chan, (region->sy << 16) | region->sx); + OUT_RING(chan, (region->dy << 16) | region->dx); + OUT_RING(chan, (region->height << 16) | region->width); + FIRE_RING(chan); + return 0; +} + +int +nv04_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + int ret; + + ret = RING_SPACE(chan, 7); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubGdiRect, 0x02fc, 1); + OUT_RING(chan, (rect->rop != ROP_COPY) ? 1 : 3); + BEGIN_RING(chan, NvSubGdiRect, 0x03fc, 1); + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) + OUT_RING(chan, ((uint32_t *)info->pseudo_palette)[rect->color]); + else + OUT_RING(chan, rect->color); + BEGIN_RING(chan, NvSubGdiRect, 0x0400, 2); + OUT_RING(chan, (rect->dx << 16) | rect->dy); + OUT_RING(chan, (rect->width << 16) | rect->height); + FIRE_RING(chan); + return 0; +} + +int +nv04_fbcon_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + uint32_t fg; + uint32_t bg; + uint32_t dsize; + uint32_t width; + uint32_t *data = (uint32_t *)image->data; + int ret; + + if (image->depth != 1) + return -ENODEV; + + ret = RING_SPACE(chan, 8); + if (ret) + return ret; + + width = ALIGN(image->width, 8); + dsize = ALIGN(width * image->height, 32) >> 5; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + fg = ((uint32_t *) info->pseudo_palette)[image->fg_color]; + bg = ((uint32_t *) info->pseudo_palette)[image->bg_color]; + } else { + fg = image->fg_color; + bg = image->bg_color; + } + + BEGIN_RING(chan, NvSubGdiRect, 0x0be4, 7); + OUT_RING(chan, (image->dy << 16) | (image->dx & 0xffff)); + OUT_RING(chan, ((image->dy + image->height) << 16) | + ((image->dx + image->width) & 0xffff)); + OUT_RING(chan, bg); + OUT_RING(chan, fg); + OUT_RING(chan, (image->height << 16) | width); + OUT_RING(chan, (image->height << 16) | image->width); + OUT_RING(chan, (image->dy << 16) | (image->dx & 0xffff)); + + while (dsize) { + int iter_len = dsize > 128 ? 128 : dsize; + + ret = RING_SPACE(chan, iter_len + 1); + if (ret) + return ret; + + BEGIN_RING(chan, NvSubGdiRect, 0x0c00, iter_len); + OUT_RINGp(chan, data, iter_len); + data += iter_len; + dsize -= iter_len; + } + + FIRE_RING(chan); + return 0; +} + +int +nv04_fbcon_accel_init(struct fb_info *info) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + const int sub = NvSubCtxSurf2D; + int surface_fmt, pattern_fmt, rect_fmt; + int ret; + + switch (info->var.bits_per_pixel) { + case 8: + surface_fmt = 1; + pattern_fmt = 3; + rect_fmt = 3; + break; + case 16: + surface_fmt = 4; + pattern_fmt = 1; + rect_fmt = 1; + break; + case 32: + switch (info->var.transp.length) { + case 0: /* depth 24 */ + case 8: /* depth 32 */ + break; + default: + return -EINVAL; + } + + surface_fmt = 6; + pattern_fmt = 3; + rect_fmt = 3; + break; + default: + return -EINVAL; + } + + ret = nouveau_gpuobj_gr_new(chan, NvCtxSurf2D, + dev_priv->card_type >= NV_10 ? + 0x0062 : 0x0042); + if (ret) + return ret; + + ret = nouveau_gpuobj_gr_new(chan, NvClipRect, 0x0019); + if (ret) + return ret; + + ret = nouveau_gpuobj_gr_new(chan, NvRop, 0x0043); + if (ret) + return ret; + + ret = nouveau_gpuobj_gr_new(chan, NvImagePatt, 0x0044); + if (ret) + return ret; + + ret = nouveau_gpuobj_gr_new(chan, NvGdiRect, 0x004a); + if (ret) + return ret; + + ret = nouveau_gpuobj_gr_new(chan, NvImageBlit, + dev_priv->chipset >= 0x11 ? + 0x009f : 0x005f); + if (ret) + return ret; + + if (RING_SPACE(chan, 49)) { + nouveau_fbcon_gpu_lockup(info); + return 0; + } + + BEGIN_RING(chan, sub, 0x0000, 1); + OUT_RING(chan, NvCtxSurf2D); + BEGIN_RING(chan, sub, 0x0184, 2); + OUT_RING(chan, NvDmaFB); + OUT_RING(chan, NvDmaFB); + BEGIN_RING(chan, sub, 0x0300, 4); + OUT_RING(chan, surface_fmt); + OUT_RING(chan, info->fix.line_length | (info->fix.line_length << 16)); + OUT_RING(chan, info->fix.smem_start - dev->mode_config.fb_base); + OUT_RING(chan, info->fix.smem_start - dev->mode_config.fb_base); + + BEGIN_RING(chan, sub, 0x0000, 1); + OUT_RING(chan, NvRop); + BEGIN_RING(chan, sub, 0x0300, 1); + OUT_RING(chan, 0x55); + + BEGIN_RING(chan, sub, 0x0000, 1); + OUT_RING(chan, NvImagePatt); + BEGIN_RING(chan, sub, 0x0300, 8); + OUT_RING(chan, pattern_fmt); +#ifdef __BIG_ENDIAN + OUT_RING(chan, 2); +#else + OUT_RING(chan, 1); +#endif + OUT_RING(chan, 0); + OUT_RING(chan, 1); + OUT_RING(chan, ~0); + OUT_RING(chan, ~0); + OUT_RING(chan, ~0); + OUT_RING(chan, ~0); + + BEGIN_RING(chan, sub, 0x0000, 1); + OUT_RING(chan, NvClipRect); + BEGIN_RING(chan, sub, 0x0300, 2); + OUT_RING(chan, 0); + OUT_RING(chan, (info->var.yres_virtual << 16) | info->var.xres_virtual); + + BEGIN_RING(chan, NvSubImageBlit, 0x0000, 1); + OUT_RING(chan, NvImageBlit); + BEGIN_RING(chan, NvSubImageBlit, 0x019c, 1); + OUT_RING(chan, NvCtxSurf2D); + BEGIN_RING(chan, NvSubImageBlit, 0x02fc, 1); + OUT_RING(chan, 3); + + BEGIN_RING(chan, NvSubGdiRect, 0x0000, 1); + OUT_RING(chan, NvGdiRect); + BEGIN_RING(chan, NvSubGdiRect, 0x0198, 1); + OUT_RING(chan, NvCtxSurf2D); + BEGIN_RING(chan, NvSubGdiRect, 0x0188, 2); + OUT_RING(chan, NvImagePatt); + OUT_RING(chan, NvRop); + BEGIN_RING(chan, NvSubGdiRect, 0x0304, 1); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSubGdiRect, 0x0300, 1); + OUT_RING(chan, rect_fmt); + BEGIN_RING(chan, NvSubGdiRect, 0x02fc, 1); + OUT_RING(chan, 3); + + FIRE_RING(chan); + + return 0; +} + diff --git a/drivers/gpu/drm/nouveau/nv04_fifo.c b/drivers/gpu/drm/nouveau/nv04_fifo.c new file mode 100644 index 00000000..db465a3e --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_fifo.c @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" +#include "nouveau_util.h" + +#define NV04_RAMFC(c) (dev_priv->ramfc->pinst + ((c) * NV04_RAMFC__SIZE)) +#define NV04_RAMFC__SIZE 32 +#define NV04_RAMFC_DMA_PUT 0x00 +#define NV04_RAMFC_DMA_GET 0x04 +#define NV04_RAMFC_DMA_INSTANCE 0x08 +#define NV04_RAMFC_DMA_STATE 0x0C +#define NV04_RAMFC_DMA_FETCH 0x10 +#define NV04_RAMFC_ENGINE 0x14 +#define NV04_RAMFC_PULL1_ENGINE 0x18 + +#define RAMFC_WR(offset, val) nv_wo32(chan->ramfc, NV04_RAMFC_##offset, (val)) +#define RAMFC_RD(offset) nv_ro32(chan->ramfc, NV04_RAMFC_##offset) + +void +nv04_fifo_disable(struct drm_device *dev) +{ + uint32_t tmp; + + tmp = nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_PUSH); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUSH, tmp & ~1); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH0, 0); + tmp = nv_rd32(dev, NV03_PFIFO_CACHE1_PULL1); + nv_wr32(dev, NV04_PFIFO_CACHE1_PULL0, tmp & ~1); +} + +void +nv04_fifo_enable(struct drm_device *dev) +{ + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH0, 1); + nv_wr32(dev, NV04_PFIFO_CACHE1_PULL0, 1); +} + +bool +nv04_fifo_reassign(struct drm_device *dev, bool enable) +{ + uint32_t reassign = nv_rd32(dev, NV03_PFIFO_CACHES); + + nv_wr32(dev, NV03_PFIFO_CACHES, enable ? 1 : 0); + return (reassign == 1); +} + +bool +nv04_fifo_cache_pull(struct drm_device *dev, bool enable) +{ + int pull = nv_mask(dev, NV04_PFIFO_CACHE1_PULL0, 1, enable); + + if (!enable) { + /* In some cases the PFIFO puller may be left in an + * inconsistent state if you try to stop it when it's + * busy translating handles. Sometimes you get a + * PFIFO_CACHE_ERROR, sometimes it just fails silently + * sending incorrect instance offsets to PGRAPH after + * it's started up again. To avoid the latter we + * invalidate the most recently calculated instance. + */ + if (!nv_wait(dev, NV04_PFIFO_CACHE1_PULL0, + NV04_PFIFO_CACHE1_PULL0_HASH_BUSY, 0)) + NV_ERROR(dev, "Timeout idling the PFIFO puller.\n"); + + if (nv_rd32(dev, NV04_PFIFO_CACHE1_PULL0) & + NV04_PFIFO_CACHE1_PULL0_HASH_FAILED) + nv_wr32(dev, NV03_PFIFO_INTR_0, + NV_PFIFO_INTR_CACHE_ERROR); + + nv_wr32(dev, NV04_PFIFO_CACHE1_HASH, 0); + } + + return pull & 1; +} + +int +nv04_fifo_channel_id(struct drm_device *dev) +{ + return nv_rd32(dev, NV03_PFIFO_CACHE1_PUSH1) & + NV03_PFIFO_CACHE1_PUSH1_CHID_MASK; +} + +#ifdef __BIG_ENDIAN +#define DMA_FETCH_ENDIANNESS NV_PFIFO_CACHE1_BIG_ENDIAN +#else +#define DMA_FETCH_ENDIANNESS 0 +#endif + +int +nv04_fifo_create_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + int ret; + + ret = nouveau_gpuobj_new_fake(dev, NV04_RAMFC(chan->id), ~0, + NV04_RAMFC__SIZE, + NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, + &chan->ramfc); + if (ret) + return ret; + + chan->user = ioremap(pci_resource_start(dev->pdev, 0) + + NV03_USER(chan->id), PAGE_SIZE); + if (!chan->user) + return -ENOMEM; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + + /* Setup initial state */ + RAMFC_WR(DMA_PUT, chan->pushbuf_base); + RAMFC_WR(DMA_GET, chan->pushbuf_base); + RAMFC_WR(DMA_INSTANCE, chan->pushbuf->pinst >> 4); + RAMFC_WR(DMA_FETCH, (NV_PFIFO_CACHE1_DMA_FETCH_TRIG_128_BYTES | + NV_PFIFO_CACHE1_DMA_FETCH_SIZE_128_BYTES | + NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_8 | + DMA_FETCH_ENDIANNESS)); + + /* enable the fifo dma operation */ + nv_wr32(dev, NV04_PFIFO_MODE, + nv_rd32(dev, NV04_PFIFO_MODE) | (1 << chan->id)); + + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + return 0; +} + +void +nv04_fifo_destroy_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + pfifo->reassign(dev, false); + + /* Unload the context if it's the currently active one */ + if (pfifo->channel_id(dev) == chan->id) { + pfifo->disable(dev); + pfifo->unload_context(dev); + pfifo->enable(dev); + } + + /* Keep it from being rescheduled */ + nv_mask(dev, NV04_PFIFO_MODE, 1 << chan->id, 0); + + pfifo->reassign(dev, true); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + /* Free the channel resources */ + if (chan->user) { + iounmap(chan->user); + chan->user = NULL; + } + nouveau_gpuobj_ref(NULL, &chan->ramfc); +} + +static void +nv04_fifo_do_load_context(struct drm_device *dev, int chid) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t fc = NV04_RAMFC(chid), tmp; + + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUT, nv_ri32(dev, fc + 0)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_GET, nv_ri32(dev, fc + 4)); + tmp = nv_ri32(dev, fc + 8); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_INSTANCE, tmp & 0xFFFF); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_DCOUNT, tmp >> 16); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_STATE, nv_ri32(dev, fc + 12)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_FETCH, nv_ri32(dev, fc + 16)); + nv_wr32(dev, NV04_PFIFO_CACHE1_ENGINE, nv_ri32(dev, fc + 20)); + nv_wr32(dev, NV04_PFIFO_CACHE1_PULL1, nv_ri32(dev, fc + 24)); + + nv_wr32(dev, NV03_PFIFO_CACHE1_GET, 0); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUT, 0); +} + +int +nv04_fifo_load_context(struct nouveau_channel *chan) +{ + uint32_t tmp; + + nv_wr32(chan->dev, NV03_PFIFO_CACHE1_PUSH1, + NV03_PFIFO_CACHE1_PUSH1_DMA | chan->id); + nv04_fifo_do_load_context(chan->dev, chan->id); + nv_wr32(chan->dev, NV04_PFIFO_CACHE1_DMA_PUSH, 1); + + /* Reset NV04_PFIFO_CACHE1_DMA_CTL_AT_INFO to INVALID */ + tmp = nv_rd32(chan->dev, NV04_PFIFO_CACHE1_DMA_CTL) & ~(1 << 31); + nv_wr32(chan->dev, NV04_PFIFO_CACHE1_DMA_CTL, tmp); + + return 0; +} + +int +nv04_fifo_unload_context(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_channel *chan = NULL; + uint32_t tmp; + int chid; + + chid = pfifo->channel_id(dev); + if (chid < 0 || chid >= dev_priv->engine.fifo.channels) + return 0; + + chan = dev_priv->channels.ptr[chid]; + if (!chan) { + NV_ERROR(dev, "Inactive channel on PFIFO: %d\n", chid); + return -EINVAL; + } + + RAMFC_WR(DMA_PUT, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_PUT)); + RAMFC_WR(DMA_GET, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_GET)); + tmp = nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_DCOUNT) << 16; + tmp |= nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_INSTANCE); + RAMFC_WR(DMA_INSTANCE, tmp); + RAMFC_WR(DMA_STATE, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_STATE)); + RAMFC_WR(DMA_FETCH, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_FETCH)); + RAMFC_WR(ENGINE, nv_rd32(dev, NV04_PFIFO_CACHE1_ENGINE)); + RAMFC_WR(PULL1_ENGINE, nv_rd32(dev, NV04_PFIFO_CACHE1_PULL1)); + + nv04_fifo_do_load_context(dev, pfifo->channels - 1); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, pfifo->channels - 1); + return 0; +} + +static void +nv04_fifo_init_reset(struct drm_device *dev) +{ + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) & ~NV_PMC_ENABLE_PFIFO); + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) | NV_PMC_ENABLE_PFIFO); + + nv_wr32(dev, 0x003224, 0x000f0078); + nv_wr32(dev, 0x002044, 0x0101ffff); + nv_wr32(dev, 0x002040, 0x000000ff); + nv_wr32(dev, 0x002500, 0x00000000); + nv_wr32(dev, 0x003000, 0x00000000); + nv_wr32(dev, 0x003050, 0x00000000); + nv_wr32(dev, 0x003200, 0x00000000); + nv_wr32(dev, 0x003250, 0x00000000); + nv_wr32(dev, 0x003220, 0x00000000); + + nv_wr32(dev, 0x003250, 0x00000000); + nv_wr32(dev, 0x003270, 0x00000000); + nv_wr32(dev, 0x003210, 0x00000000); +} + +static void +nv04_fifo_init_ramxx(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nv_wr32(dev, NV03_PFIFO_RAMHT, (0x03 << 24) /* search 128 */ | + ((dev_priv->ramht->bits - 9) << 16) | + (dev_priv->ramht->gpuobj->pinst >> 8)); + nv_wr32(dev, NV03_PFIFO_RAMRO, dev_priv->ramro->pinst >> 8); + nv_wr32(dev, NV03_PFIFO_RAMFC, dev_priv->ramfc->pinst >> 8); +} + +static void +nv04_fifo_init_intr(struct drm_device *dev) +{ + nouveau_irq_register(dev, 8, nv04_fifo_isr); + nv_wr32(dev, 0x002100, 0xffffffff); + nv_wr32(dev, 0x002140, 0xffffffff); +} + +int +nv04_fifo_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + int i; + + nv04_fifo_init_reset(dev); + nv04_fifo_init_ramxx(dev); + + nv04_fifo_do_load_context(dev, pfifo->channels - 1); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, pfifo->channels - 1); + + nv04_fifo_init_intr(dev); + pfifo->enable(dev); + pfifo->reassign(dev, true); + + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + if (dev_priv->channels.ptr[i]) { + uint32_t mode = nv_rd32(dev, NV04_PFIFO_MODE); + nv_wr32(dev, NV04_PFIFO_MODE, mode | (1 << i)); + } + } + + return 0; +} + +void +nv04_fifo_fini(struct drm_device *dev) +{ + nv_wr32(dev, 0x2140, 0x00000000); + nouveau_irq_unregister(dev, 8); +} + +static bool +nouveau_fifo_swmthd(struct drm_device *dev, u32 chid, u32 addr, u32 data) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = NULL; + struct nouveau_gpuobj *obj; + unsigned long flags; + const int subc = (addr >> 13) & 0x7; + const int mthd = addr & 0x1ffc; + bool handled = false; + u32 engine; + + spin_lock_irqsave(&dev_priv->channels.lock, flags); + if (likely(chid >= 0 && chid < dev_priv->engine.fifo.channels)) + chan = dev_priv->channels.ptr[chid]; + if (unlikely(!chan)) + goto out; + + switch (mthd) { + case 0x0000: /* bind object to subchannel */ + obj = nouveau_ramht_find(chan, data); + if (unlikely(!obj || obj->engine != NVOBJ_ENGINE_SW)) + break; + + chan->sw_subchannel[subc] = obj->class; + engine = 0x0000000f << (subc * 4); + + nv_mask(dev, NV04_PFIFO_CACHE1_ENGINE, engine, 0x00000000); + handled = true; + break; + default: + engine = nv_rd32(dev, NV04_PFIFO_CACHE1_ENGINE); + if (unlikely(((engine >> (subc * 4)) & 0xf) != 0)) + break; + + if (!nouveau_gpuobj_mthd_call(chan, chan->sw_subchannel[subc], + mthd, data)) + handled = true; + break; + } + +out: + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + return handled; +} + +static const char *nv_dma_state_err(u32 state) +{ + static const char * const desc[] = { + "NONE", "CALL_SUBR_ACTIVE", "INVALID_MTHD", "RET_SUBR_INACTIVE", + "INVALID_CMD", "IB_EMPTY"/* NV50+ */, "MEM_FAULT", "UNK" + }; + return desc[(state >> 29) & 0x7]; +} + +void +nv04_fifo_isr(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_engine *engine = &dev_priv->engine; + uint32_t status, reassign; + int cnt = 0; + + reassign = nv_rd32(dev, NV03_PFIFO_CACHES) & 1; + while ((status = nv_rd32(dev, NV03_PFIFO_INTR_0)) && (cnt++ < 100)) { + uint32_t chid, get; + + nv_wr32(dev, NV03_PFIFO_CACHES, 0); + + chid = engine->fifo.channel_id(dev); + get = nv_rd32(dev, NV03_PFIFO_CACHE1_GET); + + if (status & NV_PFIFO_INTR_CACHE_ERROR) { + uint32_t mthd, data; + int ptr; + + /* NV_PFIFO_CACHE1_GET actually goes to 0xffc before + * wrapping on my G80 chips, but CACHE1 isn't big + * enough for this much data.. Tests show that it + * wraps around to the start at GET=0x800.. No clue + * as to why.. + */ + ptr = (get & 0x7ff) >> 2; + + if (dev_priv->card_type < NV_40) { + mthd = nv_rd32(dev, + NV04_PFIFO_CACHE1_METHOD(ptr)); + data = nv_rd32(dev, + NV04_PFIFO_CACHE1_DATA(ptr)); + } else { + mthd = nv_rd32(dev, + NV40_PFIFO_CACHE1_METHOD(ptr)); + data = nv_rd32(dev, + NV40_PFIFO_CACHE1_DATA(ptr)); + } + + if (!nouveau_fifo_swmthd(dev, chid, mthd, data)) { + NV_INFO(dev, "PFIFO_CACHE_ERROR - Ch %d/%d " + "Mthd 0x%04x Data 0x%08x\n", + chid, (mthd >> 13) & 7, mthd & 0x1ffc, + data); + } + + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUSH, 0); + nv_wr32(dev, NV03_PFIFO_INTR_0, + NV_PFIFO_INTR_CACHE_ERROR); + + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH0, + nv_rd32(dev, NV03_PFIFO_CACHE1_PUSH0) & ~1); + nv_wr32(dev, NV03_PFIFO_CACHE1_GET, get + 4); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH0, + nv_rd32(dev, NV03_PFIFO_CACHE1_PUSH0) | 1); + nv_wr32(dev, NV04_PFIFO_CACHE1_HASH, 0); + + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUSH, + nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_PUSH) | 1); + nv_wr32(dev, NV04_PFIFO_CACHE1_PULL0, 1); + + status &= ~NV_PFIFO_INTR_CACHE_ERROR; + } + + if (status & NV_PFIFO_INTR_DMA_PUSHER) { + u32 dma_get = nv_rd32(dev, 0x003244); + u32 dma_put = nv_rd32(dev, 0x003240); + u32 push = nv_rd32(dev, 0x003220); + u32 state = nv_rd32(dev, 0x003228); + + if (dev_priv->card_type == NV_50) { + u32 ho_get = nv_rd32(dev, 0x003328); + u32 ho_put = nv_rd32(dev, 0x003320); + u32 ib_get = nv_rd32(dev, 0x003334); + u32 ib_put = nv_rd32(dev, 0x003330); + + if (nouveau_ratelimit()) + NV_INFO(dev, "PFIFO_DMA_PUSHER - Ch %d Get 0x%02x%08x " + "Put 0x%02x%08x IbGet 0x%08x IbPut 0x%08x " + "State 0x%08x (err: %s) Push 0x%08x\n", + chid, ho_get, dma_get, ho_put, + dma_put, ib_get, ib_put, state, + nv_dma_state_err(state), + push); + + /* METHOD_COUNT, in DMA_STATE on earlier chipsets */ + nv_wr32(dev, 0x003364, 0x00000000); + if (dma_get != dma_put || ho_get != ho_put) { + nv_wr32(dev, 0x003244, dma_put); + nv_wr32(dev, 0x003328, ho_put); + } else + if (ib_get != ib_put) { + nv_wr32(dev, 0x003334, ib_put); + } + } else { + NV_INFO(dev, "PFIFO_DMA_PUSHER - Ch %d Get 0x%08x " + "Put 0x%08x State 0x%08x (err: %s) Push 0x%08x\n", + chid, dma_get, dma_put, state, + nv_dma_state_err(state), push); + + if (dma_get != dma_put) + nv_wr32(dev, 0x003244, dma_put); + } + + nv_wr32(dev, 0x003228, 0x00000000); + nv_wr32(dev, 0x003220, 0x00000001); + nv_wr32(dev, 0x002100, NV_PFIFO_INTR_DMA_PUSHER); + status &= ~NV_PFIFO_INTR_DMA_PUSHER; + } + + if (status & NV_PFIFO_INTR_SEMAPHORE) { + uint32_t sem; + + status &= ~NV_PFIFO_INTR_SEMAPHORE; + nv_wr32(dev, NV03_PFIFO_INTR_0, + NV_PFIFO_INTR_SEMAPHORE); + + sem = nv_rd32(dev, NV10_PFIFO_CACHE1_SEMAPHORE); + nv_wr32(dev, NV10_PFIFO_CACHE1_SEMAPHORE, sem | 0x1); + + nv_wr32(dev, NV03_PFIFO_CACHE1_GET, get + 4); + nv_wr32(dev, NV04_PFIFO_CACHE1_PULL0, 1); + } + + if (dev_priv->card_type == NV_50) { + if (status & 0x00000010) { + nv50_fb_vm_trap(dev, nouveau_ratelimit()); + status &= ~0x00000010; + nv_wr32(dev, 0x002100, 0x00000010); + } + } + + if (status) { + if (nouveau_ratelimit()) + NV_INFO(dev, "PFIFO_INTR 0x%08x - Ch %d\n", + status, chid); + nv_wr32(dev, NV03_PFIFO_INTR_0, status); + status = 0; + } + + nv_wr32(dev, NV03_PFIFO_CACHES, reassign); + } + + if (status) { + NV_INFO(dev, "PFIFO still angry after %d spins, halt\n", cnt); + nv_wr32(dev, 0x2140, 0); + nv_wr32(dev, 0x140, 0); + } + + nv_wr32(dev, NV03_PMC_INTR_0, NV_PMC_INTR_0_PFIFO_PENDING); +} diff --git a/drivers/gpu/drm/nouveau/nv04_graph.c b/drivers/gpu/drm/nouveau/nv04_graph.c new file mode 100644 index 00000000..dbdea8ed --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_graph.c @@ -0,0 +1,1353 @@ +/* + * Copyright 2007 Stephane Marchesin + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drm.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" +#include "nouveau_util.h" +#include "nouveau_ramht.h" + +struct nv04_graph_engine { + struct nouveau_exec_engine base; +}; + +static uint32_t nv04_graph_ctx_regs[] = { + 0x0040053c, + 0x00400544, + 0x00400540, + 0x00400548, + NV04_PGRAPH_CTX_SWITCH1, + NV04_PGRAPH_CTX_SWITCH2, + NV04_PGRAPH_CTX_SWITCH3, + NV04_PGRAPH_CTX_SWITCH4, + NV04_PGRAPH_CTX_CACHE1, + NV04_PGRAPH_CTX_CACHE2, + NV04_PGRAPH_CTX_CACHE3, + NV04_PGRAPH_CTX_CACHE4, + 0x00400184, + 0x004001a4, + 0x004001c4, + 0x004001e4, + 0x00400188, + 0x004001a8, + 0x004001c8, + 0x004001e8, + 0x0040018c, + 0x004001ac, + 0x004001cc, + 0x004001ec, + 0x00400190, + 0x004001b0, + 0x004001d0, + 0x004001f0, + 0x00400194, + 0x004001b4, + 0x004001d4, + 0x004001f4, + 0x00400198, + 0x004001b8, + 0x004001d8, + 0x004001f8, + 0x0040019c, + 0x004001bc, + 0x004001dc, + 0x004001fc, + 0x00400174, + NV04_PGRAPH_DMA_START_0, + NV04_PGRAPH_DMA_START_1, + NV04_PGRAPH_DMA_LENGTH, + NV04_PGRAPH_DMA_MISC, + NV04_PGRAPH_DMA_PITCH, + NV04_PGRAPH_BOFFSET0, + NV04_PGRAPH_BBASE0, + NV04_PGRAPH_BLIMIT0, + NV04_PGRAPH_BOFFSET1, + NV04_PGRAPH_BBASE1, + NV04_PGRAPH_BLIMIT1, + NV04_PGRAPH_BOFFSET2, + NV04_PGRAPH_BBASE2, + NV04_PGRAPH_BLIMIT2, + NV04_PGRAPH_BOFFSET3, + NV04_PGRAPH_BBASE3, + NV04_PGRAPH_BLIMIT3, + NV04_PGRAPH_BOFFSET4, + NV04_PGRAPH_BBASE4, + NV04_PGRAPH_BLIMIT4, + NV04_PGRAPH_BOFFSET5, + NV04_PGRAPH_BBASE5, + NV04_PGRAPH_BLIMIT5, + NV04_PGRAPH_BPITCH0, + NV04_PGRAPH_BPITCH1, + NV04_PGRAPH_BPITCH2, + NV04_PGRAPH_BPITCH3, + NV04_PGRAPH_BPITCH4, + NV04_PGRAPH_SURFACE, + NV04_PGRAPH_STATE, + NV04_PGRAPH_BSWIZZLE2, + NV04_PGRAPH_BSWIZZLE5, + NV04_PGRAPH_BPIXEL, + NV04_PGRAPH_NOTIFY, + NV04_PGRAPH_PATT_COLOR0, + NV04_PGRAPH_PATT_COLOR1, + NV04_PGRAPH_PATT_COLORRAM+0x00, + NV04_PGRAPH_PATT_COLORRAM+0x04, + NV04_PGRAPH_PATT_COLORRAM+0x08, + NV04_PGRAPH_PATT_COLORRAM+0x0c, + NV04_PGRAPH_PATT_COLORRAM+0x10, + NV04_PGRAPH_PATT_COLORRAM+0x14, + NV04_PGRAPH_PATT_COLORRAM+0x18, + NV04_PGRAPH_PATT_COLORRAM+0x1c, + NV04_PGRAPH_PATT_COLORRAM+0x20, + NV04_PGRAPH_PATT_COLORRAM+0x24, + NV04_PGRAPH_PATT_COLORRAM+0x28, + NV04_PGRAPH_PATT_COLORRAM+0x2c, + NV04_PGRAPH_PATT_COLORRAM+0x30, + NV04_PGRAPH_PATT_COLORRAM+0x34, + NV04_PGRAPH_PATT_COLORRAM+0x38, + NV04_PGRAPH_PATT_COLORRAM+0x3c, + NV04_PGRAPH_PATT_COLORRAM+0x40, + NV04_PGRAPH_PATT_COLORRAM+0x44, + NV04_PGRAPH_PATT_COLORRAM+0x48, + NV04_PGRAPH_PATT_COLORRAM+0x4c, + NV04_PGRAPH_PATT_COLORRAM+0x50, + NV04_PGRAPH_PATT_COLORRAM+0x54, + NV04_PGRAPH_PATT_COLORRAM+0x58, + NV04_PGRAPH_PATT_COLORRAM+0x5c, + NV04_PGRAPH_PATT_COLORRAM+0x60, + NV04_PGRAPH_PATT_COLORRAM+0x64, + NV04_PGRAPH_PATT_COLORRAM+0x68, + NV04_PGRAPH_PATT_COLORRAM+0x6c, + NV04_PGRAPH_PATT_COLORRAM+0x70, + NV04_PGRAPH_PATT_COLORRAM+0x74, + NV04_PGRAPH_PATT_COLORRAM+0x78, + NV04_PGRAPH_PATT_COLORRAM+0x7c, + NV04_PGRAPH_PATT_COLORRAM+0x80, + NV04_PGRAPH_PATT_COLORRAM+0x84, + NV04_PGRAPH_PATT_COLORRAM+0x88, + NV04_PGRAPH_PATT_COLORRAM+0x8c, + NV04_PGRAPH_PATT_COLORRAM+0x90, + NV04_PGRAPH_PATT_COLORRAM+0x94, + NV04_PGRAPH_PATT_COLORRAM+0x98, + NV04_PGRAPH_PATT_COLORRAM+0x9c, + NV04_PGRAPH_PATT_COLORRAM+0xa0, + NV04_PGRAPH_PATT_COLORRAM+0xa4, + NV04_PGRAPH_PATT_COLORRAM+0xa8, + NV04_PGRAPH_PATT_COLORRAM+0xac, + NV04_PGRAPH_PATT_COLORRAM+0xb0, + NV04_PGRAPH_PATT_COLORRAM+0xb4, + NV04_PGRAPH_PATT_COLORRAM+0xb8, + NV04_PGRAPH_PATT_COLORRAM+0xbc, + NV04_PGRAPH_PATT_COLORRAM+0xc0, + NV04_PGRAPH_PATT_COLORRAM+0xc4, + NV04_PGRAPH_PATT_COLORRAM+0xc8, + NV04_PGRAPH_PATT_COLORRAM+0xcc, + NV04_PGRAPH_PATT_COLORRAM+0xd0, + NV04_PGRAPH_PATT_COLORRAM+0xd4, + NV04_PGRAPH_PATT_COLORRAM+0xd8, + NV04_PGRAPH_PATT_COLORRAM+0xdc, + NV04_PGRAPH_PATT_COLORRAM+0xe0, + NV04_PGRAPH_PATT_COLORRAM+0xe4, + NV04_PGRAPH_PATT_COLORRAM+0xe8, + NV04_PGRAPH_PATT_COLORRAM+0xec, + NV04_PGRAPH_PATT_COLORRAM+0xf0, + NV04_PGRAPH_PATT_COLORRAM+0xf4, + NV04_PGRAPH_PATT_COLORRAM+0xf8, + NV04_PGRAPH_PATT_COLORRAM+0xfc, + NV04_PGRAPH_PATTERN, + 0x0040080c, + NV04_PGRAPH_PATTERN_SHAPE, + 0x00400600, + NV04_PGRAPH_ROP3, + NV04_PGRAPH_CHROMA, + NV04_PGRAPH_BETA_AND, + NV04_PGRAPH_BETA_PREMULT, + NV04_PGRAPH_CONTROL0, + NV04_PGRAPH_CONTROL1, + NV04_PGRAPH_CONTROL2, + NV04_PGRAPH_BLEND, + NV04_PGRAPH_STORED_FMT, + NV04_PGRAPH_SOURCE_COLOR, + 0x00400560, + 0x00400568, + 0x00400564, + 0x0040056c, + 0x00400400, + 0x00400480, + 0x00400404, + 0x00400484, + 0x00400408, + 0x00400488, + 0x0040040c, + 0x0040048c, + 0x00400410, + 0x00400490, + 0x00400414, + 0x00400494, + 0x00400418, + 0x00400498, + 0x0040041c, + 0x0040049c, + 0x00400420, + 0x004004a0, + 0x00400424, + 0x004004a4, + 0x00400428, + 0x004004a8, + 0x0040042c, + 0x004004ac, + 0x00400430, + 0x004004b0, + 0x00400434, + 0x004004b4, + 0x00400438, + 0x004004b8, + 0x0040043c, + 0x004004bc, + 0x00400440, + 0x004004c0, + 0x00400444, + 0x004004c4, + 0x00400448, + 0x004004c8, + 0x0040044c, + 0x004004cc, + 0x00400450, + 0x004004d0, + 0x00400454, + 0x004004d4, + 0x00400458, + 0x004004d8, + 0x0040045c, + 0x004004dc, + 0x00400460, + 0x004004e0, + 0x00400464, + 0x004004e4, + 0x00400468, + 0x004004e8, + 0x0040046c, + 0x004004ec, + 0x00400470, + 0x004004f0, + 0x00400474, + 0x004004f4, + 0x00400478, + 0x004004f8, + 0x0040047c, + 0x004004fc, + 0x00400534, + 0x00400538, + 0x00400514, + 0x00400518, + 0x0040051c, + 0x00400520, + 0x00400524, + 0x00400528, + 0x0040052c, + 0x00400530, + 0x00400d00, + 0x00400d40, + 0x00400d80, + 0x00400d04, + 0x00400d44, + 0x00400d84, + 0x00400d08, + 0x00400d48, + 0x00400d88, + 0x00400d0c, + 0x00400d4c, + 0x00400d8c, + 0x00400d10, + 0x00400d50, + 0x00400d90, + 0x00400d14, + 0x00400d54, + 0x00400d94, + 0x00400d18, + 0x00400d58, + 0x00400d98, + 0x00400d1c, + 0x00400d5c, + 0x00400d9c, + 0x00400d20, + 0x00400d60, + 0x00400da0, + 0x00400d24, + 0x00400d64, + 0x00400da4, + 0x00400d28, + 0x00400d68, + 0x00400da8, + 0x00400d2c, + 0x00400d6c, + 0x00400dac, + 0x00400d30, + 0x00400d70, + 0x00400db0, + 0x00400d34, + 0x00400d74, + 0x00400db4, + 0x00400d38, + 0x00400d78, + 0x00400db8, + 0x00400d3c, + 0x00400d7c, + 0x00400dbc, + 0x00400590, + 0x00400594, + 0x00400598, + 0x0040059c, + 0x004005a8, + 0x004005ac, + 0x004005b0, + 0x004005b4, + 0x004005c0, + 0x004005c4, + 0x004005c8, + 0x004005cc, + 0x004005d0, + 0x004005d4, + 0x004005d8, + 0x004005dc, + 0x004005e0, + NV04_PGRAPH_PASSTHRU_0, + NV04_PGRAPH_PASSTHRU_1, + NV04_PGRAPH_PASSTHRU_2, + NV04_PGRAPH_DVD_COLORFMT, + NV04_PGRAPH_SCALED_FORMAT, + NV04_PGRAPH_MISC24_0, + NV04_PGRAPH_MISC24_1, + NV04_PGRAPH_MISC24_2, + 0x00400500, + 0x00400504, + NV04_PGRAPH_VALID1, + NV04_PGRAPH_VALID2, + NV04_PGRAPH_DEBUG_3 +}; + +struct graph_state { + uint32_t nv04[ARRAY_SIZE(nv04_graph_ctx_regs)]; +}; + +static struct nouveau_channel * +nv04_graph_channel(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chid = dev_priv->engine.fifo.channels; + + if (nv_rd32(dev, NV04_PGRAPH_CTX_CONTROL) & 0x00010000) + chid = nv_rd32(dev, NV04_PGRAPH_CTX_USER) >> 24; + + if (chid >= dev_priv->engine.fifo.channels) + return NULL; + + return dev_priv->channels.ptr[chid]; +} + +static uint32_t *ctx_reg(struct graph_state *ctx, uint32_t reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(nv04_graph_ctx_regs); i++) { + if (nv04_graph_ctx_regs[i] == reg) + return &ctx->nv04[i]; + } + + return NULL; +} + +static int +nv04_graph_load_context(struct nouveau_channel *chan) +{ + struct graph_state *pgraph_ctx = chan->engctx[NVOBJ_ENGINE_GR]; + struct drm_device *dev = chan->dev; + uint32_t tmp; + int i; + + for (i = 0; i < ARRAY_SIZE(nv04_graph_ctx_regs); i++) + nv_wr32(dev, nv04_graph_ctx_regs[i], pgraph_ctx->nv04[i]); + + nv_wr32(dev, NV04_PGRAPH_CTX_CONTROL, 0x10010100); + + tmp = nv_rd32(dev, NV04_PGRAPH_CTX_USER) & 0x00ffffff; + nv_wr32(dev, NV04_PGRAPH_CTX_USER, tmp | chan->id << 24); + + tmp = nv_rd32(dev, NV04_PGRAPH_FFINTFC_ST2); + nv_wr32(dev, NV04_PGRAPH_FFINTFC_ST2, tmp & 0x000fffff); + + return 0; +} + +static int +nv04_graph_unload_context(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = NULL; + struct graph_state *ctx; + uint32_t tmp; + int i; + + chan = nv04_graph_channel(dev); + if (!chan) + return 0; + ctx = chan->engctx[NVOBJ_ENGINE_GR]; + + for (i = 0; i < ARRAY_SIZE(nv04_graph_ctx_regs); i++) + ctx->nv04[i] = nv_rd32(dev, nv04_graph_ctx_regs[i]); + + nv_wr32(dev, NV04_PGRAPH_CTX_CONTROL, 0x10000000); + tmp = nv_rd32(dev, NV04_PGRAPH_CTX_USER) & 0x00ffffff; + tmp |= (dev_priv->engine.fifo.channels - 1) << 24; + nv_wr32(dev, NV04_PGRAPH_CTX_USER, tmp); + return 0; +} + +static int +nv04_graph_context_new(struct nouveau_channel *chan, int engine) +{ + struct graph_state *pgraph_ctx; + NV_DEBUG(chan->dev, "nv04_graph_context_create %d\n", chan->id); + + pgraph_ctx = kzalloc(sizeof(*pgraph_ctx), GFP_KERNEL); + if (pgraph_ctx == NULL) + return -ENOMEM; + + *ctx_reg(pgraph_ctx, NV04_PGRAPH_DEBUG_3) = 0xfad4ff31; + + chan->engctx[engine] = pgraph_ctx; + return 0; +} + +static void +nv04_graph_context_del(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct graph_state *pgraph_ctx = chan->engctx[engine]; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000000); + + /* Unload the context if it's the currently active one */ + if (nv04_graph_channel(dev) == chan) + nv04_graph_unload_context(dev); + + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + /* Free the context resources */ + kfree(pgraph_ctx); + chan->engctx[engine] = NULL; +} + +int +nv04_graph_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + struct drm_device *dev = chan->dev; + struct nouveau_gpuobj *obj = NULL; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 16, 16, NVOBJ_FLAG_ZERO_FREE, &obj); + if (ret) + return ret; + obj->engine = 1; + obj->class = class; + +#ifdef __BIG_ENDIAN + nv_wo32(obj, 0x00, 0x00080000 | class); +#else + nv_wo32(obj, 0x00, class); +#endif + nv_wo32(obj, 0x04, 0x00000000); + nv_wo32(obj, 0x08, 0x00000000); + nv_wo32(obj, 0x0c, 0x00000000); + + ret = nouveau_ramht_insert(chan, handle, obj); + nouveau_gpuobj_ref(NULL, &obj); + return ret; +} + +static int +nv04_graph_init(struct drm_device *dev, int engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t tmp; + + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) & + ~NV_PMC_ENABLE_PGRAPH); + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) | + NV_PMC_ENABLE_PGRAPH); + + /* Enable PGRAPH interrupts */ + nv_wr32(dev, NV03_PGRAPH_INTR, 0xFFFFFFFF); + nv_wr32(dev, NV03_PGRAPH_INTR_EN, 0xFFFFFFFF); + + nv_wr32(dev, NV04_PGRAPH_VALID1, 0); + nv_wr32(dev, NV04_PGRAPH_VALID2, 0); + /*nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0x000001FF); + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0x001FFFFF);*/ + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0x1231c000); + /*1231C000 blob, 001 haiku*/ + /*V_WRITE(NV04_PGRAPH_DEBUG_1, 0xf2d91100);*/ + nv_wr32(dev, NV04_PGRAPH_DEBUG_1, 0x72111100); + /*0x72111100 blob , 01 haiku*/ + /*nv_wr32(dev, NV04_PGRAPH_DEBUG_2, 0x11d5f870);*/ + nv_wr32(dev, NV04_PGRAPH_DEBUG_2, 0x11d5f071); + /*haiku same*/ + + /*nv_wr32(dev, NV04_PGRAPH_DEBUG_3, 0xfad4ff31);*/ + nv_wr32(dev, NV04_PGRAPH_DEBUG_3, 0xf0d4ff31); + /*haiku and blob 10d4*/ + + nv_wr32(dev, NV04_PGRAPH_STATE , 0xFFFFFFFF); + nv_wr32(dev, NV04_PGRAPH_CTX_CONTROL , 0x10000100); + tmp = nv_rd32(dev, NV04_PGRAPH_CTX_USER) & 0x00ffffff; + tmp |= (dev_priv->engine.fifo.channels - 1) << 24; + nv_wr32(dev, NV04_PGRAPH_CTX_USER, tmp); + + /* These don't belong here, they're part of a per-channel context */ + nv_wr32(dev, NV04_PGRAPH_PATTERN_SHAPE, 0x00000000); + nv_wr32(dev, NV04_PGRAPH_BETA_AND , 0xFFFFFFFF); + + return 0; +} + +static int +nv04_graph_fini(struct drm_device *dev, int engine, bool suspend) +{ + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000000); + if (!nv_wait(dev, NV04_PGRAPH_STATUS, ~0, 0) && suspend) { + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000001); + return -EBUSY; + } + nv04_graph_unload_context(dev); + nv_wr32(dev, NV03_PGRAPH_INTR_EN, 0x00000000); + return 0; +} + +static int +nv04_graph_mthd_set_ref(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + atomic_set(&chan->fence.last_sequence_irq, data); + return 0; +} + +int +nv04_graph_mthd_page_flip(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + struct drm_device *dev = chan->dev; + struct nouveau_page_flip_state s; + + if (!nouveau_finish_page_flip(chan, &s)) + nv_set_crtc_base(dev, s.crtc, + s.offset + s.y * s.pitch + s.x * s.bpp / 8); + + return 0; +} + +/* + * Software methods, why they are needed, and how they all work: + * + * NV04 and NV05 keep most of the state in PGRAPH context itself, but some + * 2d engine settings are kept inside the grobjs themselves. The grobjs are + * 3 words long on both. grobj format on NV04 is: + * + * word 0: + * - bits 0-7: class + * - bit 12: color key active + * - bit 13: clip rect active + * - bit 14: if set, destination surface is swizzled and taken from buffer 5 + * [set by NV04_SWIZZLED_SURFACE], otherwise it's linear and taken + * from buffer 0 [set by NV04_CONTEXT_SURFACES_2D or + * NV03_CONTEXT_SURFACE_DST]. + * - bits 15-17: 2d operation [aka patch config] + * - bit 24: patch valid [enables rendering using this object] + * - bit 25: surf3d valid [for tex_tri and multitex_tri only] + * word 1: + * - bits 0-1: mono format + * - bits 8-13: color format + * - bits 16-31: DMA_NOTIFY instance + * word 2: + * - bits 0-15: DMA_A instance + * - bits 16-31: DMA_B instance + * + * On NV05 it's: + * + * word 0: + * - bits 0-7: class + * - bit 12: color key active + * - bit 13: clip rect active + * - bit 14: if set, destination surface is swizzled and taken from buffer 5 + * [set by NV04_SWIZZLED_SURFACE], otherwise it's linear and taken + * from buffer 0 [set by NV04_CONTEXT_SURFACES_2D or + * NV03_CONTEXT_SURFACE_DST]. + * - bits 15-17: 2d operation [aka patch config] + * - bits 20-22: dither mode + * - bit 24: patch valid [enables rendering using this object] + * - bit 25: surface_dst/surface_color/surf2d/surf3d valid + * - bit 26: surface_src/surface_zeta valid + * - bit 27: pattern valid + * - bit 28: rop valid + * - bit 29: beta1 valid + * - bit 30: beta4 valid + * word 1: + * - bits 0-1: mono format + * - bits 8-13: color format + * - bits 16-31: DMA_NOTIFY instance + * word 2: + * - bits 0-15: DMA_A instance + * - bits 16-31: DMA_B instance + * + * NV05 will set/unset the relevant valid bits when you poke the relevant + * object-binding methods with object of the proper type, or with the NULL + * type. It'll only allow rendering using the grobj if all needed objects + * are bound. The needed set of objects depends on selected operation: for + * example rop object is needed by ROP_AND, but not by SRCCOPY_AND. + * + * NV04 doesn't have these methods implemented at all, and doesn't have the + * relevant bits in grobj. Instead, it'll allow rendering whenever bit 24 + * is set. So we have to emulate them in software, internally keeping the + * same bits as NV05 does. Since grobjs are aligned to 16 bytes on nv04, + * but the last word isn't actually used for anything, we abuse it for this + * purpose. + * + * Actually, NV05 can optionally check bit 24 too, but we disable this since + * there's no use for it. + * + * For unknown reasons, NV04 implements surf3d binding in hardware as an + * exception. Also for unknown reasons, NV04 doesn't implement the clipping + * methods on the surf3d object, so we have to emulate them too. + */ + +static void +nv04_graph_set_ctx1(struct nouveau_channel *chan, u32 mask, u32 value) +{ + struct drm_device *dev = chan->dev; + u32 instance = (nv_rd32(dev, NV04_PGRAPH_CTX_SWITCH4) & 0xffff) << 4; + int subc = (nv_rd32(dev, NV04_PGRAPH_TRAPPED_ADDR) >> 13) & 0x7; + u32 tmp; + + tmp = nv_ri32(dev, instance); + tmp &= ~mask; + tmp |= value; + + nv_wi32(dev, instance, tmp); + nv_wr32(dev, NV04_PGRAPH_CTX_SWITCH1, tmp); + nv_wr32(dev, NV04_PGRAPH_CTX_CACHE1 + (subc<<2), tmp); +} + +static void +nv04_graph_set_ctx_val(struct nouveau_channel *chan, u32 mask, u32 value) +{ + struct drm_device *dev = chan->dev; + u32 instance = (nv_rd32(dev, NV04_PGRAPH_CTX_SWITCH4) & 0xffff) << 4; + u32 tmp, ctx1; + int class, op, valid = 1; + + ctx1 = nv_ri32(dev, instance); + class = ctx1 & 0xff; + op = (ctx1 >> 15) & 7; + tmp = nv_ri32(dev, instance + 0xc); + tmp &= ~mask; + tmp |= value; + nv_wi32(dev, instance + 0xc, tmp); + + /* check for valid surf2d/surf_dst/surf_color */ + if (!(tmp & 0x02000000)) + valid = 0; + /* check for valid surf_src/surf_zeta */ + if ((class == 0x1f || class == 0x48) && !(tmp & 0x04000000)) + valid = 0; + + switch (op) { + /* SRCCOPY_AND, SRCCOPY: no extra objects required */ + case 0: + case 3: + break; + /* ROP_AND: requires pattern and rop */ + case 1: + if (!(tmp & 0x18000000)) + valid = 0; + break; + /* BLEND_AND: requires beta1 */ + case 2: + if (!(tmp & 0x20000000)) + valid = 0; + break; + /* SRCCOPY_PREMULT, BLEND_PREMULT: beta4 required */ + case 4: + case 5: + if (!(tmp & 0x40000000)) + valid = 0; + break; + } + + nv04_graph_set_ctx1(chan, 0x01000000, valid << 24); +} + +static int +nv04_graph_mthd_set_operation(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + if (data > 5) + return 1; + /* Old versions of the objects only accept first three operations. */ + if (data > 2 && class < 0x40) + return 1; + nv04_graph_set_ctx1(chan, 0x00038000, data << 15); + /* changing operation changes set of objects needed for validation */ + nv04_graph_set_ctx_val(chan, 0, 0); + return 0; +} + +static int +nv04_graph_mthd_surf3d_clip_h(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + uint32_t min = data & 0xffff, max; + uint32_t w = data >> 16; + if (min & 0x8000) + /* too large */ + return 1; + if (w & 0x8000) + /* yes, it accepts negative for some reason. */ + w |= 0xffff0000; + max = min + w; + max &= 0x3ffff; + nv_wr32(chan->dev, 0x40053c, min); + nv_wr32(chan->dev, 0x400544, max); + return 0; +} + +static int +nv04_graph_mthd_surf3d_clip_v(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + uint32_t min = data & 0xffff, max; + uint32_t w = data >> 16; + if (min & 0x8000) + /* too large */ + return 1; + if (w & 0x8000) + /* yes, it accepts negative for some reason. */ + w |= 0xffff0000; + max = min + w; + max &= 0x3ffff; + nv_wr32(chan->dev, 0x400540, min); + nv_wr32(chan->dev, 0x400548, max); + return 0; +} + +static int +nv04_graph_mthd_bind_surf2d(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx1(chan, 0x00004000, 0); + nv04_graph_set_ctx_val(chan, 0x02000000, 0); + return 0; + case 0x42: + nv04_graph_set_ctx1(chan, 0x00004000, 0); + nv04_graph_set_ctx_val(chan, 0x02000000, 0x02000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_surf2d_swzsurf(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx1(chan, 0x00004000, 0); + nv04_graph_set_ctx_val(chan, 0x02000000, 0); + return 0; + case 0x42: + nv04_graph_set_ctx1(chan, 0x00004000, 0); + nv04_graph_set_ctx_val(chan, 0x02000000, 0x02000000); + return 0; + case 0x52: + nv04_graph_set_ctx1(chan, 0x00004000, 0x00004000); + nv04_graph_set_ctx_val(chan, 0x02000000, 0x02000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_nv01_patt(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x08000000, 0); + return 0; + case 0x18: + nv04_graph_set_ctx_val(chan, 0x08000000, 0x08000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_nv04_patt(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x08000000, 0); + return 0; + case 0x44: + nv04_graph_set_ctx_val(chan, 0x08000000, 0x08000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_rop(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x10000000, 0); + return 0; + case 0x43: + nv04_graph_set_ctx_val(chan, 0x10000000, 0x10000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_beta1(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x20000000, 0); + return 0; + case 0x12: + nv04_graph_set_ctx_val(chan, 0x20000000, 0x20000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_beta4(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x40000000, 0); + return 0; + case 0x72: + nv04_graph_set_ctx_val(chan, 0x40000000, 0x40000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_surf_dst(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x02000000, 0); + return 0; + case 0x58: + nv04_graph_set_ctx_val(chan, 0x02000000, 0x02000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_surf_src(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x04000000, 0); + return 0; + case 0x59: + nv04_graph_set_ctx_val(chan, 0x04000000, 0x04000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_surf_color(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x02000000, 0); + return 0; + case 0x5a: + nv04_graph_set_ctx_val(chan, 0x02000000, 0x02000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_surf_zeta(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx_val(chan, 0x04000000, 0); + return 0; + case 0x5b: + nv04_graph_set_ctx_val(chan, 0x04000000, 0x04000000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_clip(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx1(chan, 0x2000, 0); + return 0; + case 0x19: + nv04_graph_set_ctx1(chan, 0x2000, 0x2000); + return 0; + } + return 1; +} + +static int +nv04_graph_mthd_bind_chroma(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + switch (nv_ri32(chan->dev, data << 4) & 0xff) { + case 0x30: + nv04_graph_set_ctx1(chan, 0x1000, 0); + return 0; + /* Yes, for some reason even the old versions of objects + * accept 0x57 and not 0x17. Consistency be damned. + */ + case 0x57: + nv04_graph_set_ctx1(chan, 0x1000, 0x1000); + return 0; + } + return 1; +} + +static struct nouveau_bitfield nv04_graph_intr[] = { + { NV_PGRAPH_INTR_NOTIFY, "NOTIFY" }, + {} +}; + +static struct nouveau_bitfield nv04_graph_nstatus[] = { + { NV04_PGRAPH_NSTATUS_STATE_IN_USE, "STATE_IN_USE" }, + { NV04_PGRAPH_NSTATUS_INVALID_STATE, "INVALID_STATE" }, + { NV04_PGRAPH_NSTATUS_BAD_ARGUMENT, "BAD_ARGUMENT" }, + { NV04_PGRAPH_NSTATUS_PROTECTION_FAULT, "PROTECTION_FAULT" }, + {} +}; + +struct nouveau_bitfield nv04_graph_nsource[] = { + { NV03_PGRAPH_NSOURCE_NOTIFICATION, "NOTIFICATION" }, + { NV03_PGRAPH_NSOURCE_DATA_ERROR, "DATA_ERROR" }, + { NV03_PGRAPH_NSOURCE_PROTECTION_ERROR, "PROTECTION_ERROR" }, + { NV03_PGRAPH_NSOURCE_RANGE_EXCEPTION, "RANGE_EXCEPTION" }, + { NV03_PGRAPH_NSOURCE_LIMIT_COLOR, "LIMIT_COLOR" }, + { NV03_PGRAPH_NSOURCE_LIMIT_ZETA, "LIMIT_ZETA" }, + { NV03_PGRAPH_NSOURCE_ILLEGAL_MTHD, "ILLEGAL_MTHD" }, + { NV03_PGRAPH_NSOURCE_DMA_R_PROTECTION, "DMA_R_PROTECTION" }, + { NV03_PGRAPH_NSOURCE_DMA_W_PROTECTION, "DMA_W_PROTECTION" }, + { NV03_PGRAPH_NSOURCE_FORMAT_EXCEPTION, "FORMAT_EXCEPTION" }, + { NV03_PGRAPH_NSOURCE_PATCH_EXCEPTION, "PATCH_EXCEPTION" }, + { NV03_PGRAPH_NSOURCE_STATE_INVALID, "STATE_INVALID" }, + { NV03_PGRAPH_NSOURCE_DOUBLE_NOTIFY, "DOUBLE_NOTIFY" }, + { NV03_PGRAPH_NSOURCE_NOTIFY_IN_USE, "NOTIFY_IN_USE" }, + { NV03_PGRAPH_NSOURCE_METHOD_CNT, "METHOD_CNT" }, + { NV03_PGRAPH_NSOURCE_BFR_NOTIFICATION, "BFR_NOTIFICATION" }, + { NV03_PGRAPH_NSOURCE_DMA_VTX_PROTECTION, "DMA_VTX_PROTECTION" }, + { NV03_PGRAPH_NSOURCE_DMA_WIDTH_A, "DMA_WIDTH_A" }, + { NV03_PGRAPH_NSOURCE_DMA_WIDTH_B, "DMA_WIDTH_B" }, + {} +}; + +static void +nv04_graph_context_switch(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = NULL; + int chid; + + nouveau_wait_for_idle(dev); + + /* If previous context is valid, we need to save it */ + nv04_graph_unload_context(dev); + + /* Load context for next channel */ + chid = dev_priv->engine.fifo.channel_id(dev); + chan = dev_priv->channels.ptr[chid]; + if (chan) + nv04_graph_load_context(chan); +} + +static void +nv04_graph_isr(struct drm_device *dev) +{ + u32 stat; + + while ((stat = nv_rd32(dev, NV03_PGRAPH_INTR))) { + u32 nsource = nv_rd32(dev, NV03_PGRAPH_NSOURCE); + u32 nstatus = nv_rd32(dev, NV03_PGRAPH_NSTATUS); + u32 addr = nv_rd32(dev, NV04_PGRAPH_TRAPPED_ADDR); + u32 chid = (addr & 0x0f000000) >> 24; + u32 subc = (addr & 0x0000e000) >> 13; + u32 mthd = (addr & 0x00001ffc); + u32 data = nv_rd32(dev, NV04_PGRAPH_TRAPPED_DATA); + u32 class = nv_rd32(dev, 0x400180 + subc * 4) & 0xff; + u32 show = stat; + + if (stat & NV_PGRAPH_INTR_NOTIFY) { + if (nsource & NV03_PGRAPH_NSOURCE_ILLEGAL_MTHD) { + if (!nouveau_gpuobj_mthd_call2(dev, chid, class, mthd, data)) + show &= ~NV_PGRAPH_INTR_NOTIFY; + } + } + + if (stat & NV_PGRAPH_INTR_CONTEXT_SWITCH) { + nv_wr32(dev, NV03_PGRAPH_INTR, NV_PGRAPH_INTR_CONTEXT_SWITCH); + stat &= ~NV_PGRAPH_INTR_CONTEXT_SWITCH; + show &= ~NV_PGRAPH_INTR_CONTEXT_SWITCH; + nv04_graph_context_switch(dev); + } + + nv_wr32(dev, NV03_PGRAPH_INTR, stat); + nv_wr32(dev, NV04_PGRAPH_FIFO, 0x00000001); + + if (show && nouveau_ratelimit()) { + NV_INFO(dev, "PGRAPH -"); + nouveau_bitfield_print(nv04_graph_intr, show); + printk(" nsource:"); + nouveau_bitfield_print(nv04_graph_nsource, nsource); + printk(" nstatus:"); + nouveau_bitfield_print(nv04_graph_nstatus, nstatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - ch %d/%d class 0x%04x " + "mthd 0x%04x data 0x%08x\n", + chid, subc, class, mthd, data); + } + } +} + +static void +nv04_graph_destroy(struct drm_device *dev, int engine) +{ + struct nv04_graph_engine *pgraph = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, 12); + + NVOBJ_ENGINE_DEL(dev, GR); + kfree(pgraph); +} + +int +nv04_graph_create(struct drm_device *dev) +{ + struct nv04_graph_engine *pgraph; + + pgraph = kzalloc(sizeof(*pgraph), GFP_KERNEL); + if (!pgraph) + return -ENOMEM; + + pgraph->base.destroy = nv04_graph_destroy; + pgraph->base.init = nv04_graph_init; + pgraph->base.fini = nv04_graph_fini; + pgraph->base.context_new = nv04_graph_context_new; + pgraph->base.context_del = nv04_graph_context_del; + pgraph->base.object_new = nv04_graph_object_new; + + NVOBJ_ENGINE_ADD(dev, GR, &pgraph->base); + nouveau_irq_register(dev, 12, nv04_graph_isr); + + /* dvd subpicture */ + NVOBJ_CLASS(dev, 0x0038, GR); + + /* m2mf */ + NVOBJ_CLASS(dev, 0x0039, GR); + + /* nv03 gdirect */ + NVOBJ_CLASS(dev, 0x004b, GR); + NVOBJ_MTHD (dev, 0x004b, 0x0184, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x004b, 0x0188, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x004b, 0x018c, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x004b, 0x0190, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x004b, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 gdirect */ + NVOBJ_CLASS(dev, 0x004a, GR); + NVOBJ_MTHD (dev, 0x004a, 0x0188, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x004a, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x004a, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x004a, 0x0194, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x004a, 0x0198, nv04_graph_mthd_bind_surf2d); + NVOBJ_MTHD (dev, 0x004a, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv01 imageblit */ + NVOBJ_CLASS(dev, 0x001f, GR); + NVOBJ_MTHD (dev, 0x001f, 0x0184, nv04_graph_mthd_bind_chroma); + NVOBJ_MTHD (dev, 0x001f, 0x0188, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x001f, 0x018c, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x001f, 0x0190, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x001f, 0x0194, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x001f, 0x0198, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x001f, 0x019c, nv04_graph_mthd_bind_surf_src); + NVOBJ_MTHD (dev, 0x001f, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 imageblit */ + NVOBJ_CLASS(dev, 0x005f, GR); + NVOBJ_MTHD (dev, 0x005f, 0x0184, nv04_graph_mthd_bind_chroma); + NVOBJ_MTHD (dev, 0x005f, 0x0188, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x005f, 0x018c, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x005f, 0x0190, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x005f, 0x0194, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x005f, 0x0198, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x005f, 0x019c, nv04_graph_mthd_bind_surf2d); + NVOBJ_MTHD (dev, 0x005f, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 iifc */ + NVOBJ_CLASS(dev, 0x0060, GR); + NVOBJ_MTHD (dev, 0x0060, 0x0188, nv04_graph_mthd_bind_chroma); + NVOBJ_MTHD (dev, 0x0060, 0x018c, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x0060, 0x0190, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x0060, 0x0194, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x0060, 0x0198, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x0060, 0x019c, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x0060, 0x01a0, nv04_graph_mthd_bind_surf2d_swzsurf); + NVOBJ_MTHD (dev, 0x0060, 0x03e4, nv04_graph_mthd_set_operation); + + /* nv05 iifc */ + NVOBJ_CLASS(dev, 0x0064, GR); + + /* nv01 ifc */ + NVOBJ_CLASS(dev, 0x0021, GR); + NVOBJ_MTHD (dev, 0x0021, 0x0184, nv04_graph_mthd_bind_chroma); + NVOBJ_MTHD (dev, 0x0021, 0x0188, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x0021, 0x018c, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x0021, 0x0190, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x0021, 0x0194, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x0021, 0x0198, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x0021, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 ifc */ + NVOBJ_CLASS(dev, 0x0061, GR); + NVOBJ_MTHD (dev, 0x0061, 0x0184, nv04_graph_mthd_bind_chroma); + NVOBJ_MTHD (dev, 0x0061, 0x0188, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x0061, 0x018c, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x0061, 0x0190, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x0061, 0x0194, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x0061, 0x0198, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x0061, 0x019c, nv04_graph_mthd_bind_surf2d); + NVOBJ_MTHD (dev, 0x0061, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv05 ifc */ + NVOBJ_CLASS(dev, 0x0065, GR); + + /* nv03 sifc */ + NVOBJ_CLASS(dev, 0x0036, GR); + NVOBJ_MTHD (dev, 0x0036, 0x0184, nv04_graph_mthd_bind_chroma); + NVOBJ_MTHD (dev, 0x0036, 0x0188, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x0036, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x0036, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x0036, 0x0194, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x0036, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 sifc */ + NVOBJ_CLASS(dev, 0x0076, GR); + NVOBJ_MTHD (dev, 0x0076, 0x0184, nv04_graph_mthd_bind_chroma); + NVOBJ_MTHD (dev, 0x0076, 0x0188, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x0076, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x0076, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x0076, 0x0194, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x0076, 0x0198, nv04_graph_mthd_bind_surf2d); + NVOBJ_MTHD (dev, 0x0076, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv05 sifc */ + NVOBJ_CLASS(dev, 0x0066, GR); + + /* nv03 sifm */ + NVOBJ_CLASS(dev, 0x0037, GR); + NVOBJ_MTHD (dev, 0x0037, 0x0188, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x0037, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x0037, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x0037, 0x0194, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x0037, 0x0304, nv04_graph_mthd_set_operation); + + /* nv04 sifm */ + NVOBJ_CLASS(dev, 0x0077, GR); + NVOBJ_MTHD (dev, 0x0077, 0x0188, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x0077, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x0077, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x0077, 0x0194, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x0077, 0x0198, nv04_graph_mthd_bind_surf2d_swzsurf); + NVOBJ_MTHD (dev, 0x0077, 0x0304, nv04_graph_mthd_set_operation); + + /* null */ + NVOBJ_CLASS(dev, 0x0030, GR); + + /* surf2d */ + NVOBJ_CLASS(dev, 0x0042, GR); + + /* rop */ + NVOBJ_CLASS(dev, 0x0043, GR); + + /* beta1 */ + NVOBJ_CLASS(dev, 0x0012, GR); + + /* beta4 */ + NVOBJ_CLASS(dev, 0x0072, GR); + + /* cliprect */ + NVOBJ_CLASS(dev, 0x0019, GR); + + /* nv01 pattern */ + NVOBJ_CLASS(dev, 0x0018, GR); + + /* nv04 pattern */ + NVOBJ_CLASS(dev, 0x0044, GR); + + /* swzsurf */ + NVOBJ_CLASS(dev, 0x0052, GR); + + /* surf3d */ + NVOBJ_CLASS(dev, 0x0053, GR); + NVOBJ_MTHD (dev, 0x0053, 0x02f8, nv04_graph_mthd_surf3d_clip_h); + NVOBJ_MTHD (dev, 0x0053, 0x02fc, nv04_graph_mthd_surf3d_clip_v); + + /* nv03 tex_tri */ + NVOBJ_CLASS(dev, 0x0048, GR); + NVOBJ_MTHD (dev, 0x0048, 0x0188, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x0048, 0x018c, nv04_graph_mthd_bind_surf_color); + NVOBJ_MTHD (dev, 0x0048, 0x0190, nv04_graph_mthd_bind_surf_zeta); + + /* tex_tri */ + NVOBJ_CLASS(dev, 0x0054, GR); + + /* multitex_tri */ + NVOBJ_CLASS(dev, 0x0055, GR); + + /* nv01 chroma */ + NVOBJ_CLASS(dev, 0x0017, GR); + + /* nv04 chroma */ + NVOBJ_CLASS(dev, 0x0057, GR); + + /* surf_dst */ + NVOBJ_CLASS(dev, 0x0058, GR); + + /* surf_src */ + NVOBJ_CLASS(dev, 0x0059, GR); + + /* surf_color */ + NVOBJ_CLASS(dev, 0x005a, GR); + + /* surf_zeta */ + NVOBJ_CLASS(dev, 0x005b, GR); + + /* nv01 line */ + NVOBJ_CLASS(dev, 0x001c, GR); + NVOBJ_MTHD (dev, 0x001c, 0x0184, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x001c, 0x0188, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x001c, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x001c, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x001c, 0x0194, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x001c, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 line */ + NVOBJ_CLASS(dev, 0x005c, GR); + NVOBJ_MTHD (dev, 0x005c, 0x0184, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x005c, 0x0188, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x005c, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x005c, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x005c, 0x0194, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x005c, 0x0198, nv04_graph_mthd_bind_surf2d); + NVOBJ_MTHD (dev, 0x005c, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv01 tri */ + NVOBJ_CLASS(dev, 0x001d, GR); + NVOBJ_MTHD (dev, 0x001d, 0x0184, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x001d, 0x0188, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x001d, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x001d, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x001d, 0x0194, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x001d, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 tri */ + NVOBJ_CLASS(dev, 0x005d, GR); + NVOBJ_MTHD (dev, 0x005d, 0x0184, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x005d, 0x0188, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x005d, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x005d, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x005d, 0x0194, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x005d, 0x0198, nv04_graph_mthd_bind_surf2d); + NVOBJ_MTHD (dev, 0x005d, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv01 rect */ + NVOBJ_CLASS(dev, 0x001e, GR); + NVOBJ_MTHD (dev, 0x001e, 0x0184, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x001e, 0x0188, nv04_graph_mthd_bind_nv01_patt); + NVOBJ_MTHD (dev, 0x001e, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x001e, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x001e, 0x0194, nv04_graph_mthd_bind_surf_dst); + NVOBJ_MTHD (dev, 0x001e, 0x02fc, nv04_graph_mthd_set_operation); + + /* nv04 rect */ + NVOBJ_CLASS(dev, 0x005e, GR); + NVOBJ_MTHD (dev, 0x005e, 0x0184, nv04_graph_mthd_bind_clip); + NVOBJ_MTHD (dev, 0x005e, 0x0188, nv04_graph_mthd_bind_nv04_patt); + NVOBJ_MTHD (dev, 0x005e, 0x018c, nv04_graph_mthd_bind_rop); + NVOBJ_MTHD (dev, 0x005e, 0x0190, nv04_graph_mthd_bind_beta1); + NVOBJ_MTHD (dev, 0x005e, 0x0194, nv04_graph_mthd_bind_beta4); + NVOBJ_MTHD (dev, 0x005e, 0x0198, nv04_graph_mthd_bind_surf2d); + NVOBJ_MTHD (dev, 0x005e, 0x02fc, nv04_graph_mthd_set_operation); + + /* nvsw */ + NVOBJ_CLASS(dev, 0x506e, SW); + NVOBJ_MTHD (dev, 0x506e, 0x0150, nv04_graph_mthd_set_ref); + NVOBJ_MTHD (dev, 0x506e, 0x0500, nv04_graph_mthd_page_flip); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv04_instmem.c b/drivers/gpu/drm/nouveau/nv04_instmem.c new file mode 100644 index 00000000..c1248e07 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_instmem.c @@ -0,0 +1,192 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" + +/* returns the size of fifo context */ +static int +nouveau_fifo_ctx_size(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset >= 0x40) + return 128; + else + if (dev_priv->chipset >= 0x17) + return 64; + + return 32; +} + +int nv04_instmem_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramht = NULL; + u32 offset, length; + int ret; + + /* RAMIN always available */ + dev_priv->ramin_available = true; + + /* Reserve space at end of VRAM for PRAMIN */ + if (dev_priv->card_type >= NV_40) { + u32 vs = hweight8((nv_rd32(dev, 0x001540) & 0x0000ff00) >> 8); + u32 rsvd; + + /* estimate grctx size, the magics come from nv40_grctx.c */ + if (dev_priv->chipset == 0x40) rsvd = 0x6aa0 * vs; + else if (dev_priv->chipset < 0x43) rsvd = 0x4f00 * vs; + else if (nv44_graph_class(dev)) rsvd = 0x4980 * vs; + else rsvd = 0x4a40 * vs; + rsvd += 16 * 1024; + rsvd *= dev_priv->engine.fifo.channels; + + /* pciegart table */ + if (pci_is_pcie(dev->pdev)) + rsvd += 512 * 1024; + + /* object storage */ + rsvd += 512 * 1024; + + dev_priv->ramin_rsvd_vram = round_up(rsvd, 4096); + } else { + dev_priv->ramin_rsvd_vram = 512 * 1024; + } + + /* Setup shared RAMHT */ + ret = nouveau_gpuobj_new_fake(dev, 0x10000, ~0, 4096, + NVOBJ_FLAG_ZERO_ALLOC, &ramht); + if (ret) + return ret; + + ret = nouveau_ramht_new(dev, ramht, &dev_priv->ramht); + nouveau_gpuobj_ref(NULL, &ramht); + if (ret) + return ret; + + /* And RAMRO */ + ret = nouveau_gpuobj_new_fake(dev, 0x11200, ~0, 512, + NVOBJ_FLAG_ZERO_ALLOC, &dev_priv->ramro); + if (ret) + return ret; + + /* And RAMFC */ + length = dev_priv->engine.fifo.channels * nouveau_fifo_ctx_size(dev); + switch (dev_priv->card_type) { + case NV_40: + offset = 0x20000; + break; + default: + offset = 0x11400; + break; + } + + ret = nouveau_gpuobj_new_fake(dev, offset, ~0, length, + NVOBJ_FLAG_ZERO_ALLOC, &dev_priv->ramfc); + if (ret) + return ret; + + /* Only allow space after RAMFC to be used for object allocation */ + offset += length; + + /* It appears RAMRO (or something?) is controlled by 0x2220/0x2230 + * on certain NV4x chipsets as well as RAMFC. When 0x2230 == 0 + * ("new style" control) the upper 16-bits of 0x2220 points at this + * other mysterious table that's clobbering important things. + * + * We're now pointing this at RAMIN+0x30000 to avoid RAMFC getting + * smashed to pieces on us, so reserve 0x30000-0x40000 too.. + */ + if (dev_priv->card_type >= NV_40) { + if (offset < 0x40000) + offset = 0x40000; + } + + ret = drm_mm_init(&dev_priv->ramin_heap, offset, + dev_priv->ramin_rsvd_vram - offset); + if (ret) { + NV_ERROR(dev, "Failed to init RAMIN heap: %d\n", ret); + return ret; + } + + return 0; +} + +void +nv04_instmem_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nouveau_ramht_ref(NULL, &dev_priv->ramht, NULL); + nouveau_gpuobj_ref(NULL, &dev_priv->ramro); + nouveau_gpuobj_ref(NULL, &dev_priv->ramfc); + + if (drm_mm_initialized(&dev_priv->ramin_heap)) + drm_mm_takedown(&dev_priv->ramin_heap); +} + +int +nv04_instmem_suspend(struct drm_device *dev) +{ + return 0; +} + +void +nv04_instmem_resume(struct drm_device *dev) +{ +} + +int +nv04_instmem_get(struct nouveau_gpuobj *gpuobj, struct nouveau_channel *chan, + u32 size, u32 align) +{ + struct drm_nouveau_private *dev_priv = gpuobj->dev->dev_private; + struct drm_mm_node *ramin = NULL; + + do { + if (drm_mm_pre_get(&dev_priv->ramin_heap)) + return -ENOMEM; + + spin_lock(&dev_priv->ramin_lock); + ramin = drm_mm_search_free(&dev_priv->ramin_heap, size, align, 0); + if (ramin == NULL) { + spin_unlock(&dev_priv->ramin_lock); + return -ENOMEM; + } + + ramin = drm_mm_get_block_atomic(ramin, size, align); + spin_unlock(&dev_priv->ramin_lock); + } while (ramin == NULL); + + gpuobj->node = ramin; + gpuobj->vinst = ramin->start; + return 0; +} + +void +nv04_instmem_put(struct nouveau_gpuobj *gpuobj) +{ + struct drm_nouveau_private *dev_priv = gpuobj->dev->dev_private; + + spin_lock(&dev_priv->ramin_lock); + drm_mm_put_block(gpuobj->node); + gpuobj->node = NULL; + spin_unlock(&dev_priv->ramin_lock); +} + +int +nv04_instmem_map(struct nouveau_gpuobj *gpuobj) +{ + gpuobj->pinst = gpuobj->vinst; + return 0; +} + +void +nv04_instmem_unmap(struct nouveau_gpuobj *gpuobj) +{ +} + +void +nv04_instmem_flush(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv04_mc.c b/drivers/gpu/drm/nouveau/nv04_mc.c new file mode 100644 index 00000000..2af43a1c --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_mc.c @@ -0,0 +1,24 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +int +nv04_mc_init(struct drm_device *dev) +{ + /* Power up everything, resetting each individual unit will + * be done later if needed. + */ + + nv_wr32(dev, NV03_PMC_ENABLE, 0xFFFFFFFF); + + /* Disable PROM access. */ + nv_wr32(dev, NV_PBUS_PCI_NV_20, NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED); + + return 0; +} + +void +nv04_mc_takedown(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv04_pm.c b/drivers/gpu/drm/nouveau/nv04_pm.c new file mode 100644 index 00000000..6e758991 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_pm.c @@ -0,0 +1,137 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" +#include "nouveau_pm.h" + +int +nv04_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + int ret; + + ret = nouveau_hw_get_clock(dev, PLL_CORE); + if (ret < 0) + return ret; + perflvl->core = ret; + + ret = nouveau_hw_get_clock(dev, PLL_MEMORY); + if (ret < 0) + return ret; + perflvl->memory = ret; + + return 0; +} + +struct nv04_pm_clock { + struct pll_lims pll; + struct nouveau_pll_vals calc; +}; + +struct nv04_pm_state { + struct nv04_pm_clock core; + struct nv04_pm_clock memory; +}; + +static int +calc_pll(struct drm_device *dev, u32 id, int khz, struct nv04_pm_clock *clk) +{ + int ret; + + ret = get_pll_limits(dev, id, &clk->pll); + if (ret) + return ret; + + ret = nouveau_calc_pll_mnp(dev, &clk->pll, khz, &clk->calc); + if (!ret) + return -EINVAL; + + return 0; +} + +void * +nv04_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct nv04_pm_state *info; + int ret; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + ret = calc_pll(dev, PLL_CORE, perflvl->core, &info->core); + if (ret) + goto error; + + if (perflvl->memory) { + ret = calc_pll(dev, PLL_MEMORY, perflvl->memory, &info->memory); + if (ret) + goto error; + } + + return info; +error: + kfree(info); + return ERR_PTR(ret); +} + +static void +prog_pll(struct drm_device *dev, struct nv04_pm_clock *clk) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 reg = clk->pll.reg; + + /* thank the insane nouveau_hw_setpll() interface for this */ + if (dev_priv->card_type >= NV_40) + reg += 4; + + nouveau_hw_setpll(dev, reg, &clk->calc); +} + +int +nv04_pm_clocks_set(struct drm_device *dev, void *pre_state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + struct nv04_pm_state *state = pre_state; + + prog_pll(dev, &state->core); + + if (state->memory.pll.reg) { + prog_pll(dev, &state->memory); + if (dev_priv->card_type < NV_30) { + if (dev_priv->card_type == NV_20) + nv_mask(dev, 0x1002c4, 0, 1 << 20); + + /* Reset the DLLs */ + nv_mask(dev, 0x1002c0, 0, 1 << 8); + } + } + + ptimer->init(dev); + + kfree(state); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv04_timer.c b/drivers/gpu/drm/nouveau/nv04_timer.c new file mode 100644 index 00000000..55c94529 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_timer.c @@ -0,0 +1,84 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_hw.h" + +int +nv04_timer_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 m, n, d; + + nv_wr32(dev, NV04_PTIMER_INTR_EN_0, 0x00000000); + nv_wr32(dev, NV04_PTIMER_INTR_0, 0xFFFFFFFF); + + /* aim for 31.25MHz, which gives us nanosecond timestamps */ + d = 1000000 / 32; + + /* determine base clock for timer source */ + if (dev_priv->chipset < 0x40) { + n = nouveau_hw_get_clock(dev, PLL_CORE); + } else + if (dev_priv->chipset == 0x40) { + /*XXX: figure this out */ + n = 0; + } else { + n = dev_priv->crystal; + m = 1; + while (n < (d * 2)) { + n += (n / m); + m++; + } + + nv_wr32(dev, 0x009220, m - 1); + } + + if (!n) { + NV_WARN(dev, "PTIMER: unknown input clock freq\n"); + if (!nv_rd32(dev, NV04_PTIMER_NUMERATOR) || + !nv_rd32(dev, NV04_PTIMER_DENOMINATOR)) { + nv_wr32(dev, NV04_PTIMER_NUMERATOR, 1); + nv_wr32(dev, NV04_PTIMER_DENOMINATOR, 1); + } + return 0; + } + + /* reduce ratio to acceptable values */ + while (((n % 5) == 0) && ((d % 5) == 0)) { + n /= 5; + d /= 5; + } + + while (((n % 2) == 0) && ((d % 2) == 0)) { + n /= 2; + d /= 2; + } + + while (n > 0xffff || d > 0xffff) { + n >>= 1; + d >>= 1; + } + + nv_wr32(dev, NV04_PTIMER_NUMERATOR, n); + nv_wr32(dev, NV04_PTIMER_DENOMINATOR, d); + return 0; +} + +u64 +nv04_timer_read(struct drm_device *dev) +{ + u32 hi, lo; + + do { + hi = nv_rd32(dev, NV04_PTIMER_TIME_1); + lo = nv_rd32(dev, NV04_PTIMER_TIME_0); + } while (hi != nv_rd32(dev, NV04_PTIMER_TIME_1)); + + return ((u64)hi << 32 | lo); +} + +void +nv04_timer_takedown(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv04_tv.c b/drivers/gpu/drm/nouveau/nv04_tv.c new file mode 100644 index 00000000..3eb605dd --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv04_tv.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2009 Francisco Jerez. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_crtc.h" +#include "nouveau_hw.h" +#include "drm_crtc_helper.h" + +#include "i2c/ch7006.h" + +static struct i2c_board_info nv04_tv_encoder_info[] = { + { + I2C_BOARD_INFO("ch7006", 0x75), + .platform_data = &(struct ch7006_encoder_params) { + CH7006_FORMAT_RGB24m12I, CH7006_CLOCK_MASTER, + 0, 0, 0, + CH7006_SYNC_SLAVE, CH7006_SYNC_SEPARATED, + CH7006_POUT_3_3V, CH7006_ACTIVE_HSYNC + } + }, + { } +}; + +int nv04_tv_identify(struct drm_device *dev, int i2c_index) +{ + return nouveau_i2c_identify(dev, "TV encoder", nv04_tv_encoder_info, + NULL, i2c_index); +} + + +#define PLLSEL_TV_CRTC1_MASK \ + (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1 \ + | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1) +#define PLLSEL_TV_CRTC2_MASK \ + (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2 \ + | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2) + +static void nv04_tv_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_mode_state *state = &dev_priv->mode_reg; + uint8_t crtc1A; + + NV_INFO(dev, "Setting dpms mode %d on TV encoder (output %d)\n", + mode, nv_encoder->dcb->index); + + state->pllsel &= ~(PLLSEL_TV_CRTC1_MASK | PLLSEL_TV_CRTC2_MASK); + + if (mode == DRM_MODE_DPMS_ON) { + int head = nouveau_crtc(encoder->crtc)->index; + crtc1A = NVReadVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX); + + state->pllsel |= head ? PLLSEL_TV_CRTC2_MASK : + PLLSEL_TV_CRTC1_MASK; + + /* Inhibit hsync */ + crtc1A |= 0x80; + + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX, crtc1A); + } + + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_PLL_COEFF_SELECT, state->pllsel); + + get_slave_funcs(encoder)->dpms(encoder, mode); +} + +static void nv04_tv_bind(struct drm_device *dev, int head, bool bind) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv04_crtc_reg *state = &dev_priv->mode_reg.crtc_reg[head]; + + state->tv_setup = 0; + + if (bind) + state->CRTC[NV_CIO_CRE_49] |= 0x10; + else + state->CRTC[NV_CIO_CRE_49] &= ~0x10; + + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_LCD__INDEX, + state->CRTC[NV_CIO_CRE_LCD__INDEX]); + NVWriteVgaCrtc(dev, head, NV_CIO_CRE_49, + state->CRTC[NV_CIO_CRE_49]); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_SETUP, + state->tv_setup); +} + +static void nv04_tv_prepare(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + int head = nouveau_crtc(encoder->crtc)->index; + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + + helper->dpms(encoder, DRM_MODE_DPMS_OFF); + + nv04_dfp_disable(dev, head); + + if (nv_two_heads(dev)) + nv04_tv_bind(dev, head ^ 1, false); + + nv04_tv_bind(dev, head, true); +} + +static void nv04_tv_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nv04_crtc_reg *regp = &dev_priv->mode_reg.crtc_reg[nv_crtc->index]; + + regp->tv_htotal = adjusted_mode->htotal; + regp->tv_vtotal = adjusted_mode->vtotal; + + /* These delay the TV signals with respect to the VGA port, + * they might be useful if we ever allow a CRTC to drive + * multiple outputs. + */ + regp->tv_hskew = 1; + regp->tv_hsync_delay = 1; + regp->tv_hsync_delay2 = 64; + regp->tv_vskew = 1; + regp->tv_vsync_delay = 1; + + get_slave_funcs(encoder)->mode_set(encoder, mode, adjusted_mode); +} + +static void nv04_tv_commit(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + + helper->dpms(encoder, DRM_MODE_DPMS_ON); + + NV_INFO(dev, "Output %s is running on CRTC %d using output %c\n", + drm_get_connector_name(&nouveau_encoder_connector_get(nv_encoder)->base), nv_crtc->index, + '@' + ffs(nv_encoder->dcb->or)); +} + +static void nv04_tv_destroy(struct drm_encoder *encoder) +{ + get_slave_funcs(encoder)->destroy(encoder); + drm_encoder_cleanup(encoder); + + kfree(encoder->helper_private); + kfree(nouveau_encoder(encoder)); +} + +static const struct drm_encoder_funcs nv04_tv_funcs = { + .destroy = nv04_tv_destroy, +}; + +int +nv04_tv_create(struct drm_connector *connector, struct dcb_entry *entry) +{ + struct nouveau_encoder *nv_encoder; + struct drm_encoder *encoder; + struct drm_device *dev = connector->dev; + struct drm_encoder_helper_funcs *hfuncs; + struct drm_encoder_slave_funcs *sfuncs; + struct nouveau_i2c_chan *i2c = + nouveau_i2c_find(dev, entry->i2c_index); + int type, ret; + + /* Ensure that we can talk to this encoder */ + type = nv04_tv_identify(dev, entry->i2c_index); + if (type < 0) + return type; + + /* Allocate the necessary memory */ + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + + hfuncs = kzalloc(sizeof(*hfuncs), GFP_KERNEL); + if (!hfuncs) { + ret = -ENOMEM; + goto fail_free; + } + + /* Initialize the common members */ + encoder = to_drm_encoder(nv_encoder); + + drm_encoder_init(dev, encoder, &nv04_tv_funcs, DRM_MODE_ENCODER_TVDAC); + drm_encoder_helper_add(encoder, hfuncs); + + encoder->possible_crtcs = entry->heads; + encoder->possible_clones = 0; + nv_encoder->dcb = entry; + nv_encoder->or = ffs(entry->or) - 1; + + /* Run the slave-specific initialization */ + ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), + &i2c->adapter, &nv04_tv_encoder_info[type]); + if (ret < 0) + goto fail_cleanup; + + /* Fill the function pointers */ + sfuncs = get_slave_funcs(encoder); + + *hfuncs = (struct drm_encoder_helper_funcs) { + .dpms = nv04_tv_dpms, + .save = sfuncs->save, + .restore = sfuncs->restore, + .mode_fixup = sfuncs->mode_fixup, + .prepare = nv04_tv_prepare, + .commit = nv04_tv_commit, + .mode_set = nv04_tv_mode_set, + .detect = sfuncs->detect, + }; + + /* Attach it to the specified connector. */ + sfuncs->create_resources(encoder, connector); + drm_mode_connector_attach_encoder(connector, encoder); + + return 0; + +fail_cleanup: + drm_encoder_cleanup(encoder); + kfree(hfuncs); +fail_free: + kfree(nv_encoder); + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nv10_fb.c b/drivers/gpu/drm/nouveau/nv10_fb.c new file mode 100644 index 00000000..420b1608 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv10_fb.c @@ -0,0 +1,104 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +void +nv10_fb_init_tile_region(struct drm_device *dev, int i, uint32_t addr, + uint32_t size, uint32_t pitch, uint32_t flags) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + tile->addr = 0x80000000 | addr; + tile->limit = max(1u, addr + size) - 1; + tile->pitch = pitch; +} + +void +nv10_fb_free_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + tile->addr = tile->limit = tile->pitch = tile->zcomp = 0; +} + +void +nv10_fb_set_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + nv_wr32(dev, NV10_PFB_TLIMIT(i), tile->limit); + nv_wr32(dev, NV10_PFB_TSIZE(i), tile->pitch); + nv_wr32(dev, NV10_PFB_TILE(i), tile->addr); +} + +int +nv1a_fb_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct pci_dev *bridge; + uint32_t mem, mib; + + bridge = pci_get_bus_and_slot(0, PCI_DEVFN(0, 1)); + if (!bridge) { + NV_ERROR(dev, "no bridge device\n"); + return 0; + } + + if (dev_priv->chipset == 0x1a) { + pci_read_config_dword(bridge, 0x7c, &mem); + mib = ((mem >> 6) & 31) + 1; + } else { + pci_read_config_dword(bridge, 0x84, &mem); + mib = ((mem >> 4) & 127) + 1; + } + + dev_priv->vram_size = mib * 1024 * 1024; + return 0; +} + +int +nv10_fb_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 fifo_data = nv_rd32(dev, NV04_PFB_FIFO_DATA); + u32 cfg0 = nv_rd32(dev, 0x100200); + + dev_priv->vram_size = fifo_data & NV10_PFB_FIFO_DATA_RAM_AMOUNT_MB_MASK; + + if (cfg0 & 0x00000001) + dev_priv->vram_type = NV_MEM_TYPE_DDR1; + else + dev_priv->vram_type = NV_MEM_TYPE_SDRAM; + + return 0; +} + +int +nv10_fb_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + int i; + + /* Turn all the tiling regions off. */ + pfb->num_tiles = NV10_PFB_TILE__SIZE; + for (i = 0; i < pfb->num_tiles; i++) + pfb->set_tile_region(dev, i); + + return 0; +} + +void +nv10_fb_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + int i; + + for (i = 0; i < pfb->num_tiles; i++) + pfb->free_tile_region(dev, i); +} diff --git a/drivers/gpu/drm/nouveau/nv10_fifo.c b/drivers/gpu/drm/nouveau/nv10_fifo.c new file mode 100644 index 00000000..d2ecbff4 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv10_fifo.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" + +#define NV10_RAMFC(c) (dev_priv->ramfc->pinst + ((c) * NV10_RAMFC__SIZE)) +#define NV10_RAMFC__SIZE ((dev_priv->chipset) >= 0x17 ? 64 : 32) + +int +nv10_fifo_channel_id(struct drm_device *dev) +{ + return nv_rd32(dev, NV03_PFIFO_CACHE1_PUSH1) & + NV10_PFIFO_CACHE1_PUSH1_CHID_MASK; +} + +int +nv10_fifo_create_context(struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct drm_device *dev = chan->dev; + uint32_t fc = NV10_RAMFC(chan->id); + int ret; + + ret = nouveau_gpuobj_new_fake(dev, NV10_RAMFC(chan->id), ~0, + NV10_RAMFC__SIZE, NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &chan->ramfc); + if (ret) + return ret; + + chan->user = ioremap(pci_resource_start(dev->pdev, 0) + + NV03_USER(chan->id), PAGE_SIZE); + if (!chan->user) + return -ENOMEM; + + /* Fill entries that are seen filled in dumps of nvidia driver just + * after channel's is put into DMA mode + */ + nv_wi32(dev, fc + 0, chan->pushbuf_base); + nv_wi32(dev, fc + 4, chan->pushbuf_base); + nv_wi32(dev, fc + 12, chan->pushbuf->pinst >> 4); + nv_wi32(dev, fc + 20, NV_PFIFO_CACHE1_DMA_FETCH_TRIG_128_BYTES | + NV_PFIFO_CACHE1_DMA_FETCH_SIZE_128_BYTES | + NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_8 | +#ifdef __BIG_ENDIAN + NV_PFIFO_CACHE1_BIG_ENDIAN | +#endif + 0); + + /* enable the fifo dma operation */ + nv_wr32(dev, NV04_PFIFO_MODE, + nv_rd32(dev, NV04_PFIFO_MODE) | (1 << chan->id)); + return 0; +} + +static void +nv10_fifo_do_load_context(struct drm_device *dev, int chid) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t fc = NV10_RAMFC(chid), tmp; + + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUT, nv_ri32(dev, fc + 0)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_GET, nv_ri32(dev, fc + 4)); + nv_wr32(dev, NV10_PFIFO_CACHE1_REF_CNT, nv_ri32(dev, fc + 8)); + + tmp = nv_ri32(dev, fc + 12); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_INSTANCE, tmp & 0xFFFF); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_DCOUNT, tmp >> 16); + + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_STATE, nv_ri32(dev, fc + 16)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_FETCH, nv_ri32(dev, fc + 20)); + nv_wr32(dev, NV04_PFIFO_CACHE1_ENGINE, nv_ri32(dev, fc + 24)); + nv_wr32(dev, NV04_PFIFO_CACHE1_PULL1, nv_ri32(dev, fc + 28)); + + if (dev_priv->chipset < 0x17) + goto out; + + nv_wr32(dev, NV10_PFIFO_CACHE1_ACQUIRE_VALUE, nv_ri32(dev, fc + 32)); + tmp = nv_ri32(dev, fc + 36); + nv_wr32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMESTAMP, tmp); + nv_wr32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMEOUT, nv_ri32(dev, fc + 40)); + nv_wr32(dev, NV10_PFIFO_CACHE1_SEMAPHORE, nv_ri32(dev, fc + 44)); + nv_wr32(dev, NV10_PFIFO_CACHE1_DMA_SUBROUTINE, nv_ri32(dev, fc + 48)); + +out: + nv_wr32(dev, NV03_PFIFO_CACHE1_GET, 0); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUT, 0); +} + +int +nv10_fifo_load_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + uint32_t tmp; + + nv10_fifo_do_load_context(dev, chan->id); + + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, + NV03_PFIFO_CACHE1_PUSH1_DMA | chan->id); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUSH, 1); + + /* Reset NV04_PFIFO_CACHE1_DMA_CTL_AT_INFO to INVALID */ + tmp = nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_CTL) & ~(1 << 31); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_CTL, tmp); + + return 0; +} + +int +nv10_fifo_unload_context(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + uint32_t fc, tmp; + int chid; + + chid = pfifo->channel_id(dev); + if (chid < 0 || chid >= dev_priv->engine.fifo.channels) + return 0; + fc = NV10_RAMFC(chid); + + nv_wi32(dev, fc + 0, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_PUT)); + nv_wi32(dev, fc + 4, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_GET)); + nv_wi32(dev, fc + 8, nv_rd32(dev, NV10_PFIFO_CACHE1_REF_CNT)); + tmp = nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_INSTANCE) & 0xFFFF; + tmp |= (nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_DCOUNT) << 16); + nv_wi32(dev, fc + 12, tmp); + nv_wi32(dev, fc + 16, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_STATE)); + nv_wi32(dev, fc + 20, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_FETCH)); + nv_wi32(dev, fc + 24, nv_rd32(dev, NV04_PFIFO_CACHE1_ENGINE)); + nv_wi32(dev, fc + 28, nv_rd32(dev, NV04_PFIFO_CACHE1_PULL1)); + + if (dev_priv->chipset < 0x17) + goto out; + + nv_wi32(dev, fc + 32, nv_rd32(dev, NV10_PFIFO_CACHE1_ACQUIRE_VALUE)); + tmp = nv_rd32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMESTAMP); + nv_wi32(dev, fc + 36, tmp); + nv_wi32(dev, fc + 40, nv_rd32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMEOUT)); + nv_wi32(dev, fc + 44, nv_rd32(dev, NV10_PFIFO_CACHE1_SEMAPHORE)); + nv_wi32(dev, fc + 48, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_GET)); + +out: + nv10_fifo_do_load_context(dev, pfifo->channels - 1); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, pfifo->channels - 1); + return 0; +} + +static void +nv10_fifo_init_reset(struct drm_device *dev) +{ + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) & ~NV_PMC_ENABLE_PFIFO); + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) | NV_PMC_ENABLE_PFIFO); + + nv_wr32(dev, 0x003224, 0x000f0078); + nv_wr32(dev, 0x002044, 0x0101ffff); + nv_wr32(dev, 0x002040, 0x000000ff); + nv_wr32(dev, 0x002500, 0x00000000); + nv_wr32(dev, 0x003000, 0x00000000); + nv_wr32(dev, 0x003050, 0x00000000); + + nv_wr32(dev, 0x003258, 0x00000000); + nv_wr32(dev, 0x003210, 0x00000000); + nv_wr32(dev, 0x003270, 0x00000000); +} + +static void +nv10_fifo_init_ramxx(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nv_wr32(dev, NV03_PFIFO_RAMHT, (0x03 << 24) /* search 128 */ | + ((dev_priv->ramht->bits - 9) << 16) | + (dev_priv->ramht->gpuobj->pinst >> 8)); + nv_wr32(dev, NV03_PFIFO_RAMRO, dev_priv->ramro->pinst >> 8); + + if (dev_priv->chipset < 0x17) { + nv_wr32(dev, NV03_PFIFO_RAMFC, dev_priv->ramfc->pinst >> 8); + } else { + nv_wr32(dev, NV03_PFIFO_RAMFC, (dev_priv->ramfc->pinst >> 8) | + (1 << 16) /* 64 Bytes entry*/); + /* XXX nvidia blob set bit 18, 21,23 for nv20 & nv30 */ + } +} + +static void +nv10_fifo_init_intr(struct drm_device *dev) +{ + nouveau_irq_register(dev, 8, nv04_fifo_isr); + nv_wr32(dev, 0x002100, 0xffffffff); + nv_wr32(dev, 0x002140, 0xffffffff); +} + +int +nv10_fifo_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + int i; + + nv10_fifo_init_reset(dev); + nv10_fifo_init_ramxx(dev); + + nv10_fifo_do_load_context(dev, pfifo->channels - 1); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, pfifo->channels - 1); + + nv10_fifo_init_intr(dev); + pfifo->enable(dev); + pfifo->reassign(dev, true); + + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + if (dev_priv->channels.ptr[i]) { + uint32_t mode = nv_rd32(dev, NV04_PFIFO_MODE); + nv_wr32(dev, NV04_PFIFO_MODE, mode | (1 << i)); + } + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv10_gpio.c b/drivers/gpu/drm/nouveau/nv10_gpio.c new file mode 100644 index 00000000..9d791800 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv10_gpio.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009 Francisco Jerez. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" +#include "nouveau_gpio.h" + +int +nv10_gpio_sense(struct drm_device *dev, int line) +{ + if (line < 2) { + line = line * 16; + line = NVReadCRTC(dev, 0, NV_PCRTC_GPIO) >> line; + return !!(line & 0x0100); + } else + if (line < 10) { + line = (line - 2) * 4; + line = NVReadCRTC(dev, 0, NV_PCRTC_GPIO_EXT) >> line; + return !!(line & 0x04); + } else + if (line < 14) { + line = (line - 10) * 4; + line = NVReadCRTC(dev, 0, NV_PCRTC_850) >> line; + return !!(line & 0x04); + } + + return -EINVAL; +} + +int +nv10_gpio_drive(struct drm_device *dev, int line, int dir, int out) +{ + u32 reg, mask, data; + + if (line < 2) { + line = line * 16; + reg = NV_PCRTC_GPIO; + mask = 0x00000011; + data = (dir << 4) | out; + } else + if (line < 10) { + line = (line - 2) * 4; + reg = NV_PCRTC_GPIO_EXT; + mask = 0x00000003; + data = (dir << 1) | out; + } else + if (line < 14) { + line = (line - 10) * 4; + reg = NV_PCRTC_850; + mask = 0x00000003; + data = (dir << 1) | out; + } else { + return -EINVAL; + } + + mask = NVReadCRTC(dev, 0, reg) & ~(mask << line); + NVWriteCRTC(dev, 0, reg, mask | (data << line)); + return 0; +} + +void +nv10_gpio_irq_enable(struct drm_device *dev, int line, bool on) +{ + u32 mask = 0x00010001 << line; + + nv_wr32(dev, 0x001104, mask); + nv_mask(dev, 0x001144, mask, on ? mask : 0); +} + +static void +nv10_gpio_isr(struct drm_device *dev) +{ + u32 intr = nv_rd32(dev, 0x1104); + u32 hi = (intr & 0x0000ffff) >> 0; + u32 lo = (intr & 0xffff0000) >> 16; + + nouveau_gpio_isr(dev, 0, hi | lo); + + nv_wr32(dev, 0x001104, intr); +} + +int +nv10_gpio_init(struct drm_device *dev) +{ + nv_wr32(dev, 0x001140, 0x00000000); + nv_wr32(dev, 0x001100, 0xffffffff); + nv_wr32(dev, 0x001144, 0x00000000); + nv_wr32(dev, 0x001104, 0xffffffff); + nouveau_irq_register(dev, 28, nv10_gpio_isr); /* PBUS */ + return 0; +} + +void +nv10_gpio_fini(struct drm_device *dev) +{ + nv_wr32(dev, 0x001140, 0x00000000); + nv_wr32(dev, 0x001144, 0x00000000); + nouveau_irq_unregister(dev, 28); +} diff --git a/drivers/gpu/drm/nouveau/nv10_graph.c b/drivers/gpu/drm/nouveau/nv10_graph.c new file mode 100644 index 00000000..7255e4a4 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv10_graph.c @@ -0,0 +1,1194 @@ +/* + * Copyright 2007 Matthieu CASTET <castet.matthieu@free.fr> + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drm.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" + +struct nv10_graph_engine { + struct nouveau_exec_engine base; +}; + +struct pipe_state { + uint32_t pipe_0x0000[0x040/4]; + uint32_t pipe_0x0040[0x010/4]; + uint32_t pipe_0x0200[0x0c0/4]; + uint32_t pipe_0x4400[0x080/4]; + uint32_t pipe_0x6400[0x3b0/4]; + uint32_t pipe_0x6800[0x2f0/4]; + uint32_t pipe_0x6c00[0x030/4]; + uint32_t pipe_0x7000[0x130/4]; + uint32_t pipe_0x7400[0x0c0/4]; + uint32_t pipe_0x7800[0x0c0/4]; +}; + +static int nv10_graph_ctx_regs[] = { + NV10_PGRAPH_CTX_SWITCH(0), + NV10_PGRAPH_CTX_SWITCH(1), + NV10_PGRAPH_CTX_SWITCH(2), + NV10_PGRAPH_CTX_SWITCH(3), + NV10_PGRAPH_CTX_SWITCH(4), + NV10_PGRAPH_CTX_CACHE(0, 0), + NV10_PGRAPH_CTX_CACHE(0, 1), + NV10_PGRAPH_CTX_CACHE(0, 2), + NV10_PGRAPH_CTX_CACHE(0, 3), + NV10_PGRAPH_CTX_CACHE(0, 4), + NV10_PGRAPH_CTX_CACHE(1, 0), + NV10_PGRAPH_CTX_CACHE(1, 1), + NV10_PGRAPH_CTX_CACHE(1, 2), + NV10_PGRAPH_CTX_CACHE(1, 3), + NV10_PGRAPH_CTX_CACHE(1, 4), + NV10_PGRAPH_CTX_CACHE(2, 0), + NV10_PGRAPH_CTX_CACHE(2, 1), + NV10_PGRAPH_CTX_CACHE(2, 2), + NV10_PGRAPH_CTX_CACHE(2, 3), + NV10_PGRAPH_CTX_CACHE(2, 4), + NV10_PGRAPH_CTX_CACHE(3, 0), + NV10_PGRAPH_CTX_CACHE(3, 1), + NV10_PGRAPH_CTX_CACHE(3, 2), + NV10_PGRAPH_CTX_CACHE(3, 3), + NV10_PGRAPH_CTX_CACHE(3, 4), + NV10_PGRAPH_CTX_CACHE(4, 0), + NV10_PGRAPH_CTX_CACHE(4, 1), + NV10_PGRAPH_CTX_CACHE(4, 2), + NV10_PGRAPH_CTX_CACHE(4, 3), + NV10_PGRAPH_CTX_CACHE(4, 4), + NV10_PGRAPH_CTX_CACHE(5, 0), + NV10_PGRAPH_CTX_CACHE(5, 1), + NV10_PGRAPH_CTX_CACHE(5, 2), + NV10_PGRAPH_CTX_CACHE(5, 3), + NV10_PGRAPH_CTX_CACHE(5, 4), + NV10_PGRAPH_CTX_CACHE(6, 0), + NV10_PGRAPH_CTX_CACHE(6, 1), + NV10_PGRAPH_CTX_CACHE(6, 2), + NV10_PGRAPH_CTX_CACHE(6, 3), + NV10_PGRAPH_CTX_CACHE(6, 4), + NV10_PGRAPH_CTX_CACHE(7, 0), + NV10_PGRAPH_CTX_CACHE(7, 1), + NV10_PGRAPH_CTX_CACHE(7, 2), + NV10_PGRAPH_CTX_CACHE(7, 3), + NV10_PGRAPH_CTX_CACHE(7, 4), + NV10_PGRAPH_CTX_USER, + NV04_PGRAPH_DMA_START_0, + NV04_PGRAPH_DMA_START_1, + NV04_PGRAPH_DMA_LENGTH, + NV04_PGRAPH_DMA_MISC, + NV10_PGRAPH_DMA_PITCH, + NV04_PGRAPH_BOFFSET0, + NV04_PGRAPH_BBASE0, + NV04_PGRAPH_BLIMIT0, + NV04_PGRAPH_BOFFSET1, + NV04_PGRAPH_BBASE1, + NV04_PGRAPH_BLIMIT1, + NV04_PGRAPH_BOFFSET2, + NV04_PGRAPH_BBASE2, + NV04_PGRAPH_BLIMIT2, + NV04_PGRAPH_BOFFSET3, + NV04_PGRAPH_BBASE3, + NV04_PGRAPH_BLIMIT3, + NV04_PGRAPH_BOFFSET4, + NV04_PGRAPH_BBASE4, + NV04_PGRAPH_BLIMIT4, + NV04_PGRAPH_BOFFSET5, + NV04_PGRAPH_BBASE5, + NV04_PGRAPH_BLIMIT5, + NV04_PGRAPH_BPITCH0, + NV04_PGRAPH_BPITCH1, + NV04_PGRAPH_BPITCH2, + NV04_PGRAPH_BPITCH3, + NV04_PGRAPH_BPITCH4, + NV10_PGRAPH_SURFACE, + NV10_PGRAPH_STATE, + NV04_PGRAPH_BSWIZZLE2, + NV04_PGRAPH_BSWIZZLE5, + NV04_PGRAPH_BPIXEL, + NV10_PGRAPH_NOTIFY, + NV04_PGRAPH_PATT_COLOR0, + NV04_PGRAPH_PATT_COLOR1, + NV04_PGRAPH_PATT_COLORRAM, /* 64 values from 0x400900 to 0x4009fc */ + 0x00400904, + 0x00400908, + 0x0040090c, + 0x00400910, + 0x00400914, + 0x00400918, + 0x0040091c, + 0x00400920, + 0x00400924, + 0x00400928, + 0x0040092c, + 0x00400930, + 0x00400934, + 0x00400938, + 0x0040093c, + 0x00400940, + 0x00400944, + 0x00400948, + 0x0040094c, + 0x00400950, + 0x00400954, + 0x00400958, + 0x0040095c, + 0x00400960, + 0x00400964, + 0x00400968, + 0x0040096c, + 0x00400970, + 0x00400974, + 0x00400978, + 0x0040097c, + 0x00400980, + 0x00400984, + 0x00400988, + 0x0040098c, + 0x00400990, + 0x00400994, + 0x00400998, + 0x0040099c, + 0x004009a0, + 0x004009a4, + 0x004009a8, + 0x004009ac, + 0x004009b0, + 0x004009b4, + 0x004009b8, + 0x004009bc, + 0x004009c0, + 0x004009c4, + 0x004009c8, + 0x004009cc, + 0x004009d0, + 0x004009d4, + 0x004009d8, + 0x004009dc, + 0x004009e0, + 0x004009e4, + 0x004009e8, + 0x004009ec, + 0x004009f0, + 0x004009f4, + 0x004009f8, + 0x004009fc, + NV04_PGRAPH_PATTERN, /* 2 values from 0x400808 to 0x40080c */ + 0x0040080c, + NV04_PGRAPH_PATTERN_SHAPE, + NV03_PGRAPH_MONO_COLOR0, + NV04_PGRAPH_ROP3, + NV04_PGRAPH_CHROMA, + NV04_PGRAPH_BETA_AND, + NV04_PGRAPH_BETA_PREMULT, + 0x00400e70, + 0x00400e74, + 0x00400e78, + 0x00400e7c, + 0x00400e80, + 0x00400e84, + 0x00400e88, + 0x00400e8c, + 0x00400ea0, + 0x00400ea4, + 0x00400ea8, + 0x00400e90, + 0x00400e94, + 0x00400e98, + 0x00400e9c, + NV10_PGRAPH_WINDOWCLIP_HORIZONTAL, /* 8 values from 0x400f00-0x400f1c */ + NV10_PGRAPH_WINDOWCLIP_VERTICAL, /* 8 values from 0x400f20-0x400f3c */ + 0x00400f04, + 0x00400f24, + 0x00400f08, + 0x00400f28, + 0x00400f0c, + 0x00400f2c, + 0x00400f10, + 0x00400f30, + 0x00400f14, + 0x00400f34, + 0x00400f18, + 0x00400f38, + 0x00400f1c, + 0x00400f3c, + NV10_PGRAPH_XFMODE0, + NV10_PGRAPH_XFMODE1, + NV10_PGRAPH_GLOBALSTATE0, + NV10_PGRAPH_GLOBALSTATE1, + NV04_PGRAPH_STORED_FMT, + NV04_PGRAPH_SOURCE_COLOR, + NV03_PGRAPH_ABS_X_RAM, /* 32 values from 0x400400 to 0x40047c */ + NV03_PGRAPH_ABS_Y_RAM, /* 32 values from 0x400480 to 0x4004fc */ + 0x00400404, + 0x00400484, + 0x00400408, + 0x00400488, + 0x0040040c, + 0x0040048c, + 0x00400410, + 0x00400490, + 0x00400414, + 0x00400494, + 0x00400418, + 0x00400498, + 0x0040041c, + 0x0040049c, + 0x00400420, + 0x004004a0, + 0x00400424, + 0x004004a4, + 0x00400428, + 0x004004a8, + 0x0040042c, + 0x004004ac, + 0x00400430, + 0x004004b0, + 0x00400434, + 0x004004b4, + 0x00400438, + 0x004004b8, + 0x0040043c, + 0x004004bc, + 0x00400440, + 0x004004c0, + 0x00400444, + 0x004004c4, + 0x00400448, + 0x004004c8, + 0x0040044c, + 0x004004cc, + 0x00400450, + 0x004004d0, + 0x00400454, + 0x004004d4, + 0x00400458, + 0x004004d8, + 0x0040045c, + 0x004004dc, + 0x00400460, + 0x004004e0, + 0x00400464, + 0x004004e4, + 0x00400468, + 0x004004e8, + 0x0040046c, + 0x004004ec, + 0x00400470, + 0x004004f0, + 0x00400474, + 0x004004f4, + 0x00400478, + 0x004004f8, + 0x0040047c, + 0x004004fc, + NV03_PGRAPH_ABS_UCLIP_XMIN, + NV03_PGRAPH_ABS_UCLIP_XMAX, + NV03_PGRAPH_ABS_UCLIP_YMIN, + NV03_PGRAPH_ABS_UCLIP_YMAX, + 0x00400550, + 0x00400558, + 0x00400554, + 0x0040055c, + NV03_PGRAPH_ABS_UCLIPA_XMIN, + NV03_PGRAPH_ABS_UCLIPA_XMAX, + NV03_PGRAPH_ABS_UCLIPA_YMIN, + NV03_PGRAPH_ABS_UCLIPA_YMAX, + NV03_PGRAPH_ABS_ICLIP_XMAX, + NV03_PGRAPH_ABS_ICLIP_YMAX, + NV03_PGRAPH_XY_LOGIC_MISC0, + NV03_PGRAPH_XY_LOGIC_MISC1, + NV03_PGRAPH_XY_LOGIC_MISC2, + NV03_PGRAPH_XY_LOGIC_MISC3, + NV03_PGRAPH_CLIPX_0, + NV03_PGRAPH_CLIPX_1, + NV03_PGRAPH_CLIPY_0, + NV03_PGRAPH_CLIPY_1, + NV10_PGRAPH_COMBINER0_IN_ALPHA, + NV10_PGRAPH_COMBINER1_IN_ALPHA, + NV10_PGRAPH_COMBINER0_IN_RGB, + NV10_PGRAPH_COMBINER1_IN_RGB, + NV10_PGRAPH_COMBINER_COLOR0, + NV10_PGRAPH_COMBINER_COLOR1, + NV10_PGRAPH_COMBINER0_OUT_ALPHA, + NV10_PGRAPH_COMBINER1_OUT_ALPHA, + NV10_PGRAPH_COMBINER0_OUT_RGB, + NV10_PGRAPH_COMBINER1_OUT_RGB, + NV10_PGRAPH_COMBINER_FINAL0, + NV10_PGRAPH_COMBINER_FINAL1, + 0x00400e00, + 0x00400e04, + 0x00400e08, + 0x00400e0c, + 0x00400e10, + 0x00400e14, + 0x00400e18, + 0x00400e1c, + 0x00400e20, + 0x00400e24, + 0x00400e28, + 0x00400e2c, + 0x00400e30, + 0x00400e34, + 0x00400e38, + 0x00400e3c, + NV04_PGRAPH_PASSTHRU_0, + NV04_PGRAPH_PASSTHRU_1, + NV04_PGRAPH_PASSTHRU_2, + NV10_PGRAPH_DIMX_TEXTURE, + NV10_PGRAPH_WDIMX_TEXTURE, + NV10_PGRAPH_DVD_COLORFMT, + NV10_PGRAPH_SCALED_FORMAT, + NV04_PGRAPH_MISC24_0, + NV04_PGRAPH_MISC24_1, + NV04_PGRAPH_MISC24_2, + NV03_PGRAPH_X_MISC, + NV03_PGRAPH_Y_MISC, + NV04_PGRAPH_VALID1, + NV04_PGRAPH_VALID2, +}; + +static int nv17_graph_ctx_regs[] = { + NV10_PGRAPH_DEBUG_4, + 0x004006b0, + 0x00400eac, + 0x00400eb0, + 0x00400eb4, + 0x00400eb8, + 0x00400ebc, + 0x00400ec0, + 0x00400ec4, + 0x00400ec8, + 0x00400ecc, + 0x00400ed0, + 0x00400ed4, + 0x00400ed8, + 0x00400edc, + 0x00400ee0, + 0x00400a00, + 0x00400a04, +}; + +struct graph_state { + int nv10[ARRAY_SIZE(nv10_graph_ctx_regs)]; + int nv17[ARRAY_SIZE(nv17_graph_ctx_regs)]; + struct pipe_state pipe_state; + uint32_t lma_window[4]; +}; + +#define PIPE_SAVE(dev, state, addr) \ + do { \ + int __i; \ + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, addr); \ + for (__i = 0; __i < ARRAY_SIZE(state); __i++) \ + state[__i] = nv_rd32(dev, NV10_PGRAPH_PIPE_DATA); \ + } while (0) + +#define PIPE_RESTORE(dev, state, addr) \ + do { \ + int __i; \ + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, addr); \ + for (__i = 0; __i < ARRAY_SIZE(state); __i++) \ + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, state[__i]); \ + } while (0) + +static void nv10_graph_save_pipe(struct nouveau_channel *chan) +{ + struct graph_state *pgraph_ctx = chan->engctx[NVOBJ_ENGINE_GR]; + struct pipe_state *pipe = &pgraph_ctx->pipe_state; + struct drm_device *dev = chan->dev; + + PIPE_SAVE(dev, pipe->pipe_0x4400, 0x4400); + PIPE_SAVE(dev, pipe->pipe_0x0200, 0x0200); + PIPE_SAVE(dev, pipe->pipe_0x6400, 0x6400); + PIPE_SAVE(dev, pipe->pipe_0x6800, 0x6800); + PIPE_SAVE(dev, pipe->pipe_0x6c00, 0x6c00); + PIPE_SAVE(dev, pipe->pipe_0x7000, 0x7000); + PIPE_SAVE(dev, pipe->pipe_0x7400, 0x7400); + PIPE_SAVE(dev, pipe->pipe_0x7800, 0x7800); + PIPE_SAVE(dev, pipe->pipe_0x0040, 0x0040); + PIPE_SAVE(dev, pipe->pipe_0x0000, 0x0000); +} + +static void nv10_graph_load_pipe(struct nouveau_channel *chan) +{ + struct graph_state *pgraph_ctx = chan->engctx[NVOBJ_ENGINE_GR]; + struct pipe_state *pipe = &pgraph_ctx->pipe_state; + struct drm_device *dev = chan->dev; + uint32_t xfmode0, xfmode1; + int i; + + nouveau_wait_for_idle(dev); + /* XXX check haiku comments */ + xfmode0 = nv_rd32(dev, NV10_PGRAPH_XFMODE0); + xfmode1 = nv_rd32(dev, NV10_PGRAPH_XFMODE1); + nv_wr32(dev, NV10_PGRAPH_XFMODE0, 0x10000000); + nv_wr32(dev, NV10_PGRAPH_XFMODE1, 0x00000000); + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x000064c0); + for (i = 0; i < 4; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x3f800000); + for (i = 0; i < 4; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x00000000); + + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x00006ab0); + for (i = 0; i < 3; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x3f800000); + + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x00006a80); + for (i = 0; i < 3; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x00000000); + + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x00000040); + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x00000008); + + + PIPE_RESTORE(dev, pipe->pipe_0x0200, 0x0200); + nouveau_wait_for_idle(dev); + + /* restore XFMODE */ + nv_wr32(dev, NV10_PGRAPH_XFMODE0, xfmode0); + nv_wr32(dev, NV10_PGRAPH_XFMODE1, xfmode1); + PIPE_RESTORE(dev, pipe->pipe_0x6400, 0x6400); + PIPE_RESTORE(dev, pipe->pipe_0x6800, 0x6800); + PIPE_RESTORE(dev, pipe->pipe_0x6c00, 0x6c00); + PIPE_RESTORE(dev, pipe->pipe_0x7000, 0x7000); + PIPE_RESTORE(dev, pipe->pipe_0x7400, 0x7400); + PIPE_RESTORE(dev, pipe->pipe_0x7800, 0x7800); + PIPE_RESTORE(dev, pipe->pipe_0x4400, 0x4400); + PIPE_RESTORE(dev, pipe->pipe_0x0000, 0x0000); + PIPE_RESTORE(dev, pipe->pipe_0x0040, 0x0040); + nouveau_wait_for_idle(dev); +} + +static void nv10_graph_create_pipe(struct nouveau_channel *chan) +{ + struct graph_state *pgraph_ctx = chan->engctx[NVOBJ_ENGINE_GR]; + struct pipe_state *fifo_pipe_state = &pgraph_ctx->pipe_state; + struct drm_device *dev = chan->dev; + uint32_t *fifo_pipe_state_addr; + int i; +#define PIPE_INIT(addr) \ + do { \ + fifo_pipe_state_addr = fifo_pipe_state->pipe_##addr; \ + } while (0) +#define PIPE_INIT_END(addr) \ + do { \ + uint32_t *__end_addr = fifo_pipe_state->pipe_##addr + \ + ARRAY_SIZE(fifo_pipe_state->pipe_##addr); \ + if (fifo_pipe_state_addr != __end_addr) \ + NV_ERROR(dev, "incomplete pipe init for 0x%x : %p/%p\n", \ + addr, fifo_pipe_state_addr, __end_addr); \ + } while (0) +#define NV_WRITE_PIPE_INIT(value) *(fifo_pipe_state_addr++) = value + + PIPE_INIT(0x0200); + for (i = 0; i < 48; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x0200); + + PIPE_INIT(0x6400); + for (i = 0; i < 211; i++) + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x3f800000); + NV_WRITE_PIPE_INIT(0x40000000); + NV_WRITE_PIPE_INIT(0x40000000); + NV_WRITE_PIPE_INIT(0x40000000); + NV_WRITE_PIPE_INIT(0x40000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x3f800000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x3f000000); + NV_WRITE_PIPE_INIT(0x3f000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x3f800000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x3f800000); + NV_WRITE_PIPE_INIT(0x3f800000); + NV_WRITE_PIPE_INIT(0x3f800000); + NV_WRITE_PIPE_INIT(0x3f800000); + PIPE_INIT_END(0x6400); + + PIPE_INIT(0x6800); + for (i = 0; i < 162; i++) + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x3f800000); + for (i = 0; i < 25; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x6800); + + PIPE_INIT(0x6c00); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0xbf800000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x6c00); + + PIPE_INIT(0x7000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x00000000); + NV_WRITE_PIPE_INIT(0x7149f2ca); + for (i = 0; i < 35; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x7000); + + PIPE_INIT(0x7400); + for (i = 0; i < 48; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x7400); + + PIPE_INIT(0x7800); + for (i = 0; i < 48; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x7800); + + PIPE_INIT(0x4400); + for (i = 0; i < 32; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x4400); + + PIPE_INIT(0x0000); + for (i = 0; i < 16; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x0000); + + PIPE_INIT(0x0040); + for (i = 0; i < 4; i++) + NV_WRITE_PIPE_INIT(0x00000000); + PIPE_INIT_END(0x0040); + +#undef PIPE_INIT +#undef PIPE_INIT_END +#undef NV_WRITE_PIPE_INIT +} + +static int nv10_graph_ctx_regs_find_offset(struct drm_device *dev, int reg) +{ + int i; + for (i = 0; i < ARRAY_SIZE(nv10_graph_ctx_regs); i++) { + if (nv10_graph_ctx_regs[i] == reg) + return i; + } + NV_ERROR(dev, "unknow offset nv10_ctx_regs %d\n", reg); + return -1; +} + +static int nv17_graph_ctx_regs_find_offset(struct drm_device *dev, int reg) +{ + int i; + for (i = 0; i < ARRAY_SIZE(nv17_graph_ctx_regs); i++) { + if (nv17_graph_ctx_regs[i] == reg) + return i; + } + NV_ERROR(dev, "unknow offset nv17_ctx_regs %d\n", reg); + return -1; +} + +static void nv10_graph_load_dma_vtxbuf(struct nouveau_channel *chan, + uint32_t inst) +{ + struct drm_device *dev = chan->dev; + uint32_t st2, st2_dl, st2_dh, fifo_ptr, fifo[0x60/4]; + uint32_t ctx_user, ctx_switch[5]; + int i, subchan = -1; + + /* NV10TCL_DMA_VTXBUF (method 0x18c) modifies hidden state + * that cannot be restored via MMIO. Do it through the FIFO + * instead. + */ + + /* Look for a celsius object */ + for (i = 0; i < 8; i++) { + int class = nv_rd32(dev, NV10_PGRAPH_CTX_CACHE(i, 0)) & 0xfff; + + if (class == 0x56 || class == 0x96 || class == 0x99) { + subchan = i; + break; + } + } + + if (subchan < 0 || !inst) + return; + + /* Save the current ctx object */ + ctx_user = nv_rd32(dev, NV10_PGRAPH_CTX_USER); + for (i = 0; i < 5; i++) + ctx_switch[i] = nv_rd32(dev, NV10_PGRAPH_CTX_SWITCH(i)); + + /* Save the FIFO state */ + st2 = nv_rd32(dev, NV10_PGRAPH_FFINTFC_ST2); + st2_dl = nv_rd32(dev, NV10_PGRAPH_FFINTFC_ST2_DL); + st2_dh = nv_rd32(dev, NV10_PGRAPH_FFINTFC_ST2_DH); + fifo_ptr = nv_rd32(dev, NV10_PGRAPH_FFINTFC_FIFO_PTR); + + for (i = 0; i < ARRAY_SIZE(fifo); i++) + fifo[i] = nv_rd32(dev, 0x4007a0 + 4 * i); + + /* Switch to the celsius subchannel */ + for (i = 0; i < 5; i++) + nv_wr32(dev, NV10_PGRAPH_CTX_SWITCH(i), + nv_rd32(dev, NV10_PGRAPH_CTX_CACHE(subchan, i))); + nv_mask(dev, NV10_PGRAPH_CTX_USER, 0xe000, subchan << 13); + + /* Inject NV10TCL_DMA_VTXBUF */ + nv_wr32(dev, NV10_PGRAPH_FFINTFC_FIFO_PTR, 0); + nv_wr32(dev, NV10_PGRAPH_FFINTFC_ST2, + 0x2c000000 | chan->id << 20 | subchan << 16 | 0x18c); + nv_wr32(dev, NV10_PGRAPH_FFINTFC_ST2_DL, inst); + nv_mask(dev, NV10_PGRAPH_CTX_CONTROL, 0, 0x10000); + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000001); + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000000); + + /* Restore the FIFO state */ + for (i = 0; i < ARRAY_SIZE(fifo); i++) + nv_wr32(dev, 0x4007a0 + 4 * i, fifo[i]); + + nv_wr32(dev, NV10_PGRAPH_FFINTFC_FIFO_PTR, fifo_ptr); + nv_wr32(dev, NV10_PGRAPH_FFINTFC_ST2, st2); + nv_wr32(dev, NV10_PGRAPH_FFINTFC_ST2_DL, st2_dl); + nv_wr32(dev, NV10_PGRAPH_FFINTFC_ST2_DH, st2_dh); + + /* Restore the current ctx object */ + for (i = 0; i < 5; i++) + nv_wr32(dev, NV10_PGRAPH_CTX_SWITCH(i), ctx_switch[i]); + nv_wr32(dev, NV10_PGRAPH_CTX_USER, ctx_user); +} + +static int +nv10_graph_load_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct graph_state *pgraph_ctx = chan->engctx[NVOBJ_ENGINE_GR]; + uint32_t tmp; + int i; + + for (i = 0; i < ARRAY_SIZE(nv10_graph_ctx_regs); i++) + nv_wr32(dev, nv10_graph_ctx_regs[i], pgraph_ctx->nv10[i]); + if (dev_priv->chipset >= 0x17) { + for (i = 0; i < ARRAY_SIZE(nv17_graph_ctx_regs); i++) + nv_wr32(dev, nv17_graph_ctx_regs[i], + pgraph_ctx->nv17[i]); + } + + nv10_graph_load_pipe(chan); + nv10_graph_load_dma_vtxbuf(chan, (nv_rd32(dev, NV10_PGRAPH_GLOBALSTATE1) + & 0xffff)); + + nv_wr32(dev, NV10_PGRAPH_CTX_CONTROL, 0x10010100); + tmp = nv_rd32(dev, NV10_PGRAPH_CTX_USER); + nv_wr32(dev, NV10_PGRAPH_CTX_USER, (tmp & 0xffffff) | chan->id << 24); + tmp = nv_rd32(dev, NV10_PGRAPH_FFINTFC_ST2); + nv_wr32(dev, NV10_PGRAPH_FFINTFC_ST2, tmp & 0xcfffffff); + return 0; +} + +static int +nv10_graph_unload_context(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_channel *chan; + struct graph_state *ctx; + uint32_t tmp; + int i; + + chan = nv10_graph_channel(dev); + if (!chan) + return 0; + ctx = chan->engctx[NVOBJ_ENGINE_GR]; + + for (i = 0; i < ARRAY_SIZE(nv10_graph_ctx_regs); i++) + ctx->nv10[i] = nv_rd32(dev, nv10_graph_ctx_regs[i]); + + if (dev_priv->chipset >= 0x17) { + for (i = 0; i < ARRAY_SIZE(nv17_graph_ctx_regs); i++) + ctx->nv17[i] = nv_rd32(dev, nv17_graph_ctx_regs[i]); + } + + nv10_graph_save_pipe(chan); + + nv_wr32(dev, NV10_PGRAPH_CTX_CONTROL, 0x10000000); + tmp = nv_rd32(dev, NV10_PGRAPH_CTX_USER) & 0x00ffffff; + tmp |= (pfifo->channels - 1) << 24; + nv_wr32(dev, NV10_PGRAPH_CTX_USER, tmp); + return 0; +} + +static void +nv10_graph_context_switch(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = NULL; + int chid; + + nouveau_wait_for_idle(dev); + + /* If previous context is valid, we need to save it */ + nv10_graph_unload_context(dev); + + /* Load context for next channel */ + chid = (nv_rd32(dev, NV04_PGRAPH_TRAPPED_ADDR) >> 20) & 0x1f; + chan = dev_priv->channels.ptr[chid]; + if (chan && chan->engctx[NVOBJ_ENGINE_GR]) + nv10_graph_load_context(chan); +} + +#define NV_WRITE_CTX(reg, val) do { \ + int offset = nv10_graph_ctx_regs_find_offset(dev, reg); \ + if (offset > 0) \ + pgraph_ctx->nv10[offset] = val; \ + } while (0) + +#define NV17_WRITE_CTX(reg, val) do { \ + int offset = nv17_graph_ctx_regs_find_offset(dev, reg); \ + if (offset > 0) \ + pgraph_ctx->nv17[offset] = val; \ + } while (0) + +struct nouveau_channel * +nv10_graph_channel(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chid = dev_priv->engine.fifo.channels; + + if (nv_rd32(dev, NV10_PGRAPH_CTX_CONTROL) & 0x00010000) + chid = nv_rd32(dev, NV10_PGRAPH_CTX_USER) >> 24; + + if (chid >= dev_priv->engine.fifo.channels) + return NULL; + + return dev_priv->channels.ptr[chid]; +} + +static int +nv10_graph_context_new(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct graph_state *pgraph_ctx; + + NV_DEBUG(dev, "nv10_graph_context_create %d\n", chan->id); + + pgraph_ctx = kzalloc(sizeof(*pgraph_ctx), GFP_KERNEL); + if (pgraph_ctx == NULL) + return -ENOMEM; + chan->engctx[engine] = pgraph_ctx; + + NV_WRITE_CTX(0x00400e88, 0x08000000); + NV_WRITE_CTX(0x00400e9c, 0x4b7fffff); + NV_WRITE_CTX(NV03_PGRAPH_XY_LOGIC_MISC0, 0x0001ffff); + NV_WRITE_CTX(0x00400e10, 0x00001000); + NV_WRITE_CTX(0x00400e14, 0x00001000); + NV_WRITE_CTX(0x00400e30, 0x00080008); + NV_WRITE_CTX(0x00400e34, 0x00080008); + if (dev_priv->chipset >= 0x17) { + /* is it really needed ??? */ + NV17_WRITE_CTX(NV10_PGRAPH_DEBUG_4, + nv_rd32(dev, NV10_PGRAPH_DEBUG_4)); + NV17_WRITE_CTX(0x004006b0, nv_rd32(dev, 0x004006b0)); + NV17_WRITE_CTX(0x00400eac, 0x0fff0000); + NV17_WRITE_CTX(0x00400eb0, 0x0fff0000); + NV17_WRITE_CTX(0x00400ec0, 0x00000080); + NV17_WRITE_CTX(0x00400ed0, 0x00000080); + } + NV_WRITE_CTX(NV10_PGRAPH_CTX_USER, chan->id << 24); + + nv10_graph_create_pipe(chan); + return 0; +} + +static void +nv10_graph_context_del(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct graph_state *pgraph_ctx = chan->engctx[engine]; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000000); + + /* Unload the context if it's the currently active one */ + if (nv10_graph_channel(dev) == chan) + nv10_graph_unload_context(dev); + + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + /* Free the context resources */ + chan->engctx[engine] = NULL; + kfree(pgraph_ctx); +} + +static void +nv10_graph_set_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + nv_wr32(dev, NV10_PGRAPH_TLIMIT(i), tile->limit); + nv_wr32(dev, NV10_PGRAPH_TSIZE(i), tile->pitch); + nv_wr32(dev, NV10_PGRAPH_TILE(i), tile->addr); +} + +static int +nv10_graph_init(struct drm_device *dev, int engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 tmp; + int i; + + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) & + ~NV_PMC_ENABLE_PGRAPH); + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) | + NV_PMC_ENABLE_PGRAPH); + + nv_wr32(dev, NV03_PGRAPH_INTR , 0xFFFFFFFF); + nv_wr32(dev, NV03_PGRAPH_INTR_EN, 0xFFFFFFFF); + + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0xFFFFFFFF); + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0x00000000); + nv_wr32(dev, NV04_PGRAPH_DEBUG_1, 0x00118700); + /* nv_wr32(dev, NV04_PGRAPH_DEBUG_2, 0x24E00810); */ /* 0x25f92ad9 */ + nv_wr32(dev, NV04_PGRAPH_DEBUG_2, 0x25f92ad9); + nv_wr32(dev, NV04_PGRAPH_DEBUG_3, 0x55DE0830 | + (1<<29) | + (1<<31)); + if (dev_priv->chipset >= 0x17) { + nv_wr32(dev, NV10_PGRAPH_DEBUG_4, 0x1f000000); + nv_wr32(dev, 0x400a10, 0x3ff3fb6); + nv_wr32(dev, 0x400838, 0x2f8684); + nv_wr32(dev, 0x40083c, 0x115f3f); + nv_wr32(dev, 0x004006b0, 0x40000020); + } else + nv_wr32(dev, NV10_PGRAPH_DEBUG_4, 0x00000000); + + /* Turn all the tiling regions off. */ + for (i = 0; i < NV10_PFB_TILE__SIZE; i++) + nv10_graph_set_tile_region(dev, i); + + nv_wr32(dev, NV10_PGRAPH_CTX_SWITCH(0), 0x00000000); + nv_wr32(dev, NV10_PGRAPH_CTX_SWITCH(1), 0x00000000); + nv_wr32(dev, NV10_PGRAPH_CTX_SWITCH(2), 0x00000000); + nv_wr32(dev, NV10_PGRAPH_CTX_SWITCH(3), 0x00000000); + nv_wr32(dev, NV10_PGRAPH_CTX_SWITCH(4), 0x00000000); + nv_wr32(dev, NV10_PGRAPH_STATE, 0xFFFFFFFF); + + tmp = nv_rd32(dev, NV10_PGRAPH_CTX_USER) & 0x00ffffff; + tmp |= (dev_priv->engine.fifo.channels - 1) << 24; + nv_wr32(dev, NV10_PGRAPH_CTX_USER, tmp); + nv_wr32(dev, NV10_PGRAPH_CTX_CONTROL, 0x10000100); + nv_wr32(dev, NV10_PGRAPH_FFINTFC_ST2, 0x08000000); + + return 0; +} + +static int +nv10_graph_fini(struct drm_device *dev, int engine, bool suspend) +{ + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000000); + if (!nv_wait(dev, NV04_PGRAPH_STATUS, ~0, 0) && suspend) { + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000001); + return -EBUSY; + } + nv10_graph_unload_context(dev); + nv_wr32(dev, NV03_PGRAPH_INTR_EN, 0x00000000); + return 0; +} + +static int +nv17_graph_mthd_lma_window(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + struct graph_state *ctx = chan->engctx[NVOBJ_ENGINE_GR]; + struct drm_device *dev = chan->dev; + struct pipe_state *pipe = &ctx->pipe_state; + uint32_t pipe_0x0040[1], pipe_0x64c0[8], pipe_0x6a80[3], pipe_0x6ab0[3]; + uint32_t xfmode0, xfmode1; + int i; + + ctx->lma_window[(mthd - 0x1638) / 4] = data; + + if (mthd != 0x1644) + return 0; + + nouveau_wait_for_idle(dev); + + PIPE_SAVE(dev, pipe_0x0040, 0x0040); + PIPE_SAVE(dev, pipe->pipe_0x0200, 0x0200); + + PIPE_RESTORE(dev, ctx->lma_window, 0x6790); + + nouveau_wait_for_idle(dev); + + xfmode0 = nv_rd32(dev, NV10_PGRAPH_XFMODE0); + xfmode1 = nv_rd32(dev, NV10_PGRAPH_XFMODE1); + + PIPE_SAVE(dev, pipe->pipe_0x4400, 0x4400); + PIPE_SAVE(dev, pipe_0x64c0, 0x64c0); + PIPE_SAVE(dev, pipe_0x6ab0, 0x6ab0); + PIPE_SAVE(dev, pipe_0x6a80, 0x6a80); + + nouveau_wait_for_idle(dev); + + nv_wr32(dev, NV10_PGRAPH_XFMODE0, 0x10000000); + nv_wr32(dev, NV10_PGRAPH_XFMODE1, 0x00000000); + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x000064c0); + for (i = 0; i < 4; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x3f800000); + for (i = 0; i < 4; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x00000000); + + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x00006ab0); + for (i = 0; i < 3; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x3f800000); + + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x00006a80); + for (i = 0; i < 3; i++) + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x00000000); + + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x00000040); + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x00000008); + + PIPE_RESTORE(dev, pipe->pipe_0x0200, 0x0200); + + nouveau_wait_for_idle(dev); + + PIPE_RESTORE(dev, pipe_0x0040, 0x0040); + + nv_wr32(dev, NV10_PGRAPH_XFMODE0, xfmode0); + nv_wr32(dev, NV10_PGRAPH_XFMODE1, xfmode1); + + PIPE_RESTORE(dev, pipe_0x64c0, 0x64c0); + PIPE_RESTORE(dev, pipe_0x6ab0, 0x6ab0); + PIPE_RESTORE(dev, pipe_0x6a80, 0x6a80); + PIPE_RESTORE(dev, pipe->pipe_0x4400, 0x4400); + + nv_wr32(dev, NV10_PGRAPH_PIPE_ADDRESS, 0x000000c0); + nv_wr32(dev, NV10_PGRAPH_PIPE_DATA, 0x00000000); + + nouveau_wait_for_idle(dev); + + return 0; +} + +static int +nv17_graph_mthd_lma_enable(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + struct drm_device *dev = chan->dev; + + nouveau_wait_for_idle(dev); + + nv_wr32(dev, NV10_PGRAPH_DEBUG_4, + nv_rd32(dev, NV10_PGRAPH_DEBUG_4) | 0x1 << 8); + nv_wr32(dev, 0x004006b0, + nv_rd32(dev, 0x004006b0) | 0x8 << 24); + + return 0; +} + +struct nouveau_bitfield nv10_graph_intr[] = { + { NV_PGRAPH_INTR_NOTIFY, "NOTIFY" }, + { NV_PGRAPH_INTR_ERROR, "ERROR" }, + {} +}; + +struct nouveau_bitfield nv10_graph_nstatus[] = { + { NV10_PGRAPH_NSTATUS_STATE_IN_USE, "STATE_IN_USE" }, + { NV10_PGRAPH_NSTATUS_INVALID_STATE, "INVALID_STATE" }, + { NV10_PGRAPH_NSTATUS_BAD_ARGUMENT, "BAD_ARGUMENT" }, + { NV10_PGRAPH_NSTATUS_PROTECTION_FAULT, "PROTECTION_FAULT" }, + {} +}; + +static void +nv10_graph_isr(struct drm_device *dev) +{ + u32 stat; + + while ((stat = nv_rd32(dev, NV03_PGRAPH_INTR))) { + u32 nsource = nv_rd32(dev, NV03_PGRAPH_NSOURCE); + u32 nstatus = nv_rd32(dev, NV03_PGRAPH_NSTATUS); + u32 addr = nv_rd32(dev, NV04_PGRAPH_TRAPPED_ADDR); + u32 chid = (addr & 0x01f00000) >> 20; + u32 subc = (addr & 0x00070000) >> 16; + u32 mthd = (addr & 0x00001ffc); + u32 data = nv_rd32(dev, NV04_PGRAPH_TRAPPED_DATA); + u32 class = nv_rd32(dev, 0x400160 + subc * 4) & 0xfff; + u32 show = stat; + + if (stat & NV_PGRAPH_INTR_ERROR) { + if (nsource & NV03_PGRAPH_NSOURCE_ILLEGAL_MTHD) { + if (!nouveau_gpuobj_mthd_call2(dev, chid, class, mthd, data)) + show &= ~NV_PGRAPH_INTR_ERROR; + } + } + + if (stat & NV_PGRAPH_INTR_CONTEXT_SWITCH) { + nv_wr32(dev, NV03_PGRAPH_INTR, NV_PGRAPH_INTR_CONTEXT_SWITCH); + stat &= ~NV_PGRAPH_INTR_CONTEXT_SWITCH; + show &= ~NV_PGRAPH_INTR_CONTEXT_SWITCH; + nv10_graph_context_switch(dev); + } + + nv_wr32(dev, NV03_PGRAPH_INTR, stat); + nv_wr32(dev, NV04_PGRAPH_FIFO, 0x00000001); + + if (show && nouveau_ratelimit()) { + NV_INFO(dev, "PGRAPH -"); + nouveau_bitfield_print(nv10_graph_intr, show); + printk(" nsource:"); + nouveau_bitfield_print(nv04_graph_nsource, nsource); + printk(" nstatus:"); + nouveau_bitfield_print(nv10_graph_nstatus, nstatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - ch %d/%d class 0x%04x " + "mthd 0x%04x data 0x%08x\n", + chid, subc, class, mthd, data); + } + } +} + +static void +nv10_graph_destroy(struct drm_device *dev, int engine) +{ + struct nv10_graph_engine *pgraph = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, 12); + kfree(pgraph); +} + +int +nv10_graph_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv10_graph_engine *pgraph; + + pgraph = kzalloc(sizeof(*pgraph), GFP_KERNEL); + if (!pgraph) + return -ENOMEM; + + pgraph->base.destroy = nv10_graph_destroy; + pgraph->base.init = nv10_graph_init; + pgraph->base.fini = nv10_graph_fini; + pgraph->base.context_new = nv10_graph_context_new; + pgraph->base.context_del = nv10_graph_context_del; + pgraph->base.object_new = nv04_graph_object_new; + pgraph->base.set_tile_region = nv10_graph_set_tile_region; + + NVOBJ_ENGINE_ADD(dev, GR, &pgraph->base); + nouveau_irq_register(dev, 12, nv10_graph_isr); + + /* nvsw */ + NVOBJ_CLASS(dev, 0x506e, SW); + NVOBJ_MTHD (dev, 0x506e, 0x0500, nv04_graph_mthd_page_flip); + + NVOBJ_CLASS(dev, 0x0030, GR); /* null */ + NVOBJ_CLASS(dev, 0x0039, GR); /* m2mf */ + NVOBJ_CLASS(dev, 0x004a, GR); /* gdirect */ + NVOBJ_CLASS(dev, 0x005f, GR); /* imageblit */ + NVOBJ_CLASS(dev, 0x009f, GR); /* imageblit (nv12) */ + NVOBJ_CLASS(dev, 0x008a, GR); /* ifc */ + NVOBJ_CLASS(dev, 0x0089, GR); /* sifm */ + NVOBJ_CLASS(dev, 0x0062, GR); /* surf2d */ + NVOBJ_CLASS(dev, 0x0043, GR); /* rop */ + NVOBJ_CLASS(dev, 0x0012, GR); /* beta1 */ + NVOBJ_CLASS(dev, 0x0072, GR); /* beta4 */ + NVOBJ_CLASS(dev, 0x0019, GR); /* cliprect */ + NVOBJ_CLASS(dev, 0x0044, GR); /* pattern */ + NVOBJ_CLASS(dev, 0x0052, GR); /* swzsurf */ + NVOBJ_CLASS(dev, 0x0093, GR); /* surf3d */ + NVOBJ_CLASS(dev, 0x0094, GR); /* tex_tri */ + NVOBJ_CLASS(dev, 0x0095, GR); /* multitex_tri */ + + /* celcius */ + if (dev_priv->chipset <= 0x10) { + NVOBJ_CLASS(dev, 0x0056, GR); + } else + if (dev_priv->chipset < 0x17 || dev_priv->chipset == 0x1a) { + NVOBJ_CLASS(dev, 0x0096, GR); + } else { + NVOBJ_CLASS(dev, 0x0099, GR); + NVOBJ_MTHD (dev, 0x0099, 0x1638, nv17_graph_mthd_lma_window); + NVOBJ_MTHD (dev, 0x0099, 0x163c, nv17_graph_mthd_lma_window); + NVOBJ_MTHD (dev, 0x0099, 0x1640, nv17_graph_mthd_lma_window); + NVOBJ_MTHD (dev, 0x0099, 0x1644, nv17_graph_mthd_lma_window); + NVOBJ_MTHD (dev, 0x0099, 0x1658, nv17_graph_mthd_lma_enable); + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv17_tv.c b/drivers/gpu/drm/nouveau/nv17_tv.c new file mode 100644 index 00000000..696d7e7d --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv17_tv.c @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2009 Francisco Jerez. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" +#include "nouveau_drv.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_crtc.h" +#include "nouveau_gpio.h" +#include "nouveau_hw.h" +#include "nv17_tv.h" + +static uint32_t nv42_tv_sample_load(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t testval, regoffset = nv04_dac_output_offset(encoder); + uint32_t gpio0, gpio1, fp_htotal, fp_hsync_start, fp_hsync_end, + fp_control, test_ctrl, dacclk, ctv_14, ctv_1c, ctv_6c; + uint32_t sample = 0; + int head; + +#define RGB_TEST_DATA(r, g, b) (r << 0 | g << 10 | b << 20) + testval = RGB_TEST_DATA(0x82, 0xeb, 0x82); + if (dev_priv->vbios.tvdactestval) + testval = dev_priv->vbios.tvdactestval; + + dacclk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset); + head = (dacclk & 0x100) >> 8; + + /* Save the previous state. */ + gpio1 = nouveau_gpio_func_get(dev, DCB_GPIO_TVDAC1); + gpio0 = nouveau_gpio_func_get(dev, DCB_GPIO_TVDAC0); + fp_htotal = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL); + fp_hsync_start = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START); + fp_hsync_end = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END); + fp_control = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL); + test_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset); + ctv_1c = NVReadRAMDAC(dev, head, 0x680c1c); + ctv_14 = NVReadRAMDAC(dev, head, 0x680c14); + ctv_6c = NVReadRAMDAC(dev, head, 0x680c6c); + + /* Prepare the DAC for load detection. */ + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC1, true); + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC0, true); + + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL, 1343); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START, 1047); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END, 1183); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, + NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | + NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12 | + NV_PRAMDAC_FP_TG_CONTROL_READ_PROG | + NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS | + NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS); + + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, 0); + + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, + (dacclk & ~0xff) | 0x22); + msleep(1); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, + (dacclk & ~0xff) | 0x21); + + NVWriteRAMDAC(dev, head, 0x680c1c, 1 << 20); + NVWriteRAMDAC(dev, head, 0x680c14, 4 << 16); + + /* Sample pin 0x4 (usually S-video luma). */ + NVWriteRAMDAC(dev, head, 0x680c6c, testval >> 10 & 0x3ff); + msleep(20); + sample |= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset) + & 0x4 << 28; + + /* Sample the remaining pins. */ + NVWriteRAMDAC(dev, head, 0x680c6c, testval & 0x3ff); + msleep(20); + sample |= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset) + & 0xa << 28; + + /* Restore the previous state. */ + NVWriteRAMDAC(dev, head, 0x680c1c, ctv_1c); + NVWriteRAMDAC(dev, head, 0x680c14, ctv_14); + NVWriteRAMDAC(dev, head, 0x680c6c, ctv_6c); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, dacclk); + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, test_ctrl); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, fp_control); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END, fp_hsync_end); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START, fp_hsync_start); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL, fp_htotal); + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC1, gpio1); + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC0, gpio0); + + return sample; +} + +static bool +get_tv_detect_quirks(struct drm_device *dev, uint32_t *pin_mask) +{ + /* Zotac FX5200 */ + if (nv_match_device(dev, 0x0322, 0x19da, 0x1035) || + nv_match_device(dev, 0x0322, 0x19da, 0x2035)) { + *pin_mask = 0xc; + return false; + } + + /* MSI nForce2 IGP */ + if (nv_match_device(dev, 0x01f0, 0x1462, 0x5710)) { + *pin_mask = 0xc; + return false; + } + + return true; +} + +static enum drm_connector_status +nv17_tv_detect(struct drm_encoder *encoder, struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_mode_config *conf = &dev->mode_config; + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + struct dcb_entry *dcb = tv_enc->base.dcb; + bool reliable = get_tv_detect_quirks(dev, &tv_enc->pin_mask); + + if (nv04_dac_in_use(encoder)) + return connector_status_disconnected; + + if (reliable) { + if (dev_priv->chipset == 0x42 || + dev_priv->chipset == 0x43) + tv_enc->pin_mask = + nv42_tv_sample_load(encoder) >> 28 & 0xe; + else + tv_enc->pin_mask = + nv17_dac_sample_load(encoder) >> 28 & 0xe; + } + + switch (tv_enc->pin_mask) { + case 0x2: + case 0x4: + tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Composite; + break; + case 0xc: + tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_SVIDEO; + break; + case 0xe: + if (dcb->tvconf.has_component_output) + tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Component; + else + tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_SCART; + break; + default: + tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Unknown; + break; + } + + drm_connector_property_set_value(connector, + conf->tv_subconnector_property, + tv_enc->subconnector); + + if (!reliable) { + return connector_status_unknown; + } else if (tv_enc->subconnector) { + NV_INFO(dev, "Load detected on output %c\n", + '@' + ffs(dcb->or)); + return connector_status_connected; + } else { + return connector_status_disconnected; + } +} + +static int nv17_tv_get_ld_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + const struct drm_display_mode *tv_mode; + int n = 0; + + for (tv_mode = nv17_tv_modes; tv_mode->hdisplay; tv_mode++) { + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(encoder->dev, tv_mode); + + mode->clock = tv_norm->tv_enc_mode.vrefresh * + mode->htotal / 1000 * + mode->vtotal / 1000; + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + mode->clock *= 2; + + if (mode->hdisplay == tv_norm->tv_enc_mode.hdisplay && + mode->vdisplay == tv_norm->tv_enc_mode.vdisplay) + mode->type |= DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + n++; + } + + return n; +} + +static int nv17_tv_get_hd_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + struct drm_display_mode *output_mode = &tv_norm->ctv_enc_mode.mode; + struct drm_display_mode *mode; + const struct { + int hdisplay; + int vdisplay; + } modes[] = { + { 640, 400 }, + { 640, 480 }, + { 720, 480 }, + { 720, 576 }, + { 800, 600 }, + { 1024, 768 }, + { 1280, 720 }, + { 1280, 1024 }, + { 1920, 1080 } + }; + int i, n = 0; + + for (i = 0; i < ARRAY_SIZE(modes); i++) { + if (modes[i].hdisplay > output_mode->hdisplay || + modes[i].vdisplay > output_mode->vdisplay) + continue; + + if (modes[i].hdisplay == output_mode->hdisplay && + modes[i].vdisplay == output_mode->vdisplay) { + mode = drm_mode_duplicate(encoder->dev, output_mode); + mode->type |= DRM_MODE_TYPE_PREFERRED; + + } else { + mode = drm_cvt_mode(encoder->dev, modes[i].hdisplay, + modes[i].vdisplay, 60, false, + (output_mode->flags & + DRM_MODE_FLAG_INTERLACE), false); + } + + /* CVT modes are sometimes unsuitable... */ + if (output_mode->hdisplay <= 720 + || output_mode->hdisplay >= 1920) { + mode->htotal = output_mode->htotal; + mode->hsync_start = (mode->hdisplay + (mode->htotal + - mode->hdisplay) * 9 / 10) & ~7; + mode->hsync_end = mode->hsync_start + 8; + } + + if (output_mode->vdisplay >= 1024) { + mode->vtotal = output_mode->vtotal; + mode->vsync_start = output_mode->vsync_start; + mode->vsync_end = output_mode->vsync_end; + } + + mode->type |= DRM_MODE_TYPE_DRIVER; + drm_mode_probed_add(connector, mode); + n++; + } + + return n; +} + +static int nv17_tv_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + + if (tv_norm->kind == CTV_ENC_MODE) + return nv17_tv_get_hd_modes(encoder, connector); + else + return nv17_tv_get_ld_modes(encoder, connector); +} + +static int nv17_tv_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + + if (tv_norm->kind == CTV_ENC_MODE) { + struct drm_display_mode *output_mode = + &tv_norm->ctv_enc_mode.mode; + + if (mode->clock > 400000) + return MODE_CLOCK_HIGH; + + if (mode->hdisplay > output_mode->hdisplay || + mode->vdisplay > output_mode->vdisplay) + return MODE_BAD; + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) != + (output_mode->flags & DRM_MODE_FLAG_INTERLACE)) + return MODE_NO_INTERLACE; + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + } else { + const int vsync_tolerance = 600; + + if (mode->clock > 70000) + return MODE_CLOCK_HIGH; + + if (abs(drm_mode_vrefresh(mode) * 1000 - + tv_norm->tv_enc_mode.vrefresh) > vsync_tolerance) + return MODE_VSYNC; + + /* The encoder takes care of the actual interlacing */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + } + + return MODE_OK; +} + +static bool nv17_tv_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + + if (nv04_dac_in_use(encoder)) + return false; + + if (tv_norm->kind == CTV_ENC_MODE) + adjusted_mode->clock = tv_norm->ctv_enc_mode.mode.clock; + else + adjusted_mode->clock = 90000; + + return true; +} + +static void nv17_tv_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct nv17_tv_state *regs = &to_tv_enc(encoder)->state; + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + + if (nouveau_encoder(encoder)->last_dpms == mode) + return; + nouveau_encoder(encoder)->last_dpms = mode; + + NV_INFO(dev, "Setting dpms mode %d on TV encoder (output %d)\n", + mode, nouveau_encoder(encoder)->dcb->index); + + regs->ptv_200 &= ~1; + + if (tv_norm->kind == CTV_ENC_MODE) { + nv04_dfp_update_fp_control(encoder, mode); + + } else { + nv04_dfp_update_fp_control(encoder, DRM_MODE_DPMS_OFF); + + if (mode == DRM_MODE_DPMS_ON) + regs->ptv_200 |= 1; + } + + nv_load_ptv(dev, regs, 200); + + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC1, mode == DRM_MODE_DPMS_ON); + nouveau_gpio_func_set(dev, DCB_GPIO_TVDAC0, mode == DRM_MODE_DPMS_ON); + + nv04_dac_update_dacclk(encoder, mode == DRM_MODE_DPMS_ON); +} + +static void nv17_tv_prepare(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + int head = nouveau_crtc(encoder->crtc)->index; + uint8_t *cr_lcd = &dev_priv->mode_reg.crtc_reg[head].CRTC[ + NV_CIO_CRE_LCD__INDEX]; + uint32_t dacclk_off = NV_PRAMDAC_DACCLK + + nv04_dac_output_offset(encoder); + uint32_t dacclk; + + helper->dpms(encoder, DRM_MODE_DPMS_OFF); + + nv04_dfp_disable(dev, head); + + /* Unbind any FP encoders from this head if we need the FP + * stuff enabled. */ + if (tv_norm->kind == CTV_ENC_MODE) { + struct drm_encoder *enc; + + list_for_each_entry(enc, &dev->mode_config.encoder_list, head) { + struct dcb_entry *dcb = nouveau_encoder(enc)->dcb; + + if ((dcb->type == OUTPUT_TMDS || + dcb->type == OUTPUT_LVDS) && + !enc->crtc && + nv04_dfp_get_bound_head(dev, dcb) == head) { + nv04_dfp_bind_head(dev, dcb, head ^ 1, + dev_priv->vbios.fp.dual_link); + } + } + + } + + if (tv_norm->kind == CTV_ENC_MODE) + *cr_lcd |= 0x1 | (head ? 0x0 : 0x8); + + /* Set the DACCLK register */ + dacclk = (NVReadRAMDAC(dev, 0, dacclk_off) & ~0x30) | 0x1; + + if (dev_priv->card_type == NV_40) + dacclk |= 0x1a << 16; + + if (tv_norm->kind == CTV_ENC_MODE) { + dacclk |= 0x20; + + if (head) + dacclk |= 0x100; + else + dacclk &= ~0x100; + + } else { + dacclk |= 0x10; + + } + + NVWriteRAMDAC(dev, 0, dacclk_off, dacclk); +} + +static void nv17_tv_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *drm_mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int head = nouveau_crtc(encoder->crtc)->index; + struct nv04_crtc_reg *regs = &dev_priv->mode_reg.crtc_reg[head]; + struct nv17_tv_state *tv_regs = &to_tv_enc(encoder)->state; + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + int i; + + regs->CRTC[NV_CIO_CRE_53] = 0x40; /* FP_HTIMING */ + regs->CRTC[NV_CIO_CRE_54] = 0; /* FP_VTIMING */ + regs->ramdac_630 = 0x2; /* turn off green mode (tv test pattern?) */ + regs->tv_setup = 1; + regs->ramdac_8c0 = 0x0; + + if (tv_norm->kind == TV_ENC_MODE) { + tv_regs->ptv_200 = 0x13111100; + if (head) + tv_regs->ptv_200 |= 0x10; + + tv_regs->ptv_20c = 0x808010; + tv_regs->ptv_304 = 0x2d00000; + tv_regs->ptv_600 = 0x0; + tv_regs->ptv_60c = 0x0; + tv_regs->ptv_610 = 0x1e00000; + + if (tv_norm->tv_enc_mode.vdisplay == 576) { + tv_regs->ptv_508 = 0x1200000; + tv_regs->ptv_614 = 0x33; + + } else if (tv_norm->tv_enc_mode.vdisplay == 480) { + tv_regs->ptv_508 = 0xf00000; + tv_regs->ptv_614 = 0x13; + } + + if (dev_priv->card_type >= NV_30) { + tv_regs->ptv_500 = 0xe8e0; + tv_regs->ptv_504 = 0x1710; + tv_regs->ptv_604 = 0x0; + tv_regs->ptv_608 = 0x0; + } else { + if (tv_norm->tv_enc_mode.vdisplay == 576) { + tv_regs->ptv_604 = 0x20; + tv_regs->ptv_608 = 0x10; + tv_regs->ptv_500 = 0x19710; + tv_regs->ptv_504 = 0x68f0; + + } else if (tv_norm->tv_enc_mode.vdisplay == 480) { + tv_regs->ptv_604 = 0x10; + tv_regs->ptv_608 = 0x20; + tv_regs->ptv_500 = 0x4b90; + tv_regs->ptv_504 = 0x1b480; + } + } + + for (i = 0; i < 0x40; i++) + tv_regs->tv_enc[i] = tv_norm->tv_enc_mode.tv_enc[i]; + + } else { + struct drm_display_mode *output_mode = + &tv_norm->ctv_enc_mode.mode; + + /* The registers in PRAMDAC+0xc00 control some timings and CSC + * parameters for the CTV encoder (It's only used for "HD" TV + * modes, I don't think I have enough working to guess what + * they exactly mean...), it's probably connected at the + * output of the FP encoder, but it also needs the analog + * encoder in its OR enabled and routed to the head it's + * using. It's enabled with the DACCLK register, bits [5:4]. + */ + for (i = 0; i < 38; i++) + regs->ctv_regs[i] = tv_norm->ctv_enc_mode.ctv_regs[i]; + + regs->fp_horiz_regs[FP_DISPLAY_END] = output_mode->hdisplay - 1; + regs->fp_horiz_regs[FP_TOTAL] = output_mode->htotal - 1; + regs->fp_horiz_regs[FP_SYNC_START] = + output_mode->hsync_start - 1; + regs->fp_horiz_regs[FP_SYNC_END] = output_mode->hsync_end - 1; + regs->fp_horiz_regs[FP_CRTC] = output_mode->hdisplay + + max((output_mode->hdisplay-600)/40 - 1, 1); + + regs->fp_vert_regs[FP_DISPLAY_END] = output_mode->vdisplay - 1; + regs->fp_vert_regs[FP_TOTAL] = output_mode->vtotal - 1; + regs->fp_vert_regs[FP_SYNC_START] = + output_mode->vsync_start - 1; + regs->fp_vert_regs[FP_SYNC_END] = output_mode->vsync_end - 1; + regs->fp_vert_regs[FP_CRTC] = output_mode->vdisplay - 1; + + regs->fp_control = NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | + NV_PRAMDAC_FP_TG_CONTROL_READ_PROG | + NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12; + + if (output_mode->flags & DRM_MODE_FLAG_PVSYNC) + regs->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS; + if (output_mode->flags & DRM_MODE_FLAG_PHSYNC) + regs->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS; + + regs->fp_debug_0 = NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND | + NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND | + NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR | + NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR | + NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED | + NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE | + NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE; + + regs->fp_debug_2 = 0; + + regs->fp_margin_color = 0x801080; + + } +} + +static void nv17_tv_commit(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_encoder_helper_funcs *helper = encoder->helper_private; + + if (get_tv_norm(encoder)->kind == TV_ENC_MODE) { + nv17_tv_update_rescaler(encoder); + nv17_tv_update_properties(encoder); + } else { + nv17_ctv_update_rescaler(encoder); + } + + nv17_tv_state_load(dev, &to_tv_enc(encoder)->state); + + /* This could use refinement for flatpanels, but it should work */ + if (dev_priv->chipset < 0x44) + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + + nv04_dac_output_offset(encoder), + 0xf0000000); + else + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + + nv04_dac_output_offset(encoder), + 0x00100000); + + helper->dpms(encoder, DRM_MODE_DPMS_ON); + + NV_INFO(dev, "Output %s is running on CRTC %d using output %c\n", + drm_get_connector_name( + &nouveau_encoder_connector_get(nv_encoder)->base), + nv_crtc->index, '@' + ffs(nv_encoder->dcb->or)); +} + +static void nv17_tv_save(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + + nouveau_encoder(encoder)->restore.output = + NVReadRAMDAC(dev, 0, + NV_PRAMDAC_DACCLK + + nv04_dac_output_offset(encoder)); + + nv17_tv_state_save(dev, &tv_enc->saved_state); + + tv_enc->state.ptv_200 = tv_enc->saved_state.ptv_200; +} + +static void nv17_tv_restore(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + + NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + + nv04_dac_output_offset(encoder), + nouveau_encoder(encoder)->restore.output); + + nv17_tv_state_load(dev, &to_tv_enc(encoder)->saved_state); + + nouveau_encoder(encoder)->last_dpms = NV_DPMS_CLEARED; +} + +static int nv17_tv_create_resources(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct drm_mode_config *conf = &dev->mode_config; + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + struct dcb_entry *dcb = nouveau_encoder(encoder)->dcb; + int num_tv_norms = dcb->tvconf.has_component_output ? NUM_TV_NORMS : + NUM_LD_TV_NORMS; + int i; + + if (nouveau_tv_norm) { + for (i = 0; i < num_tv_norms; i++) { + if (!strcmp(nv17_tv_norm_names[i], nouveau_tv_norm)) { + tv_enc->tv_norm = i; + break; + } + } + + if (i == num_tv_norms) + NV_WARN(dev, "Invalid TV norm setting \"%s\"\n", + nouveau_tv_norm); + } + + drm_mode_create_tv_properties(dev, num_tv_norms, nv17_tv_norm_names); + + drm_connector_attach_property(connector, + conf->tv_select_subconnector_property, + tv_enc->select_subconnector); + drm_connector_attach_property(connector, + conf->tv_subconnector_property, + tv_enc->subconnector); + drm_connector_attach_property(connector, + conf->tv_mode_property, + tv_enc->tv_norm); + drm_connector_attach_property(connector, + conf->tv_flicker_reduction_property, + tv_enc->flicker); + drm_connector_attach_property(connector, + conf->tv_saturation_property, + tv_enc->saturation); + drm_connector_attach_property(connector, + conf->tv_hue_property, + tv_enc->hue); + drm_connector_attach_property(connector, + conf->tv_overscan_property, + tv_enc->overscan); + + return 0; +} + +static int nv17_tv_set_property(struct drm_encoder *encoder, + struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + struct drm_mode_config *conf = &encoder->dev->mode_config; + struct drm_crtc *crtc = encoder->crtc; + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + bool modes_changed = false; + + if (property == conf->tv_overscan_property) { + tv_enc->overscan = val; + if (encoder->crtc) { + if (tv_norm->kind == CTV_ENC_MODE) + nv17_ctv_update_rescaler(encoder); + else + nv17_tv_update_rescaler(encoder); + } + + } else if (property == conf->tv_saturation_property) { + if (tv_norm->kind != TV_ENC_MODE) + return -EINVAL; + + tv_enc->saturation = val; + nv17_tv_update_properties(encoder); + + } else if (property == conf->tv_hue_property) { + if (tv_norm->kind != TV_ENC_MODE) + return -EINVAL; + + tv_enc->hue = val; + nv17_tv_update_properties(encoder); + + } else if (property == conf->tv_flicker_reduction_property) { + if (tv_norm->kind != TV_ENC_MODE) + return -EINVAL; + + tv_enc->flicker = val; + if (encoder->crtc) + nv17_tv_update_rescaler(encoder); + + } else if (property == conf->tv_mode_property) { + if (connector->dpms != DRM_MODE_DPMS_OFF) + return -EINVAL; + + tv_enc->tv_norm = val; + + modes_changed = true; + + } else if (property == conf->tv_select_subconnector_property) { + if (tv_norm->kind != TV_ENC_MODE) + return -EINVAL; + + tv_enc->select_subconnector = val; + nv17_tv_update_properties(encoder); + + } else { + return -EINVAL; + } + + if (modes_changed) { + drm_helper_probe_single_connector_modes(connector, 0, 0); + + /* Disable the crtc to ensure a full modeset is + * performed whenever it's turned on again. */ + if (crtc) { + struct drm_mode_set modeset = { + .crtc = crtc, + }; + + crtc->funcs->set_config(&modeset); + } + } + + return 0; +} + +static void nv17_tv_destroy(struct drm_encoder *encoder) +{ + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + + NV_DEBUG_KMS(encoder->dev, "\n"); + + drm_encoder_cleanup(encoder); + kfree(tv_enc); +} + +static struct drm_encoder_helper_funcs nv17_tv_helper_funcs = { + .dpms = nv17_tv_dpms, + .save = nv17_tv_save, + .restore = nv17_tv_restore, + .mode_fixup = nv17_tv_mode_fixup, + .prepare = nv17_tv_prepare, + .commit = nv17_tv_commit, + .mode_set = nv17_tv_mode_set, + .detect = nv17_tv_detect, +}; + +static struct drm_encoder_slave_funcs nv17_tv_slave_funcs = { + .get_modes = nv17_tv_get_modes, + .mode_valid = nv17_tv_mode_valid, + .create_resources = nv17_tv_create_resources, + .set_property = nv17_tv_set_property, +}; + +static struct drm_encoder_funcs nv17_tv_funcs = { + .destroy = nv17_tv_destroy, +}; + +int +nv17_tv_create(struct drm_connector *connector, struct dcb_entry *entry) +{ + struct drm_device *dev = connector->dev; + struct drm_encoder *encoder; + struct nv17_tv_encoder *tv_enc = NULL; + + tv_enc = kzalloc(sizeof(*tv_enc), GFP_KERNEL); + if (!tv_enc) + return -ENOMEM; + + tv_enc->overscan = 50; + tv_enc->flicker = 50; + tv_enc->saturation = 50; + tv_enc->hue = 0; + tv_enc->tv_norm = TV_NORM_PAL; + tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Unknown; + tv_enc->select_subconnector = DRM_MODE_SUBCONNECTOR_Automatic; + tv_enc->pin_mask = 0; + + encoder = to_drm_encoder(&tv_enc->base); + + tv_enc->base.dcb = entry; + tv_enc->base.or = ffs(entry->or) - 1; + + drm_encoder_init(dev, encoder, &nv17_tv_funcs, DRM_MODE_ENCODER_TVDAC); + drm_encoder_helper_add(encoder, &nv17_tv_helper_funcs); + to_encoder_slave(encoder)->slave_funcs = &nv17_tv_slave_funcs; + + encoder->possible_crtcs = entry->heads; + encoder->possible_clones = 0; + + nv17_tv_create_resources(encoder, connector); + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv17_tv.h b/drivers/gpu/drm/nouveau/nv17_tv.h new file mode 100644 index 00000000..622e7222 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv17_tv.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2009 Francisco Jerez. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV17_TV_H__ +#define __NV17_TV_H__ + +struct nv17_tv_state { + uint8_t tv_enc[0x40]; + + uint32_t hfilter[4][7]; + uint32_t hfilter2[4][7]; + uint32_t vfilter[4][7]; + + uint32_t ptv_200; + uint32_t ptv_204; + uint32_t ptv_208; + uint32_t ptv_20c; + uint32_t ptv_304; + uint32_t ptv_500; + uint32_t ptv_504; + uint32_t ptv_508; + uint32_t ptv_600; + uint32_t ptv_604; + uint32_t ptv_608; + uint32_t ptv_60c; + uint32_t ptv_610; + uint32_t ptv_614; +}; + +enum nv17_tv_norm{ + TV_NORM_PAL, + TV_NORM_PAL_M, + TV_NORM_PAL_N, + TV_NORM_PAL_NC, + TV_NORM_NTSC_M, + TV_NORM_NTSC_J, + NUM_LD_TV_NORMS, + TV_NORM_HD480I = NUM_LD_TV_NORMS, + TV_NORM_HD480P, + TV_NORM_HD576I, + TV_NORM_HD576P, + TV_NORM_HD720P, + TV_NORM_HD1080I, + NUM_TV_NORMS +}; + +struct nv17_tv_encoder { + struct nouveau_encoder base; + + struct nv17_tv_state state; + struct nv17_tv_state saved_state; + + int overscan; + int flicker; + int saturation; + int hue; + enum nv17_tv_norm tv_norm; + int subconnector; + int select_subconnector; + uint32_t pin_mask; +}; +#define to_tv_enc(x) container_of(nouveau_encoder(x), \ + struct nv17_tv_encoder, base) + +extern char *nv17_tv_norm_names[NUM_TV_NORMS]; + +extern struct nv17_tv_norm_params { + enum { + TV_ENC_MODE, + CTV_ENC_MODE, + } kind; + + union { + struct { + int hdisplay; + int vdisplay; + int vrefresh; /* mHz */ + + uint8_t tv_enc[0x40]; + } tv_enc_mode; + + struct { + struct drm_display_mode mode; + + uint32_t ctv_regs[38]; + } ctv_enc_mode; + }; + +} nv17_tv_norms[NUM_TV_NORMS]; +#define get_tv_norm(enc) (&nv17_tv_norms[to_tv_enc(enc)->tv_norm]) + +extern const struct drm_display_mode nv17_tv_modes[]; + +static inline int interpolate(int y0, int y1, int y2, int x) +{ + return y1 + (x < 50 ? y1 - y0 : y2 - y1) * (x - 50) / 50; +} + +void nv17_tv_state_save(struct drm_device *dev, struct nv17_tv_state *state); +void nv17_tv_state_load(struct drm_device *dev, struct nv17_tv_state *state); +void nv17_tv_update_properties(struct drm_encoder *encoder); +void nv17_tv_update_rescaler(struct drm_encoder *encoder); +void nv17_ctv_update_rescaler(struct drm_encoder *encoder); + +/* TV hardware access functions */ + +static inline void nv_write_ptv(struct drm_device *dev, uint32_t reg, + uint32_t val) +{ + nv_wr32(dev, reg, val); +} + +static inline uint32_t nv_read_ptv(struct drm_device *dev, uint32_t reg) +{ + return nv_rd32(dev, reg); +} + +static inline void nv_write_tv_enc(struct drm_device *dev, uint8_t reg, + uint8_t val) +{ + nv_write_ptv(dev, NV_PTV_TV_INDEX, reg); + nv_write_ptv(dev, NV_PTV_TV_DATA, val); +} + +static inline uint8_t nv_read_tv_enc(struct drm_device *dev, uint8_t reg) +{ + nv_write_ptv(dev, NV_PTV_TV_INDEX, reg); + return nv_read_ptv(dev, NV_PTV_TV_DATA); +} + +#define nv_load_ptv(dev, state, reg) \ + nv_write_ptv(dev, NV_PTV_OFFSET + 0x##reg, state->ptv_##reg) +#define nv_save_ptv(dev, state, reg) \ + state->ptv_##reg = nv_read_ptv(dev, NV_PTV_OFFSET + 0x##reg) +#define nv_load_tv_enc(dev, state, reg) \ + nv_write_tv_enc(dev, 0x##reg, state->tv_enc[0x##reg]) + +#endif diff --git a/drivers/gpu/drm/nouveau/nv17_tv_modes.c b/drivers/gpu/drm/nouveau/nv17_tv_modes.c new file mode 100644 index 00000000..4d1d29f6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv17_tv_modes.c @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2009 Francisco Jerez. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" +#include "nouveau_drv.h" +#include "nouveau_encoder.h" +#include "nouveau_crtc.h" +#include "nouveau_hw.h" +#include "nv17_tv.h" + +char *nv17_tv_norm_names[NUM_TV_NORMS] = { + [TV_NORM_PAL] = "PAL", + [TV_NORM_PAL_M] = "PAL-M", + [TV_NORM_PAL_N] = "PAL-N", + [TV_NORM_PAL_NC] = "PAL-Nc", + [TV_NORM_NTSC_M] = "NTSC-M", + [TV_NORM_NTSC_J] = "NTSC-J", + [TV_NORM_HD480I] = "hd480i", + [TV_NORM_HD480P] = "hd480p", + [TV_NORM_HD576I] = "hd576i", + [TV_NORM_HD576P] = "hd576p", + [TV_NORM_HD720P] = "hd720p", + [TV_NORM_HD1080I] = "hd1080i" +}; + +/* TV standard specific parameters */ + +struct nv17_tv_norm_params nv17_tv_norms[NUM_TV_NORMS] = { + [TV_NORM_PAL] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 576, 50000, { + 0x2a, 0x9, 0x8a, 0xcb, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x40, 0x8a, 0x35, 0x27, 0x0, 0x34, 0x3, + 0x3e, 0x3, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x3, + 0xd3, 0x4, 0xd4, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b, + 0xbd, 0x15, 0x5, 0x15, 0x3e, 0x3, 0x0, 0x0 + } } } }, + + [TV_NORM_PAL_M] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 480, 59940, { + 0x21, 0xe6, 0xef, 0xe3, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x44, 0x76, 0x32, 0x25, 0x0, 0x3c, 0x0, + 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1, + 0xc5, 0x4, 0xc5, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x18, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x40, 0x10, 0x0, 0x9c, + 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0 + } } } }, + + [TV_NORM_PAL_N] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 576, 50000, { + 0x2a, 0x9, 0x8a, 0xcb, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x40, 0x8a, 0x32, 0x25, 0x0, 0x3c, 0x0, + 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1, + 0xc5, 0x4, 0xc5, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b, + 0xbd, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0 + } } } }, + + [TV_NORM_PAL_NC] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 576, 50000, { + 0x21, 0xf6, 0x94, 0x46, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x44, 0x8a, 0x35, 0x27, 0x0, 0x34, 0x3, + 0x3e, 0x3, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x3, + 0xd3, 0x4, 0xd4, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b, + 0xbd, 0x15, 0x5, 0x15, 0x3e, 0x3, 0x0, 0x0 + } } } }, + + [TV_NORM_NTSC_M] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 480, 59940, { + 0x21, 0xf0, 0x7c, 0x1f, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x44, 0x76, 0x48, 0x0, 0x0, 0x3c, 0x0, + 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1, + 0xc5, 0x4, 0xc5, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x16, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x4, 0x10, 0x0, 0x9c, + 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0 + } } } }, + + [TV_NORM_NTSC_J] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 480, 59940, { + 0x21, 0xf0, 0x7c, 0x1f, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x44, 0x76, 0x48, 0x0, 0x0, 0x32, 0x0, + 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1, + 0xcf, 0x4, 0xcf, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x16, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x4, 0x10, 0x0, 0xa4, + 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0 + } } } }, + + [TV_NORM_HD480I] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 480, 59940, { + 0x21, 0xf0, 0x7c, 0x1f, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x44, 0x76, 0x48, 0x0, 0x0, 0x32, 0x0, + 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1, + 0xcf, 0x4, 0xcf, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x16, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x4, 0x10, 0x0, 0xa4, + 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0 + } } } }, + + [TV_NORM_HD576I] = { TV_ENC_MODE, { + .tv_enc_mode = { 720, 576, 50000, { + 0x2a, 0x9, 0x8a, 0xcb, 0x0, 0x0, 0xb, 0x18, + 0x7e, 0x40, 0x8a, 0x35, 0x27, 0x0, 0x34, 0x3, + 0x3e, 0x3, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c, + 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x3, + 0xd3, 0x4, 0xd4, 0x1, 0x2, 0x0, 0xa, 0x5, + 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0, + 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b, + 0xbd, 0x15, 0x5, 0x15, 0x3e, 0x3, 0x0, 0x0 + } } } }, + + + [TV_NORM_HD480P] = { CTV_ENC_MODE, { + .ctv_enc_mode = { + .mode = { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, + 720, 735, 743, 858, 0, 480, 490, 494, 525, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + .ctv_regs = { 0x3540000, 0x0, 0x0, 0x314, + 0x354003a, 0x40000, 0x6f0344, 0x18100000, + 0x10160004, 0x10060005, 0x1006000c, 0x10060020, + 0x10060021, 0x140e0022, 0x10060202, 0x1802020a, + 0x1810020b, 0x10000fff, 0x10000fff, 0x10000fff, + 0x10000fff, 0x10000fff, 0x10000fff, 0x70, + 0x3ff0000, 0x57, 0x2e001e, 0x258012c, + 0xa0aa04ec, 0x30, 0x80960019, 0x12c0300, + 0x2019, 0x600, 0x32060019, 0x0, 0x0, 0x400 + } } } }, + + [TV_NORM_HD576P] = { CTV_ENC_MODE, { + .ctv_enc_mode = { + .mode = { DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, + 720, 730, 738, 864, 0, 576, 581, 585, 625, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + .ctv_regs = { 0x3540000, 0x0, 0x0, 0x314, + 0x354003a, 0x40000, 0x6f0344, 0x18100000, + 0x10060001, 0x10060009, 0x10060026, 0x10060027, + 0x140e0028, 0x10060268, 0x1810026d, 0x10000fff, + 0x10000fff, 0x10000fff, 0x10000fff, 0x10000fff, + 0x10000fff, 0x10000fff, 0x10000fff, 0x69, + 0x3ff0000, 0x57, 0x2e001e, 0x258012c, + 0xa0aa04ec, 0x30, 0x80960019, 0x12c0300, + 0x2019, 0x600, 0x32060019, 0x0, 0x0, 0x400 + } } } }, + + [TV_NORM_HD720P] = { CTV_ENC_MODE, { + .ctv_enc_mode = { + .mode = { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, + 1280, 1349, 1357, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + .ctv_regs = { 0x1260394, 0x0, 0x0, 0x622, + 0x66b0021, 0x6004a, 0x1210626, 0x8170000, + 0x70004, 0x70016, 0x70017, 0x40f0018, + 0x702e8, 0x81702ed, 0xfff, 0xfff, + 0xfff, 0xfff, 0xfff, 0xfff, + 0xfff, 0xfff, 0xfff, 0x0, + 0x2e40001, 0x58, 0x2e001e, 0x258012c, + 0xa0aa04ec, 0x30, 0x810c0039, 0x12c0300, + 0xc0002039, 0x600, 0x32060039, 0x0, 0x0, 0x0 + } } } }, + + [TV_NORM_HD1080I] = { CTV_ENC_MODE, { + .ctv_enc_mode = { + .mode = { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, + 1920, 1961, 2049, 2200, 0, 1080, 1084, 1088, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC + | DRM_MODE_FLAG_INTERLACE) }, + .ctv_regs = { 0xac0420, 0x44c0478, 0x4a4, 0x4fc0868, + 0x8940028, 0x60054, 0xe80870, 0xbf70000, + 0xbc70004, 0x70005, 0x70012, 0x70013, + 0x40f0014, 0x70230, 0xbf70232, 0xbf70233, + 0x1c70237, 0x70238, 0x70244, 0x70245, + 0x40f0246, 0x70462, 0x1f70464, 0x0, + 0x2e40001, 0x58, 0x2e001e, 0x258012c, + 0xa0aa04ec, 0x30, 0x815f004c, 0x12c0300, + 0xc000204c, 0x600, 0x3206004c, 0x0, 0x0, 0x0 + } } } } +}; + +/* + * The following is some guesswork on how the TV encoder flicker + * filter/rescaler works: + * + * It seems to use some sort of resampling filter, it is controlled + * through the registers at NV_PTV_HFILTER and NV_PTV_VFILTER, they + * control the horizontal and vertical stage respectively, there is + * also NV_PTV_HFILTER2 the blob fills identically to NV_PTV_HFILTER, + * but they seem to do nothing. A rough guess might be that they could + * be used to independently control the filtering of each interlaced + * field, but I don't know how they are enabled. The whole filtering + * process seems to be disabled with bits 26:27 of PTV_200, but we + * aren't doing that. + * + * The layout of both register sets is the same: + * + * A: [BASE+0x18]...[BASE+0x0] [BASE+0x58]..[BASE+0x40] + * B: [BASE+0x34]...[BASE+0x1c] [BASE+0x74]..[BASE+0x5c] + * + * Each coefficient is stored in bits [31],[15:9] in two's complement + * format. They seem to be some kind of weights used in a low-pass + * filter. Both A and B coefficients are applied to the 14 nearest + * samples on each side (Listed from nearest to furthermost. They + * roughly cover 2 framebuffer pixels on each side). They are + * probably multiplied with some more hardwired weights before being + * used: B-coefficients are applied the same on both sides, + * A-coefficients are inverted before being applied to the opposite + * side. + * + * After all the hassle, I got the following formula by empirical + * means... + */ + +#define calc_overscan(o) interpolate(0x100, 0xe1, 0xc1, o) + +#define id1 (1LL << 8) +#define id2 (1LL << 16) +#define id3 (1LL << 24) +#define id4 (1LL << 32) +#define id5 (1LL << 48) + +static struct filter_params{ + int64_t k1; + int64_t ki; + int64_t ki2; + int64_t ki3; + int64_t kr; + int64_t kir; + int64_t ki2r; + int64_t ki3r; + int64_t kf; + int64_t kif; + int64_t ki2f; + int64_t ki3f; + int64_t krf; + int64_t kirf; + int64_t ki2rf; + int64_t ki3rf; +} fparams[2][4] = { + /* Horizontal filter parameters */ + { + {64.311690 * id5, -39.516924 * id5, 6.586143 * id5, 0.000002 * id5, + 0.051285 * id4, 26.168746 * id4, -4.361449 * id4, -0.000001 * id4, + 9.308169 * id3, 78.180965 * id3, -13.030158 * id3, -0.000001 * id3, + -8.801540 * id1, -46.572890 * id1, 7.762145 * id1, -0.000000 * id1}, + {-44.565569 * id5, -68.081246 * id5, 39.812074 * id5, -4.009316 * id5, + 29.832207 * id4, 50.047322 * id4, -25.380017 * id4, 2.546422 * id4, + 104.605622 * id3, 141.908641 * id3, -74.322319 * id3, 7.484316 * id3, + -37.081621 * id1, -90.397510 * id1, 42.784229 * id1, -4.289952 * id1}, + {-56.793244 * id5, 31.153584 * id5, -5.192247 * id5, -0.000003 * id5, + 33.541131 * id4, -34.149302 * id4, 5.691537 * id4, 0.000002 * id4, + 87.196610 * id3, -88.995169 * id3, 14.832456 * id3, 0.000012 * id3, + 17.288138 * id1, 71.864786 * id1, -11.977408 * id1, -0.000009 * id1}, + {51.787796 * id5, 21.211771 * id5, -18.993730 * id5, 1.853310 * id5, + -41.470726 * id4, -17.775823 * id4, 13.057821 * id4, -1.15823 * id4, + -154.235673 * id3, -44.878641 * id3, 40.656077 * id3, -3.695595 * id3, + 112.201065 * id1, 39.992155 * id1, -25.155714 * id1, 2.113984 * id1}, + }, + + /* Vertical filter parameters */ + { + {67.601979 * id5, 0.428319 * id5, -0.071318 * id5, -0.000012 * id5, + -3.402339 * id4, 0.000209 * id4, -0.000092 * id4, 0.000010 * id4, + -9.180996 * id3, 6.111270 * id3, -1.024457 * id3, 0.001043 * id3, + 6.060315 * id1, -0.017425 * id1, 0.007830 * id1, -0.000869 * id1}, + {6.755647 * id5, 5.841348 * id5, 1.469734 * id5, -0.149656 * id5, + 8.293120 * id4, -1.192888 * id4, -0.947652 * id4, 0.094507 * id4, + 37.526655 * id3, 10.257875 * id3, -10.823275 * id3, 1.081497 * id3, + -2.361928 * id1, -2.059432 * id1, 1.840671 * id1, -0.168100 * id1}, + {-14.780391 * id5, -16.042148 * id5, 2.673692 * id5, -0.000000 * id5, + 39.541978 * id4, 5.680053 * id4, -0.946676 * id4, 0.000000 * id4, + 152.994486 * id3, 12.625439 * id3, -2.119579 * id3, 0.002708 * id3, + -38.125089 * id1, -0.855880 * id1, 0.155359 * id1, -0.002245 * id1}, + {-27.476193 * id5, -1.454976 * id5, 1.286557 * id5, 0.025346 * id5, + 20.687300 * id4, 3.014003 * id4, -0.557786 * id4, -0.01311 * id4, + 60.008737 * id3, -0.738273 * id3, 5.408217 * id3, -0.796798 * id3, + -17.296835 * id1, 4.438577 * id1, -2.809420 * id1, 0.385491 * id1}, + } +}; + +static void tv_setup_filter(struct drm_encoder *encoder) +{ + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + struct drm_display_mode *mode = &encoder->crtc->mode; + uint32_t (*filters[])[4][7] = {&tv_enc->state.hfilter, + &tv_enc->state.vfilter}; + int i, j, k; + int32_t overscan = calc_overscan(tv_enc->overscan); + int64_t flicker = (tv_enc->flicker - 50) * (id3 / 100); + uint64_t rs[] = {mode->hdisplay * id3, + mode->vdisplay * id3}; + + do_div(rs[0], overscan * tv_norm->tv_enc_mode.hdisplay); + do_div(rs[1], overscan * tv_norm->tv_enc_mode.vdisplay); + + for (k = 0; k < 2; k++) { + rs[k] = max((int64_t)rs[k], id2); + + for (j = 0; j < 4; j++) { + struct filter_params *p = &fparams[k][j]; + + for (i = 0; i < 7; i++) { + int64_t c = (p->k1 + p->ki*i + p->ki2*i*i + + p->ki3*i*i*i) + + (p->kr + p->kir*i + p->ki2r*i*i + + p->ki3r*i*i*i) * rs[k] + + (p->kf + p->kif*i + p->ki2f*i*i + + p->ki3f*i*i*i) * flicker + + (p->krf + p->kirf*i + p->ki2rf*i*i + + p->ki3rf*i*i*i) * flicker * rs[k]; + + (*filters[k])[j][i] = (c + id5/2) >> 39 + & (0x1 << 31 | 0x7f << 9); + } + } + } +} + +/* Hardware state saving/restoring */ + +static void tv_save_filter(struct drm_device *dev, uint32_t base, + uint32_t regs[4][7]) +{ + int i, j; + uint32_t offsets[] = { base, base + 0x1c, base + 0x40, base + 0x5c }; + + for (i = 0; i < 4; i++) { + for (j = 0; j < 7; j++) + regs[i][j] = nv_read_ptv(dev, offsets[i]+4*j); + } +} + +static void tv_load_filter(struct drm_device *dev, uint32_t base, + uint32_t regs[4][7]) +{ + int i, j; + uint32_t offsets[] = { base, base + 0x1c, base + 0x40, base + 0x5c }; + + for (i = 0; i < 4; i++) { + for (j = 0; j < 7; j++) + nv_write_ptv(dev, offsets[i]+4*j, regs[i][j]); + } +} + +void nv17_tv_state_save(struct drm_device *dev, struct nv17_tv_state *state) +{ + int i; + + for (i = 0; i < 0x40; i++) + state->tv_enc[i] = nv_read_tv_enc(dev, i); + + tv_save_filter(dev, NV_PTV_HFILTER, state->hfilter); + tv_save_filter(dev, NV_PTV_HFILTER2, state->hfilter2); + tv_save_filter(dev, NV_PTV_VFILTER, state->vfilter); + + nv_save_ptv(dev, state, 200); + nv_save_ptv(dev, state, 204); + nv_save_ptv(dev, state, 208); + nv_save_ptv(dev, state, 20c); + nv_save_ptv(dev, state, 304); + nv_save_ptv(dev, state, 500); + nv_save_ptv(dev, state, 504); + nv_save_ptv(dev, state, 508); + nv_save_ptv(dev, state, 600); + nv_save_ptv(dev, state, 604); + nv_save_ptv(dev, state, 608); + nv_save_ptv(dev, state, 60c); + nv_save_ptv(dev, state, 610); + nv_save_ptv(dev, state, 614); +} + +void nv17_tv_state_load(struct drm_device *dev, struct nv17_tv_state *state) +{ + int i; + + for (i = 0; i < 0x40; i++) + nv_write_tv_enc(dev, i, state->tv_enc[i]); + + tv_load_filter(dev, NV_PTV_HFILTER, state->hfilter); + tv_load_filter(dev, NV_PTV_HFILTER2, state->hfilter2); + tv_load_filter(dev, NV_PTV_VFILTER, state->vfilter); + + nv_load_ptv(dev, state, 200); + nv_load_ptv(dev, state, 204); + nv_load_ptv(dev, state, 208); + nv_load_ptv(dev, state, 20c); + nv_load_ptv(dev, state, 304); + nv_load_ptv(dev, state, 500); + nv_load_ptv(dev, state, 504); + nv_load_ptv(dev, state, 508); + nv_load_ptv(dev, state, 600); + nv_load_ptv(dev, state, 604); + nv_load_ptv(dev, state, 608); + nv_load_ptv(dev, state, 60c); + nv_load_ptv(dev, state, 610); + nv_load_ptv(dev, state, 614); + + /* This is required for some settings to kick in. */ + nv_write_tv_enc(dev, 0x3e, 1); + nv_write_tv_enc(dev, 0x3e, 0); +} + +/* Timings similar to the ones the blob sets */ + +const struct drm_display_mode nv17_tv_modes[] = { + { DRM_MODE("320x200", DRM_MODE_TYPE_DRIVER, 0, + 320, 344, 392, 560, 0, 200, 200, 202, 220, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC + | DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_CLKDIV2) }, + { DRM_MODE("320x240", DRM_MODE_TYPE_DRIVER, 0, + 320, 344, 392, 560, 0, 240, 240, 246, 263, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC + | DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_CLKDIV2) }, + { DRM_MODE("400x300", DRM_MODE_TYPE_DRIVER, 0, + 400, 432, 496, 640, 0, 300, 300, 303, 314, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC + | DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_CLKDIV2) }, + { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 0, + 640, 672, 768, 880, 0, 480, 480, 492, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, + { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 0, + 720, 752, 872, 960, 0, 480, 480, 493, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, + { DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 0, + 720, 776, 856, 960, 0, 576, 576, 588, 597, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, + { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 0, + 800, 840, 920, 1040, 0, 600, 600, 604, 618, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, + { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 0, + 1024, 1064, 1200, 1344, 0, 768, 768, 777, 806, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, + {} +}; + +void nv17_tv_update_properties(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + struct nv17_tv_state *regs = &tv_enc->state; + struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder); + int subconnector = tv_enc->select_subconnector ? + tv_enc->select_subconnector : + tv_enc->subconnector; + + switch (subconnector) { + case DRM_MODE_SUBCONNECTOR_Composite: + { + regs->ptv_204 = 0x2; + + /* The composite connector may be found on either pin. */ + if (tv_enc->pin_mask & 0x4) + regs->ptv_204 |= 0x010000; + else if (tv_enc->pin_mask & 0x2) + regs->ptv_204 |= 0x100000; + else + regs->ptv_204 |= 0x110000; + + regs->tv_enc[0x7] = 0x10; + break; + } + case DRM_MODE_SUBCONNECTOR_SVIDEO: + regs->ptv_204 = 0x11012; + regs->tv_enc[0x7] = 0x18; + break; + + case DRM_MODE_SUBCONNECTOR_Component: + regs->ptv_204 = 0x111333; + regs->tv_enc[0x7] = 0x14; + break; + + case DRM_MODE_SUBCONNECTOR_SCART: + regs->ptv_204 = 0x111012; + regs->tv_enc[0x7] = 0x18; + break; + } + + regs->tv_enc[0x20] = interpolate(0, tv_norm->tv_enc_mode.tv_enc[0x20], + 255, tv_enc->saturation); + regs->tv_enc[0x22] = interpolate(0, tv_norm->tv_enc_mode.tv_enc[0x22], + 255, tv_enc->saturation); + regs->tv_enc[0x25] = tv_enc->hue * 255 / 100; + + nv_load_ptv(dev, regs, 204); + nv_load_tv_enc(dev, regs, 7); + nv_load_tv_enc(dev, regs, 20); + nv_load_tv_enc(dev, regs, 22); + nv_load_tv_enc(dev, regs, 25); +} + +void nv17_tv_update_rescaler(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + struct nv17_tv_state *regs = &tv_enc->state; + + regs->ptv_208 = 0x40 | (calc_overscan(tv_enc->overscan) << 8); + + tv_setup_filter(encoder); + + nv_load_ptv(dev, regs, 208); + tv_load_filter(dev, NV_PTV_HFILTER, regs->hfilter); + tv_load_filter(dev, NV_PTV_HFILTER2, regs->hfilter2); + tv_load_filter(dev, NV_PTV_VFILTER, regs->vfilter); +} + +void nv17_ctv_update_rescaler(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder); + int head = nouveau_crtc(encoder->crtc)->index; + struct nv04_crtc_reg *regs = &dev_priv->mode_reg.crtc_reg[head]; + struct drm_display_mode *crtc_mode = &encoder->crtc->mode; + struct drm_display_mode *output_mode = + &get_tv_norm(encoder)->ctv_enc_mode.mode; + int overscan, hmargin, vmargin, hratio, vratio; + + /* The rescaler doesn't do the right thing for interlaced modes. */ + if (output_mode->flags & DRM_MODE_FLAG_INTERLACE) + overscan = 100; + else + overscan = tv_enc->overscan; + + hmargin = (output_mode->hdisplay - crtc_mode->hdisplay) / 2; + vmargin = (output_mode->vdisplay - crtc_mode->vdisplay) / 2; + + hmargin = interpolate(0, min(hmargin, output_mode->hdisplay/20), + hmargin, overscan); + vmargin = interpolate(0, min(vmargin, output_mode->vdisplay/20), + vmargin, overscan); + + hratio = crtc_mode->hdisplay * 0x800 / + (output_mode->hdisplay - 2*hmargin); + vratio = crtc_mode->vdisplay * 0x800 / + (output_mode->vdisplay - 2*vmargin) & ~3; + + regs->fp_horiz_regs[FP_VALID_START] = hmargin; + regs->fp_horiz_regs[FP_VALID_END] = output_mode->hdisplay - hmargin - 1; + regs->fp_vert_regs[FP_VALID_START] = vmargin; + regs->fp_vert_regs[FP_VALID_END] = output_mode->vdisplay - vmargin - 1; + + regs->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_YSCALE_TESTMODE_ENABLE | + XLATE(vratio, 0, NV_PRAMDAC_FP_DEBUG_1_YSCALE_VALUE) | + NV_PRAMDAC_FP_DEBUG_1_XSCALE_TESTMODE_ENABLE | + XLATE(hratio, 0, NV_PRAMDAC_FP_DEBUG_1_XSCALE_VALUE); + + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HVALID_START, + regs->fp_horiz_regs[FP_VALID_START]); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HVALID_END, + regs->fp_horiz_regs[FP_VALID_END]); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_VVALID_START, + regs->fp_vert_regs[FP_VALID_START]); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_VVALID_END, + regs->fp_vert_regs[FP_VALID_END]); + NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_1, regs->fp_debug_1); +} diff --git a/drivers/gpu/drm/nouveau/nv20_fb.c b/drivers/gpu/drm/nouveau/nv20_fb.c new file mode 100644 index 00000000..19bd6405 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv20_fb.c @@ -0,0 +1,148 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +static struct drm_mm_node * +nv20_fb_alloc_tag(struct drm_device *dev, uint32_t size) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + struct drm_mm_node *mem; + int ret; + + ret = drm_mm_pre_get(&pfb->tag_heap); + if (ret) + return NULL; + + spin_lock(&dev_priv->tile.lock); + mem = drm_mm_search_free(&pfb->tag_heap, size, 0, 0); + if (mem) + mem = drm_mm_get_block_atomic(mem, size, 0); + spin_unlock(&dev_priv->tile.lock); + + return mem; +} + +static void +nv20_fb_free_tag(struct drm_device *dev, struct drm_mm_node **pmem) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct drm_mm_node *mem = *pmem; + if (mem) { + spin_lock(&dev_priv->tile.lock); + drm_mm_put_block(mem); + spin_unlock(&dev_priv->tile.lock); + *pmem = NULL; + } +} + +void +nv20_fb_init_tile_region(struct drm_device *dev, int i, uint32_t addr, + uint32_t size, uint32_t pitch, uint32_t flags) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + int bpp = (flags & NOUVEAU_GEM_TILE_32BPP ? 32 : 16); + + tile->addr = 0x00000001 | addr; + tile->limit = max(1u, addr + size) - 1; + tile->pitch = pitch; + + /* Allocate some of the on-die tag memory, used to store Z + * compression meta-data (most likely just a bitmap determining + * if a given tile is compressed or not). + */ + if (flags & NOUVEAU_GEM_TILE_ZETA) { + tile->tag_mem = nv20_fb_alloc_tag(dev, size / 256); + if (tile->tag_mem) { + /* Enable Z compression */ + tile->zcomp = tile->tag_mem->start; + if (dev_priv->chipset >= 0x25) { + if (bpp == 16) + tile->zcomp |= NV25_PFB_ZCOMP_MODE_16; + else + tile->zcomp |= NV25_PFB_ZCOMP_MODE_32; + } else { + tile->zcomp |= NV20_PFB_ZCOMP_EN; + if (bpp != 16) + tile->zcomp |= NV20_PFB_ZCOMP_MODE_32; + } + } + + tile->addr |= 2; + } +} + +void +nv20_fb_free_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + tile->addr = tile->limit = tile->pitch = tile->zcomp = 0; + nv20_fb_free_tag(dev, &tile->tag_mem); +} + +void +nv20_fb_set_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + nv_wr32(dev, NV10_PFB_TLIMIT(i), tile->limit); + nv_wr32(dev, NV10_PFB_TSIZE(i), tile->pitch); + nv_wr32(dev, NV10_PFB_TILE(i), tile->addr); + nv_wr32(dev, NV20_PFB_ZCOMP(i), tile->zcomp); +} + +int +nv20_fb_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 mem_size = nv_rd32(dev, 0x10020c); + u32 pbus1218 = nv_rd32(dev, 0x001218); + + dev_priv->vram_size = mem_size & 0xff000000; + switch (pbus1218 & 0x00000300) { + case 0x00000000: dev_priv->vram_type = NV_MEM_TYPE_SDRAM; break; + case 0x00000100: dev_priv->vram_type = NV_MEM_TYPE_DDR1; break; + case 0x00000200: dev_priv->vram_type = NV_MEM_TYPE_GDDR3; break; + case 0x00000300: dev_priv->vram_type = NV_MEM_TYPE_GDDR2; break; + } + + return 0; +} + +int +nv20_fb_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + int i; + + if (dev_priv->chipset >= 0x25) + drm_mm_init(&pfb->tag_heap, 0, 64 * 1024); + else + drm_mm_init(&pfb->tag_heap, 0, 32 * 1024); + + /* Turn all the tiling regions off. */ + pfb->num_tiles = NV10_PFB_TILE__SIZE; + for (i = 0; i < pfb->num_tiles; i++) + pfb->set_tile_region(dev, i); + + return 0; +} + +void +nv20_fb_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + int i; + + for (i = 0; i < pfb->num_tiles; i++) + pfb->free_tile_region(dev, i); + + drm_mm_takedown(&pfb->tag_heap); +} diff --git a/drivers/gpu/drm/nouveau/nv20_graph.c b/drivers/gpu/drm/nouveau/nv20_graph.c new file mode 100644 index 00000000..183e3751 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv20_graph.c @@ -0,0 +1,842 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +/* + * NV20 + * ----- + * There are 3 families : + * NV20 is 0x10de:0x020* + * NV25/28 is 0x10de:0x025* / 0x10de:0x028* + * NV2A is 0x10de:0x02A0 + * + * NV30 + * ----- + * There are 3 families : + * NV30/31 is 0x10de:0x030* / 0x10de:0x031* + * NV34 is 0x10de:0x032* + * NV35/36 is 0x10de:0x033* / 0x10de:0x034* + * + * Not seen in the wild, no dumps (probably NV35) : + * NV37 is 0x10de:0x00fc, 0x10de:0x00fd + * NV38 is 0x10de:0x0333, 0x10de:0x00fe + * + */ + +struct nv20_graph_engine { + struct nouveau_exec_engine base; + struct nouveau_gpuobj *ctxtab; + void (*grctx_init)(struct nouveau_gpuobj *); + u32 grctx_size; + u32 grctx_user; +}; + +#define NV20_GRCTX_SIZE (3580*4) +#define NV25_GRCTX_SIZE (3529*4) +#define NV2A_GRCTX_SIZE (3500*4) + +#define NV30_31_GRCTX_SIZE (24392) +#define NV34_GRCTX_SIZE (18140) +#define NV35_36_GRCTX_SIZE (22396) + +int +nv20_graph_unload_context(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_channel *chan; + struct nouveau_gpuobj *grctx; + u32 tmp; + + chan = nv10_graph_channel(dev); + if (!chan) + return 0; + grctx = chan->engctx[NVOBJ_ENGINE_GR]; + + nv_wr32(dev, NV20_PGRAPH_CHANNEL_CTX_POINTER, grctx->pinst >> 4); + nv_wr32(dev, NV20_PGRAPH_CHANNEL_CTX_XFER, + NV20_PGRAPH_CHANNEL_CTX_XFER_SAVE); + + nouveau_wait_for_idle(dev); + + nv_wr32(dev, NV10_PGRAPH_CTX_CONTROL, 0x10000000); + tmp = nv_rd32(dev, NV10_PGRAPH_CTX_USER) & 0x00ffffff; + tmp |= (pfifo->channels - 1) << 24; + nv_wr32(dev, NV10_PGRAPH_CTX_USER, tmp); + return 0; +} + +static void +nv20_graph_rdi(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i, writecount = 32; + uint32_t rdi_index = 0x2c80000; + + if (dev_priv->chipset == 0x20) { + rdi_index = 0x3d0000; + writecount = 15; + } + + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, rdi_index); + for (i = 0; i < writecount; i++) + nv_wr32(dev, NV10_PGRAPH_RDI_DATA, 0); + + nouveau_wait_for_idle(dev); +} + +static void +nv20_graph_context_init(struct nouveau_gpuobj *ctx) +{ + int i; + + nv_wo32(ctx, 0x033c, 0xffff0000); + nv_wo32(ctx, 0x03a0, 0x0fff0000); + nv_wo32(ctx, 0x03a4, 0x0fff0000); + nv_wo32(ctx, 0x047c, 0x00000101); + nv_wo32(ctx, 0x0490, 0x00000111); + nv_wo32(ctx, 0x04a8, 0x44400000); + for (i = 0x04d4; i <= 0x04e0; i += 4) + nv_wo32(ctx, i, 0x00030303); + for (i = 0x04f4; i <= 0x0500; i += 4) + nv_wo32(ctx, i, 0x00080000); + for (i = 0x050c; i <= 0x0518; i += 4) + nv_wo32(ctx, i, 0x01012000); + for (i = 0x051c; i <= 0x0528; i += 4) + nv_wo32(ctx, i, 0x000105b8); + for (i = 0x052c; i <= 0x0538; i += 4) + nv_wo32(ctx, i, 0x00080008); + for (i = 0x055c; i <= 0x0598; i += 4) + nv_wo32(ctx, i, 0x07ff0000); + nv_wo32(ctx, 0x05a4, 0x4b7fffff); + nv_wo32(ctx, 0x05fc, 0x00000001); + nv_wo32(ctx, 0x0604, 0x00004000); + nv_wo32(ctx, 0x0610, 0x00000001); + nv_wo32(ctx, 0x0618, 0x00040000); + nv_wo32(ctx, 0x061c, 0x00010000); + for (i = 0x1c1c; i <= 0x248c; i += 16) { + nv_wo32(ctx, (i + 0), 0x10700ff9); + nv_wo32(ctx, (i + 4), 0x0436086c); + nv_wo32(ctx, (i + 8), 0x000c001b); + } + nv_wo32(ctx, 0x281c, 0x3f800000); + nv_wo32(ctx, 0x2830, 0x3f800000); + nv_wo32(ctx, 0x285c, 0x40000000); + nv_wo32(ctx, 0x2860, 0x3f800000); + nv_wo32(ctx, 0x2864, 0x3f000000); + nv_wo32(ctx, 0x286c, 0x40000000); + nv_wo32(ctx, 0x2870, 0x3f800000); + nv_wo32(ctx, 0x2878, 0xbf800000); + nv_wo32(ctx, 0x2880, 0xbf800000); + nv_wo32(ctx, 0x34a4, 0x000fe000); + nv_wo32(ctx, 0x3530, 0x000003f8); + nv_wo32(ctx, 0x3540, 0x002fe000); + for (i = 0x355c; i <= 0x3578; i += 4) + nv_wo32(ctx, i, 0x001c527c); +} + +static void +nv25_graph_context_init(struct nouveau_gpuobj *ctx) +{ + int i; + + nv_wo32(ctx, 0x035c, 0xffff0000); + nv_wo32(ctx, 0x03c0, 0x0fff0000); + nv_wo32(ctx, 0x03c4, 0x0fff0000); + nv_wo32(ctx, 0x049c, 0x00000101); + nv_wo32(ctx, 0x04b0, 0x00000111); + nv_wo32(ctx, 0x04c8, 0x00000080); + nv_wo32(ctx, 0x04cc, 0xffff0000); + nv_wo32(ctx, 0x04d0, 0x00000001); + nv_wo32(ctx, 0x04e4, 0x44400000); + nv_wo32(ctx, 0x04fc, 0x4b800000); + for (i = 0x0510; i <= 0x051c; i += 4) + nv_wo32(ctx, i, 0x00030303); + for (i = 0x0530; i <= 0x053c; i += 4) + nv_wo32(ctx, i, 0x00080000); + for (i = 0x0548; i <= 0x0554; i += 4) + nv_wo32(ctx, i, 0x01012000); + for (i = 0x0558; i <= 0x0564; i += 4) + nv_wo32(ctx, i, 0x000105b8); + for (i = 0x0568; i <= 0x0574; i += 4) + nv_wo32(ctx, i, 0x00080008); + for (i = 0x0598; i <= 0x05d4; i += 4) + nv_wo32(ctx, i, 0x07ff0000); + nv_wo32(ctx, 0x05e0, 0x4b7fffff); + nv_wo32(ctx, 0x0620, 0x00000080); + nv_wo32(ctx, 0x0624, 0x30201000); + nv_wo32(ctx, 0x0628, 0x70605040); + nv_wo32(ctx, 0x062c, 0xb0a09080); + nv_wo32(ctx, 0x0630, 0xf0e0d0c0); + nv_wo32(ctx, 0x0664, 0x00000001); + nv_wo32(ctx, 0x066c, 0x00004000); + nv_wo32(ctx, 0x0678, 0x00000001); + nv_wo32(ctx, 0x0680, 0x00040000); + nv_wo32(ctx, 0x0684, 0x00010000); + for (i = 0x1b04; i <= 0x2374; i += 16) { + nv_wo32(ctx, (i + 0), 0x10700ff9); + nv_wo32(ctx, (i + 4), 0x0436086c); + nv_wo32(ctx, (i + 8), 0x000c001b); + } + nv_wo32(ctx, 0x2704, 0x3f800000); + nv_wo32(ctx, 0x2718, 0x3f800000); + nv_wo32(ctx, 0x2744, 0x40000000); + nv_wo32(ctx, 0x2748, 0x3f800000); + nv_wo32(ctx, 0x274c, 0x3f000000); + nv_wo32(ctx, 0x2754, 0x40000000); + nv_wo32(ctx, 0x2758, 0x3f800000); + nv_wo32(ctx, 0x2760, 0xbf800000); + nv_wo32(ctx, 0x2768, 0xbf800000); + nv_wo32(ctx, 0x308c, 0x000fe000); + nv_wo32(ctx, 0x3108, 0x000003f8); + nv_wo32(ctx, 0x3468, 0x002fe000); + for (i = 0x3484; i <= 0x34a0; i += 4) + nv_wo32(ctx, i, 0x001c527c); +} + +static void +nv2a_graph_context_init(struct nouveau_gpuobj *ctx) +{ + int i; + + nv_wo32(ctx, 0x033c, 0xffff0000); + nv_wo32(ctx, 0x03a0, 0x0fff0000); + nv_wo32(ctx, 0x03a4, 0x0fff0000); + nv_wo32(ctx, 0x047c, 0x00000101); + nv_wo32(ctx, 0x0490, 0x00000111); + nv_wo32(ctx, 0x04a8, 0x44400000); + for (i = 0x04d4; i <= 0x04e0; i += 4) + nv_wo32(ctx, i, 0x00030303); + for (i = 0x04f4; i <= 0x0500; i += 4) + nv_wo32(ctx, i, 0x00080000); + for (i = 0x050c; i <= 0x0518; i += 4) + nv_wo32(ctx, i, 0x01012000); + for (i = 0x051c; i <= 0x0528; i += 4) + nv_wo32(ctx, i, 0x000105b8); + for (i = 0x052c; i <= 0x0538; i += 4) + nv_wo32(ctx, i, 0x00080008); + for (i = 0x055c; i <= 0x0598; i += 4) + nv_wo32(ctx, i, 0x07ff0000); + nv_wo32(ctx, 0x05a4, 0x4b7fffff); + nv_wo32(ctx, 0x05fc, 0x00000001); + nv_wo32(ctx, 0x0604, 0x00004000); + nv_wo32(ctx, 0x0610, 0x00000001); + nv_wo32(ctx, 0x0618, 0x00040000); + nv_wo32(ctx, 0x061c, 0x00010000); + for (i = 0x1a9c; i <= 0x22fc; i += 16) { /*XXX: check!! */ + nv_wo32(ctx, (i + 0), 0x10700ff9); + nv_wo32(ctx, (i + 4), 0x0436086c); + nv_wo32(ctx, (i + 8), 0x000c001b); + } + nv_wo32(ctx, 0x269c, 0x3f800000); + nv_wo32(ctx, 0x26b0, 0x3f800000); + nv_wo32(ctx, 0x26dc, 0x40000000); + nv_wo32(ctx, 0x26e0, 0x3f800000); + nv_wo32(ctx, 0x26e4, 0x3f000000); + nv_wo32(ctx, 0x26ec, 0x40000000); + nv_wo32(ctx, 0x26f0, 0x3f800000); + nv_wo32(ctx, 0x26f8, 0xbf800000); + nv_wo32(ctx, 0x2700, 0xbf800000); + nv_wo32(ctx, 0x3024, 0x000fe000); + nv_wo32(ctx, 0x30a0, 0x000003f8); + nv_wo32(ctx, 0x33fc, 0x002fe000); + for (i = 0x341c; i <= 0x3438; i += 4) + nv_wo32(ctx, i, 0x001c527c); +} + +static void +nv30_31_graph_context_init(struct nouveau_gpuobj *ctx) +{ + int i; + + nv_wo32(ctx, 0x0410, 0x00000101); + nv_wo32(ctx, 0x0424, 0x00000111); + nv_wo32(ctx, 0x0428, 0x00000060); + nv_wo32(ctx, 0x0444, 0x00000080); + nv_wo32(ctx, 0x0448, 0xffff0000); + nv_wo32(ctx, 0x044c, 0x00000001); + nv_wo32(ctx, 0x0460, 0x44400000); + nv_wo32(ctx, 0x048c, 0xffff0000); + for (i = 0x04e0; i < 0x04e8; i += 4) + nv_wo32(ctx, i, 0x0fff0000); + nv_wo32(ctx, 0x04ec, 0x00011100); + for (i = 0x0508; i < 0x0548; i += 4) + nv_wo32(ctx, i, 0x07ff0000); + nv_wo32(ctx, 0x0550, 0x4b7fffff); + nv_wo32(ctx, 0x058c, 0x00000080); + nv_wo32(ctx, 0x0590, 0x30201000); + nv_wo32(ctx, 0x0594, 0x70605040); + nv_wo32(ctx, 0x0598, 0xb8a89888); + nv_wo32(ctx, 0x059c, 0xf8e8d8c8); + nv_wo32(ctx, 0x05b0, 0xb0000000); + for (i = 0x0600; i < 0x0640; i += 4) + nv_wo32(ctx, i, 0x00010588); + for (i = 0x0640; i < 0x0680; i += 4) + nv_wo32(ctx, i, 0x00030303); + for (i = 0x06c0; i < 0x0700; i += 4) + nv_wo32(ctx, i, 0x0008aae4); + for (i = 0x0700; i < 0x0740; i += 4) + nv_wo32(ctx, i, 0x01012000); + for (i = 0x0740; i < 0x0780; i += 4) + nv_wo32(ctx, i, 0x00080008); + nv_wo32(ctx, 0x085c, 0x00040000); + nv_wo32(ctx, 0x0860, 0x00010000); + for (i = 0x0864; i < 0x0874; i += 4) + nv_wo32(ctx, i, 0x00040004); + for (i = 0x1f18; i <= 0x3088 ; i += 16) { + nv_wo32(ctx, i + 0, 0x10700ff9); + nv_wo32(ctx, i + 1, 0x0436086c); + nv_wo32(ctx, i + 2, 0x000c001b); + } + for (i = 0x30b8; i < 0x30c8; i += 4) + nv_wo32(ctx, i, 0x0000ffff); + nv_wo32(ctx, 0x344c, 0x3f800000); + nv_wo32(ctx, 0x3808, 0x3f800000); + nv_wo32(ctx, 0x381c, 0x3f800000); + nv_wo32(ctx, 0x3848, 0x40000000); + nv_wo32(ctx, 0x384c, 0x3f800000); + nv_wo32(ctx, 0x3850, 0x3f000000); + nv_wo32(ctx, 0x3858, 0x40000000); + nv_wo32(ctx, 0x385c, 0x3f800000); + nv_wo32(ctx, 0x3864, 0xbf800000); + nv_wo32(ctx, 0x386c, 0xbf800000); +} + +static void +nv34_graph_context_init(struct nouveau_gpuobj *ctx) +{ + int i; + + nv_wo32(ctx, 0x040c, 0x01000101); + nv_wo32(ctx, 0x0420, 0x00000111); + nv_wo32(ctx, 0x0424, 0x00000060); + nv_wo32(ctx, 0x0440, 0x00000080); + nv_wo32(ctx, 0x0444, 0xffff0000); + nv_wo32(ctx, 0x0448, 0x00000001); + nv_wo32(ctx, 0x045c, 0x44400000); + nv_wo32(ctx, 0x0480, 0xffff0000); + for (i = 0x04d4; i < 0x04dc; i += 4) + nv_wo32(ctx, i, 0x0fff0000); + nv_wo32(ctx, 0x04e0, 0x00011100); + for (i = 0x04fc; i < 0x053c; i += 4) + nv_wo32(ctx, i, 0x07ff0000); + nv_wo32(ctx, 0x0544, 0x4b7fffff); + nv_wo32(ctx, 0x057c, 0x00000080); + nv_wo32(ctx, 0x0580, 0x30201000); + nv_wo32(ctx, 0x0584, 0x70605040); + nv_wo32(ctx, 0x0588, 0xb8a89888); + nv_wo32(ctx, 0x058c, 0xf8e8d8c8); + nv_wo32(ctx, 0x05a0, 0xb0000000); + for (i = 0x05f0; i < 0x0630; i += 4) + nv_wo32(ctx, i, 0x00010588); + for (i = 0x0630; i < 0x0670; i += 4) + nv_wo32(ctx, i, 0x00030303); + for (i = 0x06b0; i < 0x06f0; i += 4) + nv_wo32(ctx, i, 0x0008aae4); + for (i = 0x06f0; i < 0x0730; i += 4) + nv_wo32(ctx, i, 0x01012000); + for (i = 0x0730; i < 0x0770; i += 4) + nv_wo32(ctx, i, 0x00080008); + nv_wo32(ctx, 0x0850, 0x00040000); + nv_wo32(ctx, 0x0854, 0x00010000); + for (i = 0x0858; i < 0x0868; i += 4) + nv_wo32(ctx, i, 0x00040004); + for (i = 0x15ac; i <= 0x271c ; i += 16) { + nv_wo32(ctx, i + 0, 0x10700ff9); + nv_wo32(ctx, i + 1, 0x0436086c); + nv_wo32(ctx, i + 2, 0x000c001b); + } + for (i = 0x274c; i < 0x275c; i += 4) + nv_wo32(ctx, i, 0x0000ffff); + nv_wo32(ctx, 0x2ae0, 0x3f800000); + nv_wo32(ctx, 0x2e9c, 0x3f800000); + nv_wo32(ctx, 0x2eb0, 0x3f800000); + nv_wo32(ctx, 0x2edc, 0x40000000); + nv_wo32(ctx, 0x2ee0, 0x3f800000); + nv_wo32(ctx, 0x2ee4, 0x3f000000); + nv_wo32(ctx, 0x2eec, 0x40000000); + nv_wo32(ctx, 0x2ef0, 0x3f800000); + nv_wo32(ctx, 0x2ef8, 0xbf800000); + nv_wo32(ctx, 0x2f00, 0xbf800000); +} + +static void +nv35_36_graph_context_init(struct nouveau_gpuobj *ctx) +{ + int i; + + nv_wo32(ctx, 0x040c, 0x00000101); + nv_wo32(ctx, 0x0420, 0x00000111); + nv_wo32(ctx, 0x0424, 0x00000060); + nv_wo32(ctx, 0x0440, 0x00000080); + nv_wo32(ctx, 0x0444, 0xffff0000); + nv_wo32(ctx, 0x0448, 0x00000001); + nv_wo32(ctx, 0x045c, 0x44400000); + nv_wo32(ctx, 0x0488, 0xffff0000); + for (i = 0x04dc; i < 0x04e4; i += 4) + nv_wo32(ctx, i, 0x0fff0000); + nv_wo32(ctx, 0x04e8, 0x00011100); + for (i = 0x0504; i < 0x0544; i += 4) + nv_wo32(ctx, i, 0x07ff0000); + nv_wo32(ctx, 0x054c, 0x4b7fffff); + nv_wo32(ctx, 0x0588, 0x00000080); + nv_wo32(ctx, 0x058c, 0x30201000); + nv_wo32(ctx, 0x0590, 0x70605040); + nv_wo32(ctx, 0x0594, 0xb8a89888); + nv_wo32(ctx, 0x0598, 0xf8e8d8c8); + nv_wo32(ctx, 0x05ac, 0xb0000000); + for (i = 0x0604; i < 0x0644; i += 4) + nv_wo32(ctx, i, 0x00010588); + for (i = 0x0644; i < 0x0684; i += 4) + nv_wo32(ctx, i, 0x00030303); + for (i = 0x06c4; i < 0x0704; i += 4) + nv_wo32(ctx, i, 0x0008aae4); + for (i = 0x0704; i < 0x0744; i += 4) + nv_wo32(ctx, i, 0x01012000); + for (i = 0x0744; i < 0x0784; i += 4) + nv_wo32(ctx, i, 0x00080008); + nv_wo32(ctx, 0x0860, 0x00040000); + nv_wo32(ctx, 0x0864, 0x00010000); + for (i = 0x0868; i < 0x0878; i += 4) + nv_wo32(ctx, i, 0x00040004); + for (i = 0x1f1c; i <= 0x308c ; i += 16) { + nv_wo32(ctx, i + 0, 0x10700ff9); + nv_wo32(ctx, i + 4, 0x0436086c); + nv_wo32(ctx, i + 8, 0x000c001b); + } + for (i = 0x30bc; i < 0x30cc; i += 4) + nv_wo32(ctx, i, 0x0000ffff); + nv_wo32(ctx, 0x3450, 0x3f800000); + nv_wo32(ctx, 0x380c, 0x3f800000); + nv_wo32(ctx, 0x3820, 0x3f800000); + nv_wo32(ctx, 0x384c, 0x40000000); + nv_wo32(ctx, 0x3850, 0x3f800000); + nv_wo32(ctx, 0x3854, 0x3f000000); + nv_wo32(ctx, 0x385c, 0x40000000); + nv_wo32(ctx, 0x3860, 0x3f800000); + nv_wo32(ctx, 0x3868, 0xbf800000); + nv_wo32(ctx, 0x3870, 0xbf800000); +} + +int +nv20_graph_context_new(struct nouveau_channel *chan, int engine) +{ + struct nv20_graph_engine *pgraph = nv_engine(chan->dev, engine); + struct nouveau_gpuobj *grctx = NULL; + struct drm_device *dev = chan->dev; + int ret; + + ret = nouveau_gpuobj_new(dev, NULL, pgraph->grctx_size, 16, + NVOBJ_FLAG_ZERO_ALLOC, &grctx); + if (ret) + return ret; + + /* Initialise default context values */ + pgraph->grctx_init(grctx); + + /* nv20: nv_wo32(dev, chan->ramin_grctx->gpuobj, 10, chan->id<<24); */ + /* CTX_USER */ + nv_wo32(grctx, pgraph->grctx_user, (chan->id << 24) | 0x1); + + nv_wo32(pgraph->ctxtab, chan->id * 4, grctx->pinst >> 4); + chan->engctx[engine] = grctx; + return 0; +} + +void +nv20_graph_context_del(struct nouveau_channel *chan, int engine) +{ + struct nv20_graph_engine *pgraph = nv_engine(chan->dev, engine); + struct nouveau_gpuobj *grctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000000); + + /* Unload the context if it's the currently active one */ + if (nv10_graph_channel(dev) == chan) + nv20_graph_unload_context(dev); + + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + /* Free the context resources */ + nv_wo32(pgraph->ctxtab, chan->id * 4, 0); + + nouveau_gpuobj_ref(NULL, &grctx); + chan->engctx[engine] = NULL; +} + +static void +nv20_graph_set_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + nv_wr32(dev, NV20_PGRAPH_TLIMIT(i), tile->limit); + nv_wr32(dev, NV20_PGRAPH_TSIZE(i), tile->pitch); + nv_wr32(dev, NV20_PGRAPH_TILE(i), tile->addr); + + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0030 + 4 * i); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA, tile->limit); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0050 + 4 * i); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA, tile->pitch); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0010 + 4 * i); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA, tile->addr); + + if (dev_priv->card_type == NV_20) { + nv_wr32(dev, NV20_PGRAPH_ZCOMP(i), tile->zcomp); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00ea0090 + 4 * i); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA, tile->zcomp); + } +} + +int +nv20_graph_init(struct drm_device *dev, int engine) +{ + struct nv20_graph_engine *pgraph = nv_engine(dev, engine); + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t tmp, vramsz; + int i; + + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) & ~NV_PMC_ENABLE_PGRAPH); + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) | NV_PMC_ENABLE_PGRAPH); + + nv_wr32(dev, NV20_PGRAPH_CHANNEL_CTX_TABLE, pgraph->ctxtab->pinst >> 4); + + nv20_graph_rdi(dev); + + nv_wr32(dev, NV03_PGRAPH_INTR , 0xFFFFFFFF); + nv_wr32(dev, NV03_PGRAPH_INTR_EN, 0xFFFFFFFF); + + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0xFFFFFFFF); + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0x00000000); + nv_wr32(dev, NV04_PGRAPH_DEBUG_1, 0x00118700); + nv_wr32(dev, NV04_PGRAPH_DEBUG_3, 0xF3CE0475); /* 0x4 = auto ctx switch */ + nv_wr32(dev, NV10_PGRAPH_DEBUG_4, 0x00000000); + nv_wr32(dev, 0x40009C , 0x00000040); + + if (dev_priv->chipset >= 0x25) { + nv_wr32(dev, 0x400890, 0x00a8cfff); + nv_wr32(dev, 0x400610, 0x304B1FB6); + nv_wr32(dev, 0x400B80, 0x1cbd3883); + nv_wr32(dev, 0x400B84, 0x44000000); + nv_wr32(dev, 0x400098, 0x40000080); + nv_wr32(dev, 0x400B88, 0x000000ff); + + } else { + nv_wr32(dev, 0x400880, 0x0008c7df); + nv_wr32(dev, 0x400094, 0x00000005); + nv_wr32(dev, 0x400B80, 0x45eae20e); + nv_wr32(dev, 0x400B84, 0x24000000); + nv_wr32(dev, 0x400098, 0x00000040); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00E00038); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , 0x00000030); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00E10038); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , 0x00000030); + } + + /* Turn all the tiling regions off. */ + for (i = 0; i < NV10_PFB_TILE__SIZE; i++) + nv20_graph_set_tile_region(dev, i); + + nv_wr32(dev, 0x4009a0, nv_rd32(dev, 0x100324)); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA000C); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA, nv_rd32(dev, 0x100324)); + + nv_wr32(dev, NV10_PGRAPH_CTX_CONTROL, 0x10000100); + nv_wr32(dev, NV10_PGRAPH_STATE , 0xFFFFFFFF); + + tmp = nv_rd32(dev, NV10_PGRAPH_SURFACE) & 0x0007ff00; + nv_wr32(dev, NV10_PGRAPH_SURFACE, tmp); + tmp = nv_rd32(dev, NV10_PGRAPH_SURFACE) | 0x00020100; + nv_wr32(dev, NV10_PGRAPH_SURFACE, tmp); + + /* begin RAM config */ + vramsz = pci_resource_len(dev->pdev, 0) - 1; + nv_wr32(dev, 0x4009A4, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x4009A8, nv_rd32(dev, NV04_PFB_CFG1)); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0000); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0004); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , nv_rd32(dev, NV04_PFB_CFG1)); + nv_wr32(dev, 0x400820, 0); + nv_wr32(dev, 0x400824, 0); + nv_wr32(dev, 0x400864, vramsz - 1); + nv_wr32(dev, 0x400868, vramsz - 1); + + /* interesting.. the below overwrites some of the tile setup above.. */ + nv_wr32(dev, 0x400B20, 0x00000000); + nv_wr32(dev, 0x400B04, 0xFFFFFFFF); + + nv_wr32(dev, NV03_PGRAPH_ABS_UCLIP_XMIN, 0); + nv_wr32(dev, NV03_PGRAPH_ABS_UCLIP_YMIN, 0); + nv_wr32(dev, NV03_PGRAPH_ABS_UCLIP_XMAX, 0x7fff); + nv_wr32(dev, NV03_PGRAPH_ABS_UCLIP_YMAX, 0x7fff); + + return 0; +} + +int +nv30_graph_init(struct drm_device *dev, int engine) +{ + struct nv20_graph_engine *pgraph = nv_engine(dev, engine); + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i; + + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) & ~NV_PMC_ENABLE_PGRAPH); + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) | NV_PMC_ENABLE_PGRAPH); + + nv_wr32(dev, NV20_PGRAPH_CHANNEL_CTX_TABLE, pgraph->ctxtab->pinst >> 4); + + nv_wr32(dev, NV03_PGRAPH_INTR , 0xFFFFFFFF); + nv_wr32(dev, NV03_PGRAPH_INTR_EN, 0xFFFFFFFF); + + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0xFFFFFFFF); + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0x00000000); + nv_wr32(dev, NV04_PGRAPH_DEBUG_1, 0x401287c0); + nv_wr32(dev, 0x400890, 0x01b463ff); + nv_wr32(dev, NV04_PGRAPH_DEBUG_3, 0xf2de0475); + nv_wr32(dev, NV10_PGRAPH_DEBUG_4, 0x00008000); + nv_wr32(dev, NV04_PGRAPH_LIMIT_VIOL_PIX, 0xf04bdff6); + nv_wr32(dev, 0x400B80, 0x1003d888); + nv_wr32(dev, 0x400B84, 0x0c000000); + nv_wr32(dev, 0x400098, 0x00000000); + nv_wr32(dev, 0x40009C, 0x0005ad00); + nv_wr32(dev, 0x400B88, 0x62ff00ff); /* suspiciously like PGRAPH_DEBUG_2 */ + nv_wr32(dev, 0x4000a0, 0x00000000); + nv_wr32(dev, 0x4000a4, 0x00000008); + nv_wr32(dev, 0x4008a8, 0xb784a400); + nv_wr32(dev, 0x400ba0, 0x002f8685); + nv_wr32(dev, 0x400ba4, 0x00231f3f); + nv_wr32(dev, 0x4008a4, 0x40000020); + + if (dev_priv->chipset == 0x34) { + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0004); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , 0x00200201); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0008); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , 0x00000008); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00EA0000); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , 0x00000032); + nv_wr32(dev, NV10_PGRAPH_RDI_INDEX, 0x00E00004); + nv_wr32(dev, NV10_PGRAPH_RDI_DATA , 0x00000002); + } + + nv_wr32(dev, 0x4000c0, 0x00000016); + + /* Turn all the tiling regions off. */ + for (i = 0; i < NV10_PFB_TILE__SIZE; i++) + nv20_graph_set_tile_region(dev, i); + + nv_wr32(dev, NV10_PGRAPH_CTX_CONTROL, 0x10000100); + nv_wr32(dev, NV10_PGRAPH_STATE , 0xFFFFFFFF); + nv_wr32(dev, 0x0040075c , 0x00000001); + + /* begin RAM config */ + /* vramsz = pci_resource_len(dev->pdev, 0) - 1; */ + nv_wr32(dev, 0x4009A4, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x4009A8, nv_rd32(dev, NV04_PFB_CFG1)); + if (dev_priv->chipset != 0x34) { + nv_wr32(dev, 0x400750, 0x00EA0000); + nv_wr32(dev, 0x400754, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x400750, 0x00EA0004); + nv_wr32(dev, 0x400754, nv_rd32(dev, NV04_PFB_CFG1)); + } + + return 0; +} + +int +nv20_graph_fini(struct drm_device *dev, int engine, bool suspend) +{ + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000000); + if (!nv_wait(dev, NV04_PGRAPH_STATUS, ~0, 0) && suspend) { + nv_mask(dev, NV04_PGRAPH_FIFO, 0x00000001, 0x00000001); + return -EBUSY; + } + nv20_graph_unload_context(dev); + nv_wr32(dev, NV03_PGRAPH_INTR_EN, 0x00000000); + return 0; +} + +static void +nv20_graph_isr(struct drm_device *dev) +{ + u32 stat; + + while ((stat = nv_rd32(dev, NV03_PGRAPH_INTR))) { + u32 nsource = nv_rd32(dev, NV03_PGRAPH_NSOURCE); + u32 nstatus = nv_rd32(dev, NV03_PGRAPH_NSTATUS); + u32 addr = nv_rd32(dev, NV04_PGRAPH_TRAPPED_ADDR); + u32 chid = (addr & 0x01f00000) >> 20; + u32 subc = (addr & 0x00070000) >> 16; + u32 mthd = (addr & 0x00001ffc); + u32 data = nv_rd32(dev, NV04_PGRAPH_TRAPPED_DATA); + u32 class = nv_rd32(dev, 0x400160 + subc * 4) & 0xfff; + u32 show = stat; + + if (stat & NV_PGRAPH_INTR_ERROR) { + if (nsource & NV03_PGRAPH_NSOURCE_ILLEGAL_MTHD) { + if (!nouveau_gpuobj_mthd_call2(dev, chid, class, mthd, data)) + show &= ~NV_PGRAPH_INTR_ERROR; + } + } + + nv_wr32(dev, NV03_PGRAPH_INTR, stat); + nv_wr32(dev, NV04_PGRAPH_FIFO, 0x00000001); + + if (show && nouveau_ratelimit()) { + NV_INFO(dev, "PGRAPH -"); + nouveau_bitfield_print(nv10_graph_intr, show); + printk(" nsource:"); + nouveau_bitfield_print(nv04_graph_nsource, nsource); + printk(" nstatus:"); + nouveau_bitfield_print(nv10_graph_nstatus, nstatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - ch %d/%d class 0x%04x " + "mthd 0x%04x data 0x%08x\n", + chid, subc, class, mthd, data); + } + } +} + +static void +nv20_graph_destroy(struct drm_device *dev, int engine) +{ + struct nv20_graph_engine *pgraph = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, 12); + nouveau_gpuobj_ref(NULL, &pgraph->ctxtab); + + NVOBJ_ENGINE_DEL(dev, GR); + kfree(pgraph); +} + +int +nv20_graph_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv20_graph_engine *pgraph; + int ret; + + pgraph = kzalloc(sizeof(*pgraph), GFP_KERNEL); + if (!pgraph) + return -ENOMEM; + + pgraph->base.destroy = nv20_graph_destroy; + pgraph->base.fini = nv20_graph_fini; + pgraph->base.context_new = nv20_graph_context_new; + pgraph->base.context_del = nv20_graph_context_del; + pgraph->base.object_new = nv04_graph_object_new; + pgraph->base.set_tile_region = nv20_graph_set_tile_region; + + pgraph->grctx_user = 0x0028; + if (dev_priv->card_type == NV_20) { + pgraph->base.init = nv20_graph_init; + switch (dev_priv->chipset) { + case 0x20: + pgraph->grctx_init = nv20_graph_context_init; + pgraph->grctx_size = NV20_GRCTX_SIZE; + pgraph->grctx_user = 0x0000; + break; + case 0x25: + case 0x28: + pgraph->grctx_init = nv25_graph_context_init; + pgraph->grctx_size = NV25_GRCTX_SIZE; + break; + case 0x2a: + pgraph->grctx_init = nv2a_graph_context_init; + pgraph->grctx_size = NV2A_GRCTX_SIZE; + pgraph->grctx_user = 0x0000; + break; + default: + NV_ERROR(dev, "PGRAPH: unknown chipset\n"); + kfree(pgraph); + return 0; + } + } else { + pgraph->base.init = nv30_graph_init; + switch (dev_priv->chipset) { + case 0x30: + case 0x31: + pgraph->grctx_init = nv30_31_graph_context_init; + pgraph->grctx_size = NV30_31_GRCTX_SIZE; + break; + case 0x34: + pgraph->grctx_init = nv34_graph_context_init; + pgraph->grctx_size = NV34_GRCTX_SIZE; + break; + case 0x35: + case 0x36: + pgraph->grctx_init = nv35_36_graph_context_init; + pgraph->grctx_size = NV35_36_GRCTX_SIZE; + break; + default: + NV_ERROR(dev, "PGRAPH: unknown chipset\n"); + kfree(pgraph); + return 0; + } + } + + /* Create Context Pointer Table */ + ret = nouveau_gpuobj_new(dev, NULL, 32 * 4, 16, NVOBJ_FLAG_ZERO_ALLOC, + &pgraph->ctxtab); + if (ret) { + kfree(pgraph); + return ret; + } + + NVOBJ_ENGINE_ADD(dev, GR, &pgraph->base); + nouveau_irq_register(dev, 12, nv20_graph_isr); + + /* nvsw */ + NVOBJ_CLASS(dev, 0x506e, SW); + NVOBJ_MTHD (dev, 0x506e, 0x0500, nv04_graph_mthd_page_flip); + + NVOBJ_CLASS(dev, 0x0030, GR); /* null */ + NVOBJ_CLASS(dev, 0x0039, GR); /* m2mf */ + NVOBJ_CLASS(dev, 0x004a, GR); /* gdirect */ + NVOBJ_CLASS(dev, 0x009f, GR); /* imageblit (nv12) */ + NVOBJ_CLASS(dev, 0x008a, GR); /* ifc */ + NVOBJ_CLASS(dev, 0x0089, GR); /* sifm */ + NVOBJ_CLASS(dev, 0x0062, GR); /* surf2d */ + NVOBJ_CLASS(dev, 0x0043, GR); /* rop */ + NVOBJ_CLASS(dev, 0x0012, GR); /* beta1 */ + NVOBJ_CLASS(dev, 0x0072, GR); /* beta4 */ + NVOBJ_CLASS(dev, 0x0019, GR); /* cliprect */ + NVOBJ_CLASS(dev, 0x0044, GR); /* pattern */ + if (dev_priv->card_type == NV_20) { + NVOBJ_CLASS(dev, 0x009e, GR); /* swzsurf */ + NVOBJ_CLASS(dev, 0x0096, GR); /* celcius */ + + /* kelvin */ + if (dev_priv->chipset < 0x25) + NVOBJ_CLASS(dev, 0x0097, GR); + else + NVOBJ_CLASS(dev, 0x0597, GR); + } else { + NVOBJ_CLASS(dev, 0x038a, GR); /* ifc (nv30) */ + NVOBJ_CLASS(dev, 0x0389, GR); /* sifm (nv30) */ + NVOBJ_CLASS(dev, 0x0362, GR); /* surf2d (nv30) */ + NVOBJ_CLASS(dev, 0x039e, GR); /* swzsurf */ + + /* rankine */ + if (0x00000003 & (1 << (dev_priv->chipset & 0x0f))) + NVOBJ_CLASS(dev, 0x0397, GR); + else + if (0x00000010 & (1 << (dev_priv->chipset & 0x0f))) + NVOBJ_CLASS(dev, 0x0697, GR); + else + if (0x000001e0 & (1 << (dev_priv->chipset & 0x0f))) + NVOBJ_CLASS(dev, 0x0497, GR); + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv30_fb.c b/drivers/gpu/drm/nouveau/nv30_fb.c new file mode 100644 index 00000000..e0135f0e --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv30_fb.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2010 Francisco Jerez. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +void +nv30_fb_init_tile_region(struct drm_device *dev, int i, uint32_t addr, + uint32_t size, uint32_t pitch, uint32_t flags) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + tile->addr = addr | 1; + tile->limit = max(1u, addr + size) - 1; + tile->pitch = pitch; +} + +void +nv30_fb_free_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + tile->addr = tile->limit = tile->pitch = 0; +} + +static int +calc_bias(struct drm_device *dev, int k, int i, int j) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int b = (dev_priv->chipset > 0x30 ? + nv_rd32(dev, 0x122c + 0x10 * k + 0x4 * j) >> (4 * (i ^ 1)) : + 0) & 0xf; + + return 2 * (b & 0x8 ? b - 0x10 : b); +} + +static int +calc_ref(struct drm_device *dev, int l, int k, int i) +{ + int j, x = 0; + + for (j = 0; j < 4; j++) { + int m = (l >> (8 * i) & 0xff) + calc_bias(dev, k, i, j); + + x |= (0x80 | clamp(m, 0, 0x1f)) << (8 * j); + } + + return x; +} + +int +nv30_fb_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + int i, j; + + pfb->num_tiles = NV10_PFB_TILE__SIZE; + + /* Turn all the tiling regions off. */ + for (i = 0; i < pfb->num_tiles; i++) + pfb->set_tile_region(dev, i); + + /* Init the memory timing regs at 0x10037c/0x1003ac */ + if (dev_priv->chipset == 0x30 || + dev_priv->chipset == 0x31 || + dev_priv->chipset == 0x35) { + /* Related to ROP count */ + int n = (dev_priv->chipset == 0x31 ? 2 : 4); + int l = nv_rd32(dev, 0x1003d0); + + for (i = 0; i < n; i++) { + for (j = 0; j < 3; j++) + nv_wr32(dev, 0x10037c + 0xc * i + 0x4 * j, + calc_ref(dev, l, 0, j)); + + for (j = 0; j < 2; j++) + nv_wr32(dev, 0x1003ac + 0x8 * i + 0x4 * j, + calc_ref(dev, l, 1, j)); + } + } + + return 0; +} + +void +nv30_fb_takedown(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv31_mpeg.c b/drivers/gpu/drm/nouveau/nv31_mpeg.c new file mode 100644 index 00000000..6f06a071 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv31_mpeg.c @@ -0,0 +1,344 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" + +struct nv31_mpeg_engine { + struct nouveau_exec_engine base; + atomic_t refcount; +}; + + +static int +nv31_mpeg_context_new(struct nouveau_channel *chan, int engine) +{ + struct nv31_mpeg_engine *pmpeg = nv_engine(chan->dev, engine); + + if (!atomic_add_unless(&pmpeg->refcount, 1, 1)) + return -EBUSY; + + chan->engctx[engine] = (void *)0xdeadcafe; + return 0; +} + +static void +nv31_mpeg_context_del(struct nouveau_channel *chan, int engine) +{ + struct nv31_mpeg_engine *pmpeg = nv_engine(chan->dev, engine); + atomic_dec(&pmpeg->refcount); + chan->engctx[engine] = NULL; +} + +static int +nv40_mpeg_context_new(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ctx = NULL; + unsigned long flags; + int ret; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + ret = nouveau_gpuobj_new(dev, NULL, 264 * 4, 16, NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &ctx); + if (ret) + return ret; + + nv_wo32(ctx, 0x78, 0x02001ec1); + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, 0x002500, 0x00000001, 0x00000000); + if ((nv_rd32(dev, 0x003204) & 0x1f) == chan->id) + nv_wr32(dev, 0x00330c, ctx->pinst >> 4); + nv_wo32(chan->ramfc, 0x54, ctx->pinst >> 4); + nv_mask(dev, 0x002500, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + chan->engctx[engine] = ctx; + return 0; +} + +static void +nv40_mpeg_context_del(struct nouveau_channel *chan, int engine) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_gpuobj *ctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + unsigned long flags; + u32 inst = 0x80000000 | (ctx->pinst >> 4); + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000000); + if (nv_rd32(dev, 0x00b318) == inst) + nv_mask(dev, 0x00b318, 0x80000000, 0x00000000); + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + nouveau_gpuobj_ref(NULL, &ctx); + chan->engctx[engine] = NULL; +} + +static int +nv31_mpeg_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + struct drm_device *dev = chan->dev; + struct nouveau_gpuobj *obj = NULL; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 20, 16, NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &obj); + if (ret) + return ret; + obj->engine = 2; + obj->class = class; + + nv_wo32(obj, 0x00, class); + + ret = nouveau_ramht_insert(chan, handle, obj); + nouveau_gpuobj_ref(NULL, &obj); + return ret; +} + +static int +nv31_mpeg_init(struct drm_device *dev, int engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv31_mpeg_engine *pmpeg = nv_engine(dev, engine); + int i; + + /* VPE init */ + nv_mask(dev, 0x000200, 0x00000002, 0x00000000); + nv_mask(dev, 0x000200, 0x00000002, 0x00000002); + nv_wr32(dev, 0x00b0e0, 0x00000020); /* nvidia: rd 0x01, wr 0x20 */ + nv_wr32(dev, 0x00b0e8, 0x00000020); /* nvidia: rd 0x01, wr 0x20 */ + + for (i = 0; i < dev_priv->engine.fb.num_tiles; i++) + pmpeg->base.set_tile_region(dev, i); + + /* PMPEG init */ + nv_wr32(dev, 0x00b32c, 0x00000000); + nv_wr32(dev, 0x00b314, 0x00000100); + nv_wr32(dev, 0x00b220, nv44_graph_class(dev) ? 0x00000044 : 0x00000031); + nv_wr32(dev, 0x00b300, 0x02001ec1); + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000001); + + nv_wr32(dev, 0x00b100, 0xffffffff); + nv_wr32(dev, 0x00b140, 0xffffffff); + + if (!nv_wait(dev, 0x00b200, 0x00000001, 0x00000000)) { + NV_ERROR(dev, "PMPEG init: 0x%08x\n", nv_rd32(dev, 0x00b200)); + return -EBUSY; + } + + return 0; +} + +static int +nv31_mpeg_fini(struct drm_device *dev, int engine, bool suspend) +{ + /*XXX: context save? */ + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000000); + nv_wr32(dev, 0x00b140, 0x00000000); + return 0; +} + +static int +nv31_mpeg_mthd_dma(struct nouveau_channel *chan, u32 class, u32 mthd, u32 data) +{ + struct drm_device *dev = chan->dev; + u32 inst = data << 4; + u32 dma0 = nv_ri32(dev, inst + 0); + u32 dma1 = nv_ri32(dev, inst + 4); + u32 dma2 = nv_ri32(dev, inst + 8); + u32 base = (dma2 & 0xfffff000) | (dma0 >> 20); + u32 size = dma1 + 1; + + /* only allow linear DMA objects */ + if (!(dma0 & 0x00002000)) + return -EINVAL; + + if (mthd == 0x0190) { + /* DMA_CMD */ + nv_mask(dev, 0x00b300, 0x00030000, (dma0 & 0x00030000)); + nv_wr32(dev, 0x00b334, base); + nv_wr32(dev, 0x00b324, size); + } else + if (mthd == 0x01a0) { + /* DMA_DATA */ + nv_mask(dev, 0x00b300, 0x000c0000, (dma0 & 0x00030000) << 2); + nv_wr32(dev, 0x00b360, base); + nv_wr32(dev, 0x00b364, size); + } else { + /* DMA_IMAGE, VRAM only */ + if (dma0 & 0x000c0000) + return -EINVAL; + + nv_wr32(dev, 0x00b370, base); + nv_wr32(dev, 0x00b374, size); + } + + return 0; +} + +static int +nv31_mpeg_isr_chid(struct drm_device *dev, u32 inst) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ctx; + unsigned long flags; + int i; + + /* hardcode drm channel id on nv3x, so swmthd lookup works */ + if (dev_priv->card_type < NV_40) + return 0; + + spin_lock_irqsave(&dev_priv->channels.lock, flags); + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + if (!dev_priv->channels.ptr[i]) + continue; + + ctx = dev_priv->channels.ptr[i]->engctx[NVOBJ_ENGINE_MPEG]; + if (ctx && ctx->pinst == inst) + break; + } + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + return i; +} + +static void +nv31_vpe_set_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + nv_wr32(dev, 0x00b008 + (i * 0x10), tile->pitch); + nv_wr32(dev, 0x00b004 + (i * 0x10), tile->limit); + nv_wr32(dev, 0x00b000 + (i * 0x10), tile->addr); +} + +static void +nv31_mpeg_isr(struct drm_device *dev) +{ + u32 inst = (nv_rd32(dev, 0x00b318) & 0x000fffff) << 4; + u32 chid = nv31_mpeg_isr_chid(dev, inst); + u32 stat = nv_rd32(dev, 0x00b100); + u32 type = nv_rd32(dev, 0x00b230); + u32 mthd = nv_rd32(dev, 0x00b234); + u32 data = nv_rd32(dev, 0x00b238); + u32 show = stat; + + if (stat & 0x01000000) { + /* happens on initial binding of the object */ + if (type == 0x00000020 && mthd == 0x0000) { + nv_mask(dev, 0x00b308, 0x00000000, 0x00000000); + show &= ~0x01000000; + } + + if (type == 0x00000010) { + if (!nouveau_gpuobj_mthd_call2(dev, chid, 0x3174, mthd, data)) + show &= ~0x01000000; + } + } + + nv_wr32(dev, 0x00b100, stat); + nv_wr32(dev, 0x00b230, 0x00000001); + + if (show && nouveau_ratelimit()) { + NV_INFO(dev, "PMPEG: Ch %d [0x%08x] 0x%08x 0x%08x 0x%08x 0x%08x\n", + chid, inst, stat, type, mthd, data); + } +} + +static void +nv31_vpe_isr(struct drm_device *dev) +{ + if (nv_rd32(dev, 0x00b100)) + nv31_mpeg_isr(dev); + + if (nv_rd32(dev, 0x00b800)) { + u32 stat = nv_rd32(dev, 0x00b800); + NV_INFO(dev, "PMSRCH: 0x%08x\n", stat); + nv_wr32(dev, 0xb800, stat); + } +} + +static void +nv31_mpeg_destroy(struct drm_device *dev, int engine) +{ + struct nv31_mpeg_engine *pmpeg = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, 0); + + NVOBJ_ENGINE_DEL(dev, MPEG); + kfree(pmpeg); +} + +int +nv31_mpeg_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv31_mpeg_engine *pmpeg; + + pmpeg = kzalloc(sizeof(*pmpeg), GFP_KERNEL); + if (!pmpeg) + return -ENOMEM; + atomic_set(&pmpeg->refcount, 0); + + pmpeg->base.destroy = nv31_mpeg_destroy; + pmpeg->base.init = nv31_mpeg_init; + pmpeg->base.fini = nv31_mpeg_fini; + if (dev_priv->card_type < NV_40) { + pmpeg->base.context_new = nv31_mpeg_context_new; + pmpeg->base.context_del = nv31_mpeg_context_del; + } else { + pmpeg->base.context_new = nv40_mpeg_context_new; + pmpeg->base.context_del = nv40_mpeg_context_del; + } + pmpeg->base.object_new = nv31_mpeg_object_new; + + /* ISR vector, PMC_ENABLE bit, and TILE regs are shared between + * all VPE engines, for this driver's purposes the PMPEG engine + * will be treated as the "master" and handle the global VPE + * bits too + */ + pmpeg->base.set_tile_region = nv31_vpe_set_tile_region; + nouveau_irq_register(dev, 0, nv31_vpe_isr); + + NVOBJ_ENGINE_ADD(dev, MPEG, &pmpeg->base); + NVOBJ_CLASS(dev, 0x3174, MPEG); + NVOBJ_MTHD (dev, 0x3174, 0x0190, nv31_mpeg_mthd_dma); + NVOBJ_MTHD (dev, 0x3174, 0x01a0, nv31_mpeg_mthd_dma); + NVOBJ_MTHD (dev, 0x3174, 0x01b0, nv31_mpeg_mthd_dma); + +#if 0 + NVOBJ_ENGINE_ADD(dev, ME, &pme->base); + NVOBJ_CLASS(dev, 0x4075, ME); +#endif + return 0; + +} diff --git a/drivers/gpu/drm/nouveau/nv40_fb.c b/drivers/gpu/drm/nouveau/nv40_fb.c new file mode 100644 index 00000000..7fbcb334 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv40_fb.c @@ -0,0 +1,163 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +void +nv40_fb_set_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + switch (dev_priv->chipset) { + case 0x40: + nv_wr32(dev, NV10_PFB_TLIMIT(i), tile->limit); + nv_wr32(dev, NV10_PFB_TSIZE(i), tile->pitch); + nv_wr32(dev, NV10_PFB_TILE(i), tile->addr); + break; + + default: + nv_wr32(dev, NV40_PFB_TLIMIT(i), tile->limit); + nv_wr32(dev, NV40_PFB_TSIZE(i), tile->pitch); + nv_wr32(dev, NV40_PFB_TILE(i), tile->addr); + break; + } +} + +static void +nv40_fb_init_gart(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gart = dev_priv->gart_info.sg_ctxdma; + + if (dev_priv->gart_info.type != NOUVEAU_GART_HW) { + nv_wr32(dev, 0x100800, 0x00000001); + return; + } + + nv_wr32(dev, 0x100800, gart->pinst | 0x00000002); + nv_mask(dev, 0x10008c, 0x00000100, 0x00000100); + nv_wr32(dev, 0x100820, 0x00000000); +} + +static void +nv44_fb_init_gart(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *gart = dev_priv->gart_info.sg_ctxdma; + u32 vinst; + + if (dev_priv->gart_info.type != NOUVEAU_GART_HW) { + nv_wr32(dev, 0x100850, 0x80000000); + nv_wr32(dev, 0x100800, 0x00000001); + return; + } + + /* calculate vram address of this PRAMIN block, object + * must be allocated on 512KiB alignment, and not exceed + * a total size of 512KiB for this to work correctly + */ + vinst = nv_rd32(dev, 0x10020c); + vinst -= ((gart->pinst >> 19) + 1) << 19; + + nv_wr32(dev, 0x100850, 0x80000000); + nv_wr32(dev, 0x100818, dev_priv->gart_info.dummy.addr); + + nv_wr32(dev, 0x100804, dev_priv->gart_info.aper_size); + nv_wr32(dev, 0x100850, 0x00008000); + nv_mask(dev, 0x10008c, 0x00000200, 0x00000200); + nv_wr32(dev, 0x100820, 0x00000000); + nv_wr32(dev, 0x10082c, 0x00000001); + nv_wr32(dev, 0x100800, vinst | 0x00000010); +} + +int +nv40_fb_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* 0x001218 is actually present on a few other NV4X I looked at, + * and even contains sane values matching 0x100474. From looking + * at various vbios images however, this isn't the case everywhere. + * So, I chose to use the same regs I've seen NVIDIA reading around + * the memory detection, hopefully that'll get us the right numbers + */ + if (dev_priv->chipset == 0x40) { + u32 pbus1218 = nv_rd32(dev, 0x001218); + switch (pbus1218 & 0x00000300) { + case 0x00000000: dev_priv->vram_type = NV_MEM_TYPE_SDRAM; break; + case 0x00000100: dev_priv->vram_type = NV_MEM_TYPE_DDR1; break; + case 0x00000200: dev_priv->vram_type = NV_MEM_TYPE_GDDR3; break; + case 0x00000300: dev_priv->vram_type = NV_MEM_TYPE_DDR2; break; + } + } else + if (dev_priv->chipset == 0x49 || dev_priv->chipset == 0x4b) { + u32 pfb914 = nv_rd32(dev, 0x100914); + switch (pfb914 & 0x00000003) { + case 0x00000000: dev_priv->vram_type = NV_MEM_TYPE_DDR1; break; + case 0x00000001: dev_priv->vram_type = NV_MEM_TYPE_DDR2; break; + case 0x00000002: dev_priv->vram_type = NV_MEM_TYPE_GDDR3; break; + case 0x00000003: break; + } + } else + if (dev_priv->chipset != 0x4e) { + u32 pfb474 = nv_rd32(dev, 0x100474); + if (pfb474 & 0x00000004) + dev_priv->vram_type = NV_MEM_TYPE_GDDR3; + if (pfb474 & 0x00000002) + dev_priv->vram_type = NV_MEM_TYPE_DDR2; + if (pfb474 & 0x00000001) + dev_priv->vram_type = NV_MEM_TYPE_DDR1; + } else { + dev_priv->vram_type = NV_MEM_TYPE_STOLEN; + } + + dev_priv->vram_size = nv_rd32(dev, 0x10020c) & 0xff000000; + return 0; +} + +int +nv40_fb_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + uint32_t tmp; + int i; + + if (dev_priv->chipset != 0x40 && dev_priv->chipset != 0x45) { + if (nv44_graph_class(dev)) + nv44_fb_init_gart(dev); + else + nv40_fb_init_gart(dev); + } + + switch (dev_priv->chipset) { + case 0x40: + case 0x45: + tmp = nv_rd32(dev, NV10_PFB_CLOSE_PAGE2); + nv_wr32(dev, NV10_PFB_CLOSE_PAGE2, tmp & ~(1 << 15)); + pfb->num_tiles = NV10_PFB_TILE__SIZE; + break; + case 0x46: /* G72 */ + case 0x47: /* G70 */ + case 0x49: /* G71 */ + case 0x4b: /* G73 */ + case 0x4c: /* C51 (G7X version) */ + pfb->num_tiles = NV40_PFB_TILE__SIZE_1; + break; + default: + pfb->num_tiles = NV40_PFB_TILE__SIZE_0; + break; + } + + /* Turn all the tiling regions off. */ + for (i = 0; i < pfb->num_tiles; i++) + pfb->set_tile_region(dev, i); + + return 0; +} + +void +nv40_fb_takedown(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv40_fifo.c b/drivers/gpu/drm/nouveau/nv40_fifo.c new file mode 100644 index 00000000..68cb2d99 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv40_fifo.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" +#include "nouveau_ramht.h" + +#define NV40_RAMFC(c) (dev_priv->ramfc->pinst + ((c) * NV40_RAMFC__SIZE)) +#define NV40_RAMFC__SIZE 128 + +int +nv40_fifo_create_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t fc = NV40_RAMFC(chan->id); + unsigned long flags; + int ret; + + ret = nouveau_gpuobj_new_fake(dev, NV40_RAMFC(chan->id), ~0, + NV40_RAMFC__SIZE, NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &chan->ramfc); + if (ret) + return ret; + + chan->user = ioremap(pci_resource_start(dev->pdev, 0) + + NV40_USER(chan->id), PAGE_SIZE); + if (!chan->user) + return -ENOMEM; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + + nv_wi32(dev, fc + 0, chan->pushbuf_base); + nv_wi32(dev, fc + 4, chan->pushbuf_base); + nv_wi32(dev, fc + 12, chan->pushbuf->pinst >> 4); + nv_wi32(dev, fc + 24, NV_PFIFO_CACHE1_DMA_FETCH_TRIG_128_BYTES | + NV_PFIFO_CACHE1_DMA_FETCH_SIZE_128_BYTES | + NV_PFIFO_CACHE1_DMA_FETCH_MAX_REQS_8 | +#ifdef __BIG_ENDIAN + NV_PFIFO_CACHE1_BIG_ENDIAN | +#endif + 0x30000000 /* no idea.. */); + nv_wi32(dev, fc + 60, 0x0001FFFF); + + /* enable the fifo dma operation */ + nv_wr32(dev, NV04_PFIFO_MODE, + nv_rd32(dev, NV04_PFIFO_MODE) | (1 << chan->id)); + + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + return 0; +} + +static void +nv40_fifo_do_load_context(struct drm_device *dev, int chid) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t fc = NV40_RAMFC(chid), tmp, tmp2; + + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUT, nv_ri32(dev, fc + 0)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_GET, nv_ri32(dev, fc + 4)); + nv_wr32(dev, NV10_PFIFO_CACHE1_REF_CNT, nv_ri32(dev, fc + 8)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_INSTANCE, nv_ri32(dev, fc + 12)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_DCOUNT, nv_ri32(dev, fc + 16)); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_STATE, nv_ri32(dev, fc + 20)); + + /* No idea what 0x2058 is.. */ + tmp = nv_ri32(dev, fc + 24); + tmp2 = nv_rd32(dev, 0x2058) & 0xFFF; + tmp2 |= (tmp & 0x30000000); + nv_wr32(dev, 0x2058, tmp2); + tmp &= ~0x30000000; + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_FETCH, tmp); + + nv_wr32(dev, NV04_PFIFO_CACHE1_ENGINE, nv_ri32(dev, fc + 28)); + nv_wr32(dev, NV04_PFIFO_CACHE1_PULL1, nv_ri32(dev, fc + 32)); + nv_wr32(dev, NV10_PFIFO_CACHE1_ACQUIRE_VALUE, nv_ri32(dev, fc + 36)); + tmp = nv_ri32(dev, fc + 40); + nv_wr32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMESTAMP, tmp); + nv_wr32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMEOUT, nv_ri32(dev, fc + 44)); + nv_wr32(dev, NV10_PFIFO_CACHE1_SEMAPHORE, nv_ri32(dev, fc + 48)); + nv_wr32(dev, NV10_PFIFO_CACHE1_DMA_SUBROUTINE, nv_ri32(dev, fc + 52)); + nv_wr32(dev, NV40_PFIFO_GRCTX_INSTANCE, nv_ri32(dev, fc + 56)); + + /* Don't clobber the TIMEOUT_ENABLED flag when restoring from RAMFC */ + tmp = nv_rd32(dev, NV04_PFIFO_DMA_TIMESLICE) & ~0x1FFFF; + tmp |= nv_ri32(dev, fc + 60) & 0x1FFFF; + nv_wr32(dev, NV04_PFIFO_DMA_TIMESLICE, tmp); + + nv_wr32(dev, 0x32e4, nv_ri32(dev, fc + 64)); + /* NVIDIA does this next line twice... */ + nv_wr32(dev, 0x32e8, nv_ri32(dev, fc + 68)); + nv_wr32(dev, 0x2088, nv_ri32(dev, fc + 76)); + nv_wr32(dev, 0x3300, nv_ri32(dev, fc + 80)); + nv_wr32(dev, 0x330c, nv_ri32(dev, fc + 84)); + + nv_wr32(dev, NV03_PFIFO_CACHE1_GET, 0); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUT, 0); +} + +int +nv40_fifo_load_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + uint32_t tmp; + + nv40_fifo_do_load_context(dev, chan->id); + + /* Set channel active, and in DMA mode */ + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, + NV40_PFIFO_CACHE1_PUSH1_DMA | chan->id); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_PUSH, 1); + + /* Reset DMA_CTL_AT_INFO to INVALID */ + tmp = nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_CTL) & ~(1 << 31); + nv_wr32(dev, NV04_PFIFO_CACHE1_DMA_CTL, tmp); + + return 0; +} + +int +nv40_fifo_unload_context(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + uint32_t fc, tmp; + int chid; + + chid = pfifo->channel_id(dev); + if (chid < 0 || chid >= dev_priv->engine.fifo.channels) + return 0; + fc = NV40_RAMFC(chid); + + nv_wi32(dev, fc + 0, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_PUT)); + nv_wi32(dev, fc + 4, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_GET)); + nv_wi32(dev, fc + 8, nv_rd32(dev, NV10_PFIFO_CACHE1_REF_CNT)); + nv_wi32(dev, fc + 12, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_INSTANCE)); + nv_wi32(dev, fc + 16, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_DCOUNT)); + nv_wi32(dev, fc + 20, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_STATE)); + tmp = nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_FETCH); + tmp |= nv_rd32(dev, 0x2058) & 0x30000000; + nv_wi32(dev, fc + 24, tmp); + nv_wi32(dev, fc + 28, nv_rd32(dev, NV04_PFIFO_CACHE1_ENGINE)); + nv_wi32(dev, fc + 32, nv_rd32(dev, NV04_PFIFO_CACHE1_PULL1)); + nv_wi32(dev, fc + 36, nv_rd32(dev, NV10_PFIFO_CACHE1_ACQUIRE_VALUE)); + tmp = nv_rd32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMESTAMP); + nv_wi32(dev, fc + 40, tmp); + nv_wi32(dev, fc + 44, nv_rd32(dev, NV10_PFIFO_CACHE1_ACQUIRE_TIMEOUT)); + nv_wi32(dev, fc + 48, nv_rd32(dev, NV10_PFIFO_CACHE1_SEMAPHORE)); + /* NVIDIA read 0x3228 first, then write DMA_GET here.. maybe something + * more involved depending on the value of 0x3228? + */ + nv_wi32(dev, fc + 52, nv_rd32(dev, NV04_PFIFO_CACHE1_DMA_GET)); + nv_wi32(dev, fc + 56, nv_rd32(dev, NV40_PFIFO_GRCTX_INSTANCE)); + nv_wi32(dev, fc + 60, nv_rd32(dev, NV04_PFIFO_DMA_TIMESLICE) & 0x1ffff); + /* No idea what the below is for exactly, ripped from a mmio-trace */ + nv_wi32(dev, fc + 64, nv_rd32(dev, NV40_PFIFO_UNK32E4)); + /* NVIDIA do this next line twice.. bug? */ + nv_wi32(dev, fc + 68, nv_rd32(dev, 0x32e8)); + nv_wi32(dev, fc + 76, nv_rd32(dev, 0x2088)); + nv_wi32(dev, fc + 80, nv_rd32(dev, 0x3300)); +#if 0 /* no real idea which is PUT/GET in UNK_48.. */ + tmp = nv_rd32(dev, NV04_PFIFO_CACHE1_GET); + tmp |= (nv_rd32(dev, NV04_PFIFO_CACHE1_PUT) << 16); + nv_wi32(dev, fc + 72, tmp); +#endif + nv_wi32(dev, fc + 84, nv_rd32(dev, 0x330c)); + + nv40_fifo_do_load_context(dev, pfifo->channels - 1); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, + NV40_PFIFO_CACHE1_PUSH1_DMA | (pfifo->channels - 1)); + return 0; +} + +static void +nv40_fifo_init_reset(struct drm_device *dev) +{ + int i; + + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) & ~NV_PMC_ENABLE_PFIFO); + nv_wr32(dev, NV03_PMC_ENABLE, + nv_rd32(dev, NV03_PMC_ENABLE) | NV_PMC_ENABLE_PFIFO); + + nv_wr32(dev, 0x003224, 0x000f0078); + nv_wr32(dev, 0x003210, 0x00000000); + nv_wr32(dev, 0x003270, 0x00000000); + nv_wr32(dev, 0x003240, 0x00000000); + nv_wr32(dev, 0x003244, 0x00000000); + nv_wr32(dev, 0x003258, 0x00000000); + nv_wr32(dev, 0x002504, 0x00000000); + for (i = 0; i < 16; i++) + nv_wr32(dev, 0x002510 + (i * 4), 0x00000000); + nv_wr32(dev, 0x00250c, 0x0000ffff); + nv_wr32(dev, 0x002048, 0x00000000); + nv_wr32(dev, 0x003228, 0x00000000); + nv_wr32(dev, 0x0032e8, 0x00000000); + nv_wr32(dev, 0x002410, 0x00000000); + nv_wr32(dev, 0x002420, 0x00000000); + nv_wr32(dev, 0x002058, 0x00000001); + nv_wr32(dev, 0x00221c, 0x00000000); + /* something with 0x2084, read/modify/write, no change */ + nv_wr32(dev, 0x002040, 0x000000ff); + nv_wr32(dev, 0x002500, 0x00000000); + nv_wr32(dev, 0x003200, 0x00000000); + + nv_wr32(dev, NV04_PFIFO_DMA_TIMESLICE, 0x2101ffff); +} + +static void +nv40_fifo_init_ramxx(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nv_wr32(dev, NV03_PFIFO_RAMHT, (0x03 << 24) /* search 128 */ | + ((dev_priv->ramht->bits - 9) << 16) | + (dev_priv->ramht->gpuobj->pinst >> 8)); + nv_wr32(dev, NV03_PFIFO_RAMRO, dev_priv->ramro->pinst >> 8); + + switch (dev_priv->chipset) { + case 0x47: + case 0x49: + case 0x4b: + nv_wr32(dev, 0x2230, 1); + break; + default: + break; + } + + switch (dev_priv->chipset) { + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x45: + case 0x47: + case 0x48: + case 0x49: + case 0x4b: + nv_wr32(dev, NV40_PFIFO_RAMFC, 0x30002); + break; + default: + nv_wr32(dev, 0x2230, 0); + nv_wr32(dev, NV40_PFIFO_RAMFC, + ((dev_priv->vram_size - 512 * 1024 + + dev_priv->ramfc->pinst) >> 16) | (3 << 16)); + break; + } +} + +static void +nv40_fifo_init_intr(struct drm_device *dev) +{ + nouveau_irq_register(dev, 8, nv04_fifo_isr); + nv_wr32(dev, 0x002100, 0xffffffff); + nv_wr32(dev, 0x002140, 0xffffffff); +} + +int +nv40_fifo_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + int i; + + nv40_fifo_init_reset(dev); + nv40_fifo_init_ramxx(dev); + + nv40_fifo_do_load_context(dev, pfifo->channels - 1); + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, pfifo->channels - 1); + + nv40_fifo_init_intr(dev); + pfifo->enable(dev); + pfifo->reassign(dev, true); + + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + if (dev_priv->channels.ptr[i]) { + uint32_t mode = nv_rd32(dev, NV04_PFIFO_MODE); + nv_wr32(dev, NV04_PFIFO_MODE, mode | (1 << i)); + } + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv40_graph.c b/drivers/gpu/drm/nouveau/nv40_graph.c new file mode 100644 index 00000000..ba14a93d --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv40_graph.c @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_grctx.h" +#include "nouveau_ramht.h" + +struct nv40_graph_engine { + struct nouveau_exec_engine base; + u32 grctx_size; +}; + +static int +nv40_graph_context_new(struct nouveau_channel *chan, int engine) +{ + struct nv40_graph_engine *pgraph = nv_engine(chan->dev, engine); + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *grctx = NULL; + struct nouveau_grctx ctx = {}; + unsigned long flags; + int ret; + + ret = nouveau_gpuobj_new(dev, NULL, pgraph->grctx_size, 16, + NVOBJ_FLAG_ZERO_ALLOC, &grctx); + if (ret) + return ret; + + /* Initialise default context values */ + ctx.dev = chan->dev; + ctx.mode = NOUVEAU_GRCTX_VALS; + ctx.data = grctx; + nv40_grctx_init(&ctx); + + nv_wo32(grctx, 0, grctx->vinst); + + /* init grctx pointer in ramfc, and on PFIFO if channel is + * already active there + */ + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_wo32(chan->ramfc, 0x38, grctx->vinst >> 4); + nv_mask(dev, 0x002500, 0x00000001, 0x00000000); + if ((nv_rd32(dev, 0x003204) & 0x0000001f) == chan->id) + nv_wr32(dev, 0x0032e0, grctx->vinst >> 4); + nv_mask(dev, 0x002500, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + chan->engctx[engine] = grctx; + return 0; +} + +static void +nv40_graph_context_del(struct nouveau_channel *chan, int engine) +{ + struct nouveau_gpuobj *grctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 inst = 0x01000000 | (grctx->pinst >> 4); + unsigned long flags; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, 0x400720, 0x00000000, 0x00000001); + if (nv_rd32(dev, 0x40032c) == inst) + nv_mask(dev, 0x40032c, 0x01000000, 0x00000000); + if (nv_rd32(dev, 0x400330) == inst) + nv_mask(dev, 0x400330, 0x01000000, 0x00000000); + nv_mask(dev, 0x400720, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + /* Free the context resources */ + nouveau_gpuobj_ref(NULL, &grctx); + chan->engctx[engine] = NULL; +} + +int +nv40_graph_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + struct drm_device *dev = chan->dev; + struct nouveau_gpuobj *obj = NULL; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 20, 16, NVOBJ_FLAG_ZERO_FREE, &obj); + if (ret) + return ret; + obj->engine = 1; + obj->class = class; + + nv_wo32(obj, 0x00, class); + nv_wo32(obj, 0x04, 0x00000000); +#ifndef __BIG_ENDIAN + nv_wo32(obj, 0x08, 0x00000000); +#else + nv_wo32(obj, 0x08, 0x01000000); +#endif + nv_wo32(obj, 0x0c, 0x00000000); + nv_wo32(obj, 0x10, 0x00000000); + + ret = nouveau_ramht_insert(chan, handle, obj); + nouveau_gpuobj_ref(NULL, &obj); + return ret; +} + +static void +nv40_graph_set_tile_region(struct drm_device *dev, int i) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_tile_reg *tile = &dev_priv->tile.reg[i]; + + switch (dev_priv->chipset) { + case 0x40: + case 0x41: /* guess */ + case 0x42: + case 0x43: + case 0x45: /* guess */ + case 0x4e: + nv_wr32(dev, NV20_PGRAPH_TSIZE(i), tile->pitch); + nv_wr32(dev, NV20_PGRAPH_TLIMIT(i), tile->limit); + nv_wr32(dev, NV20_PGRAPH_TILE(i), tile->addr); + nv_wr32(dev, NV40_PGRAPH_TSIZE1(i), tile->pitch); + nv_wr32(dev, NV40_PGRAPH_TLIMIT1(i), tile->limit); + nv_wr32(dev, NV40_PGRAPH_TILE1(i), tile->addr); + break; + case 0x44: + case 0x4a: + nv_wr32(dev, NV20_PGRAPH_TSIZE(i), tile->pitch); + nv_wr32(dev, NV20_PGRAPH_TLIMIT(i), tile->limit); + nv_wr32(dev, NV20_PGRAPH_TILE(i), tile->addr); + break; + case 0x46: + case 0x47: + case 0x49: + case 0x4b: + case 0x4c: + case 0x67: + default: + nv_wr32(dev, NV47_PGRAPH_TSIZE(i), tile->pitch); + nv_wr32(dev, NV47_PGRAPH_TLIMIT(i), tile->limit); + nv_wr32(dev, NV47_PGRAPH_TILE(i), tile->addr); + nv_wr32(dev, NV40_PGRAPH_TSIZE1(i), tile->pitch); + nv_wr32(dev, NV40_PGRAPH_TLIMIT1(i), tile->limit); + nv_wr32(dev, NV40_PGRAPH_TILE1(i), tile->addr); + break; + } +} + +/* + * G70 0x47 + * G71 0x49 + * NV45 0x48 + * G72[M] 0x46 + * G73 0x4b + * C51_G7X 0x4c + * C51 0x4e + */ +int +nv40_graph_init(struct drm_device *dev, int engine) +{ + struct nv40_graph_engine *pgraph = nv_engine(dev, engine); + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + struct nouveau_grctx ctx = {}; + uint32_t vramsz, *cp; + int i, j; + + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) & + ~NV_PMC_ENABLE_PGRAPH); + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) | + NV_PMC_ENABLE_PGRAPH); + + cp = kmalloc(sizeof(*cp) * 256, GFP_KERNEL); + if (!cp) + return -ENOMEM; + + ctx.dev = dev; + ctx.mode = NOUVEAU_GRCTX_PROG; + ctx.data = cp; + ctx.ctxprog_max = 256; + nv40_grctx_init(&ctx); + pgraph->grctx_size = ctx.ctxvals_pos * 4; + + nv_wr32(dev, NV40_PGRAPH_CTXCTL_UCODE_INDEX, 0); + for (i = 0; i < ctx.ctxprog_len; i++) + nv_wr32(dev, NV40_PGRAPH_CTXCTL_UCODE_DATA, cp[i]); + + kfree(cp); + + /* No context present currently */ + nv_wr32(dev, NV40_PGRAPH_CTXCTL_CUR, 0x00000000); + + nv_wr32(dev, NV03_PGRAPH_INTR , 0xFFFFFFFF); + nv_wr32(dev, NV40_PGRAPH_INTR_EN, 0xFFFFFFFF); + + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0xFFFFFFFF); + nv_wr32(dev, NV04_PGRAPH_DEBUG_0, 0x00000000); + nv_wr32(dev, NV04_PGRAPH_DEBUG_1, 0x401287c0); + nv_wr32(dev, NV04_PGRAPH_DEBUG_3, 0xe0de8055); + nv_wr32(dev, NV10_PGRAPH_DEBUG_4, 0x00008000); + nv_wr32(dev, NV04_PGRAPH_LIMIT_VIOL_PIX, 0x00be3c5f); + + nv_wr32(dev, NV10_PGRAPH_CTX_CONTROL, 0x10010100); + nv_wr32(dev, NV10_PGRAPH_STATE , 0xFFFFFFFF); + + j = nv_rd32(dev, 0x1540) & 0xff; + if (j) { + for (i = 0; !(j & 1); j >>= 1, i++) + ; + nv_wr32(dev, 0x405000, i); + } + + if (dev_priv->chipset == 0x40) { + nv_wr32(dev, 0x4009b0, 0x83280fff); + nv_wr32(dev, 0x4009b4, 0x000000a0); + } else { + nv_wr32(dev, 0x400820, 0x83280eff); + nv_wr32(dev, 0x400824, 0x000000a0); + } + + switch (dev_priv->chipset) { + case 0x40: + case 0x45: + nv_wr32(dev, 0x4009b8, 0x0078e366); + nv_wr32(dev, 0x4009bc, 0x0000014c); + break; + case 0x41: + case 0x42: /* pciid also 0x00Cx */ + /* case 0x0120: XXX (pciid) */ + nv_wr32(dev, 0x400828, 0x007596ff); + nv_wr32(dev, 0x40082c, 0x00000108); + break; + case 0x43: + nv_wr32(dev, 0x400828, 0x0072cb77); + nv_wr32(dev, 0x40082c, 0x00000108); + break; + case 0x44: + case 0x46: /* G72 */ + case 0x4a: + case 0x4c: /* G7x-based C51 */ + case 0x4e: + nv_wr32(dev, 0x400860, 0); + nv_wr32(dev, 0x400864, 0); + break; + case 0x47: /* G70 */ + case 0x49: /* G71 */ + case 0x4b: /* G73 */ + nv_wr32(dev, 0x400828, 0x07830610); + nv_wr32(dev, 0x40082c, 0x0000016A); + break; + default: + break; + } + + nv_wr32(dev, 0x400b38, 0x2ffff800); + nv_wr32(dev, 0x400b3c, 0x00006000); + + /* Tiling related stuff. */ + switch (dev_priv->chipset) { + case 0x44: + case 0x4a: + nv_wr32(dev, 0x400bc4, 0x1003d888); + nv_wr32(dev, 0x400bbc, 0xb7a7b500); + break; + case 0x46: + nv_wr32(dev, 0x400bc4, 0x0000e024); + nv_wr32(dev, 0x400bbc, 0xb7a7b520); + break; + case 0x4c: + case 0x4e: + case 0x67: + nv_wr32(dev, 0x400bc4, 0x1003d888); + nv_wr32(dev, 0x400bbc, 0xb7a7b540); + break; + default: + break; + } + + /* Turn all the tiling regions off. */ + for (i = 0; i < pfb->num_tiles; i++) + nv40_graph_set_tile_region(dev, i); + + /* begin RAM config */ + vramsz = pci_resource_len(dev->pdev, 0) - 1; + switch (dev_priv->chipset) { + case 0x40: + nv_wr32(dev, 0x4009A4, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x4009A8, nv_rd32(dev, NV04_PFB_CFG1)); + nv_wr32(dev, 0x4069A4, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x4069A8, nv_rd32(dev, NV04_PFB_CFG1)); + nv_wr32(dev, 0x400820, 0); + nv_wr32(dev, 0x400824, 0); + nv_wr32(dev, 0x400864, vramsz); + nv_wr32(dev, 0x400868, vramsz); + break; + default: + switch (dev_priv->chipset) { + case 0x41: + case 0x42: + case 0x43: + case 0x45: + case 0x4e: + case 0x44: + case 0x4a: + nv_wr32(dev, 0x4009F0, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x4009F4, nv_rd32(dev, NV04_PFB_CFG1)); + break; + default: + nv_wr32(dev, 0x400DF0, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x400DF4, nv_rd32(dev, NV04_PFB_CFG1)); + break; + } + nv_wr32(dev, 0x4069F0, nv_rd32(dev, NV04_PFB_CFG0)); + nv_wr32(dev, 0x4069F4, nv_rd32(dev, NV04_PFB_CFG1)); + nv_wr32(dev, 0x400840, 0); + nv_wr32(dev, 0x400844, 0); + nv_wr32(dev, 0x4008A0, vramsz); + nv_wr32(dev, 0x4008A4, vramsz); + break; + } + + return 0; +} + +static int +nv40_graph_fini(struct drm_device *dev, int engine, bool suspend) +{ + u32 inst = nv_rd32(dev, 0x40032c); + if (inst & 0x01000000) { + nv_wr32(dev, 0x400720, 0x00000000); + nv_wr32(dev, 0x400784, inst); + nv_mask(dev, 0x400310, 0x00000020, 0x00000020); + nv_mask(dev, 0x400304, 0x00000001, 0x00000001); + if (!nv_wait(dev, 0x400300, 0x00000001, 0x00000000)) { + u32 insn = nv_rd32(dev, 0x400308); + NV_ERROR(dev, "PGRAPH: ctxprog timeout 0x%08x\n", insn); + } + nv_mask(dev, 0x40032c, 0x01000000, 0x00000000); + } + return 0; +} + +static int +nv40_graph_isr_chid(struct drm_device *dev, u32 inst) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *grctx; + unsigned long flags; + int i; + + spin_lock_irqsave(&dev_priv->channels.lock, flags); + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + if (!dev_priv->channels.ptr[i]) + continue; + grctx = dev_priv->channels.ptr[i]->engctx[NVOBJ_ENGINE_GR]; + + if (grctx && grctx->pinst == inst) + break; + } + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + return i; +} + +static void +nv40_graph_isr(struct drm_device *dev) +{ + u32 stat; + + while ((stat = nv_rd32(dev, NV03_PGRAPH_INTR))) { + u32 nsource = nv_rd32(dev, NV03_PGRAPH_NSOURCE); + u32 nstatus = nv_rd32(dev, NV03_PGRAPH_NSTATUS); + u32 inst = (nv_rd32(dev, 0x40032c) & 0x000fffff) << 4; + u32 chid = nv40_graph_isr_chid(dev, inst); + u32 addr = nv_rd32(dev, NV04_PGRAPH_TRAPPED_ADDR); + u32 subc = (addr & 0x00070000) >> 16; + u32 mthd = (addr & 0x00001ffc); + u32 data = nv_rd32(dev, NV04_PGRAPH_TRAPPED_DATA); + u32 class = nv_rd32(dev, 0x400160 + subc * 4) & 0xffff; + u32 show = stat; + + if (stat & NV_PGRAPH_INTR_ERROR) { + if (nsource & NV03_PGRAPH_NSOURCE_ILLEGAL_MTHD) { + if (!nouveau_gpuobj_mthd_call2(dev, chid, class, mthd, data)) + show &= ~NV_PGRAPH_INTR_ERROR; + } else + if (nsource & NV03_PGRAPH_NSOURCE_DMA_VTX_PROTECTION) { + nv_mask(dev, 0x402000, 0, 0); + } + } + + nv_wr32(dev, NV03_PGRAPH_INTR, stat); + nv_wr32(dev, NV04_PGRAPH_FIFO, 0x00000001); + + if (show && nouveau_ratelimit()) { + NV_INFO(dev, "PGRAPH -"); + nouveau_bitfield_print(nv10_graph_intr, show); + printk(" nsource:"); + nouveau_bitfield_print(nv04_graph_nsource, nsource); + printk(" nstatus:"); + nouveau_bitfield_print(nv10_graph_nstatus, nstatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - ch %d (0x%08x) subc %d " + "class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, subc, class, mthd, data); + } + } +} + +static void +nv40_graph_destroy(struct drm_device *dev, int engine) +{ + struct nv40_graph_engine *pgraph = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, 12); + + NVOBJ_ENGINE_DEL(dev, GR); + kfree(pgraph); +} + +int +nv40_graph_create(struct drm_device *dev) +{ + struct nv40_graph_engine *pgraph; + + pgraph = kzalloc(sizeof(*pgraph), GFP_KERNEL); + if (!pgraph) + return -ENOMEM; + + pgraph->base.destroy = nv40_graph_destroy; + pgraph->base.init = nv40_graph_init; + pgraph->base.fini = nv40_graph_fini; + pgraph->base.context_new = nv40_graph_context_new; + pgraph->base.context_del = nv40_graph_context_del; + pgraph->base.object_new = nv40_graph_object_new; + pgraph->base.set_tile_region = nv40_graph_set_tile_region; + + NVOBJ_ENGINE_ADD(dev, GR, &pgraph->base); + nouveau_irq_register(dev, 12, nv40_graph_isr); + + NVOBJ_CLASS(dev, 0x506e, SW); /* nvsw */ + NVOBJ_CLASS(dev, 0x0030, GR); /* null */ + NVOBJ_CLASS(dev, 0x0039, GR); /* m2mf */ + NVOBJ_CLASS(dev, 0x004a, GR); /* gdirect */ + NVOBJ_CLASS(dev, 0x009f, GR); /* imageblit (nv12) */ + NVOBJ_CLASS(dev, 0x008a, GR); /* ifc */ + NVOBJ_CLASS(dev, 0x0089, GR); /* sifm */ + NVOBJ_CLASS(dev, 0x3089, GR); /* sifm (nv40) */ + NVOBJ_CLASS(dev, 0x0062, GR); /* surf2d */ + NVOBJ_CLASS(dev, 0x3062, GR); /* surf2d (nv40) */ + NVOBJ_CLASS(dev, 0x0043, GR); /* rop */ + NVOBJ_CLASS(dev, 0x0012, GR); /* beta1 */ + NVOBJ_CLASS(dev, 0x0072, GR); /* beta4 */ + NVOBJ_CLASS(dev, 0x0019, GR); /* cliprect */ + NVOBJ_CLASS(dev, 0x0044, GR); /* pattern */ + NVOBJ_CLASS(dev, 0x309e, GR); /* swzsurf */ + + /* curie */ + if (nv44_graph_class(dev)) + NVOBJ_CLASS(dev, 0x4497, GR); + else + NVOBJ_CLASS(dev, 0x4097, GR); + + /* nvsw */ + NVOBJ_CLASS(dev, 0x506e, SW); + NVOBJ_MTHD (dev, 0x506e, 0x0500, nv04_graph_mthd_page_flip); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv40_grctx.c b/drivers/gpu/drm/nouveau/nv40_grctx.c new file mode 100644 index 00000000..f70447d1 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv40_grctx.c @@ -0,0 +1,662 @@ +/* + * Copyright 2009 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +/* NVIDIA context programs handle a number of other conditions which are + * not implemented in our versions. It's not clear why NVIDIA context + * programs have this code, nor whether it's strictly necessary for + * correct operation. We'll implement additional handling if/when we + * discover it's necessary. + * + * - On context save, NVIDIA set 0x400314 bit 0 to 1 if the "3D state" + * flag is set, this gets saved into the context. + * - On context save, the context program for all cards load nsource + * into a flag register and check for ILLEGAL_MTHD. If it's set, + * opcode 0x60000d is called before resuming normal operation. + * - Some context programs check more conditions than the above. NV44 + * checks: ((nsource & 0x0857) || (0x400718 & 0x0100) || (intr & 0x0001)) + * and calls 0x60000d before resuming normal operation. + * - At the very beginning of NVIDIA's context programs, flag 9 is checked + * and if true 0x800001 is called with count=0, pos=0, the flag is cleared + * and then the ctxprog is aborted. It looks like a complicated NOP, + * its purpose is unknown. + * - In the section of code that loads the per-vs state, NVIDIA check + * flag 10. If it's set, they only transfer the small 0x300 byte block + * of state + the state for a single vs as opposed to the state for + * all vs units. It doesn't seem likely that it'll occur in normal + * operation, especially seeing as it appears NVIDIA may have screwed + * up the ctxprogs for some cards and have an invalid instruction + * rather than a cp_lsr(ctx, dwords_for_1_vs_unit) instruction. + * - There's a number of places where context offset 0 (where we place + * the PRAMIN offset of the context) is loaded into either 0x408000, + * 0x408004 or 0x408008. Not sure what's up there either. + * - The ctxprogs for some cards save 0x400a00 again during the cleanup + * path for auto-loadctx. + */ + +#define CP_FLAG_CLEAR 0 +#define CP_FLAG_SET 1 +#define CP_FLAG_SWAP_DIRECTION ((0 * 32) + 0) +#define CP_FLAG_SWAP_DIRECTION_LOAD 0 +#define CP_FLAG_SWAP_DIRECTION_SAVE 1 +#define CP_FLAG_USER_SAVE ((0 * 32) + 5) +#define CP_FLAG_USER_SAVE_NOT_PENDING 0 +#define CP_FLAG_USER_SAVE_PENDING 1 +#define CP_FLAG_USER_LOAD ((0 * 32) + 6) +#define CP_FLAG_USER_LOAD_NOT_PENDING 0 +#define CP_FLAG_USER_LOAD_PENDING 1 +#define CP_FLAG_STATUS ((3 * 32) + 0) +#define CP_FLAG_STATUS_IDLE 0 +#define CP_FLAG_STATUS_BUSY 1 +#define CP_FLAG_AUTO_SAVE ((3 * 32) + 4) +#define CP_FLAG_AUTO_SAVE_NOT_PENDING 0 +#define CP_FLAG_AUTO_SAVE_PENDING 1 +#define CP_FLAG_AUTO_LOAD ((3 * 32) + 5) +#define CP_FLAG_AUTO_LOAD_NOT_PENDING 0 +#define CP_FLAG_AUTO_LOAD_PENDING 1 +#define CP_FLAG_UNK54 ((3 * 32) + 6) +#define CP_FLAG_UNK54_CLEAR 0 +#define CP_FLAG_UNK54_SET 1 +#define CP_FLAG_ALWAYS ((3 * 32) + 8) +#define CP_FLAG_ALWAYS_FALSE 0 +#define CP_FLAG_ALWAYS_TRUE 1 +#define CP_FLAG_UNK57 ((3 * 32) + 9) +#define CP_FLAG_UNK57_CLEAR 0 +#define CP_FLAG_UNK57_SET 1 + +#define CP_CTX 0x00100000 +#define CP_CTX_COUNT 0x000fc000 +#define CP_CTX_COUNT_SHIFT 14 +#define CP_CTX_REG 0x00003fff +#define CP_LOAD_SR 0x00200000 +#define CP_LOAD_SR_VALUE 0x000fffff +#define CP_BRA 0x00400000 +#define CP_BRA_IP 0x0000ff00 +#define CP_BRA_IP_SHIFT 8 +#define CP_BRA_IF_CLEAR 0x00000080 +#define CP_BRA_FLAG 0x0000007f +#define CP_WAIT 0x00500000 +#define CP_WAIT_SET 0x00000080 +#define CP_WAIT_FLAG 0x0000007f +#define CP_SET 0x00700000 +#define CP_SET_1 0x00000080 +#define CP_SET_FLAG 0x0000007f +#define CP_NEXT_TO_SWAP 0x00600007 +#define CP_NEXT_TO_CURRENT 0x00600009 +#define CP_SET_CONTEXT_POINTER 0x0060000a +#define CP_END 0x0060000e +#define CP_LOAD_MAGIC_UNK01 0x00800001 /* unknown */ +#define CP_LOAD_MAGIC_NV44TCL 0x00800029 /* per-vs state (0x4497) */ +#define CP_LOAD_MAGIC_NV40TCL 0x00800041 /* per-vs state (0x4097) */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_grctx.h" + +/* TODO: + * - get vs count from 0x1540 + */ + +static int +nv40_graph_vs_count(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + switch (dev_priv->chipset) { + case 0x47: + case 0x49: + case 0x4b: + return 8; + case 0x40: + return 6; + case 0x41: + case 0x42: + return 5; + case 0x43: + case 0x44: + case 0x46: + case 0x4a: + return 3; + case 0x4c: + case 0x4e: + case 0x67: + default: + return 1; + } +} + + +enum cp_label { + cp_check_load = 1, + cp_setup_auto_load, + cp_setup_load, + cp_setup_save, + cp_swap_state, + cp_swap_state3d_3_is_save, + cp_prepare_exit, + cp_exit, +}; + +static void +nv40_graph_construct_general(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i; + + cp_ctx(ctx, 0x4000a4, 1); + gr_def(ctx, 0x4000a4, 0x00000008); + cp_ctx(ctx, 0x400144, 58); + gr_def(ctx, 0x400144, 0x00000001); + cp_ctx(ctx, 0x400314, 1); + gr_def(ctx, 0x400314, 0x00000000); + cp_ctx(ctx, 0x400400, 10); + cp_ctx(ctx, 0x400480, 10); + cp_ctx(ctx, 0x400500, 19); + gr_def(ctx, 0x400514, 0x00040000); + gr_def(ctx, 0x400524, 0x55555555); + gr_def(ctx, 0x400528, 0x55555555); + gr_def(ctx, 0x40052c, 0x55555555); + gr_def(ctx, 0x400530, 0x55555555); + cp_ctx(ctx, 0x400560, 6); + gr_def(ctx, 0x400568, 0x0000ffff); + gr_def(ctx, 0x40056c, 0x0000ffff); + cp_ctx(ctx, 0x40057c, 5); + cp_ctx(ctx, 0x400710, 3); + gr_def(ctx, 0x400710, 0x20010001); + gr_def(ctx, 0x400714, 0x0f73ef00); + cp_ctx(ctx, 0x400724, 1); + gr_def(ctx, 0x400724, 0x02008821); + cp_ctx(ctx, 0x400770, 3); + if (dev_priv->chipset == 0x40) { + cp_ctx(ctx, 0x400814, 4); + cp_ctx(ctx, 0x400828, 5); + cp_ctx(ctx, 0x400840, 5); + gr_def(ctx, 0x400850, 0x00000040); + cp_ctx(ctx, 0x400858, 4); + gr_def(ctx, 0x400858, 0x00000040); + gr_def(ctx, 0x40085c, 0x00000040); + gr_def(ctx, 0x400864, 0x80000000); + cp_ctx(ctx, 0x40086c, 9); + gr_def(ctx, 0x40086c, 0x80000000); + gr_def(ctx, 0x400870, 0x80000000); + gr_def(ctx, 0x400874, 0x80000000); + gr_def(ctx, 0x400878, 0x80000000); + gr_def(ctx, 0x400888, 0x00000040); + gr_def(ctx, 0x40088c, 0x80000000); + cp_ctx(ctx, 0x4009c0, 8); + gr_def(ctx, 0x4009cc, 0x80000000); + gr_def(ctx, 0x4009dc, 0x80000000); + } else { + cp_ctx(ctx, 0x400840, 20); + if (nv44_graph_class(ctx->dev)) { + for (i = 0; i < 8; i++) + gr_def(ctx, 0x400860 + (i * 4), 0x00000001); + } + gr_def(ctx, 0x400880, 0x00000040); + gr_def(ctx, 0x400884, 0x00000040); + gr_def(ctx, 0x400888, 0x00000040); + cp_ctx(ctx, 0x400894, 11); + gr_def(ctx, 0x400894, 0x00000040); + if (!nv44_graph_class(ctx->dev)) { + for (i = 0; i < 8; i++) + gr_def(ctx, 0x4008a0 + (i * 4), 0x80000000); + } + cp_ctx(ctx, 0x4008e0, 2); + cp_ctx(ctx, 0x4008f8, 2); + if (dev_priv->chipset == 0x4c || + (dev_priv->chipset & 0xf0) == 0x60) + cp_ctx(ctx, 0x4009f8, 1); + } + cp_ctx(ctx, 0x400a00, 73); + gr_def(ctx, 0x400b0c, 0x0b0b0b0c); + cp_ctx(ctx, 0x401000, 4); + cp_ctx(ctx, 0x405004, 1); + switch (dev_priv->chipset) { + case 0x47: + case 0x49: + case 0x4b: + cp_ctx(ctx, 0x403448, 1); + gr_def(ctx, 0x403448, 0x00001010); + break; + default: + cp_ctx(ctx, 0x403440, 1); + switch (dev_priv->chipset) { + case 0x40: + gr_def(ctx, 0x403440, 0x00000010); + break; + case 0x44: + case 0x46: + case 0x4a: + gr_def(ctx, 0x403440, 0x00003010); + break; + case 0x41: + case 0x42: + case 0x43: + case 0x4c: + case 0x4e: + case 0x67: + default: + gr_def(ctx, 0x403440, 0x00001010); + break; + } + break; + } +} + +static void +nv40_graph_construct_state3d(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i; + + if (dev_priv->chipset == 0x40) { + cp_ctx(ctx, 0x401880, 51); + gr_def(ctx, 0x401940, 0x00000100); + } else + if (dev_priv->chipset == 0x46 || dev_priv->chipset == 0x47 || + dev_priv->chipset == 0x49 || dev_priv->chipset == 0x4b) { + cp_ctx(ctx, 0x401880, 32); + for (i = 0; i < 16; i++) + gr_def(ctx, 0x401880 + (i * 4), 0x00000111); + if (dev_priv->chipset == 0x46) + cp_ctx(ctx, 0x401900, 16); + cp_ctx(ctx, 0x401940, 3); + } + cp_ctx(ctx, 0x40194c, 18); + gr_def(ctx, 0x401954, 0x00000111); + gr_def(ctx, 0x401958, 0x00080060); + gr_def(ctx, 0x401974, 0x00000080); + gr_def(ctx, 0x401978, 0xffff0000); + gr_def(ctx, 0x40197c, 0x00000001); + gr_def(ctx, 0x401990, 0x46400000); + if (dev_priv->chipset == 0x40) { + cp_ctx(ctx, 0x4019a0, 2); + cp_ctx(ctx, 0x4019ac, 5); + } else { + cp_ctx(ctx, 0x4019a0, 1); + cp_ctx(ctx, 0x4019b4, 3); + } + gr_def(ctx, 0x4019bc, 0xffff0000); + switch (dev_priv->chipset) { + case 0x46: + case 0x47: + case 0x49: + case 0x4b: + cp_ctx(ctx, 0x4019c0, 18); + for (i = 0; i < 16; i++) + gr_def(ctx, 0x4019c0 + (i * 4), 0x88888888); + break; + } + cp_ctx(ctx, 0x401a08, 8); + gr_def(ctx, 0x401a10, 0x0fff0000); + gr_def(ctx, 0x401a14, 0x0fff0000); + gr_def(ctx, 0x401a1c, 0x00011100); + cp_ctx(ctx, 0x401a2c, 4); + cp_ctx(ctx, 0x401a44, 26); + for (i = 0; i < 16; i++) + gr_def(ctx, 0x401a44 + (i * 4), 0x07ff0000); + gr_def(ctx, 0x401a8c, 0x4b7fffff); + if (dev_priv->chipset == 0x40) { + cp_ctx(ctx, 0x401ab8, 3); + } else { + cp_ctx(ctx, 0x401ab8, 1); + cp_ctx(ctx, 0x401ac0, 1); + } + cp_ctx(ctx, 0x401ad0, 8); + gr_def(ctx, 0x401ad0, 0x30201000); + gr_def(ctx, 0x401ad4, 0x70605040); + gr_def(ctx, 0x401ad8, 0xb8a89888); + gr_def(ctx, 0x401adc, 0xf8e8d8c8); + cp_ctx(ctx, 0x401b10, dev_priv->chipset == 0x40 ? 2 : 1); + gr_def(ctx, 0x401b10, 0x40100000); + cp_ctx(ctx, 0x401b18, dev_priv->chipset == 0x40 ? 6 : 5); + gr_def(ctx, 0x401b28, dev_priv->chipset == 0x40 ? + 0x00000004 : 0x00000000); + cp_ctx(ctx, 0x401b30, 25); + gr_def(ctx, 0x401b34, 0x0000ffff); + gr_def(ctx, 0x401b68, 0x435185d6); + gr_def(ctx, 0x401b6c, 0x2155b699); + gr_def(ctx, 0x401b70, 0xfedcba98); + gr_def(ctx, 0x401b74, 0x00000098); + gr_def(ctx, 0x401b84, 0xffffffff); + gr_def(ctx, 0x401b88, 0x00ff7000); + gr_def(ctx, 0x401b8c, 0x0000ffff); + if (dev_priv->chipset != 0x44 && dev_priv->chipset != 0x4a && + dev_priv->chipset != 0x4e) + cp_ctx(ctx, 0x401b94, 1); + cp_ctx(ctx, 0x401b98, 8); + gr_def(ctx, 0x401b9c, 0x00ff0000); + cp_ctx(ctx, 0x401bc0, 9); + gr_def(ctx, 0x401be0, 0x00ffff00); + cp_ctx(ctx, 0x401c00, 192); + for (i = 0; i < 16; i++) { /* fragment texture units */ + gr_def(ctx, 0x401c40 + (i * 4), 0x00018488); + gr_def(ctx, 0x401c80 + (i * 4), 0x00028202); + gr_def(ctx, 0x401d00 + (i * 4), 0x0000aae4); + gr_def(ctx, 0x401d40 + (i * 4), 0x01012000); + gr_def(ctx, 0x401d80 + (i * 4), 0x00080008); + gr_def(ctx, 0x401e00 + (i * 4), 0x00100008); + } + for (i = 0; i < 4; i++) { /* vertex texture units */ + gr_def(ctx, 0x401e90 + (i * 4), 0x0001bc80); + gr_def(ctx, 0x401ea0 + (i * 4), 0x00000202); + gr_def(ctx, 0x401ec0 + (i * 4), 0x00000008); + gr_def(ctx, 0x401ee0 + (i * 4), 0x00080008); + } + cp_ctx(ctx, 0x400f5c, 3); + gr_def(ctx, 0x400f5c, 0x00000002); + cp_ctx(ctx, 0x400f84, 1); +} + +static void +nv40_graph_construct_state3d_2(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i; + + cp_ctx(ctx, 0x402000, 1); + cp_ctx(ctx, 0x402404, dev_priv->chipset == 0x40 ? 1 : 2); + switch (dev_priv->chipset) { + case 0x40: + gr_def(ctx, 0x402404, 0x00000001); + break; + case 0x4c: + case 0x4e: + case 0x67: + gr_def(ctx, 0x402404, 0x00000020); + break; + case 0x46: + case 0x49: + case 0x4b: + gr_def(ctx, 0x402404, 0x00000421); + break; + default: + gr_def(ctx, 0x402404, 0x00000021); + } + if (dev_priv->chipset != 0x40) + gr_def(ctx, 0x402408, 0x030c30c3); + switch (dev_priv->chipset) { + case 0x44: + case 0x46: + case 0x4a: + case 0x4c: + case 0x4e: + case 0x67: + cp_ctx(ctx, 0x402440, 1); + gr_def(ctx, 0x402440, 0x00011001); + break; + default: + break; + } + cp_ctx(ctx, 0x402480, dev_priv->chipset == 0x40 ? 8 : 9); + gr_def(ctx, 0x402488, 0x3e020200); + gr_def(ctx, 0x40248c, 0x00ffffff); + switch (dev_priv->chipset) { + case 0x40: + gr_def(ctx, 0x402490, 0x60103f00); + break; + case 0x47: + gr_def(ctx, 0x402490, 0x40103f00); + break; + case 0x41: + case 0x42: + case 0x49: + case 0x4b: + gr_def(ctx, 0x402490, 0x20103f00); + break; + default: + gr_def(ctx, 0x402490, 0x0c103f00); + break; + } + gr_def(ctx, 0x40249c, dev_priv->chipset <= 0x43 ? + 0x00020000 : 0x00040000); + cp_ctx(ctx, 0x402500, 31); + gr_def(ctx, 0x402530, 0x00008100); + if (dev_priv->chipset == 0x40) + cp_ctx(ctx, 0x40257c, 6); + cp_ctx(ctx, 0x402594, 16); + cp_ctx(ctx, 0x402800, 17); + gr_def(ctx, 0x402800, 0x00000001); + switch (dev_priv->chipset) { + case 0x47: + case 0x49: + case 0x4b: + cp_ctx(ctx, 0x402864, 1); + gr_def(ctx, 0x402864, 0x00001001); + cp_ctx(ctx, 0x402870, 3); + gr_def(ctx, 0x402878, 0x00000003); + if (dev_priv->chipset != 0x47) { /* belong at end!! */ + cp_ctx(ctx, 0x402900, 1); + cp_ctx(ctx, 0x402940, 1); + cp_ctx(ctx, 0x402980, 1); + cp_ctx(ctx, 0x4029c0, 1); + cp_ctx(ctx, 0x402a00, 1); + cp_ctx(ctx, 0x402a40, 1); + cp_ctx(ctx, 0x402a80, 1); + cp_ctx(ctx, 0x402ac0, 1); + } + break; + case 0x40: + cp_ctx(ctx, 0x402844, 1); + gr_def(ctx, 0x402844, 0x00000001); + cp_ctx(ctx, 0x402850, 1); + break; + default: + cp_ctx(ctx, 0x402844, 1); + gr_def(ctx, 0x402844, 0x00001001); + cp_ctx(ctx, 0x402850, 2); + gr_def(ctx, 0x402854, 0x00000003); + break; + } + + cp_ctx(ctx, 0x402c00, 4); + gr_def(ctx, 0x402c00, dev_priv->chipset == 0x40 ? + 0x80800001 : 0x00888001); + switch (dev_priv->chipset) { + case 0x47: + case 0x49: + case 0x4b: + cp_ctx(ctx, 0x402c20, 40); + for (i = 0; i < 32; i++) + gr_def(ctx, 0x402c40 + (i * 4), 0xffffffff); + cp_ctx(ctx, 0x4030b8, 13); + gr_def(ctx, 0x4030dc, 0x00000005); + gr_def(ctx, 0x4030e8, 0x0000ffff); + break; + default: + cp_ctx(ctx, 0x402c10, 4); + if (dev_priv->chipset == 0x40) + cp_ctx(ctx, 0x402c20, 36); + else + if (dev_priv->chipset <= 0x42) + cp_ctx(ctx, 0x402c20, 24); + else + if (dev_priv->chipset <= 0x4a) + cp_ctx(ctx, 0x402c20, 16); + else + cp_ctx(ctx, 0x402c20, 8); + cp_ctx(ctx, 0x402cb0, dev_priv->chipset == 0x40 ? 12 : 13); + gr_def(ctx, 0x402cd4, 0x00000005); + if (dev_priv->chipset != 0x40) + gr_def(ctx, 0x402ce0, 0x0000ffff); + break; + } + + cp_ctx(ctx, 0x403400, dev_priv->chipset == 0x40 ? 4 : 3); + cp_ctx(ctx, 0x403410, dev_priv->chipset == 0x40 ? 4 : 3); + cp_ctx(ctx, 0x403420, nv40_graph_vs_count(ctx->dev)); + for (i = 0; i < nv40_graph_vs_count(ctx->dev); i++) + gr_def(ctx, 0x403420 + (i * 4), 0x00005555); + + if (dev_priv->chipset != 0x40) { + cp_ctx(ctx, 0x403600, 1); + gr_def(ctx, 0x403600, 0x00000001); + } + cp_ctx(ctx, 0x403800, 1); + + cp_ctx(ctx, 0x403c18, 1); + gr_def(ctx, 0x403c18, 0x00000001); + switch (dev_priv->chipset) { + case 0x46: + case 0x47: + case 0x49: + case 0x4b: + cp_ctx(ctx, 0x405018, 1); + gr_def(ctx, 0x405018, 0x08e00001); + cp_ctx(ctx, 0x405c24, 1); + gr_def(ctx, 0x405c24, 0x000e3000); + break; + } + if (dev_priv->chipset != 0x4e) + cp_ctx(ctx, 0x405800, 11); + cp_ctx(ctx, 0x407000, 1); +} + +static void +nv40_graph_construct_state3d_3(struct nouveau_grctx *ctx) +{ + int len = nv44_graph_class(ctx->dev) ? 0x0084 : 0x0684; + + cp_out (ctx, 0x300000); + cp_lsr (ctx, len - 4); + cp_bra (ctx, SWAP_DIRECTION, SAVE, cp_swap_state3d_3_is_save); + cp_lsr (ctx, len); + cp_name(ctx, cp_swap_state3d_3_is_save); + cp_out (ctx, 0x800001); + + ctx->ctxvals_pos += len; +} + +static void +nv40_graph_construct_shader(struct nouveau_grctx *ctx) +{ + struct drm_device *dev = ctx->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *obj = ctx->data; + int vs, vs_nr, vs_len, vs_nr_b0, vs_nr_b1, b0_offset, b1_offset; + int offset, i; + + vs_nr = nv40_graph_vs_count(ctx->dev); + vs_nr_b0 = 363; + vs_nr_b1 = dev_priv->chipset == 0x40 ? 128 : 64; + if (dev_priv->chipset == 0x40) { + b0_offset = 0x2200/4; /* 33a0 */ + b1_offset = 0x55a0/4; /* 1500 */ + vs_len = 0x6aa0/4; + } else + if (dev_priv->chipset == 0x41 || dev_priv->chipset == 0x42) { + b0_offset = 0x2200/4; /* 2200 */ + b1_offset = 0x4400/4; /* 0b00 */ + vs_len = 0x4f00/4; + } else { + b0_offset = 0x1d40/4; /* 2200 */ + b1_offset = 0x3f40/4; /* 0b00 : 0a40 */ + vs_len = nv44_graph_class(dev) ? 0x4980/4 : 0x4a40/4; + } + + cp_lsr(ctx, vs_len * vs_nr + 0x300/4); + cp_out(ctx, nv44_graph_class(dev) ? 0x800029 : 0x800041); + + offset = ctx->ctxvals_pos; + ctx->ctxvals_pos += (0x0300/4 + (vs_nr * vs_len)); + + if (ctx->mode != NOUVEAU_GRCTX_VALS) + return; + + offset += 0x0280/4; + for (i = 0; i < 16; i++, offset += 2) + nv_wo32(obj, offset * 4, 0x3f800000); + + for (vs = 0; vs < vs_nr; vs++, offset += vs_len) { + for (i = 0; i < vs_nr_b0 * 6; i += 6) + nv_wo32(obj, (offset + b0_offset + i) * 4, 0x00000001); + for (i = 0; i < vs_nr_b1 * 4; i += 4) + nv_wo32(obj, (offset + b1_offset + i) * 4, 0x3f800000); + } +} + +void +nv40_grctx_init(struct nouveau_grctx *ctx) +{ + /* decide whether we're loading/unloading the context */ + cp_bra (ctx, AUTO_SAVE, PENDING, cp_setup_save); + cp_bra (ctx, USER_SAVE, PENDING, cp_setup_save); + + cp_name(ctx, cp_check_load); + cp_bra (ctx, AUTO_LOAD, PENDING, cp_setup_auto_load); + cp_bra (ctx, USER_LOAD, PENDING, cp_setup_load); + cp_bra (ctx, ALWAYS, TRUE, cp_exit); + + /* setup for context load */ + cp_name(ctx, cp_setup_auto_load); + cp_wait(ctx, STATUS, IDLE); + cp_out (ctx, CP_NEXT_TO_SWAP); + cp_name(ctx, cp_setup_load); + cp_wait(ctx, STATUS, IDLE); + cp_set (ctx, SWAP_DIRECTION, LOAD); + cp_out (ctx, 0x00910880); /* ?? */ + cp_out (ctx, 0x00901ffe); /* ?? */ + cp_out (ctx, 0x01940000); /* ?? */ + cp_lsr (ctx, 0x20); + cp_out (ctx, 0x0060000b); /* ?? */ + cp_wait(ctx, UNK57, CLEAR); + cp_out (ctx, 0x0060000c); /* ?? */ + cp_bra (ctx, ALWAYS, TRUE, cp_swap_state); + + /* setup for context save */ + cp_name(ctx, cp_setup_save); + cp_set (ctx, SWAP_DIRECTION, SAVE); + + /* general PGRAPH state */ + cp_name(ctx, cp_swap_state); + cp_pos (ctx, 0x00020/4); + nv40_graph_construct_general(ctx); + cp_wait(ctx, STATUS, IDLE); + + /* 3D state, block 1 */ + cp_bra (ctx, UNK54, CLEAR, cp_prepare_exit); + nv40_graph_construct_state3d(ctx); + cp_wait(ctx, STATUS, IDLE); + + /* 3D state, block 2 */ + nv40_graph_construct_state3d_2(ctx); + + /* Some other block of "random" state */ + nv40_graph_construct_state3d_3(ctx); + + /* Per-vertex shader state */ + cp_pos (ctx, ctx->ctxvals_pos); + nv40_graph_construct_shader(ctx); + + /* pre-exit state updates */ + cp_name(ctx, cp_prepare_exit); + cp_bra (ctx, SWAP_DIRECTION, SAVE, cp_check_load); + cp_bra (ctx, USER_SAVE, PENDING, cp_exit); + cp_out (ctx, CP_NEXT_TO_CURRENT); + + cp_name(ctx, cp_exit); + cp_set (ctx, USER_SAVE, NOT_PENDING); + cp_set (ctx, USER_LOAD, NOT_PENDING); + cp_out (ctx, CP_END); +} + diff --git a/drivers/gpu/drm/nouveau/nv40_mc.c b/drivers/gpu/drm/nouveau/nv40_mc.c new file mode 100644 index 00000000..03c0d4c3 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv40_mc.c @@ -0,0 +1,28 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +int +nv40_mc_init(struct drm_device *dev) +{ + /* Power up everything, resetting each individual unit will + * be done later if needed. + */ + nv_wr32(dev, NV03_PMC_ENABLE, 0xFFFFFFFF); + + if (nv44_graph_class(dev)) { + u32 tmp = nv_rd32(dev, NV04_PFB_FIFO_DATA); + nv_wr32(dev, NV40_PMC_1700, tmp); + nv_wr32(dev, NV40_PMC_1704, 0); + nv_wr32(dev, NV40_PMC_1708, 0); + nv_wr32(dev, NV40_PMC_170C, tmp); + } + + return 0; +} + +void +nv40_mc_takedown(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv40_pm.c b/drivers/gpu/drm/nouveau/nv40_pm.c new file mode 100644 index 00000000..c7615381 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv40_pm.c @@ -0,0 +1,394 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_bios.h" +#include "nouveau_pm.h" +#include "nouveau_hw.h" + +#define min2(a,b) ((a) < (b) ? (a) : (b)) + +static u32 +read_pll_1(struct drm_device *dev, u32 reg) +{ + u32 ctrl = nv_rd32(dev, reg + 0x00); + int P = (ctrl & 0x00070000) >> 16; + int N = (ctrl & 0x0000ff00) >> 8; + int M = (ctrl & 0x000000ff) >> 0; + u32 ref = 27000, clk = 0; + + if (ctrl & 0x80000000) + clk = ref * N / M; + + return clk >> P; +} + +static u32 +read_pll_2(struct drm_device *dev, u32 reg) +{ + u32 ctrl = nv_rd32(dev, reg + 0x00); + u32 coef = nv_rd32(dev, reg + 0x04); + int N2 = (coef & 0xff000000) >> 24; + int M2 = (coef & 0x00ff0000) >> 16; + int N1 = (coef & 0x0000ff00) >> 8; + int M1 = (coef & 0x000000ff) >> 0; + int P = (ctrl & 0x00070000) >> 16; + u32 ref = 27000, clk = 0; + + if ((ctrl & 0x80000000) && M1) { + clk = ref * N1 / M1; + if ((ctrl & 0x40000100) == 0x40000000) { + if (M2) + clk = clk * N2 / M2; + else + clk = 0; + } + } + + return clk >> P; +} + +static u32 +read_clk(struct drm_device *dev, u32 src) +{ + switch (src) { + case 3: + return read_pll_2(dev, 0x004000); + case 2: + return read_pll_1(dev, 0x004008); + default: + break; + } + + return 0; +} + +int +nv40_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + u32 ctrl = nv_rd32(dev, 0x00c040); + + perflvl->core = read_clk(dev, (ctrl & 0x00000003) >> 0); + perflvl->shader = read_clk(dev, (ctrl & 0x00000030) >> 4); + perflvl->memory = read_pll_2(dev, 0x4020); + return 0; +} + +struct nv40_pm_state { + u32 ctrl; + u32 npll_ctrl; + u32 npll_coef; + u32 spll; + u32 mpll_ctrl; + u32 mpll_coef; +}; + +static int +nv40_calc_pll(struct drm_device *dev, u32 reg, struct pll_lims *pll, + u32 clk, int *N1, int *M1, int *N2, int *M2, int *log2P) +{ + struct nouveau_pll_vals coef; + int ret; + + ret = get_pll_limits(dev, reg, pll); + if (ret) + return ret; + + if (clk < pll->vco1.maxfreq) + pll->vco2.maxfreq = 0; + + ret = nouveau_calc_pll_mnp(dev, pll, clk, &coef); + if (ret == 0) + return -ERANGE; + + *N1 = coef.N1; + *M1 = coef.M1; + if (N2 && M2) { + if (pll->vco2.maxfreq) { + *N2 = coef.N2; + *M2 = coef.M2; + } else { + *N2 = 1; + *M2 = 1; + } + } + *log2P = coef.log2P; + return 0; +} + +void * +nv40_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct nv40_pm_state *info; + struct pll_lims pll; + int N1, N2, M1, M2, log2P; + int ret; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + /* core/geometric clock */ + ret = nv40_calc_pll(dev, 0x004000, &pll, perflvl->core, + &N1, &M1, &N2, &M2, &log2P); + if (ret < 0) + goto out; + + if (N2 == M2) { + info->npll_ctrl = 0x80000100 | (log2P << 16); + info->npll_coef = (N1 << 8) | M1; + } else { + info->npll_ctrl = 0xc0000000 | (log2P << 16); + info->npll_coef = (N2 << 24) | (M2 << 16) | (N1 << 8) | M1; + } + + /* use the second PLL for shader/rop clock, if it differs from core */ + if (perflvl->shader && perflvl->shader != perflvl->core) { + ret = nv40_calc_pll(dev, 0x004008, &pll, perflvl->shader, + &N1, &M1, NULL, NULL, &log2P); + if (ret < 0) + goto out; + + info->spll = 0xc0000000 | (log2P << 16) | (N1 << 8) | M1; + info->ctrl = 0x00000223; + } else { + info->spll = 0x00000000; + info->ctrl = 0x00000333; + } + + /* memory clock */ + if (!perflvl->memory) { + info->mpll_ctrl = 0x00000000; + goto out; + } + + ret = nv40_calc_pll(dev, 0x004020, &pll, perflvl->memory, + &N1, &M1, &N2, &M2, &log2P); + if (ret < 0) + goto out; + + info->mpll_ctrl = 0x80000000 | (log2P << 16); + info->mpll_ctrl |= min2(pll.log2p_bias + log2P, pll.max_log2p) << 20; + if (N2 == M2) { + info->mpll_ctrl |= 0x00000100; + info->mpll_coef = (N1 << 8) | M1; + } else { + info->mpll_ctrl |= 0x40000000; + info->mpll_coef = (N2 << 24) | (M2 << 16) | (N1 << 8) | M1; + } + +out: + if (ret < 0) { + kfree(info); + info = ERR_PTR(ret); + } + return info; +} + +static bool +nv40_pm_gr_idle(void *data) +{ + struct drm_device *dev = data; + + if ((nv_rd32(dev, 0x400760) & 0x000000f0) >> 4 != + (nv_rd32(dev, 0x400760) & 0x0000000f)) + return false; + + if (nv_rd32(dev, 0x400700)) + return false; + + return true; +} + +int +nv40_pm_clocks_set(struct drm_device *dev, void *pre_state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv40_pm_state *info = pre_state; + unsigned long flags; + struct bit_entry M; + u32 crtc_mask = 0; + u8 sr1[2]; + int i, ret = -EAGAIN; + + /* determine which CRTCs are active, fetch VGA_SR1 for each */ + for (i = 0; i < 2; i++) { + u32 vbl = nv_rd32(dev, 0x600808 + (i * 0x2000)); + u32 cnt = 0; + do { + if (vbl != nv_rd32(dev, 0x600808 + (i * 0x2000))) { + nv_wr08(dev, 0x0c03c4 + (i * 0x2000), 0x01); + sr1[i] = nv_rd08(dev, 0x0c03c5 + (i * 0x2000)); + if (!(sr1[i] & 0x20)) + crtc_mask |= (1 << i); + break; + } + udelay(1); + } while (cnt++ < 32); + } + + /* halt and idle engines */ + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, 0x002500, 0x00000001, 0x00000000); + if (!nv_wait(dev, 0x002500, 0x00000010, 0x00000000)) + goto resume; + nv_mask(dev, 0x003220, 0x00000001, 0x00000000); + if (!nv_wait(dev, 0x003220, 0x00000010, 0x00000000)) + goto resume; + nv_mask(dev, 0x003200, 0x00000001, 0x00000000); + nv04_fifo_cache_pull(dev, false); + + if (!nv_wait_cb(dev, nv40_pm_gr_idle, dev)) + goto resume; + + ret = 0; + + /* set engine clocks */ + nv_mask(dev, 0x00c040, 0x00000333, 0x00000000); + nv_wr32(dev, 0x004004, info->npll_coef); + nv_mask(dev, 0x004000, 0xc0070100, info->npll_ctrl); + nv_mask(dev, 0x004008, 0xc007ffff, info->spll); + mdelay(5); + nv_mask(dev, 0x00c040, 0x00000333, info->ctrl); + + if (!info->mpll_ctrl) + goto resume; + + /* wait for vblank start on active crtcs, disable memory access */ + for (i = 0; i < 2; i++) { + if (!(crtc_mask & (1 << i))) + continue; + nv_wait(dev, 0x600808 + (i * 0x2000), 0x00010000, 0x00000000); + nv_wait(dev, 0x600808 + (i * 0x2000), 0x00010000, 0x00010000); + nv_wr08(dev, 0x0c03c4 + (i * 0x2000), 0x01); + nv_wr08(dev, 0x0c03c5 + (i * 0x2000), sr1[i] | 0x20); + } + + /* prepare ram for reclocking */ + nv_wr32(dev, 0x1002d4, 0x00000001); /* precharge */ + nv_wr32(dev, 0x1002d0, 0x00000001); /* refresh */ + nv_wr32(dev, 0x1002d0, 0x00000001); /* refresh */ + nv_mask(dev, 0x100210, 0x80000000, 0x00000000); /* no auto refresh */ + nv_wr32(dev, 0x1002dc, 0x00000001); /* enable self-refresh */ + + /* change the PLL of each memory partition */ + nv_mask(dev, 0x00c040, 0x0000c000, 0x00000000); + switch (dev_priv->chipset) { + case 0x40: + case 0x45: + case 0x41: + case 0x42: + case 0x47: + nv_mask(dev, 0x004044, 0xc0771100, info->mpll_ctrl); + nv_mask(dev, 0x00402c, 0xc0771100, info->mpll_ctrl); + nv_wr32(dev, 0x004048, info->mpll_coef); + nv_wr32(dev, 0x004030, info->mpll_coef); + case 0x43: + case 0x49: + case 0x4b: + nv_mask(dev, 0x004038, 0xc0771100, info->mpll_ctrl); + nv_wr32(dev, 0x00403c, info->mpll_coef); + default: + nv_mask(dev, 0x004020, 0xc0771100, info->mpll_ctrl); + nv_wr32(dev, 0x004024, info->mpll_coef); + break; + } + udelay(100); + nv_mask(dev, 0x00c040, 0x0000c000, 0x0000c000); + + /* re-enable normal operation of memory controller */ + nv_wr32(dev, 0x1002dc, 0x00000000); + nv_mask(dev, 0x100210, 0x80000000, 0x80000000); + udelay(100); + + /* execute memory reset script from vbios */ + if (!bit_table(dev, 'M', &M)) + nouveau_bios_init_exec(dev, ROM16(M.data[0])); + + /* make sure we're in vblank (hopefully the same one as before), and + * then re-enable crtc memory access + */ + for (i = 0; i < 2; i++) { + if (!(crtc_mask & (1 << i))) + continue; + nv_wait(dev, 0x600808 + (i * 0x2000), 0x00010000, 0x00010000); + nv_wr08(dev, 0x0c03c4 + (i * 0x2000), 0x01); + nv_wr08(dev, 0x0c03c5 + (i * 0x2000), sr1[i]); + } + + /* resume engines */ +resume: + nv_wr32(dev, 0x003250, 0x00000001); + nv_mask(dev, 0x003220, 0x00000001, 0x00000001); + nv_wr32(dev, 0x003200, 0x00000001); + nv_wr32(dev, 0x002500, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + kfree(info); + return ret; +} + +int +nv40_pm_pwm_get(struct drm_device *dev, int line, u32 *divs, u32 *duty) +{ + if (line == 2) { + u32 reg = nv_rd32(dev, 0x0010f0); + if (reg & 0x80000000) { + *duty = (reg & 0x7fff0000) >> 16; + *divs = (reg & 0x00007fff); + return 0; + } + } else + if (line == 9) { + u32 reg = nv_rd32(dev, 0x0015f4); + if (reg & 0x80000000) { + *divs = nv_rd32(dev, 0x0015f8); + *duty = (reg & 0x7fffffff); + return 0; + } + } else { + NV_ERROR(dev, "unknown pwm ctrl for gpio %d\n", line); + return -ENODEV; + } + + return -EINVAL; +} + +int +nv40_pm_pwm_set(struct drm_device *dev, int line, u32 divs, u32 duty) +{ + if (line == 2) { + nv_wr32(dev, 0x0010f0, 0x80000000 | (duty << 16) | divs); + } else + if (line == 9) { + nv_wr32(dev, 0x0015f8, divs); + nv_wr32(dev, 0x0015f4, duty | 0x80000000); + } else { + NV_ERROR(dev, "unknown pwm ctrl for gpio %d\n", line); + return -ENODEV; + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv50_calc.c b/drivers/gpu/drm/nouveau/nv50_calc.c new file mode 100644 index 00000000..8cf63a8b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_calc.c @@ -0,0 +1,97 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" + +int +nv50_calc_pll(struct drm_device *dev, struct pll_lims *pll, int clk, + int *N1, int *M1, int *N2, int *M2, int *P) +{ + struct nouveau_pll_vals pll_vals; + int ret; + + ret = nouveau_calc_pll_mnp(dev, pll, clk, &pll_vals); + if (ret <= 0) + return ret; + + *N1 = pll_vals.N1; + *M1 = pll_vals.M1; + *N2 = pll_vals.N2; + *M2 = pll_vals.M2; + *P = pll_vals.log2P; + return ret; +} + +int +nva3_calc_pll(struct drm_device *dev, struct pll_lims *pll, int clk, + int *pN, int *pfN, int *pM, int *P) +{ + u32 best_err = ~0, err; + int M, lM, hM, N, fN; + + *P = pll->vco1.maxfreq / clk; + if (*P > pll->max_p) + *P = pll->max_p; + if (*P < pll->min_p) + *P = pll->min_p; + + lM = (pll->refclk + pll->vco1.max_inputfreq) / pll->vco1.max_inputfreq; + lM = max(lM, (int)pll->vco1.min_m); + hM = (pll->refclk + pll->vco1.min_inputfreq) / pll->vco1.min_inputfreq; + hM = min(hM, (int)pll->vco1.max_m); + + for (M = lM; M <= hM; M++) { + u32 tmp = clk * *P * M; + N = tmp / pll->refclk; + fN = tmp % pll->refclk; + if (!pfN && fN >= pll->refclk / 2) + N++; + + if (N < pll->vco1.min_n) + continue; + if (N > pll->vco1.max_n) + break; + + err = abs(clk - (pll->refclk * N / M / *P)); + if (err < best_err) { + best_err = err; + *pN = N; + *pM = M; + } + + if (pfN) { + *pfN = (((fN << 13) / pll->refclk) - 4096) & 0xffff; + return clk; + } + } + + if (unlikely(best_err == ~0)) { + NV_ERROR(dev, "unable to find matching pll values\n"); + return -EINVAL; + } + + return pll->refclk * *pN / *pM / *P; +} diff --git a/drivers/gpu/drm/nouveau/nv50_crtc.c b/drivers/gpu/drm/nouveau/nv50_crtc.c new file mode 100644 index 00000000..701b9279 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_crtc.c @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm_mode.h" +#include "drm_crtc_helper.h" + +#define NOUVEAU_DMA_DEBUG (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO) +#include "nouveau_reg.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" +#include "nouveau_encoder.h" +#include "nouveau_crtc.h" +#include "nouveau_fb.h" +#include "nouveau_connector.h" +#include "nv50_display.h" + +static void +nv50_crtc_lut_load(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + void __iomem *lut = nvbo_kmap_obj_iovirtual(nv_crtc->lut.nvbo); + int i; + + NV_DEBUG_KMS(crtc->dev, "\n"); + + for (i = 0; i < 256; i++) { + writew(nv_crtc->lut.r[i] >> 2, lut + 8*i + 0); + writew(nv_crtc->lut.g[i] >> 2, lut + 8*i + 2); + writew(nv_crtc->lut.b[i] >> 2, lut + 8*i + 4); + } + + if (nv_crtc->lut.depth == 30) { + writew(nv_crtc->lut.r[i - 1] >> 2, lut + 8*i + 0); + writew(nv_crtc->lut.g[i - 1] >> 2, lut + 8*i + 2); + writew(nv_crtc->lut.b[i - 1] >> 2, lut + 8*i + 4); + } +} + +int +nv50_crtc_blank(struct nouveau_crtc *nv_crtc, bool blanked) +{ + struct drm_device *dev = nv_crtc->base.dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *evo = nv50_display(dev)->master; + int index = nv_crtc->index, ret; + + NV_DEBUG_KMS(dev, "index %d\n", nv_crtc->index); + NV_DEBUG_KMS(dev, "%s\n", blanked ? "blanked" : "unblanked"); + + if (blanked) { + nv_crtc->cursor.hide(nv_crtc, false); + + ret = RING_SPACE(evo, dev_priv->chipset != 0x50 ? 7 : 5); + if (ret) { + NV_ERROR(dev, "no space while blanking crtc\n"); + return ret; + } + BEGIN_RING(evo, 0, NV50_EVO_CRTC(index, CLUT_MODE), 2); + OUT_RING(evo, NV50_EVO_CRTC_CLUT_MODE_BLANK); + OUT_RING(evo, 0); + if (dev_priv->chipset != 0x50) { + BEGIN_RING(evo, 0, NV84_EVO_CRTC(index, CLUT_DMA), 1); + OUT_RING(evo, NV84_EVO_CRTC_CLUT_DMA_HANDLE_NONE); + } + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(index, FB_DMA), 1); + OUT_RING(evo, NV50_EVO_CRTC_FB_DMA_HANDLE_NONE); + } else { + if (nv_crtc->cursor.visible) + nv_crtc->cursor.show(nv_crtc, false); + else + nv_crtc->cursor.hide(nv_crtc, false); + + ret = RING_SPACE(evo, dev_priv->chipset != 0x50 ? 10 : 8); + if (ret) { + NV_ERROR(dev, "no space while unblanking crtc\n"); + return ret; + } + BEGIN_RING(evo, 0, NV50_EVO_CRTC(index, CLUT_MODE), 2); + OUT_RING(evo, nv_crtc->lut.depth == 8 ? + NV50_EVO_CRTC_CLUT_MODE_OFF : + NV50_EVO_CRTC_CLUT_MODE_ON); + OUT_RING(evo, nv_crtc->lut.nvbo->bo.offset >> 8); + if (dev_priv->chipset != 0x50) { + BEGIN_RING(evo, 0, NV84_EVO_CRTC(index, CLUT_DMA), 1); + OUT_RING(evo, NvEvoVRAM); + } + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(index, FB_OFFSET), 2); + OUT_RING(evo, nv_crtc->fb.offset >> 8); + OUT_RING(evo, 0); + BEGIN_RING(evo, 0, NV50_EVO_CRTC(index, FB_DMA), 1); + if (dev_priv->chipset != 0x50) + if (nv_crtc->fb.tile_flags == 0x7a00 || + nv_crtc->fb.tile_flags == 0xfe00) + OUT_RING(evo, NvEvoFB32); + else + if (nv_crtc->fb.tile_flags == 0x7000) + OUT_RING(evo, NvEvoFB16); + else + OUT_RING(evo, NvEvoVRAM_LP); + else + OUT_RING(evo, NvEvoVRAM_LP); + } + + nv_crtc->fb.blanked = blanked; + return 0; +} + +static int +nv50_crtc_set_dither(struct nouveau_crtc *nv_crtc, bool update) +{ + struct nouveau_channel *evo = nv50_display(nv_crtc->base.dev)->master; + struct nouveau_connector *nv_connector; + struct drm_connector *connector; + int head = nv_crtc->index, ret; + u32 mode = 0x00; + + nv_connector = nouveau_crtc_connector_get(nv_crtc); + connector = &nv_connector->base; + if (nv_connector->dithering_mode == DITHERING_MODE_AUTO) { + if (nv_crtc->base.fb->depth > connector->display_info.bpc * 3) + mode = DITHERING_MODE_DYNAMIC2X2; + } else { + mode = nv_connector->dithering_mode; + } + + if (nv_connector->dithering_depth == DITHERING_DEPTH_AUTO) { + if (connector->display_info.bpc >= 8) + mode |= DITHERING_DEPTH_8BPC; + } else { + mode |= nv_connector->dithering_depth; + } + + ret = RING_SPACE(evo, 2 + (update ? 2 : 0)); + if (ret == 0) { + BEGIN_RING(evo, 0, NV50_EVO_CRTC(head, DITHER_CTRL), 1); + OUT_RING (evo, mode); + if (update) { + BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1); + OUT_RING (evo, 0); + FIRE_RING (evo); + } + } + + return ret; +} + +static int +nv50_crtc_set_color_vibrance(struct nouveau_crtc *nv_crtc, bool update) +{ + struct drm_device *dev = nv_crtc->base.dev; + struct nouveau_channel *evo = nv50_display(dev)->master; + int ret; + int adj; + u32 hue, vib; + + NV_DEBUG_KMS(dev, "vibrance = %i, hue = %i\n", + nv_crtc->color_vibrance, nv_crtc->vibrant_hue); + + ret = RING_SPACE(evo, 2 + (update ? 2 : 0)); + if (ret) { + NV_ERROR(dev, "no space while setting color vibrance\n"); + return ret; + } + + adj = (nv_crtc->color_vibrance > 0) ? 50 : 0; + vib = ((nv_crtc->color_vibrance * 2047 + adj) / 100) & 0xfff; + + hue = ((nv_crtc->vibrant_hue * 2047) / 100) & 0xfff; + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, COLOR_CTRL), 1); + OUT_RING (evo, (hue << 20) | (vib << 8)); + + if (update) { + BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1); + OUT_RING (evo, 0); + FIRE_RING (evo); + } + + return 0; +} + +struct nouveau_connector * +nouveau_crtc_connector_get(struct nouveau_crtc *nv_crtc) +{ + struct drm_device *dev = nv_crtc->base.dev; + struct drm_connector *connector; + struct drm_crtc *crtc = to_drm_crtc(nv_crtc); + + /* The safest approach is to find an encoder with the right crtc, that + * is also linked to a connector. */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder) + if (connector->encoder->crtc == crtc) + return nouveau_connector(connector); + } + + return NULL; +} + +static int +nv50_crtc_set_scale(struct nouveau_crtc *nv_crtc, bool update) +{ + struct nouveau_connector *nv_connector; + struct drm_crtc *crtc = &nv_crtc->base; + struct drm_device *dev = crtc->dev; + struct nouveau_channel *evo = nv50_display(dev)->master; + struct drm_display_mode *umode = &crtc->mode; + struct drm_display_mode *omode; + int scaling_mode, ret; + u32 ctrl = 0, oX, oY; + + NV_DEBUG_KMS(dev, "\n"); + + nv_connector = nouveau_crtc_connector_get(nv_crtc); + if (!nv_connector || !nv_connector->native_mode) { + NV_ERROR(dev, "no native mode, forcing panel scaling\n"); + scaling_mode = DRM_MODE_SCALE_NONE; + } else { + scaling_mode = nv_connector->scaling_mode; + } + + /* start off at the resolution we programmed the crtc for, this + * effectively handles NONE/FULL scaling + */ + if (scaling_mode != DRM_MODE_SCALE_NONE) + omode = nv_connector->native_mode; + else + omode = umode; + + oX = omode->hdisplay; + oY = omode->vdisplay; + if (omode->flags & DRM_MODE_FLAG_DBLSCAN) + oY *= 2; + + /* add overscan compensation if necessary, will keep the aspect + * ratio the same as the backend mode unless overridden by the + * user setting both hborder and vborder properties. + */ + if (nv_connector && ( nv_connector->underscan == UNDERSCAN_ON || + (nv_connector->underscan == UNDERSCAN_AUTO && + nv_connector->edid && + drm_detect_hdmi_monitor(nv_connector->edid)))) { + u32 bX = nv_connector->underscan_hborder; + u32 bY = nv_connector->underscan_vborder; + u32 aspect = (oY << 19) / oX; + + if (bX) { + oX -= (bX * 2); + if (bY) oY -= (bY * 2); + else oY = ((oX * aspect) + (aspect / 2)) >> 19; + } else { + oX -= (oX >> 4) + 32; + if (bY) oY -= (bY * 2); + else oY = ((oX * aspect) + (aspect / 2)) >> 19; + } + } + + /* handle CENTER/ASPECT scaling, taking into account the areas + * removed already for overscan compensation + */ + switch (scaling_mode) { + case DRM_MODE_SCALE_CENTER: + oX = min((u32)umode->hdisplay, oX); + oY = min((u32)umode->vdisplay, oY); + /* fall-through */ + case DRM_MODE_SCALE_ASPECT: + if (oY < oX) { + u32 aspect = (umode->hdisplay << 19) / umode->vdisplay; + oX = ((oY * aspect) + (aspect / 2)) >> 19; + } else { + u32 aspect = (umode->vdisplay << 19) / umode->hdisplay; + oY = ((oX * aspect) + (aspect / 2)) >> 19; + } + break; + default: + break; + } + + if (umode->hdisplay != oX || umode->vdisplay != oY || + umode->flags & DRM_MODE_FLAG_INTERLACE || + umode->flags & DRM_MODE_FLAG_DBLSCAN) + ctrl |= NV50_EVO_CRTC_SCALE_CTRL_ACTIVE; + + ret = RING_SPACE(evo, 5); + if (ret) + return ret; + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, SCALE_CTRL), 1); + OUT_RING (evo, ctrl); + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, SCALE_RES1), 2); + OUT_RING (evo, oY << 16 | oX); + OUT_RING (evo, oY << 16 | oX); + + if (update) { + nv50_display_flip_stop(crtc); + nv50_display_sync(dev); + nv50_display_flip_next(crtc, crtc->fb, NULL); + } + + return 0; +} + +int +nv50_crtc_set_clock(struct drm_device *dev, int head, int pclk) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct pll_lims pll; + uint32_t reg1, reg2; + int ret, N1, M1, N2, M2, P; + + ret = get_pll_limits(dev, PLL_VPLL0 + head, &pll); + if (ret) + return ret; + + if (pll.vco2.maxfreq) { + ret = nv50_calc_pll(dev, &pll, pclk, &N1, &M1, &N2, &M2, &P); + if (ret <= 0) + return 0; + + NV_DEBUG(dev, "pclk %d out %d NM1 %d %d NM2 %d %d P %d\n", + pclk, ret, N1, M1, N2, M2, P); + + reg1 = nv_rd32(dev, pll.reg + 4) & 0xff00ff00; + reg2 = nv_rd32(dev, pll.reg + 8) & 0x8000ff00; + nv_wr32(dev, pll.reg + 0, 0x10000611); + nv_wr32(dev, pll.reg + 4, reg1 | (M1 << 16) | N1); + nv_wr32(dev, pll.reg + 8, reg2 | (P << 28) | (M2 << 16) | N2); + } else + if (dev_priv->chipset < NV_C0) { + ret = nva3_calc_pll(dev, &pll, pclk, &N1, &N2, &M1, &P); + if (ret <= 0) + return 0; + + NV_DEBUG(dev, "pclk %d out %d N %d fN 0x%04x M %d P %d\n", + pclk, ret, N1, N2, M1, P); + + reg1 = nv_rd32(dev, pll.reg + 4) & 0xffc00000; + nv_wr32(dev, pll.reg + 0, 0x50000610); + nv_wr32(dev, pll.reg + 4, reg1 | (P << 16) | (M1 << 8) | N1); + nv_wr32(dev, pll.reg + 8, N2); + } else { + ret = nva3_calc_pll(dev, &pll, pclk, &N1, &N2, &M1, &P); + if (ret <= 0) + return 0; + + NV_DEBUG(dev, "pclk %d out %d N %d fN 0x%04x M %d P %d\n", + pclk, ret, N1, N2, M1, P); + + nv_mask(dev, pll.reg + 0x0c, 0x00000000, 0x00000100); + nv_wr32(dev, pll.reg + 0x04, (P << 16) | (N1 << 8) | M1); + nv_wr32(dev, pll.reg + 0x10, N2 << 16); + } + + return 0; +} + +static void +nv50_crtc_destroy(struct drm_crtc *crtc) +{ + struct drm_device *dev; + struct nouveau_crtc *nv_crtc; + + if (!crtc) + return; + + dev = crtc->dev; + nv_crtc = nouveau_crtc(crtc); + + NV_DEBUG_KMS(dev, "\n"); + + drm_crtc_cleanup(&nv_crtc->base); + + nouveau_bo_unmap(nv_crtc->lut.nvbo); + nouveau_bo_ref(NULL, &nv_crtc->lut.nvbo); + nouveau_bo_unmap(nv_crtc->cursor.nvbo); + nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo); + kfree(nv_crtc); +} + +int +nv50_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv, + uint32_t buffer_handle, uint32_t width, uint32_t height) +{ + struct drm_device *dev = crtc->dev; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nouveau_bo *cursor = NULL; + struct drm_gem_object *gem; + int ret = 0, i; + + if (!buffer_handle) { + nv_crtc->cursor.hide(nv_crtc, true); + return 0; + } + + if (width != 64 || height != 64) + return -EINVAL; + + gem = drm_gem_object_lookup(dev, file_priv, buffer_handle); + if (!gem) + return -ENOENT; + cursor = nouveau_gem_object(gem); + + ret = nouveau_bo_map(cursor); + if (ret) + goto out; + + /* The simple will do for now. */ + for (i = 0; i < 64 * 64; i++) + nouveau_bo_wr32(nv_crtc->cursor.nvbo, i, nouveau_bo_rd32(cursor, i)); + + nouveau_bo_unmap(cursor); + + nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.nvbo->bo.offset); + nv_crtc->cursor.show(nv_crtc, true); + +out: + drm_gem_object_unreference_unlocked(gem); + return ret; +} + +int +nv50_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + nv_crtc->cursor.set_pos(nv_crtc, x, y); + return 0; +} + +static void +nv50_crtc_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, + uint32_t start, uint32_t size) +{ + int end = (start + size > 256) ? 256 : start + size, i; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + for (i = start; i < end; i++) { + nv_crtc->lut.r[i] = r[i]; + nv_crtc->lut.g[i] = g[i]; + nv_crtc->lut.b[i] = b[i]; + } + + /* We need to know the depth before we upload, but it's possible to + * get called before a framebuffer is bound. If this is the case, + * mark the lut values as dirty by setting depth==0, and it'll be + * uploaded on the first mode_set_base() + */ + if (!nv_crtc->base.fb) { + nv_crtc->lut.depth = 0; + return; + } + + nv50_crtc_lut_load(crtc); +} + +static void +nv50_crtc_save(struct drm_crtc *crtc) +{ + NV_ERROR(crtc->dev, "!!\n"); +} + +static void +nv50_crtc_restore(struct drm_crtc *crtc) +{ + NV_ERROR(crtc->dev, "!!\n"); +} + +static const struct drm_crtc_funcs nv50_crtc_funcs = { + .save = nv50_crtc_save, + .restore = nv50_crtc_restore, + .cursor_set = nv50_crtc_cursor_set, + .cursor_move = nv50_crtc_cursor_move, + .gamma_set = nv50_crtc_gamma_set, + .set_config = drm_crtc_helper_set_config, + .page_flip = nouveau_crtc_page_flip, + .destroy = nv50_crtc_destroy, +}; + +static void +nv50_crtc_dpms(struct drm_crtc *crtc, int mode) +{ +} + +static void +nv50_crtc_prepare(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_device *dev = crtc->dev; + + NV_DEBUG_KMS(dev, "index %d\n", nv_crtc->index); + + nv50_display_flip_stop(crtc); + drm_vblank_pre_modeset(dev, nv_crtc->index); + nv50_crtc_blank(nv_crtc, true); +} + +static void +nv50_crtc_commit(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + + NV_DEBUG_KMS(dev, "index %d\n", nv_crtc->index); + + nv50_crtc_blank(nv_crtc, false); + drm_vblank_post_modeset(dev, nv_crtc->index); + nv50_display_sync(dev); + nv50_display_flip_next(crtc, crtc->fb, NULL); +} + +static bool +nv50_crtc_mode_fixup(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int +nv50_crtc_do_mode_set_base(struct drm_crtc *crtc, + struct drm_framebuffer *passed_fb, + int x, int y, bool atomic) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_device *dev = nv_crtc->base.dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *evo = nv50_display(dev)->master; + struct drm_framebuffer *drm_fb; + struct nouveau_framebuffer *fb; + int ret; + + NV_DEBUG_KMS(dev, "index %d\n", nv_crtc->index); + + /* no fb bound */ + if (!atomic && !crtc->fb) { + NV_DEBUG_KMS(dev, "No FB bound\n"); + return 0; + } + + /* If atomic, we want to switch to the fb we were passed, so + * now we update pointers to do that. (We don't pin; just + * assume we're already pinned and update the base address.) + */ + if (atomic) { + drm_fb = passed_fb; + fb = nouveau_framebuffer(passed_fb); + } else { + drm_fb = crtc->fb; + fb = nouveau_framebuffer(crtc->fb); + /* If not atomic, we can go ahead and pin, and unpin the + * old fb we were passed. + */ + ret = nouveau_bo_pin(fb->nvbo, TTM_PL_FLAG_VRAM); + if (ret) + return ret; + + if (passed_fb) { + struct nouveau_framebuffer *ofb = nouveau_framebuffer(passed_fb); + nouveau_bo_unpin(ofb->nvbo); + } + } + + nv_crtc->fb.offset = fb->nvbo->bo.offset; + nv_crtc->fb.tile_flags = nouveau_bo_tile_layout(fb->nvbo); + nv_crtc->fb.cpp = drm_fb->bits_per_pixel / 8; + if (!nv_crtc->fb.blanked && dev_priv->chipset != 0x50) { + ret = RING_SPACE(evo, 2); + if (ret) + return ret; + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, FB_DMA), 1); + OUT_RING (evo, fb->r_dma); + } + + ret = RING_SPACE(evo, 12); + if (ret) + return ret; + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, FB_OFFSET), 5); + OUT_RING (evo, nv_crtc->fb.offset >> 8); + OUT_RING (evo, 0); + OUT_RING (evo, (drm_fb->height << 16) | drm_fb->width); + OUT_RING (evo, fb->r_pitch); + OUT_RING (evo, fb->r_format); + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, CLUT_MODE), 1); + OUT_RING (evo, fb->base.depth == 8 ? + NV50_EVO_CRTC_CLUT_MODE_OFF : NV50_EVO_CRTC_CLUT_MODE_ON); + + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, FB_POS), 1); + OUT_RING (evo, (y << 16) | x); + + if (nv_crtc->lut.depth != fb->base.depth) { + nv_crtc->lut.depth = fb->base.depth; + nv50_crtc_lut_load(crtc); + } + + return 0; +} + +static int +nv50_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *umode, + struct drm_display_mode *mode, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct drm_device *dev = crtc->dev; + struct nouveau_channel *evo = nv50_display(dev)->master; + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + u32 head = nv_crtc->index * 0x400; + u32 ilace = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 2 : 1; + u32 vscan = (mode->flags & DRM_MODE_FLAG_DBLSCAN) ? 2 : 1; + u32 hactive, hsynce, hbackp, hfrontp, hblanke, hblanks; + u32 vactive, vsynce, vbackp, vfrontp, vblanke, vblanks; + u32 vblan2e = 0, vblan2s = 1; + int ret; + + /* hw timing description looks like this: + * + * <sync> <back porch> <---------display---------> <front porch> + * ______ + * |____________|---------------------------|____________| + * + * ^ synce ^ blanke ^ blanks ^ active + * + * interlaced modes also have 2 additional values pointing at the end + * and start of the next field's blanking period. + */ + + hactive = mode->htotal; + hsynce = mode->hsync_end - mode->hsync_start - 1; + hbackp = mode->htotal - mode->hsync_end; + hblanke = hsynce + hbackp; + hfrontp = mode->hsync_start - mode->hdisplay; + hblanks = mode->htotal - hfrontp - 1; + + vactive = mode->vtotal * vscan / ilace; + vsynce = ((mode->vsync_end - mode->vsync_start) * vscan / ilace) - 1; + vbackp = (mode->vtotal - mode->vsync_end) * vscan / ilace; + vblanke = vsynce + vbackp; + vfrontp = (mode->vsync_start - mode->vdisplay) * vscan / ilace; + vblanks = vactive - vfrontp - 1; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + vblan2e = vactive + vsynce + vbackp; + vblan2s = vblan2e + (mode->vdisplay * vscan / ilace); + vactive = (vactive * 2) + 1; + } + + ret = RING_SPACE(evo, 18); + if (ret == 0) { + BEGIN_RING(evo, 0, 0x0804 + head, 2); + OUT_RING (evo, 0x00800000 | mode->clock); + OUT_RING (evo, (ilace == 2) ? 2 : 0); + BEGIN_RING(evo, 0, 0x0810 + head, 6); + OUT_RING (evo, 0x00000000); /* border colour */ + OUT_RING (evo, (vactive << 16) | hactive); + OUT_RING (evo, ( vsynce << 16) | hsynce); + OUT_RING (evo, (vblanke << 16) | hblanke); + OUT_RING (evo, (vblanks << 16) | hblanks); + OUT_RING (evo, (vblan2e << 16) | vblan2s); + BEGIN_RING(evo, 0, 0x082c + head, 1); + OUT_RING (evo, 0x00000000); + BEGIN_RING(evo, 0, 0x0900 + head, 1); + OUT_RING (evo, 0x00000311); /* makes sync channel work */ + BEGIN_RING(evo, 0, 0x08c8 + head, 1); + OUT_RING (evo, (umode->vdisplay << 16) | umode->hdisplay); + BEGIN_RING(evo, 0, 0x08d4 + head, 1); + OUT_RING (evo, 0x00000000); /* screen position */ + } + + nv_crtc->set_dither(nv_crtc, false); + nv_crtc->set_scale(nv_crtc, false); + nv_crtc->set_color_vibrance(nv_crtc, false); + + return nv50_crtc_do_mode_set_base(crtc, old_fb, x, y, false); +} + +static int +nv50_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + int ret; + + nv50_display_flip_stop(crtc); + ret = nv50_crtc_do_mode_set_base(crtc, old_fb, x, y, false); + if (ret) + return ret; + + ret = nv50_display_sync(crtc->dev); + if (ret) + return ret; + + return nv50_display_flip_next(crtc, crtc->fb, NULL); +} + +static int +nv50_crtc_mode_set_base_atomic(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + int x, int y, enum mode_set_atomic state) +{ + int ret; + + nv50_display_flip_stop(crtc); + ret = nv50_crtc_do_mode_set_base(crtc, fb, x, y, true); + if (ret) + return ret; + + return nv50_display_sync(crtc->dev); +} + +static const struct drm_crtc_helper_funcs nv50_crtc_helper_funcs = { + .dpms = nv50_crtc_dpms, + .prepare = nv50_crtc_prepare, + .commit = nv50_crtc_commit, + .mode_fixup = nv50_crtc_mode_fixup, + .mode_set = nv50_crtc_mode_set, + .mode_set_base = nv50_crtc_mode_set_base, + .mode_set_base_atomic = nv50_crtc_mode_set_base_atomic, + .load_lut = nv50_crtc_lut_load, +}; + +int +nv50_crtc_create(struct drm_device *dev, int index) +{ + struct nouveau_crtc *nv_crtc = NULL; + int ret, i; + + NV_DEBUG_KMS(dev, "\n"); + + nv_crtc = kzalloc(sizeof(*nv_crtc), GFP_KERNEL); + if (!nv_crtc) + return -ENOMEM; + + nv_crtc->color_vibrance = 50; + nv_crtc->vibrant_hue = 0; + + /* Default CLUT parameters, will be activated on the hw upon + * first mode set. + */ + for (i = 0; i < 256; i++) { + nv_crtc->lut.r[i] = i << 8; + nv_crtc->lut.g[i] = i << 8; + nv_crtc->lut.b[i] = i << 8; + } + nv_crtc->lut.depth = 0; + + ret = nouveau_bo_new(dev, 4096, 0x100, TTM_PL_FLAG_VRAM, + 0, 0x0000, &nv_crtc->lut.nvbo); + if (!ret) { + ret = nouveau_bo_pin(nv_crtc->lut.nvbo, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(nv_crtc->lut.nvbo); + if (ret) + nouveau_bo_ref(NULL, &nv_crtc->lut.nvbo); + } + + if (ret) { + kfree(nv_crtc); + return ret; + } + + nv_crtc->index = index; + + /* set function pointers */ + nv_crtc->set_dither = nv50_crtc_set_dither; + nv_crtc->set_scale = nv50_crtc_set_scale; + nv_crtc->set_color_vibrance = nv50_crtc_set_color_vibrance; + + drm_crtc_init(dev, &nv_crtc->base, &nv50_crtc_funcs); + drm_crtc_helper_add(&nv_crtc->base, &nv50_crtc_helper_funcs); + drm_mode_crtc_set_gamma_size(&nv_crtc->base, 256); + + ret = nouveau_bo_new(dev, 64*64*4, 0x100, TTM_PL_FLAG_VRAM, + 0, 0x0000, &nv_crtc->cursor.nvbo); + if (!ret) { + ret = nouveau_bo_pin(nv_crtc->cursor.nvbo, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(nv_crtc->cursor.nvbo); + if (ret) + nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo); + } + + nv50_cursor_init(nv_crtc); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv50_cursor.c b/drivers/gpu/drm/nouveau/nv50_cursor.c new file mode 100644 index 00000000..adfc9b60 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_cursor.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm_mode.h" + +#define NOUVEAU_DMA_DEBUG (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO) +#include "nouveau_reg.h" +#include "nouveau_drv.h" +#include "nouveau_crtc.h" +#include "nv50_display.h" + +static void +nv50_cursor_show(struct nouveau_crtc *nv_crtc, bool update) +{ + struct drm_device *dev = nv_crtc->base.dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *evo = nv50_display(dev)->master; + int ret; + + NV_DEBUG_KMS(dev, "\n"); + + if (update && nv_crtc->cursor.visible) + return; + + ret = RING_SPACE(evo, (dev_priv->chipset != 0x50 ? 5 : 3) + update * 2); + if (ret) { + NV_ERROR(dev, "no space while unhiding cursor\n"); + return; + } + + if (dev_priv->chipset != 0x50) { + BEGIN_RING(evo, 0, NV84_EVO_CRTC(nv_crtc->index, CURSOR_DMA), 1); + OUT_RING(evo, NvEvoVRAM); + } + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, CURSOR_CTRL), 2); + OUT_RING(evo, NV50_EVO_CRTC_CURSOR_CTRL_SHOW); + OUT_RING(evo, nv_crtc->cursor.offset >> 8); + + if (update) { + BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1); + OUT_RING(evo, 0); + FIRE_RING(evo); + nv_crtc->cursor.visible = true; + } +} + +static void +nv50_cursor_hide(struct nouveau_crtc *nv_crtc, bool update) +{ + struct drm_device *dev = nv_crtc->base.dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *evo = nv50_display(dev)->master; + int ret; + + NV_DEBUG_KMS(dev, "\n"); + + if (update && !nv_crtc->cursor.visible) + return; + + ret = RING_SPACE(evo, (dev_priv->chipset != 0x50 ? 5 : 3) + update * 2); + if (ret) { + NV_ERROR(dev, "no space while hiding cursor\n"); + return; + } + BEGIN_RING(evo, 0, NV50_EVO_CRTC(nv_crtc->index, CURSOR_CTRL), 2); + OUT_RING(evo, NV50_EVO_CRTC_CURSOR_CTRL_HIDE); + OUT_RING(evo, 0); + if (dev_priv->chipset != 0x50) { + BEGIN_RING(evo, 0, NV84_EVO_CRTC(nv_crtc->index, CURSOR_DMA), 1); + OUT_RING(evo, NV84_EVO_CRTC_CURSOR_DMA_HANDLE_NONE); + } + + if (update) { + BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1); + OUT_RING(evo, 0); + FIRE_RING(evo); + nv_crtc->cursor.visible = false; + } +} + +static void +nv50_cursor_set_pos(struct nouveau_crtc *nv_crtc, int x, int y) +{ + struct drm_device *dev = nv_crtc->base.dev; + + nv_crtc->cursor_saved_x = x; nv_crtc->cursor_saved_y = y; + nv_wr32(dev, NV50_PDISPLAY_CURSOR_USER_POS(nv_crtc->index), + ((y & 0xFFFF) << 16) | (x & 0xFFFF)); + /* Needed to make the cursor move. */ + nv_wr32(dev, NV50_PDISPLAY_CURSOR_USER_POS_CTRL(nv_crtc->index), 0); +} + +static void +nv50_cursor_set_offset(struct nouveau_crtc *nv_crtc, uint32_t offset) +{ + NV_DEBUG_KMS(nv_crtc->base.dev, "\n"); + if (offset == nv_crtc->cursor.offset) + return; + + nv_crtc->cursor.offset = offset; + if (nv_crtc->cursor.visible) { + nv_crtc->cursor.visible = false; + nv_crtc->cursor.show(nv_crtc, true); + } +} + +int +nv50_cursor_init(struct nouveau_crtc *nv_crtc) +{ + nv_crtc->cursor.set_offset = nv50_cursor_set_offset; + nv_crtc->cursor.set_pos = nv50_cursor_set_pos; + nv_crtc->cursor.hide = nv50_cursor_hide; + nv_crtc->cursor.show = nv50_cursor_show; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv50_dac.c b/drivers/gpu/drm/nouveau/nv50_dac.c new file mode 100644 index 00000000..55c56330 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_dac.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" + +#define NOUVEAU_DMA_DEBUG (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO) +#include "nouveau_reg.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_crtc.h" +#include "nv50_display.h" + +static void +nv50_dac_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_channel *evo = nv50_display(dev)->master; + int ret; + + if (!nv_encoder->crtc) + return; + nv50_crtc_blank(nouveau_crtc(nv_encoder->crtc), true); + + NV_DEBUG_KMS(dev, "Disconnecting DAC %d\n", nv_encoder->or); + + ret = RING_SPACE(evo, 4); + if (ret) { + NV_ERROR(dev, "no space while disconnecting DAC\n"); + return; + } + BEGIN_RING(evo, 0, NV50_EVO_DAC(nv_encoder->or, MODE_CTRL), 1); + OUT_RING (evo, 0); + BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1); + OUT_RING (evo, 0); + + nv_encoder->crtc = NULL; +} + +static enum drm_connector_status +nv50_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + enum drm_connector_status status = connector_status_disconnected; + uint32_t dpms_state, load_pattern, load_state; + int or = nv_encoder->or; + + nv_wr32(dev, NV50_PDISPLAY_DAC_CLK_CTRL1(or), 0x00000001); + dpms_state = nv_rd32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or)); + + nv_wr32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or), + 0x00150000 | NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING); + if (!nv_wait(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or), + NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING, 0)) { + NV_ERROR(dev, "timeout: DAC_DPMS_CTRL_PENDING(%d) == 0\n", or); + NV_ERROR(dev, "DAC_DPMS_CTRL(%d) = 0x%08x\n", or, + nv_rd32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or))); + return status; + } + + /* Use bios provided value if possible. */ + if (dev_priv->vbios.dactestval) { + load_pattern = dev_priv->vbios.dactestval; + NV_DEBUG_KMS(dev, "Using bios provided load_pattern of %d\n", + load_pattern); + } else { + load_pattern = 340; + NV_DEBUG_KMS(dev, "Using default load_pattern of %d\n", + load_pattern); + } + + nv_wr32(dev, NV50_PDISPLAY_DAC_LOAD_CTRL(or), + NV50_PDISPLAY_DAC_LOAD_CTRL_ACTIVE | load_pattern); + mdelay(45); /* give it some time to process */ + load_state = nv_rd32(dev, NV50_PDISPLAY_DAC_LOAD_CTRL(or)); + + nv_wr32(dev, NV50_PDISPLAY_DAC_LOAD_CTRL(or), 0); + nv_wr32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or), dpms_state | + NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING); + + if ((load_state & NV50_PDISPLAY_DAC_LOAD_CTRL_PRESENT) == + NV50_PDISPLAY_DAC_LOAD_CTRL_PRESENT) + status = connector_status_connected; + + if (status == connector_status_connected) + NV_DEBUG_KMS(dev, "Load was detected on output with or %d\n", or); + else + NV_DEBUG_KMS(dev, "Load was not detected on output with or %d\n", or); + + return status; +} + +static void +nv50_dac_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + uint32_t val; + int or = nv_encoder->or; + + NV_DEBUG_KMS(dev, "or %d mode %d\n", or, mode); + + /* wait for it to be done */ + if (!nv_wait(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or), + NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING, 0)) { + NV_ERROR(dev, "timeout: DAC_DPMS_CTRL_PENDING(%d) == 0\n", or); + NV_ERROR(dev, "DAC_DPMS_CTRL(%d) = 0x%08x\n", or, + nv_rd32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or))); + return; + } + + val = nv_rd32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or)) & ~0x7F; + + if (mode != DRM_MODE_DPMS_ON) + val |= NV50_PDISPLAY_DAC_DPMS_CTRL_BLANKED; + + switch (mode) { + case DRM_MODE_DPMS_STANDBY: + val |= NV50_PDISPLAY_DAC_DPMS_CTRL_HSYNC_OFF; + break; + case DRM_MODE_DPMS_SUSPEND: + val |= NV50_PDISPLAY_DAC_DPMS_CTRL_VSYNC_OFF; + break; + case DRM_MODE_DPMS_OFF: + val |= NV50_PDISPLAY_DAC_DPMS_CTRL_OFF; + val |= NV50_PDISPLAY_DAC_DPMS_CTRL_HSYNC_OFF; + val |= NV50_PDISPLAY_DAC_DPMS_CTRL_VSYNC_OFF; + break; + default: + break; + } + + nv_wr32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(or), val | + NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING); +} + +static void +nv50_dac_save(struct drm_encoder *encoder) +{ + NV_ERROR(encoder->dev, "!!\n"); +} + +static void +nv50_dac_restore(struct drm_encoder *encoder) +{ + NV_ERROR(encoder->dev, "!!\n"); +} + +static bool +nv50_dac_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *connector; + + NV_DEBUG_KMS(encoder->dev, "or %d\n", nv_encoder->or); + + connector = nouveau_encoder_connector_get(nv_encoder); + if (!connector) { + NV_ERROR(encoder->dev, "Encoder has no connector\n"); + return false; + } + + if (connector->scaling_mode != DRM_MODE_SCALE_NONE && + connector->native_mode) + drm_mode_copy(adjusted_mode, connector->native_mode); + + return true; +} + +static void +nv50_dac_commit(struct drm_encoder *encoder) +{ +} + +static void +nv50_dac_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_channel *evo = nv50_display(dev)->master; + struct nouveau_crtc *crtc = nouveau_crtc(encoder->crtc); + uint32_t mode_ctl = 0, mode_ctl2 = 0; + int ret; + + NV_DEBUG_KMS(dev, "or %d type %d crtc %d\n", + nv_encoder->or, nv_encoder->dcb->type, crtc->index); + + nv50_dac_dpms(encoder, DRM_MODE_DPMS_ON); + + if (crtc->index == 1) + mode_ctl |= NV50_EVO_DAC_MODE_CTRL_CRTC1; + else + mode_ctl |= NV50_EVO_DAC_MODE_CTRL_CRTC0; + + /* Lacking a working tv-out, this is not a 100% sure. */ + if (nv_encoder->dcb->type == OUTPUT_ANALOG) + mode_ctl |= 0x40; + else + if (nv_encoder->dcb->type == OUTPUT_TV) + mode_ctl |= 0x100; + + if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) + mode_ctl2 |= NV50_EVO_DAC_MODE_CTRL2_NHSYNC; + + if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) + mode_ctl2 |= NV50_EVO_DAC_MODE_CTRL2_NVSYNC; + + ret = RING_SPACE(evo, 3); + if (ret) { + NV_ERROR(dev, "no space while connecting DAC\n"); + return; + } + BEGIN_RING(evo, 0, NV50_EVO_DAC(nv_encoder->or, MODE_CTRL), 2); + OUT_RING(evo, mode_ctl); + OUT_RING(evo, mode_ctl2); + + nv_encoder->crtc = encoder->crtc; +} + +static struct drm_crtc * +nv50_dac_crtc_get(struct drm_encoder *encoder) +{ + return nouveau_encoder(encoder)->crtc; +} + +static const struct drm_encoder_helper_funcs nv50_dac_helper_funcs = { + .dpms = nv50_dac_dpms, + .save = nv50_dac_save, + .restore = nv50_dac_restore, + .mode_fixup = nv50_dac_mode_fixup, + .prepare = nv50_dac_disconnect, + .commit = nv50_dac_commit, + .mode_set = nv50_dac_mode_set, + .get_crtc = nv50_dac_crtc_get, + .detect = nv50_dac_detect, + .disable = nv50_dac_disconnect +}; + +static void +nv50_dac_destroy(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + if (!encoder) + return; + + NV_DEBUG_KMS(encoder->dev, "\n"); + + drm_encoder_cleanup(encoder); + kfree(nv_encoder); +} + +static const struct drm_encoder_funcs nv50_dac_encoder_funcs = { + .destroy = nv50_dac_destroy, +}; + +int +nv50_dac_create(struct drm_connector *connector, struct dcb_entry *entry) +{ + struct nouveau_encoder *nv_encoder; + struct drm_encoder *encoder; + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + encoder = to_drm_encoder(nv_encoder); + + nv_encoder->dcb = entry; + nv_encoder->or = ffs(entry->or) - 1; + + drm_encoder_init(connector->dev, encoder, &nv50_dac_encoder_funcs, + DRM_MODE_ENCODER_DAC); + drm_encoder_helper_add(encoder, &nv50_dac_helper_funcs); + + encoder->possible_crtcs = entry->heads; + encoder->possible_clones = 0; + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} + diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c new file mode 100644 index 00000000..8b78b9cf --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_display.c @@ -0,0 +1,1038 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#define NOUVEAU_DMA_DEBUG (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO) +#include "nv50_display.h" +#include "nouveau_crtc.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_fb.h" +#include "nouveau_fbcon.h" +#include "nouveau_ramht.h" +#include "drm_crtc_helper.h" + +static void nv50_display_isr(struct drm_device *); +static void nv50_display_bh(unsigned long); + +static inline int +nv50_sor_nr(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset < 0x90 || + dev_priv->chipset == 0x92 || + dev_priv->chipset == 0xa0) + return 2; + + return 4; +} + +u32 +nv50_display_active_crtcs(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 mask = 0; + int i; + + if (dev_priv->chipset < 0x90 || + dev_priv->chipset == 0x92 || + dev_priv->chipset == 0xa0) { + for (i = 0; i < 2; i++) + mask |= nv_rd32(dev, NV50_PDISPLAY_SOR_MODE_CTRL_C(i)); + } else { + for (i = 0; i < 4; i++) + mask |= nv_rd32(dev, NV90_PDISPLAY_SOR_MODE_CTRL_C(i)); + } + + for (i = 0; i < 3; i++) + mask |= nv_rd32(dev, NV50_PDISPLAY_DAC_MODE_CTRL_C(i)); + + return mask & 3; +} + +static int +evo_icmd(struct drm_device *dev, int ch, u32 mthd, u32 data) +{ + int ret = 0; + nv_mask(dev, 0x610300 + (ch * 0x08), 0x00000001, 0x00000001); + nv_wr32(dev, 0x610304 + (ch * 0x08), data); + nv_wr32(dev, 0x610300 + (ch * 0x08), 0x80000001 | mthd); + if (!nv_wait(dev, 0x610300 + (ch * 0x08), 0x80000000, 0x00000000)) + ret = -EBUSY; + if (ret || (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO)) + NV_INFO(dev, "EvoPIO: %d 0x%04x 0x%08x\n", ch, mthd, data); + nv_mask(dev, 0x610300 + (ch * 0x08), 0x00000001, 0x00000000); + return ret; +} + +int +nv50_display_early_init(struct drm_device *dev) +{ + u32 ctrl = nv_rd32(dev, 0x610200); + int i; + + /* check if master evo channel is already active, a good a sign as any + * that the display engine is in a weird state (hibernate/kexec), if + * it is, do our best to reset the display engine... + */ + if ((ctrl & 0x00000003) == 0x00000003) { + NV_INFO(dev, "PDISP: EVO(0) 0x%08x, resetting...\n", ctrl); + + /* deactivate both heads first, PDISP will disappear forever + * (well, until you power cycle) on some boards as soon as + * PMC_ENABLE is hit unless they are.. + */ + for (i = 0; i < 2; i++) { + evo_icmd(dev, 0, 0x0880 + (i * 0x400), 0x05000000); + evo_icmd(dev, 0, 0x089c + (i * 0x400), 0); + evo_icmd(dev, 0, 0x0840 + (i * 0x400), 0); + evo_icmd(dev, 0, 0x0844 + (i * 0x400), 0); + evo_icmd(dev, 0, 0x085c + (i * 0x400), 0); + evo_icmd(dev, 0, 0x0874 + (i * 0x400), 0); + } + evo_icmd(dev, 0, 0x0080, 0); + + /* reset PDISP */ + nv_mask(dev, 0x000200, 0x40000000, 0x00000000); + nv_mask(dev, 0x000200, 0x40000000, 0x40000000); + } + + return 0; +} + +void +nv50_display_late_takedown(struct drm_device *dev) +{ +} + +int +nv50_display_sync(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + struct nv50_display *disp = nv50_display(dev); + struct nouveau_channel *evo = disp->master; + u64 start; + int ret; + + ret = RING_SPACE(evo, 6); + if (ret == 0) { + BEGIN_RING(evo, 0, 0x0084, 1); + OUT_RING (evo, 0x80000000); + BEGIN_RING(evo, 0, 0x0080, 1); + OUT_RING (evo, 0); + BEGIN_RING(evo, 0, 0x0084, 1); + OUT_RING (evo, 0x00000000); + + nv_wo32(disp->ntfy, 0x000, 0x00000000); + FIRE_RING (evo); + + start = ptimer->read(dev); + do { + if (nv_ro32(disp->ntfy, 0x000)) + return 0; + } while (ptimer->read(dev) - start < 2000000000ULL); + } + + return -EBUSY; +} + +int +nv50_display_init(struct drm_device *dev) +{ + struct nouveau_channel *evo; + int ret, i; + u32 val; + + NV_DEBUG_KMS(dev, "\n"); + + nv_wr32(dev, 0x00610184, nv_rd32(dev, 0x00614004)); + + /* + * I think the 0x006101XX range is some kind of main control area + * that enables things. + */ + /* CRTC? */ + for (i = 0; i < 2; i++) { + val = nv_rd32(dev, 0x00616100 + (i * 0x800)); + nv_wr32(dev, 0x00610190 + (i * 0x10), val); + val = nv_rd32(dev, 0x00616104 + (i * 0x800)); + nv_wr32(dev, 0x00610194 + (i * 0x10), val); + val = nv_rd32(dev, 0x00616108 + (i * 0x800)); + nv_wr32(dev, 0x00610198 + (i * 0x10), val); + val = nv_rd32(dev, 0x0061610c + (i * 0x800)); + nv_wr32(dev, 0x0061019c + (i * 0x10), val); + } + + /* DAC */ + for (i = 0; i < 3; i++) { + val = nv_rd32(dev, 0x0061a000 + (i * 0x800)); + nv_wr32(dev, 0x006101d0 + (i * 0x04), val); + } + + /* SOR */ + for (i = 0; i < nv50_sor_nr(dev); i++) { + val = nv_rd32(dev, 0x0061c000 + (i * 0x800)); + nv_wr32(dev, 0x006101e0 + (i * 0x04), val); + } + + /* EXT */ + for (i = 0; i < 3; i++) { + val = nv_rd32(dev, 0x0061e000 + (i * 0x800)); + nv_wr32(dev, 0x006101f0 + (i * 0x04), val); + } + + for (i = 0; i < 3; i++) { + nv_wr32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(i), 0x00550000 | + NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING); + nv_wr32(dev, NV50_PDISPLAY_DAC_CLK_CTRL1(i), 0x00000001); + } + + /* The precise purpose is unknown, i suspect it has something to do + * with text mode. + */ + if (nv_rd32(dev, NV50_PDISPLAY_INTR_1) & 0x100) { + nv_wr32(dev, NV50_PDISPLAY_INTR_1, 0x100); + nv_wr32(dev, 0x006194e8, nv_rd32(dev, 0x006194e8) & ~1); + if (!nv_wait(dev, 0x006194e8, 2, 0)) { + NV_ERROR(dev, "timeout: (0x6194e8 & 2) != 0\n"); + NV_ERROR(dev, "0x6194e8 = 0x%08x\n", + nv_rd32(dev, 0x6194e8)); + return -EBUSY; + } + } + + for (i = 0; i < 2; i++) { + nv_wr32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), 0x2000); + if (!nv_wait(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), + NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, 0)) { + NV_ERROR(dev, "timeout: CURSOR_CTRL2_STATUS == 0\n"); + NV_ERROR(dev, "CURSOR_CTRL2 = 0x%08x\n", + nv_rd32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i))); + return -EBUSY; + } + + nv_wr32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), + NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_ON); + if (!nv_wait(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), + NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, + NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_ACTIVE)) { + NV_ERROR(dev, "timeout: " + "CURSOR_CTRL2_STATUS_ACTIVE(%d)\n", i); + NV_ERROR(dev, "CURSOR_CTRL2(%d) = 0x%08x\n", i, + nv_rd32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i))); + return -EBUSY; + } + } + + nv_wr32(dev, NV50_PDISPLAY_PIO_CTRL, 0x00000000); + nv_mask(dev, NV50_PDISPLAY_INTR_0, 0x00000000, 0x00000000); + nv_wr32(dev, NV50_PDISPLAY_INTR_EN_0, 0x00000000); + nv_mask(dev, NV50_PDISPLAY_INTR_1, 0x00000000, 0x00000000); + nv_wr32(dev, NV50_PDISPLAY_INTR_EN_1, + NV50_PDISPLAY_INTR_EN_1_CLK_UNK10 | + NV50_PDISPLAY_INTR_EN_1_CLK_UNK20 | + NV50_PDISPLAY_INTR_EN_1_CLK_UNK40); + + ret = nv50_evo_init(dev); + if (ret) + return ret; + evo = nv50_display(dev)->master; + + nv_wr32(dev, NV50_PDISPLAY_OBJECTS, (evo->ramin->vinst >> 8) | 9); + + ret = RING_SPACE(evo, 3); + if (ret) + return ret; + BEGIN_RING(evo, 0, NV50_EVO_UNK84, 2); + OUT_RING (evo, NV50_EVO_UNK84_NOTIFY_DISABLED); + OUT_RING (evo, NvEvoSync); + + return nv50_display_sync(dev); +} + +void +nv50_display_fini(struct drm_device *dev) +{ + struct nv50_display *disp = nv50_display(dev); + struct nouveau_channel *evo = disp->master; + struct drm_crtc *drm_crtc; + int ret, i; + + NV_DEBUG_KMS(dev, "\n"); + + list_for_each_entry(drm_crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_crtc *crtc = nouveau_crtc(drm_crtc); + + nv50_crtc_blank(crtc, true); + } + + ret = RING_SPACE(evo, 2); + if (ret == 0) { + BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1); + OUT_RING(evo, 0); + } + FIRE_RING(evo); + + /* Almost like ack'ing a vblank interrupt, maybe in the spirit of + * cleaning up? + */ + list_for_each_entry(drm_crtc, &dev->mode_config.crtc_list, head) { + struct nouveau_crtc *crtc = nouveau_crtc(drm_crtc); + uint32_t mask = NV50_PDISPLAY_INTR_1_VBLANK_CRTC_(crtc->index); + + if (!crtc->base.enabled) + continue; + + nv_wr32(dev, NV50_PDISPLAY_INTR_1, mask); + if (!nv_wait(dev, NV50_PDISPLAY_INTR_1, mask, mask)) { + NV_ERROR(dev, "timeout: (0x610024 & 0x%08x) == " + "0x%08x\n", mask, mask); + NV_ERROR(dev, "0x610024 = 0x%08x\n", + nv_rd32(dev, NV50_PDISPLAY_INTR_1)); + } + } + + for (i = 0; i < 2; i++) { + nv_wr32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), 0); + if (!nv_wait(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), + NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, 0)) { + NV_ERROR(dev, "timeout: CURSOR_CTRL2_STATUS == 0\n"); + NV_ERROR(dev, "CURSOR_CTRL2 = 0x%08x\n", + nv_rd32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i))); + } + } + + nv50_evo_fini(dev); + + for (i = 0; i < 3; i++) { + if (!nv_wait(dev, NV50_PDISPLAY_SOR_DPMS_STATE(i), + NV50_PDISPLAY_SOR_DPMS_STATE_WAIT, 0)) { + NV_ERROR(dev, "timeout: SOR_DPMS_STATE_WAIT(%d) == 0\n", i); + NV_ERROR(dev, "SOR_DPMS_STATE(%d) = 0x%08x\n", i, + nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_STATE(i))); + } + } + + /* disable interrupts. */ + nv_wr32(dev, NV50_PDISPLAY_INTR_EN_1, 0x00000000); +} + +int +nv50_display_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct dcb_table *dcb = &dev_priv->vbios.dcb; + struct drm_connector *connector, *ct; + struct nv50_display *priv; + int ret, i; + + NV_DEBUG_KMS(dev, "\n"); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_priv->engine.display.priv = priv; + + /* Create CRTC objects */ + for (i = 0; i < 2; i++) + nv50_crtc_create(dev, i); + + /* We setup the encoders from the BIOS table */ + for (i = 0 ; i < dcb->entries; i++) { + struct dcb_entry *entry = &dcb->entry[i]; + + if (entry->location != DCB_LOC_ON_CHIP) { + NV_WARN(dev, "Off-chip encoder %d/%d unsupported\n", + entry->type, ffs(entry->or) - 1); + continue; + } + + connector = nouveau_connector_create(dev, entry->connector); + if (IS_ERR(connector)) + continue; + + switch (entry->type) { + case OUTPUT_TMDS: + case OUTPUT_LVDS: + case OUTPUT_DP: + nv50_sor_create(connector, entry); + break; + case OUTPUT_ANALOG: + nv50_dac_create(connector, entry); + break; + default: + NV_WARN(dev, "DCB encoder %d unknown\n", entry->type); + continue; + } + } + + list_for_each_entry_safe(connector, ct, + &dev->mode_config.connector_list, head) { + if (!connector->encoder_ids[0]) { + NV_WARN(dev, "%s has no encoders, removing\n", + drm_get_connector_name(connector)); + connector->funcs->destroy(connector); + } + } + + tasklet_init(&priv->tasklet, nv50_display_bh, (unsigned long)dev); + nouveau_irq_register(dev, 26, nv50_display_isr); + + ret = nv50_evo_create(dev); + if (ret) { + nv50_display_destroy(dev); + return ret; + } + + return 0; +} + +void +nv50_display_destroy(struct drm_device *dev) +{ + struct nv50_display *disp = nv50_display(dev); + + NV_DEBUG_KMS(dev, "\n"); + + nv50_evo_destroy(dev); + nouveau_irq_unregister(dev, 26); + kfree(disp); +} + +void +nv50_display_flip_stop(struct drm_crtc *crtc) +{ + struct nv50_display *disp = nv50_display(crtc->dev); + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nv50_display_crtc *dispc = &disp->crtc[nv_crtc->index]; + struct nouveau_channel *evo = dispc->sync; + int ret; + + ret = RING_SPACE(evo, 8); + if (ret) { + WARN_ON(1); + return; + } + + BEGIN_RING(evo, 0, 0x0084, 1); + OUT_RING (evo, 0x00000000); + BEGIN_RING(evo, 0, 0x0094, 1); + OUT_RING (evo, 0x00000000); + BEGIN_RING(evo, 0, 0x00c0, 1); + OUT_RING (evo, 0x00000000); + BEGIN_RING(evo, 0, 0x0080, 1); + OUT_RING (evo, 0x00000000); + FIRE_RING (evo); +} + +int +nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = crtc->dev->dev_private; + struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb); + struct nv50_display *disp = nv50_display(crtc->dev); + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nv50_display_crtc *dispc = &disp->crtc[nv_crtc->index]; + struct nouveau_channel *evo = dispc->sync; + int ret; + + ret = RING_SPACE(evo, chan ? 25 : 27); + if (unlikely(ret)) + return ret; + + /* synchronise with the rendering channel, if necessary */ + if (likely(chan)) { + ret = RING_SPACE(chan, 10); + if (ret) { + WIND_RING(evo); + return ret; + } + + if (dev_priv->chipset < 0xc0) { + BEGIN_RING(chan, 0, 0x0060, 2); + OUT_RING (chan, NvEvoSema0 + nv_crtc->index); + OUT_RING (chan, dispc->sem.offset); + BEGIN_RING(chan, 0, 0x006c, 1); + OUT_RING (chan, 0xf00d0000 | dispc->sem.value); + BEGIN_RING(chan, 0, 0x0064, 2); + OUT_RING (chan, dispc->sem.offset ^ 0x10); + OUT_RING (chan, 0x74b1e000); + BEGIN_RING(chan, 0, 0x0060, 1); + if (dev_priv->chipset < 0x84) + OUT_RING (chan, NvSema); + else + OUT_RING (chan, chan->vram_handle); + } else { + u64 offset = chan->dispc_vma[nv_crtc->index].offset; + offset += dispc->sem.offset; + BEGIN_NVC0(chan, 2, 0, 0x0010, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 0xf00d0000 | dispc->sem.value); + OUT_RING (chan, 0x1002); + BEGIN_NVC0(chan, 2, 0, 0x0010, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset ^ 0x10)); + OUT_RING (chan, 0x74b1e000); + OUT_RING (chan, 0x1001); + } + FIRE_RING (chan); + } else { + nouveau_bo_wr32(dispc->sem.bo, dispc->sem.offset / 4, + 0xf00d0000 | dispc->sem.value); + } + + /* queue the flip on the crtc's "display sync" channel */ + BEGIN_RING(evo, 0, 0x0100, 1); + OUT_RING (evo, 0xfffe0000); + if (chan) { + BEGIN_RING(evo, 0, 0x0084, 1); + OUT_RING (evo, 0x00000100); + } else { + BEGIN_RING(evo, 0, 0x0084, 1); + OUT_RING (evo, 0x00000010); + /* allows gamma somehow, PDISP will bitch at you if + * you don't wait for vblank before changing this.. + */ + BEGIN_RING(evo, 0, 0x00e0, 1); + OUT_RING (evo, 0x40000000); + } + BEGIN_RING(evo, 0, 0x0088, 4); + OUT_RING (evo, dispc->sem.offset); + OUT_RING (evo, 0xf00d0000 | dispc->sem.value); + OUT_RING (evo, 0x74b1e000); + OUT_RING (evo, NvEvoSync); + BEGIN_RING(evo, 0, 0x00a0, 2); + OUT_RING (evo, 0x00000000); + OUT_RING (evo, 0x00000000); + BEGIN_RING(evo, 0, 0x00c0, 1); + OUT_RING (evo, nv_fb->r_dma); + BEGIN_RING(evo, 0, 0x0110, 2); + OUT_RING (evo, 0x00000000); + OUT_RING (evo, 0x00000000); + BEGIN_RING(evo, 0, 0x0800, 5); + OUT_RING (evo, nv_fb->nvbo->bo.offset >> 8); + OUT_RING (evo, 0); + OUT_RING (evo, (fb->height << 16) | fb->width); + OUT_RING (evo, nv_fb->r_pitch); + OUT_RING (evo, nv_fb->r_format); + BEGIN_RING(evo, 0, 0x0080, 1); + OUT_RING (evo, 0x00000000); + FIRE_RING (evo); + + dispc->sem.offset ^= 0x10; + dispc->sem.value++; + return 0; +} + +static u16 +nv50_display_script_select(struct drm_device *dev, struct dcb_entry *dcb, + u32 mc, int pxclk) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_connector *nv_connector = NULL; + struct drm_encoder *encoder; + struct nvbios *bios = &dev_priv->vbios; + u32 script = 0, or; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + if (nv_encoder->dcb != dcb) + continue; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + break; + } + + or = ffs(dcb->or) - 1; + switch (dcb->type) { + case OUTPUT_LVDS: + script = (mc >> 8) & 0xf; + if (bios->fp_no_ddc) { + if (bios->fp.dual_link) + script |= 0x0100; + if (bios->fp.if_is_24bit) + script |= 0x0200; + } else { + /* determine number of lvds links */ + if (nv_connector && nv_connector->edid && + nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) { + /* http://www.spwg.org */ + if (((u8 *)nv_connector->edid)[121] == 2) + script |= 0x0100; + } else + if (pxclk >= bios->fp.duallink_transition_clk) { + script |= 0x0100; + } + + /* determine panel depth */ + if (script & 0x0100) { + if (bios->fp.strapless_is_24bit & 2) + script |= 0x0200; + } else { + if (bios->fp.strapless_is_24bit & 1) + script |= 0x0200; + } + + if (nv_connector && nv_connector->edid && + (nv_connector->edid->revision >= 4) && + (nv_connector->edid->input & 0x70) >= 0x20) + script |= 0x0200; + } + + if (nouveau_uscript_lvds >= 0) { + NV_INFO(dev, "override script 0x%04x with 0x%04x " + "for output LVDS-%d\n", script, + nouveau_uscript_lvds, or); + script = nouveau_uscript_lvds; + } + break; + case OUTPUT_TMDS: + script = (mc >> 8) & 0xf; + if (pxclk >= 165000) + script |= 0x0100; + + if (nouveau_uscript_tmds >= 0) { + NV_INFO(dev, "override script 0x%04x with 0x%04x " + "for output TMDS-%d\n", script, + nouveau_uscript_tmds, or); + script = nouveau_uscript_tmds; + } + break; + case OUTPUT_DP: + script = (mc >> 8) & 0xf; + break; + case OUTPUT_ANALOG: + script = 0xff; + break; + default: + NV_ERROR(dev, "modeset on unsupported output type!\n"); + break; + } + + return script; +} + +static void +nv50_display_vblank_crtc_handler(struct drm_device *dev, int crtc) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan, *tmp; + + list_for_each_entry_safe(chan, tmp, &dev_priv->vbl_waiting, + nvsw.vbl_wait) { + if (chan->nvsw.vblsem_head != crtc) + continue; + + nouveau_bo_wr32(chan->notifier_bo, chan->nvsw.vblsem_offset, + chan->nvsw.vblsem_rval); + list_del(&chan->nvsw.vbl_wait); + drm_vblank_put(dev, crtc); + } + + drm_handle_vblank(dev, crtc); +} + +static void +nv50_display_vblank_handler(struct drm_device *dev, uint32_t intr) +{ + if (intr & NV50_PDISPLAY_INTR_1_VBLANK_CRTC_0) + nv50_display_vblank_crtc_handler(dev, 0); + + if (intr & NV50_PDISPLAY_INTR_1_VBLANK_CRTC_1) + nv50_display_vblank_crtc_handler(dev, 1); + + nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_VBLANK_CRTC); +} + +static void +nv50_display_unk10_handler(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *disp = nv50_display(dev); + u32 unk30 = nv_rd32(dev, 0x610030), mc; + int i, crtc, or = 0, type = OUTPUT_ANY; + + NV_DEBUG_KMS(dev, "0x610030: 0x%08x\n", unk30); + disp->irq.dcb = NULL; + + nv_wr32(dev, 0x619494, nv_rd32(dev, 0x619494) & ~8); + + /* Determine which CRTC we're dealing with, only 1 ever will be + * signalled at the same time with the current nouveau code. + */ + crtc = ffs((unk30 & 0x00000060) >> 5) - 1; + if (crtc < 0) + goto ack; + + /* Nothing needs to be done for the encoder */ + crtc = ffs((unk30 & 0x00000180) >> 7) - 1; + if (crtc < 0) + goto ack; + + /* Find which encoder was connected to the CRTC */ + for (i = 0; type == OUTPUT_ANY && i < 3; i++) { + mc = nv_rd32(dev, NV50_PDISPLAY_DAC_MODE_CTRL_C(i)); + NV_DEBUG_KMS(dev, "DAC-%d mc: 0x%08x\n", i, mc); + if (!(mc & (1 << crtc))) + continue; + + switch ((mc & 0x00000f00) >> 8) { + case 0: type = OUTPUT_ANALOG; break; + case 1: type = OUTPUT_TV; break; + default: + NV_ERROR(dev, "invalid mc, DAC-%d: 0x%08x\n", i, mc); + goto ack; + } + + or = i; + } + + for (i = 0; type == OUTPUT_ANY && i < nv50_sor_nr(dev); i++) { + if (dev_priv->chipset < 0x90 || + dev_priv->chipset == 0x92 || + dev_priv->chipset == 0xa0) + mc = nv_rd32(dev, NV50_PDISPLAY_SOR_MODE_CTRL_C(i)); + else + mc = nv_rd32(dev, NV90_PDISPLAY_SOR_MODE_CTRL_C(i)); + + NV_DEBUG_KMS(dev, "SOR-%d mc: 0x%08x\n", i, mc); + if (!(mc & (1 << crtc))) + continue; + + switch ((mc & 0x00000f00) >> 8) { + case 0: type = OUTPUT_LVDS; break; + case 1: type = OUTPUT_TMDS; break; + case 2: type = OUTPUT_TMDS; break; + case 5: type = OUTPUT_TMDS; break; + case 8: type = OUTPUT_DP; break; + case 9: type = OUTPUT_DP; break; + default: + NV_ERROR(dev, "invalid mc, SOR-%d: 0x%08x\n", i, mc); + goto ack; + } + + or = i; + } + + /* There was no encoder to disable */ + if (type == OUTPUT_ANY) + goto ack; + + /* Disable the encoder */ + for (i = 0; i < dev_priv->vbios.dcb.entries; i++) { + struct dcb_entry *dcb = &dev_priv->vbios.dcb.entry[i]; + + if (dcb->type == type && (dcb->or & (1 << or))) { + nouveau_bios_run_display_table(dev, 0, -1, dcb, -1); + disp->irq.dcb = dcb; + goto ack; + } + } + + NV_ERROR(dev, "no dcb for %d %d 0x%08x\n", or, type, mc); +ack: + nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK10); + nv_wr32(dev, 0x610030, 0x80000000); +} + +static void +nv50_display_unk20_handler(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *disp = nv50_display(dev); + u32 unk30 = nv_rd32(dev, 0x610030), tmp, pclk, script, mc = 0; + struct dcb_entry *dcb; + int i, crtc, or = 0, type = OUTPUT_ANY; + + NV_DEBUG_KMS(dev, "0x610030: 0x%08x\n", unk30); + dcb = disp->irq.dcb; + if (dcb) { + nouveau_bios_run_display_table(dev, 0, -2, dcb, -1); + disp->irq.dcb = NULL; + } + + /* CRTC clock change requested? */ + crtc = ffs((unk30 & 0x00000600) >> 9) - 1; + if (crtc >= 0) { + pclk = nv_rd32(dev, NV50_PDISPLAY_CRTC_P(crtc, CLOCK)); + pclk &= 0x003fffff; + if (pclk) + nv50_crtc_set_clock(dev, crtc, pclk); + + tmp = nv_rd32(dev, NV50_PDISPLAY_CRTC_CLK_CTRL2(crtc)); + tmp &= ~0x000000f; + nv_wr32(dev, NV50_PDISPLAY_CRTC_CLK_CTRL2(crtc), tmp); + } + + /* Nothing needs to be done for the encoder */ + crtc = ffs((unk30 & 0x00000180) >> 7) - 1; + if (crtc < 0) + goto ack; + pclk = nv_rd32(dev, NV50_PDISPLAY_CRTC_P(crtc, CLOCK)) & 0x003fffff; + + /* Find which encoder is connected to the CRTC */ + for (i = 0; type == OUTPUT_ANY && i < 3; i++) { + mc = nv_rd32(dev, NV50_PDISPLAY_DAC_MODE_CTRL_P(i)); + NV_DEBUG_KMS(dev, "DAC-%d mc: 0x%08x\n", i, mc); + if (!(mc & (1 << crtc))) + continue; + + switch ((mc & 0x00000f00) >> 8) { + case 0: type = OUTPUT_ANALOG; break; + case 1: type = OUTPUT_TV; break; + default: + NV_ERROR(dev, "invalid mc, DAC-%d: 0x%08x\n", i, mc); + goto ack; + } + + or = i; + } + + for (i = 0; type == OUTPUT_ANY && i < nv50_sor_nr(dev); i++) { + if (dev_priv->chipset < 0x90 || + dev_priv->chipset == 0x92 || + dev_priv->chipset == 0xa0) + mc = nv_rd32(dev, NV50_PDISPLAY_SOR_MODE_CTRL_P(i)); + else + mc = nv_rd32(dev, NV90_PDISPLAY_SOR_MODE_CTRL_P(i)); + + NV_DEBUG_KMS(dev, "SOR-%d mc: 0x%08x\n", i, mc); + if (!(mc & (1 << crtc))) + continue; + + switch ((mc & 0x00000f00) >> 8) { + case 0: type = OUTPUT_LVDS; break; + case 1: type = OUTPUT_TMDS; break; + case 2: type = OUTPUT_TMDS; break; + case 5: type = OUTPUT_TMDS; break; + case 8: type = OUTPUT_DP; break; + case 9: type = OUTPUT_DP; break; + default: + NV_ERROR(dev, "invalid mc, SOR-%d: 0x%08x\n", i, mc); + goto ack; + } + + or = i; + } + + if (type == OUTPUT_ANY) + goto ack; + + /* Enable the encoder */ + for (i = 0; i < dev_priv->vbios.dcb.entries; i++) { + dcb = &dev_priv->vbios.dcb.entry[i]; + if (dcb->type == type && (dcb->or & (1 << or))) + break; + } + + if (i == dev_priv->vbios.dcb.entries) { + NV_ERROR(dev, "no dcb for %d %d 0x%08x\n", or, type, mc); + goto ack; + } + + script = nv50_display_script_select(dev, dcb, mc, pclk); + nouveau_bios_run_display_table(dev, script, pclk, dcb, -1); + + if (type == OUTPUT_DP) { + int link = !(dcb->dpconf.sor.link & 1); + if ((mc & 0x000f0000) == 0x00020000) + nv50_sor_dp_calc_tu(dev, or, link, pclk, 18); + else + nv50_sor_dp_calc_tu(dev, or, link, pclk, 24); + } + + if (dcb->type != OUTPUT_ANALOG) { + tmp = nv_rd32(dev, NV50_PDISPLAY_SOR_CLK_CTRL2(or)); + tmp &= ~0x00000f0f; + if (script & 0x0100) + tmp |= 0x00000101; + nv_wr32(dev, NV50_PDISPLAY_SOR_CLK_CTRL2(or), tmp); + } else { + nv_wr32(dev, NV50_PDISPLAY_DAC_CLK_CTRL2(or), 0); + } + + disp->irq.dcb = dcb; + disp->irq.pclk = pclk; + disp->irq.script = script; + +ack: + nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK20); + nv_wr32(dev, 0x610030, 0x80000000); +} + +/* If programming a TMDS output on a SOR that can also be configured for + * DisplayPort, make sure NV50_SOR_DP_CTRL_ENABLE is forced off. + * + * It looks like the VBIOS TMDS scripts make an attempt at this, however, + * the VBIOS scripts on at least one board I have only switch it off on + * link 0, causing a blank display if the output has previously been + * programmed for DisplayPort. + */ +static void +nv50_display_unk40_dp_set_tmds(struct drm_device *dev, struct dcb_entry *dcb) +{ + int or = ffs(dcb->or) - 1, link = !(dcb->dpconf.sor.link & 1); + struct drm_encoder *encoder; + u32 tmp; + + if (dcb->type != OUTPUT_TMDS) + return; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + if (nv_encoder->dcb->type == OUTPUT_DP && + nv_encoder->dcb->or & (1 << or)) { + tmp = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)); + tmp &= ~NV50_SOR_DP_CTRL_ENABLED; + nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), tmp); + break; + } + } +} + +static void +nv50_display_unk40_handler(struct drm_device *dev) +{ + struct nv50_display *disp = nv50_display(dev); + struct dcb_entry *dcb = disp->irq.dcb; + u16 script = disp->irq.script; + u32 unk30 = nv_rd32(dev, 0x610030), pclk = disp->irq.pclk; + + NV_DEBUG_KMS(dev, "0x610030: 0x%08x\n", unk30); + disp->irq.dcb = NULL; + if (!dcb) + goto ack; + + nouveau_bios_run_display_table(dev, script, -pclk, dcb, -1); + nv50_display_unk40_dp_set_tmds(dev, dcb); + +ack: + nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK40); + nv_wr32(dev, 0x610030, 0x80000000); + nv_wr32(dev, 0x619494, nv_rd32(dev, 0x619494) | 8); +} + +static void +nv50_display_bh(unsigned long data) +{ + struct drm_device *dev = (struct drm_device *)data; + + for (;;) { + uint32_t intr0 = nv_rd32(dev, NV50_PDISPLAY_INTR_0); + uint32_t intr1 = nv_rd32(dev, NV50_PDISPLAY_INTR_1); + + NV_DEBUG_KMS(dev, "PDISPLAY_INTR_BH 0x%08x 0x%08x\n", intr0, intr1); + + if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK10) + nv50_display_unk10_handler(dev); + else + if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK20) + nv50_display_unk20_handler(dev); + else + if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK40) + nv50_display_unk40_handler(dev); + else + break; + } + + nv_wr32(dev, NV03_PMC_INTR_EN_0, 1); +} + +static void +nv50_display_error_handler(struct drm_device *dev) +{ + u32 channels = (nv_rd32(dev, NV50_PDISPLAY_INTR_0) & 0x001f0000) >> 16; + u32 addr, data; + int chid; + + for (chid = 0; chid < 5; chid++) { + if (!(channels & (1 << chid))) + continue; + + nv_wr32(dev, NV50_PDISPLAY_INTR_0, 0x00010000 << chid); + addr = nv_rd32(dev, NV50_PDISPLAY_TRAPPED_ADDR(chid)); + data = nv_rd32(dev, NV50_PDISPLAY_TRAPPED_DATA(chid)); + NV_ERROR(dev, "EvoCh %d Mthd 0x%04x Data 0x%08x " + "(0x%04x 0x%02x)\n", chid, + addr & 0xffc, data, addr >> 16, (addr >> 12) & 0xf); + + nv_wr32(dev, NV50_PDISPLAY_TRAPPED_ADDR(chid), 0x90000000); + } +} + +static void +nv50_display_isr(struct drm_device *dev) +{ + struct nv50_display *disp = nv50_display(dev); + uint32_t delayed = 0; + + while (nv_rd32(dev, NV50_PMC_INTR_0) & NV50_PMC_INTR_0_DISPLAY) { + uint32_t intr0 = nv_rd32(dev, NV50_PDISPLAY_INTR_0); + uint32_t intr1 = nv_rd32(dev, NV50_PDISPLAY_INTR_1); + uint32_t clock; + + NV_DEBUG_KMS(dev, "PDISPLAY_INTR 0x%08x 0x%08x\n", intr0, intr1); + + if (!intr0 && !(intr1 & ~delayed)) + break; + + if (intr0 & 0x001f0000) { + nv50_display_error_handler(dev); + intr0 &= ~0x001f0000; + } + + if (intr1 & NV50_PDISPLAY_INTR_1_VBLANK_CRTC) { + nv50_display_vblank_handler(dev, intr1); + intr1 &= ~NV50_PDISPLAY_INTR_1_VBLANK_CRTC; + } + + clock = (intr1 & (NV50_PDISPLAY_INTR_1_CLK_UNK10 | + NV50_PDISPLAY_INTR_1_CLK_UNK20 | + NV50_PDISPLAY_INTR_1_CLK_UNK40)); + if (clock) { + nv_wr32(dev, NV03_PMC_INTR_EN_0, 0); + tasklet_schedule(&disp->tasklet); + delayed |= clock; + intr1 &= ~clock; + } + + if (intr0) { + NV_ERROR(dev, "unknown PDISPLAY_INTR_0: 0x%08x\n", intr0); + nv_wr32(dev, NV50_PDISPLAY_INTR_0, intr0); + } + + if (intr1) { + NV_ERROR(dev, + "unknown PDISPLAY_INTR_1: 0x%08x\n", intr1); + nv_wr32(dev, NV50_PDISPLAY_INTR_1, intr1); + } + } +} diff --git a/drivers/gpu/drm/nouveau/nv50_display.h b/drivers/gpu/drm/nouveau/nv50_display.h new file mode 100644 index 00000000..5d3dd14d --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_display.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_DISPLAY_H__ +#define __NV50_DISPLAY_H__ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_reg.h" +#include "nouveau_crtc.h" +#include "nv50_evo.h" + +struct nv50_display_crtc { + struct nouveau_channel *sync; + struct { + struct nouveau_bo *bo; + u32 offset; + u16 value; + } sem; +}; + +struct nv50_display { + struct nouveau_channel *master; + struct nouveau_gpuobj *ntfy; + + struct nv50_display_crtc crtc[2]; + + struct tasklet_struct tasklet; + struct { + struct dcb_entry *dcb; + u16 script; + u32 pclk; + } irq; +}; + +static inline struct nv50_display * +nv50_display(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return dev_priv->engine.display.priv; +} + +int nv50_display_early_init(struct drm_device *dev); +void nv50_display_late_takedown(struct drm_device *dev); +int nv50_display_create(struct drm_device *dev); +int nv50_display_init(struct drm_device *dev); +void nv50_display_fini(struct drm_device *dev); +void nv50_display_destroy(struct drm_device *dev); +int nv50_crtc_blank(struct nouveau_crtc *, bool blank); +int nv50_crtc_set_clock(struct drm_device *, int head, int pclk); + +u32 nv50_display_active_crtcs(struct drm_device *); + +int nv50_display_sync(struct drm_device *); +int nv50_display_flip_next(struct drm_crtc *, struct drm_framebuffer *, + struct nouveau_channel *chan); +void nv50_display_flip_stop(struct drm_crtc *); + +int nv50_evo_create(struct drm_device *dev); +void nv50_evo_destroy(struct drm_device *dev); +int nv50_evo_init(struct drm_device *dev); +void nv50_evo_fini(struct drm_device *dev); +void nv50_evo_dmaobj_init(struct nouveau_gpuobj *, u32 memtype, u64 base, + u64 size); +int nv50_evo_dmaobj_new(struct nouveau_channel *, u32 handle, u32 memtype, + u64 base, u64 size, struct nouveau_gpuobj **); + +#endif /* __NV50_DISPLAY_H__ */ diff --git a/drivers/gpu/drm/nouveau/nv50_evo.c b/drivers/gpu/drm/nouveau/nv50_evo.c new file mode 100644 index 00000000..9b962e98 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_evo.c @@ -0,0 +1,417 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_ramht.h" +#include "nv50_display.h" + +static void +nv50_evo_channel_del(struct nouveau_channel **pevo) +{ + struct nouveau_channel *evo = *pevo; + + if (!evo) + return; + *pevo = NULL; + + nouveau_ramht_ref(NULL, &evo->ramht, evo); + nouveau_gpuobj_channel_takedown(evo); + nouveau_bo_unmap(evo->pushbuf_bo); + nouveau_bo_ref(NULL, &evo->pushbuf_bo); + + if (evo->user) + iounmap(evo->user); + + kfree(evo); +} + +void +nv50_evo_dmaobj_init(struct nouveau_gpuobj *obj, u32 memtype, u64 base, u64 size) +{ + struct drm_nouveau_private *dev_priv = obj->dev->dev_private; + u32 flags5; + + if (dev_priv->chipset < 0xc0) { + /* not supported on 0x50, specified in format mthd */ + if (dev_priv->chipset == 0x50) + memtype = 0; + flags5 = 0x00010000; + } else { + if (memtype & 0x80000000) + flags5 = 0x00000000; /* large pages */ + else + flags5 = 0x00020000; + } + + nv50_gpuobj_dma_init(obj, 0, 0x3d, base, size, NV_MEM_TARGET_VRAM, + NV_MEM_ACCESS_RW, (memtype >> 8) & 0xff, 0); + nv_wo32(obj, 0x14, flags5); + dev_priv->engine.instmem.flush(obj->dev); +} + +int +nv50_evo_dmaobj_new(struct nouveau_channel *evo, u32 handle, u32 memtype, + u64 base, u64 size, struct nouveau_gpuobj **pobj) +{ + struct nv50_display *disp = nv50_display(evo->dev); + struct nouveau_gpuobj *obj = NULL; + int ret; + + ret = nouveau_gpuobj_new(evo->dev, disp->master, 6*4, 32, 0, &obj); + if (ret) + return ret; + obj->engine = NVOBJ_ENGINE_DISPLAY; + + nv50_evo_dmaobj_init(obj, memtype, base, size); + + ret = nouveau_ramht_insert(evo, handle, obj); + if (ret) + goto out; + + if (pobj) + nouveau_gpuobj_ref(obj, pobj); +out: + nouveau_gpuobj_ref(NULL, &obj); + return ret; +} + +static int +nv50_evo_channel_new(struct drm_device *dev, int chid, + struct nouveau_channel **pevo) +{ + struct nv50_display *disp = nv50_display(dev); + struct nouveau_channel *evo; + int ret; + + evo = kzalloc(sizeof(struct nouveau_channel), GFP_KERNEL); + if (!evo) + return -ENOMEM; + *pevo = evo; + + evo->id = chid; + evo->dev = dev; + evo->user_get = 4; + evo->user_put = 0; + + ret = nouveau_bo_new(dev, 4096, 0, TTM_PL_FLAG_VRAM, 0, 0, + &evo->pushbuf_bo); + if (ret == 0) + ret = nouveau_bo_pin(evo->pushbuf_bo, TTM_PL_FLAG_VRAM); + if (ret) { + NV_ERROR(dev, "Error creating EVO DMA push buffer: %d\n", ret); + nv50_evo_channel_del(pevo); + return ret; + } + + ret = nouveau_bo_map(evo->pushbuf_bo); + if (ret) { + NV_ERROR(dev, "Error mapping EVO DMA push buffer: %d\n", ret); + nv50_evo_channel_del(pevo); + return ret; + } + + evo->user = ioremap(pci_resource_start(dev->pdev, 0) + + NV50_PDISPLAY_USER(evo->id), PAGE_SIZE); + if (!evo->user) { + NV_ERROR(dev, "Error mapping EVO control regs.\n"); + nv50_evo_channel_del(pevo); + return -ENOMEM; + } + + /* bind primary evo channel's ramht to the channel */ + if (disp->master && evo != disp->master) + nouveau_ramht_ref(disp->master->ramht, &evo->ramht, NULL); + + return 0; +} + +static int +nv50_evo_channel_init(struct nouveau_channel *evo) +{ + struct drm_device *dev = evo->dev; + int id = evo->id, ret, i; + u64 pushbuf = evo->pushbuf_bo->bo.offset; + u32 tmp; + + tmp = nv_rd32(dev, NV50_PDISPLAY_EVO_CTRL(id)); + if ((tmp & 0x009f0000) == 0x00020000) + nv_wr32(dev, NV50_PDISPLAY_EVO_CTRL(id), tmp | 0x00800000); + + tmp = nv_rd32(dev, NV50_PDISPLAY_EVO_CTRL(id)); + if ((tmp & 0x003f0000) == 0x00030000) + nv_wr32(dev, NV50_PDISPLAY_EVO_CTRL(id), tmp | 0x00600000); + + /* initialise fifo */ + nv_wr32(dev, NV50_PDISPLAY_EVO_DMA_CB(id), pushbuf >> 8 | + NV50_PDISPLAY_EVO_DMA_CB_LOCATION_VRAM | + NV50_PDISPLAY_EVO_DMA_CB_VALID); + nv_wr32(dev, NV50_PDISPLAY_EVO_UNK2(id), 0x00010000); + nv_wr32(dev, NV50_PDISPLAY_EVO_HASH_TAG(id), id); + nv_mask(dev, NV50_PDISPLAY_EVO_CTRL(id), NV50_PDISPLAY_EVO_CTRL_DMA, + NV50_PDISPLAY_EVO_CTRL_DMA_ENABLED); + + nv_wr32(dev, NV50_PDISPLAY_USER_PUT(id), 0x00000000); + nv_wr32(dev, NV50_PDISPLAY_EVO_CTRL(id), 0x01000003 | + NV50_PDISPLAY_EVO_CTRL_DMA_ENABLED); + if (!nv_wait(dev, NV50_PDISPLAY_EVO_CTRL(id), 0x80000000, 0x00000000)) { + NV_ERROR(dev, "EvoCh %d init timeout: 0x%08x\n", id, + nv_rd32(dev, NV50_PDISPLAY_EVO_CTRL(id))); + return -EBUSY; + } + + /* enable error reporting on the channel */ + nv_mask(dev, 0x610028, 0x00000000, 0x00010001 << id); + + evo->dma.max = (4096/4) - 2; + evo->dma.max &= ~7; + evo->dma.put = 0; + evo->dma.cur = evo->dma.put; + evo->dma.free = evo->dma.max - evo->dma.cur; + + ret = RING_SPACE(evo, NOUVEAU_DMA_SKIPS); + if (ret) + return ret; + + for (i = 0; i < NOUVEAU_DMA_SKIPS; i++) + OUT_RING(evo, 0); + + return 0; +} + +static void +nv50_evo_channel_fini(struct nouveau_channel *evo) +{ + struct drm_device *dev = evo->dev; + int id = evo->id; + + nv_mask(dev, 0x610028, 0x00010001 << id, 0x00000000); + nv_mask(dev, NV50_PDISPLAY_EVO_CTRL(id), 0x00001010, 0x00001000); + nv_wr32(dev, NV50_PDISPLAY_INTR_0, (1 << id)); + nv_mask(dev, NV50_PDISPLAY_EVO_CTRL(id), 0x00000003, 0x00000000); + if (!nv_wait(dev, NV50_PDISPLAY_EVO_CTRL(id), 0x001e0000, 0x00000000)) { + NV_ERROR(dev, "EvoCh %d takedown timeout: 0x%08x\n", id, + nv_rd32(dev, NV50_PDISPLAY_EVO_CTRL(id))); + } +} + +void +nv50_evo_destroy(struct drm_device *dev) +{ + struct nv50_display *disp = nv50_display(dev); + int i; + + for (i = 0; i < 2; i++) { + if (disp->crtc[i].sem.bo) { + nouveau_bo_unmap(disp->crtc[i].sem.bo); + nouveau_bo_ref(NULL, &disp->crtc[i].sem.bo); + } + nv50_evo_channel_del(&disp->crtc[i].sync); + } + nouveau_gpuobj_ref(NULL, &disp->ntfy); + nv50_evo_channel_del(&disp->master); +} + +int +nv50_evo_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_display *disp = nv50_display(dev); + struct nouveau_gpuobj *ramht = NULL; + struct nouveau_channel *evo; + int ret, i, j; + + /* create primary evo channel, the one we use for modesetting + * purporses + */ + ret = nv50_evo_channel_new(dev, 0, &disp->master); + if (ret) + return ret; + evo = disp->master; + + /* setup object management on it, any other evo channel will + * use this also as there's no per-channel support on the + * hardware + */ + ret = nouveau_gpuobj_new(dev, NULL, 32768, 65536, + NVOBJ_FLAG_ZERO_ALLOC, &evo->ramin); + if (ret) { + NV_ERROR(dev, "Error allocating EVO channel memory: %d\n", ret); + goto err; + } + + ret = drm_mm_init(&evo->ramin_heap, 0, 32768); + if (ret) { + NV_ERROR(dev, "Error initialising EVO PRAMIN heap: %d\n", ret); + goto err; + } + + ret = nouveau_gpuobj_new(dev, evo, 4096, 16, 0, &ramht); + if (ret) { + NV_ERROR(dev, "Unable to allocate EVO RAMHT: %d\n", ret); + goto err; + } + + ret = nouveau_ramht_new(dev, ramht, &evo->ramht); + nouveau_gpuobj_ref(NULL, &ramht); + if (ret) + goto err; + + /* not sure exactly what this is.. + * + * the first dword of the structure is used by nvidia to wait on + * full completion of an EVO "update" command. + * + * method 0x8c on the master evo channel will fill a lot more of + * this structure with some undefined info + */ + ret = nouveau_gpuobj_new(dev, disp->master, 0x1000, 0, + NVOBJ_FLAG_ZERO_ALLOC, &disp->ntfy); + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(disp->master, NvEvoSync, 0x0000, + disp->ntfy->vinst, disp->ntfy->size, NULL); + if (ret) + goto err; + + /* create some default objects for the scanout memtypes we support */ + ret = nv50_evo_dmaobj_new(disp->master, NvEvoVRAM, 0x0000, + 0, dev_priv->vram_size, NULL); + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(disp->master, NvEvoVRAM_LP, 0x80000000, + 0, dev_priv->vram_size, NULL); + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(disp->master, NvEvoFB32, 0x80000000 | + (dev_priv->chipset < 0xc0 ? 0x7a00 : 0xfe00), + 0, dev_priv->vram_size, NULL); + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(disp->master, NvEvoFB16, 0x80000000 | + (dev_priv->chipset < 0xc0 ? 0x7000 : 0xfe00), + 0, dev_priv->vram_size, NULL); + if (ret) + goto err; + + /* create "display sync" channels and other structures we need + * to implement page flipping + */ + for (i = 0; i < 2; i++) { + struct nv50_display_crtc *dispc = &disp->crtc[i]; + u64 offset; + + ret = nv50_evo_channel_new(dev, 1 + i, &dispc->sync); + if (ret) + goto err; + + ret = nouveau_bo_new(dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, + 0, 0x0000, &dispc->sem.bo); + if (!ret) { + ret = nouveau_bo_pin(dispc->sem.bo, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(dispc->sem.bo); + if (ret) + nouveau_bo_ref(NULL, &dispc->sem.bo); + offset = dispc->sem.bo->bo.offset; + } + + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(dispc->sync, NvEvoSync, 0x0000, + offset, 4096, NULL); + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(dispc->sync, NvEvoVRAM_LP, 0x80000000, + 0, dev_priv->vram_size, NULL); + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(dispc->sync, NvEvoFB32, 0x80000000 | + (dev_priv->chipset < 0xc0 ? + 0x7a00 : 0xfe00), + 0, dev_priv->vram_size, NULL); + if (ret) + goto err; + + ret = nv50_evo_dmaobj_new(dispc->sync, NvEvoFB16, 0x80000000 | + (dev_priv->chipset < 0xc0 ? + 0x7000 : 0xfe00), + 0, dev_priv->vram_size, NULL); + if (ret) + goto err; + + for (j = 0; j < 4096; j += 4) + nouveau_bo_wr32(dispc->sem.bo, j / 4, 0x74b1e000); + dispc->sem.offset = 0; + } + + return 0; + +err: + nv50_evo_destroy(dev); + return ret; +} + +int +nv50_evo_init(struct drm_device *dev) +{ + struct nv50_display *disp = nv50_display(dev); + int ret, i; + + ret = nv50_evo_channel_init(disp->master); + if (ret) + return ret; + + for (i = 0; i < 2; i++) { + ret = nv50_evo_channel_init(disp->crtc[i].sync); + if (ret) + return ret; + } + + return 0; +} + +void +nv50_evo_fini(struct drm_device *dev) +{ + struct nv50_display *disp = nv50_display(dev); + int i; + + for (i = 0; i < 2; i++) { + if (disp->crtc[i].sync) + nv50_evo_channel_fini(disp->crtc[i].sync); + } + + if (disp->master) + nv50_evo_channel_fini(disp->master); +} diff --git a/drivers/gpu/drm/nouveau/nv50_evo.h b/drivers/gpu/drm/nouveau/nv50_evo.h new file mode 100644 index 00000000..771d879b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_evo.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __NV50_EVO_H__ +#define __NV50_EVO_H__ + +#define NV50_EVO_UPDATE 0x00000080 +#define NV50_EVO_UNK84 0x00000084 +#define NV50_EVO_UNK84_NOTIFY 0x40000000 +#define NV50_EVO_UNK84_NOTIFY_DISABLED 0x00000000 +#define NV50_EVO_UNK84_NOTIFY_ENABLED 0x40000000 +#define NV50_EVO_DMA_NOTIFY 0x00000088 +#define NV50_EVO_DMA_NOTIFY_HANDLE 0xffffffff +#define NV50_EVO_DMA_NOTIFY_HANDLE_NONE 0x00000000 +#define NV50_EVO_UNK8C 0x0000008C + +#define NV50_EVO_DAC(n, r) ((n) * 0x80 + NV50_EVO_DAC_##r) +#define NV50_EVO_DAC_MODE_CTRL 0x00000400 +#define NV50_EVO_DAC_MODE_CTRL_CRTC0 0x00000001 +#define NV50_EVO_DAC_MODE_CTRL_CRTC1 0x00000002 +#define NV50_EVO_DAC_MODE_CTRL2 0x00000404 +#define NV50_EVO_DAC_MODE_CTRL2_NHSYNC 0x00000001 +#define NV50_EVO_DAC_MODE_CTRL2_NVSYNC 0x00000002 + +#define NV50_EVO_SOR(n, r) ((n) * 0x40 + NV50_EVO_SOR_##r) +#define NV50_EVO_SOR_MODE_CTRL 0x00000600 +#define NV50_EVO_SOR_MODE_CTRL_CRTC0 0x00000001 +#define NV50_EVO_SOR_MODE_CTRL_CRTC1 0x00000002 +#define NV50_EVO_SOR_MODE_CTRL_TMDS 0x00000100 +#define NV50_EVO_SOR_MODE_CTRL_TMDS_DUAL_LINK 0x00000400 +#define NV50_EVO_SOR_MODE_CTRL_NHSYNC 0x00001000 +#define NV50_EVO_SOR_MODE_CTRL_NVSYNC 0x00002000 + +#define NV50_EVO_CRTC(n, r) ((n) * 0x400 + NV50_EVO_CRTC_##r) +#define NV84_EVO_CRTC(n, r) ((n) * 0x400 + NV84_EVO_CRTC_##r) +#define NV50_EVO_CRTC_UNK0800 0x00000800 +#define NV50_EVO_CRTC_CLOCK 0x00000804 +#define NV50_EVO_CRTC_INTERLACE 0x00000808 +#define NV50_EVO_CRTC_DISPLAY_START 0x00000810 +#define NV50_EVO_CRTC_DISPLAY_TOTAL 0x00000814 +#define NV50_EVO_CRTC_SYNC_DURATION 0x00000818 +#define NV50_EVO_CRTC_SYNC_START_TO_BLANK_END 0x0000081c +#define NV50_EVO_CRTC_UNK0820 0x00000820 +#define NV50_EVO_CRTC_UNK0824 0x00000824 +#define NV50_EVO_CRTC_UNK082C 0x0000082c +#define NV50_EVO_CRTC_CLUT_MODE 0x00000840 +/* You can't have a palette in 8 bit mode (=OFF) */ +#define NV50_EVO_CRTC_CLUT_MODE_BLANK 0x00000000 +#define NV50_EVO_CRTC_CLUT_MODE_OFF 0x80000000 +#define NV50_EVO_CRTC_CLUT_MODE_ON 0xC0000000 +#define NV50_EVO_CRTC_CLUT_OFFSET 0x00000844 +#define NV84_EVO_CRTC_CLUT_DMA 0x0000085C +#define NV84_EVO_CRTC_CLUT_DMA_HANDLE 0xffffffff +#define NV84_EVO_CRTC_CLUT_DMA_HANDLE_NONE 0x00000000 +#define NV50_EVO_CRTC_FB_OFFSET 0x00000860 +#define NV50_EVO_CRTC_FB_SIZE 0x00000868 +#define NV50_EVO_CRTC_FB_CONFIG 0x0000086c +#define NV50_EVO_CRTC_FB_CONFIG_MODE 0x00100000 +#define NV50_EVO_CRTC_FB_CONFIG_MODE_TILE 0x00000000 +#define NV50_EVO_CRTC_FB_CONFIG_MODE_PITCH 0x00100000 +#define NV50_EVO_CRTC_FB_DEPTH 0x00000870 +#define NV50_EVO_CRTC_FB_DEPTH_8 0x00001e00 +#define NV50_EVO_CRTC_FB_DEPTH_15 0x0000e900 +#define NV50_EVO_CRTC_FB_DEPTH_16 0x0000e800 +#define NV50_EVO_CRTC_FB_DEPTH_24 0x0000cf00 +#define NV50_EVO_CRTC_FB_DEPTH_30 0x0000d100 +#define NV50_EVO_CRTC_FB_DMA 0x00000874 +#define NV50_EVO_CRTC_FB_DMA_HANDLE 0xffffffff +#define NV50_EVO_CRTC_FB_DMA_HANDLE_NONE 0x00000000 +#define NV50_EVO_CRTC_CURSOR_CTRL 0x00000880 +#define NV50_EVO_CRTC_CURSOR_CTRL_HIDE 0x05000000 +#define NV50_EVO_CRTC_CURSOR_CTRL_SHOW 0x85000000 +#define NV50_EVO_CRTC_CURSOR_OFFSET 0x00000884 +#define NV84_EVO_CRTC_CURSOR_DMA 0x0000089c +#define NV84_EVO_CRTC_CURSOR_DMA_HANDLE 0xffffffff +#define NV84_EVO_CRTC_CURSOR_DMA_HANDLE_NONE 0x00000000 +#define NV50_EVO_CRTC_DITHER_CTRL 0x000008a0 +#define NV50_EVO_CRTC_DITHER_CTRL_OFF 0x00000000 +#define NV50_EVO_CRTC_DITHER_CTRL_ON 0x00000011 +#define NV50_EVO_CRTC_SCALE_CTRL 0x000008a4 +#define NV50_EVO_CRTC_SCALE_CTRL_INACTIVE 0x00000000 +#define NV50_EVO_CRTC_SCALE_CTRL_ACTIVE 0x00000009 +#define NV50_EVO_CRTC_COLOR_CTRL 0x000008a8 +#define NV50_EVO_CRTC_COLOR_CTRL_VIBRANCE 0x000fff00 +#define NV50_EVO_CRTC_COLOR_CTRL_HUE 0xfff00000 +#define NV50_EVO_CRTC_FB_POS 0x000008c0 +#define NV50_EVO_CRTC_REAL_RES 0x000008c8 +#define NV50_EVO_CRTC_SCALE_CENTER_OFFSET 0x000008d4 +#define NV50_EVO_CRTC_SCALE_CENTER_OFFSET_VAL(x, y) \ + ((((unsigned)y << 16) & 0xFFFF0000) | (((unsigned)x) & 0x0000FFFF)) +/* Both of these are needed, otherwise nothing happens. */ +#define NV50_EVO_CRTC_SCALE_RES1 0x000008d8 +#define NV50_EVO_CRTC_SCALE_RES2 0x000008dc +#define NV50_EVO_CRTC_UNK900 0x00000900 +#define NV50_EVO_CRTC_UNK904 0x00000904 + +#endif diff --git a/drivers/gpu/drm/nouveau/nv50_fb.c b/drivers/gpu/drm/nouveau/nv50_fb.c new file mode 100644 index 00000000..bdd2afe2 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_fb.c @@ -0,0 +1,294 @@ +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +struct nv50_fb_priv { + struct page *r100c08_page; + dma_addr_t r100c08; +}; + +static void +nv50_fb_destroy(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + struct nv50_fb_priv *priv = pfb->priv; + + if (drm_mm_initialized(&pfb->tag_heap)) + drm_mm_takedown(&pfb->tag_heap); + + if (priv->r100c08_page) { + pci_unmap_page(dev->pdev, priv->r100c08, PAGE_SIZE, + PCI_DMA_BIDIRECTIONAL); + __free_page(priv->r100c08_page); + } + + kfree(priv); + pfb->priv = NULL; +} + +static int +nv50_fb_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + struct nv50_fb_priv *priv; + u32 tagmem; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + pfb->priv = priv; + + priv->r100c08_page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!priv->r100c08_page) { + nv50_fb_destroy(dev); + return -ENOMEM; + } + + priv->r100c08 = pci_map_page(dev->pdev, priv->r100c08_page, 0, + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(dev->pdev, priv->r100c08)) { + nv50_fb_destroy(dev); + return -EFAULT; + } + + tagmem = nv_rd32(dev, 0x100320); + NV_DEBUG(dev, "%d tags available\n", tagmem); + ret = drm_mm_init(&pfb->tag_heap, 0, tagmem); + if (ret) { + nv50_fb_destroy(dev); + return ret; + } + + return 0; +} + +int +nv50_fb_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_fb_priv *priv; + int ret; + + if (!dev_priv->engine.fb.priv) { + ret = nv50_fb_create(dev); + if (ret) + return ret; + } + priv = dev_priv->engine.fb.priv; + + /* Not a clue what this is exactly. Without pointing it at a + * scratch page, VRAM->GART blits with M2MF (as in DDX DFS) + * cause IOMMU "read from address 0" errors (rh#561267) + */ + nv_wr32(dev, 0x100c08, priv->r100c08 >> 8); + + /* This is needed to get meaningful information from 100c90 + * on traps. No idea what these values mean exactly. */ + switch (dev_priv->chipset) { + case 0x50: + nv_wr32(dev, 0x100c90, 0x000707ff); + break; + case 0xa3: + case 0xa5: + case 0xa8: + nv_wr32(dev, 0x100c90, 0x000d0fff); + break; + case 0xaf: + nv_wr32(dev, 0x100c90, 0x089d1fff); + break; + default: + nv_wr32(dev, 0x100c90, 0x001d07ff); + break; + } + + return 0; +} + +void +nv50_fb_takedown(struct drm_device *dev) +{ + nv50_fb_destroy(dev); +} + +static struct nouveau_enum vm_dispatch_subclients[] = { + { 0x00000000, "GRCTX", NULL }, + { 0x00000001, "NOTIFY", NULL }, + { 0x00000002, "QUERY", NULL }, + { 0x00000003, "COND", NULL }, + { 0x00000004, "M2M_IN", NULL }, + { 0x00000005, "M2M_OUT", NULL }, + { 0x00000006, "M2M_NOTIFY", NULL }, + {} +}; + +static struct nouveau_enum vm_ccache_subclients[] = { + { 0x00000000, "CB", NULL }, + { 0x00000001, "TIC", NULL }, + { 0x00000002, "TSC", NULL }, + {} +}; + +static struct nouveau_enum vm_prop_subclients[] = { + { 0x00000000, "RT0", NULL }, + { 0x00000001, "RT1", NULL }, + { 0x00000002, "RT2", NULL }, + { 0x00000003, "RT3", NULL }, + { 0x00000004, "RT4", NULL }, + { 0x00000005, "RT5", NULL }, + { 0x00000006, "RT6", NULL }, + { 0x00000007, "RT7", NULL }, + { 0x00000008, "ZETA", NULL }, + { 0x00000009, "LOCAL", NULL }, + { 0x0000000a, "GLOBAL", NULL }, + { 0x0000000b, "STACK", NULL }, + { 0x0000000c, "DST2D", NULL }, + {} +}; + +static struct nouveau_enum vm_pfifo_subclients[] = { + { 0x00000000, "PUSHBUF", NULL }, + { 0x00000001, "SEMAPHORE", NULL }, + {} +}; + +static struct nouveau_enum vm_bar_subclients[] = { + { 0x00000000, "FB", NULL }, + { 0x00000001, "IN", NULL }, + {} +}; + +static struct nouveau_enum vm_client[] = { + { 0x00000000, "STRMOUT", NULL }, + { 0x00000003, "DISPATCH", vm_dispatch_subclients }, + { 0x00000004, "PFIFO_WRITE", NULL }, + { 0x00000005, "CCACHE", vm_ccache_subclients }, + { 0x00000006, "PPPP", NULL }, + { 0x00000007, "CLIPID", NULL }, + { 0x00000008, "PFIFO_READ", NULL }, + { 0x00000009, "VFETCH", NULL }, + { 0x0000000a, "TEXTURE", NULL }, + { 0x0000000b, "PROP", vm_prop_subclients }, + { 0x0000000c, "PVP", NULL }, + { 0x0000000d, "PBSP", NULL }, + { 0x0000000e, "PCRYPT", NULL }, + { 0x0000000f, "PCOUNTER", NULL }, + { 0x00000011, "PDAEMON", NULL }, + {} +}; + +static struct nouveau_enum vm_engine[] = { + { 0x00000000, "PGRAPH", NULL }, + { 0x00000001, "PVP", NULL }, + { 0x00000004, "PEEPHOLE", NULL }, + { 0x00000005, "PFIFO", vm_pfifo_subclients }, + { 0x00000006, "BAR", vm_bar_subclients }, + { 0x00000008, "PPPP", NULL }, + { 0x00000009, "PBSP", NULL }, + { 0x0000000a, "PCRYPT", NULL }, + { 0x0000000b, "PCOUNTER", NULL }, + { 0x0000000c, "SEMAPHORE_BG", NULL }, + { 0x0000000d, "PCOPY", NULL }, + { 0x0000000e, "PDAEMON", NULL }, + {} +}; + +static struct nouveau_enum vm_fault[] = { + { 0x00000000, "PT_NOT_PRESENT", NULL }, + { 0x00000001, "PT_TOO_SHORT", NULL }, + { 0x00000002, "PAGE_NOT_PRESENT", NULL }, + { 0x00000003, "PAGE_SYSTEM_ONLY", NULL }, + { 0x00000004, "PAGE_READ_ONLY", NULL }, + { 0x00000006, "NULL_DMAOBJ", NULL }, + { 0x00000007, "WRONG_MEMTYPE", NULL }, + { 0x0000000b, "VRAM_LIMIT", NULL }, + { 0x0000000f, "DMAOBJ_LIMIT", NULL }, + {} +}; + +void +nv50_fb_vm_trap(struct drm_device *dev, int display) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + const struct nouveau_enum *en, *cl; + unsigned long flags; + u32 trap[6], idx, chinst; + u8 st0, st1, st2, st3; + int i, ch; + + idx = nv_rd32(dev, 0x100c90); + if (!(idx & 0x80000000)) + return; + idx &= 0x00ffffff; + + for (i = 0; i < 6; i++) { + nv_wr32(dev, 0x100c90, idx | i << 24); + trap[i] = nv_rd32(dev, 0x100c94); + } + nv_wr32(dev, 0x100c90, idx | 0x80000000); + + if (!display) + return; + + /* lookup channel id */ + chinst = (trap[2] << 16) | trap[1]; + spin_lock_irqsave(&dev_priv->channels.lock, flags); + for (ch = 0; ch < dev_priv->engine.fifo.channels; ch++) { + struct nouveau_channel *chan = dev_priv->channels.ptr[ch]; + + if (!chan || !chan->ramin) + continue; + + if (chinst == chan->ramin->vinst >> 12) + break; + } + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + + /* decode status bits into something more useful */ + if (dev_priv->chipset < 0xa3 || + dev_priv->chipset == 0xaa || dev_priv->chipset == 0xac) { + st0 = (trap[0] & 0x0000000f) >> 0; + st1 = (trap[0] & 0x000000f0) >> 4; + st2 = (trap[0] & 0x00000f00) >> 8; + st3 = (trap[0] & 0x0000f000) >> 12; + } else { + st0 = (trap[0] & 0x000000ff) >> 0; + st1 = (trap[0] & 0x0000ff00) >> 8; + st2 = (trap[0] & 0x00ff0000) >> 16; + st3 = (trap[0] & 0xff000000) >> 24; + } + + NV_INFO(dev, "VM: trapped %s at 0x%02x%04x%04x on ch %d [0x%08x] ", + (trap[5] & 0x00000100) ? "read" : "write", + trap[5] & 0xff, trap[4] & 0xffff, trap[3] & 0xffff, ch, chinst); + + en = nouveau_enum_find(vm_engine, st0); + if (en) + printk("%s/", en->name); + else + printk("%02x/", st0); + + cl = nouveau_enum_find(vm_client, st2); + if (cl) + printk("%s/", cl->name); + else + printk("%02x/", st2); + + if (cl && cl->data) cl = nouveau_enum_find(cl->data, st3); + else if (en && en->data) cl = nouveau_enum_find(en->data, st3); + else cl = NULL; + if (cl) + printk("%s", cl->name); + else + printk("%02x", st3); + + printk(" reason: "); + en = nouveau_enum_find(vm_fault, st1); + if (en) + printk("%s\n", en->name); + else + printk("0x%08x\n", st1); +} diff --git a/drivers/gpu/drm/nouveau/nv50_fbcon.c b/drivers/gpu/drm/nouveau/nv50_fbcon.c new file mode 100644 index 00000000..dc75a720 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_fbcon.c @@ -0,0 +1,264 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_ramht.h" +#include "nouveau_fbcon.h" +#include "nouveau_mm.h" + +int +nv50_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + int ret; + + ret = RING_SPACE(chan, rect->rop == ROP_COPY ? 7 : 11); + if (ret) + return ret; + + if (rect->rop != ROP_COPY) { + BEGIN_RING(chan, NvSub2D, 0x02ac, 1); + OUT_RING(chan, 1); + } + BEGIN_RING(chan, NvSub2D, 0x0588, 1); + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) + OUT_RING(chan, ((uint32_t *)info->pseudo_palette)[rect->color]); + else + OUT_RING(chan, rect->color); + BEGIN_RING(chan, NvSub2D, 0x0600, 4); + OUT_RING(chan, rect->dx); + OUT_RING(chan, rect->dy); + OUT_RING(chan, rect->dx + rect->width); + OUT_RING(chan, rect->dy + rect->height); + if (rect->rop != ROP_COPY) { + BEGIN_RING(chan, NvSub2D, 0x02ac, 1); + OUT_RING(chan, 3); + } + FIRE_RING(chan); + return 0; +} + +int +nv50_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + int ret; + + ret = RING_SPACE(chan, 12); + if (ret) + return ret; + + BEGIN_RING(chan, NvSub2D, 0x0110, 1); + OUT_RING(chan, 0); + BEGIN_RING(chan, NvSub2D, 0x08b0, 4); + OUT_RING(chan, region->dx); + OUT_RING(chan, region->dy); + OUT_RING(chan, region->width); + OUT_RING(chan, region->height); + BEGIN_RING(chan, NvSub2D, 0x08d0, 4); + OUT_RING(chan, 0); + OUT_RING(chan, region->sx); + OUT_RING(chan, 0); + OUT_RING(chan, region->sy); + FIRE_RING(chan); + return 0; +} + +int +nv50_fbcon_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + uint32_t width, dwords, *data = (uint32_t *)image->data; + uint32_t mask = ~(~0 >> (32 - info->var.bits_per_pixel)); + uint32_t *palette = info->pseudo_palette; + int ret; + + if (image->depth != 1) + return -ENODEV; + + ret = RING_SPACE(chan, 11); + if (ret) + return ret; + + width = ALIGN(image->width, 32); + dwords = (width * image->height) >> 5; + + BEGIN_RING(chan, NvSub2D, 0x0814, 2); + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + OUT_RING(chan, palette[image->bg_color] | mask); + OUT_RING(chan, palette[image->fg_color] | mask); + } else { + OUT_RING(chan, image->bg_color); + OUT_RING(chan, image->fg_color); + } + BEGIN_RING(chan, NvSub2D, 0x0838, 2); + OUT_RING(chan, image->width); + OUT_RING(chan, image->height); + BEGIN_RING(chan, NvSub2D, 0x0850, 4); + OUT_RING(chan, 0); + OUT_RING(chan, image->dx); + OUT_RING(chan, 0); + OUT_RING(chan, image->dy); + + while (dwords) { + int push = dwords > 2047 ? 2047 : dwords; + + ret = RING_SPACE(chan, push + 1); + if (ret) + return ret; + + dwords -= push; + + BEGIN_RING(chan, NvSub2D, 0x40000860, push); + OUT_RINGp(chan, data, push); + data += push; + } + + FIRE_RING(chan); + return 0; +} + +int +nv50_fbcon_accel_init(struct fb_info *info) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + struct nouveau_framebuffer *fb = &nfbdev->nouveau_fb; + int ret, format; + + switch (info->var.bits_per_pixel) { + case 8: + format = 0xf3; + break; + case 15: + format = 0xf8; + break; + case 16: + format = 0xe8; + break; + case 32: + switch (info->var.transp.length) { + case 0: /* depth 24 */ + case 8: /* depth 32, just use 24.. */ + format = 0xe6; + break; + case 2: /* depth 30 */ + format = 0xd1; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + ret = nouveau_gpuobj_gr_new(dev_priv->channel, Nv2D, 0x502d); + if (ret) + return ret; + + ret = RING_SPACE(chan, 59); + if (ret) { + nouveau_fbcon_gpu_lockup(info); + return ret; + } + + BEGIN_RING(chan, NvSub2D, 0x0000, 1); + OUT_RING(chan, Nv2D); + BEGIN_RING(chan, NvSub2D, 0x0180, 4); + OUT_RING(chan, NvNotify0); + OUT_RING(chan, chan->vram_handle); + OUT_RING(chan, chan->vram_handle); + OUT_RING(chan, chan->vram_handle); + BEGIN_RING(chan, NvSub2D, 0x0290, 1); + OUT_RING(chan, 0); + BEGIN_RING(chan, NvSub2D, 0x0888, 1); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x02ac, 1); + OUT_RING(chan, 3); + BEGIN_RING(chan, NvSub2D, 0x02a0, 1); + OUT_RING(chan, 0x55); + BEGIN_RING(chan, NvSub2D, 0x08c0, 4); + OUT_RING(chan, 0); + OUT_RING(chan, 1); + OUT_RING(chan, 0); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x0580, 2); + OUT_RING(chan, 4); + OUT_RING(chan, format); + BEGIN_RING(chan, NvSub2D, 0x02e8, 2); + OUT_RING(chan, 2); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x0804, 1); + OUT_RING(chan, format); + BEGIN_RING(chan, NvSub2D, 0x0800, 1); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x0808, 3); + OUT_RING(chan, 0); + OUT_RING(chan, 0); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x081c, 1); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x0840, 4); + OUT_RING(chan, 0); + OUT_RING(chan, 1); + OUT_RING(chan, 0); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x0200, 2); + OUT_RING(chan, format); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x0214, 5); + OUT_RING(chan, info->fix.line_length); + OUT_RING(chan, info->var.xres_virtual); + OUT_RING(chan, info->var.yres_virtual); + OUT_RING(chan, upper_32_bits(fb->vma.offset)); + OUT_RING(chan, lower_32_bits(fb->vma.offset)); + BEGIN_RING(chan, NvSub2D, 0x0230, 2); + OUT_RING(chan, format); + OUT_RING(chan, 1); + BEGIN_RING(chan, NvSub2D, 0x0244, 5); + OUT_RING(chan, info->fix.line_length); + OUT_RING(chan, info->var.xres_virtual); + OUT_RING(chan, info->var.yres_virtual); + OUT_RING(chan, upper_32_bits(fb->vma.offset)); + OUT_RING(chan, lower_32_bits(fb->vma.offset)); + + return 0; +} + diff --git a/drivers/gpu/drm/nouveau/nv50_fifo.c b/drivers/gpu/drm/nouveau/nv50_fifo.c new file mode 100644 index 00000000..3bc2a565 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_fifo.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" +#include "nouveau_vm.h" + +static void +nv50_fifo_playlist_update(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_gpuobj *cur; + int i, nr; + + NV_DEBUG(dev, "\n"); + + cur = pfifo->playlist[pfifo->cur_playlist]; + pfifo->cur_playlist = !pfifo->cur_playlist; + + /* We never schedule channel 0 or 127 */ + for (i = 1, nr = 0; i < 127; i++) { + if (dev_priv->channels.ptr[i] && + dev_priv->channels.ptr[i]->ramfc) { + nv_wo32(cur, (nr * 4), i); + nr++; + } + } + dev_priv->engine.instmem.flush(dev); + + nv_wr32(dev, 0x32f4, cur->vinst >> 12); + nv_wr32(dev, 0x32ec, nr); + nv_wr32(dev, 0x2500, 0x101); +} + +static void +nv50_fifo_channel_enable(struct drm_device *dev, int channel) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channels.ptr[channel]; + uint32_t inst; + + NV_DEBUG(dev, "ch%d\n", channel); + + if (dev_priv->chipset == 0x50) + inst = chan->ramfc->vinst >> 12; + else + inst = chan->ramfc->vinst >> 8; + + nv_wr32(dev, NV50_PFIFO_CTX_TABLE(channel), inst | + NV50_PFIFO_CTX_TABLE_CHANNEL_ENABLED); +} + +static void +nv50_fifo_channel_disable(struct drm_device *dev, int channel) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t inst; + + NV_DEBUG(dev, "ch%d\n", channel); + + if (dev_priv->chipset == 0x50) + inst = NV50_PFIFO_CTX_TABLE_INSTANCE_MASK_G80; + else + inst = NV50_PFIFO_CTX_TABLE_INSTANCE_MASK_G84; + nv_wr32(dev, NV50_PFIFO_CTX_TABLE(channel), inst); +} + +static void +nv50_fifo_init_reset(struct drm_device *dev) +{ + uint32_t pmc_e = NV_PMC_ENABLE_PFIFO; + + NV_DEBUG(dev, "\n"); + + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) & ~pmc_e); + nv_wr32(dev, NV03_PMC_ENABLE, nv_rd32(dev, NV03_PMC_ENABLE) | pmc_e); +} + +static void +nv50_fifo_init_intr(struct drm_device *dev) +{ + NV_DEBUG(dev, "\n"); + + nouveau_irq_register(dev, 8, nv04_fifo_isr); + nv_wr32(dev, NV03_PFIFO_INTR_0, 0xFFFFFFFF); + nv_wr32(dev, NV03_PFIFO_INTR_EN_0, 0xFFFFFFFF); +} + +static void +nv50_fifo_init_context_table(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i; + + NV_DEBUG(dev, "\n"); + + for (i = 0; i < NV50_PFIFO_CTX_TABLE__SIZE; i++) { + if (dev_priv->channels.ptr[i]) + nv50_fifo_channel_enable(dev, i); + else + nv50_fifo_channel_disable(dev, i); + } + + nv50_fifo_playlist_update(dev); +} + +static void +nv50_fifo_init_regs__nv(struct drm_device *dev) +{ + NV_DEBUG(dev, "\n"); + + nv_wr32(dev, 0x250c, 0x6f3cfc34); +} + +static void +nv50_fifo_init_regs(struct drm_device *dev) +{ + NV_DEBUG(dev, "\n"); + + nv_wr32(dev, 0x2500, 0); + nv_wr32(dev, 0x3250, 0); + nv_wr32(dev, 0x3220, 0); + nv_wr32(dev, 0x3204, 0); + nv_wr32(dev, 0x3210, 0); + nv_wr32(dev, 0x3270, 0); + nv_wr32(dev, 0x2044, 0x01003fff); + + /* Enable dummy channels setup by nv50_instmem.c */ + nv50_fifo_channel_enable(dev, 0); + nv50_fifo_channel_enable(dev, 127); +} + +int +nv50_fifo_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + int ret; + + NV_DEBUG(dev, "\n"); + + if (pfifo->playlist[0]) { + pfifo->cur_playlist = !pfifo->cur_playlist; + goto just_reset; + } + + ret = nouveau_gpuobj_new(dev, NULL, 128*4, 0x1000, + NVOBJ_FLAG_ZERO_ALLOC, + &pfifo->playlist[0]); + if (ret) { + NV_ERROR(dev, "error creating playlist 0: %d\n", ret); + return ret; + } + + ret = nouveau_gpuobj_new(dev, NULL, 128*4, 0x1000, + NVOBJ_FLAG_ZERO_ALLOC, + &pfifo->playlist[1]); + if (ret) { + nouveau_gpuobj_ref(NULL, &pfifo->playlist[0]); + NV_ERROR(dev, "error creating playlist 1: %d\n", ret); + return ret; + } + +just_reset: + nv50_fifo_init_reset(dev); + nv50_fifo_init_intr(dev); + nv50_fifo_init_context_table(dev); + nv50_fifo_init_regs__nv(dev); + nv50_fifo_init_regs(dev); + dev_priv->engine.fifo.enable(dev); + dev_priv->engine.fifo.reassign(dev, true); + + return 0; +} + +void +nv50_fifo_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + + NV_DEBUG(dev, "\n"); + + if (!pfifo->playlist[0]) + return; + + nv_wr32(dev, 0x2140, 0x00000000); + nouveau_irq_unregister(dev, 8); + + nouveau_gpuobj_ref(NULL, &pfifo->playlist[0]); + nouveau_gpuobj_ref(NULL, &pfifo->playlist[1]); +} + +int +nv50_fifo_channel_id(struct drm_device *dev) +{ + return nv_rd32(dev, NV03_PFIFO_CACHE1_PUSH1) & + NV50_PFIFO_CACHE1_PUSH1_CHID_MASK; +} + +int +nv50_fifo_create_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramfc = NULL; + uint64_t ib_offset = chan->pushbuf_base + chan->dma.ib_base * 4; + unsigned long flags; + int ret; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + if (dev_priv->chipset == 0x50) { + ret = nouveau_gpuobj_new_fake(dev, chan->ramin->pinst, + chan->ramin->vinst, 0x100, + NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, + &chan->ramfc); + if (ret) + return ret; + + ret = nouveau_gpuobj_new_fake(dev, chan->ramin->pinst + 0x0400, + chan->ramin->vinst + 0x0400, + 4096, 0, &chan->cache); + if (ret) + return ret; + } else { + ret = nouveau_gpuobj_new(dev, chan, 0x100, 256, + NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &chan->ramfc); + if (ret) + return ret; + + ret = nouveau_gpuobj_new(dev, chan, 4096, 1024, + 0, &chan->cache); + if (ret) + return ret; + } + ramfc = chan->ramfc; + + chan->user = ioremap(pci_resource_start(dev->pdev, 0) + + NV50_USER(chan->id), PAGE_SIZE); + if (!chan->user) + return -ENOMEM; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + + nv_wo32(ramfc, 0x48, chan->pushbuf->cinst >> 4); + nv_wo32(ramfc, 0x80, ((chan->ramht->bits - 9) << 27) | + (4 << 24) /* SEARCH_FULL */ | + (chan->ramht->gpuobj->cinst >> 4)); + nv_wo32(ramfc, 0x44, 0x01003fff); + nv_wo32(ramfc, 0x60, 0x7fffffff); + nv_wo32(ramfc, 0x40, 0x00000000); + nv_wo32(ramfc, 0x7c, 0x30000001); + nv_wo32(ramfc, 0x78, 0x00000000); + nv_wo32(ramfc, 0x3c, 0x403f6078); + nv_wo32(ramfc, 0x50, lower_32_bits(ib_offset)); + nv_wo32(ramfc, 0x54, upper_32_bits(ib_offset) | + drm_order(chan->dma.ib_max + 1) << 16); + + if (dev_priv->chipset != 0x50) { + nv_wo32(chan->ramin, 0, chan->id); + nv_wo32(chan->ramin, 4, chan->ramfc->vinst >> 8); + + nv_wo32(ramfc, 0x88, chan->cache->vinst >> 10); + nv_wo32(ramfc, 0x98, chan->ramin->vinst >> 12); + } + + dev_priv->engine.instmem.flush(dev); + + nv50_fifo_channel_enable(dev, chan->id); + nv50_fifo_playlist_update(dev); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + return 0; +} + +void +nv50_fifo_destroy_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_gpuobj *ramfc = NULL; + unsigned long flags; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + pfifo->reassign(dev, false); + + /* Unload the context if it's the currently active one */ + if (pfifo->channel_id(dev) == chan->id) { + pfifo->disable(dev); + pfifo->unload_context(dev); + pfifo->enable(dev); + } + + /* This will ensure the channel is seen as disabled. */ + nouveau_gpuobj_ref(chan->ramfc, &ramfc); + nouveau_gpuobj_ref(NULL, &chan->ramfc); + nv50_fifo_channel_disable(dev, chan->id); + + /* Dummy channel, also used on ch 127 */ + if (chan->id == 0) + nv50_fifo_channel_disable(dev, 127); + nv50_fifo_playlist_update(dev); + + pfifo->reassign(dev, true); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + /* Free the channel resources */ + if (chan->user) { + iounmap(chan->user); + chan->user = NULL; + } + nouveau_gpuobj_ref(NULL, &ramfc); + nouveau_gpuobj_ref(NULL, &chan->cache); +} + +int +nv50_fifo_load_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramfc = chan->ramfc; + struct nouveau_gpuobj *cache = chan->cache; + int ptr, cnt; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + nv_wr32(dev, 0x3330, nv_ro32(ramfc, 0x00)); + nv_wr32(dev, 0x3334, nv_ro32(ramfc, 0x04)); + nv_wr32(dev, 0x3240, nv_ro32(ramfc, 0x08)); + nv_wr32(dev, 0x3320, nv_ro32(ramfc, 0x0c)); + nv_wr32(dev, 0x3244, nv_ro32(ramfc, 0x10)); + nv_wr32(dev, 0x3328, nv_ro32(ramfc, 0x14)); + nv_wr32(dev, 0x3368, nv_ro32(ramfc, 0x18)); + nv_wr32(dev, 0x336c, nv_ro32(ramfc, 0x1c)); + nv_wr32(dev, 0x3370, nv_ro32(ramfc, 0x20)); + nv_wr32(dev, 0x3374, nv_ro32(ramfc, 0x24)); + nv_wr32(dev, 0x3378, nv_ro32(ramfc, 0x28)); + nv_wr32(dev, 0x337c, nv_ro32(ramfc, 0x2c)); + nv_wr32(dev, 0x3228, nv_ro32(ramfc, 0x30)); + nv_wr32(dev, 0x3364, nv_ro32(ramfc, 0x34)); + nv_wr32(dev, 0x32a0, nv_ro32(ramfc, 0x38)); + nv_wr32(dev, 0x3224, nv_ro32(ramfc, 0x3c)); + nv_wr32(dev, 0x324c, nv_ro32(ramfc, 0x40)); + nv_wr32(dev, 0x2044, nv_ro32(ramfc, 0x44)); + nv_wr32(dev, 0x322c, nv_ro32(ramfc, 0x48)); + nv_wr32(dev, 0x3234, nv_ro32(ramfc, 0x4c)); + nv_wr32(dev, 0x3340, nv_ro32(ramfc, 0x50)); + nv_wr32(dev, 0x3344, nv_ro32(ramfc, 0x54)); + nv_wr32(dev, 0x3280, nv_ro32(ramfc, 0x58)); + nv_wr32(dev, 0x3254, nv_ro32(ramfc, 0x5c)); + nv_wr32(dev, 0x3260, nv_ro32(ramfc, 0x60)); + nv_wr32(dev, 0x3264, nv_ro32(ramfc, 0x64)); + nv_wr32(dev, 0x3268, nv_ro32(ramfc, 0x68)); + nv_wr32(dev, 0x326c, nv_ro32(ramfc, 0x6c)); + nv_wr32(dev, 0x32e4, nv_ro32(ramfc, 0x70)); + nv_wr32(dev, 0x3248, nv_ro32(ramfc, 0x74)); + nv_wr32(dev, 0x2088, nv_ro32(ramfc, 0x78)); + nv_wr32(dev, 0x2058, nv_ro32(ramfc, 0x7c)); + nv_wr32(dev, 0x2210, nv_ro32(ramfc, 0x80)); + + cnt = nv_ro32(ramfc, 0x84); + for (ptr = 0; ptr < cnt; ptr++) { + nv_wr32(dev, NV40_PFIFO_CACHE1_METHOD(ptr), + nv_ro32(cache, (ptr * 8) + 0)); + nv_wr32(dev, NV40_PFIFO_CACHE1_DATA(ptr), + nv_ro32(cache, (ptr * 8) + 4)); + } + nv_wr32(dev, NV03_PFIFO_CACHE1_PUT, cnt << 2); + nv_wr32(dev, NV03_PFIFO_CACHE1_GET, 0); + + /* guessing that all the 0x34xx regs aren't on NV50 */ + if (dev_priv->chipset != 0x50) { + nv_wr32(dev, 0x340c, nv_ro32(ramfc, 0x88)); + nv_wr32(dev, 0x3400, nv_ro32(ramfc, 0x8c)); + nv_wr32(dev, 0x3404, nv_ro32(ramfc, 0x90)); + nv_wr32(dev, 0x3408, nv_ro32(ramfc, 0x94)); + nv_wr32(dev, 0x3410, nv_ro32(ramfc, 0x98)); + } + + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, chan->id | (1<<16)); + return 0; +} + +int +nv50_fifo_unload_context(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_gpuobj *ramfc, *cache; + struct nouveau_channel *chan = NULL; + int chid, get, put, ptr; + + NV_DEBUG(dev, "\n"); + + chid = pfifo->channel_id(dev); + if (chid < 1 || chid >= dev_priv->engine.fifo.channels - 1) + return 0; + + chan = dev_priv->channels.ptr[chid]; + if (!chan) { + NV_ERROR(dev, "Inactive channel on PFIFO: %d\n", chid); + return -EINVAL; + } + NV_DEBUG(dev, "ch%d\n", chan->id); + ramfc = chan->ramfc; + cache = chan->cache; + + nv_wo32(ramfc, 0x00, nv_rd32(dev, 0x3330)); + nv_wo32(ramfc, 0x04, nv_rd32(dev, 0x3334)); + nv_wo32(ramfc, 0x08, nv_rd32(dev, 0x3240)); + nv_wo32(ramfc, 0x0c, nv_rd32(dev, 0x3320)); + nv_wo32(ramfc, 0x10, nv_rd32(dev, 0x3244)); + nv_wo32(ramfc, 0x14, nv_rd32(dev, 0x3328)); + nv_wo32(ramfc, 0x18, nv_rd32(dev, 0x3368)); + nv_wo32(ramfc, 0x1c, nv_rd32(dev, 0x336c)); + nv_wo32(ramfc, 0x20, nv_rd32(dev, 0x3370)); + nv_wo32(ramfc, 0x24, nv_rd32(dev, 0x3374)); + nv_wo32(ramfc, 0x28, nv_rd32(dev, 0x3378)); + nv_wo32(ramfc, 0x2c, nv_rd32(dev, 0x337c)); + nv_wo32(ramfc, 0x30, nv_rd32(dev, 0x3228)); + nv_wo32(ramfc, 0x34, nv_rd32(dev, 0x3364)); + nv_wo32(ramfc, 0x38, nv_rd32(dev, 0x32a0)); + nv_wo32(ramfc, 0x3c, nv_rd32(dev, 0x3224)); + nv_wo32(ramfc, 0x40, nv_rd32(dev, 0x324c)); + nv_wo32(ramfc, 0x44, nv_rd32(dev, 0x2044)); + nv_wo32(ramfc, 0x48, nv_rd32(dev, 0x322c)); + nv_wo32(ramfc, 0x4c, nv_rd32(dev, 0x3234)); + nv_wo32(ramfc, 0x50, nv_rd32(dev, 0x3340)); + nv_wo32(ramfc, 0x54, nv_rd32(dev, 0x3344)); + nv_wo32(ramfc, 0x58, nv_rd32(dev, 0x3280)); + nv_wo32(ramfc, 0x5c, nv_rd32(dev, 0x3254)); + nv_wo32(ramfc, 0x60, nv_rd32(dev, 0x3260)); + nv_wo32(ramfc, 0x64, nv_rd32(dev, 0x3264)); + nv_wo32(ramfc, 0x68, nv_rd32(dev, 0x3268)); + nv_wo32(ramfc, 0x6c, nv_rd32(dev, 0x326c)); + nv_wo32(ramfc, 0x70, nv_rd32(dev, 0x32e4)); + nv_wo32(ramfc, 0x74, nv_rd32(dev, 0x3248)); + nv_wo32(ramfc, 0x78, nv_rd32(dev, 0x2088)); + nv_wo32(ramfc, 0x7c, nv_rd32(dev, 0x2058)); + nv_wo32(ramfc, 0x80, nv_rd32(dev, 0x2210)); + + put = (nv_rd32(dev, NV03_PFIFO_CACHE1_PUT) & 0x7ff) >> 2; + get = (nv_rd32(dev, NV03_PFIFO_CACHE1_GET) & 0x7ff) >> 2; + ptr = 0; + while (put != get) { + nv_wo32(cache, ptr + 0, + nv_rd32(dev, NV40_PFIFO_CACHE1_METHOD(get))); + nv_wo32(cache, ptr + 4, + nv_rd32(dev, NV40_PFIFO_CACHE1_DATA(get))); + get = (get + 1) & 0x1ff; + ptr += 8; + } + + /* guessing that all the 0x34xx regs aren't on NV50 */ + if (dev_priv->chipset != 0x50) { + nv_wo32(ramfc, 0x84, ptr >> 3); + nv_wo32(ramfc, 0x88, nv_rd32(dev, 0x340c)); + nv_wo32(ramfc, 0x8c, nv_rd32(dev, 0x3400)); + nv_wo32(ramfc, 0x90, nv_rd32(dev, 0x3404)); + nv_wo32(ramfc, 0x94, nv_rd32(dev, 0x3408)); + nv_wo32(ramfc, 0x98, nv_rd32(dev, 0x3410)); + } + + dev_priv->engine.instmem.flush(dev); + + /*XXX: probably reload ch127 (NULL) state back too */ + nv_wr32(dev, NV03_PFIFO_CACHE1_PUSH1, 127); + return 0; +} + +void +nv50_fifo_tlb_flush(struct drm_device *dev) +{ + nv50_vm_flush_engine(dev, 5); +} diff --git a/drivers/gpu/drm/nouveau/nv50_gpio.c b/drivers/gpu/drm/nouveau/nv50_gpio.c new file mode 100644 index 00000000..f429e6a8 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_gpio.c @@ -0,0 +1,139 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_hw.h" +#include "nouveau_gpio.h" + +#include "nv50_display.h" + +static int +nv50_gpio_location(int line, u32 *reg, u32 *shift) +{ + const uint32_t nv50_gpio_reg[4] = { 0xe104, 0xe108, 0xe280, 0xe284 }; + + if (line >= 32) + return -EINVAL; + + *reg = nv50_gpio_reg[line >> 3]; + *shift = (line & 7) << 2; + return 0; +} + +int +nv50_gpio_drive(struct drm_device *dev, int line, int dir, int out) +{ + u32 reg, shift; + + if (nv50_gpio_location(line, ®, &shift)) + return -EINVAL; + + nv_mask(dev, reg, 7 << shift, (((dir ^ 1) << 1) | out) << shift); + return 0; +} + +int +nv50_gpio_sense(struct drm_device *dev, int line) +{ + u32 reg, shift; + + if (nv50_gpio_location(line, ®, &shift)) + return -EINVAL; + + return !!(nv_rd32(dev, reg) & (4 << shift)); +} + +void +nv50_gpio_irq_enable(struct drm_device *dev, int line, bool on) +{ + u32 reg = line < 16 ? 0xe050 : 0xe070; + u32 mask = 0x00010001 << (line & 0xf); + + nv_wr32(dev, reg + 4, mask); + nv_mask(dev, reg + 0, mask, on ? mask : 0); +} + +int +nvd0_gpio_drive(struct drm_device *dev, int line, int dir, int out) +{ + u32 data = ((dir ^ 1) << 13) | (out << 12); + nv_mask(dev, 0x00d610 + (line * 4), 0x00003000, data); + nv_mask(dev, 0x00d604, 0x00000001, 0x00000001); /* update? */ + return 0; +} + +int +nvd0_gpio_sense(struct drm_device *dev, int line) +{ + return !!(nv_rd32(dev, 0x00d610 + (line * 4)) & 0x00004000); +} + +static void +nv50_gpio_isr(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 intr0, intr1 = 0; + u32 hi, lo; + + intr0 = nv_rd32(dev, 0xe054) & nv_rd32(dev, 0xe050); + if (dev_priv->chipset >= 0x90) + intr1 = nv_rd32(dev, 0xe074) & nv_rd32(dev, 0xe070); + + hi = (intr0 & 0x0000ffff) | (intr1 << 16); + lo = (intr0 >> 16) | (intr1 & 0xffff0000); + nouveau_gpio_isr(dev, 0, hi | lo); + + nv_wr32(dev, 0xe054, intr0); + if (dev_priv->chipset >= 0x90) + nv_wr32(dev, 0xe074, intr1); +} + +int +nv50_gpio_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + /* disable, and ack any pending gpio interrupts */ + nv_wr32(dev, 0xe050, 0x00000000); + nv_wr32(dev, 0xe054, 0xffffffff); + if (dev_priv->chipset >= 0x90) { + nv_wr32(dev, 0xe070, 0x00000000); + nv_wr32(dev, 0xe074, 0xffffffff); + } + + nouveau_irq_register(dev, 21, nv50_gpio_isr); + return 0; +} + +void +nv50_gpio_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nv_wr32(dev, 0xe050, 0x00000000); + if (dev_priv->chipset >= 0x90) + nv_wr32(dev, 0xe070, 0x00000000); + nouveau_irq_unregister(dev, 21); +} diff --git a/drivers/gpu/drm/nouveau/nv50_graph.c b/drivers/gpu/drm/nouveau/nv50_graph.c new file mode 100644 index 00000000..33d5711a --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_graph.c @@ -0,0 +1,1079 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" +#include "nouveau_grctx.h" +#include "nouveau_dma.h" +#include "nouveau_vm.h" +#include "nv50_evo.h" + +struct nv50_graph_engine { + struct nouveau_exec_engine base; + u32 ctxprog[512]; + u32 ctxprog_size; + u32 grctx_size; +}; + +static void +nv50_graph_fifo_access(struct drm_device *dev, bool enabled) +{ + const uint32_t mask = 0x00010001; + + if (enabled) + nv_wr32(dev, 0x400500, nv_rd32(dev, 0x400500) | mask); + else + nv_wr32(dev, 0x400500, nv_rd32(dev, 0x400500) & ~mask); +} + +static struct nouveau_channel * +nv50_graph_channel(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t inst; + int i; + + /* Be sure we're not in the middle of a context switch or bad things + * will happen, such as unloading the wrong pgraph context. + */ + if (!nv_wait(dev, 0x400300, 0x00000001, 0x00000000)) + NV_ERROR(dev, "Ctxprog is still running\n"); + + inst = nv_rd32(dev, NV50_PGRAPH_CTXCTL_CUR); + if (!(inst & NV50_PGRAPH_CTXCTL_CUR_LOADED)) + return NULL; + inst = (inst & NV50_PGRAPH_CTXCTL_CUR_INSTANCE) << 12; + + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + struct nouveau_channel *chan = dev_priv->channels.ptr[i]; + + if (chan && chan->ramin && chan->ramin->vinst == inst) + return chan; + } + + return NULL; +} + +static int +nv50_graph_do_load_context(struct drm_device *dev, uint32_t inst) +{ + uint32_t fifo = nv_rd32(dev, 0x400500); + + nv_wr32(dev, 0x400500, fifo & ~1); + nv_wr32(dev, 0x400784, inst); + nv_wr32(dev, 0x400824, nv_rd32(dev, 0x400824) | 0x40); + nv_wr32(dev, 0x400320, nv_rd32(dev, 0x400320) | 0x11); + nv_wr32(dev, 0x400040, 0xffffffff); + (void)nv_rd32(dev, 0x400040); + nv_wr32(dev, 0x400040, 0x00000000); + nv_wr32(dev, 0x400304, nv_rd32(dev, 0x400304) | 1); + + if (nouveau_wait_for_idle(dev)) + nv_wr32(dev, 0x40032c, inst | (1<<31)); + nv_wr32(dev, 0x400500, fifo); + + return 0; +} + +static int +nv50_graph_unload_context(struct drm_device *dev) +{ + uint32_t inst; + + inst = nv_rd32(dev, NV50_PGRAPH_CTXCTL_CUR); + if (!(inst & NV50_PGRAPH_CTXCTL_CUR_LOADED)) + return 0; + inst &= NV50_PGRAPH_CTXCTL_CUR_INSTANCE; + + nouveau_wait_for_idle(dev); + nv_wr32(dev, 0x400784, inst); + nv_wr32(dev, 0x400824, nv_rd32(dev, 0x400824) | 0x20); + nv_wr32(dev, 0x400304, nv_rd32(dev, 0x400304) | 0x01); + nouveau_wait_for_idle(dev); + + nv_wr32(dev, NV50_PGRAPH_CTXCTL_CUR, inst); + return 0; +} + +static int +nv50_graph_init(struct drm_device *dev, int engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_graph_engine *pgraph = nv_engine(dev, engine); + u32 units = nv_rd32(dev, 0x001540); + int i; + + NV_DEBUG(dev, "\n"); + + /* master reset */ + nv_mask(dev, 0x000200, 0x00201000, 0x00000000); + nv_mask(dev, 0x000200, 0x00201000, 0x00201000); + nv_wr32(dev, 0x40008c, 0x00000004); /* HW_CTX_SWITCH_ENABLED */ + + /* reset/enable traps and interrupts */ + nv_wr32(dev, 0x400804, 0xc0000000); + nv_wr32(dev, 0x406800, 0xc0000000); + nv_wr32(dev, 0x400c04, 0xc0000000); + nv_wr32(dev, 0x401800, 0xc0000000); + nv_wr32(dev, 0x405018, 0xc0000000); + nv_wr32(dev, 0x402000, 0xc0000000); + for (i = 0; i < 16; i++) { + if (!(units & (1 << i))) + continue; + + if (dev_priv->chipset < 0xa0) { + nv_wr32(dev, 0x408900 + (i << 12), 0xc0000000); + nv_wr32(dev, 0x408e08 + (i << 12), 0xc0000000); + nv_wr32(dev, 0x408314 + (i << 12), 0xc0000000); + } else { + nv_wr32(dev, 0x408600 + (i << 11), 0xc0000000); + nv_wr32(dev, 0x408708 + (i << 11), 0xc0000000); + nv_wr32(dev, 0x40831c + (i << 11), 0xc0000000); + } + } + + nv_wr32(dev, 0x400108, 0xffffffff); + nv_wr32(dev, 0x400138, 0xffffffff); + nv_wr32(dev, 0x400100, 0xffffffff); + nv_wr32(dev, 0x40013c, 0xffffffff); + nv_wr32(dev, 0x400500, 0x00010001); + + /* upload context program, initialise ctxctl defaults */ + nv_wr32(dev, 0x400324, 0x00000000); + for (i = 0; i < pgraph->ctxprog_size; i++) + nv_wr32(dev, 0x400328, pgraph->ctxprog[i]); + nv_wr32(dev, 0x400824, 0x00000000); + nv_wr32(dev, 0x400828, 0x00000000); + nv_wr32(dev, 0x40082c, 0x00000000); + nv_wr32(dev, 0x400830, 0x00000000); + nv_wr32(dev, 0x400724, 0x00000000); + nv_wr32(dev, 0x40032c, 0x00000000); + nv_wr32(dev, 0x400320, 4); /* CTXCTL_CMD = NEWCTXDMA */ + + /* some unknown zcull magic */ + switch (dev_priv->chipset & 0xf0) { + case 0x50: + case 0x80: + case 0x90: + nv_wr32(dev, 0x402ca8, 0x00000800); + break; + case 0xa0: + default: + nv_wr32(dev, 0x402cc0, 0x00000000); + if (dev_priv->chipset == 0xa0 || + dev_priv->chipset == 0xaa || + dev_priv->chipset == 0xac) { + nv_wr32(dev, 0x402ca8, 0x00000802); + } else { + nv_wr32(dev, 0x402cc0, 0x00000000); + nv_wr32(dev, 0x402ca8, 0x00000002); + } + + break; + } + + /* zero out zcull regions */ + for (i = 0; i < 8; i++) { + nv_wr32(dev, 0x402c20 + (i * 8), 0x00000000); + nv_wr32(dev, 0x402c24 + (i * 8), 0x00000000); + nv_wr32(dev, 0x402c28 + (i * 8), 0x00000000); + nv_wr32(dev, 0x402c2c + (i * 8), 0x00000000); + } + + return 0; +} + +static int +nv50_graph_fini(struct drm_device *dev, int engine, bool suspend) +{ + nv_mask(dev, 0x400500, 0x00010001, 0x00000000); + if (!nv_wait(dev, 0x400700, ~0, 0) && suspend) { + nv_mask(dev, 0x400500, 0x00010001, 0x00010001); + return -EBUSY; + } + nv50_graph_unload_context(dev); + nv_wr32(dev, 0x40013c, 0x00000000); + return 0; +} + +static int +nv50_graph_context_new(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramin = chan->ramin; + struct nouveau_gpuobj *grctx = NULL; + struct nv50_graph_engine *pgraph = nv_engine(dev, engine); + struct nouveau_grctx ctx = {}; + int hdr, ret; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + ret = nouveau_gpuobj_new(dev, NULL, pgraph->grctx_size, 0, + NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &grctx); + if (ret) + return ret; + + hdr = (dev_priv->chipset == 0x50) ? 0x200 : 0x20; + nv_wo32(ramin, hdr + 0x00, 0x00190002); + nv_wo32(ramin, hdr + 0x04, grctx->vinst + grctx->size - 1); + nv_wo32(ramin, hdr + 0x08, grctx->vinst); + nv_wo32(ramin, hdr + 0x0c, 0); + nv_wo32(ramin, hdr + 0x10, 0); + nv_wo32(ramin, hdr + 0x14, 0x00010000); + + ctx.dev = chan->dev; + ctx.mode = NOUVEAU_GRCTX_VALS; + ctx.data = grctx; + nv50_grctx_init(&ctx); + + nv_wo32(grctx, 0x00000, chan->ramin->vinst >> 12); + + dev_priv->engine.instmem.flush(dev); + + atomic_inc(&chan->vm->engref[NVOBJ_ENGINE_GR]); + chan->engctx[NVOBJ_ENGINE_GR] = grctx; + return 0; +} + +static void +nv50_graph_context_del(struct nouveau_channel *chan, int engine) +{ + struct nouveau_gpuobj *grctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + int i, hdr = (dev_priv->chipset == 0x50) ? 0x200 : 0x20; + unsigned long flags; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + if (!chan->ramin) + return; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + pfifo->reassign(dev, false); + nv50_graph_fifo_access(dev, false); + + if (nv50_graph_channel(dev) == chan) + nv50_graph_unload_context(dev); + + for (i = hdr; i < hdr + 24; i += 4) + nv_wo32(chan->ramin, i, 0); + dev_priv->engine.instmem.flush(dev); + + nv50_graph_fifo_access(dev, true); + pfifo->reassign(dev, true); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + nouveau_gpuobj_ref(NULL, &grctx); + + atomic_dec(&chan->vm->engref[engine]); + chan->engctx[engine] = NULL; +} + +static int +nv50_graph_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *obj = NULL; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 16, 16, NVOBJ_FLAG_ZERO_FREE, &obj); + if (ret) + return ret; + obj->engine = 1; + obj->class = class; + + nv_wo32(obj, 0x00, class); + nv_wo32(obj, 0x04, 0x00000000); + nv_wo32(obj, 0x08, 0x00000000); + nv_wo32(obj, 0x0c, 0x00000000); + dev_priv->engine.instmem.flush(dev); + + ret = nouveau_ramht_insert(chan, handle, obj); + nouveau_gpuobj_ref(NULL, &obj); + return ret; +} + +static void +nv50_graph_context_switch(struct drm_device *dev) +{ + uint32_t inst; + + nv50_graph_unload_context(dev); + + inst = nv_rd32(dev, NV50_PGRAPH_CTXCTL_NEXT); + inst &= NV50_PGRAPH_CTXCTL_NEXT_INSTANCE; + nv50_graph_do_load_context(dev, inst); + + nv_wr32(dev, NV40_PGRAPH_INTR_EN, nv_rd32(dev, + NV40_PGRAPH_INTR_EN) | NV_PGRAPH_INTR_CONTEXT_SWITCH); +} + +static int +nv50_graph_nvsw_dma_vblsem(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + struct nouveau_gpuobj *gpuobj; + + gpuobj = nouveau_ramht_find(chan, data); + if (!gpuobj) + return -ENOENT; + + if (nouveau_notifier_offset(gpuobj, NULL)) + return -EINVAL; + + chan->nvsw.vblsem = gpuobj; + chan->nvsw.vblsem_offset = ~0; + return 0; +} + +static int +nv50_graph_nvsw_vblsem_offset(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + if (nouveau_notifier_offset(chan->nvsw.vblsem, &data)) + return -ERANGE; + + chan->nvsw.vblsem_offset = data >> 2; + return 0; +} + +static int +nv50_graph_nvsw_vblsem_release_val(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + chan->nvsw.vblsem_rval = data; + return 0; +} + +static int +nv50_graph_nvsw_vblsem_release(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (!chan->nvsw.vblsem || chan->nvsw.vblsem_offset == ~0 || data > 1) + return -EINVAL; + + drm_vblank_get(dev, data); + + chan->nvsw.vblsem_head = data; + list_add(&chan->nvsw.vbl_wait, &dev_priv->vbl_waiting); + + return 0; +} + +static int +nv50_graph_nvsw_mthd_page_flip(struct nouveau_channel *chan, + u32 class, u32 mthd, u32 data) +{ + nouveau_finish_page_flip(chan, NULL); + return 0; +} + + +static void +nv50_graph_tlb_flush(struct drm_device *dev, int engine) +{ + nv50_vm_flush_engine(dev, 0); +} + +static void +nv84_graph_tlb_flush(struct drm_device *dev, int engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; + bool idle, timeout = false; + unsigned long flags; + u64 start; + u32 tmp; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, 0x400500, 0x00000001, 0x00000000); + + start = ptimer->read(dev); + do { + idle = true; + + for (tmp = nv_rd32(dev, 0x400380); tmp && idle; tmp >>= 3) { + if ((tmp & 7) == 1) + idle = false; + } + + for (tmp = nv_rd32(dev, 0x400384); tmp && idle; tmp >>= 3) { + if ((tmp & 7) == 1) + idle = false; + } + + for (tmp = nv_rd32(dev, 0x400388); tmp && idle; tmp >>= 3) { + if ((tmp & 7) == 1) + idle = false; + } + } while (!idle && !(timeout = ptimer->read(dev) - start > 2000000000)); + + if (timeout) { + NV_ERROR(dev, "PGRAPH TLB flush idle timeout fail: " + "0x%08x 0x%08x 0x%08x 0x%08x\n", + nv_rd32(dev, 0x400700), nv_rd32(dev, 0x400380), + nv_rd32(dev, 0x400384), nv_rd32(dev, 0x400388)); + } + + nv50_vm_flush_engine(dev, 0); + + nv_mask(dev, 0x400500, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); +} + +static struct nouveau_enum nv50_mp_exec_error_names[] = { + { 3, "STACK_UNDERFLOW", NULL }, + { 4, "QUADON_ACTIVE", NULL }, + { 8, "TIMEOUT", NULL }, + { 0x10, "INVALID_OPCODE", NULL }, + { 0x40, "BREAKPOINT", NULL }, + {} +}; + +static struct nouveau_bitfield nv50_graph_trap_m2mf[] = { + { 0x00000001, "NOTIFY" }, + { 0x00000002, "IN" }, + { 0x00000004, "OUT" }, + {} +}; + +static struct nouveau_bitfield nv50_graph_trap_vfetch[] = { + { 0x00000001, "FAULT" }, + {} +}; + +static struct nouveau_bitfield nv50_graph_trap_strmout[] = { + { 0x00000001, "FAULT" }, + {} +}; + +static struct nouveau_bitfield nv50_graph_trap_ccache[] = { + { 0x00000001, "FAULT" }, + {} +}; + +/* There must be a *lot* of these. Will take some time to gather them up. */ +struct nouveau_enum nv50_data_error_names[] = { + { 0x00000003, "INVALID_QUERY_OR_TEXTURE", NULL }, + { 0x00000004, "INVALID_VALUE", NULL }, + { 0x00000005, "INVALID_ENUM", NULL }, + { 0x00000008, "INVALID_OBJECT", NULL }, + { 0x00000009, "READ_ONLY_OBJECT", NULL }, + { 0x0000000a, "SUPERVISOR_OBJECT", NULL }, + { 0x0000000b, "INVALID_ADDRESS_ALIGNMENT", NULL }, + { 0x0000000c, "INVALID_BITFIELD", NULL }, + { 0x0000000d, "BEGIN_END_ACTIVE", NULL }, + { 0x0000000e, "SEMANTIC_COLOR_BACK_OVER_LIMIT", NULL }, + { 0x0000000f, "VIEWPORT_ID_NEEDS_GP", NULL }, + { 0x00000010, "RT_DOUBLE_BIND", NULL }, + { 0x00000011, "RT_TYPES_MISMATCH", NULL }, + { 0x00000012, "RT_LINEAR_WITH_ZETA", NULL }, + { 0x00000015, "FP_TOO_FEW_REGS", NULL }, + { 0x00000016, "ZETA_FORMAT_CSAA_MISMATCH", NULL }, + { 0x00000017, "RT_LINEAR_WITH_MSAA", NULL }, + { 0x00000018, "FP_INTERPOLANT_START_OVER_LIMIT", NULL }, + { 0x00000019, "SEMANTIC_LAYER_OVER_LIMIT", NULL }, + { 0x0000001a, "RT_INVALID_ALIGNMENT", NULL }, + { 0x0000001b, "SAMPLER_OVER_LIMIT", NULL }, + { 0x0000001c, "TEXTURE_OVER_LIMIT", NULL }, + { 0x0000001e, "GP_TOO_MANY_OUTPUTS", NULL }, + { 0x0000001f, "RT_BPP128_WITH_MS8", NULL }, + { 0x00000021, "Z_OUT_OF_BOUNDS", NULL }, + { 0x00000023, "XY_OUT_OF_BOUNDS", NULL }, + { 0x00000027, "CP_MORE_PARAMS_THAN_SHARED", NULL }, + { 0x00000028, "CP_NO_REG_SPACE_STRIPED", NULL }, + { 0x00000029, "CP_NO_REG_SPACE_PACKED", NULL }, + { 0x0000002a, "CP_NOT_ENOUGH_WARPS", NULL }, + { 0x0000002b, "CP_BLOCK_SIZE_MISMATCH", NULL }, + { 0x0000002c, "CP_NOT_ENOUGH_LOCAL_WARPS", NULL }, + { 0x0000002d, "CP_NOT_ENOUGH_STACK_WARPS", NULL }, + { 0x0000002e, "CP_NO_BLOCKDIM_LATCH", NULL }, + { 0x00000031, "ENG2D_FORMAT_MISMATCH", NULL }, + { 0x0000003f, "PRIMITIVE_ID_NEEDS_GP", NULL }, + { 0x00000044, "SEMANTIC_VIEWPORT_OVER_LIMIT", NULL }, + { 0x00000045, "SEMANTIC_COLOR_FRONT_OVER_LIMIT", NULL }, + { 0x00000046, "LAYER_ID_NEEDS_GP", NULL }, + { 0x00000047, "SEMANTIC_CLIP_OVER_LIMIT", NULL }, + { 0x00000048, "SEMANTIC_PTSZ_OVER_LIMIT", NULL }, + {} +}; + +static struct nouveau_bitfield nv50_graph_intr[] = { + { 0x00000001, "NOTIFY" }, + { 0x00000002, "COMPUTE_QUERY" }, + { 0x00000010, "ILLEGAL_MTHD" }, + { 0x00000020, "ILLEGAL_CLASS" }, + { 0x00000040, "DOUBLE_NOTIFY" }, + { 0x00001000, "CONTEXT_SWITCH" }, + { 0x00010000, "BUFFER_NOTIFY" }, + { 0x00100000, "DATA_ERROR" }, + { 0x00200000, "TRAP" }, + { 0x01000000, "SINGLE_STEP" }, + {} +}; + +static void +nv50_pgraph_mp_trap(struct drm_device *dev, int tpid, int display) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + uint32_t units = nv_rd32(dev, 0x1540); + uint32_t addr, mp10, status, pc, oplow, ophigh; + int i; + int mps = 0; + for (i = 0; i < 4; i++) { + if (!(units & 1 << (i+24))) + continue; + if (dev_priv->chipset < 0xa0) + addr = 0x408200 + (tpid << 12) + (i << 7); + else + addr = 0x408100 + (tpid << 11) + (i << 7); + mp10 = nv_rd32(dev, addr + 0x10); + status = nv_rd32(dev, addr + 0x14); + if (!status) + continue; + if (display) { + nv_rd32(dev, addr + 0x20); + pc = nv_rd32(dev, addr + 0x24); + oplow = nv_rd32(dev, addr + 0x70); + ophigh = nv_rd32(dev, addr + 0x74); + NV_INFO(dev, "PGRAPH_TRAP_MP_EXEC - " + "TP %d MP %d: ", tpid, i); + nouveau_enum_print(nv50_mp_exec_error_names, status); + printk(" at %06x warp %d, opcode %08x %08x\n", + pc&0xffffff, pc >> 24, + oplow, ophigh); + } + nv_wr32(dev, addr + 0x10, mp10); + nv_wr32(dev, addr + 0x14, 0); + mps++; + } + if (!mps && display) + NV_INFO(dev, "PGRAPH_TRAP_MP_EXEC - TP %d: " + "No MPs claiming errors?\n", tpid); +} + +static void +nv50_pgraph_tp_trap(struct drm_device *dev, int type, uint32_t ustatus_old, + uint32_t ustatus_new, int display, const char *name) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int tps = 0; + uint32_t units = nv_rd32(dev, 0x1540); + int i, r; + uint32_t ustatus_addr, ustatus; + for (i = 0; i < 16; i++) { + if (!(units & (1 << i))) + continue; + if (dev_priv->chipset < 0xa0) + ustatus_addr = ustatus_old + (i << 12); + else + ustatus_addr = ustatus_new + (i << 11); + ustatus = nv_rd32(dev, ustatus_addr) & 0x7fffffff; + if (!ustatus) + continue; + tps++; + switch (type) { + case 6: /* texture error... unknown for now */ + if (display) { + NV_ERROR(dev, "magic set %d:\n", i); + for (r = ustatus_addr + 4; r <= ustatus_addr + 0x10; r += 4) + NV_ERROR(dev, "\t0x%08x: 0x%08x\n", r, + nv_rd32(dev, r)); + } + break; + case 7: /* MP error */ + if (ustatus & 0x04030000) { + nv50_pgraph_mp_trap(dev, i, display); + ustatus &= ~0x04030000; + } + break; + case 8: /* TPDMA error */ + { + uint32_t e0c = nv_rd32(dev, ustatus_addr + 4); + uint32_t e10 = nv_rd32(dev, ustatus_addr + 8); + uint32_t e14 = nv_rd32(dev, ustatus_addr + 0xc); + uint32_t e18 = nv_rd32(dev, ustatus_addr + 0x10); + uint32_t e1c = nv_rd32(dev, ustatus_addr + 0x14); + uint32_t e20 = nv_rd32(dev, ustatus_addr + 0x18); + uint32_t e24 = nv_rd32(dev, ustatus_addr + 0x1c); + /* 2d engine destination */ + if (ustatus & 0x00000010) { + if (display) { + NV_INFO(dev, "PGRAPH_TRAP_TPDMA_2D - TP %d - Unknown fault at address %02x%08x\n", + i, e14, e10); + NV_INFO(dev, "PGRAPH_TRAP_TPDMA_2D - TP %d - e0c: %08x, e18: %08x, e1c: %08x, e20: %08x, e24: %08x\n", + i, e0c, e18, e1c, e20, e24); + } + ustatus &= ~0x00000010; + } + /* Render target */ + if (ustatus & 0x00000040) { + if (display) { + NV_INFO(dev, "PGRAPH_TRAP_TPDMA_RT - TP %d - Unknown fault at address %02x%08x\n", + i, e14, e10); + NV_INFO(dev, "PGRAPH_TRAP_TPDMA_RT - TP %d - e0c: %08x, e18: %08x, e1c: %08x, e20: %08x, e24: %08x\n", + i, e0c, e18, e1c, e20, e24); + } + ustatus &= ~0x00000040; + } + /* CUDA memory: l[], g[] or stack. */ + if (ustatus & 0x00000080) { + if (display) { + if (e18 & 0x80000000) { + /* g[] read fault? */ + NV_INFO(dev, "PGRAPH_TRAP_TPDMA - TP %d - Global read fault at address %02x%08x\n", + i, e14, e10 | ((e18 >> 24) & 0x1f)); + e18 &= ~0x1f000000; + } else if (e18 & 0xc) { + /* g[] write fault? */ + NV_INFO(dev, "PGRAPH_TRAP_TPDMA - TP %d - Global write fault at address %02x%08x\n", + i, e14, e10 | ((e18 >> 7) & 0x1f)); + e18 &= ~0x00000f80; + } else { + NV_INFO(dev, "PGRAPH_TRAP_TPDMA - TP %d - Unknown CUDA fault at address %02x%08x\n", + i, e14, e10); + } + NV_INFO(dev, "PGRAPH_TRAP_TPDMA - TP %d - e0c: %08x, e18: %08x, e1c: %08x, e20: %08x, e24: %08x\n", + i, e0c, e18, e1c, e20, e24); + } + ustatus &= ~0x00000080; + } + } + break; + } + if (ustatus) { + if (display) + NV_INFO(dev, "%s - TP%d: Unhandled ustatus 0x%08x\n", name, i, ustatus); + } + nv_wr32(dev, ustatus_addr, 0xc0000000); + } + + if (!tps && display) + NV_INFO(dev, "%s - No TPs claiming errors?\n", name); +} + +static int +nv50_pgraph_trap_handler(struct drm_device *dev, u32 display, u64 inst, u32 chid) +{ + u32 status = nv_rd32(dev, 0x400108); + u32 ustatus; + + if (!status && display) { + NV_INFO(dev, "PGRAPH - TRAP: no units reporting traps?\n"); + return 1; + } + + /* DISPATCH: Relays commands to other units and handles NOTIFY, + * COND, QUERY. If you get a trap from it, the command is still stuck + * in DISPATCH and you need to do something about it. */ + if (status & 0x001) { + ustatus = nv_rd32(dev, 0x400804) & 0x7fffffff; + if (!ustatus && display) { + NV_INFO(dev, "PGRAPH_TRAP_DISPATCH - no ustatus?\n"); + } + + nv_wr32(dev, 0x400500, 0x00000000); + + /* Known to be triggered by screwed up NOTIFY and COND... */ + if (ustatus & 0x00000001) { + u32 addr = nv_rd32(dev, 0x400808); + u32 subc = (addr & 0x00070000) >> 16; + u32 mthd = (addr & 0x00001ffc); + u32 datal = nv_rd32(dev, 0x40080c); + u32 datah = nv_rd32(dev, 0x400810); + u32 class = nv_rd32(dev, 0x400814); + u32 r848 = nv_rd32(dev, 0x400848); + + NV_INFO(dev, "PGRAPH - TRAP DISPATCH_FAULT\n"); + if (display && (addr & 0x80000000)) { + NV_INFO(dev, "PGRAPH - ch %d (0x%010llx) " + "subc %d class 0x%04x mthd 0x%04x " + "data 0x%08x%08x " + "400808 0x%08x 400848 0x%08x\n", + chid, inst, subc, class, mthd, datah, + datal, addr, r848); + } else + if (display) { + NV_INFO(dev, "PGRAPH - no stuck command?\n"); + } + + nv_wr32(dev, 0x400808, 0); + nv_wr32(dev, 0x4008e8, nv_rd32(dev, 0x4008e8) & 3); + nv_wr32(dev, 0x400848, 0); + ustatus &= ~0x00000001; + } + + if (ustatus & 0x00000002) { + u32 addr = nv_rd32(dev, 0x40084c); + u32 subc = (addr & 0x00070000) >> 16; + u32 mthd = (addr & 0x00001ffc); + u32 data = nv_rd32(dev, 0x40085c); + u32 class = nv_rd32(dev, 0x400814); + + NV_INFO(dev, "PGRAPH - TRAP DISPATCH_QUERY\n"); + if (display && (addr & 0x80000000)) { + NV_INFO(dev, "PGRAPH - ch %d (0x%010llx) " + "subc %d class 0x%04x mthd 0x%04x " + "data 0x%08x 40084c 0x%08x\n", + chid, inst, subc, class, mthd, + data, addr); + } else + if (display) { + NV_INFO(dev, "PGRAPH - no stuck command?\n"); + } + + nv_wr32(dev, 0x40084c, 0); + ustatus &= ~0x00000002; + } + + if (ustatus && display) { + NV_INFO(dev, "PGRAPH - TRAP_DISPATCH (unknown " + "0x%08x)\n", ustatus); + } + + nv_wr32(dev, 0x400804, 0xc0000000); + nv_wr32(dev, 0x400108, 0x001); + status &= ~0x001; + if (!status) + return 0; + } + + /* M2MF: Memory to memory copy engine. */ + if (status & 0x002) { + u32 ustatus = nv_rd32(dev, 0x406800) & 0x7fffffff; + if (display) { + NV_INFO(dev, "PGRAPH - TRAP_M2MF"); + nouveau_bitfield_print(nv50_graph_trap_m2mf, ustatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - TRAP_M2MF %08x %08x %08x %08x\n", + nv_rd32(dev, 0x406804), nv_rd32(dev, 0x406808), + nv_rd32(dev, 0x40680c), nv_rd32(dev, 0x406810)); + + } + + /* No sane way found yet -- just reset the bugger. */ + nv_wr32(dev, 0x400040, 2); + nv_wr32(dev, 0x400040, 0); + nv_wr32(dev, 0x406800, 0xc0000000); + nv_wr32(dev, 0x400108, 0x002); + status &= ~0x002; + } + + /* VFETCH: Fetches data from vertex buffers. */ + if (status & 0x004) { + u32 ustatus = nv_rd32(dev, 0x400c04) & 0x7fffffff; + if (display) { + NV_INFO(dev, "PGRAPH - TRAP_VFETCH"); + nouveau_bitfield_print(nv50_graph_trap_vfetch, ustatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - TRAP_VFETCH %08x %08x %08x %08x\n", + nv_rd32(dev, 0x400c00), nv_rd32(dev, 0x400c08), + nv_rd32(dev, 0x400c0c), nv_rd32(dev, 0x400c10)); + } + + nv_wr32(dev, 0x400c04, 0xc0000000); + nv_wr32(dev, 0x400108, 0x004); + status &= ~0x004; + } + + /* STRMOUT: DirectX streamout / OpenGL transform feedback. */ + if (status & 0x008) { + ustatus = nv_rd32(dev, 0x401800) & 0x7fffffff; + if (display) { + NV_INFO(dev, "PGRAPH - TRAP_STRMOUT"); + nouveau_bitfield_print(nv50_graph_trap_strmout, ustatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - TRAP_STRMOUT %08x %08x %08x %08x\n", + nv_rd32(dev, 0x401804), nv_rd32(dev, 0x401808), + nv_rd32(dev, 0x40180c), nv_rd32(dev, 0x401810)); + + } + + /* No sane way found yet -- just reset the bugger. */ + nv_wr32(dev, 0x400040, 0x80); + nv_wr32(dev, 0x400040, 0); + nv_wr32(dev, 0x401800, 0xc0000000); + nv_wr32(dev, 0x400108, 0x008); + status &= ~0x008; + } + + /* CCACHE: Handles code and c[] caches and fills them. */ + if (status & 0x010) { + ustatus = nv_rd32(dev, 0x405018) & 0x7fffffff; + if (display) { + NV_INFO(dev, "PGRAPH - TRAP_CCACHE"); + nouveau_bitfield_print(nv50_graph_trap_ccache, ustatus); + printk("\n"); + NV_INFO(dev, "PGRAPH - TRAP_CCACHE %08x %08x %08x %08x" + " %08x %08x %08x\n", + nv_rd32(dev, 0x405000), nv_rd32(dev, 0x405004), + nv_rd32(dev, 0x405008), nv_rd32(dev, 0x40500c), + nv_rd32(dev, 0x405010), nv_rd32(dev, 0x405014), + nv_rd32(dev, 0x40501c)); + + } + + nv_wr32(dev, 0x405018, 0xc0000000); + nv_wr32(dev, 0x400108, 0x010); + status &= ~0x010; + } + + /* Unknown, not seen yet... 0x402000 is the only trap status reg + * remaining, so try to handle it anyway. Perhaps related to that + * unknown DMA slot on tesla? */ + if (status & 0x20) { + ustatus = nv_rd32(dev, 0x402000) & 0x7fffffff; + if (display) + NV_INFO(dev, "PGRAPH - TRAP_UNKC04 0x%08x\n", ustatus); + nv_wr32(dev, 0x402000, 0xc0000000); + /* no status modifiction on purpose */ + } + + /* TEXTURE: CUDA texturing units */ + if (status & 0x040) { + nv50_pgraph_tp_trap(dev, 6, 0x408900, 0x408600, display, + "PGRAPH - TRAP_TEXTURE"); + nv_wr32(dev, 0x400108, 0x040); + status &= ~0x040; + } + + /* MP: CUDA execution engines. */ + if (status & 0x080) { + nv50_pgraph_tp_trap(dev, 7, 0x408314, 0x40831c, display, + "PGRAPH - TRAP_MP"); + nv_wr32(dev, 0x400108, 0x080); + status &= ~0x080; + } + + /* TPDMA: Handles TP-initiated uncached memory accesses: + * l[], g[], stack, 2d surfaces, render targets. */ + if (status & 0x100) { + nv50_pgraph_tp_trap(dev, 8, 0x408e08, 0x408708, display, + "PGRAPH - TRAP_TPDMA"); + nv_wr32(dev, 0x400108, 0x100); + status &= ~0x100; + } + + if (status) { + if (display) + NV_INFO(dev, "PGRAPH - TRAP: unknown 0x%08x\n", status); + nv_wr32(dev, 0x400108, status); + } + + return 1; +} + +int +nv50_graph_isr_chid(struct drm_device *dev, u64 inst) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan; + unsigned long flags; + int i; + + spin_lock_irqsave(&dev_priv->channels.lock, flags); + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + chan = dev_priv->channels.ptr[i]; + if (!chan || !chan->ramin) + continue; + + if (inst == chan->ramin->vinst) + break; + } + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + return i; +} + +static void +nv50_graph_isr(struct drm_device *dev) +{ + u32 stat; + + while ((stat = nv_rd32(dev, 0x400100))) { + u64 inst = (u64)(nv_rd32(dev, 0x40032c) & 0x0fffffff) << 12; + u32 chid = nv50_graph_isr_chid(dev, inst); + u32 addr = nv_rd32(dev, NV04_PGRAPH_TRAPPED_ADDR); + u32 subc = (addr & 0x00070000) >> 16; + u32 mthd = (addr & 0x00001ffc); + u32 data = nv_rd32(dev, NV04_PGRAPH_TRAPPED_DATA); + u32 class = nv_rd32(dev, 0x400814); + u32 show = stat; + + if (stat & 0x00000010) { + if (!nouveau_gpuobj_mthd_call2(dev, chid, class, + mthd, data)) + show &= ~0x00000010; + } + + if (stat & 0x00001000) { + nv_wr32(dev, 0x400500, 0x00000000); + nv_wr32(dev, 0x400100, 0x00001000); + nv_mask(dev, 0x40013c, 0x00001000, 0x00000000); + nv50_graph_context_switch(dev); + stat &= ~0x00001000; + show &= ~0x00001000; + } + + show = (show && nouveau_ratelimit()) ? show : 0; + + if (show & 0x00100000) { + u32 ecode = nv_rd32(dev, 0x400110); + NV_INFO(dev, "PGRAPH - DATA_ERROR "); + nouveau_enum_print(nv50_data_error_names, ecode); + printk("\n"); + } + + if (stat & 0x00200000) { + if (!nv50_pgraph_trap_handler(dev, show, inst, chid)) + show &= ~0x00200000; + } + + nv_wr32(dev, 0x400100, stat); + nv_wr32(dev, 0x400500, 0x00010001); + + if (show) { + NV_INFO(dev, "PGRAPH -"); + nouveau_bitfield_print(nv50_graph_intr, show); + printk("\n"); + NV_INFO(dev, "PGRAPH - ch %d (0x%010llx) subc %d " + "class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, subc, class, mthd, data); + nv50_fb_vm_trap(dev, 1); + } + } + + if (nv_rd32(dev, 0x400824) & (1 << 31)) + nv_wr32(dev, 0x400824, nv_rd32(dev, 0x400824) & ~(1 << 31)); +} + +static void +nv50_graph_destroy(struct drm_device *dev, int engine) +{ + struct nv50_graph_engine *pgraph = nv_engine(dev, engine); + + NVOBJ_ENGINE_DEL(dev, GR); + + nouveau_irq_unregister(dev, 12); + kfree(pgraph); +} + +int +nv50_graph_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_graph_engine *pgraph; + struct nouveau_grctx ctx = {}; + int ret; + + pgraph = kzalloc(sizeof(*pgraph),GFP_KERNEL); + if (!pgraph) + return -ENOMEM; + + ctx.dev = dev; + ctx.mode = NOUVEAU_GRCTX_PROG; + ctx.data = pgraph->ctxprog; + ctx.ctxprog_max = ARRAY_SIZE(pgraph->ctxprog); + + ret = nv50_grctx_init(&ctx); + if (ret) { + NV_ERROR(dev, "PGRAPH: ctxprog build failed\n"); + kfree(pgraph); + return 0; + } + + pgraph->grctx_size = ctx.ctxvals_pos * 4; + pgraph->ctxprog_size = ctx.ctxprog_len; + + pgraph->base.destroy = nv50_graph_destroy; + pgraph->base.init = nv50_graph_init; + pgraph->base.fini = nv50_graph_fini; + pgraph->base.context_new = nv50_graph_context_new; + pgraph->base.context_del = nv50_graph_context_del; + pgraph->base.object_new = nv50_graph_object_new; + if (dev_priv->chipset == 0x50 || dev_priv->chipset == 0xac) + pgraph->base.tlb_flush = nv50_graph_tlb_flush; + else + pgraph->base.tlb_flush = nv84_graph_tlb_flush; + + nouveau_irq_register(dev, 12, nv50_graph_isr); + + /* NVSW really doesn't live here... */ + NVOBJ_CLASS(dev, 0x506e, SW); /* nvsw */ + NVOBJ_MTHD (dev, 0x506e, 0x018c, nv50_graph_nvsw_dma_vblsem); + NVOBJ_MTHD (dev, 0x506e, 0x0400, nv50_graph_nvsw_vblsem_offset); + NVOBJ_MTHD (dev, 0x506e, 0x0404, nv50_graph_nvsw_vblsem_release_val); + NVOBJ_MTHD (dev, 0x506e, 0x0408, nv50_graph_nvsw_vblsem_release); + NVOBJ_MTHD (dev, 0x506e, 0x0500, nv50_graph_nvsw_mthd_page_flip); + + NVOBJ_ENGINE_ADD(dev, GR, &pgraph->base); + NVOBJ_CLASS(dev, 0x0030, GR); /* null */ + NVOBJ_CLASS(dev, 0x5039, GR); /* m2mf */ + NVOBJ_CLASS(dev, 0x502d, GR); /* 2d */ + + /* tesla */ + if (dev_priv->chipset == 0x50) + NVOBJ_CLASS(dev, 0x5097, GR); /* tesla (nv50) */ + else + if (dev_priv->chipset < 0xa0) + NVOBJ_CLASS(dev, 0x8297, GR); /* tesla (nv8x/nv9x) */ + else { + switch (dev_priv->chipset) { + case 0xa0: + case 0xaa: + case 0xac: + NVOBJ_CLASS(dev, 0x8397, GR); + break; + case 0xa3: + case 0xa5: + case 0xa8: + NVOBJ_CLASS(dev, 0x8597, GR); + break; + case 0xaf: + NVOBJ_CLASS(dev, 0x8697, GR); + break; + } + } + + /* compute */ + NVOBJ_CLASS(dev, 0x50c0, GR); + if (dev_priv->chipset > 0xa0 && + dev_priv->chipset != 0xaa && + dev_priv->chipset != 0xac) + NVOBJ_CLASS(dev, 0x85c0, GR); + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv50_grctx.c b/drivers/gpu/drm/nouveau/nv50_grctx.c new file mode 100644 index 00000000..4b46d696 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_grctx.c @@ -0,0 +1,3333 @@ +/* + * Copyright 2009 Marcin KoÅ›cielnicki + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#define CP_FLAG_CLEAR 0 +#define CP_FLAG_SET 1 +#define CP_FLAG_SWAP_DIRECTION ((0 * 32) + 0) +#define CP_FLAG_SWAP_DIRECTION_LOAD 0 +#define CP_FLAG_SWAP_DIRECTION_SAVE 1 +#define CP_FLAG_UNK01 ((0 * 32) + 1) +#define CP_FLAG_UNK01_CLEAR 0 +#define CP_FLAG_UNK01_SET 1 +#define CP_FLAG_UNK03 ((0 * 32) + 3) +#define CP_FLAG_UNK03_CLEAR 0 +#define CP_FLAG_UNK03_SET 1 +#define CP_FLAG_USER_SAVE ((0 * 32) + 5) +#define CP_FLAG_USER_SAVE_NOT_PENDING 0 +#define CP_FLAG_USER_SAVE_PENDING 1 +#define CP_FLAG_USER_LOAD ((0 * 32) + 6) +#define CP_FLAG_USER_LOAD_NOT_PENDING 0 +#define CP_FLAG_USER_LOAD_PENDING 1 +#define CP_FLAG_UNK0B ((0 * 32) + 0xb) +#define CP_FLAG_UNK0B_CLEAR 0 +#define CP_FLAG_UNK0B_SET 1 +#define CP_FLAG_XFER_SWITCH ((0 * 32) + 0xe) +#define CP_FLAG_XFER_SWITCH_DISABLE 0 +#define CP_FLAG_XFER_SWITCH_ENABLE 1 +#define CP_FLAG_STATE ((0 * 32) + 0x1c) +#define CP_FLAG_STATE_STOPPED 0 +#define CP_FLAG_STATE_RUNNING 1 +#define CP_FLAG_UNK1D ((0 * 32) + 0x1d) +#define CP_FLAG_UNK1D_CLEAR 0 +#define CP_FLAG_UNK1D_SET 1 +#define CP_FLAG_UNK20 ((1 * 32) + 0) +#define CP_FLAG_UNK20_CLEAR 0 +#define CP_FLAG_UNK20_SET 1 +#define CP_FLAG_STATUS ((2 * 32) + 0) +#define CP_FLAG_STATUS_BUSY 0 +#define CP_FLAG_STATUS_IDLE 1 +#define CP_FLAG_AUTO_SAVE ((2 * 32) + 4) +#define CP_FLAG_AUTO_SAVE_NOT_PENDING 0 +#define CP_FLAG_AUTO_SAVE_PENDING 1 +#define CP_FLAG_AUTO_LOAD ((2 * 32) + 5) +#define CP_FLAG_AUTO_LOAD_NOT_PENDING 0 +#define CP_FLAG_AUTO_LOAD_PENDING 1 +#define CP_FLAG_NEWCTX ((2 * 32) + 10) +#define CP_FLAG_NEWCTX_BUSY 0 +#define CP_FLAG_NEWCTX_DONE 1 +#define CP_FLAG_XFER ((2 * 32) + 11) +#define CP_FLAG_XFER_IDLE 0 +#define CP_FLAG_XFER_BUSY 1 +#define CP_FLAG_ALWAYS ((2 * 32) + 13) +#define CP_FLAG_ALWAYS_FALSE 0 +#define CP_FLAG_ALWAYS_TRUE 1 +#define CP_FLAG_INTR ((2 * 32) + 15) +#define CP_FLAG_INTR_NOT_PENDING 0 +#define CP_FLAG_INTR_PENDING 1 + +#define CP_CTX 0x00100000 +#define CP_CTX_COUNT 0x000f0000 +#define CP_CTX_COUNT_SHIFT 16 +#define CP_CTX_REG 0x00003fff +#define CP_LOAD_SR 0x00200000 +#define CP_LOAD_SR_VALUE 0x000fffff +#define CP_BRA 0x00400000 +#define CP_BRA_IP 0x0001ff00 +#define CP_BRA_IP_SHIFT 8 +#define CP_BRA_IF_CLEAR 0x00000080 +#define CP_BRA_FLAG 0x0000007f +#define CP_WAIT 0x00500000 +#define CP_WAIT_SET 0x00000080 +#define CP_WAIT_FLAG 0x0000007f +#define CP_SET 0x00700000 +#define CP_SET_1 0x00000080 +#define CP_SET_FLAG 0x0000007f +#define CP_NEWCTX 0x00600004 +#define CP_NEXT_TO_SWAP 0x00600005 +#define CP_SET_CONTEXT_POINTER 0x00600006 +#define CP_SET_XFER_POINTER 0x00600007 +#define CP_ENABLE 0x00600009 +#define CP_END 0x0060000c +#define CP_NEXT_TO_CURRENT 0x0060000d +#define CP_DISABLE1 0x0090ffff +#define CP_DISABLE2 0x0091ffff +#define CP_XFER_1 0x008000ff +#define CP_XFER_2 0x008800ff +#define CP_SEEK_1 0x00c000ff +#define CP_SEEK_2 0x00c800ff + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_grctx.h" + +#define IS_NVA3F(x) (((x) > 0xa0 && (x) < 0xaa) || (x) == 0xaf) +#define IS_NVAAF(x) ((x) >= 0xaa && (x) <= 0xac) + +/* + * This code deals with PGRAPH contexts on NV50 family cards. Like NV40, it's + * the GPU itself that does context-switching, but it needs a special + * microcode to do it. And it's the driver's task to supply this microcode, + * further known as ctxprog, as well as the initial context values, known + * as ctxvals. + * + * Without ctxprog, you cannot switch contexts. Not even in software, since + * the majority of context [xfer strands] isn't accessible directly. You're + * stuck with a single channel, and you also suffer all the problems resulting + * from missing ctxvals, since you cannot load them. + * + * Without ctxvals, you're stuck with PGRAPH's default context. It's enough to + * run 2d operations, but trying to utilise 3d or CUDA will just lock you up, + * since you don't have... some sort of needed setup. + * + * Nouveau will just disable acceleration if not given ctxprog + ctxvals, since + * it's too much hassle to handle no-ctxprog as a special case. + */ + +/* + * How ctxprogs work. + * + * The ctxprog is written in its own kind of microcode, with very small and + * crappy set of available commands. You upload it to a small [512 insns] + * area of memory on PGRAPH, and it'll be run when PFIFO wants PGRAPH to + * switch channel. or when the driver explicitely requests it. Stuff visible + * to ctxprog consists of: PGRAPH MMIO registers, PGRAPH context strands, + * the per-channel context save area in VRAM [known as ctxvals or grctx], + * 4 flags registers, a scratch register, two grctx pointers, plus many + * random poorly-understood details. + * + * When ctxprog runs, it's supposed to check what operations are asked of it, + * save old context if requested, optionally reset PGRAPH and switch to the + * new channel, and load the new context. Context consists of three major + * parts: subset of MMIO registers and two "xfer areas". + */ + +/* TODO: + * - document unimplemented bits compared to nvidia + * - NVAx: make a TP subroutine, use it. + * - use 0x4008fc instead of 0x1540? + */ + +enum cp_label { + cp_check_load = 1, + cp_setup_auto_load, + cp_setup_load, + cp_setup_save, + cp_swap_state, + cp_prepare_exit, + cp_exit, +}; + +static void nv50_graph_construct_mmio(struct nouveau_grctx *ctx); +static void nv50_graph_construct_xfer1(struct nouveau_grctx *ctx); +static void nv50_graph_construct_xfer2(struct nouveau_grctx *ctx); + +/* Main function: construct the ctxprog skeleton, call the other functions. */ + +int +nv50_grctx_init(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + + switch (dev_priv->chipset) { + case 0x50: + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0x98: + case 0xa0: + case 0xa3: + case 0xa5: + case 0xa8: + case 0xaa: + case 0xac: + case 0xaf: + break; + default: + NV_ERROR(ctx->dev, "I don't know how to make a ctxprog for " + "your NV%x card.\n", dev_priv->chipset); + NV_ERROR(ctx->dev, "Disabling acceleration. Please contact " + "the devs.\n"); + return -ENOSYS; + } + + cp_set (ctx, STATE, RUNNING); + cp_set (ctx, XFER_SWITCH, ENABLE); + /* decide whether we're loading/unloading the context */ + cp_bra (ctx, AUTO_SAVE, PENDING, cp_setup_save); + cp_bra (ctx, USER_SAVE, PENDING, cp_setup_save); + + cp_name(ctx, cp_check_load); + cp_bra (ctx, AUTO_LOAD, PENDING, cp_setup_auto_load); + cp_bra (ctx, USER_LOAD, PENDING, cp_setup_load); + cp_bra (ctx, ALWAYS, TRUE, cp_exit); + + /* setup for context load */ + cp_name(ctx, cp_setup_auto_load); + cp_out (ctx, CP_DISABLE1); + cp_out (ctx, CP_DISABLE2); + cp_out (ctx, CP_ENABLE); + cp_out (ctx, CP_NEXT_TO_SWAP); + cp_set (ctx, UNK01, SET); + cp_name(ctx, cp_setup_load); + cp_out (ctx, CP_NEWCTX); + cp_wait(ctx, NEWCTX, BUSY); + cp_set (ctx, UNK1D, CLEAR); + cp_set (ctx, SWAP_DIRECTION, LOAD); + cp_bra (ctx, UNK0B, SET, cp_prepare_exit); + cp_bra (ctx, ALWAYS, TRUE, cp_swap_state); + + /* setup for context save */ + cp_name(ctx, cp_setup_save); + cp_set (ctx, UNK1D, SET); + cp_wait(ctx, STATUS, BUSY); + cp_wait(ctx, INTR, PENDING); + cp_bra (ctx, STATUS, BUSY, cp_setup_save); + cp_set (ctx, UNK01, SET); + cp_set (ctx, SWAP_DIRECTION, SAVE); + + /* general PGRAPH state */ + cp_name(ctx, cp_swap_state); + cp_set (ctx, UNK03, SET); + cp_pos (ctx, 0x00004/4); + cp_ctx (ctx, 0x400828, 1); /* needed. otherwise, flickering happens. */ + cp_pos (ctx, 0x00100/4); + nv50_graph_construct_mmio(ctx); + nv50_graph_construct_xfer1(ctx); + nv50_graph_construct_xfer2(ctx); + + cp_bra (ctx, SWAP_DIRECTION, SAVE, cp_check_load); + + cp_set (ctx, UNK20, SET); + cp_set (ctx, SWAP_DIRECTION, SAVE); /* no idea why this is needed, but fixes at least one lockup. */ + cp_lsr (ctx, ctx->ctxvals_base); + cp_out (ctx, CP_SET_XFER_POINTER); + cp_lsr (ctx, 4); + cp_out (ctx, CP_SEEK_1); + cp_out (ctx, CP_XFER_1); + cp_wait(ctx, XFER, BUSY); + + /* pre-exit state updates */ + cp_name(ctx, cp_prepare_exit); + cp_set (ctx, UNK01, CLEAR); + cp_set (ctx, UNK03, CLEAR); + cp_set (ctx, UNK1D, CLEAR); + + cp_bra (ctx, USER_SAVE, PENDING, cp_exit); + cp_out (ctx, CP_NEXT_TO_CURRENT); + + cp_name(ctx, cp_exit); + cp_set (ctx, USER_SAVE, NOT_PENDING); + cp_set (ctx, USER_LOAD, NOT_PENDING); + cp_set (ctx, XFER_SWITCH, DISABLE); + cp_set (ctx, STATE, STOPPED); + cp_out (ctx, CP_END); + ctx->ctxvals_pos += 0x400; /* padding... no idea why you need it */ + + return 0; +} + +/* + * Constructs MMIO part of ctxprog and ctxvals. Just a matter of knowing which + * registers to save/restore and the default values for them. + */ + +static void +nv50_graph_construct_mmio_ddata(struct nouveau_grctx *ctx); + +static void +nv50_graph_construct_mmio(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i, j; + int offset, base; + uint32_t units = nv_rd32 (ctx->dev, 0x1540); + + /* 0800: DISPATCH */ + cp_ctx(ctx, 0x400808, 7); + gr_def(ctx, 0x400814, 0x00000030); + cp_ctx(ctx, 0x400834, 0x32); + if (dev_priv->chipset == 0x50) { + gr_def(ctx, 0x400834, 0xff400040); + gr_def(ctx, 0x400838, 0xfff00080); + gr_def(ctx, 0x40083c, 0xfff70090); + gr_def(ctx, 0x400840, 0xffe806a8); + } + gr_def(ctx, 0x400844, 0x00000002); + if (IS_NVA3F(dev_priv->chipset)) + gr_def(ctx, 0x400894, 0x00001000); + gr_def(ctx, 0x4008e8, 0x00000003); + gr_def(ctx, 0x4008ec, 0x00001000); + if (dev_priv->chipset == 0x50) + cp_ctx(ctx, 0x400908, 0xb); + else if (dev_priv->chipset < 0xa0) + cp_ctx(ctx, 0x400908, 0xc); + else + cp_ctx(ctx, 0x400908, 0xe); + + if (dev_priv->chipset >= 0xa0) + cp_ctx(ctx, 0x400b00, 0x1); + if (IS_NVA3F(dev_priv->chipset)) { + cp_ctx(ctx, 0x400b10, 0x1); + gr_def(ctx, 0x400b10, 0x0001629d); + cp_ctx(ctx, 0x400b20, 0x1); + gr_def(ctx, 0x400b20, 0x0001629d); + } + + nv50_graph_construct_mmio_ddata(ctx); + + /* 0C00: VFETCH */ + cp_ctx(ctx, 0x400c08, 0x2); + gr_def(ctx, 0x400c08, 0x0000fe0c); + + /* 1000 */ + if (dev_priv->chipset < 0xa0) { + cp_ctx(ctx, 0x401008, 0x4); + gr_def(ctx, 0x401014, 0x00001000); + } else if (!IS_NVA3F(dev_priv->chipset)) { + cp_ctx(ctx, 0x401008, 0x5); + gr_def(ctx, 0x401018, 0x00001000); + } else { + cp_ctx(ctx, 0x401008, 0x5); + gr_def(ctx, 0x401018, 0x00004000); + } + + /* 1400 */ + cp_ctx(ctx, 0x401400, 0x8); + cp_ctx(ctx, 0x401424, 0x3); + if (dev_priv->chipset == 0x50) + gr_def(ctx, 0x40142c, 0x0001fd87); + else + gr_def(ctx, 0x40142c, 0x00000187); + cp_ctx(ctx, 0x401540, 0x5); + gr_def(ctx, 0x401550, 0x00001018); + + /* 1800: STREAMOUT */ + cp_ctx(ctx, 0x401814, 0x1); + gr_def(ctx, 0x401814, 0x000000ff); + if (dev_priv->chipset == 0x50) { + cp_ctx(ctx, 0x40181c, 0xe); + gr_def(ctx, 0x401850, 0x00000004); + } else if (dev_priv->chipset < 0xa0) { + cp_ctx(ctx, 0x40181c, 0xf); + gr_def(ctx, 0x401854, 0x00000004); + } else { + cp_ctx(ctx, 0x40181c, 0x13); + gr_def(ctx, 0x401864, 0x00000004); + } + + /* 1C00 */ + cp_ctx(ctx, 0x401c00, 0x1); + switch (dev_priv->chipset) { + case 0x50: + gr_def(ctx, 0x401c00, 0x0001005f); + break; + case 0x84: + case 0x86: + case 0x94: + gr_def(ctx, 0x401c00, 0x044d00df); + break; + case 0x92: + case 0x96: + case 0x98: + case 0xa0: + case 0xaa: + case 0xac: + gr_def(ctx, 0x401c00, 0x042500df); + break; + case 0xa3: + case 0xa5: + case 0xa8: + case 0xaf: + gr_def(ctx, 0x401c00, 0x142500df); + break; + } + + /* 2000 */ + + /* 2400 */ + cp_ctx(ctx, 0x402400, 0x1); + if (dev_priv->chipset == 0x50) + cp_ctx(ctx, 0x402408, 0x1); + else + cp_ctx(ctx, 0x402408, 0x2); + gr_def(ctx, 0x402408, 0x00000600); + + /* 2800: CSCHED */ + cp_ctx(ctx, 0x402800, 0x1); + if (dev_priv->chipset == 0x50) + gr_def(ctx, 0x402800, 0x00000006); + + /* 2C00: ZCULL */ + cp_ctx(ctx, 0x402c08, 0x6); + if (dev_priv->chipset != 0x50) + gr_def(ctx, 0x402c14, 0x01000000); + gr_def(ctx, 0x402c18, 0x000000ff); + if (dev_priv->chipset == 0x50) + cp_ctx(ctx, 0x402ca0, 0x1); + else + cp_ctx(ctx, 0x402ca0, 0x2); + if (dev_priv->chipset < 0xa0) + gr_def(ctx, 0x402ca0, 0x00000400); + else if (!IS_NVA3F(dev_priv->chipset)) + gr_def(ctx, 0x402ca0, 0x00000800); + else + gr_def(ctx, 0x402ca0, 0x00000400); + cp_ctx(ctx, 0x402cac, 0x4); + + /* 3000: ENG2D */ + cp_ctx(ctx, 0x403004, 0x1); + gr_def(ctx, 0x403004, 0x00000001); + + /* 3400 */ + if (dev_priv->chipset >= 0xa0) { + cp_ctx(ctx, 0x403404, 0x1); + gr_def(ctx, 0x403404, 0x00000001); + } + + /* 5000: CCACHE */ + cp_ctx(ctx, 0x405000, 0x1); + switch (dev_priv->chipset) { + case 0x50: + gr_def(ctx, 0x405000, 0x00300080); + break; + case 0x84: + case 0xa0: + case 0xa3: + case 0xa5: + case 0xa8: + case 0xaa: + case 0xac: + case 0xaf: + gr_def(ctx, 0x405000, 0x000e0080); + break; + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0x98: + gr_def(ctx, 0x405000, 0x00000080); + break; + } + cp_ctx(ctx, 0x405014, 0x1); + gr_def(ctx, 0x405014, 0x00000004); + cp_ctx(ctx, 0x40501c, 0x1); + cp_ctx(ctx, 0x405024, 0x1); + cp_ctx(ctx, 0x40502c, 0x1); + + /* 6000? */ + if (dev_priv->chipset == 0x50) + cp_ctx(ctx, 0x4063e0, 0x1); + + /* 6800: M2MF */ + if (dev_priv->chipset < 0x90) { + cp_ctx(ctx, 0x406814, 0x2b); + gr_def(ctx, 0x406818, 0x00000f80); + gr_def(ctx, 0x406860, 0x007f0080); + gr_def(ctx, 0x40689c, 0x007f0080); + } else { + cp_ctx(ctx, 0x406814, 0x4); + if (dev_priv->chipset == 0x98) + gr_def(ctx, 0x406818, 0x00000f80); + else + gr_def(ctx, 0x406818, 0x00001f80); + if (IS_NVA3F(dev_priv->chipset)) + gr_def(ctx, 0x40681c, 0x00000030); + cp_ctx(ctx, 0x406830, 0x3); + } + + /* 7000: per-ROP group state */ + for (i = 0; i < 8; i++) { + if (units & (1<<(i+16))) { + cp_ctx(ctx, 0x407000 + (i<<8), 3); + if (dev_priv->chipset == 0x50) + gr_def(ctx, 0x407000 + (i<<8), 0x1b74f820); + else if (dev_priv->chipset != 0xa5) + gr_def(ctx, 0x407000 + (i<<8), 0x3b74f821); + else + gr_def(ctx, 0x407000 + (i<<8), 0x7b74f821); + gr_def(ctx, 0x407004 + (i<<8), 0x89058001); + + if (dev_priv->chipset == 0x50) { + cp_ctx(ctx, 0x407010 + (i<<8), 1); + } else if (dev_priv->chipset < 0xa0) { + cp_ctx(ctx, 0x407010 + (i<<8), 2); + gr_def(ctx, 0x407010 + (i<<8), 0x00001000); + gr_def(ctx, 0x407014 + (i<<8), 0x0000001f); + } else { + cp_ctx(ctx, 0x407010 + (i<<8), 3); + gr_def(ctx, 0x407010 + (i<<8), 0x00001000); + if (dev_priv->chipset != 0xa5) + gr_def(ctx, 0x407014 + (i<<8), 0x000000ff); + else + gr_def(ctx, 0x407014 + (i<<8), 0x000001ff); + } + + cp_ctx(ctx, 0x407080 + (i<<8), 4); + if (dev_priv->chipset != 0xa5) + gr_def(ctx, 0x407080 + (i<<8), 0x027c10fa); + else + gr_def(ctx, 0x407080 + (i<<8), 0x827c10fa); + if (dev_priv->chipset == 0x50) + gr_def(ctx, 0x407084 + (i<<8), 0x000000c0); + else + gr_def(ctx, 0x407084 + (i<<8), 0x400000c0); + gr_def(ctx, 0x407088 + (i<<8), 0xb7892080); + + if (dev_priv->chipset < 0xa0) + cp_ctx(ctx, 0x407094 + (i<<8), 1); + else if (!IS_NVA3F(dev_priv->chipset)) + cp_ctx(ctx, 0x407094 + (i<<8), 3); + else { + cp_ctx(ctx, 0x407094 + (i<<8), 4); + gr_def(ctx, 0x4070a0 + (i<<8), 1); + } + } + } + + cp_ctx(ctx, 0x407c00, 0x3); + if (dev_priv->chipset < 0x90) + gr_def(ctx, 0x407c00, 0x00010040); + else if (dev_priv->chipset < 0xa0) + gr_def(ctx, 0x407c00, 0x00390040); + else + gr_def(ctx, 0x407c00, 0x003d0040); + gr_def(ctx, 0x407c08, 0x00000022); + if (dev_priv->chipset >= 0xa0) { + cp_ctx(ctx, 0x407c10, 0x3); + cp_ctx(ctx, 0x407c20, 0x1); + cp_ctx(ctx, 0x407c2c, 0x1); + } + + if (dev_priv->chipset < 0xa0) { + cp_ctx(ctx, 0x407d00, 0x9); + } else { + cp_ctx(ctx, 0x407d00, 0x15); + } + if (dev_priv->chipset == 0x98) + gr_def(ctx, 0x407d08, 0x00380040); + else { + if (dev_priv->chipset < 0x90) + gr_def(ctx, 0x407d08, 0x00010040); + else if (dev_priv->chipset < 0xa0) + gr_def(ctx, 0x407d08, 0x00390040); + else + gr_def(ctx, 0x407d08, 0x003d0040); + gr_def(ctx, 0x407d0c, 0x00000022); + } + + /* 8000+: per-TP state */ + for (i = 0; i < 10; i++) { + if (units & (1<<i)) { + if (dev_priv->chipset < 0xa0) + base = 0x408000 + (i<<12); + else + base = 0x408000 + (i<<11); + if (dev_priv->chipset < 0xa0) + offset = base + 0xc00; + else + offset = base + 0x80; + cp_ctx(ctx, offset + 0x00, 1); + gr_def(ctx, offset + 0x00, 0x0000ff0a); + cp_ctx(ctx, offset + 0x08, 1); + + /* per-MP state */ + for (j = 0; j < (dev_priv->chipset < 0xa0 ? 2 : 4); j++) { + if (!(units & (1 << (j+24)))) continue; + if (dev_priv->chipset < 0xa0) + offset = base + 0x200 + (j<<7); + else + offset = base + 0x100 + (j<<7); + cp_ctx(ctx, offset, 0x20); + gr_def(ctx, offset + 0x00, 0x01800000); + gr_def(ctx, offset + 0x04, 0x00160000); + gr_def(ctx, offset + 0x08, 0x01800000); + gr_def(ctx, offset + 0x18, 0x0003ffff); + switch (dev_priv->chipset) { + case 0x50: + gr_def(ctx, offset + 0x1c, 0x00080000); + break; + case 0x84: + gr_def(ctx, offset + 0x1c, 0x00880000); + break; + case 0x86: + gr_def(ctx, offset + 0x1c, 0x018c0000); + break; + case 0x92: + case 0x96: + case 0x98: + gr_def(ctx, offset + 0x1c, 0x118c0000); + break; + case 0x94: + gr_def(ctx, offset + 0x1c, 0x10880000); + break; + case 0xa0: + case 0xa5: + gr_def(ctx, offset + 0x1c, 0x310c0000); + break; + case 0xa3: + case 0xa8: + case 0xaa: + case 0xac: + case 0xaf: + gr_def(ctx, offset + 0x1c, 0x300c0000); + break; + } + gr_def(ctx, offset + 0x40, 0x00010401); + if (dev_priv->chipset == 0x50) + gr_def(ctx, offset + 0x48, 0x00000040); + else + gr_def(ctx, offset + 0x48, 0x00000078); + gr_def(ctx, offset + 0x50, 0x000000bf); + gr_def(ctx, offset + 0x58, 0x00001210); + if (dev_priv->chipset == 0x50) + gr_def(ctx, offset + 0x5c, 0x00000080); + else + gr_def(ctx, offset + 0x5c, 0x08000080); + if (dev_priv->chipset >= 0xa0) + gr_def(ctx, offset + 0x68, 0x0000003e); + } + + if (dev_priv->chipset < 0xa0) + cp_ctx(ctx, base + 0x300, 0x4); + else + cp_ctx(ctx, base + 0x300, 0x5); + if (dev_priv->chipset == 0x50) + gr_def(ctx, base + 0x304, 0x00007070); + else if (dev_priv->chipset < 0xa0) + gr_def(ctx, base + 0x304, 0x00027070); + else if (!IS_NVA3F(dev_priv->chipset)) + gr_def(ctx, base + 0x304, 0x01127070); + else + gr_def(ctx, base + 0x304, 0x05127070); + + if (dev_priv->chipset < 0xa0) + cp_ctx(ctx, base + 0x318, 1); + else + cp_ctx(ctx, base + 0x320, 1); + if (dev_priv->chipset == 0x50) + gr_def(ctx, base + 0x318, 0x0003ffff); + else if (dev_priv->chipset < 0xa0) + gr_def(ctx, base + 0x318, 0x03ffffff); + else + gr_def(ctx, base + 0x320, 0x07ffffff); + + if (dev_priv->chipset < 0xa0) + cp_ctx(ctx, base + 0x324, 5); + else + cp_ctx(ctx, base + 0x328, 4); + + if (dev_priv->chipset < 0xa0) { + cp_ctx(ctx, base + 0x340, 9); + offset = base + 0x340; + } else if (!IS_NVA3F(dev_priv->chipset)) { + cp_ctx(ctx, base + 0x33c, 0xb); + offset = base + 0x344; + } else { + cp_ctx(ctx, base + 0x33c, 0xd); + offset = base + 0x344; + } + gr_def(ctx, offset + 0x0, 0x00120407); + gr_def(ctx, offset + 0x4, 0x05091507); + if (dev_priv->chipset == 0x84) + gr_def(ctx, offset + 0x8, 0x05100202); + else + gr_def(ctx, offset + 0x8, 0x05010202); + gr_def(ctx, offset + 0xc, 0x00030201); + if (dev_priv->chipset == 0xa3) + cp_ctx(ctx, base + 0x36c, 1); + + cp_ctx(ctx, base + 0x400, 2); + gr_def(ctx, base + 0x404, 0x00000040); + cp_ctx(ctx, base + 0x40c, 2); + gr_def(ctx, base + 0x40c, 0x0d0c0b0a); + gr_def(ctx, base + 0x410, 0x00141210); + + if (dev_priv->chipset < 0xa0) + offset = base + 0x800; + else + offset = base + 0x500; + cp_ctx(ctx, offset, 6); + gr_def(ctx, offset + 0x0, 0x000001f0); + gr_def(ctx, offset + 0x4, 0x00000001); + gr_def(ctx, offset + 0x8, 0x00000003); + if (dev_priv->chipset == 0x50 || IS_NVAAF(dev_priv->chipset)) + gr_def(ctx, offset + 0xc, 0x00008000); + gr_def(ctx, offset + 0x14, 0x00039e00); + cp_ctx(ctx, offset + 0x1c, 2); + if (dev_priv->chipset == 0x50) + gr_def(ctx, offset + 0x1c, 0x00000040); + else + gr_def(ctx, offset + 0x1c, 0x00000100); + gr_def(ctx, offset + 0x20, 0x00003800); + + if (dev_priv->chipset >= 0xa0) { + cp_ctx(ctx, base + 0x54c, 2); + if (!IS_NVA3F(dev_priv->chipset)) + gr_def(ctx, base + 0x54c, 0x003fe006); + else + gr_def(ctx, base + 0x54c, 0x003fe007); + gr_def(ctx, base + 0x550, 0x003fe000); + } + + if (dev_priv->chipset < 0xa0) + offset = base + 0xa00; + else + offset = base + 0x680; + cp_ctx(ctx, offset, 1); + gr_def(ctx, offset, 0x00404040); + + if (dev_priv->chipset < 0xa0) + offset = base + 0xe00; + else + offset = base + 0x700; + cp_ctx(ctx, offset, 2); + if (dev_priv->chipset < 0xa0) + gr_def(ctx, offset, 0x0077f005); + else if (dev_priv->chipset == 0xa5) + gr_def(ctx, offset, 0x6cf7f007); + else if (dev_priv->chipset == 0xa8) + gr_def(ctx, offset, 0x6cfff007); + else if (dev_priv->chipset == 0xac) + gr_def(ctx, offset, 0x0cfff007); + else + gr_def(ctx, offset, 0x0cf7f007); + if (dev_priv->chipset == 0x50) + gr_def(ctx, offset + 0x4, 0x00007fff); + else if (dev_priv->chipset < 0xa0) + gr_def(ctx, offset + 0x4, 0x003f7fff); + else + gr_def(ctx, offset + 0x4, 0x02bf7fff); + cp_ctx(ctx, offset + 0x2c, 1); + if (dev_priv->chipset == 0x50) { + cp_ctx(ctx, offset + 0x50, 9); + gr_def(ctx, offset + 0x54, 0x000003ff); + gr_def(ctx, offset + 0x58, 0x00000003); + gr_def(ctx, offset + 0x5c, 0x00000003); + gr_def(ctx, offset + 0x60, 0x000001ff); + gr_def(ctx, offset + 0x64, 0x0000001f); + gr_def(ctx, offset + 0x68, 0x0000000f); + gr_def(ctx, offset + 0x6c, 0x0000000f); + } else if (dev_priv->chipset < 0xa0) { + cp_ctx(ctx, offset + 0x50, 1); + cp_ctx(ctx, offset + 0x70, 1); + } else { + cp_ctx(ctx, offset + 0x50, 1); + cp_ctx(ctx, offset + 0x60, 5); + } + } + } +} + +static void +dd_emit(struct nouveau_grctx *ctx, int num, uint32_t val) { + int i; + if (val && ctx->mode == NOUVEAU_GRCTX_VALS) + for (i = 0; i < num; i++) + nv_wo32(ctx->data, 4 * (ctx->ctxvals_pos + i), val); + ctx->ctxvals_pos += num; +} + +static void +nv50_graph_construct_mmio_ddata(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int base, num; + base = ctx->ctxvals_pos; + + /* tesla state */ + dd_emit(ctx, 1, 0); /* 00000001 UNK0F90 */ + dd_emit(ctx, 1, 0); /* 00000001 UNK135C */ + + /* SRC_TIC state */ + dd_emit(ctx, 1, 0); /* 00000007 SRC_TILE_MODE_Z */ + dd_emit(ctx, 1, 2); /* 00000007 SRC_TILE_MODE_Y */ + dd_emit(ctx, 1, 1); /* 00000001 SRC_LINEAR #1 */ + dd_emit(ctx, 1, 0); /* 000000ff SRC_ADDRESS_HIGH */ + dd_emit(ctx, 1, 0); /* 00000001 SRC_SRGB */ + if (dev_priv->chipset >= 0x94) + dd_emit(ctx, 1, 0); /* 00000003 eng2d UNK0258 */ + dd_emit(ctx, 1, 1); /* 00000fff SRC_DEPTH */ + dd_emit(ctx, 1, 0x100); /* 0000ffff SRC_HEIGHT */ + + /* turing state */ + dd_emit(ctx, 1, 0); /* 0000000f TEXTURES_LOG2 */ + dd_emit(ctx, 1, 0); /* 0000000f SAMPLERS_LOG2 */ + dd_emit(ctx, 1, 0); /* 000000ff CB_DEF_ADDRESS_HIGH */ + dd_emit(ctx, 1, 0); /* ffffffff CB_DEF_ADDRESS_LOW */ + dd_emit(ctx, 1, 0); /* ffffffff SHARED_SIZE */ + dd_emit(ctx, 1, 2); /* ffffffff REG_MODE */ + dd_emit(ctx, 1, 1); /* 0000ffff BLOCK_ALLOC_THREADS */ + dd_emit(ctx, 1, 1); /* 00000001 LANES32 */ + dd_emit(ctx, 1, 0); /* 000000ff UNK370 */ + dd_emit(ctx, 1, 0); /* 000000ff USER_PARAM_UNK */ + dd_emit(ctx, 1, 0); /* 000000ff USER_PARAM_COUNT */ + dd_emit(ctx, 1, 1); /* 000000ff UNK384 bits 8-15 */ + dd_emit(ctx, 1, 0x3fffff); /* 003fffff TIC_LIMIT */ + dd_emit(ctx, 1, 0x1fff); /* 000fffff TSC_LIMIT */ + dd_emit(ctx, 1, 0); /* 0000ffff CB_ADDR_INDEX */ + dd_emit(ctx, 1, 1); /* 000007ff BLOCKDIM_X */ + dd_emit(ctx, 1, 1); /* 000007ff BLOCKDIM_XMY */ + dd_emit(ctx, 1, 0); /* 00000001 BLOCKDIM_XMY_OVERFLOW */ + dd_emit(ctx, 1, 1); /* 0003ffff BLOCKDIM_XMYMZ */ + dd_emit(ctx, 1, 1); /* 000007ff BLOCKDIM_Y */ + dd_emit(ctx, 1, 1); /* 0000007f BLOCKDIM_Z */ + dd_emit(ctx, 1, 4); /* 000000ff CP_REG_ALLOC_TEMP */ + dd_emit(ctx, 1, 1); /* 00000001 BLOCKDIM_DIRTY */ + if (IS_NVA3F(dev_priv->chipset)) + dd_emit(ctx, 1, 0); /* 00000003 UNK03E8 */ + dd_emit(ctx, 1, 1); /* 0000007f BLOCK_ALLOC_HALFWARPS */ + dd_emit(ctx, 1, 1); /* 00000007 LOCAL_WARPS_NO_CLAMP */ + dd_emit(ctx, 1, 7); /* 00000007 LOCAL_WARPS_LOG_ALLOC */ + dd_emit(ctx, 1, 1); /* 00000007 STACK_WARPS_NO_CLAMP */ + dd_emit(ctx, 1, 7); /* 00000007 STACK_WARPS_LOG_ALLOC */ + dd_emit(ctx, 1, 1); /* 00001fff BLOCK_ALLOC_REGSLOTS_PACKED */ + dd_emit(ctx, 1, 1); /* 00001fff BLOCK_ALLOC_REGSLOTS_STRIDED */ + dd_emit(ctx, 1, 1); /* 000007ff BLOCK_ALLOC_THREADS */ + + /* compat 2d state */ + if (dev_priv->chipset == 0x50) { + dd_emit(ctx, 4, 0); /* 0000ffff clip X, Y, W, H */ + + dd_emit(ctx, 1, 1); /* ffffffff chroma COLOR_FORMAT */ + + dd_emit(ctx, 1, 1); /* ffffffff pattern COLOR_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff pattern SHAPE */ + dd_emit(ctx, 1, 1); /* ffffffff pattern PATTERN_SELECT */ + + dd_emit(ctx, 1, 0xa); /* ffffffff surf2d SRC_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff surf2d DMA_SRC */ + dd_emit(ctx, 1, 0); /* 000000ff surf2d SRC_ADDRESS_HIGH */ + dd_emit(ctx, 1, 0); /* ffffffff surf2d SRC_ADDRESS_LOW */ + dd_emit(ctx, 1, 0x40); /* 0000ffff surf2d SRC_PITCH */ + dd_emit(ctx, 1, 0); /* 0000000f surf2d SRC_TILE_MODE_Z */ + dd_emit(ctx, 1, 2); /* 0000000f surf2d SRC_TILE_MODE_Y */ + dd_emit(ctx, 1, 0x100); /* ffffffff surf2d SRC_HEIGHT */ + dd_emit(ctx, 1, 1); /* 00000001 surf2d SRC_LINEAR */ + dd_emit(ctx, 1, 0x100); /* ffffffff surf2d SRC_WIDTH */ + + dd_emit(ctx, 1, 0); /* 0000ffff gdirect CLIP_B_X */ + dd_emit(ctx, 1, 0); /* 0000ffff gdirect CLIP_B_Y */ + dd_emit(ctx, 1, 0); /* 0000ffff gdirect CLIP_C_X */ + dd_emit(ctx, 1, 0); /* 0000ffff gdirect CLIP_C_Y */ + dd_emit(ctx, 1, 0); /* 0000ffff gdirect CLIP_D_X */ + dd_emit(ctx, 1, 0); /* 0000ffff gdirect CLIP_D_Y */ + dd_emit(ctx, 1, 1); /* ffffffff gdirect COLOR_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff gdirect OPERATION */ + dd_emit(ctx, 1, 0); /* 0000ffff gdirect POINT_X */ + dd_emit(ctx, 1, 0); /* 0000ffff gdirect POINT_Y */ + + dd_emit(ctx, 1, 0); /* 0000ffff blit SRC_Y */ + dd_emit(ctx, 1, 0); /* ffffffff blit OPERATION */ + + dd_emit(ctx, 1, 0); /* ffffffff ifc OPERATION */ + + dd_emit(ctx, 1, 0); /* ffffffff iifc INDEX_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff iifc LUT_OFFSET */ + dd_emit(ctx, 1, 4); /* ffffffff iifc COLOR_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff iifc OPERATION */ + } + + /* m2mf state */ + dd_emit(ctx, 1, 0); /* ffffffff m2mf LINE_COUNT */ + dd_emit(ctx, 1, 0); /* ffffffff m2mf LINE_LENGTH_IN */ + dd_emit(ctx, 2, 0); /* ffffffff m2mf OFFSET_IN, OFFSET_OUT */ + dd_emit(ctx, 1, 1); /* ffffffff m2mf TILING_DEPTH_OUT */ + dd_emit(ctx, 1, 0x100); /* ffffffff m2mf TILING_HEIGHT_OUT */ + dd_emit(ctx, 1, 0); /* ffffffff m2mf TILING_POSITION_OUT_Z */ + dd_emit(ctx, 1, 1); /* 00000001 m2mf LINEAR_OUT */ + dd_emit(ctx, 2, 0); /* 0000ffff m2mf TILING_POSITION_OUT_X, Y */ + dd_emit(ctx, 1, 0x100); /* ffffffff m2mf TILING_PITCH_OUT */ + dd_emit(ctx, 1, 1); /* ffffffff m2mf TILING_DEPTH_IN */ + dd_emit(ctx, 1, 0x100); /* ffffffff m2mf TILING_HEIGHT_IN */ + dd_emit(ctx, 1, 0); /* ffffffff m2mf TILING_POSITION_IN_Z */ + dd_emit(ctx, 1, 1); /* 00000001 m2mf LINEAR_IN */ + dd_emit(ctx, 2, 0); /* 0000ffff m2mf TILING_POSITION_IN_X, Y */ + dd_emit(ctx, 1, 0x100); /* ffffffff m2mf TILING_PITCH_IN */ + + /* more compat 2d state */ + if (dev_priv->chipset == 0x50) { + dd_emit(ctx, 1, 1); /* ffffffff line COLOR_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff line OPERATION */ + + dd_emit(ctx, 1, 1); /* ffffffff triangle COLOR_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff triangle OPERATION */ + + dd_emit(ctx, 1, 0); /* 0000000f sifm TILE_MODE_Z */ + dd_emit(ctx, 1, 2); /* 0000000f sifm TILE_MODE_Y */ + dd_emit(ctx, 1, 0); /* 000000ff sifm FORMAT_FILTER */ + dd_emit(ctx, 1, 1); /* 000000ff sifm FORMAT_ORIGIN */ + dd_emit(ctx, 1, 0); /* 0000ffff sifm SRC_PITCH */ + dd_emit(ctx, 1, 1); /* 00000001 sifm SRC_LINEAR */ + dd_emit(ctx, 1, 0); /* 000000ff sifm SRC_OFFSET_HIGH */ + dd_emit(ctx, 1, 0); /* ffffffff sifm SRC_OFFSET */ + dd_emit(ctx, 1, 0); /* 0000ffff sifm SRC_HEIGHT */ + dd_emit(ctx, 1, 0); /* 0000ffff sifm SRC_WIDTH */ + dd_emit(ctx, 1, 3); /* ffffffff sifm COLOR_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff sifm OPERATION */ + + dd_emit(ctx, 1, 0); /* ffffffff sifc OPERATION */ + } + + /* tesla state */ + dd_emit(ctx, 1, 0); /* 0000000f GP_TEXTURES_LOG2 */ + dd_emit(ctx, 1, 0); /* 0000000f GP_SAMPLERS_LOG2 */ + dd_emit(ctx, 1, 0); /* 000000ff */ + dd_emit(ctx, 1, 0); /* ffffffff */ + dd_emit(ctx, 1, 4); /* 000000ff UNK12B0_0 */ + dd_emit(ctx, 1, 0x70); /* 000000ff UNK12B0_1 */ + dd_emit(ctx, 1, 0x80); /* 000000ff UNK12B0_3 */ + dd_emit(ctx, 1, 0); /* 000000ff UNK12B0_2 */ + dd_emit(ctx, 1, 0); /* 0000000f FP_TEXTURES_LOG2 */ + dd_emit(ctx, 1, 0); /* 0000000f FP_SAMPLERS_LOG2 */ + if (IS_NVA3F(dev_priv->chipset)) { + dd_emit(ctx, 1, 0); /* ffffffff */ + dd_emit(ctx, 1, 0); /* 0000007f MULTISAMPLE_SAMPLES_LOG2 */ + } else { + dd_emit(ctx, 1, 0); /* 0000000f MULTISAMPLE_SAMPLES_LOG2 */ + } + dd_emit(ctx, 1, 0xc); /* 000000ff SEMANTIC_COLOR.BFC0_ID */ + if (dev_priv->chipset != 0x50) + dd_emit(ctx, 1, 0); /* 00000001 SEMANTIC_COLOR.CLMP_EN */ + dd_emit(ctx, 1, 8); /* 000000ff SEMANTIC_COLOR.COLR_NR */ + dd_emit(ctx, 1, 0x14); /* 000000ff SEMANTIC_COLOR.FFC0_ID */ + if (dev_priv->chipset == 0x50) { + dd_emit(ctx, 1, 0); /* 000000ff SEMANTIC_LAYER */ + dd_emit(ctx, 1, 0); /* 00000001 */ + } else { + dd_emit(ctx, 1, 0); /* 00000001 SEMANTIC_PTSZ.ENABLE */ + dd_emit(ctx, 1, 0x29); /* 000000ff SEMANTIC_PTSZ.PTSZ_ID */ + dd_emit(ctx, 1, 0x27); /* 000000ff SEMANTIC_PRIM */ + dd_emit(ctx, 1, 0x26); /* 000000ff SEMANTIC_LAYER */ + dd_emit(ctx, 1, 8); /* 0000000f SMENATIC_CLIP.CLIP_HIGH */ + dd_emit(ctx, 1, 4); /* 000000ff SEMANTIC_CLIP.CLIP_LO */ + dd_emit(ctx, 1, 0x27); /* 000000ff UNK0FD4 */ + dd_emit(ctx, 1, 0); /* 00000001 UNK1900 */ + } + dd_emit(ctx, 1, 0); /* 00000007 RT_CONTROL_MAP0 */ + dd_emit(ctx, 1, 1); /* 00000007 RT_CONTROL_MAP1 */ + dd_emit(ctx, 1, 2); /* 00000007 RT_CONTROL_MAP2 */ + dd_emit(ctx, 1, 3); /* 00000007 RT_CONTROL_MAP3 */ + dd_emit(ctx, 1, 4); /* 00000007 RT_CONTROL_MAP4 */ + dd_emit(ctx, 1, 5); /* 00000007 RT_CONTROL_MAP5 */ + dd_emit(ctx, 1, 6); /* 00000007 RT_CONTROL_MAP6 */ + dd_emit(ctx, 1, 7); /* 00000007 RT_CONTROL_MAP7 */ + dd_emit(ctx, 1, 1); /* 0000000f RT_CONTROL_COUNT */ + dd_emit(ctx, 8, 0); /* 00000001 RT_HORIZ_UNK */ + dd_emit(ctx, 8, 0); /* ffffffff RT_ADDRESS_LOW */ + dd_emit(ctx, 1, 0xcf); /* 000000ff RT_FORMAT */ + dd_emit(ctx, 7, 0); /* 000000ff RT_FORMAT */ + if (dev_priv->chipset != 0x50) + dd_emit(ctx, 3, 0); /* 1, 1, 1 */ + else + dd_emit(ctx, 2, 0); /* 1, 1 */ + dd_emit(ctx, 1, 0); /* ffffffff GP_ENABLE */ + dd_emit(ctx, 1, 0x80); /* 0000ffff GP_VERTEX_OUTPUT_COUNT*/ + dd_emit(ctx, 1, 4); /* 000000ff GP_REG_ALLOC_RESULT */ + dd_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + if (IS_NVA3F(dev_priv->chipset)) { + dd_emit(ctx, 1, 3); /* 00000003 */ + dd_emit(ctx, 1, 0); /* 00000001 UNK1418. Alone. */ + } + if (dev_priv->chipset != 0x50) + dd_emit(ctx, 1, 3); /* 00000003 UNK15AC */ + dd_emit(ctx, 1, 1); /* ffffffff RASTERIZE_ENABLE */ + dd_emit(ctx, 1, 0); /* 00000001 FP_CONTROL.EXPORTS_Z */ + if (dev_priv->chipset != 0x50) + dd_emit(ctx, 1, 0); /* 00000001 FP_CONTROL.MULTIPLE_RESULTS */ + dd_emit(ctx, 1, 0x12); /* 000000ff FP_INTERPOLANT_CTRL.COUNT */ + dd_emit(ctx, 1, 0x10); /* 000000ff FP_INTERPOLANT_CTRL.COUNT_NONFLAT */ + dd_emit(ctx, 1, 0xc); /* 000000ff FP_INTERPOLANT_CTRL.OFFSET */ + dd_emit(ctx, 1, 1); /* 00000001 FP_INTERPOLANT_CTRL.UMASK.W */ + dd_emit(ctx, 1, 0); /* 00000001 FP_INTERPOLANT_CTRL.UMASK.X */ + dd_emit(ctx, 1, 0); /* 00000001 FP_INTERPOLANT_CTRL.UMASK.Y */ + dd_emit(ctx, 1, 0); /* 00000001 FP_INTERPOLANT_CTRL.UMASK.Z */ + dd_emit(ctx, 1, 4); /* 000000ff FP_RESULT_COUNT */ + dd_emit(ctx, 1, 2); /* ffffffff REG_MODE */ + dd_emit(ctx, 1, 4); /* 000000ff FP_REG_ALLOC_TEMP */ + if (dev_priv->chipset >= 0xa0) + dd_emit(ctx, 1, 0); /* ffffffff */ + dd_emit(ctx, 1, 0); /* 00000001 GP_BUILTIN_RESULT_EN.LAYER_IDX */ + dd_emit(ctx, 1, 0); /* ffffffff STRMOUT_ENABLE */ + dd_emit(ctx, 1, 0x3fffff); /* 003fffff TIC_LIMIT */ + dd_emit(ctx, 1, 0x1fff); /* 000fffff TSC_LIMIT */ + dd_emit(ctx, 1, 0); /* 00000001 VERTEX_TWO_SIDE_ENABLE*/ + if (dev_priv->chipset != 0x50) + dd_emit(ctx, 8, 0); /* 00000001 */ + if (dev_priv->chipset >= 0xa0) { + dd_emit(ctx, 1, 1); /* 00000007 VTX_ATTR_DEFINE.COMP */ + dd_emit(ctx, 1, 1); /* 00000007 VTX_ATTR_DEFINE.SIZE */ + dd_emit(ctx, 1, 2); /* 00000007 VTX_ATTR_DEFINE.TYPE */ + dd_emit(ctx, 1, 0); /* 000000ff VTX_ATTR_DEFINE.ATTR */ + } + dd_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + dd_emit(ctx, 1, 0x14); /* 0000001f ZETA_FORMAT */ + dd_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + dd_emit(ctx, 1, 0); /* 0000000f VP_TEXTURES_LOG2 */ + dd_emit(ctx, 1, 0); /* 0000000f VP_SAMPLERS_LOG2 */ + if (IS_NVA3F(dev_priv->chipset)) + dd_emit(ctx, 1, 0); /* 00000001 */ + dd_emit(ctx, 1, 2); /* 00000003 POLYGON_MODE_BACK */ + if (dev_priv->chipset >= 0xa0) + dd_emit(ctx, 1, 0); /* 00000003 VTX_ATTR_DEFINE.SIZE - 1 */ + dd_emit(ctx, 1, 0); /* 0000ffff CB_ADDR_INDEX */ + if (dev_priv->chipset >= 0xa0) + dd_emit(ctx, 1, 0); /* 00000003 */ + dd_emit(ctx, 1, 0); /* 00000001 CULL_FACE_ENABLE */ + dd_emit(ctx, 1, 1); /* 00000003 CULL_FACE */ + dd_emit(ctx, 1, 0); /* 00000001 FRONT_FACE */ + dd_emit(ctx, 1, 2); /* 00000003 POLYGON_MODE_FRONT */ + dd_emit(ctx, 1, 0x1000); /* 00007fff UNK141C */ + if (dev_priv->chipset != 0x50) { + dd_emit(ctx, 1, 0xe00); /* 7fff */ + dd_emit(ctx, 1, 0x1000); /* 7fff */ + dd_emit(ctx, 1, 0x1e00); /* 7fff */ + } + dd_emit(ctx, 1, 0); /* 00000001 BEGIN_END_ACTIVE */ + dd_emit(ctx, 1, 1); /* 00000001 POLYGON_MODE_??? */ + dd_emit(ctx, 1, 1); /* 000000ff GP_REG_ALLOC_TEMP / 4 rounded up */ + dd_emit(ctx, 1, 1); /* 000000ff FP_REG_ALLOC_TEMP... without /4? */ + dd_emit(ctx, 1, 1); /* 000000ff VP_REG_ALLOC_TEMP / 4 rounded up */ + dd_emit(ctx, 1, 1); /* 00000001 */ + dd_emit(ctx, 1, 0); /* 00000001 */ + dd_emit(ctx, 1, 0); /* 00000001 VTX_ATTR_MASK_UNK0 nonempty */ + dd_emit(ctx, 1, 0); /* 00000001 VTX_ATTR_MASK_UNK1 nonempty */ + dd_emit(ctx, 1, 0x200); /* 0003ffff GP_VERTEX_OUTPUT_COUNT*GP_REG_ALLOC_RESULT */ + if (IS_NVA3F(dev_priv->chipset)) + dd_emit(ctx, 1, 0x200); + dd_emit(ctx, 1, 0); /* 00000001 */ + if (dev_priv->chipset < 0xa0) { + dd_emit(ctx, 1, 1); /* 00000001 */ + dd_emit(ctx, 1, 0x70); /* 000000ff */ + dd_emit(ctx, 1, 0x80); /* 000000ff */ + dd_emit(ctx, 1, 0); /* 000000ff */ + dd_emit(ctx, 1, 0); /* 00000001 */ + dd_emit(ctx, 1, 1); /* 00000001 */ + dd_emit(ctx, 1, 0x70); /* 000000ff */ + dd_emit(ctx, 1, 0x80); /* 000000ff */ + dd_emit(ctx, 1, 0); /* 000000ff */ + } else { + dd_emit(ctx, 1, 1); /* 00000001 */ + dd_emit(ctx, 1, 0xf0); /* 000000ff */ + dd_emit(ctx, 1, 0xff); /* 000000ff */ + dd_emit(ctx, 1, 0); /* 000000ff */ + dd_emit(ctx, 1, 0); /* 00000001 */ + dd_emit(ctx, 1, 1); /* 00000001 */ + dd_emit(ctx, 1, 0xf0); /* 000000ff */ + dd_emit(ctx, 1, 0xff); /* 000000ff */ + dd_emit(ctx, 1, 0); /* 000000ff */ + dd_emit(ctx, 1, 9); /* 0000003f UNK114C.COMP,SIZE */ + } + + /* eng2d state */ + dd_emit(ctx, 1, 0); /* 00000001 eng2d COLOR_KEY_ENABLE */ + dd_emit(ctx, 1, 0); /* 00000007 eng2d COLOR_KEY_FORMAT */ + dd_emit(ctx, 1, 1); /* ffffffff eng2d DST_DEPTH */ + dd_emit(ctx, 1, 0xcf); /* 000000ff eng2d DST_FORMAT */ + dd_emit(ctx, 1, 0); /* ffffffff eng2d DST_LAYER */ + dd_emit(ctx, 1, 1); /* 00000001 eng2d DST_LINEAR */ + dd_emit(ctx, 1, 0); /* 00000007 eng2d PATTERN_COLOR_FORMAT */ + dd_emit(ctx, 1, 0); /* 00000007 eng2d OPERATION */ + dd_emit(ctx, 1, 0); /* 00000003 eng2d PATTERN_SELECT */ + dd_emit(ctx, 1, 0xcf); /* 000000ff eng2d SIFC_FORMAT */ + dd_emit(ctx, 1, 0); /* 00000001 eng2d SIFC_BITMAP_ENABLE */ + dd_emit(ctx, 1, 2); /* 00000003 eng2d SIFC_BITMAP_UNK808 */ + dd_emit(ctx, 1, 0); /* ffffffff eng2d BLIT_DU_DX_FRACT */ + dd_emit(ctx, 1, 1); /* ffffffff eng2d BLIT_DU_DX_INT */ + dd_emit(ctx, 1, 0); /* ffffffff eng2d BLIT_DV_DY_FRACT */ + dd_emit(ctx, 1, 1); /* ffffffff eng2d BLIT_DV_DY_INT */ + dd_emit(ctx, 1, 0); /* 00000001 eng2d BLIT_CONTROL_FILTER */ + dd_emit(ctx, 1, 0xcf); /* 000000ff eng2d DRAW_COLOR_FORMAT */ + dd_emit(ctx, 1, 0xcf); /* 000000ff eng2d SRC_FORMAT */ + dd_emit(ctx, 1, 1); /* 00000001 eng2d SRC_LINEAR #2 */ + + num = ctx->ctxvals_pos - base; + ctx->ctxvals_pos = base; + if (IS_NVA3F(dev_priv->chipset)) + cp_ctx(ctx, 0x404800, num); + else + cp_ctx(ctx, 0x405400, num); +} + +/* + * xfer areas. These are a pain. + * + * There are 2 xfer areas: the first one is big and contains all sorts of + * stuff, the second is small and contains some per-TP context. + * + * Each area is split into 8 "strands". The areas, when saved to grctx, + * are made of 8-word blocks. Each block contains a single word from + * each strand. The strands are independent of each other, their + * addresses are unrelated to each other, and data in them is closely + * packed together. The strand layout varies a bit between cards: here + * and there, a single word is thrown out in the middle and the whole + * strand is offset by a bit from corresponding one on another chipset. + * For this reason, addresses of stuff in strands are almost useless. + * Knowing sequence of stuff and size of gaps between them is much more + * useful, and that's how we build the strands in our generator. + * + * NVA0 takes this mess to a whole new level by cutting the old strands + * into a few dozen pieces [known as genes], rearranging them randomly, + * and putting them back together to make new strands. Hopefully these + * genes correspond more or less directly to the same PGRAPH subunits + * as in 400040 register. + * + * The most common value in default context is 0, and when the genes + * are separated by 0's, gene bounduaries are quite speculative... + * some of them can be clearly deduced, others can be guessed, and yet + * others won't be resolved without figuring out the real meaning of + * given ctxval. For the same reason, ending point of each strand + * is unknown. Except for strand 0, which is the longest strand and + * its end corresponds to end of the whole xfer. + * + * An unsolved mystery is the seek instruction: it takes an argument + * in bits 8-18, and that argument is clearly the place in strands to + * seek to... but the offsets don't seem to correspond to offsets as + * seen in grctx. Perhaps there's another, real, not randomly-changing + * addressing in strands, and the xfer insn just happens to skip over + * the unused bits? NV10-NV30 PIPE comes to mind... + * + * As far as I know, there's no way to access the xfer areas directly + * without the help of ctxprog. + */ + +static void +xf_emit(struct nouveau_grctx *ctx, int num, uint32_t val) { + int i; + if (val && ctx->mode == NOUVEAU_GRCTX_VALS) + for (i = 0; i < num; i++) + nv_wo32(ctx->data, 4 * (ctx->ctxvals_pos + (i << 3)), val); + ctx->ctxvals_pos += num << 3; +} + +/* Gene declarations... */ + +static void nv50_graph_construct_gene_dispatch(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_m2mf(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_ccache(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_unk10xx(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_unk14xx(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_zcull(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_clipid(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_unk24xx(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_vfetch(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_eng2d(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_csched(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_unk1cxx(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_strmout(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_unk34xx(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_ropm1(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_ropm2(struct nouveau_grctx *ctx); +static void nv50_graph_construct_gene_ropc(struct nouveau_grctx *ctx); +static void nv50_graph_construct_xfer_tp(struct nouveau_grctx *ctx); + +static void +nv50_graph_construct_xfer1(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i; + int offset; + int size = 0; + uint32_t units = nv_rd32 (ctx->dev, 0x1540); + + offset = (ctx->ctxvals_pos+0x3f)&~0x3f; + ctx->ctxvals_base = offset; + + if (dev_priv->chipset < 0xa0) { + /* Strand 0 */ + ctx->ctxvals_pos = offset; + nv50_graph_construct_gene_dispatch(ctx); + nv50_graph_construct_gene_m2mf(ctx); + nv50_graph_construct_gene_unk24xx(ctx); + nv50_graph_construct_gene_clipid(ctx); + nv50_graph_construct_gene_zcull(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 1 */ + ctx->ctxvals_pos = offset + 0x1; + nv50_graph_construct_gene_vfetch(ctx); + nv50_graph_construct_gene_eng2d(ctx); + nv50_graph_construct_gene_csched(ctx); + nv50_graph_construct_gene_ropm1(ctx); + nv50_graph_construct_gene_ropm2(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 2 */ + ctx->ctxvals_pos = offset + 0x2; + nv50_graph_construct_gene_ccache(ctx); + nv50_graph_construct_gene_unk1cxx(ctx); + nv50_graph_construct_gene_strmout(ctx); + nv50_graph_construct_gene_unk14xx(ctx); + nv50_graph_construct_gene_unk10xx(ctx); + nv50_graph_construct_gene_unk34xx(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 3: per-ROP group state */ + ctx->ctxvals_pos = offset + 3; + for (i = 0; i < 6; i++) + if (units & (1 << (i + 16))) + nv50_graph_construct_gene_ropc(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strands 4-7: per-TP state */ + for (i = 0; i < 4; i++) { + ctx->ctxvals_pos = offset + 4 + i; + if (units & (1 << (2 * i))) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << (2 * i + 1))) + nv50_graph_construct_xfer_tp(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + } + } else { + /* Strand 0 */ + ctx->ctxvals_pos = offset; + nv50_graph_construct_gene_dispatch(ctx); + nv50_graph_construct_gene_m2mf(ctx); + nv50_graph_construct_gene_unk34xx(ctx); + nv50_graph_construct_gene_csched(ctx); + nv50_graph_construct_gene_unk1cxx(ctx); + nv50_graph_construct_gene_strmout(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 1 */ + ctx->ctxvals_pos = offset + 1; + nv50_graph_construct_gene_unk10xx(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 2 */ + ctx->ctxvals_pos = offset + 2; + if (dev_priv->chipset == 0xa0) + nv50_graph_construct_gene_unk14xx(ctx); + nv50_graph_construct_gene_unk24xx(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 3 */ + ctx->ctxvals_pos = offset + 3; + nv50_graph_construct_gene_vfetch(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 4 */ + ctx->ctxvals_pos = offset + 4; + nv50_graph_construct_gene_ccache(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 5 */ + ctx->ctxvals_pos = offset + 5; + nv50_graph_construct_gene_ropm2(ctx); + nv50_graph_construct_gene_ropm1(ctx); + /* per-ROP context */ + for (i = 0; i < 8; i++) + if (units & (1<<(i+16))) + nv50_graph_construct_gene_ropc(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 6 */ + ctx->ctxvals_pos = offset + 6; + nv50_graph_construct_gene_zcull(ctx); + nv50_graph_construct_gene_clipid(ctx); + nv50_graph_construct_gene_eng2d(ctx); + if (units & (1 << 0)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 1)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 2)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 3)) + nv50_graph_construct_xfer_tp(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 7 */ + ctx->ctxvals_pos = offset + 7; + if (dev_priv->chipset == 0xa0) { + if (units & (1 << 4)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 5)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 6)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 7)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 8)) + nv50_graph_construct_xfer_tp(ctx); + if (units & (1 << 9)) + nv50_graph_construct_xfer_tp(ctx); + } else { + nv50_graph_construct_gene_unk14xx(ctx); + } + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + } + + ctx->ctxvals_pos = offset + size * 8; + ctx->ctxvals_pos = (ctx->ctxvals_pos+0x3f)&~0x3f; + cp_lsr (ctx, offset); + cp_out (ctx, CP_SET_XFER_POINTER); + cp_lsr (ctx, size); + cp_out (ctx, CP_SEEK_1); + cp_out (ctx, CP_XFER_1); + cp_wait(ctx, XFER, BUSY); +} + +/* + * non-trivial demagiced parts of ctx init go here + */ + +static void +nv50_graph_construct_gene_dispatch(struct nouveau_grctx *ctx) +{ + /* start of strand 0 */ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + /* SEEK */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 5, 0); + else if (!IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 6, 0); + else + xf_emit(ctx, 4, 0); + /* SEEK */ + /* the PGRAPH's internal FIFO */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 8*3, 0); + else + xf_emit(ctx, 0x100*3, 0); + /* and another bonus slot?!? */ + xf_emit(ctx, 3, 0); + /* and YET ANOTHER bonus slot? */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 3, 0); + /* SEEK */ + /* CTX_SWITCH: caches of gr objects bound to subchannels. 8 values, last used index */ + xf_emit(ctx, 9, 0); + /* SEEK */ + xf_emit(ctx, 9, 0); + /* SEEK */ + xf_emit(ctx, 9, 0); + /* SEEK */ + xf_emit(ctx, 9, 0); + /* SEEK */ + if (dev_priv->chipset < 0x90) + xf_emit(ctx, 4, 0); + /* SEEK */ + xf_emit(ctx, 2, 0); + /* SEEK */ + xf_emit(ctx, 6*2, 0); + xf_emit(ctx, 2, 0); + /* SEEK */ + xf_emit(ctx, 2, 0); + /* SEEK */ + xf_emit(ctx, 6*2, 0); + xf_emit(ctx, 2, 0); + /* SEEK */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 0x1c, 0); + else if (dev_priv->chipset < 0xa0) + xf_emit(ctx, 0x1e, 0); + else + xf_emit(ctx, 0x22, 0); + /* SEEK */ + xf_emit(ctx, 0x15, 0); +} + +static void +nv50_graph_construct_gene_m2mf(struct nouveau_grctx *ctx) +{ + /* Strand 0, right after dispatch */ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int smallm2mf = 0; + if (dev_priv->chipset < 0x92 || dev_priv->chipset == 0x98) + smallm2mf = 1; + /* SEEK */ + xf_emit (ctx, 1, 0); /* DMA_NOTIFY instance >> 4 */ + xf_emit (ctx, 1, 0); /* DMA_BUFFER_IN instance >> 4 */ + xf_emit (ctx, 1, 0); /* DMA_BUFFER_OUT instance >> 4 */ + xf_emit (ctx, 1, 0); /* OFFSET_IN */ + xf_emit (ctx, 1, 0); /* OFFSET_OUT */ + xf_emit (ctx, 1, 0); /* PITCH_IN */ + xf_emit (ctx, 1, 0); /* PITCH_OUT */ + xf_emit (ctx, 1, 0); /* LINE_LENGTH */ + xf_emit (ctx, 1, 0); /* LINE_COUNT */ + xf_emit (ctx, 1, 0x21); /* FORMAT: bits 0-4 INPUT_INC, bits 5-9 OUTPUT_INC */ + xf_emit (ctx, 1, 1); /* LINEAR_IN */ + xf_emit (ctx, 1, 0x2); /* TILING_MODE_IN: bits 0-2 y tiling, bits 3-5 z tiling */ + xf_emit (ctx, 1, 0x100); /* TILING_PITCH_IN */ + xf_emit (ctx, 1, 0x100); /* TILING_HEIGHT_IN */ + xf_emit (ctx, 1, 1); /* TILING_DEPTH_IN */ + xf_emit (ctx, 1, 0); /* TILING_POSITION_IN_Z */ + xf_emit (ctx, 1, 0); /* TILING_POSITION_IN */ + xf_emit (ctx, 1, 1); /* LINEAR_OUT */ + xf_emit (ctx, 1, 0x2); /* TILING_MODE_OUT: bits 0-2 y tiling, bits 3-5 z tiling */ + xf_emit (ctx, 1, 0x100); /* TILING_PITCH_OUT */ + xf_emit (ctx, 1, 0x100); /* TILING_HEIGHT_OUT */ + xf_emit (ctx, 1, 1); /* TILING_DEPTH_OUT */ + xf_emit (ctx, 1, 0); /* TILING_POSITION_OUT_Z */ + xf_emit (ctx, 1, 0); /* TILING_POSITION_OUT */ + xf_emit (ctx, 1, 0); /* OFFSET_IN_HIGH */ + xf_emit (ctx, 1, 0); /* OFFSET_OUT_HIGH */ + /* SEEK */ + if (smallm2mf) + xf_emit(ctx, 0x40, 0); /* 20 * ffffffff, 3ffff */ + else + xf_emit(ctx, 0x100, 0); /* 80 * ffffffff, 3ffff */ + xf_emit(ctx, 4, 0); /* 1f/7f, 0, 1f/7f, 0 [1f for smallm2mf, 7f otherwise] */ + /* SEEK */ + if (smallm2mf) + xf_emit(ctx, 0x400, 0); /* ffffffff */ + else + xf_emit(ctx, 0x800, 0); /* ffffffff */ + xf_emit(ctx, 4, 0); /* ff/1ff, 0, 0, 0 [ff for smallm2mf, 1ff otherwise] */ + /* SEEK */ + xf_emit(ctx, 0x40, 0); /* 20 * bits ffffffff, 3ffff */ + xf_emit(ctx, 0x6, 0); /* 1f, 0, 1f, 0, 1f, 0 */ +} + +static void +nv50_graph_construct_gene_ccache(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + xf_emit(ctx, 2, 0); /* RO */ + xf_emit(ctx, 0x800, 0); /* ffffffff */ + switch (dev_priv->chipset) { + case 0x50: + case 0x92: + case 0xa0: + xf_emit(ctx, 0x2b, 0); + break; + case 0x84: + xf_emit(ctx, 0x29, 0); + break; + case 0x94: + case 0x96: + case 0xa3: + xf_emit(ctx, 0x27, 0); + break; + case 0x86: + case 0x98: + case 0xa5: + case 0xa8: + case 0xaa: + case 0xac: + case 0xaf: + xf_emit(ctx, 0x25, 0); + break; + } + /* CB bindings, 0x80 of them. first word is address >> 8, second is + * size >> 4 | valid << 24 */ + xf_emit(ctx, 0x100, 0); /* ffffffff CB_DEF */ + xf_emit(ctx, 1, 0); /* 0000007f CB_ADDR_BUFFER */ + xf_emit(ctx, 1, 0); /* 0 */ + xf_emit(ctx, 0x30, 0); /* ff SET_PROGRAM_CB */ + xf_emit(ctx, 1, 0); /* 3f last SET_PROGRAM_CB */ + xf_emit(ctx, 4, 0); /* RO */ + xf_emit(ctx, 0x100, 0); /* ffffffff */ + xf_emit(ctx, 8, 0); /* 1f, 0, 0, ... */ + xf_emit(ctx, 8, 0); /* ffffffff */ + xf_emit(ctx, 4, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 3 */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_CODE_CB */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_TIC */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_TSC */ + xf_emit(ctx, 1, 0); /* 00000001 LINKED_TSC */ + xf_emit(ctx, 1, 0); /* 000000ff TIC_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff TIC_ADDRESS_LOW */ + xf_emit(ctx, 1, 0x3fffff); /* 003fffff TIC_LIMIT */ + xf_emit(ctx, 1, 0); /* 000000ff TSC_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff TSC_ADDRESS_LOW */ + xf_emit(ctx, 1, 0x1fff); /* 000fffff TSC_LIMIT */ + xf_emit(ctx, 1, 0); /* 000000ff VP_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff VP_ADDRESS_LOW */ + xf_emit(ctx, 1, 0); /* 00ffffff VP_START_ID */ + xf_emit(ctx, 1, 0); /* 000000ff CB_DEF_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff CB_DEF_ADDRESS_LOW */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0); /* 000000ff GP_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff GP_ADDRESS_LOW */ + xf_emit(ctx, 1, 0); /* 00ffffff GP_START_ID */ + xf_emit(ctx, 1, 0); /* 000000ff FP_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff FP_ADDRESS_LOW */ + xf_emit(ctx, 1, 0); /* 00ffffff FP_START_ID */ +} + +static void +nv50_graph_construct_gene_unk10xx(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i; + /* end of area 2 on pre-NVA0, area 1 on NVAx */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0x80); /* 0000ffff GP_VERTEX_OUTPUT_COUNT */ + xf_emit(ctx, 1, 4); /* 000000ff GP_REG_ALLOC_RESULT */ + xf_emit(ctx, 1, 0x80c14); /* 01ffffff SEMANTIC_COLOR */ + xf_emit(ctx, 1, 0); /* 00000001 VERTEX_TWO_SIDE_ENABLE */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 1, 0x3ff); + else + xf_emit(ctx, 1, 0x7ff); /* 000007ff */ + xf_emit(ctx, 1, 0); /* 111/113 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + for (i = 0; i < 8; i++) { + switch (dev_priv->chipset) { + case 0x50: + case 0x86: + case 0x98: + case 0xaa: + case 0xac: + xf_emit(ctx, 0xa0, 0); /* ffffffff */ + break; + case 0x84: + case 0x92: + case 0x94: + case 0x96: + xf_emit(ctx, 0x120, 0); + break; + case 0xa5: + case 0xa8: + xf_emit(ctx, 0x100, 0); /* ffffffff */ + break; + case 0xa0: + case 0xa3: + case 0xaf: + xf_emit(ctx, 0x400, 0); /* ffffffff */ + break; + } + xf_emit(ctx, 4, 0); /* 3f, 0, 0, 0 */ + xf_emit(ctx, 4, 0); /* ffffffff */ + } + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0x80); /* 0000ffff GP_VERTEX_OUTPUT_COUNT */ + xf_emit(ctx, 1, 4); /* 000000ff GP_REG_ALLOC_TEMP */ + xf_emit(ctx, 1, 1); /* 00000001 RASTERIZE_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 0x27); /* 000000ff UNK0FD4 */ + xf_emit(ctx, 1, 0); /* 0001ffff GP_BUILTIN_RESULT_EN */ + xf_emit(ctx, 1, 0x26); /* 000000ff SEMANTIC_LAYER */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ +} + +static void +nv50_graph_construct_gene_unk34xx(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + /* end of area 2 on pre-NVA0, area 1 on NVAx */ + xf_emit(ctx, 1, 0); /* 00000001 VIEWPORT_CLIP_RECTS_EN */ + xf_emit(ctx, 1, 0); /* 00000003 VIEWPORT_CLIP_MODE */ + xf_emit(ctx, 0x10, 0x04000000); /* 07ffffff VIEWPORT_CLIP_HORIZ*8, VIEWPORT_CLIP_VERT*8 */ + xf_emit(ctx, 1, 0); /* 00000001 POLYGON_STIPPLE_ENABLE */ + xf_emit(ctx, 0x20, 0); /* ffffffff POLYGON_STIPPLE */ + xf_emit(ctx, 2, 0); /* 00007fff WINDOW_OFFSET_XY */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 0x04e3bfdf); /* ffffffff UNK0D64 */ + xf_emit(ctx, 1, 0x04e3bfdf); /* ffffffff UNK0DF4 */ + xf_emit(ctx, 1, 0); /* 00000003 WINDOW_ORIGIN */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0x1fe21); /* 0001ffff tesla UNK0FAC */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 1, 0x0fac6881); + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 1); + xf_emit(ctx, 3, 0); + } +} + +static void +nv50_graph_construct_gene_unk14xx(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + /* middle of area 2 on pre-NVA0, beginning of area 2 on NVA0, area 7 on >NVA0 */ + if (dev_priv->chipset != 0x50) { + xf_emit(ctx, 5, 0); /* ffffffff */ + xf_emit(ctx, 1, 0x80c14); /* 01ffffff SEMANTIC_COLOR */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + xf_emit(ctx, 1, 0x804); /* 00000fff SEMANTIC_CLIP */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 2, 4); /* 7f, ff */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + } + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0x10); /* 7f/ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 1, 0); /* 000000ff VP_CLIP_DISTANCE_ENABLE */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 0); /* 3ff */ + xf_emit(ctx, 1, 0); /* 000000ff tesla UNK1940 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK0D7C */ + xf_emit(ctx, 1, 0x804); /* 00000fff SEMANTIC_CLIP */ + xf_emit(ctx, 1, 1); /* 00000001 VIEWPORT_TRANSFORM_EN */ + xf_emit(ctx, 1, 0x1a); /* 0000001f POLYGON_MODE */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 0x7f); /* 000000ff tesla UNK0FFC */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 1); /* 00000001 SHADE_MODEL */ + xf_emit(ctx, 1, 0x80c14); /* 01ffffff SEMANTIC_COLOR */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0x10); /* 7f/ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK0D7C */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK0F8C */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 1); /* 00000001 VIEWPORT_TRANSFORM_EN */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 4, 0); /* ffffffff NOPERSPECTIVE_BITMAP */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 0); /* 0000000f */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 1, 0x3ff); /* 000003ff tesla UNK0D68 */ + else + xf_emit(ctx, 1, 0x7ff); /* 000007ff tesla UNK0D68 */ + xf_emit(ctx, 1, 0x80c14); /* 01ffffff SEMANTIC_COLOR */ + xf_emit(ctx, 1, 0); /* 00000001 VERTEX_TWO_SIDE_ENABLE */ + xf_emit(ctx, 0x30, 0); /* ffffffff VIEWPORT_SCALE: X0, Y0, Z0, X1, Y1, ... */ + xf_emit(ctx, 3, 0); /* f, 0, 0 */ + xf_emit(ctx, 3, 0); /* ffffffff last VIEWPORT_SCALE? */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 1); /* 00000001 VIEWPORT_TRANSFORM_EN */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1924 */ + xf_emit(ctx, 1, 0x10); /* 000000ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 0x30, 0); /* ffffffff VIEWPORT_TRANSLATE */ + xf_emit(ctx, 3, 0); /* f, 0, 0 */ + xf_emit(ctx, 3, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 2, 0x88); /* 000001ff tesla UNK19D8 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1924 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 4); /* 0000000f CULL_MODE */ + xf_emit(ctx, 2, 0); /* 07ffffff SCREEN_SCISSOR */ + xf_emit(ctx, 2, 0); /* 00007fff WINDOW_OFFSET_XY */ + xf_emit(ctx, 1, 0); /* 00000003 WINDOW_ORIGIN */ + xf_emit(ctx, 0x10, 0); /* 00000001 SCISSOR_ENABLE */ + xf_emit(ctx, 1, 0); /* 0001ffff GP_BUILTIN_RESULT_EN */ + xf_emit(ctx, 1, 0x26); /* 000000ff SEMANTIC_LAYER */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 0); /* 0000000f */ + xf_emit(ctx, 1, 0x3f800000); /* ffffffff LINE_WIDTH */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_STIPPLE_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_SMOOTH_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0x1a); /* 0000001f POLYGON_MODE */ + xf_emit(ctx, 1, 0x10); /* 000000ff VIEW_VOLUME_CLIP_CTRL */ + if (dev_priv->chipset != 0x50) { + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + } + xf_emit(ctx, 0x20, 0); /* 10xbits ffffffff, 3fffff. SCISSOR_* */ + xf_emit(ctx, 1, 0); /* f */ + xf_emit(ctx, 1, 0); /* 0? */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 003fffff */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 0x52); /* 000001ff SEMANTIC_PTSZ */ + xf_emit(ctx, 1, 0); /* 0001ffff GP_BUILTIN_RESULT_EN */ + xf_emit(ctx, 1, 0x26); /* 000000ff SEMANTIC_LAYER */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0x1a); /* 0000001f POLYGON_MODE */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_SMOOTH_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_STIPPLE_ENABLE */ + xf_emit(ctx, 1, 0x00ffff00); /* 00ffffff LINE_STIPPLE_PATTERN */ + xf_emit(ctx, 1, 0); /* 0000000f */ +} + +static void +nv50_graph_construct_gene_zcull(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + /* end of strand 0 on pre-NVA0, beginning of strand 6 on NVAx */ + /* SEEK */ + xf_emit(ctx, 1, 0x3f); /* 0000003f UNK1590 */ + xf_emit(ctx, 1, 0); /* 00000001 ALPHA_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_BACK_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_REF */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_BACK_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 1, 2); /* 00000003 tesla UNK143C */ + xf_emit(ctx, 2, 0x04000000); /* 07ffffff tesla UNK0D6C */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 0); /* 00000001 CLIPID_ENABLE */ + xf_emit(ctx, 2, 0); /* ffffffff DEPTH_BOUNDS */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 00000007 DEPTH_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 4); /* 0000000f CULL_MODE */ + xf_emit(ctx, 1, 0); /* 0000ffff */ + xf_emit(ctx, 1, 0); /* 00000001 UNK0FB0 */ + xf_emit(ctx, 1, 0); /* 00000001 POLYGON_STIPPLE_ENABLE */ + xf_emit(ctx, 1, 4); /* 00000007 FP_CONTROL */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 0001ffff GP_BUILTIN_RESULT_EN */ + xf_emit(ctx, 1, 0); /* 000000ff CLEAR_STENCIL */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_FRONT_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_REF */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_FRONT_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_BACK_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff CLEAR_DEPTH */ + xf_emit(ctx, 1, 0); /* 00000007 */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1108 */ + xf_emit(ctx, 1, 0); /* 00000001 SAMPLECNT_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0x1001); /* 00001fff ZETA_ARRAY_MODE */ + /* SEEK */ + xf_emit(ctx, 4, 0xffff); /* 0000ffff MSAA_MASK */ + xf_emit(ctx, 0x10, 0); /* 00000001 SCISSOR_ENABLE */ + xf_emit(ctx, 0x10, 0); /* ffffffff DEPTH_RANGE_NEAR */ + xf_emit(ctx, 0x10, 0x3f800000); /* ffffffff DEPTH_RANGE_FAR */ + xf_emit(ctx, 1, 0x10); /* 7f/ff/3ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 1, 0); /* 00000001 VIEWPORT_CLIP_RECTS_EN */ + xf_emit(ctx, 1, 3); /* 00000003 FP_CTRL_UNK196C */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1968 */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 0); /* 0fffffff tesla UNK1104 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK151C */ +} + +static void +nv50_graph_construct_gene_clipid(struct nouveau_grctx *ctx) +{ + /* middle of strand 0 on pre-NVA0 [after 24xx], middle of area 6 on NVAx */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* 00000007 UNK0FB4 */ + /* SEEK */ + xf_emit(ctx, 4, 0); /* 07ffffff CLIPID_REGION_HORIZ */ + xf_emit(ctx, 4, 0); /* 07ffffff CLIPID_REGION_VERT */ + xf_emit(ctx, 2, 0); /* 07ffffff SCREEN_SCISSOR */ + xf_emit(ctx, 2, 0x04000000); /* 07ffffff UNK1508 */ + xf_emit(ctx, 1, 0); /* 00000001 CLIPID_ENABLE */ + xf_emit(ctx, 1, 0x80); /* 00003fff CLIPID_WIDTH */ + xf_emit(ctx, 1, 0); /* 000000ff CLIPID_ID */ + xf_emit(ctx, 1, 0); /* 000000ff CLIPID_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff CLIPID_ADDRESS_LOW */ + xf_emit(ctx, 1, 0x80); /* 00003fff CLIPID_HEIGHT */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_CLIPID */ +} + +static void +nv50_graph_construct_gene_unk24xx(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i; + /* middle of strand 0 on pre-NVA0 [after m2mf], end of strand 2 on NVAx */ + /* SEEK */ + xf_emit(ctx, 0x33, 0); + /* SEEK */ + xf_emit(ctx, 2, 0); + /* SEEK */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 4, 0); /* RO */ + xf_emit(ctx, 0xe10, 0); /* 190 * 9: 8*ffffffff, 7ff */ + xf_emit(ctx, 1, 0); /* 1ff */ + xf_emit(ctx, 8, 0); /* 0? */ + xf_emit(ctx, 9, 0); /* ffffffff, 7ff */ + + xf_emit(ctx, 4, 0); /* RO */ + xf_emit(ctx, 0xe10, 0); /* 190 * 9: 8*ffffffff, 7ff */ + xf_emit(ctx, 1, 0); /* 1ff */ + xf_emit(ctx, 8, 0); /* 0? */ + xf_emit(ctx, 9, 0); /* ffffffff, 7ff */ + } else { + xf_emit(ctx, 0xc, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 0xe10, 0); /* 190 * 9: 8*ffffffff, 7ff */ + xf_emit(ctx, 1, 0); /* 1ff */ + xf_emit(ctx, 8, 0); /* 0? */ + + /* SEEK */ + xf_emit(ctx, 0xc, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 0xe10, 0); /* 190 * 9: 8*ffffffff, 7ff */ + xf_emit(ctx, 1, 0); /* 1ff */ + xf_emit(ctx, 8, 0); /* 0? */ + } + /* SEEK */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 3); /* 00000003 tesla UNK1100 */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 1, 0); /* 0000000f VP_GP_BUILTIN_ATTR_EN */ + xf_emit(ctx, 1, 0x80c14); /* 01ffffff SEMANTIC_COLOR */ + xf_emit(ctx, 1, 1); /* 00000001 */ + /* SEEK */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 2, 4); /* 000000ff */ + xf_emit(ctx, 1, 0x80c14); /* 01ffffff SEMANTIC_COLOR */ + xf_emit(ctx, 1, 0); /* 00000001 VERTEX_TWO_SIDE_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 POINT_SPRITE_ENABLE */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 1, 0x27); /* 000000ff SEMANTIC_PRIM_ID */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000000f */ + xf_emit(ctx, 1, 1); /* 00000001 */ + for (i = 0; i < 10; i++) { + /* SEEK */ + xf_emit(ctx, 0x40, 0); /* ffffffff */ + xf_emit(ctx, 0x10, 0); /* 3, 0, 0.... */ + xf_emit(ctx, 0x10, 0); /* ffffffff */ + } + /* SEEK */ + xf_emit(ctx, 1, 0); /* 00000001 POINT_SPRITE_CTRL */ + xf_emit(ctx, 1, 1); /* 00000001 */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 4, 0); /* ffffffff NOPERSPECTIVE_BITMAP */ + xf_emit(ctx, 0x10, 0); /* 00ffffff POINT_COORD_REPLACE_MAP */ + xf_emit(ctx, 1, 0); /* 00000003 WINDOW_ORIGIN */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 0); /* 000003ff */ +} + +static void +nv50_graph_construct_gene_vfetch(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int acnt = 0x10, rep, i; + /* beginning of strand 1 on pre-NVA0, strand 3 on NVAx */ + if (IS_NVA3F(dev_priv->chipset)) + acnt = 0x20; + /* SEEK */ + if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK13A4 */ + xf_emit(ctx, 1, 1); /* 00000fff tesla UNK1318 */ + } + xf_emit(ctx, 1, 0); /* ffffffff VERTEX_BUFFER_FIRST */ + xf_emit(ctx, 1, 0); /* 00000001 PRIMITIVE_RESTART_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 UNK0DE8 */ + xf_emit(ctx, 1, 0); /* ffffffff PRIMITIVE_RESTART_INDEX */ + xf_emit(ctx, 1, 0xf); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, (acnt/8)-1, 0); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, acnt/8, 0); /* ffffffff VTX_ATR_MASK_UNK0DD0 */ + xf_emit(ctx, 1, 0); /* 0000000f VP_GP_BUILTIN_ATTR_EN */ + xf_emit(ctx, 1, 0x20); /* 0000ffff tesla UNK129C */ + xf_emit(ctx, 1, 0); /* 000000ff turing UNK370??? */ + xf_emit(ctx, 1, 0); /* 0000ffff turing USER_PARAM_COUNT */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0xb, 0); /* RO */ + else if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 0x9, 0); /* RO */ + else + xf_emit(ctx, 0x8, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* 00000001 EDGE_FLAG */ + xf_emit(ctx, 1, 0); /* 00000001 PROVOKING_VERTEX_LAST */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0x1a); /* 0000001f POLYGON_MODE */ + /* SEEK */ + xf_emit(ctx, 0xc, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* 7f/ff */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_REG_ALLOC_RESULT */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 0000000f VP_GP_BUILTIN_ATTR_EN */ + xf_emit(ctx, 1, 4); /* 000001ff UNK1A28 */ + xf_emit(ctx, 1, 8); /* 000001ff UNK0DF0 */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 1, 0x3ff); /* 3ff tesla UNK0D68 */ + else + xf_emit(ctx, 1, 0x7ff); /* 7ff tesla UNK0D68 */ + if (dev_priv->chipset == 0xa8) + xf_emit(ctx, 1, 0x1e00); /* 7fff */ + /* SEEK */ + xf_emit(ctx, 0xc, 0); /* RO or close */ + /* SEEK */ + xf_emit(ctx, 1, 0xf); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, (acnt/8)-1, 0); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, 1, 0); /* 0000000f VP_GP_BUILTIN_ATTR_EN */ + if (dev_priv->chipset > 0x50 && dev_priv->chipset < 0xa0) + xf_emit(ctx, 2, 0); /* ffffffff */ + else + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK0FD8 */ + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 0x10, 0); /* 0? */ + xf_emit(ctx, 2, 0); /* weird... */ + xf_emit(ctx, 2, 0); /* RO */ + } else { + xf_emit(ctx, 8, 0); /* 0? */ + xf_emit(ctx, 1, 0); /* weird... */ + xf_emit(ctx, 2, 0); /* RO */ + } + /* SEEK */ + xf_emit(ctx, 1, 0); /* ffffffff VB_ELEMENT_BASE */ + xf_emit(ctx, 1, 0); /* ffffffff UNK1438 */ + xf_emit(ctx, acnt, 0); /* 1 tesla UNK1000 */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1118? */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* ffffffff VERTEX_ARRAY_UNK90C */ + xf_emit(ctx, 1, 0); /* f/1f */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* ffffffff VERTEX_ARRAY_UNK90C */ + xf_emit(ctx, 1, 0); /* f/1f */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* RO */ + xf_emit(ctx, 2, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK111C? */ + xf_emit(ctx, 1, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* 000000ff UNK15F4_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff UNK15F4_ADDRESS_LOW */ + xf_emit(ctx, 1, 0); /* 000000ff UNK0F84_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff UNK0F84_ADDRESS_LOW */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* 00003fff VERTEX_ARRAY_ATTRIB_OFFSET */ + xf_emit(ctx, 3, 0); /* f/1f */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* 00000fff VERTEX_ARRAY_STRIDE */ + xf_emit(ctx, 3, 0); /* f/1f */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* ffffffff VERTEX_ARRAY_LOW */ + xf_emit(ctx, 3, 0); /* f/1f */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* 000000ff VERTEX_ARRAY_HIGH */ + xf_emit(ctx, 3, 0); /* f/1f */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* ffffffff VERTEX_LIMIT_LOW */ + xf_emit(ctx, 3, 0); /* f/1f */ + /* SEEK */ + xf_emit(ctx, acnt, 0); /* 000000ff VERTEX_LIMIT_HIGH */ + xf_emit(ctx, 3, 0); /* f/1f */ + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, acnt, 0); /* f */ + xf_emit(ctx, 3, 0); /* f/1f */ + } + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 2, 0); /* RO */ + else + xf_emit(ctx, 5, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* ffff DMA_VTXBUF */ + /* SEEK */ + if (dev_priv->chipset < 0xa0) { + xf_emit(ctx, 0x41, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 0x11, 0); /* RO */ + } else if (!IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0x50, 0); /* RO */ + else + xf_emit(ctx, 0x58, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 1, 0xf); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, (acnt/8)-1, 0); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, 1, 1); /* 1 UNK0DEC */ + /* SEEK */ + xf_emit(ctx, acnt*4, 0); /* ffffffff VTX_ATTR */ + xf_emit(ctx, 4, 0); /* f/1f, 0, 0, 0 */ + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0x1d, 0); /* RO */ + else + xf_emit(ctx, 0x16, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 1, 0xf); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, (acnt/8)-1, 0); /* ffffffff VP_ATTR_EN */ + /* SEEK */ + if (dev_priv->chipset < 0xa0) + xf_emit(ctx, 8, 0); /* RO */ + else if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0xc, 0); /* RO */ + else + xf_emit(ctx, 7, 0); /* RO */ + /* SEEK */ + xf_emit(ctx, 0xa, 0); /* RO */ + if (dev_priv->chipset == 0xa0) + rep = 0xc; + else + rep = 4; + for (i = 0; i < rep; i++) { + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0x20, 0); /* ffffffff */ + xf_emit(ctx, 0x200, 0); /* ffffffff */ + xf_emit(ctx, 4, 0); /* 7f/ff, 0, 0, 0 */ + xf_emit(ctx, 4, 0); /* ffffffff */ + } + /* SEEK */ + xf_emit(ctx, 1, 0); /* 113/111 */ + xf_emit(ctx, 1, 0xf); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, (acnt/8)-1, 0); /* ffffffff VP_ATTR_EN */ + xf_emit(ctx, acnt/8, 0); /* ffffffff VTX_ATTR_MASK_UNK0DD0 */ + xf_emit(ctx, 1, 0); /* 0000000f VP_GP_BUILTIN_ATTR_EN */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + /* SEEK */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 7, 0); /* weird... */ + else + xf_emit(ctx, 5, 0); /* weird... */ +} + +static void +nv50_graph_construct_gene_eng2d(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + /* middle of strand 1 on pre-NVA0 [after vfetch], middle of strand 6 on NVAx */ + /* SEEK */ + xf_emit(ctx, 2, 0); /* 0001ffff CLIP_X, CLIP_Y */ + xf_emit(ctx, 2, 0); /* 0000ffff CLIP_W, CLIP_H */ + xf_emit(ctx, 1, 0); /* 00000001 CLIP_ENABLE */ + if (dev_priv->chipset < 0xa0) { + /* this is useless on everything but the original NV50, + * guess they forgot to nuke it. Or just didn't bother. */ + xf_emit(ctx, 2, 0); /* 0000ffff IFC_CLIP_X, Y */ + xf_emit(ctx, 2, 1); /* 0000ffff IFC_CLIP_W, H */ + xf_emit(ctx, 1, 0); /* 00000001 IFC_CLIP_ENABLE */ + } + xf_emit(ctx, 1, 1); /* 00000001 DST_LINEAR */ + xf_emit(ctx, 1, 0x100); /* 0001ffff DST_WIDTH */ + xf_emit(ctx, 1, 0x100); /* 0001ffff DST_HEIGHT */ + xf_emit(ctx, 1, 0x11); /* 3f[NV50]/7f[NV84+] DST_FORMAT */ + xf_emit(ctx, 1, 0); /* 0001ffff DRAW_POINT_X */ + xf_emit(ctx, 1, 8); /* 0000000f DRAW_UNK58C */ + xf_emit(ctx, 1, 0); /* 000fffff SIFC_DST_X_FRACT */ + xf_emit(ctx, 1, 0); /* 0001ffff SIFC_DST_X_INT */ + xf_emit(ctx, 1, 0); /* 000fffff SIFC_DST_Y_FRACT */ + xf_emit(ctx, 1, 0); /* 0001ffff SIFC_DST_Y_INT */ + xf_emit(ctx, 1, 0); /* 000fffff SIFC_DX_DU_FRACT */ + xf_emit(ctx, 1, 1); /* 0001ffff SIFC_DX_DU_INT */ + xf_emit(ctx, 1, 0); /* 000fffff SIFC_DY_DV_FRACT */ + xf_emit(ctx, 1, 1); /* 0001ffff SIFC_DY_DV_INT */ + xf_emit(ctx, 1, 1); /* 0000ffff SIFC_WIDTH */ + xf_emit(ctx, 1, 1); /* 0000ffff SIFC_HEIGHT */ + xf_emit(ctx, 1, 0xcf); /* 000000ff SIFC_FORMAT */ + xf_emit(ctx, 1, 2); /* 00000003 SIFC_BITMAP_UNK808 */ + xf_emit(ctx, 1, 0); /* 00000003 SIFC_BITMAP_LINE_PACK_MODE */ + xf_emit(ctx, 1, 0); /* 00000001 SIFC_BITMAP_LSB_FIRST */ + xf_emit(ctx, 1, 0); /* 00000001 SIFC_BITMAP_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000ffff BLIT_DST_X */ + xf_emit(ctx, 1, 0); /* 0000ffff BLIT_DST_Y */ + xf_emit(ctx, 1, 0); /* 000fffff BLIT_DU_DX_FRACT */ + xf_emit(ctx, 1, 1); /* 0001ffff BLIT_DU_DX_INT */ + xf_emit(ctx, 1, 0); /* 000fffff BLIT_DV_DY_FRACT */ + xf_emit(ctx, 1, 1); /* 0001ffff BLIT_DV_DY_INT */ + xf_emit(ctx, 1, 1); /* 0000ffff BLIT_DST_W */ + xf_emit(ctx, 1, 1); /* 0000ffff BLIT_DST_H */ + xf_emit(ctx, 1, 0); /* 000fffff BLIT_SRC_X_FRACT */ + xf_emit(ctx, 1, 0); /* 0001ffff BLIT_SRC_X_INT */ + xf_emit(ctx, 1, 0); /* 000fffff BLIT_SRC_Y_FRACT */ + xf_emit(ctx, 1, 0); /* 00000001 UNK888 */ + xf_emit(ctx, 1, 4); /* 0000003f UNK884 */ + xf_emit(ctx, 1, 0); /* 00000007 UNK880 */ + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK0FB8 */ + xf_emit(ctx, 1, 0x15); /* 000000ff tesla UNK128C */ + xf_emit(ctx, 2, 0); /* 00000007, ffff0ff3 */ + xf_emit(ctx, 1, 0); /* 00000001 UNK260 */ + xf_emit(ctx, 1, 0x4444480); /* 1fffffff UNK870 */ + /* SEEK */ + xf_emit(ctx, 0x10, 0); + /* SEEK */ + xf_emit(ctx, 0x27, 0); +} + +static void +nv50_graph_construct_gene_csched(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + /* middle of strand 1 on pre-NVA0 [after eng2d], middle of strand 0 on NVAx */ + /* SEEK */ + xf_emit(ctx, 2, 0); /* 00007fff WINDOW_OFFSET_XY... what is it doing here??? */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1924 */ + xf_emit(ctx, 1, 0); /* 00000003 WINDOW_ORIGIN */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 1, 0); /* 000003ff */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* ffffffff turing UNK364 */ + xf_emit(ctx, 1, 0); /* 0000000f turing UNK36C */ + xf_emit(ctx, 1, 0); /* 0000ffff USER_PARAM_COUNT */ + xf_emit(ctx, 1, 0x100); /* 00ffffff turing UNK384 */ + xf_emit(ctx, 1, 0); /* 0000000f turing UNK2A0 */ + xf_emit(ctx, 1, 0); /* 0000ffff GRIDID */ + xf_emit(ctx, 1, 0x10001); /* ffffffff GRIDDIM_XY */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0x10001); /* ffffffff BLOCKDIM_XY */ + xf_emit(ctx, 1, 1); /* 0000ffff BLOCKDIM_Z */ + xf_emit(ctx, 1, 0x10001); /* 00ffffff BLOCK_ALLOC */ + xf_emit(ctx, 1, 1); /* 00000001 LANES32 */ + xf_emit(ctx, 1, 4); /* 000000ff FP_REG_ALLOC_TEMP */ + xf_emit(ctx, 1, 2); /* 00000003 REG_MODE */ + /* SEEK */ + xf_emit(ctx, 0x40, 0); /* ffffffff USER_PARAM */ + switch (dev_priv->chipset) { + case 0x50: + case 0x92: + xf_emit(ctx, 8, 0); /* 7, 0, 0, 0, ... */ + xf_emit(ctx, 0x80, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 0x10*2, 0); /* ffffffff, 1f */ + break; + case 0x84: + xf_emit(ctx, 8, 0); /* 7, 0, 0, 0, ... */ + xf_emit(ctx, 0x60, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 0xc*2, 0); /* ffffffff, 1f */ + break; + case 0x94: + case 0x96: + xf_emit(ctx, 8, 0); /* 7, 0, 0, 0, ... */ + xf_emit(ctx, 0x40, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 8*2, 0); /* ffffffff, 1f */ + break; + case 0x86: + case 0x98: + xf_emit(ctx, 4, 0); /* f, 0, 0, 0 */ + xf_emit(ctx, 0x10, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 2*2, 0); /* ffffffff, 1f */ + break; + case 0xa0: + xf_emit(ctx, 8, 0); /* 7, 0, 0, 0, ... */ + xf_emit(ctx, 0xf0, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 0x1e*2, 0); /* ffffffff, 1f */ + break; + case 0xa3: + xf_emit(ctx, 8, 0); /* 7, 0, 0, 0, ... */ + xf_emit(ctx, 0x60, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 0xc*2, 0); /* ffffffff, 1f */ + break; + case 0xa5: + case 0xaf: + xf_emit(ctx, 8, 0); /* 7, 0, 0, 0, ... */ + xf_emit(ctx, 0x30, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 6*2, 0); /* ffffffff, 1f */ + break; + case 0xaa: + xf_emit(ctx, 0x12, 0); + break; + case 0xa8: + case 0xac: + xf_emit(ctx, 4, 0); /* f, 0, 0, 0 */ + xf_emit(ctx, 0x10, 0); /* fff */ + xf_emit(ctx, 2, 0); /* ff, fff */ + xf_emit(ctx, 2*2, 0); /* ffffffff, 1f */ + break; + } + xf_emit(ctx, 1, 0); /* 0000000f */ + xf_emit(ctx, 1, 0); /* 00000000 */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 0000001f */ + xf_emit(ctx, 4, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 00000003 turing UNK35C */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 4, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 00000003 turing UNK35C */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 000000ff */ +} + +static void +nv50_graph_construct_gene_unk1cxx(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + xf_emit(ctx, 2, 0); /* 00007fff WINDOW_OFFSET_XY */ + xf_emit(ctx, 1, 0x3f800000); /* ffffffff LINE_WIDTH */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_SMOOTH_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1658 */ + xf_emit(ctx, 1, 0); /* 00000001 POLYGON_SMOOTH_ENABLE */ + xf_emit(ctx, 3, 0); /* 00000001 POLYGON_OFFSET_*_ENABLE */ + xf_emit(ctx, 1, 4); /* 0000000f CULL_MODE */ + xf_emit(ctx, 1, 0x1a); /* 0000001f POLYGON_MODE */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 0); /* 00000001 POINT_SPRITE_ENABLE */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK165C */ + xf_emit(ctx, 0x10, 0); /* 00000001 SCISSOR_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_STIPPLE_ENABLE */ + xf_emit(ctx, 1, 0x00ffff00); /* 00ffffff LINE_STIPPLE_PATTERN */ + xf_emit(ctx, 1, 0); /* ffffffff POLYGON_OFFSET_UNITS */ + xf_emit(ctx, 1, 0); /* ffffffff POLYGON_OFFSET_FACTOR */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1668 */ + xf_emit(ctx, 2, 0); /* 07ffffff SCREEN_SCISSOR */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 0xf); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 7, 0); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 1, 0x11); /* 0000007f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 0000007f RT_FORMAT */ + xf_emit(ctx, 8, 0); /* 00000001 RT_HORIZ_LINEAR */ + xf_emit(ctx, 1, 4); /* 00000007 FP_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000001 ALPHA_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 ALPHA_TEST_FUNC */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 3); /* 00000003 UNK16B4 */ + else if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 1, 1); /* 00000001 UNK16B4 */ + xf_emit(ctx, 1, 0); /* 00000003 MULTISAMPLE_CTRL */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK0F90 */ + xf_emit(ctx, 1, 2); /* 00000003 tesla UNK143C */ + xf_emit(ctx, 2, 0x04000000); /* 07ffffff tesla UNK0D6C */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_MASK */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 SAMPLECNT_ENABLE */ + xf_emit(ctx, 1, 5); /* 0000000f UNK1408 */ + xf_emit(ctx, 1, 0x52); /* 000001ff SEMANTIC_PTSZ */ + xf_emit(ctx, 1, 0); /* ffffffff POINT_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 00000007 tesla UNK0FB4 */ + if (dev_priv->chipset != 0x50) { + xf_emit(ctx, 1, 0); /* 3ff */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK1110 */ + } + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1928 */ + xf_emit(ctx, 0x10, 0); /* ffffffff DEPTH_RANGE_NEAR */ + xf_emit(ctx, 0x10, 0x3f800000); /* ffffffff DEPTH_RANGE_FAR */ + xf_emit(ctx, 1, 0x10); /* 000000ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 0x20, 0); /* 07ffffff VIEWPORT_HORIZ, then VIEWPORT_VERT. (W&0x3fff)<<13 | (X&0x1fff). */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK187C */ + xf_emit(ctx, 1, 0); /* 00000003 WINDOW_ORIGIN */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_BACK_ENABLE */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_MASK */ + xf_emit(ctx, 1, 0x8100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 1, 5); /* 0000000f tesla UNK1220 */ + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 000000ff tesla UNK1A20 */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 VERTEX_TWO_SIDE_ENABLE */ + xf_emit(ctx, 4, 0xffff); /* 0000ffff MSAA_MASK */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 3); /* 00000003 tesla UNK1100 */ + if (dev_priv->chipset < 0xa0) + xf_emit(ctx, 0x1c, 0); /* RO */ + else if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0x9, 0); + xf_emit(ctx, 1, 0); /* 00000001 UNK1534 */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_SMOOTH_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_STIPPLE_ENABLE */ + xf_emit(ctx, 1, 0x00ffff00); /* 00ffffff LINE_STIPPLE_PATTERN */ + xf_emit(ctx, 1, 0x1a); /* 0000001f POLYGON_MODE */ + xf_emit(ctx, 1, 0); /* 00000003 WINDOW_ORIGIN */ + if (dev_priv->chipset != 0x50) { + xf_emit(ctx, 1, 3); /* 00000003 tesla UNK1100 */ + xf_emit(ctx, 1, 0); /* 3ff */ + } + /* XXX: the following block could belong either to unk1cxx, or + * to STRMOUT. Rather hard to tell. */ + if (dev_priv->chipset < 0xa0) + xf_emit(ctx, 0x25, 0); + else + xf_emit(ctx, 0x3b, 0); +} + +static void +nv50_graph_construct_gene_strmout(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + xf_emit(ctx, 1, 0x102); /* 0000ffff STRMOUT_BUFFER_CTRL */ + xf_emit(ctx, 1, 0); /* ffffffff STRMOUT_PRIMITIVE_COUNT */ + xf_emit(ctx, 4, 4); /* 000000ff STRMOUT_NUM_ATTRIBS */ + if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 4, 0); /* ffffffff UNK1A8C */ + xf_emit(ctx, 4, 0); /* ffffffff UNK1780 */ + } + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 4); /* 0000007f VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 1, 0x3ff); /* 000003ff tesla UNK0D68 */ + else + xf_emit(ctx, 1, 0x7ff); /* 000007ff tesla UNK0D68 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + /* SEEK */ + xf_emit(ctx, 1, 0x102); /* 0000ffff STRMOUT_BUFFER_CTRL */ + xf_emit(ctx, 1, 0); /* ffffffff STRMOUT_PRIMITIVE_COUNT */ + xf_emit(ctx, 4, 0); /* 000000ff STRMOUT_ADDRESS_HIGH */ + xf_emit(ctx, 4, 0); /* ffffffff STRMOUT_ADDRESS_LOW */ + xf_emit(ctx, 4, 4); /* 000000ff STRMOUT_NUM_ATTRIBS */ + if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 4, 0); /* ffffffff UNK1A8C */ + xf_emit(ctx, 4, 0); /* ffffffff UNK1780 */ + } + xf_emit(ctx, 1, 0); /* 0000ffff DMA_STRMOUT */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_QUERY */ + xf_emit(ctx, 1, 0); /* 000000ff QUERY_ADDRESS_HIGH */ + xf_emit(ctx, 2, 0); /* ffffffff QUERY_ADDRESS_LOW QUERY_COUNTER */ + xf_emit(ctx, 2, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + /* SEEK */ + xf_emit(ctx, 0x20, 0); /* ffffffff STRMOUT_MAP */ + xf_emit(ctx, 1, 0); /* 0000000f */ + xf_emit(ctx, 1, 0); /* 00000000? */ + xf_emit(ctx, 2, 0); /* ffffffff */ +} + +static void +nv50_graph_construct_gene_ropm1(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + xf_emit(ctx, 1, 0x4e3bfdf); /* ffffffff UNK0D64 */ + xf_emit(ctx, 1, 0x4e3bfdf); /* ffffffff UNK0DF4 */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 0x11); /* 000000ff tesla UNK1968 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ +} + +static void +nv50_graph_construct_gene_ropm2(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + /* SEEK */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_QUERY */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 2, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 000000ff QUERY_ADDRESS_HIGH */ + xf_emit(ctx, 2, 0); /* ffffffff QUERY_ADDRESS_LOW, COUNTER */ + xf_emit(ctx, 1, 0); /* 00000001 SAMPLECNT_ENABLE */ + xf_emit(ctx, 1, 0); /* 7 */ + /* SEEK */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_QUERY */ + xf_emit(ctx, 1, 0); /* 000000ff QUERY_ADDRESS_HIGH */ + xf_emit(ctx, 2, 0); /* ffffffff QUERY_ADDRESS_LOW, COUNTER */ + xf_emit(ctx, 1, 0x4e3bfdf); /* ffffffff UNK0D64 */ + xf_emit(ctx, 1, 0x4e3bfdf); /* ffffffff UNK0DF4 */ + xf_emit(ctx, 1, 0); /* 00000001 eng2d UNK260 */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 0); /* 00000007 */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 0x11); /* 000000ff tesla UNK1968 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ +} + +static void +nv50_graph_construct_gene_ropc(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int magic2; + if (dev_priv->chipset == 0x50) { + magic2 = 0x00003e60; + } else if (!IS_NVA3F(dev_priv->chipset)) { + magic2 = 0x001ffe67; + } else { + magic2 = 0x00087e67; + } + xf_emit(ctx, 1, 0); /* f/7 MUTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_BACK_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_BACK_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 1, 2); /* 00000003 tesla UNK143C */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, magic2); /* 001fffff tesla UNK0F78 */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000007 DEPTH_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_FRONT_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_FRONT_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + if (dev_priv->chipset >= 0xa0 && !IS_NVAAF(dev_priv->chipset)) + xf_emit(ctx, 1, 0x15); /* 000000ff */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_BACK_ENABLE */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK15B4 */ + xf_emit(ctx, 1, 0x10); /* 3ff/ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 1, 0); /* ffffffff CLEAR_DEPTH */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + if (dev_priv->chipset == 0x86 || dev_priv->chipset == 0x92 || dev_priv->chipset == 0x98 || dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 3, 0); /* ff, ffffffff, ffffffff */ + xf_emit(ctx, 1, 4); /* 7 */ + xf_emit(ctx, 1, 0x400); /* fffffff */ + xf_emit(ctx, 1, 0x300); /* ffff */ + xf_emit(ctx, 1, 0x1001); /* 1fff */ + if (dev_priv->chipset != 0xa0) { + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 0); /* 0000000f UNK15C8 */ + else + xf_emit(ctx, 1, 0x15); /* ff */ + } + } + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_BACK_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 2); /* 00000003 tesla UNK143C */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000007 DEPTH_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_FRONT_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_BACK_ENABLE */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK15B4 */ + xf_emit(ctx, 1, 0x10); /* 7f/ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1900 */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_BACK_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_REF */ + xf_emit(ctx, 2, 0); /* ffffffff DEPTH_BOUNDS */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000007 DEPTH_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000000f */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK0FB0 */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_FRONT_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_REF */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_BACK_ENABLE */ + xf_emit(ctx, 1, 0x10); /* 7f/ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 0x10, 0); /* ffffffff DEPTH_RANGE_NEAR */ + xf_emit(ctx, 0x10, 0x3f800000); /* ffffffff DEPTH_RANGE_FAR */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_BACK_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_FUNC_REF */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_BACK_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 2, 0); /* ffffffff DEPTH_BOUNDS */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000007 DEPTH_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 0); /* 000000ff CLEAR_STENCIL */ + xf_emit(ctx, 1, 0); /* 00000007 STENCIL_FRONT_FUNC_FUNC */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_FUNC_REF */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_FRONT_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_BACK_ENABLE */ + xf_emit(ctx, 1, 0x10); /* 7f/ff VIEW_VOLUME_CLIP_CTRL */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 0x3f); /* 0000003f UNK1590 */ + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 2, 0); /* ffff0ff3, ffff */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK0FB0 */ + xf_emit(ctx, 1, 0); /* 0001ffff GP_BUILTIN_RESULT_EN */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK15B4 */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff CLEAR_DEPTH */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK19CC */ + if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 2, 0); + xf_emit(ctx, 1, 0x1001); + xf_emit(ctx, 0xb, 0); + } else { + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 8, 0); /* 00000001 BLEND_ENABLE */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + } + xf_emit(ctx, 1, 0x11); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 1, 0xf); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 7, 0); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 1, 0x11); /* 3f/7f */ + xf_emit(ctx, 1, 0); /* 00000001 LOGIC_OP_ENABLE */ + if (dev_priv->chipset != 0x50) { + xf_emit(ctx, 1, 0); /* 0000000f LOGIC_OP */ + xf_emit(ctx, 1, 0); /* 000000ff */ + } + xf_emit(ctx, 1, 0); /* 00000007 OPERATION */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 0); /* 00000003 UNK0F90 */ + xf_emit(ctx, 2, 1); /* 00000007 BLEND_EQUATION_RGB, ALPHA */ + xf_emit(ctx, 1, 1); /* 00000001 UNK133C */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_RGB */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_RGB */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, magic2); /* 001fffff tesla UNK0F78 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK12E4 */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_RGB */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_ALPHA */ + xf_emit(ctx, 8, 1); /* 00000001 IBLEND_UNK00 */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_FUNC_SRC_RGB */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_FUNC_DST_RGB */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1140 */ + xf_emit(ctx, 2, 0); /* 00000001 */ + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, 0); /* 0000000f */ + xf_emit(ctx, 1, 0); /* 00000003 */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 2, 0); /* 00000001 */ + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + } else if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 2, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0); /* 00000003 */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 2, 0); /* 00000001 */ + } else { + xf_emit(ctx, 1, 0); /* 00000007 MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1430 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + } + xf_emit(ctx, 4, 0); /* ffffffff CLEAR_COLOR */ + xf_emit(ctx, 4, 0); /* ffffffff BLEND_COLOR A R G B */ + xf_emit(ctx, 1, 0); /* 00000fff eng2d UNK2B0 */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 2, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + xf_emit(ctx, 8, 0); /* 00000001 BLEND_ENABLE */ + xf_emit(ctx, 1, 1); /* 00000001 UNK133C */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_RGB */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_RGB */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_RGB */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_ALPHA */ + xf_emit(ctx, 1, 0); /* 00000001 UNK19C0 */ + xf_emit(ctx, 1, 0); /* 00000001 LOGIC_OP_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000000f LOGIC_OP */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 1, 0); /* 00000001 UNK12E4? NVA3+ only? */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 8, 1); /* 00000001 IBLEND_UNK00 */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_RGB */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_FUNC_SRC_RGB */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_FUNC_DST_RGB */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_ALPHA */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK15C4 */ + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1140 */ + } + xf_emit(ctx, 1, 0x11); /* 3f/7f DST_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 DST_LINEAR */ + xf_emit(ctx, 1, 0); /* 00000007 PATTERN_COLOR_FORMAT */ + xf_emit(ctx, 2, 0); /* ffffffff PATTERN_MONO_COLOR */ + xf_emit(ctx, 1, 0); /* 00000001 PATTERN_MONO_FORMAT */ + xf_emit(ctx, 2, 0); /* ffffffff PATTERN_MONO_BITMAP */ + xf_emit(ctx, 1, 0); /* 00000003 PATTERN_SELECT */ + xf_emit(ctx, 1, 0); /* 000000ff ROP */ + xf_emit(ctx, 1, 0); /* ffffffff BETA1 */ + xf_emit(ctx, 1, 0); /* ffffffff BETA4 */ + xf_emit(ctx, 1, 0); /* 00000007 OPERATION */ + xf_emit(ctx, 0x50, 0); /* 10x ffffff, ffffff, ffffff, ffffff, 3 PATTERN */ +} + +static void +nv50_graph_construct_xfer_unk84xx(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int magic3; + switch (dev_priv->chipset) { + case 0x50: + magic3 = 0x1000; + break; + case 0x86: + case 0x98: + case 0xa8: + case 0xaa: + case 0xac: + case 0xaf: + magic3 = 0x1e00; + break; + default: + magic3 = 0; + } + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 7f/ff[NVA0+] VP_REG_ALLOC_RESULT */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 0); /* 111/113[NVA0+] */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0x1f, 0); /* ffffffff */ + else if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 0x0f, 0); /* ffffffff */ + else + xf_emit(ctx, 0x10, 0); /* fffffff VP_RESULT_MAP_1 up */ + xf_emit(ctx, 2, 0); /* f/1f[NVA3], fffffff/ffffffff[NVA0+] */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_REG_ALLOC_RESULT */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_RESULT_MAP_SIZE */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 1, 0x03020100); /* ffffffff */ + else + xf_emit(ctx, 1, 0x00608080); /* fffffff VP_RESULT_MAP_0 */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 2, 0); /* 111/113, 7f/ff */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_REG_ALLOC_RESULT */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0x80); /* 0000ffff GP_VERTEX_OUTPUT_COUNT */ + if (magic3) + xf_emit(ctx, 1, magic3); /* 00007fff tesla UNK141C */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 0); /* 111/113 */ + xf_emit(ctx, 0x1f, 0); /* ffffffff GP_RESULT_MAP_1 up */ + xf_emit(ctx, 1, 0); /* 0000001f */ + xf_emit(ctx, 1, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_REG_ALLOC_RESULT */ + xf_emit(ctx, 1, 0x80); /* 0000ffff GP_VERTEX_OUTPUT_COUNT */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0x03020100); /* ffffffff GP_RESULT_MAP_0 */ + xf_emit(ctx, 1, 3); /* 00000003 GP_OUTPUT_PRIMITIVE_TYPE */ + if (magic3) + xf_emit(ctx, 1, magic3); /* 7fff tesla UNK141C */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 0); /* 00000001 PROVOKING_VERTEX_LAST */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 0); /* 111/113 */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 3); /* 00000003 GP_OUTPUT_PRIMITIVE_TYPE */ + xf_emit(ctx, 1, 0); /* 00000001 PROVOKING_VERTEX_LAST */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK13A0 */ + xf_emit(ctx, 1, 4); /* 7f/ff VP_REG_ALLOC_RESULT */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + xf_emit(ctx, 1, 0); /* 111/113 */ + if (dev_priv->chipset == 0x94 || dev_priv->chipset == 0x96) + xf_emit(ctx, 0x1020, 0); /* 4 x (0x400 x 0xffffffff, ff, 0, 0, 0, 4 x ffffffff) */ + else if (dev_priv->chipset < 0xa0) + xf_emit(ctx, 0xa20, 0); /* 4 x (0x280 x 0xffffffff, ff, 0, 0, 0, 4 x ffffffff) */ + else if (!IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0x210, 0); /* ffffffff */ + else + xf_emit(ctx, 0x410, 0); /* ffffffff */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 000000ff GP_RESULT_MAP_SIZE */ + xf_emit(ctx, 1, 3); /* 00000003 GP_OUTPUT_PRIMITIVE_TYPE */ + xf_emit(ctx, 1, 0); /* 00000001 PROVOKING_VERTEX_LAST */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ +} + +static void +nv50_graph_construct_xfer_tprop(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int magic1, magic2; + if (dev_priv->chipset == 0x50) { + magic1 = 0x3ff; + magic2 = 0x00003e60; + } else if (!IS_NVA3F(dev_priv->chipset)) { + magic1 = 0x7ff; + magic2 = 0x001ffe67; + } else { + magic1 = 0x7ff; + magic2 = 0x00087e67; + } + xf_emit(ctx, 1, 0); /* 00000007 ALPHA_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* ffffffff ALPHA_TEST_REF */ + xf_emit(ctx, 1, 0); /* 00000001 ALPHA_TEST_ENABLE */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000000f UNK16A0 */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_BACK_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_BACK_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 4, 0); /* ffffffff BLEND_COLOR */ + xf_emit(ctx, 1, 0); /* 00000001 UNK19C0 */ + xf_emit(ctx, 1, 0); /* 00000001 UNK0FDC */ + xf_emit(ctx, 1, 0xf); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 7, 0); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 LOGIC_OP_ENABLE */ + xf_emit(ctx, 1, 0); /* ff[NV50]/3ff[NV84+] */ + xf_emit(ctx, 1, 4); /* 00000007 FP_CONTROL */ + xf_emit(ctx, 4, 0xffff); /* 0000ffff MSAA_MASK */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_MASK */ + xf_emit(ctx, 3, 0); /* 00000007 STENCIL_FRONT_OP_FAIL, ZFAIL, ZPASS */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_BACK_ENABLE */ + xf_emit(ctx, 2, 0); /* 00007fff WINDOW_OFFSET_XY */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK19CC */ + xf_emit(ctx, 1, 0); /* 7 */ + xf_emit(ctx, 1, 0); /* 00000001 SAMPLECNT_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff COLOR_KEY */ + xf_emit(ctx, 1, 0); /* 00000001 COLOR_KEY_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 COLOR_KEY_FORMAT */ + xf_emit(ctx, 2, 0); /* ffffffff SIFC_BITMAP_COLOR */ + xf_emit(ctx, 1, 1); /* 00000001 SIFC_BITMAP_WRITE_BIT0_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 ALPHA_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* 00000001 ALPHA_TEST_ENABLE */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 3); /* 00000003 tesla UNK16B4 */ + xf_emit(ctx, 1, 0); /* 00000003 */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1298 */ + } else if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK16B4 */ + xf_emit(ctx, 1, 0); /* 00000003 */ + } else { + xf_emit(ctx, 1, 0); /* 00000003 MULTISAMPLE_CTRL */ + } + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 8, 0); /* 00000001 BLEND_ENABLE */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_ALPHA */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_RGB */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_RGB */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_RGB */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 0); /* 00000001 UNK12E4 */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_RGB */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_ALPHA */ + xf_emit(ctx, 8, 1); /* 00000001 IBLEND_UNK00 */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_SRC_RGB */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_DST_RGB */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_SRC_ALPHA */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_DST_ALPHA */ + xf_emit(ctx, 1, 0); /* 00000001 UNK1140 */ + } + xf_emit(ctx, 1, 1); /* 00000001 UNK133C */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 0x11); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000001 LOGIC_OP_ENABLE */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 4); /* 00000007 FP_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000003 UNK0F90 */ + xf_emit(ctx, 1, 0); /* 00000001 FRAMEBUFFER_SRGB */ + xf_emit(ctx, 1, 0); /* 7 */ + xf_emit(ctx, 1, 0x11); /* 3f/7f DST_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 DST_LINEAR */ + xf_emit(ctx, 1, 0); /* 00000007 OPERATION */ + xf_emit(ctx, 1, 0xcf); /* 000000ff SIFC_FORMAT */ + xf_emit(ctx, 1, 0xcf); /* 000000ff DRAW_COLOR_FORMAT */ + xf_emit(ctx, 1, 0xcf); /* 000000ff SRC_FORMAT */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + xf_emit(ctx, 1, 0); /* 7/f[NVA3] MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 8, 0); /* 00000001 BLEND_ENABLE */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_ALPHA */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_RGB */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_RGB */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_RGB */ + xf_emit(ctx, 1, 1); /* 00000001 UNK133C */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 8, 1); /* 00000001 UNK19E0 */ + xf_emit(ctx, 1, 0x11); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 1, 0xf); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 7, 0); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 1, magic2); /* 001fffff tesla UNK0F78 */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0x11); /* 3f/7f DST_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 DST_LINEAR */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 1, 0); /* ff */ + else + xf_emit(ctx, 3, 0); /* 1, 7, 3ff */ + xf_emit(ctx, 1, 4); /* 00000007 FP_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000003 UNK0F90 */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0); /* 00000001 SAMPLECNT_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 0x11); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 0x11); /* 3f/7f DST_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 DST_LINEAR */ + xf_emit(ctx, 1, 0); /* 000fffff BLIT_DU_DX_FRACT */ + xf_emit(ctx, 1, 1); /* 0001ffff BLIT_DU_DX_INT */ + xf_emit(ctx, 1, 0); /* 000fffff BLIT_DV_DY_FRACT */ + xf_emit(ctx, 1, 1); /* 0001ffff BLIT_DV_DY_INT */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, magic1); /* 3ff/7ff tesla UNK0D68 */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK15B4 */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 8, 0); /* 0000ffff DMA_COLOR */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_GLOBAL */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_LOCAL */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_STACK */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_DST */ + xf_emit(ctx, 1, 0); /* 7 */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 8, 0); /* 000000ff RT_ADDRESS_HIGH */ + xf_emit(ctx, 8, 0); /* ffffffff RT_LAYER_STRIDE */ + xf_emit(ctx, 8, 0); /* ffffffff RT_ADDRESS_LOW */ + xf_emit(ctx, 8, 8); /* 0000007f RT_TILE_MODE */ + xf_emit(ctx, 1, 0x11); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 8, 0x400); /* 0fffffff RT_HORIZ */ + xf_emit(ctx, 8, 0x300); /* 0000ffff RT_VERT */ + xf_emit(ctx, 1, 1); /* 00001fff RT_ARRAY_MODE */ + xf_emit(ctx, 1, 0xf); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 7, 0); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 1, 0x20); /* 00000fff DST_TILE_MODE */ + xf_emit(ctx, 1, 0x11); /* 3f/7f DST_FORMAT */ + xf_emit(ctx, 1, 0x100); /* 0001ffff DST_HEIGHT */ + xf_emit(ctx, 1, 0); /* 000007ff DST_LAYER */ + xf_emit(ctx, 1, 1); /* 00000001 DST_LINEAR */ + xf_emit(ctx, 1, 0); /* ffffffff DST_ADDRESS_LOW */ + xf_emit(ctx, 1, 0); /* 000000ff DST_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0x40); /* 0007ffff DST_PITCH */ + xf_emit(ctx, 1, 0x100); /* 0001ffff DST_WIDTH */ + xf_emit(ctx, 1, 0); /* 0000ffff */ + xf_emit(ctx, 1, 3); /* 00000003 tesla UNK15AC */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 0); /* 0001ffff GP_BUILTIN_RESULT_EN */ + xf_emit(ctx, 1, 0); /* 00000003 UNK0F90 */ + xf_emit(ctx, 1, 0); /* 00000007 */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, magic2); /* 001fffff tesla UNK0F78 */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 2); /* 00000003 tesla UNK143C */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_ZETA */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 2, 0); /* ffff, ff/3ff */ + xf_emit(ctx, 1, 0); /* 0001ffff GP_BUILTIN_RESULT_EN */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 0); /* 000000ff STENCIL_FRONT_MASK */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK15B4 */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0); /* ffffffff ZETA_LAYER_STRIDE */ + xf_emit(ctx, 1, 0); /* 000000ff ZETA_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff ZETA_ADDRESS_LOW */ + xf_emit(ctx, 1, 4); /* 00000007 ZETA_TILE_MODE */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + xf_emit(ctx, 1, 0x400); /* 0fffffff ZETA_HORIZ */ + xf_emit(ctx, 1, 0x300); /* 0000ffff ZETA_VERT */ + xf_emit(ctx, 1, 0x1001); /* 00001fff ZETA_ARRAY_MODE */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 0); /* 00000001 */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 0x11); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 3f/7f RT_FORMAT */ + xf_emit(ctx, 1, 0x0fac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 1, 0xf); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 7, 0); /* 0000000f COLOR_MASK */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 8, 0); /* 00000001 BLEND_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000003 UNK0F90 */ + xf_emit(ctx, 1, 0); /* 00000001 FRAMEBUFFER_SRGB */ + xf_emit(ctx, 1, 0); /* 7 */ + xf_emit(ctx, 1, 0); /* 00000001 LOGIC_OP_ENABLE */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 0); /* 00000001 UNK1140 */ + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + } + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 UNK1534 */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 1, 0x0fac6881); /* fffffff */ + xf_emit(ctx, 1, magic2); /* 001fffff tesla UNK0F78 */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_BOUNDS_EN */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE_ENABLE */ + xf_emit(ctx, 1, 0x11); /* 3f/7f DST_FORMAT */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK0FB0 */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 4); /* 00000007 FP_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000001 STENCIL_FRONT_ENABLE */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK15B4 */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK19CC */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0); /* 00000001 SAMPLECNT_ENABLE */ + xf_emit(ctx, 1, 0); /* 0000000f ZETA_FORMAT */ + xf_emit(ctx, 1, 1); /* 00000001 ZETA_ENABLE */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, 0); /* 0000000f tesla UNK15C8 */ + } + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A3C */ + if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 3, 0); /* 7/f, 1, ffff0ff3 */ + xf_emit(ctx, 1, 0xfac6881); /* fffffff */ + xf_emit(ctx, 4, 0); /* 1, 1, 1, 3ff */ + xf_emit(ctx, 1, 4); /* 7 */ + xf_emit(ctx, 1, 0); /* 1 */ + xf_emit(ctx, 2, 1); /* 1 */ + xf_emit(ctx, 2, 0); /* 7, f */ + xf_emit(ctx, 1, 1); /* 1 */ + xf_emit(ctx, 1, 0); /* 7/f */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 0x9, 0); /* 1 */ + else + xf_emit(ctx, 0x8, 0); /* 1 */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 8, 1); /* 1 */ + xf_emit(ctx, 1, 0x11); /* 7f */ + xf_emit(ctx, 7, 0); /* 7f */ + xf_emit(ctx, 1, 0xfac6881); /* fffffff */ + xf_emit(ctx, 1, 0xf); /* f */ + xf_emit(ctx, 7, 0); /* f */ + xf_emit(ctx, 1, 0x11); /* 7f */ + xf_emit(ctx, 1, 1); /* 1 */ + xf_emit(ctx, 5, 0); /* 1, 7, 3ff, 3, 7 */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 0); /* 00000001 UNK1140 */ + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + } + } +} + +static void +nv50_graph_construct_xfer_tex(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + xf_emit(ctx, 2, 0); /* 1 LINKED_TSC. yes, 2. */ + if (dev_priv->chipset != 0x50) + xf_emit(ctx, 1, 0); /* 3 */ + xf_emit(ctx, 1, 1); /* 1ffff BLIT_DU_DX_INT */ + xf_emit(ctx, 1, 0); /* fffff BLIT_DU_DX_FRACT */ + xf_emit(ctx, 1, 1); /* 1ffff BLIT_DV_DY_INT */ + xf_emit(ctx, 1, 0); /* fffff BLIT_DV_DY_FRACT */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 1, 0); /* 3 BLIT_CONTROL */ + else + xf_emit(ctx, 2, 0); /* 3ff, 1 */ + xf_emit(ctx, 1, 0x2a712488); /* ffffffff SRC_TIC_0 */ + xf_emit(ctx, 1, 0); /* ffffffff SRC_TIC_1 */ + xf_emit(ctx, 1, 0x4085c000); /* ffffffff SRC_TIC_2 */ + xf_emit(ctx, 1, 0x40); /* ffffffff SRC_TIC_3 */ + xf_emit(ctx, 1, 0x100); /* ffffffff SRC_TIC_4 */ + xf_emit(ctx, 1, 0x10100); /* ffffffff SRC_TIC_5 */ + xf_emit(ctx, 1, 0x02800000); /* ffffffff SRC_TIC_6 */ + xf_emit(ctx, 1, 0); /* ffffffff SRC_TIC_7 */ + if (dev_priv->chipset == 0x50) { + xf_emit(ctx, 1, 0); /* 00000001 turing UNK358 */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A34? */ + xf_emit(ctx, 1, 0); /* 00000003 turing UNK37C tesla UNK1690 */ + xf_emit(ctx, 1, 0); /* 00000003 BLIT_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000001 turing UNK32C tesla UNK0F94 */ + } else if (!IS_NVAAF(dev_priv->chipset)) { + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A34? */ + xf_emit(ctx, 1, 0); /* 00000003 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + xf_emit(ctx, 1, 0); /* 00000003 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1664 / turing UNK03E8 */ + xf_emit(ctx, 1, 0); /* 00000003 */ + xf_emit(ctx, 1, 0); /* 000003ff */ + } else { + xf_emit(ctx, 0x6, 0); + } + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A34 */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_TEXTURE */ + xf_emit(ctx, 1, 0); /* 0000ffff DMA_SRC */ +} + +static void +nv50_graph_construct_xfer_unk8cxx(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + xf_emit(ctx, 1, 0); /* 00000001 UNK1534 */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 2, 0); /* 7, ffff0ff3 */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE */ + xf_emit(ctx, 1, 0x04e3bfdf); /* ffffffff UNK0D64 */ + xf_emit(ctx, 1, 0x04e3bfdf); /* ffffffff UNK0DF4 */ + xf_emit(ctx, 1, 1); /* 00000001 UNK15B4 */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_STIPPLE_ENABLE */ + xf_emit(ctx, 1, 0x00ffff00); /* 00ffffff LINE_STIPPLE_PATTERN */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK0F98 */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1668 */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_STIPPLE_ENABLE */ + xf_emit(ctx, 1, 0x00ffff00); /* 00ffffff LINE_STIPPLE_PATTERN */ + xf_emit(ctx, 1, 0); /* 00000001 POLYGON_SMOOTH_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 UNK1534 */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1658 */ + xf_emit(ctx, 1, 0); /* 00000001 LINE_SMOOTH_ENABLE */ + xf_emit(ctx, 1, 0); /* ffff0ff3 */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 DEPTH_WRITE */ + xf_emit(ctx, 1, 1); /* 00000001 UNK15B4 */ + xf_emit(ctx, 1, 0); /* 00000001 POINT_SPRITE_ENABLE */ + xf_emit(ctx, 1, 1); /* 00000001 tesla UNK165C */ + xf_emit(ctx, 1, 0x30201000); /* ffffffff tesla UNK1670 */ + xf_emit(ctx, 1, 0x70605040); /* ffffffff tesla UNK1670 */ + xf_emit(ctx, 1, 0xb8a89888); /* ffffffff tesla UNK1670 */ + xf_emit(ctx, 1, 0xf8e8d8c8); /* ffffffff tesla UNK1670 */ + xf_emit(ctx, 1, 0); /* 00000001 VERTEX_TWO_SIDE_ENABLE */ + xf_emit(ctx, 1, 0x1a); /* 0000001f POLYGON_MODE */ +} + +static void +nv50_graph_construct_xfer_tp(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + if (dev_priv->chipset < 0xa0) { + nv50_graph_construct_xfer_unk84xx(ctx); + nv50_graph_construct_xfer_tprop(ctx); + nv50_graph_construct_xfer_tex(ctx); + nv50_graph_construct_xfer_unk8cxx(ctx); + } else { + nv50_graph_construct_xfer_tex(ctx); + nv50_graph_construct_xfer_tprop(ctx); + nv50_graph_construct_xfer_unk8cxx(ctx); + nv50_graph_construct_xfer_unk84xx(ctx); + } +} + +static void +nv50_graph_construct_xfer_mpc(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i, mpcnt = 2; + switch (dev_priv->chipset) { + case 0x98: + case 0xaa: + mpcnt = 1; + break; + case 0x50: + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0xa8: + case 0xac: + mpcnt = 2; + break; + case 0xa0: + case 0xa3: + case 0xa5: + case 0xaf: + mpcnt = 3; + break; + } + for (i = 0; i < mpcnt; i++) { + xf_emit(ctx, 1, 0); /* ff */ + xf_emit(ctx, 1, 0x80); /* ffffffff tesla UNK1404 */ + xf_emit(ctx, 1, 0x80007004); /* ffffffff tesla UNK12B0 */ + xf_emit(ctx, 1, 0x04000400); /* ffffffff */ + if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 1, 0xc0); /* 00007fff tesla UNK152C */ + xf_emit(ctx, 1, 0x1000); /* 0000ffff tesla UNK0D60 */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A30 */ + if (dev_priv->chipset == 0x86 || dev_priv->chipset == 0x98 || dev_priv->chipset == 0xa8 || IS_NVAAF(dev_priv->chipset)) { + xf_emit(ctx, 1, 0xe00); /* 7fff */ + xf_emit(ctx, 1, 0x1e00); /* 7fff */ + } + xf_emit(ctx, 1, 1); /* 000000ff VP_REG_ALLOC_TEMP */ + xf_emit(ctx, 1, 0); /* 00000001 LINKED_TSC */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 2, 0x1000); /* 7fff tesla UNK141C */ + xf_emit(ctx, 1, 1); /* 000000ff GP_REG_ALLOC_TEMP */ + xf_emit(ctx, 1, 0); /* 00000001 GP_ENABLE */ + xf_emit(ctx, 1, 4); /* 000000ff FP_REG_ALLOC_TEMP */ + xf_emit(ctx, 1, 2); /* 00000003 REG_MODE */ + if (IS_NVAAF(dev_priv->chipset)) + xf_emit(ctx, 0xb, 0); /* RO */ + else if (dev_priv->chipset >= 0xa0) + xf_emit(ctx, 0xc, 0); /* RO */ + else + xf_emit(ctx, 0xa, 0); /* RO */ + } + xf_emit(ctx, 1, 0x08100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + if (dev_priv->chipset >= 0xa0) { + xf_emit(ctx, 1, 0x1fe21); /* 0003ffff tesla UNK0FAC */ + } + xf_emit(ctx, 3, 0); /* 7fff, 0, 0 */ + xf_emit(ctx, 1, 0); /* 00000001 tesla UNK1534 */ + xf_emit(ctx, 1, 0); /* 7/f MULTISAMPLE_SAMPLES_LOG2 */ + xf_emit(ctx, 4, 0xffff); /* 0000ffff MSAA_MASK */ + xf_emit(ctx, 1, 1); /* 00000001 LANES32 */ + xf_emit(ctx, 1, 0x10001); /* 00ffffff BLOCK_ALLOC */ + xf_emit(ctx, 1, 0x10001); /* ffffffff BLOCKDIM_XY */ + xf_emit(ctx, 1, 1); /* 0000ffff BLOCKDIM_Z */ + xf_emit(ctx, 1, 0); /* ffffffff SHARED_SIZE */ + xf_emit(ctx, 1, 0x1fe21); /* 1ffff/3ffff[NVA0+] tesla UNk0FAC */ + xf_emit(ctx, 1, 0); /* ffffffff tesla UNK1A34 */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 1); /* 0000001f tesla UNK169C */ + xf_emit(ctx, 1, 0); /* ff/3ff */ + xf_emit(ctx, 1, 0); /* 1 LINKED_TSC */ + xf_emit(ctx, 1, 0); /* ff FP_ADDRESS_HIGH */ + xf_emit(ctx, 1, 0); /* ffffffff FP_ADDRESS_LOW */ + xf_emit(ctx, 1, 0x08100c12); /* 1fffffff FP_INTERPOLANT_CTRL */ + xf_emit(ctx, 1, 4); /* 00000007 FP_CONTROL */ + xf_emit(ctx, 1, 0); /* 000000ff FRAG_COLOR_CLAMP_EN */ + xf_emit(ctx, 1, 2); /* 00000003 REG_MODE */ + xf_emit(ctx, 1, 0x11); /* 0000007f RT_FORMAT */ + xf_emit(ctx, 7, 0); /* 0000007f RT_FORMAT */ + xf_emit(ctx, 1, 0); /* 00000007 */ + xf_emit(ctx, 1, 0xfac6881); /* 0fffffff RT_CONTROL */ + xf_emit(ctx, 1, 0); /* 00000003 MULTISAMPLE_CTRL */ + if (IS_NVA3F(dev_priv->chipset)) + xf_emit(ctx, 1, 3); /* 00000003 tesla UNK16B4 */ + xf_emit(ctx, 1, 0); /* 00000001 ALPHA_TEST_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000007 ALPHA_TEST_FUNC */ + xf_emit(ctx, 1, 0); /* 00000001 FRAMEBUFFER_SRGB */ + xf_emit(ctx, 1, 4); /* ffffffff tesla UNK1400 */ + xf_emit(ctx, 8, 0); /* 00000001 BLEND_ENABLE */ + xf_emit(ctx, 1, 0); /* 00000001 LOGIC_OP_ENABLE */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_RGB */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_RGB */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_RGB */ + xf_emit(ctx, 1, 2); /* 0000001f BLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 1, 1); /* 0000001f BLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 1, 1); /* 00000007 BLEND_EQUATION_ALPHA */ + xf_emit(ctx, 1, 1); /* 00000001 UNK133C */ + if (IS_NVA3F(dev_priv->chipset)) { + xf_emit(ctx, 1, 0); /* 00000001 UNK12E4 */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_FUNC_SRC_RGB */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_FUNC_DST_RGB */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_RGB */ + xf_emit(ctx, 8, 2); /* 0000001f IBLEND_FUNC_SRC_ALPHA */ + xf_emit(ctx, 8, 1); /* 0000001f IBLEND_FUNC_DST_ALPHA */ + xf_emit(ctx, 8, 1); /* 00000007 IBLEND_EQUATION_ALPHA */ + xf_emit(ctx, 8, 1); /* 00000001 IBLEND_UNK00 */ + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK1928 */ + xf_emit(ctx, 1, 0); /* 00000001 UNK1140 */ + } + xf_emit(ctx, 1, 0); /* 00000003 tesla UNK0F90 */ + xf_emit(ctx, 1, 4); /* 000000ff FP_RESULT_COUNT */ + /* XXX: demagic this part some day */ + if (dev_priv->chipset == 0x50) + xf_emit(ctx, 0x3a0, 0); + else if (dev_priv->chipset < 0x94) + xf_emit(ctx, 0x3a2, 0); + else if (dev_priv->chipset == 0x98 || dev_priv->chipset == 0xaa) + xf_emit(ctx, 0x39f, 0); + else + xf_emit(ctx, 0x3a3, 0); + xf_emit(ctx, 1, 0x11); /* 3f/7f DST_FORMAT */ + xf_emit(ctx, 1, 0); /* 7 OPERATION */ + xf_emit(ctx, 1, 1); /* 1 DST_LINEAR */ + xf_emit(ctx, 0x2d, 0); +} + +static void +nv50_graph_construct_xfer2(struct nouveau_grctx *ctx) +{ + struct drm_nouveau_private *dev_priv = ctx->dev->dev_private; + int i; + uint32_t offset; + uint32_t units = nv_rd32 (ctx->dev, 0x1540); + int size = 0; + + offset = (ctx->ctxvals_pos+0x3f)&~0x3f; + + if (dev_priv->chipset < 0xa0) { + for (i = 0; i < 8; i++) { + ctx->ctxvals_pos = offset + i; + /* that little bugger belongs to csched. No idea + * what it's doing here. */ + if (i == 0) + xf_emit(ctx, 1, 0x08100c12); /* FP_INTERPOLANT_CTRL */ + if (units & (1 << i)) + nv50_graph_construct_xfer_mpc(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + } + } else { + /* Strand 0: TPs 0, 1 */ + ctx->ctxvals_pos = offset; + /* that little bugger belongs to csched. No idea + * what it's doing here. */ + xf_emit(ctx, 1, 0x08100c12); /* FP_INTERPOLANT_CTRL */ + if (units & (1 << 0)) + nv50_graph_construct_xfer_mpc(ctx); + if (units & (1 << 1)) + nv50_graph_construct_xfer_mpc(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 1: TPs 2, 3 */ + ctx->ctxvals_pos = offset + 1; + if (units & (1 << 2)) + nv50_graph_construct_xfer_mpc(ctx); + if (units & (1 << 3)) + nv50_graph_construct_xfer_mpc(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 2: TPs 4, 5, 6 */ + ctx->ctxvals_pos = offset + 2; + if (units & (1 << 4)) + nv50_graph_construct_xfer_mpc(ctx); + if (units & (1 << 5)) + nv50_graph_construct_xfer_mpc(ctx); + if (units & (1 << 6)) + nv50_graph_construct_xfer_mpc(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + + /* Strand 3: TPs 7, 8, 9 */ + ctx->ctxvals_pos = offset + 3; + if (units & (1 << 7)) + nv50_graph_construct_xfer_mpc(ctx); + if (units & (1 << 8)) + nv50_graph_construct_xfer_mpc(ctx); + if (units & (1 << 9)) + nv50_graph_construct_xfer_mpc(ctx); + if ((ctx->ctxvals_pos-offset)/8 > size) + size = (ctx->ctxvals_pos-offset)/8; + } + ctx->ctxvals_pos = offset + size * 8; + ctx->ctxvals_pos = (ctx->ctxvals_pos+0x3f)&~0x3f; + cp_lsr (ctx, offset); + cp_out (ctx, CP_SET_XFER_POINTER); + cp_lsr (ctx, size); + cp_out (ctx, CP_SEEK_2); + cp_out (ctx, CP_XFER_2); + cp_wait(ctx, XFER, BUSY); +} diff --git a/drivers/gpu/drm/nouveau/nv50_instmem.c b/drivers/gpu/drm/nouveau/nv50_instmem.c new file mode 100644 index 00000000..a7c12c94 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_instmem.c @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" + +#include "nouveau_drv.h" +#include "nouveau_vm.h" + +#define BAR1_VM_BASE 0x0020000000ULL +#define BAR1_VM_SIZE pci_resource_len(dev->pdev, 1) +#define BAR3_VM_BASE 0x0000000000ULL +#define BAR3_VM_SIZE pci_resource_len(dev->pdev, 3) + +struct nv50_instmem_priv { + uint32_t save1700[5]; /* 0x1700->0x1710 */ + + struct nouveau_gpuobj *bar1_dmaobj; + struct nouveau_gpuobj *bar3_dmaobj; +}; + +static void +nv50_channel_del(struct nouveau_channel **pchan) +{ + struct nouveau_channel *chan; + + chan = *pchan; + *pchan = NULL; + if (!chan) + return; + + nouveau_gpuobj_ref(NULL, &chan->ramfc); + nouveau_vm_ref(NULL, &chan->vm, chan->vm_pd); + nouveau_gpuobj_ref(NULL, &chan->vm_pd); + if (drm_mm_initialized(&chan->ramin_heap)) + drm_mm_takedown(&chan->ramin_heap); + nouveau_gpuobj_ref(NULL, &chan->ramin); + kfree(chan); +} + +static int +nv50_channel_new(struct drm_device *dev, u32 size, struct nouveau_vm *vm, + struct nouveau_channel **pchan) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 pgd = (dev_priv->chipset == 0x50) ? 0x1400 : 0x0200; + u32 fc = (dev_priv->chipset == 0x50) ? 0x0000 : 0x4200; + struct nouveau_channel *chan; + int ret, i; + + chan = kzalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + chan->dev = dev; + + ret = nouveau_gpuobj_new(dev, NULL, size, 0x1000, 0, &chan->ramin); + if (ret) { + nv50_channel_del(&chan); + return ret; + } + + ret = drm_mm_init(&chan->ramin_heap, 0x6000, chan->ramin->size); + if (ret) { + nv50_channel_del(&chan); + return ret; + } + + ret = nouveau_gpuobj_new_fake(dev, chan->ramin->pinst == ~0 ? ~0 : + chan->ramin->pinst + pgd, + chan->ramin->vinst + pgd, + 0x4000, NVOBJ_FLAG_ZERO_ALLOC, + &chan->vm_pd); + if (ret) { + nv50_channel_del(&chan); + return ret; + } + + for (i = 0; i < 0x4000; i += 8) { + nv_wo32(chan->vm_pd, i + 0, 0x00000000); + nv_wo32(chan->vm_pd, i + 4, 0xdeadcafe); + } + + ret = nouveau_vm_ref(vm, &chan->vm, chan->vm_pd); + if (ret) { + nv50_channel_del(&chan); + return ret; + } + + ret = nouveau_gpuobj_new_fake(dev, chan->ramin->pinst == ~0 ? ~0 : + chan->ramin->pinst + fc, + chan->ramin->vinst + fc, 0x100, + NVOBJ_FLAG_ZERO_ALLOC, &chan->ramfc); + if (ret) { + nv50_channel_del(&chan); + return ret; + } + + *pchan = chan; + return 0; +} + +int +nv50_instmem_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_instmem_priv *priv; + struct nouveau_channel *chan; + struct nouveau_vm *vm; + int ret, i; + u32 tmp; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_priv->engine.instmem.priv = priv; + + /* Save state, will restore at takedown. */ + for (i = 0x1700; i <= 0x1710; i += 4) + priv->save1700[(i-0x1700)/4] = nv_rd32(dev, i); + + /* Global PRAMIN heap */ + ret = drm_mm_init(&dev_priv->ramin_heap, 0, dev_priv->ramin_size); + if (ret) { + NV_ERROR(dev, "Failed to init RAMIN heap\n"); + goto error; + } + + /* BAR3 */ + ret = nouveau_vm_new(dev, BAR3_VM_BASE, BAR3_VM_SIZE, BAR3_VM_BASE, + &dev_priv->bar3_vm); + if (ret) + goto error; + + ret = nouveau_gpuobj_new(dev, NULL, (BAR3_VM_SIZE >> 12) * 8, + 0x1000, NVOBJ_FLAG_DONT_MAP | + NVOBJ_FLAG_ZERO_ALLOC, + &dev_priv->bar3_vm->pgt[0].obj[0]); + if (ret) + goto error; + dev_priv->bar3_vm->pgt[0].refcount[0] = 1; + + nv50_instmem_map(dev_priv->bar3_vm->pgt[0].obj[0]); + + ret = nv50_channel_new(dev, 128 * 1024, dev_priv->bar3_vm, &chan); + if (ret) + goto error; + dev_priv->channels.ptr[0] = dev_priv->channels.ptr[127] = chan; + + ret = nv50_gpuobj_dma_new(chan, 0x0000, BAR3_VM_BASE, BAR3_VM_SIZE, + NV_MEM_TARGET_VM, NV_MEM_ACCESS_VM, + NV_MEM_TYPE_VM, NV_MEM_COMP_VM, + &priv->bar3_dmaobj); + if (ret) + goto error; + + nv_wr32(dev, 0x001704, 0x00000000 | (chan->ramin->vinst >> 12)); + nv_wr32(dev, 0x001704, 0x40000000 | (chan->ramin->vinst >> 12)); + nv_wr32(dev, 0x00170c, 0x80000000 | (priv->bar3_dmaobj->cinst >> 4)); + + dev_priv->engine.instmem.flush(dev); + dev_priv->ramin_available = true; + + tmp = nv_ro32(chan->ramin, 0); + nv_wo32(chan->ramin, 0, ~tmp); + if (nv_ro32(chan->ramin, 0) != ~tmp) { + NV_ERROR(dev, "PRAMIN readback failed\n"); + ret = -EIO; + goto error; + } + nv_wo32(chan->ramin, 0, tmp); + + /* BAR1 */ + ret = nouveau_vm_new(dev, BAR1_VM_BASE, BAR1_VM_SIZE, BAR1_VM_BASE, &vm); + if (ret) + goto error; + + ret = nouveau_vm_ref(vm, &dev_priv->bar1_vm, chan->vm_pd); + if (ret) + goto error; + nouveau_vm_ref(NULL, &vm, NULL); + + ret = nv50_gpuobj_dma_new(chan, 0x0000, BAR1_VM_BASE, BAR1_VM_SIZE, + NV_MEM_TARGET_VM, NV_MEM_ACCESS_VM, + NV_MEM_TYPE_VM, NV_MEM_COMP_VM, + &priv->bar1_dmaobj); + if (ret) + goto error; + + nv_wr32(dev, 0x001708, 0x80000000 | (priv->bar1_dmaobj->cinst >> 4)); + for (i = 0; i < 8; i++) + nv_wr32(dev, 0x1900 + (i*4), 0); + + /* Create shared channel VM, space is reserved at the beginning + * to catch "NULL pointer" references + */ + ret = nouveau_vm_new(dev, 0, (1ULL << 40), 0x0020000000ULL, + &dev_priv->chan_vm); + if (ret) + return ret; + + return 0; + +error: + nv50_instmem_takedown(dev); + return ret; +} + +void +nv50_instmem_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_instmem_priv *priv = dev_priv->engine.instmem.priv; + struct nouveau_channel *chan = dev_priv->channels.ptr[0]; + int i; + + NV_DEBUG(dev, "\n"); + + if (!priv) + return; + + dev_priv->ramin_available = false; + + nouveau_vm_ref(NULL, &dev_priv->chan_vm, NULL); + + for (i = 0x1700; i <= 0x1710; i += 4) + nv_wr32(dev, i, priv->save1700[(i - 0x1700) / 4]); + + nouveau_gpuobj_ref(NULL, &priv->bar3_dmaobj); + nouveau_gpuobj_ref(NULL, &priv->bar1_dmaobj); + + nouveau_vm_ref(NULL, &dev_priv->bar1_vm, chan->vm_pd); + dev_priv->channels.ptr[127] = 0; + nv50_channel_del(&dev_priv->channels.ptr[0]); + + nouveau_gpuobj_ref(NULL, &dev_priv->bar3_vm->pgt[0].obj[0]); + nouveau_vm_ref(NULL, &dev_priv->bar3_vm, NULL); + + if (drm_mm_initialized(&dev_priv->ramin_heap)) + drm_mm_takedown(&dev_priv->ramin_heap); + + dev_priv->engine.instmem.priv = NULL; + kfree(priv); +} + +int +nv50_instmem_suspend(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + dev_priv->ramin_available = false; + return 0; +} + +void +nv50_instmem_resume(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_instmem_priv *priv = dev_priv->engine.instmem.priv; + struct nouveau_channel *chan = dev_priv->channels.ptr[0]; + int i; + + /* Poke the relevant regs, and pray it works :) */ + nv_wr32(dev, NV50_PUNK_BAR_CFG_BASE, (chan->ramin->vinst >> 12)); + nv_wr32(dev, NV50_PUNK_UNK1710, 0); + nv_wr32(dev, NV50_PUNK_BAR_CFG_BASE, (chan->ramin->vinst >> 12) | + NV50_PUNK_BAR_CFG_BASE_VALID); + nv_wr32(dev, NV50_PUNK_BAR1_CTXDMA, (priv->bar1_dmaobj->cinst >> 4) | + NV50_PUNK_BAR1_CTXDMA_VALID); + nv_wr32(dev, NV50_PUNK_BAR3_CTXDMA, (priv->bar3_dmaobj->cinst >> 4) | + NV50_PUNK_BAR3_CTXDMA_VALID); + + for (i = 0; i < 8; i++) + nv_wr32(dev, 0x1900 + (i*4), 0); + + dev_priv->ramin_available = true; +} + +struct nv50_gpuobj_node { + struct nouveau_mem *vram; + struct nouveau_vma chan_vma; + u32 align; +}; + +int +nv50_instmem_get(struct nouveau_gpuobj *gpuobj, struct nouveau_channel *chan, + u32 size, u32 align) +{ + struct drm_device *dev = gpuobj->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_vram_engine *vram = &dev_priv->engine.vram; + struct nv50_gpuobj_node *node = NULL; + int ret; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return -ENOMEM; + node->align = align; + + size = (size + 4095) & ~4095; + align = max(align, (u32)4096); + + ret = vram->get(dev, size, align, 0, 0, &node->vram); + if (ret) { + kfree(node); + return ret; + } + + gpuobj->vinst = node->vram->offset; + + if (gpuobj->flags & NVOBJ_FLAG_VM) { + u32 flags = NV_MEM_ACCESS_RW; + if (!(gpuobj->flags & NVOBJ_FLAG_VM_USER)) + flags |= NV_MEM_ACCESS_SYS; + + ret = nouveau_vm_get(chan->vm, size, 12, flags, + &node->chan_vma); + if (ret) { + vram->put(dev, &node->vram); + kfree(node); + return ret; + } + + nouveau_vm_map(&node->chan_vma, node->vram); + gpuobj->linst = node->chan_vma.offset; + } + + gpuobj->size = size; + gpuobj->node = node; + return 0; +} + +void +nv50_instmem_put(struct nouveau_gpuobj *gpuobj) +{ + struct drm_device *dev = gpuobj->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_vram_engine *vram = &dev_priv->engine.vram; + struct nv50_gpuobj_node *node; + + node = gpuobj->node; + gpuobj->node = NULL; + + if (node->chan_vma.node) { + nouveau_vm_unmap(&node->chan_vma); + nouveau_vm_put(&node->chan_vma); + } + vram->put(dev, &node->vram); + kfree(node); +} + +int +nv50_instmem_map(struct nouveau_gpuobj *gpuobj) +{ + struct drm_nouveau_private *dev_priv = gpuobj->dev->dev_private; + struct nv50_gpuobj_node *node = gpuobj->node; + int ret; + + ret = nouveau_vm_get(dev_priv->bar3_vm, gpuobj->size, 12, + NV_MEM_ACCESS_RW, &node->vram->bar_vma); + if (ret) + return ret; + + nouveau_vm_map(&node->vram->bar_vma, node->vram); + gpuobj->pinst = node->vram->bar_vma.offset; + return 0; +} + +void +nv50_instmem_unmap(struct nouveau_gpuobj *gpuobj) +{ + struct nv50_gpuobj_node *node = gpuobj->node; + + if (node->vram->bar_vma.node) { + nouveau_vm_unmap(&node->vram->bar_vma); + nouveau_vm_put(&node->vram->bar_vma); + } +} + +void +nv50_instmem_flush(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->vm_lock, flags); + nv_wr32(dev, 0x00330c, 0x00000001); + if (!nv_wait(dev, 0x00330c, 0x00000002, 0x00000000)) + NV_ERROR(dev, "PRAMIN flush timeout\n"); + spin_unlock_irqrestore(&dev_priv->vm_lock, flags); +} + +void +nv84_instmem_flush(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->vm_lock, flags); + nv_wr32(dev, 0x070000, 0x00000001); + if (!nv_wait(dev, 0x070000, 0x00000002, 0x00000000)) + NV_ERROR(dev, "PRAMIN flush timeout\n"); + spin_unlock_irqrestore(&dev_priv->vm_lock, flags); +} + diff --git a/drivers/gpu/drm/nouveau/nv50_mc.c b/drivers/gpu/drm/nouveau/nv50_mc.c new file mode 100644 index 00000000..e0a9c3fa --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_mc.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 Ben Skeggs. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" + +int +nv50_mc_init(struct drm_device *dev) +{ + nv_wr32(dev, NV03_PMC_ENABLE, 0xFFFFFFFF); + return 0; +} + +void nv50_mc_takedown(struct drm_device *dev) +{ +} diff --git a/drivers/gpu/drm/nouveau/nv50_mpeg.c b/drivers/gpu/drm/nouveau/nv50_mpeg.c new file mode 100644 index 00000000..b57a2d18 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_mpeg.c @@ -0,0 +1,256 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_ramht.h" + +struct nv50_mpeg_engine { + struct nouveau_exec_engine base; +}; + +static inline u32 +CTX_PTR(struct drm_device *dev, u32 offset) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset == 0x50) + offset += 0x0260; + else + offset += 0x0060; + + return offset; +} + +static int +nv50_mpeg_context_new(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramin = chan->ramin; + struct nouveau_gpuobj *ctx = NULL; + int ret; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + ret = nouveau_gpuobj_new(dev, chan, 128 * 4, 0, NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &ctx); + if (ret) + return ret; + + nv_wo32(ramin, CTX_PTR(dev, 0x00), 0x80190002); + nv_wo32(ramin, CTX_PTR(dev, 0x04), ctx->vinst + ctx->size - 1); + nv_wo32(ramin, CTX_PTR(dev, 0x08), ctx->vinst); + nv_wo32(ramin, CTX_PTR(dev, 0x0c), 0); + nv_wo32(ramin, CTX_PTR(dev, 0x10), 0); + nv_wo32(ramin, CTX_PTR(dev, 0x14), 0x00010000); + + nv_wo32(ctx, 0x70, 0x00801ec1); + nv_wo32(ctx, 0x7c, 0x0000037c); + dev_priv->engine.instmem.flush(dev); + + chan->engctx[engine] = ctx; + return 0; +} + +static void +nv50_mpeg_context_del(struct nouveau_channel *chan, int engine) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nouveau_gpuobj *ctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + unsigned long flags; + u32 inst, i; + + if (!chan->ramin) + return; + + inst = chan->ramin->vinst >> 12; + inst |= 0x80000000; + + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000000); + if (nv_rd32(dev, 0x00b318) == inst) + nv_mask(dev, 0x00b318, 0x80000000, 0x00000000); + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000001); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + + for (i = 0x00; i <= 0x14; i += 4) + nv_wo32(chan->ramin, CTX_PTR(dev, i), 0x00000000); + nouveau_gpuobj_ref(NULL, &ctx); + chan->engctx[engine] = NULL; +} + +static int +nv50_mpeg_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *obj = NULL; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 16, 16, NVOBJ_FLAG_ZERO_FREE, &obj); + if (ret) + return ret; + obj->engine = 2; + obj->class = class; + + nv_wo32(obj, 0x00, class); + nv_wo32(obj, 0x04, 0x00000000); + nv_wo32(obj, 0x08, 0x00000000); + nv_wo32(obj, 0x0c, 0x00000000); + dev_priv->engine.instmem.flush(dev); + + ret = nouveau_ramht_insert(chan, handle, obj); + nouveau_gpuobj_ref(NULL, &obj); + return ret; +} + +static void +nv50_mpeg_tlb_flush(struct drm_device *dev, int engine) +{ + nv50_vm_flush_engine(dev, 0x08); +} + +static int +nv50_mpeg_init(struct drm_device *dev, int engine) +{ + nv_wr32(dev, 0x00b32c, 0x00000000); + nv_wr32(dev, 0x00b314, 0x00000100); + nv_wr32(dev, 0x00b0e0, 0x0000001a); + + nv_wr32(dev, 0x00b220, 0x00000044); + nv_wr32(dev, 0x00b300, 0x00801ec1); + nv_wr32(dev, 0x00b390, 0x00000000); + nv_wr32(dev, 0x00b394, 0x00000000); + nv_wr32(dev, 0x00b398, 0x00000000); + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000001); + + nv_wr32(dev, 0x00b100, 0xffffffff); + nv_wr32(dev, 0x00b140, 0xffffffff); + + if (!nv_wait(dev, 0x00b200, 0x00000001, 0x00000000)) { + NV_ERROR(dev, "PMPEG init: 0x%08x\n", nv_rd32(dev, 0x00b200)); + return -EBUSY; + } + + return 0; +} + +static int +nv50_mpeg_fini(struct drm_device *dev, int engine, bool suspend) +{ + /*XXX: context save for s/r */ + nv_mask(dev, 0x00b32c, 0x00000001, 0x00000000); + nv_wr32(dev, 0x00b140, 0x00000000); + return 0; +} + +static void +nv50_mpeg_isr(struct drm_device *dev) +{ + u32 stat = nv_rd32(dev, 0x00b100); + u32 type = nv_rd32(dev, 0x00b230); + u32 mthd = nv_rd32(dev, 0x00b234); + u32 data = nv_rd32(dev, 0x00b238); + u32 show = stat; + + if (stat & 0x01000000) { + /* happens on initial binding of the object */ + if (type == 0x00000020 && mthd == 0x0000) { + nv_wr32(dev, 0x00b308, 0x00000100); + show &= ~0x01000000; + } + } + + if (show && nouveau_ratelimit()) { + NV_INFO(dev, "PMPEG - 0x%08x 0x%08x 0x%08x 0x%08x\n", + stat, type, mthd, data); + } + + nv_wr32(dev, 0x00b100, stat); + nv_wr32(dev, 0x00b230, 0x00000001); + nv50_fb_vm_trap(dev, 1); +} + +static void +nv50_vpe_isr(struct drm_device *dev) +{ + if (nv_rd32(dev, 0x00b100)) + nv50_mpeg_isr(dev); + + if (nv_rd32(dev, 0x00b800)) { + u32 stat = nv_rd32(dev, 0x00b800); + NV_INFO(dev, "PMSRCH: 0x%08x\n", stat); + nv_wr32(dev, 0xb800, stat); + } +} + +static void +nv50_mpeg_destroy(struct drm_device *dev, int engine) +{ + struct nv50_mpeg_engine *pmpeg = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, 0); + + NVOBJ_ENGINE_DEL(dev, MPEG); + kfree(pmpeg); +} + +int +nv50_mpeg_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_mpeg_engine *pmpeg; + + pmpeg = kzalloc(sizeof(*pmpeg), GFP_KERNEL); + if (!pmpeg) + return -ENOMEM; + + pmpeg->base.destroy = nv50_mpeg_destroy; + pmpeg->base.init = nv50_mpeg_init; + pmpeg->base.fini = nv50_mpeg_fini; + pmpeg->base.context_new = nv50_mpeg_context_new; + pmpeg->base.context_del = nv50_mpeg_context_del; + pmpeg->base.object_new = nv50_mpeg_object_new; + pmpeg->base.tlb_flush = nv50_mpeg_tlb_flush; + + if (dev_priv->chipset == 0x50) { + nouveau_irq_register(dev, 0, nv50_vpe_isr); + NVOBJ_ENGINE_ADD(dev, MPEG, &pmpeg->base); + NVOBJ_CLASS(dev, 0x3174, MPEG); +#if 0 + NVOBJ_ENGINE_ADD(dev, ME, &pme->base); + NVOBJ_CLASS(dev, 0x4075, ME); +#endif + } else { + nouveau_irq_register(dev, 0, nv50_mpeg_isr); + NVOBJ_ENGINE_ADD(dev, MPEG, &pmpeg->base); + NVOBJ_CLASS(dev, 0x8274, MPEG); + } + + return 0; + +} diff --git a/drivers/gpu/drm/nouveau/nv50_pm.c b/drivers/gpu/drm/nouveau/nv50_pm.c new file mode 100644 index 00000000..d020ed49 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_pm.c @@ -0,0 +1,886 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_bios.h" +#include "nouveau_hw.h" +#include "nouveau_pm.h" +#include "nouveau_hwsq.h" +#include "nv50_display.h" + +enum clk_src { + clk_src_crystal, + clk_src_href, + clk_src_hclk, + clk_src_hclkm3, + clk_src_hclkm3d2, + clk_src_host, + clk_src_nvclk, + clk_src_sclk, + clk_src_mclk, + clk_src_vdec, + clk_src_dom6 +}; + +static u32 read_clk(struct drm_device *, enum clk_src); + +static u32 +read_div(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + switch (dev_priv->chipset) { + case 0x50: /* it exists, but only has bit 31, not the dividers.. */ + case 0x84: + case 0x86: + case 0x98: + case 0xa0: + return nv_rd32(dev, 0x004700); + case 0x92: + case 0x94: + case 0x96: + return nv_rd32(dev, 0x004800); + default: + return 0x00000000; + } +} + +static u32 +read_pll_src(struct drm_device *dev, u32 base) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 coef, ref = read_clk(dev, clk_src_crystal); + u32 rsel = nv_rd32(dev, 0x00e18c); + int P, N, M, id; + + switch (dev_priv->chipset) { + case 0x50: + case 0xa0: + switch (base) { + case 0x4020: + case 0x4028: id = !!(rsel & 0x00000004); break; + case 0x4008: id = !!(rsel & 0x00000008); break; + case 0x4030: id = 0; break; + default: + NV_ERROR(dev, "ref: bad pll 0x%06x\n", base); + return 0; + } + + coef = nv_rd32(dev, 0x00e81c + (id * 0x0c)); + ref *= (coef & 0x01000000) ? 2 : 4; + P = (coef & 0x00070000) >> 16; + N = ((coef & 0x0000ff00) >> 8) + 1; + M = ((coef & 0x000000ff) >> 0) + 1; + break; + case 0x84: + case 0x86: + case 0x92: + coef = nv_rd32(dev, 0x00e81c); + P = (coef & 0x00070000) >> 16; + N = (coef & 0x0000ff00) >> 8; + M = (coef & 0x000000ff) >> 0; + break; + case 0x94: + case 0x96: + case 0x98: + rsel = nv_rd32(dev, 0x00c050); + switch (base) { + case 0x4020: rsel = (rsel & 0x00000003) >> 0; break; + case 0x4008: rsel = (rsel & 0x0000000c) >> 2; break; + case 0x4028: rsel = (rsel & 0x00001800) >> 11; break; + case 0x4030: rsel = 3; break; + default: + NV_ERROR(dev, "ref: bad pll 0x%06x\n", base); + return 0; + } + + switch (rsel) { + case 0: id = 1; break; + case 1: return read_clk(dev, clk_src_crystal); + case 2: return read_clk(dev, clk_src_href); + case 3: id = 0; break; + } + + coef = nv_rd32(dev, 0x00e81c + (id * 0x28)); + P = (nv_rd32(dev, 0x00e824 + (id * 0x28)) >> 16) & 7; + P += (coef & 0x00070000) >> 16; + N = (coef & 0x0000ff00) >> 8; + M = (coef & 0x000000ff) >> 0; + break; + default: + BUG_ON(1); + } + + if (M) + return (ref * N / M) >> P; + return 0; +} + +static u32 +read_pll_ref(struct drm_device *dev, u32 base) +{ + u32 src, mast = nv_rd32(dev, 0x00c040); + + switch (base) { + case 0x004028: + src = !!(mast & 0x00200000); + break; + case 0x004020: + src = !!(mast & 0x00400000); + break; + case 0x004008: + src = !!(mast & 0x00010000); + break; + case 0x004030: + src = !!(mast & 0x02000000); + break; + case 0x00e810: + return read_clk(dev, clk_src_crystal); + default: + NV_ERROR(dev, "bad pll 0x%06x\n", base); + return 0; + } + + if (src) + return read_clk(dev, clk_src_href); + return read_pll_src(dev, base); +} + +static u32 +read_pll(struct drm_device *dev, u32 base) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 mast = nv_rd32(dev, 0x00c040); + u32 ctrl = nv_rd32(dev, base + 0); + u32 coef = nv_rd32(dev, base + 4); + u32 ref = read_pll_ref(dev, base); + u32 clk = 0; + int N1, N2, M1, M2; + + if (base == 0x004028 && (mast & 0x00100000)) { + /* wtf, appears to only disable post-divider on nva0 */ + if (dev_priv->chipset != 0xa0) + return read_clk(dev, clk_src_dom6); + } + + N2 = (coef & 0xff000000) >> 24; + M2 = (coef & 0x00ff0000) >> 16; + N1 = (coef & 0x0000ff00) >> 8; + M1 = (coef & 0x000000ff); + if ((ctrl & 0x80000000) && M1) { + clk = ref * N1 / M1; + if ((ctrl & 0x40000100) == 0x40000000) { + if (M2) + clk = clk * N2 / M2; + else + clk = 0; + } + } + + return clk; +} + +static u32 +read_clk(struct drm_device *dev, enum clk_src src) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 mast = nv_rd32(dev, 0x00c040); + u32 P = 0; + + switch (src) { + case clk_src_crystal: + return dev_priv->crystal; + case clk_src_href: + return 100000; /* PCIE reference clock */ + case clk_src_hclk: + return read_clk(dev, clk_src_href) * 27778 / 10000; + case clk_src_hclkm3: + return read_clk(dev, clk_src_hclk) * 3; + case clk_src_hclkm3d2: + return read_clk(dev, clk_src_hclk) * 3 / 2; + case clk_src_host: + switch (mast & 0x30000000) { + case 0x00000000: return read_clk(dev, clk_src_href); + case 0x10000000: break; + case 0x20000000: /* !0x50 */ + case 0x30000000: return read_clk(dev, clk_src_hclk); + } + break; + case clk_src_nvclk: + if (!(mast & 0x00100000)) + P = (nv_rd32(dev, 0x004028) & 0x00070000) >> 16; + switch (mast & 0x00000003) { + case 0x00000000: return read_clk(dev, clk_src_crystal) >> P; + case 0x00000001: return read_clk(dev, clk_src_dom6); + case 0x00000002: return read_pll(dev, 0x004020) >> P; + case 0x00000003: return read_pll(dev, 0x004028) >> P; + } + break; + case clk_src_sclk: + P = (nv_rd32(dev, 0x004020) & 0x00070000) >> 16; + switch (mast & 0x00000030) { + case 0x00000000: + if (mast & 0x00000080) + return read_clk(dev, clk_src_host) >> P; + return read_clk(dev, clk_src_crystal) >> P; + case 0x00000010: break; + case 0x00000020: return read_pll(dev, 0x004028) >> P; + case 0x00000030: return read_pll(dev, 0x004020) >> P; + } + break; + case clk_src_mclk: + P = (nv_rd32(dev, 0x004008) & 0x00070000) >> 16; + if (nv_rd32(dev, 0x004008) & 0x00000200) { + switch (mast & 0x0000c000) { + case 0x00000000: + return read_clk(dev, clk_src_crystal) >> P; + case 0x00008000: + case 0x0000c000: + return read_clk(dev, clk_src_href) >> P; + } + } else { + return read_pll(dev, 0x004008) >> P; + } + break; + case clk_src_vdec: + P = (read_div(dev) & 0x00000700) >> 8; + switch (dev_priv->chipset) { + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0xa0: + switch (mast & 0x00000c00) { + case 0x00000000: + if (dev_priv->chipset == 0xa0) /* wtf?? */ + return read_clk(dev, clk_src_nvclk) >> P; + return read_clk(dev, clk_src_crystal) >> P; + case 0x00000400: + return 0; + case 0x00000800: + if (mast & 0x01000000) + return read_pll(dev, 0x004028) >> P; + return read_pll(dev, 0x004030) >> P; + case 0x00000c00: + return read_clk(dev, clk_src_nvclk) >> P; + } + break; + case 0x98: + switch (mast & 0x00000c00) { + case 0x00000000: + return read_clk(dev, clk_src_nvclk) >> P; + case 0x00000400: + return 0; + case 0x00000800: + return read_clk(dev, clk_src_hclkm3d2) >> P; + case 0x00000c00: + return read_clk(dev, clk_src_mclk) >> P; + } + break; + } + break; + case clk_src_dom6: + switch (dev_priv->chipset) { + case 0x50: + case 0xa0: + return read_pll(dev, 0x00e810) >> 2; + case 0x84: + case 0x86: + case 0x92: + case 0x94: + case 0x96: + case 0x98: + P = (read_div(dev) & 0x00000007) >> 0; + switch (mast & 0x0c000000) { + case 0x00000000: return read_clk(dev, clk_src_href); + case 0x04000000: break; + case 0x08000000: return read_clk(dev, clk_src_hclk); + case 0x0c000000: + return read_clk(dev, clk_src_hclkm3) >> P; + } + break; + default: + break; + } + default: + break; + } + + NV_DEBUG(dev, "unknown clock source %d 0x%08x\n", src, mast); + return 0; +} + +int +nv50_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + if (dev_priv->chipset == 0xaa || + dev_priv->chipset == 0xac) + return 0; + + perflvl->core = read_clk(dev, clk_src_nvclk); + perflvl->shader = read_clk(dev, clk_src_sclk); + perflvl->memory = read_clk(dev, clk_src_mclk); + if (dev_priv->chipset != 0x50) { + perflvl->vdec = read_clk(dev, clk_src_vdec); + perflvl->dom6 = read_clk(dev, clk_src_dom6); + } + + return 0; +} + +struct nv50_pm_state { + struct nouveau_pm_level *perflvl; + struct hwsq_ucode eclk_hwsq; + struct hwsq_ucode mclk_hwsq; + u32 mscript; + u32 mmast; + u32 mctrl; + u32 mcoef; +}; + +static u32 +calc_pll(struct drm_device *dev, u32 reg, struct pll_lims *pll, + u32 clk, int *N1, int *M1, int *log2P) +{ + struct nouveau_pll_vals coef; + int ret; + + ret = get_pll_limits(dev, reg, pll); + if (ret) + return 0; + + pll->vco2.maxfreq = 0; + pll->refclk = read_pll_ref(dev, reg); + if (!pll->refclk) + return 0; + + ret = nouveau_calc_pll_mnp(dev, pll, clk, &coef); + if (ret == 0) + return 0; + + *N1 = coef.N1; + *M1 = coef.M1; + *log2P = coef.log2P; + return ret; +} + +static inline u32 +calc_div(u32 src, u32 target, int *div) +{ + u32 clk0 = src, clk1 = src; + for (*div = 0; *div <= 7; (*div)++) { + if (clk0 <= target) { + clk1 = clk0 << (*div ? 1 : 0); + break; + } + clk0 >>= 1; + } + + if (target - clk0 <= clk1 - target) + return clk0; + (*div)--; + return clk1; +} + +static inline u32 +clk_same(u32 a, u32 b) +{ + return ((a / 1000) == (b / 1000)); +} + +static void +mclk_precharge(struct nouveau_mem_exec_func *exec) +{ + struct nv50_pm_state *info = exec->priv; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + + hwsq_wr32(hwsq, 0x1002d4, 0x00000001); +} + +static void +mclk_refresh(struct nouveau_mem_exec_func *exec) +{ + struct nv50_pm_state *info = exec->priv; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + + hwsq_wr32(hwsq, 0x1002d0, 0x00000001); +} + +static void +mclk_refresh_auto(struct nouveau_mem_exec_func *exec, bool enable) +{ + struct nv50_pm_state *info = exec->priv; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + + hwsq_wr32(hwsq, 0x100210, enable ? 0x80000000 : 0x00000000); +} + +static void +mclk_refresh_self(struct nouveau_mem_exec_func *exec, bool enable) +{ + struct nv50_pm_state *info = exec->priv; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + + hwsq_wr32(hwsq, 0x1002dc, enable ? 0x00000001 : 0x00000000); +} + +static void +mclk_wait(struct nouveau_mem_exec_func *exec, u32 nsec) +{ + struct nv50_pm_state *info = exec->priv; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + + if (nsec > 1000) + hwsq_usec(hwsq, (nsec + 500) / 1000); +} + +static u32 +mclk_mrg(struct nouveau_mem_exec_func *exec, int mr) +{ + if (mr <= 1) + return nv_rd32(exec->dev, 0x1002c0 + ((mr - 0) * 4)); + if (mr <= 3) + return nv_rd32(exec->dev, 0x1002e0 + ((mr - 2) * 4)); + return 0; +} + +static void +mclk_mrs(struct nouveau_mem_exec_func *exec, int mr, u32 data) +{ + struct drm_nouveau_private *dev_priv = exec->dev->dev_private; + struct nv50_pm_state *info = exec->priv; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + + if (mr <= 1) { + if (dev_priv->vram_rank_B) + hwsq_wr32(hwsq, 0x1002c8 + ((mr - 0) * 4), data); + hwsq_wr32(hwsq, 0x1002c0 + ((mr - 0) * 4), data); + } else + if (mr <= 3) { + if (dev_priv->vram_rank_B) + hwsq_wr32(hwsq, 0x1002e8 + ((mr - 2) * 4), data); + hwsq_wr32(hwsq, 0x1002e0 + ((mr - 2) * 4), data); + } +} + +static void +mclk_clock_set(struct nouveau_mem_exec_func *exec) +{ + struct nv50_pm_state *info = exec->priv; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + u32 ctrl = nv_rd32(exec->dev, 0x004008); + + info->mmast = nv_rd32(exec->dev, 0x00c040); + info->mmast &= ~0xc0000000; /* get MCLK_2 from HREF */ + info->mmast |= 0x0000c000; /* use MCLK_2 as MPLL_BYPASS clock */ + + hwsq_wr32(hwsq, 0xc040, info->mmast); + hwsq_wr32(hwsq, 0x4008, ctrl | 0x00000200); /* bypass MPLL */ + if (info->mctrl & 0x80000000) + hwsq_wr32(hwsq, 0x400c, info->mcoef); + hwsq_wr32(hwsq, 0x4008, info->mctrl); +} + +static void +mclk_timing_set(struct nouveau_mem_exec_func *exec) +{ + struct drm_device *dev = exec->dev; + struct nv50_pm_state *info = exec->priv; + struct nouveau_pm_level *perflvl = info->perflvl; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + int i; + + for (i = 0; i < 9; i++) { + u32 reg = 0x100220 + (i * 4); + u32 val = nv_rd32(dev, reg); + if (val != perflvl->timing.reg[i]) + hwsq_wr32(hwsq, reg, perflvl->timing.reg[i]); + } +} + +static int +calc_mclk(struct drm_device *dev, struct nouveau_pm_level *perflvl, + struct nv50_pm_state *info) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 crtc_mask = nv50_display_active_crtcs(dev); + struct nouveau_mem_exec_func exec = { + .dev = dev, + .precharge = mclk_precharge, + .refresh = mclk_refresh, + .refresh_auto = mclk_refresh_auto, + .refresh_self = mclk_refresh_self, + .wait = mclk_wait, + .mrg = mclk_mrg, + .mrs = mclk_mrs, + .clock_set = mclk_clock_set, + .timing_set = mclk_timing_set, + .priv = info + }; + struct hwsq_ucode *hwsq = &info->mclk_hwsq; + struct pll_lims pll; + int N, M, P; + int ret; + + /* use pcie refclock if possible, otherwise use mpll */ + info->mctrl = nv_rd32(dev, 0x004008); + info->mctrl &= ~0x81ff0200; + if (clk_same(perflvl->memory, read_clk(dev, clk_src_href))) { + info->mctrl |= 0x00000200 | (pll.log2p_bias << 19); + } else { + ret = calc_pll(dev, 0x4008, &pll, perflvl->memory, &N, &M, &P); + if (ret == 0) + return -EINVAL; + + info->mctrl |= 0x80000000 | (P << 22) | (P << 16); + info->mctrl |= pll.log2p_bias << 19; + info->mcoef = (N << 8) | M; + } + + /* build the ucode which will reclock the memory for us */ + hwsq_init(hwsq); + if (crtc_mask) { + hwsq_op5f(hwsq, crtc_mask, 0x00); /* wait for scanout */ + hwsq_op5f(hwsq, crtc_mask, 0x01); /* wait for vblank */ + } + if (dev_priv->chipset >= 0x92) + hwsq_wr32(hwsq, 0x611200, 0x00003300); /* disable scanout */ + hwsq_setf(hwsq, 0x10, 0); /* disable bus access */ + hwsq_op5f(hwsq, 0x00, 0x01); /* no idea :s */ + + ret = nouveau_mem_exec(&exec, perflvl); + if (ret) + return ret; + + hwsq_setf(hwsq, 0x10, 1); /* enable bus access */ + hwsq_op5f(hwsq, 0x00, 0x00); /* no idea, reverse of 0x00, 0x01? */ + if (dev_priv->chipset >= 0x92) + hwsq_wr32(hwsq, 0x611200, 0x00003330); /* enable scanout */ + hwsq_fini(hwsq); + return 0; +} + +void * +nv50_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nv50_pm_state *info; + struct hwsq_ucode *hwsq; + struct pll_lims pll; + u32 out, mast, divs, ctrl; + int clk, ret = -EINVAL; + int N, M, P1, P2; + + if (dev_priv->chipset == 0xaa || + dev_priv->chipset == 0xac) + return ERR_PTR(-ENODEV); + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + info->perflvl = perflvl; + + /* memory: build hwsq ucode which we'll use to reclock memory. + * use pcie refclock if possible, otherwise use mpll */ + info->mclk_hwsq.len = 0; + if (perflvl->memory) { + ret = calc_mclk(dev, perflvl, info); + if (ret) + goto error; + info->mscript = perflvl->memscript; + } + + divs = read_div(dev); + mast = info->mmast; + + /* start building HWSQ script for engine reclocking */ + hwsq = &info->eclk_hwsq; + hwsq_init(hwsq); + hwsq_setf(hwsq, 0x10, 0); /* disable bus access */ + hwsq_op5f(hwsq, 0x00, 0x01); /* wait for access disabled? */ + + /* vdec/dom6: switch to "safe" clocks temporarily */ + if (perflvl->vdec) { + mast &= ~0x00000c00; + divs &= ~0x00000700; + } + + if (perflvl->dom6) { + mast &= ~0x0c000000; + divs &= ~0x00000007; + } + + hwsq_wr32(hwsq, 0x00c040, mast); + + /* vdec: avoid modifying xpll until we know exactly how the other + * clock domains work, i suspect at least some of them can also be + * tied to xpll... + */ + if (perflvl->vdec) { + /* see how close we can get using nvclk as a source */ + clk = calc_div(perflvl->core, perflvl->vdec, &P1); + + /* see how close we can get using xpll/hclk as a source */ + if (dev_priv->chipset != 0x98) + out = read_pll(dev, 0x004030); + else + out = read_clk(dev, clk_src_hclkm3d2); + out = calc_div(out, perflvl->vdec, &P2); + + /* select whichever gets us closest */ + if (abs((int)perflvl->vdec - clk) <= + abs((int)perflvl->vdec - out)) { + if (dev_priv->chipset != 0x98) + mast |= 0x00000c00; + divs |= P1 << 8; + } else { + mast |= 0x00000800; + divs |= P2 << 8; + } + } + + /* dom6: nfi what this is, but we're limited to various combinations + * of the host clock frequency + */ + if (perflvl->dom6) { + if (clk_same(perflvl->dom6, read_clk(dev, clk_src_href))) { + mast |= 0x00000000; + } else + if (clk_same(perflvl->dom6, read_clk(dev, clk_src_hclk))) { + mast |= 0x08000000; + } else { + clk = read_clk(dev, clk_src_hclk) * 3; + clk = calc_div(clk, perflvl->dom6, &P1); + + mast |= 0x0c000000; + divs |= P1; + } + } + + /* vdec/dom6: complete switch to new clocks */ + switch (dev_priv->chipset) { + case 0x92: + case 0x94: + case 0x96: + hwsq_wr32(hwsq, 0x004800, divs); + break; + default: + hwsq_wr32(hwsq, 0x004700, divs); + break; + } + + hwsq_wr32(hwsq, 0x00c040, mast); + + /* core/shader: make sure sclk/nvclk are disconnected from their + * PLLs (nvclk to dom6, sclk to hclk) + */ + if (dev_priv->chipset < 0x92) + mast = (mast & ~0x001000b0) | 0x00100080; + else + mast = (mast & ~0x000000b3) | 0x00000081; + + hwsq_wr32(hwsq, 0x00c040, mast); + + /* core: for the moment at least, always use nvpll */ + clk = calc_pll(dev, 0x4028, &pll, perflvl->core, &N, &M, &P1); + if (clk == 0) + goto error; + + ctrl = nv_rd32(dev, 0x004028) & ~0xc03f0100; + mast &= ~0x00100000; + mast |= 3; + + hwsq_wr32(hwsq, 0x004028, 0x80000000 | (P1 << 19) | (P1 << 16) | ctrl); + hwsq_wr32(hwsq, 0x00402c, (N << 8) | M); + + /* shader: tie to nvclk if possible, otherwise use spll. have to be + * very careful that the shader clock is at least twice the core, or + * some chipsets will be very unhappy. i expect most or all of these + * cases will be handled by tying to nvclk, but it's possible there's + * corners + */ + ctrl = nv_rd32(dev, 0x004020) & ~0xc03f0100; + + if (P1-- && perflvl->shader == (perflvl->core << 1)) { + hwsq_wr32(hwsq, 0x004020, (P1 << 19) | (P1 << 16) | ctrl); + hwsq_wr32(hwsq, 0x00c040, 0x00000020 | mast); + } else { + clk = calc_pll(dev, 0x4020, &pll, perflvl->shader, &N, &M, &P1); + if (clk == 0) + goto error; + ctrl |= 0x80000000; + + hwsq_wr32(hwsq, 0x004020, (P1 << 19) | (P1 << 16) | ctrl); + hwsq_wr32(hwsq, 0x004024, (N << 8) | M); + hwsq_wr32(hwsq, 0x00c040, 0x00000030 | mast); + } + + hwsq_setf(hwsq, 0x10, 1); /* enable bus access */ + hwsq_op5f(hwsq, 0x00, 0x00); /* wait for access enabled? */ + hwsq_fini(hwsq); + + return info; +error: + kfree(info); + return ERR_PTR(ret); +} + +static int +prog_hwsq(struct drm_device *dev, struct hwsq_ucode *hwsq) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 hwsq_data, hwsq_kick; + int i; + + if (dev_priv->chipset < 0x94) { + hwsq_data = 0x001400; + hwsq_kick = 0x00000003; + } else { + hwsq_data = 0x080000; + hwsq_kick = 0x00000001; + } + /* upload hwsq ucode */ + nv_mask(dev, 0x001098, 0x00000008, 0x00000000); + nv_wr32(dev, 0x001304, 0x00000000); + if (dev_priv->chipset >= 0x92) + nv_wr32(dev, 0x001318, 0x00000000); + for (i = 0; i < hwsq->len / 4; i++) + nv_wr32(dev, hwsq_data + (i * 4), hwsq->ptr.u32[i]); + nv_mask(dev, 0x001098, 0x00000018, 0x00000018); + + /* launch, and wait for completion */ + nv_wr32(dev, 0x00130c, hwsq_kick); + if (!nv_wait(dev, 0x001308, 0x00000100, 0x00000000)) { + NV_ERROR(dev, "hwsq ucode exec timed out\n"); + NV_ERROR(dev, "0x001308: 0x%08x\n", nv_rd32(dev, 0x001308)); + for (i = 0; i < hwsq->len / 4; i++) { + NV_ERROR(dev, "0x%06x: 0x%08x\n", 0x1400 + (i * 4), + nv_rd32(dev, 0x001400 + (i * 4))); + } + + return -EIO; + } + + return 0; +} + +int +nv50_pm_clocks_set(struct drm_device *dev, void *data) +{ + struct nv50_pm_state *info = data; + struct bit_entry M; + int ret = -EBUSY; + + /* halt and idle execution engines */ + nv_mask(dev, 0x002504, 0x00000001, 0x00000001); + if (!nv_wait(dev, 0x002504, 0x00000010, 0x00000010)) + goto resume; + if (!nv_wait(dev, 0x00251c, 0x0000003f, 0x0000003f)) + goto resume; + + /* program memory clock, if necessary - must come before engine clock + * reprogramming due to how we construct the hwsq scripts in pre() + */ + if (info->mclk_hwsq.len) { + /* execute some scripts that do ??? from the vbios.. */ + if (!bit_table(dev, 'M', &M) && M.version == 1) { + if (M.length >= 6) + nouveau_bios_init_exec(dev, ROM16(M.data[5])); + if (M.length >= 8) + nouveau_bios_init_exec(dev, ROM16(M.data[7])); + if (M.length >= 10) + nouveau_bios_init_exec(dev, ROM16(M.data[9])); + nouveau_bios_init_exec(dev, info->mscript); + } + + ret = prog_hwsq(dev, &info->mclk_hwsq); + if (ret) + goto resume; + } + + /* program engine clocks */ + ret = prog_hwsq(dev, &info->eclk_hwsq); + +resume: + nv_mask(dev, 0x002504, 0x00000001, 0x00000000); + kfree(info); + return ret; +} + +static int +pwm_info(struct drm_device *dev, int *line, int *ctrl, int *indx) +{ + if (*line == 0x04) { + *ctrl = 0x00e100; + *line = 4; + *indx = 0; + } else + if (*line == 0x09) { + *ctrl = 0x00e100; + *line = 9; + *indx = 1; + } else + if (*line == 0x10) { + *ctrl = 0x00e28c; + *line = 0; + *indx = 0; + } else { + NV_ERROR(dev, "unknown pwm ctrl for gpio %d\n", *line); + return -ENODEV; + } + + return 0; +} + +int +nv50_pm_pwm_get(struct drm_device *dev, int line, u32 *divs, u32 *duty) +{ + int ctrl, id, ret = pwm_info(dev, &line, &ctrl, &id); + if (ret) + return ret; + + if (nv_rd32(dev, ctrl) & (1 << line)) { + *divs = nv_rd32(dev, 0x00e114 + (id * 8)); + *duty = nv_rd32(dev, 0x00e118 + (id * 8)); + return 0; + } + + return -EINVAL; +} + +int +nv50_pm_pwm_set(struct drm_device *dev, int line, u32 divs, u32 duty) +{ + int ctrl, id, ret = pwm_info(dev, &line, &ctrl, &id); + if (ret) + return ret; + + nv_mask(dev, ctrl, 0x00010001 << line, 0x00000001 << line); + nv_wr32(dev, 0x00e114 + (id * 8), divs); + nv_wr32(dev, 0x00e118 + (id * 8), duty | 0x80000000); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv50_sor.c b/drivers/gpu/drm/nouveau/nv50_sor.c new file mode 100644 index 00000000..27464021 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_sor.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2008 Maarten Maathuis. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "drmP.h" +#include "drm_crtc_helper.h" + +#define NOUVEAU_DMA_DEBUG (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO) +#include "nouveau_reg.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_encoder.h" +#include "nouveau_connector.h" +#include "nouveau_crtc.h" +#include "nv50_display.h" + +static u32 +nv50_sor_dp_lane_map(struct drm_device *dev, struct dcb_entry *dcb, u8 lane) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */ + static const u8 nv50[] = { 16, 8, 0, 24 }; + if (dev_priv->chipset == 0xaf) + return nvaf[lane]; + return nv50[lane]; +} + +static void +nv50_sor_dp_train_set(struct drm_device *dev, struct dcb_entry *dcb, u8 pattern) +{ + u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); + nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x0f000000, pattern << 24); +} + +static void +nv50_sor_dp_train_adj(struct drm_device *dev, struct dcb_entry *dcb, + u8 lane, u8 swing, u8 preem) +{ + u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); + u32 shift = nv50_sor_dp_lane_map(dev, dcb, lane); + u32 mask = 0x000000ff << shift; + u8 *table, *entry, *config; + + table = nouveau_dp_bios_data(dev, dcb, &entry); + if (!table || (table[0] != 0x20 && table[0] != 0x21)) { + NV_ERROR(dev, "PDISP: unsupported DP table for chipset\n"); + return; + } + + config = entry + table[4]; + while (config[0] != swing || config[1] != preem) { + config += table[5]; + if (config >= entry + table[4] + entry[4] * table[5]) + return; + } + + nv_mask(dev, NV50_SOR_DP_UNK118(or, link), mask, config[2] << shift); + nv_mask(dev, NV50_SOR_DP_UNK120(or, link), mask, config[3] << shift); + nv_mask(dev, NV50_SOR_DP_UNK130(or, link), 0x0000ff00, config[4] << 8); +} + +static void +nv50_sor_dp_link_set(struct drm_device *dev, struct dcb_entry *dcb, int crtc, + int link_nr, u32 link_bw, bool enhframe) +{ + u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); + u32 dpctrl = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)) & ~0x001f4000; + u32 clksor = nv_rd32(dev, 0x614300 + (or * 0x800)) & ~0x000c0000; + u8 *table, *entry, mask; + int i; + + table = nouveau_dp_bios_data(dev, dcb, &entry); + if (!table || (table[0] != 0x20 && table[0] != 0x21)) { + NV_ERROR(dev, "PDISP: unsupported DP table for chipset\n"); + return; + } + + entry = ROMPTR(dev, entry[10]); + if (entry) { + while (link_bw < ROM16(entry[0]) * 10) + entry += 4; + + nouveau_bios_run_init_table(dev, ROM16(entry[2]), dcb, crtc); + } + + dpctrl |= ((1 << link_nr) - 1) << 16; + if (enhframe) + dpctrl |= 0x00004000; + + if (link_bw > 162000) + clksor |= 0x00040000; + + nv_wr32(dev, 0x614300 + (or * 0x800), clksor); + nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), dpctrl); + + mask = 0; + for (i = 0; i < link_nr; i++) + mask |= 1 << (nv50_sor_dp_lane_map(dev, dcb, i) >> 3); + nv_mask(dev, NV50_SOR_DP_UNK130(or, link), 0x0000000f, mask); +} + +static void +nv50_sor_dp_link_get(struct drm_device *dev, u32 or, u32 link, u32 *nr, u32 *bw) +{ + u32 dpctrl = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)) & 0x000f0000; + u32 clksor = nv_rd32(dev, 0x614300 + (or * 0x800)); + if (clksor & 0x000c0000) + *bw = 270000; + else + *bw = 162000; + + if (dpctrl > 0x00030000) *nr = 4; + else if (dpctrl > 0x00010000) *nr = 2; + else *nr = 1; +} + +void +nv50_sor_dp_calc_tu(struct drm_device *dev, int or, int link, u32 clk, u32 bpp) +{ + const u32 symbol = 100000; + int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0; + int TU, VTUi, VTUf, VTUa; + u64 link_data_rate, link_ratio, unk; + u32 best_diff = 64 * symbol; + u32 link_nr, link_bw, r; + + /* calculate packed data rate for each lane */ + nv50_sor_dp_link_get(dev, or, link, &link_nr, &link_bw); + link_data_rate = (clk * bpp / 8) / link_nr; + + /* calculate ratio of packed data rate to link symbol rate */ + link_ratio = link_data_rate * symbol; + r = do_div(link_ratio, link_bw); + + for (TU = 64; TU >= 32; TU--) { + /* calculate average number of valid symbols in each TU */ + u32 tu_valid = link_ratio * TU; + u32 calc, diff; + + /* find a hw representation for the fraction.. */ + VTUi = tu_valid / symbol; + calc = VTUi * symbol; + diff = tu_valid - calc; + if (diff) { + if (diff >= (symbol / 2)) { + VTUf = symbol / (symbol - diff); + if (symbol - (VTUf * diff)) + VTUf++; + + if (VTUf <= 15) { + VTUa = 1; + calc += symbol - (symbol / VTUf); + } else { + VTUa = 0; + VTUf = 1; + calc += symbol; + } + } else { + VTUa = 0; + VTUf = min((int)(symbol / diff), 15); + calc += symbol / VTUf; + } + + diff = calc - tu_valid; + } else { + /* no remainder, but the hw doesn't like the fractional + * part to be zero. decrement the integer part and + * have the fraction add a whole symbol back + */ + VTUa = 0; + VTUf = 1; + VTUi--; + } + + if (diff < best_diff) { + best_diff = diff; + bestTU = TU; + bestVTUa = VTUa; + bestVTUf = VTUf; + bestVTUi = VTUi; + if (diff == 0) + break; + } + } + + if (!bestTU) { + NV_ERROR(dev, "DP: unable to find suitable config\n"); + return; + } + + /* XXX close to vbios numbers, but not right */ + unk = (symbol - link_ratio) * bestTU; + unk *= link_ratio; + r = do_div(unk, symbol); + r = do_div(unk, symbol); + unk += 6; + + nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x000001fc, bestTU << 2); + nv_mask(dev, NV50_SOR_DP_SCFG(or, link), 0x010f7f3f, bestVTUa << 24 | + bestVTUf << 16 | + bestVTUi << 8 | + unk); +} +static void +nv50_sor_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_channel *evo = nv50_display(dev)->master; + int ret; + + if (!nv_encoder->crtc) + return; + nv50_crtc_blank(nouveau_crtc(nv_encoder->crtc), true); + + NV_DEBUG_KMS(dev, "Disconnecting SOR %d\n", nv_encoder->or); + + ret = RING_SPACE(evo, 4); + if (ret) { + NV_ERROR(dev, "no space while disconnecting SOR\n"); + return; + } + BEGIN_RING(evo, 0, NV50_EVO_SOR(nv_encoder->or, MODE_CTRL), 1); + OUT_RING (evo, 0); + BEGIN_RING(evo, 0, NV50_EVO_UPDATE, 1); + OUT_RING (evo, 0); + + nouveau_hdmi_mode_set(encoder, NULL); + + nv_encoder->crtc = NULL; + nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; +} + +static void +nv50_sor_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_encoder *enc; + uint32_t val; + int or = nv_encoder->or; + + NV_DEBUG_KMS(dev, "or %d type %d mode %d\n", or, nv_encoder->dcb->type, mode); + + nv_encoder->last_dpms = mode; + list_for_each_entry(enc, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nvenc = nouveau_encoder(enc); + + if (nvenc == nv_encoder || + (nvenc->dcb->type != OUTPUT_TMDS && + nvenc->dcb->type != OUTPUT_LVDS && + nvenc->dcb->type != OUTPUT_DP) || + nvenc->dcb->or != nv_encoder->dcb->or) + continue; + + if (nvenc->last_dpms == DRM_MODE_DPMS_ON) + return; + } + + /* wait for it to be done */ + if (!nv_wait(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or), + NV50_PDISPLAY_SOR_DPMS_CTRL_PENDING, 0)) { + NV_ERROR(dev, "timeout: SOR_DPMS_CTRL_PENDING(%d) == 0\n", or); + NV_ERROR(dev, "SOR_DPMS_CTRL(%d) = 0x%08x\n", or, + nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or))); + } + + val = nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or)); + + if (mode == DRM_MODE_DPMS_ON) + val |= NV50_PDISPLAY_SOR_DPMS_CTRL_ON; + else + val &= ~NV50_PDISPLAY_SOR_DPMS_CTRL_ON; + + nv_wr32(dev, NV50_PDISPLAY_SOR_DPMS_CTRL(or), val | + NV50_PDISPLAY_SOR_DPMS_CTRL_PENDING); + if (!nv_wait(dev, NV50_PDISPLAY_SOR_DPMS_STATE(or), + NV50_PDISPLAY_SOR_DPMS_STATE_WAIT, 0)) { + NV_ERROR(dev, "timeout: SOR_DPMS_STATE_WAIT(%d) == 0\n", or); + NV_ERROR(dev, "SOR_DPMS_STATE(%d) = 0x%08x\n", or, + nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_STATE(or))); + } + + if (nv_encoder->dcb->type == OUTPUT_DP) { + struct dp_train_func func = { + .link_set = nv50_sor_dp_link_set, + .train_set = nv50_sor_dp_train_set, + .train_adj = nv50_sor_dp_train_adj + }; + + nouveau_dp_dpms(encoder, mode, nv_encoder->dp.datarate, &func); + } +} + +static void +nv50_sor_save(struct drm_encoder *encoder) +{ + NV_ERROR(encoder->dev, "!!\n"); +} + +static void +nv50_sor_restore(struct drm_encoder *encoder) +{ + NV_ERROR(encoder->dev, "!!\n"); +} + +static bool +nv50_sor_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *connector; + + NV_DEBUG_KMS(encoder->dev, "or %d\n", nv_encoder->or); + + connector = nouveau_encoder_connector_get(nv_encoder); + if (!connector) { + NV_ERROR(encoder->dev, "Encoder has no connector\n"); + return false; + } + + if (connector->scaling_mode != DRM_MODE_SCALE_NONE && + connector->native_mode) + drm_mode_copy(adjusted_mode, connector->native_mode); + + return true; +} + +static void +nv50_sor_prepare(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + nv50_sor_disconnect(encoder); + if (nv_encoder->dcb->type == OUTPUT_DP) { + /* avoid race between link training and supervisor intr */ + nv50_display_sync(encoder->dev); + } +} + +static void +nv50_sor_commit(struct drm_encoder *encoder) +{ +} + +static void +nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode, + struct drm_display_mode *mode) +{ + struct nouveau_channel *evo = nv50_display(encoder->dev)->master; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct nouveau_crtc *crtc = nouveau_crtc(encoder->crtc); + struct nouveau_connector *nv_connector; + uint32_t mode_ctl = 0; + int ret; + + NV_DEBUG_KMS(dev, "or %d type %d -> crtc %d\n", + nv_encoder->or, nv_encoder->dcb->type, crtc->index); + nv_encoder->crtc = encoder->crtc; + + switch (nv_encoder->dcb->type) { + case OUTPUT_TMDS: + if (nv_encoder->dcb->sorconf.link & 1) { + if (mode->clock < 165000) + mode_ctl = 0x0100; + else + mode_ctl = 0x0500; + } else + mode_ctl = 0x0200; + + nouveau_hdmi_mode_set(encoder, mode); + break; + case OUTPUT_DP: + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (nv_connector && nv_connector->base.display_info.bpc == 6) { + nv_encoder->dp.datarate = mode->clock * 18 / 8; + mode_ctl |= 0x00020000; + } else { + nv_encoder->dp.datarate = mode->clock * 24 / 8; + mode_ctl |= 0x00050000; + } + + if (nv_encoder->dcb->sorconf.link & 1) + mode_ctl |= 0x00000800; + else + mode_ctl |= 0x00000900; + break; + default: + break; + } + + if (crtc->index == 1) + mode_ctl |= NV50_EVO_SOR_MODE_CTRL_CRTC1; + else + mode_ctl |= NV50_EVO_SOR_MODE_CTRL_CRTC0; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + mode_ctl |= NV50_EVO_SOR_MODE_CTRL_NHSYNC; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + mode_ctl |= NV50_EVO_SOR_MODE_CTRL_NVSYNC; + + nv50_sor_dpms(encoder, DRM_MODE_DPMS_ON); + + ret = RING_SPACE(evo, 2); + if (ret) { + NV_ERROR(dev, "no space while connecting SOR\n"); + nv_encoder->crtc = NULL; + return; + } + BEGIN_RING(evo, 0, NV50_EVO_SOR(nv_encoder->or, MODE_CTRL), 1); + OUT_RING(evo, mode_ctl); +} + +static struct drm_crtc * +nv50_sor_crtc_get(struct drm_encoder *encoder) +{ + return nouveau_encoder(encoder)->crtc; +} + +static const struct drm_encoder_helper_funcs nv50_sor_helper_funcs = { + .dpms = nv50_sor_dpms, + .save = nv50_sor_save, + .restore = nv50_sor_restore, + .mode_fixup = nv50_sor_mode_fixup, + .prepare = nv50_sor_prepare, + .commit = nv50_sor_commit, + .mode_set = nv50_sor_mode_set, + .get_crtc = nv50_sor_crtc_get, + .detect = NULL, + .disable = nv50_sor_disconnect +}; + +static void +nv50_sor_destroy(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + + if (!encoder) + return; + + NV_DEBUG_KMS(encoder->dev, "\n"); + + drm_encoder_cleanup(encoder); + + kfree(nv_encoder); +} + +static const struct drm_encoder_funcs nv50_sor_encoder_funcs = { + .destroy = nv50_sor_destroy, +}; + +int +nv50_sor_create(struct drm_connector *connector, struct dcb_entry *entry) +{ + struct nouveau_encoder *nv_encoder = NULL; + struct drm_device *dev = connector->dev; + struct drm_encoder *encoder; + int type; + + NV_DEBUG_KMS(dev, "\n"); + + switch (entry->type) { + case OUTPUT_TMDS: + case OUTPUT_DP: + type = DRM_MODE_ENCODER_TMDS; + break; + case OUTPUT_LVDS: + type = DRM_MODE_ENCODER_LVDS; + break; + default: + return -EINVAL; + } + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + encoder = to_drm_encoder(nv_encoder); + + nv_encoder->dcb = entry; + nv_encoder->or = ffs(entry->or) - 1; + nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; + + drm_encoder_init(dev, encoder, &nv50_sor_encoder_funcs, type); + drm_encoder_helper_add(encoder, &nv50_sor_helper_funcs); + + encoder->possible_crtcs = entry->heads; + encoder->possible_clones = 0; + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv50_vm.c b/drivers/gpu/drm/nouveau/nv50_vm.c new file mode 100644 index 00000000..44fbac9c --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_vm.c @@ -0,0 +1,179 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_vm.h" + +void +nv50_vm_map_pgt(struct nouveau_gpuobj *pgd, u32 pde, + struct nouveau_gpuobj *pgt[2]) +{ + u64 phys = 0xdeadcafe00000000ULL; + u32 coverage = 0; + + if (pgt[0]) { + phys = 0x00000003 | pgt[0]->vinst; /* present, 4KiB pages */ + coverage = (pgt[0]->size >> 3) << 12; + } else + if (pgt[1]) { + phys = 0x00000001 | pgt[1]->vinst; /* present */ + coverage = (pgt[1]->size >> 3) << 16; + } + + if (phys & 1) { + if (coverage <= 32 * 1024 * 1024) + phys |= 0x60; + else if (coverage <= 64 * 1024 * 1024) + phys |= 0x40; + else if (coverage <= 128 * 1024 * 1024) + phys |= 0x20; + } + + nv_wo32(pgd, (pde * 8) + 0, lower_32_bits(phys)); + nv_wo32(pgd, (pde * 8) + 4, upper_32_bits(phys)); +} + +static inline u64 +vm_addr(struct nouveau_vma *vma, u64 phys, u32 memtype, u32 target) +{ + phys |= 1; /* present */ + phys |= (u64)memtype << 40; + phys |= target << 4; + if (vma->access & NV_MEM_ACCESS_SYS) + phys |= (1 << 6); + if (!(vma->access & NV_MEM_ACCESS_WO)) + phys |= (1 << 3); + return phys; +} + +void +nv50_vm_map(struct nouveau_vma *vma, struct nouveau_gpuobj *pgt, + struct nouveau_mem *mem, u32 pte, u32 cnt, u64 phys, u64 delta) +{ + struct drm_nouveau_private *dev_priv = vma->vm->dev->dev_private; + u32 comp = (mem->memtype & 0x180) >> 7; + u32 block, target; + int i; + + /* IGPs don't have real VRAM, re-target to stolen system memory */ + target = 0; + if (dev_priv->vram_sys_base) { + phys += dev_priv->vram_sys_base; + target = 3; + } + + phys = vm_addr(vma, phys, mem->memtype, target); + pte <<= 3; + cnt <<= 3; + + while (cnt) { + u32 offset_h = upper_32_bits(phys); + u32 offset_l = lower_32_bits(phys); + + for (i = 7; i >= 0; i--) { + block = 1 << (i + 3); + if (cnt >= block && !(pte & (block - 1))) + break; + } + offset_l |= (i << 7); + + phys += block << (vma->node->type - 3); + cnt -= block; + if (comp) { + u32 tag = mem->tag->start + ((delta >> 16) * comp); + offset_h |= (tag << 17); + delta += block << (vma->node->type - 3); + } + + while (block) { + nv_wo32(pgt, pte + 0, offset_l); + nv_wo32(pgt, pte + 4, offset_h); + pte += 8; + block -= 8; + } + } +} + +void +nv50_vm_map_sg(struct nouveau_vma *vma, struct nouveau_gpuobj *pgt, + struct nouveau_mem *mem, u32 pte, u32 cnt, dma_addr_t *list) +{ + u32 target = (vma->access & NV_MEM_ACCESS_NOSNOOP) ? 3 : 2; + pte <<= 3; + while (cnt--) { + u64 phys = vm_addr(vma, (u64)*list++, mem->memtype, target); + nv_wo32(pgt, pte + 0, lower_32_bits(phys)); + nv_wo32(pgt, pte + 4, upper_32_bits(phys)); + pte += 8; + } +} + +void +nv50_vm_unmap(struct nouveau_gpuobj *pgt, u32 pte, u32 cnt) +{ + pte <<= 3; + while (cnt--) { + nv_wo32(pgt, pte + 0, 0x00000000); + nv_wo32(pgt, pte + 4, 0x00000000); + pte += 8; + } +} + +void +nv50_vm_flush(struct nouveau_vm *vm) +{ + struct drm_nouveau_private *dev_priv = vm->dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + int i; + + pinstmem->flush(vm->dev); + + /* BAR */ + if (vm == dev_priv->bar1_vm || vm == dev_priv->bar3_vm) { + nv50_vm_flush_engine(vm->dev, 6); + return; + } + + pfifo->tlb_flush(vm->dev); + for (i = 0; i < NVOBJ_ENGINE_NR; i++) { + if (atomic_read(&vm->engref[i])) + dev_priv->eng[i]->tlb_flush(vm->dev, i); + } +} + +void +nv50_vm_flush_engine(struct drm_device *dev, int engine) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + unsigned long flags; + + spin_lock_irqsave(&dev_priv->vm_lock, flags); + nv_wr32(dev, 0x100c80, (engine << 16) | 1); + if (!nv_wait(dev, 0x100c80, 0x00000001, 0x00000000)) + NV_ERROR(dev, "vm flush timeout: engine %d\n", engine); + spin_unlock_irqrestore(&dev_priv->vm_lock, flags); +} diff --git a/drivers/gpu/drm/nouveau/nv50_vram.c b/drivers/gpu/drm/nouveau/nv50_vram.c new file mode 100644 index 00000000..9ed9ae39 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv50_vram.c @@ -0,0 +1,237 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_mm.h" + +static int types[0x80] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 1, 0, 2, 0, 1, 0, 2, 0, 1, 1, 2, 2, 1, 1, 0, 0 +}; + +bool +nv50_vram_flags_valid(struct drm_device *dev, u32 tile_flags) +{ + int type = (tile_flags & NOUVEAU_GEM_TILE_LAYOUT_MASK) >> 8; + + if (likely(type < ARRAY_SIZE(types) && types[type])) + return true; + return false; +} + +void +nv50_vram_del(struct drm_device *dev, struct nouveau_mem **pmem) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_mm *mm = &dev_priv->engine.vram.mm; + struct nouveau_mm_node *this; + struct nouveau_mem *mem; + + mem = *pmem; + *pmem = NULL; + if (unlikely(mem == NULL)) + return; + + mutex_lock(&mm->mutex); + while (!list_empty(&mem->regions)) { + this = list_first_entry(&mem->regions, struct nouveau_mm_node, rl_entry); + + list_del(&this->rl_entry); + nouveau_mm_put(mm, this); + } + + if (mem->tag) { + drm_mm_put_block(mem->tag); + mem->tag = NULL; + } + mutex_unlock(&mm->mutex); + + kfree(mem); +} + +int +nv50_vram_new(struct drm_device *dev, u64 size, u32 align, u32 size_nc, + u32 memtype, struct nouveau_mem **pmem) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_mm *mm = &dev_priv->engine.vram.mm; + struct nouveau_mm_node *r; + struct nouveau_mem *mem; + int comp = (memtype & 0x300) >> 8; + int type = (memtype & 0x07f); + int ret; + + if (!types[type]) + return -EINVAL; + size >>= 12; + align >>= 12; + size_nc >>= 12; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) + return -ENOMEM; + + mutex_lock(&mm->mutex); + if (comp) { + if (align == 16) { + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + int n = (size >> 4) * comp; + + mem->tag = drm_mm_search_free(&pfb->tag_heap, n, 0, 0); + if (mem->tag) + mem->tag = drm_mm_get_block(mem->tag, n, 0); + } + + if (unlikely(!mem->tag)) + comp = 0; + } + + INIT_LIST_HEAD(&mem->regions); + mem->dev = dev_priv->dev; + mem->memtype = (comp << 7) | type; + mem->size = size; + + do { + ret = nouveau_mm_get(mm, types[type], size, size_nc, align, &r); + if (ret) { + mutex_unlock(&mm->mutex); + nv50_vram_del(dev, &mem); + return ret; + } + + list_add_tail(&r->rl_entry, &mem->regions); + size -= r->length; + } while (size); + mutex_unlock(&mm->mutex); + + r = list_first_entry(&mem->regions, struct nouveau_mm_node, rl_entry); + mem->offset = (u64)r->offset << 12; + *pmem = mem; + return 0; +} + +static u32 +nv50_vram_rblock(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i, parts, colbits, rowbitsa, rowbitsb, banks; + u64 rowsize, predicted; + u32 r0, r4, rt, ru, rblock_size; + + r0 = nv_rd32(dev, 0x100200); + r4 = nv_rd32(dev, 0x100204); + rt = nv_rd32(dev, 0x100250); + ru = nv_rd32(dev, 0x001540); + NV_DEBUG(dev, "memcfg 0x%08x 0x%08x 0x%08x 0x%08x\n", r0, r4, rt, ru); + + for (i = 0, parts = 0; i < 8; i++) { + if (ru & (0x00010000 << i)) + parts++; + } + + colbits = (r4 & 0x0000f000) >> 12; + rowbitsa = ((r4 & 0x000f0000) >> 16) + 8; + rowbitsb = ((r4 & 0x00f00000) >> 20) + 8; + banks = 1 << (((r4 & 0x03000000) >> 24) + 2); + + rowsize = parts * banks * (1 << colbits) * 8; + predicted = rowsize << rowbitsa; + if (r0 & 0x00000004) + predicted += rowsize << rowbitsb; + + if (predicted != dev_priv->vram_size) { + NV_WARN(dev, "memory controller reports %dMiB VRAM\n", + (u32)(dev_priv->vram_size >> 20)); + NV_WARN(dev, "we calculated %dMiB VRAM\n", + (u32)(predicted >> 20)); + } + + rblock_size = rowsize; + if (rt & 1) + rblock_size *= 3; + + NV_DEBUG(dev, "rblock %d bytes\n", rblock_size); + return rblock_size; +} + +int +nv50_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_vram_engine *vram = &dev_priv->engine.vram; + const u32 rsvd_head = ( 256 * 1024) >> 12; /* vga memory */ + const u32 rsvd_tail = (1024 * 1024) >> 12; /* vbios etc */ + u32 pfb714 = nv_rd32(dev, 0x100714); + u32 rblock, length; + + switch (pfb714 & 0x00000007) { + case 0: dev_priv->vram_type = NV_MEM_TYPE_DDR1; break; + case 1: + if (nouveau_mem_vbios_type(dev) == NV_MEM_TYPE_DDR3) + dev_priv->vram_type = NV_MEM_TYPE_DDR3; + else + dev_priv->vram_type = NV_MEM_TYPE_DDR2; + break; + case 2: dev_priv->vram_type = NV_MEM_TYPE_GDDR3; break; + case 3: dev_priv->vram_type = NV_MEM_TYPE_GDDR4; break; + case 4: dev_priv->vram_type = NV_MEM_TYPE_GDDR5; break; + default: + break; + } + + dev_priv->vram_rank_B = !!(nv_rd32(dev, 0x100200) & 0x4); + dev_priv->vram_size = nv_rd32(dev, 0x10020c); + dev_priv->vram_size |= (dev_priv->vram_size & 0xff) << 32; + dev_priv->vram_size &= 0xffffffff00ULL; + + /* IGPs, no funky reordering happens here, they don't have VRAM */ + if (dev_priv->chipset == 0xaa || + dev_priv->chipset == 0xac || + dev_priv->chipset == 0xaf) { + dev_priv->vram_sys_base = (u64)nv_rd32(dev, 0x100e10) << 12; + rblock = 4096 >> 12; + } else { + rblock = nv50_vram_rblock(dev) >> 12; + } + + length = (dev_priv->vram_size >> 12) - rsvd_head - rsvd_tail; + + return nouveau_mm_init(&vram->mm, rsvd_head, length, rblock); +} + +void +nv50_vram_fini(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_vram_engine *vram = &dev_priv->engine.vram; + + nouveau_mm_fini(&vram->mm); +} diff --git a/drivers/gpu/drm/nouveau/nv84_bsp.c b/drivers/gpu/drm/nouveau/nv84_bsp.c new file mode 100644 index 00000000..74875739 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv84_bsp.c @@ -0,0 +1,83 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" +#include "nouveau_vm.h" +#include "nouveau_ramht.h" + +/*XXX: This stub is currently used on NV98+ also, as soon as this becomes + * more than just an enable/disable stub this needs to be split out to + * nv98_bsp.c... + */ + +struct nv84_bsp_engine { + struct nouveau_exec_engine base; +}; + +static int +nv84_bsp_fini(struct drm_device *dev, int engine, bool suspend) +{ + if (!(nv_rd32(dev, 0x000200) & 0x00008000)) + return 0; + + nv_mask(dev, 0x000200, 0x00008000, 0x00000000); + return 0; +} + +static int +nv84_bsp_init(struct drm_device *dev, int engine) +{ + nv_mask(dev, 0x000200, 0x00008000, 0x00000000); + nv_mask(dev, 0x000200, 0x00008000, 0x00008000); + return 0; +} + +static void +nv84_bsp_destroy(struct drm_device *dev, int engine) +{ + struct nv84_bsp_engine *pbsp = nv_engine(dev, engine); + + NVOBJ_ENGINE_DEL(dev, BSP); + + kfree(pbsp); +} + +int +nv84_bsp_create(struct drm_device *dev) +{ + struct nv84_bsp_engine *pbsp; + + pbsp = kzalloc(sizeof(*pbsp), GFP_KERNEL); + if (!pbsp) + return -ENOMEM; + + pbsp->base.destroy = nv84_bsp_destroy; + pbsp->base.init = nv84_bsp_init; + pbsp->base.fini = nv84_bsp_fini; + + NVOBJ_ENGINE_ADD(dev, BSP, &pbsp->base); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv84_crypt.c b/drivers/gpu/drm/nouveau/nv84_crypt.c new file mode 100644 index 00000000..edece9c6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv84_crypt.c @@ -0,0 +1,193 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" +#include "nouveau_vm.h" +#include "nouveau_ramht.h" + +struct nv84_crypt_engine { + struct nouveau_exec_engine base; +}; + +static int +nv84_crypt_context_new(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramin = chan->ramin; + struct nouveau_gpuobj *ctx; + int ret; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + ret = nouveau_gpuobj_new(dev, chan, 256, 0, NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &ctx); + if (ret) + return ret; + + nv_wo32(ramin, 0xa0, 0x00190000); + nv_wo32(ramin, 0xa4, ctx->vinst + ctx->size - 1); + nv_wo32(ramin, 0xa8, ctx->vinst); + nv_wo32(ramin, 0xac, 0); + nv_wo32(ramin, 0xb0, 0); + nv_wo32(ramin, 0xb4, 0); + dev_priv->engine.instmem.flush(dev); + + atomic_inc(&chan->vm->engref[engine]); + chan->engctx[engine] = ctx; + return 0; +} + +static void +nv84_crypt_context_del(struct nouveau_channel *chan, int engine) +{ + struct nouveau_gpuobj *ctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + u32 inst; + + inst = (chan->ramin->vinst >> 12); + inst |= 0x80000000; + + /* mark context as invalid if still on the hardware, not + * doing this causes issues the next time PCRYPT is used, + * unsurprisingly :) + */ + nv_wr32(dev, 0x10200c, 0x00000000); + if (nv_rd32(dev, 0x102188) == inst) + nv_mask(dev, 0x102188, 0x80000000, 0x00000000); + if (nv_rd32(dev, 0x10218c) == inst) + nv_mask(dev, 0x10218c, 0x80000000, 0x00000000); + nv_wr32(dev, 0x10200c, 0x00000010); + + nouveau_gpuobj_ref(NULL, &ctx); + + atomic_dec(&chan->vm->engref[engine]); + chan->engctx[engine] = NULL; +} + +static int +nv84_crypt_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *obj = NULL; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 16, 16, NVOBJ_FLAG_ZERO_FREE, &obj); + if (ret) + return ret; + obj->engine = 5; + obj->class = class; + + nv_wo32(obj, 0x00, class); + dev_priv->engine.instmem.flush(dev); + + ret = nouveau_ramht_insert(chan, handle, obj); + nouveau_gpuobj_ref(NULL, &obj); + return ret; +} + +static void +nv84_crypt_tlb_flush(struct drm_device *dev, int engine) +{ + nv50_vm_flush_engine(dev, 0x0a); +} + +static void +nv84_crypt_isr(struct drm_device *dev) +{ + u32 stat = nv_rd32(dev, 0x102130); + u32 mthd = nv_rd32(dev, 0x102190); + u32 data = nv_rd32(dev, 0x102194); + u32 inst = nv_rd32(dev, 0x102188) & 0x7fffffff; + int show = nouveau_ratelimit(); + + if (show) { + NV_INFO(dev, "PCRYPT_INTR: 0x%08x 0x%08x 0x%08x 0x%08x\n", + stat, mthd, data, inst); + } + + nv_wr32(dev, 0x102130, stat); + nv_wr32(dev, 0x10200c, 0x10); + + nv50_fb_vm_trap(dev, show); +} + +static int +nv84_crypt_fini(struct drm_device *dev, int engine, bool suspend) +{ + nv_wr32(dev, 0x102140, 0x00000000); + return 0; +} + +static int +nv84_crypt_init(struct drm_device *dev, int engine) +{ + nv_mask(dev, 0x000200, 0x00004000, 0x00000000); + nv_mask(dev, 0x000200, 0x00004000, 0x00004000); + + nv_wr32(dev, 0x102130, 0xffffffff); + nv_wr32(dev, 0x102140, 0xffffffbf); + + nv_wr32(dev, 0x10200c, 0x00000010); + return 0; +} + +static void +nv84_crypt_destroy(struct drm_device *dev, int engine) +{ + struct nv84_crypt_engine *pcrypt = nv_engine(dev, engine); + + NVOBJ_ENGINE_DEL(dev, CRYPT); + + nouveau_irq_unregister(dev, 14); + kfree(pcrypt); +} + +int +nv84_crypt_create(struct drm_device *dev) +{ + struct nv84_crypt_engine *pcrypt; + + pcrypt = kzalloc(sizeof(*pcrypt), GFP_KERNEL); + if (!pcrypt) + return -ENOMEM; + + pcrypt->base.destroy = nv84_crypt_destroy; + pcrypt->base.init = nv84_crypt_init; + pcrypt->base.fini = nv84_crypt_fini; + pcrypt->base.context_new = nv84_crypt_context_new; + pcrypt->base.context_del = nv84_crypt_context_del; + pcrypt->base.object_new = nv84_crypt_object_new; + pcrypt->base.tlb_flush = nv84_crypt_tlb_flush; + + nouveau_irq_register(dev, 14, nv84_crypt_isr); + + NVOBJ_ENGINE_ADD(dev, CRYPT, &pcrypt->base); + NVOBJ_CLASS (dev, 0x74c1, CRYPT); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv84_vp.c b/drivers/gpu/drm/nouveau/nv84_vp.c new file mode 100644 index 00000000..6570d300 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv84_vp.c @@ -0,0 +1,83 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" +#include "nouveau_vm.h" +#include "nouveau_ramht.h" + +/*XXX: This stub is currently used on NV98+ also, as soon as this becomes + * more than just an enable/disable stub this needs to be split out to + * nv98_vp.c... + */ + +struct nv84_vp_engine { + struct nouveau_exec_engine base; +}; + +static int +nv84_vp_fini(struct drm_device *dev, int engine, bool suspend) +{ + if (!(nv_rd32(dev, 0x000200) & 0x00020000)) + return 0; + + nv_mask(dev, 0x000200, 0x00020000, 0x00000000); + return 0; +} + +static int +nv84_vp_init(struct drm_device *dev, int engine) +{ + nv_mask(dev, 0x000200, 0x00020000, 0x00000000); + nv_mask(dev, 0x000200, 0x00020000, 0x00020000); + return 0; +} + +static void +nv84_vp_destroy(struct drm_device *dev, int engine) +{ + struct nv84_vp_engine *pvp = nv_engine(dev, engine); + + NVOBJ_ENGINE_DEL(dev, VP); + + kfree(pvp); +} + +int +nv84_vp_create(struct drm_device *dev) +{ + struct nv84_vp_engine *pvp; + + pvp = kzalloc(sizeof(*pvp), GFP_KERNEL); + if (!pvp) + return -ENOMEM; + + pvp->base.destroy = nv84_vp_destroy; + pvp->base.init = nv84_vp_init; + pvp->base.fini = nv84_vp_fini; + + NVOBJ_ENGINE_ADD(dev, VP, &pvp->base); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv98_crypt.c b/drivers/gpu/drm/nouveau/nv98_crypt.c new file mode 100644 index 00000000..db94ff0a --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv98_crypt.c @@ -0,0 +1,78 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" +#include "nouveau_vm.h" +#include "nouveau_ramht.h" + +struct nv98_crypt_engine { + struct nouveau_exec_engine base; +}; + +static int +nv98_crypt_fini(struct drm_device *dev, int engine, bool suspend) +{ + if (!(nv_rd32(dev, 0x000200) & 0x00004000)) + return 0; + + nv_mask(dev, 0x000200, 0x00004000, 0x00000000); + return 0; +} + +static int +nv98_crypt_init(struct drm_device *dev, int engine) +{ + nv_mask(dev, 0x000200, 0x00004000, 0x00000000); + nv_mask(dev, 0x000200, 0x00004000, 0x00004000); + return 0; +} + +static void +nv98_crypt_destroy(struct drm_device *dev, int engine) +{ + struct nv98_crypt_engine *pcrypt = nv_engine(dev, engine); + + NVOBJ_ENGINE_DEL(dev, CRYPT); + + kfree(pcrypt); +} + +int +nv98_crypt_create(struct drm_device *dev) +{ + struct nv98_crypt_engine *pcrypt; + + pcrypt = kzalloc(sizeof(*pcrypt), GFP_KERNEL); + if (!pcrypt) + return -ENOMEM; + + pcrypt->base.destroy = nv98_crypt_destroy; + pcrypt->base.init = nv98_crypt_init; + pcrypt->base.fini = nv98_crypt_fini; + + NVOBJ_ENGINE_ADD(dev, CRYPT, &pcrypt->base); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nv98_ppp.c b/drivers/gpu/drm/nouveau/nv98_ppp.c new file mode 100644 index 00000000..a987dd6e --- /dev/null +++ b/drivers/gpu/drm/nouveau/nv98_ppp.c @@ -0,0 +1,78 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" +#include "nouveau_vm.h" +#include "nouveau_ramht.h" + +struct nv98_ppp_engine { + struct nouveau_exec_engine base; +}; + +static int +nv98_ppp_fini(struct drm_device *dev, int engine, bool suspend) +{ + if (!(nv_rd32(dev, 0x000200) & 0x00000002)) + return 0; + + nv_mask(dev, 0x000200, 0x00000002, 0x00000000); + return 0; +} + +static int +nv98_ppp_init(struct drm_device *dev, int engine) +{ + nv_mask(dev, 0x000200, 0x00000002, 0x00000000); + nv_mask(dev, 0x000200, 0x00000002, 0x00000002); + return 0; +} + +static void +nv98_ppp_destroy(struct drm_device *dev, int engine) +{ + struct nv98_ppp_engine *pppp = nv_engine(dev, engine); + + NVOBJ_ENGINE_DEL(dev, PPP); + + kfree(pppp); +} + +int +nv98_ppp_create(struct drm_device *dev) +{ + struct nv98_ppp_engine *pppp; + + pppp = kzalloc(sizeof(*pppp), GFP_KERNEL); + if (!pppp) + return -ENOMEM; + + pppp->base.destroy = nv98_ppp_destroy; + pppp->base.init = nv98_ppp_init; + pppp->base.fini = nv98_ppp_fini; + + NVOBJ_ENGINE_ADD(dev, PPP, &pppp->base); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nva3_copy.c b/drivers/gpu/drm/nouveau/nva3_copy.c new file mode 100644 index 00000000..8f356d58 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nva3_copy.c @@ -0,0 +1,226 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <linux/firmware.h> +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" +#include "nouveau_vm.h" +#include "nouveau_ramht.h" +#include "nva3_copy.fuc.h" + +struct nva3_copy_engine { + struct nouveau_exec_engine base; +}; + +static int +nva3_copy_context_new(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramin = chan->ramin; + struct nouveau_gpuobj *ctx = NULL; + int ret; + + NV_DEBUG(dev, "ch%d\n", chan->id); + + ret = nouveau_gpuobj_new(dev, chan, 256, 0, NVOBJ_FLAG_ZERO_ALLOC | + NVOBJ_FLAG_ZERO_FREE, &ctx); + if (ret) + return ret; + + nv_wo32(ramin, 0xc0, 0x00190000); + nv_wo32(ramin, 0xc4, ctx->vinst + ctx->size - 1); + nv_wo32(ramin, 0xc8, ctx->vinst); + nv_wo32(ramin, 0xcc, 0x00000000); + nv_wo32(ramin, 0xd0, 0x00000000); + nv_wo32(ramin, 0xd4, 0x00000000); + dev_priv->engine.instmem.flush(dev); + + atomic_inc(&chan->vm->engref[engine]); + chan->engctx[engine] = ctx; + return 0; +} + +static int +nva3_copy_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + struct nouveau_gpuobj *ctx = chan->engctx[engine]; + + /* fuc engine doesn't need an object, our ramht code does.. */ + ctx->engine = 3; + ctx->class = class; + return nouveau_ramht_insert(chan, handle, ctx); +} + +static void +nva3_copy_context_del(struct nouveau_channel *chan, int engine) +{ + struct nouveau_gpuobj *ctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + u32 inst; + + inst = (chan->ramin->vinst >> 12); + inst |= 0x40000000; + + /* disable fifo access */ + nv_wr32(dev, 0x104048, 0x00000000); + /* mark channel as unloaded if it's currently active */ + if (nv_rd32(dev, 0x104050) == inst) + nv_mask(dev, 0x104050, 0x40000000, 0x00000000); + /* mark next channel as invalid if it's about to be loaded */ + if (nv_rd32(dev, 0x104054) == inst) + nv_mask(dev, 0x104054, 0x40000000, 0x00000000); + /* restore fifo access */ + nv_wr32(dev, 0x104048, 0x00000003); + + for (inst = 0xc0; inst <= 0xd4; inst += 4) + nv_wo32(chan->ramin, inst, 0x00000000); + + nouveau_gpuobj_ref(NULL, &ctx); + + atomic_dec(&chan->vm->engref[engine]); + chan->engctx[engine] = ctx; +} + +static void +nva3_copy_tlb_flush(struct drm_device *dev, int engine) +{ + nv50_vm_flush_engine(dev, 0x0d); +} + +static int +nva3_copy_init(struct drm_device *dev, int engine) +{ + int i; + + nv_mask(dev, 0x000200, 0x00002000, 0x00000000); + nv_mask(dev, 0x000200, 0x00002000, 0x00002000); + nv_wr32(dev, 0x104014, 0xffffffff); /* disable all interrupts */ + + /* upload ucode */ + nv_wr32(dev, 0x1041c0, 0x01000000); + for (i = 0; i < sizeof(nva3_pcopy_data) / 4; i++) + nv_wr32(dev, 0x1041c4, nva3_pcopy_data[i]); + + nv_wr32(dev, 0x104180, 0x01000000); + for (i = 0; i < sizeof(nva3_pcopy_code) / 4; i++) { + if ((i & 0x3f) == 0) + nv_wr32(dev, 0x104188, i >> 6); + nv_wr32(dev, 0x104184, nva3_pcopy_code[i]); + } + + /* start it running */ + nv_wr32(dev, 0x10410c, 0x00000000); + nv_wr32(dev, 0x104104, 0x00000000); /* ENTRY */ + nv_wr32(dev, 0x104100, 0x00000002); /* TRIGGER */ + return 0; +} + +static int +nva3_copy_fini(struct drm_device *dev, int engine, bool suspend) +{ + nv_mask(dev, 0x104048, 0x00000003, 0x00000000); + + /* trigger fuc context unload */ + nv_wait(dev, 0x104008, 0x0000000c, 0x00000000); + nv_mask(dev, 0x104054, 0x40000000, 0x00000000); + nv_wr32(dev, 0x104000, 0x00000008); + nv_wait(dev, 0x104008, 0x00000008, 0x00000000); + + nv_wr32(dev, 0x104014, 0xffffffff); + return 0; +} + +static struct nouveau_enum nva3_copy_isr_error_name[] = { + { 0x0001, "ILLEGAL_MTHD" }, + { 0x0002, "INVALID_ENUM" }, + { 0x0003, "INVALID_BITFIELD" }, + {} +}; + +static void +nva3_copy_isr(struct drm_device *dev) +{ + u32 dispatch = nv_rd32(dev, 0x10401c); + u32 stat = nv_rd32(dev, 0x104008) & dispatch & ~(dispatch >> 16); + u32 inst = nv_rd32(dev, 0x104050) & 0x3fffffff; + u32 ssta = nv_rd32(dev, 0x104040) & 0x0000ffff; + u32 addr = nv_rd32(dev, 0x104040) >> 16; + u32 mthd = (addr & 0x07ff) << 2; + u32 subc = (addr & 0x3800) >> 11; + u32 data = nv_rd32(dev, 0x104044); + int chid = nv50_graph_isr_chid(dev, inst); + + if (stat & 0x00000040) { + NV_INFO(dev, "PCOPY: DISPATCH_ERROR ["); + nouveau_enum_print(nva3_copy_isr_error_name, ssta); + printk("] ch %d [0x%08x] subc %d mthd 0x%04x data 0x%08x\n", + chid, inst, subc, mthd, data); + nv_wr32(dev, 0x104004, 0x00000040); + stat &= ~0x00000040; + } + + if (stat) { + NV_INFO(dev, "PCOPY: unhandled intr 0x%08x\n", stat); + nv_wr32(dev, 0x104004, stat); + } + nv50_fb_vm_trap(dev, 1); +} + +static void +nva3_copy_destroy(struct drm_device *dev, int engine) +{ + struct nva3_copy_engine *pcopy = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, 22); + + NVOBJ_ENGINE_DEL(dev, COPY0); + kfree(pcopy); +} + +int +nva3_copy_create(struct drm_device *dev) +{ + struct nva3_copy_engine *pcopy; + + pcopy = kzalloc(sizeof(*pcopy), GFP_KERNEL); + if (!pcopy) + return -ENOMEM; + + pcopy->base.destroy = nva3_copy_destroy; + pcopy->base.init = nva3_copy_init; + pcopy->base.fini = nva3_copy_fini; + pcopy->base.context_new = nva3_copy_context_new; + pcopy->base.context_del = nva3_copy_context_del; + pcopy->base.object_new = nva3_copy_object_new; + pcopy->base.tlb_flush = nva3_copy_tlb_flush; + + nouveau_irq_register(dev, 22, nva3_copy_isr); + + NVOBJ_ENGINE_ADD(dev, COPY0, &pcopy->base); + NVOBJ_CLASS(dev, 0x85b5, COPY0); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nva3_copy.fuc b/drivers/gpu/drm/nouveau/nva3_copy.fuc new file mode 100644 index 00000000..abc36626 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nva3_copy.fuc @@ -0,0 +1,872 @@ +/* fuc microcode for copy engine on nva3- chipsets + * + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +/* To build for nva3:nvc0 + * m4 -DNVA3 nva3_copy.fuc | envyas -a -w -m fuc -V nva3 -o nva3_copy.fuc.h + * + * To build for nvc0- + * m4 -DNVC0 nva3_copy.fuc | envyas -a -w -m fuc -V nva3 -o nvc0_copy.fuc.h + */ + +ifdef(`NVA3', +.section #nva3_pcopy_data +, +.section #nvc0_pcopy_data +) + +ctx_object: .b32 0 +ifdef(`NVA3', +ctx_dma: +ctx_dma_query: .b32 0 +ctx_dma_src: .b32 0 +ctx_dma_dst: .b32 0 +,) +.equ #ctx_dma_count 3 +ctx_query_address_high: .b32 0 +ctx_query_address_low: .b32 0 +ctx_query_counter: .b32 0 +ctx_src_address_high: .b32 0 +ctx_src_address_low: .b32 0 +ctx_src_pitch: .b32 0 +ctx_src_tile_mode: .b32 0 +ctx_src_xsize: .b32 0 +ctx_src_ysize: .b32 0 +ctx_src_zsize: .b32 0 +ctx_src_zoff: .b32 0 +ctx_src_xoff: .b32 0 +ctx_src_yoff: .b32 0 +ctx_src_cpp: .b32 0 +ctx_dst_address_high: .b32 0 +ctx_dst_address_low: .b32 0 +ctx_dst_pitch: .b32 0 +ctx_dst_tile_mode: .b32 0 +ctx_dst_xsize: .b32 0 +ctx_dst_ysize: .b32 0 +ctx_dst_zsize: .b32 0 +ctx_dst_zoff: .b32 0 +ctx_dst_xoff: .b32 0 +ctx_dst_yoff: .b32 0 +ctx_dst_cpp: .b32 0 +ctx_format: .b32 0 +ctx_swz_const0: .b32 0 +ctx_swz_const1: .b32 0 +ctx_xcnt: .b32 0 +ctx_ycnt: .b32 0 +.align 256 + +dispatch_table: +// mthd 0x0000, NAME +.b16 0x000 1 +.b32 #ctx_object ~0xffffffff +// mthd 0x0100, NOP +.b16 0x040 1 +.b32 0x00010000 + #cmd_nop ~0xffffffff +// mthd 0x0140, PM_TRIGGER +.b16 0x050 1 +.b32 0x00010000 + #cmd_pm_trigger ~0xffffffff +ifdef(`NVA3', ` +// mthd 0x0180-0x018c, DMA_ +.b16 0x060 #ctx_dma_count +dispatch_dma: +.b32 0x00010000 + #cmd_dma ~0xffffffff +.b32 0x00010000 + #cmd_dma ~0xffffffff +.b32 0x00010000 + #cmd_dma ~0xffffffff +',) +// mthd 0x0200-0x0218, SRC_TILE +.b16 0x80 7 +.b32 #ctx_src_tile_mode ~0x00000fff +.b32 #ctx_src_xsize ~0x0007ffff +.b32 #ctx_src_ysize ~0x00001fff +.b32 #ctx_src_zsize ~0x000007ff +.b32 #ctx_src_zoff ~0x00000fff +.b32 #ctx_src_xoff ~0x0007ffff +.b32 #ctx_src_yoff ~0x00001fff +// mthd 0x0220-0x0238, DST_TILE +.b16 0x88 7 +.b32 #ctx_dst_tile_mode ~0x00000fff +.b32 #ctx_dst_xsize ~0x0007ffff +.b32 #ctx_dst_ysize ~0x00001fff +.b32 #ctx_dst_zsize ~0x000007ff +.b32 #ctx_dst_zoff ~0x00000fff +.b32 #ctx_dst_xoff ~0x0007ffff +.b32 #ctx_dst_yoff ~0x00001fff +// mthd 0x0300-0x0304, EXEC, WRCACHE_FLUSH +.b16 0xc0 2 +.b32 0x00010000 + #cmd_exec ~0xffffffff +.b32 0x00010000 + #cmd_wrcache_flush ~0xffffffff +// mthd 0x030c-0x0340, various stuff +.b16 0xc3 14 +.b32 #ctx_src_address_high ~0x000000ff +.b32 #ctx_src_address_low ~0xfffffff0 +.b32 #ctx_dst_address_high ~0x000000ff +.b32 #ctx_dst_address_low ~0xfffffff0 +.b32 #ctx_src_pitch ~0x0007ffff +.b32 #ctx_dst_pitch ~0x0007ffff +.b32 #ctx_xcnt ~0x0000ffff +.b32 #ctx_ycnt ~0x00001fff +.b32 #ctx_format ~0x0333ffff +.b32 #ctx_swz_const0 ~0xffffffff +.b32 #ctx_swz_const1 ~0xffffffff +.b32 #ctx_query_address_high ~0x000000ff +.b32 #ctx_query_address_low ~0xffffffff +.b32 #ctx_query_counter ~0xffffffff +.b16 0x800 0 + +ifdef(`NVA3', +.section #nva3_pcopy_code +, +.section #nvc0_pcopy_code +) + +main: + clear b32 $r0 + mov $sp $r0 + + // setup i0 handler and route fifo and ctxswitch to it + mov $r1 #ih + mov $iv0 $r1 + mov $r1 0x400 + movw $r2 0xfff3 + sethi $r2 0 + iowr I[$r1 + 0x300] $r2 + + // enable interrupts + or $r2 0xc + iowr I[$r1] $r2 + bset $flags ie0 + + // enable fifo access and context switching + mov $r1 0x1200 + mov $r2 3 + iowr I[$r1] $r2 + + // sleep forever, waking for interrupts + bset $flags $p0 + spin: + sleep $p0 + bra #spin + +// i0 handler +ih: + iord $r1 I[$r0 + 0x200] + + and $r2 $r1 0x00000008 + bra e #ih_no_chsw + call #chsw + ih_no_chsw: + and $r2 $r1 0x00000004 + bra e #ih_no_cmd + call #dispatch + + ih_no_cmd: + and $r1 $r1 0x0000000c + iowr I[$r0 + 0x100] $r1 + iret + +// $p1 direction (0 = unload, 1 = load) +// $r3 channel +swctx: + mov $r4 0x7700 + mov $xtargets $r4 +ifdef(`NVA3', ` + // target 7 hardcoded to ctx dma object + mov $xdbase $r0 +', ` // NVC0 + // read SCRATCH3 to decide if we are PCOPY0 or PCOPY1 + mov $r4 0x2100 + iord $r4 I[$r4 + 0] + and $r4 1 + shl b32 $r4 4 + add b32 $r4 0x30 + + // channel is in vram + mov $r15 0x61c + shl b32 $r15 6 + mov $r5 0x114 + iowrs I[$r15] $r5 + + // read 16-byte PCOPYn info, containing context pointer, from channel + shl b32 $r5 $r3 4 + add b32 $r5 2 + mov $xdbase $r5 + mov $r5 $sp + // get a chunk of stack space, aligned to 256 byte boundary + sub b32 $r5 0x100 + mov $r6 0xff + not b32 $r6 + and $r5 $r6 + sethi $r5 0x00020000 + xdld $r4 $r5 + xdwait + sethi $r5 0 + + // set context pointer, from within channel VM + mov $r14 0 + iowrs I[$r15] $r14 + ld b32 $r4 D[$r5 + 0] + shr b32 $r4 8 + ld b32 $r6 D[$r5 + 4] + shl b32 $r6 24 + or $r4 $r6 + mov $xdbase $r4 +') + // 256-byte context, at start of data segment + mov b32 $r4 $r0 + sethi $r4 0x60000 + + // swap! + bra $p1 #swctx_load + xdst $r0 $r4 + bra #swctx_done + swctx_load: + xdld $r0 $r4 + swctx_done: + xdwait + ret + +chsw: + // read current channel + mov $r2 0x1400 + iord $r3 I[$r2] + + // if it's active, unload it and return + xbit $r15 $r3 0x1e + bra e #chsw_no_unload + bclr $flags $p1 + call #swctx + bclr $r3 0x1e + iowr I[$r2] $r3 + mov $r4 1 + iowr I[$r2 + 0x200] $r4 + ret + + // read next channel + chsw_no_unload: + iord $r3 I[$r2 + 0x100] + + // is there a channel waiting to be loaded? + xbit $r13 $r3 0x1e + bra e #chsw_finish_load + bset $flags $p1 + call #swctx +ifdef(`NVA3', + // load dma objects back into TARGET regs + mov $r5 #ctx_dma + mov $r6 #ctx_dma_count + chsw_load_ctx_dma: + ld b32 $r7 D[$r5 + $r6 * 4] + add b32 $r8 $r6 0x180 + shl b32 $r8 8 + iowr I[$r8] $r7 + sub b32 $r6 1 + bra nc #chsw_load_ctx_dma +,) + + chsw_finish_load: + mov $r3 2 + iowr I[$r2 + 0x200] $r3 + ret + +dispatch: + // read incoming fifo command + mov $r3 0x1900 + iord $r2 I[$r3 + 0x100] + iord $r3 I[$r3 + 0x000] + and $r4 $r2 0x7ff + // $r2 will be used to store exception data + shl b32 $r2 0x10 + + // lookup method in the dispatch table, ILLEGAL_MTHD if not found + mov $r5 #dispatch_table + clear b32 $r6 + clear b32 $r7 + dispatch_loop: + ld b16 $r6 D[$r5 + 0] + ld b16 $r7 D[$r5 + 2] + add b32 $r5 4 + cmpu b32 $r4 $r6 + bra c #dispatch_illegal_mthd + add b32 $r7 $r6 + cmpu b32 $r4 $r7 + bra c #dispatch_valid_mthd + sub b32 $r7 $r6 + shl b32 $r7 3 + add b32 $r5 $r7 + bra #dispatch_loop + + // ensure no bits set in reserved fields, INVALID_BITFIELD + dispatch_valid_mthd: + sub b32 $r4 $r6 + shl b32 $r4 3 + add b32 $r4 $r5 + ld b32 $r5 D[$r4 + 4] + and $r5 $r3 + cmpu b32 $r5 0 + bra ne #dispatch_invalid_bitfield + + // depending on dispatch flags: execute method, or save data as state + ld b16 $r5 D[$r4 + 0] + ld b16 $r6 D[$r4 + 2] + cmpu b32 $r6 0 + bra ne #dispatch_cmd + st b32 D[$r5] $r3 + bra #dispatch_done + dispatch_cmd: + bclr $flags $p1 + call $r5 + bra $p1 #dispatch_error + bra #dispatch_done + + dispatch_invalid_bitfield: + or $r2 2 + dispatch_illegal_mthd: + or $r2 1 + + // store exception data in SCRATCH0/SCRATCH1, signal hostirq + dispatch_error: + mov $r4 0x1000 + iowr I[$r4 + 0x000] $r2 + iowr I[$r4 + 0x100] $r3 + mov $r2 0x40 + iowr I[$r0] $r2 + hostirq_wait: + iord $r2 I[$r0 + 0x200] + and $r2 0x40 + cmpu b32 $r2 0 + bra ne #hostirq_wait + + dispatch_done: + mov $r2 0x1d00 + mov $r3 1 + iowr I[$r2] $r3 + ret + +// No-operation +// +// Inputs: +// $r1: irqh state +// $r2: hostirq state +// $r3: data +// $r4: dispatch table entry +// Outputs: +// $r1: irqh state +// $p1: set on error +// $r2: hostirq state +// $r3: data +cmd_nop: + ret + +// PM_TRIGGER +// +// Inputs: +// $r1: irqh state +// $r2: hostirq state +// $r3: data +// $r4: dispatch table entry +// Outputs: +// $r1: irqh state +// $p1: set on error +// $r2: hostirq state +// $r3: data +cmd_pm_trigger: + mov $r2 0x2200 + clear b32 $r3 + sethi $r3 0x20000 + iowr I[$r2] $r3 + ret + +ifdef(`NVA3', +// SET_DMA_* method handler +// +// Inputs: +// $r1: irqh state +// $r2: hostirq state +// $r3: data +// $r4: dispatch table entry +// Outputs: +// $r1: irqh state +// $p1: set on error +// $r2: hostirq state +// $r3: data +cmd_dma: + sub b32 $r4 #dispatch_dma + shr b32 $r4 1 + bset $r3 0x1e + st b32 D[$r4 + #ctx_dma] $r3 + add b32 $r4 0x600 + shl b32 $r4 6 + iowr I[$r4] $r3 + ret +,) + +// Calculates the hw swizzle mask and adjusts the surface's xcnt to match +// +cmd_exec_set_format: + // zero out a chunk of the stack to store the swizzle into + add $sp -0x10 + st b32 D[$sp + 0x00] $r0 + st b32 D[$sp + 0x04] $r0 + st b32 D[$sp + 0x08] $r0 + st b32 D[$sp + 0x0c] $r0 + + // extract cpp, src_ncomp and dst_ncomp from FORMAT + ld b32 $r4 D[$r0 + #ctx_format] + extr $r5 $r4 16:17 + add b32 $r5 1 + extr $r6 $r4 20:21 + add b32 $r6 1 + extr $r7 $r4 24:25 + add b32 $r7 1 + + // convert FORMAT swizzle mask to hw swizzle mask + bclr $flags $p2 + clear b32 $r8 + clear b32 $r9 + ncomp_loop: + and $r10 $r4 0xf + shr b32 $r4 4 + clear b32 $r11 + bpc_loop: + cmpu b8 $r10 4 + bra nc #cmp_c0 + mulu $r12 $r10 $r5 + add b32 $r12 $r11 + bset $flags $p2 + bra #bpc_next + cmp_c0: + bra ne #cmp_c1 + mov $r12 0x10 + add b32 $r12 $r11 + bra #bpc_next + cmp_c1: + cmpu b8 $r10 6 + bra nc #cmp_zero + mov $r12 0x14 + add b32 $r12 $r11 + bra #bpc_next + cmp_zero: + mov $r12 0x80 + bpc_next: + st b8 D[$sp + $r8] $r12 + add b32 $r8 1 + add b32 $r11 1 + cmpu b32 $r11 $r5 + bra c #bpc_loop + add b32 $r9 1 + cmpu b32 $r9 $r7 + bra c #ncomp_loop + + // SRC_XCNT = (xcnt * src_cpp), or 0 if no src ref in swz (hw will hang) + mulu $r6 $r5 + st b32 D[$r0 + #ctx_src_cpp] $r6 + ld b32 $r8 D[$r0 + #ctx_xcnt] + mulu $r6 $r8 + bra $p2 #dst_xcnt + clear b32 $r6 + + dst_xcnt: + mulu $r7 $r5 + st b32 D[$r0 + #ctx_dst_cpp] $r7 + mulu $r7 $r8 + + mov $r5 0x810 + shl b32 $r5 6 + iowr I[$r5 + 0x000] $r6 + iowr I[$r5 + 0x100] $r7 + add b32 $r5 0x800 + ld b32 $r6 D[$r0 + #ctx_dst_cpp] + sub b32 $r6 1 + shl b32 $r6 8 + ld b32 $r7 D[$r0 + #ctx_src_cpp] + sub b32 $r7 1 + or $r6 $r7 + iowr I[$r5 + 0x000] $r6 + add b32 $r5 0x100 + ld b32 $r6 D[$sp + 0x00] + iowr I[$r5 + 0x000] $r6 + ld b32 $r6 D[$sp + 0x04] + iowr I[$r5 + 0x100] $r6 + ld b32 $r6 D[$sp + 0x08] + iowr I[$r5 + 0x200] $r6 + ld b32 $r6 D[$sp + 0x0c] + iowr I[$r5 + 0x300] $r6 + add b32 $r5 0x400 + ld b32 $r6 D[$r0 + #ctx_swz_const0] + iowr I[$r5 + 0x000] $r6 + ld b32 $r6 D[$r0 + #ctx_swz_const1] + iowr I[$r5 + 0x100] $r6 + add $sp 0x10 + ret + +// Setup to handle a tiled surface +// +// Calculates a number of parameters the hardware requires in order +// to correctly handle tiling. +// +// Offset calculation is performed as follows (Tp/Th/Td from TILE_MODE): +// nTx = round_up(w * cpp, 1 << Tp) >> Tp +// nTy = round_up(h, 1 << Th) >> Th +// Txo = (x * cpp) & ((1 << Tp) - 1) +// Tx = (x * cpp) >> Tp +// Tyo = y & ((1 << Th) - 1) +// Ty = y >> Th +// Tzo = z & ((1 << Td) - 1) +// Tz = z >> Td +// +// off = (Tzo << Tp << Th) + (Tyo << Tp) + Txo +// off += ((Tz * nTy * nTx)) + (Ty * nTx) + Tx) << Td << Th << Tp; +// +// Inputs: +// $r4: hw command (0x104800) +// $r5: ctx offset adjustment for src/dst selection +// $p2: set if dst surface +// +cmd_exec_set_surface_tiled: + // translate TILE_MODE into Tp, Th, Td shift values + ld b32 $r7 D[$r5 + #ctx_src_tile_mode] + extr $r9 $r7 8:11 + extr $r8 $r7 4:7 +ifdef(`NVA3', + add b32 $r8 2 +, + add b32 $r8 3 +) + extr $r7 $r7 0:3 + cmp b32 $r7 0xe + bra ne #xtile64 + mov $r7 4 + bra #xtileok + xtile64: + xbit $r7 $flags $p2 + add b32 $r7 17 + bset $r4 $r7 + mov $r7 6 + xtileok: + + // Op = (x * cpp) & ((1 << Tp) - 1) + // Tx = (x * cpp) >> Tp + ld b32 $r10 D[$r5 + #ctx_src_xoff] + ld b32 $r11 D[$r5 + #ctx_src_cpp] + mulu $r10 $r11 + mov $r11 1 + shl b32 $r11 $r7 + sub b32 $r11 1 + and $r12 $r10 $r11 + shr b32 $r10 $r7 + + // Tyo = y & ((1 << Th) - 1) + // Ty = y >> Th + ld b32 $r13 D[$r5 + #ctx_src_yoff] + mov $r14 1 + shl b32 $r14 $r8 + sub b32 $r14 1 + and $r11 $r13 $r14 + shr b32 $r13 $r8 + + // YTILE = ((1 << Th) << 12) | ((1 << Th) - Tyo) + add b32 $r14 1 + shl b32 $r15 $r14 12 + sub b32 $r14 $r11 + or $r15 $r14 + xbit $r6 $flags $p2 + add b32 $r6 0x208 + shl b32 $r6 8 + iowr I[$r6 + 0x000] $r15 + + // Op += Tyo << Tp + shl b32 $r11 $r7 + add b32 $r12 $r11 + + // nTx = ((w * cpp) + ((1 << Tp) - 1) >> Tp) + ld b32 $r15 D[$r5 + #ctx_src_xsize] + ld b32 $r11 D[$r5 + #ctx_src_cpp] + mulu $r15 $r11 + mov $r11 1 + shl b32 $r11 $r7 + sub b32 $r11 1 + add b32 $r15 $r11 + shr b32 $r15 $r7 + push $r15 + + // nTy = (h + ((1 << Th) - 1)) >> Th + ld b32 $r15 D[$r5 + #ctx_src_ysize] + mov $r11 1 + shl b32 $r11 $r8 + sub b32 $r11 1 + add b32 $r15 $r11 + shr b32 $r15 $r8 + push $r15 + + // Tys = Tp + Th + // CFG_YZ_TILE_SIZE = ((1 << Th) >> 2) << Td + add b32 $r7 $r8 + sub b32 $r8 2 + mov $r11 1 + shl b32 $r11 $r8 + shl b32 $r11 $r9 + + // Tzo = z & ((1 << Td) - 1) + // Tz = z >> Td + // Op += Tzo << Tys + // Ts = Tys + Td + ld b32 $r8 D[$r5 + #ctx_src_zoff] + mov $r14 1 + shl b32 $r14 $r9 + sub b32 $r14 1 + and $r15 $r8 $r14 + shl b32 $r15 $r7 + add b32 $r12 $r15 + add b32 $r7 $r9 + shr b32 $r8 $r9 + + // Ot = ((Tz * nTy * nTx) + (Ty * nTx) + Tx) << Ts + pop $r15 + pop $r9 + mulu $r13 $r9 + add b32 $r10 $r13 + mulu $r8 $r9 + mulu $r8 $r15 + add b32 $r10 $r8 + shl b32 $r10 $r7 + + // PITCH = (nTx - 1) << Ts + sub b32 $r9 1 + shl b32 $r9 $r7 + iowr I[$r6 + 0x200] $r9 + + // SRC_ADDRESS_LOW = (Ot + Op) & 0xffffffff + // CFG_ADDRESS_HIGH |= ((Ot + Op) >> 32) << 16 + ld b32 $r7 D[$r5 + #ctx_src_address_low] + ld b32 $r8 D[$r5 + #ctx_src_address_high] + add b32 $r10 $r12 + add b32 $r7 $r10 + adc b32 $r8 0 + shl b32 $r8 16 + or $r8 $r11 + sub b32 $r6 0x600 + iowr I[$r6 + 0x000] $r7 + add b32 $r6 0x400 + iowr I[$r6 + 0x000] $r8 + ret + +// Setup to handle a linear surface +// +// Nothing to see here.. Sets ADDRESS and PITCH, pretty non-exciting +// +cmd_exec_set_surface_linear: + xbit $r6 $flags $p2 + add b32 $r6 0x202 + shl b32 $r6 8 + ld b32 $r7 D[$r5 + #ctx_src_address_low] + iowr I[$r6 + 0x000] $r7 + add b32 $r6 0x400 + ld b32 $r7 D[$r5 + #ctx_src_address_high] + shl b32 $r7 16 + iowr I[$r6 + 0x000] $r7 + add b32 $r6 0x400 + ld b32 $r7 D[$r5 + #ctx_src_pitch] + iowr I[$r6 + 0x000] $r7 + ret + +// wait for regs to be available for use +cmd_exec_wait: + push $r0 + push $r1 + mov $r0 0x800 + shl b32 $r0 6 + loop: + iord $r1 I[$r0] + and $r1 1 + bra ne #loop + pop $r1 + pop $r0 + ret + +cmd_exec_query: + // if QUERY_SHORT not set, write out { -, 0, TIME_LO, TIME_HI } + xbit $r4 $r3 13 + bra ne #query_counter + call #cmd_exec_wait + mov $r4 0x80c + shl b32 $r4 6 + ld b32 $r5 D[$r0 + #ctx_query_address_low] + add b32 $r5 4 + iowr I[$r4 + 0x000] $r5 + iowr I[$r4 + 0x100] $r0 + mov $r5 0xc + iowr I[$r4 + 0x200] $r5 + add b32 $r4 0x400 + ld b32 $r5 D[$r0 + #ctx_query_address_high] + shl b32 $r5 16 + iowr I[$r4 + 0x000] $r5 + add b32 $r4 0x500 + mov $r5 0x00000b00 + sethi $r5 0x00010000 + iowr I[$r4 + 0x000] $r5 + mov $r5 0x00004040 + shl b32 $r5 1 + sethi $r5 0x80800000 + iowr I[$r4 + 0x100] $r5 + mov $r5 0x00001110 + sethi $r5 0x13120000 + iowr I[$r4 + 0x200] $r5 + mov $r5 0x00001514 + sethi $r5 0x17160000 + iowr I[$r4 + 0x300] $r5 + mov $r5 0x00002601 + sethi $r5 0x00010000 + mov $r4 0x800 + shl b32 $r4 6 + iowr I[$r4 + 0x000] $r5 + + // write COUNTER + query_counter: + call #cmd_exec_wait + mov $r4 0x80c + shl b32 $r4 6 + ld b32 $r5 D[$r0 + #ctx_query_address_low] + iowr I[$r4 + 0x000] $r5 + iowr I[$r4 + 0x100] $r0 + mov $r5 0x4 + iowr I[$r4 + 0x200] $r5 + add b32 $r4 0x400 + ld b32 $r5 D[$r0 + #ctx_query_address_high] + shl b32 $r5 16 + iowr I[$r4 + 0x000] $r5 + add b32 $r4 0x500 + mov $r5 0x00000300 + iowr I[$r4 + 0x000] $r5 + mov $r5 0x00001110 + sethi $r5 0x13120000 + iowr I[$r4 + 0x100] $r5 + ld b32 $r5 D[$r0 + #ctx_query_counter] + add b32 $r4 0x500 + iowr I[$r4 + 0x000] $r5 + mov $r5 0x00002601 + sethi $r5 0x00010000 + mov $r4 0x800 + shl b32 $r4 6 + iowr I[$r4 + 0x000] $r5 + ret + +// Execute a copy operation +// +// Inputs: +// $r1: irqh state +// $r2: hostirq state +// $r3: data +// 000002000 QUERY_SHORT +// 000001000 QUERY +// 000000100 DST_LINEAR +// 000000010 SRC_LINEAR +// 000000001 FORMAT +// $r4: dispatch table entry +// Outputs: +// $r1: irqh state +// $p1: set on error +// $r2: hostirq state +// $r3: data +cmd_exec: + call #cmd_exec_wait + + // if format requested, call function to calculate it, otherwise + // fill in cpp/xcnt for both surfaces as if (cpp == 1) + xbit $r15 $r3 0 + bra e #cmd_exec_no_format + call #cmd_exec_set_format + mov $r4 0x200 + bra #cmd_exec_init_src_surface + cmd_exec_no_format: + mov $r6 0x810 + shl b32 $r6 6 + mov $r7 1 + st b32 D[$r0 + #ctx_src_cpp] $r7 + st b32 D[$r0 + #ctx_dst_cpp] $r7 + ld b32 $r7 D[$r0 + #ctx_xcnt] + iowr I[$r6 + 0x000] $r7 + iowr I[$r6 + 0x100] $r7 + clear b32 $r4 + + cmd_exec_init_src_surface: + bclr $flags $p2 + clear b32 $r5 + xbit $r15 $r3 4 + bra e #src_tiled + call #cmd_exec_set_surface_linear + bra #cmd_exec_init_dst_surface + src_tiled: + call #cmd_exec_set_surface_tiled + bset $r4 7 + + cmd_exec_init_dst_surface: + bset $flags $p2 + mov $r5 #ctx_dst_address_high - #ctx_src_address_high + xbit $r15 $r3 8 + bra e #dst_tiled + call #cmd_exec_set_surface_linear + bra #cmd_exec_kick + dst_tiled: + call #cmd_exec_set_surface_tiled + bset $r4 8 + + cmd_exec_kick: + mov $r5 0x800 + shl b32 $r5 6 + ld b32 $r6 D[$r0 + #ctx_ycnt] + iowr I[$r5 + 0x100] $r6 + mov $r6 0x0041 + // SRC_TARGET = 1, DST_TARGET = 2 + sethi $r6 0x44000000 + or $r4 $r6 + iowr I[$r5] $r4 + + // if requested, queue up a QUERY write after the copy has completed + xbit $r15 $r3 12 + bra e #cmd_exec_done + call #cmd_exec_query + + cmd_exec_done: + ret + +// Flush write cache +// +// Inputs: +// $r1: irqh state +// $r2: hostirq state +// $r3: data +// $r4: dispatch table entry +// Outputs: +// $r1: irqh state +// $p1: set on error +// $r2: hostirq state +// $r3: data +cmd_wrcache_flush: + mov $r2 0x2200 + clear b32 $r3 + sethi $r3 0x10000 + iowr I[$r2] $r3 + ret + +.align 0x100 diff --git a/drivers/gpu/drm/nouveau/nva3_copy.fuc.h b/drivers/gpu/drm/nouveau/nva3_copy.fuc.h new file mode 100644 index 00000000..1f33fbdc --- /dev/null +++ b/drivers/gpu/drm/nouveau/nva3_copy.fuc.h @@ -0,0 +1,534 @@ +uint32_t nva3_pcopy_data[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00010000, + 0x00000000, + 0x00000000, + 0x00010040, + 0x00010160, + 0x00000000, + 0x00010050, + 0x00010162, + 0x00000000, + 0x00030060, + 0x00010170, + 0x00000000, + 0x00010170, + 0x00000000, + 0x00010170, + 0x00000000, + 0x00070080, + 0x00000028, + 0xfffff000, + 0x0000002c, + 0xfff80000, + 0x00000030, + 0xffffe000, + 0x00000034, + 0xfffff800, + 0x00000038, + 0xfffff000, + 0x0000003c, + 0xfff80000, + 0x00000040, + 0xffffe000, + 0x00070088, + 0x00000054, + 0xfffff000, + 0x00000058, + 0xfff80000, + 0x0000005c, + 0xffffe000, + 0x00000060, + 0xfffff800, + 0x00000064, + 0xfffff000, + 0x00000068, + 0xfff80000, + 0x0000006c, + 0xffffe000, + 0x000200c0, + 0x00010492, + 0x00000000, + 0x0001051b, + 0x00000000, + 0x000e00c3, + 0x0000001c, + 0xffffff00, + 0x00000020, + 0x0000000f, + 0x00000048, + 0xffffff00, + 0x0000004c, + 0x0000000f, + 0x00000024, + 0xfff80000, + 0x00000050, + 0xfff80000, + 0x00000080, + 0xffff0000, + 0x00000084, + 0xffffe000, + 0x00000074, + 0xfccc0000, + 0x00000078, + 0x00000000, + 0x0000007c, + 0x00000000, + 0x00000010, + 0xffffff00, + 0x00000014, + 0x00000000, + 0x00000018, + 0x00000000, + 0x00000800, +}; + +uint32_t nva3_pcopy_code[] = { + 0x04fe04bd, + 0x3517f000, + 0xf10010fe, + 0xf1040017, + 0xf0fff327, + 0x12d00023, + 0x0c25f0c0, + 0xf40012d0, + 0x17f11031, + 0x27f01200, + 0x0012d003, + 0xf40031f4, + 0x0ef40028, + 0x8001cffd, + 0xf40812c4, + 0x21f4060b, + 0x0412c472, + 0xf4060bf4, + 0x11c4c321, + 0x4001d00c, + 0x47f101f8, + 0x4bfe7700, + 0x0007fe00, + 0xf00204b9, + 0x01f40643, + 0x0604fa09, + 0xfa060ef4, + 0x03f80504, + 0x27f100f8, + 0x23cf1400, + 0x1e3fc800, + 0xf4170bf4, + 0x21f40132, + 0x1e3af052, + 0xf00023d0, + 0x24d00147, + 0xcf00f880, + 0x3dc84023, + 0x220bf41e, + 0xf40131f4, + 0x57f05221, + 0x0367f004, + 0xa07856bc, + 0xb6018068, + 0x87d00884, + 0x0162b600, + 0xf0f018f4, + 0x23d00237, + 0xf100f880, + 0xcf190037, + 0x33cf4032, + 0xff24e400, + 0x1024b607, + 0x010057f1, + 0x74bd64bd, + 0x58005658, + 0x50b60157, + 0x0446b804, + 0xbb4d08f4, + 0x47b80076, + 0x0f08f404, + 0xb60276bb, + 0x57bb0374, + 0xdf0ef400, + 0xb60246bb, + 0x45bb0344, + 0x01459800, + 0xb00453fd, + 0x1bf40054, + 0x00455820, + 0xb0014658, + 0x1bf40064, + 0x00538009, + 0xf4300ef4, + 0x55f90132, + 0xf40c01f4, + 0x25f0250e, + 0x0125f002, + 0x100047f1, + 0xd00042d0, + 0x27f04043, + 0x0002d040, + 0xf08002cf, + 0x24b04024, + 0xf71bf400, + 0x1d0027f1, + 0xd00137f0, + 0x00f80023, + 0x27f100f8, + 0x34bd2200, + 0xd00233f0, + 0x00f80023, + 0x012842b7, + 0xf00145b6, + 0x43801e39, + 0x0040b701, + 0x0644b606, + 0xf80043d0, + 0xf030f400, + 0xb00001b0, + 0x01b00101, + 0x0301b002, + 0xc71d0498, + 0x50b63045, + 0x3446c701, + 0xc70160b6, + 0x70b63847, + 0x0232f401, + 0x94bd84bd, + 0xb60f4ac4, + 0xb4bd0445, + 0xf404a430, + 0xa5ff0f18, + 0x00cbbbc0, + 0xf40231f4, + 0x1bf4220e, + 0x10c7f00c, + 0xf400cbbb, + 0xa430160e, + 0x0c18f406, + 0xbb14c7f0, + 0x0ef400cb, + 0x80c7f107, + 0x01c83800, + 0xb60180b6, + 0xb5b801b0, + 0xc308f404, + 0xb80190b6, + 0x08f40497, + 0x0065fdb2, + 0x98110680, + 0x68fd2008, + 0x0502f400, + 0x75fd64bd, + 0x1c078000, + 0xf10078fd, + 0xb6081057, + 0x56d00654, + 0x4057d000, + 0x080050b7, + 0xb61c0698, + 0x64b60162, + 0x11079808, + 0xfd0172b6, + 0x56d00567, + 0x0050b700, + 0x0060b401, + 0xb40056d0, + 0x56d00160, + 0x0260b440, + 0xb48056d0, + 0x56d00360, + 0x0050b7c0, + 0x1e069804, + 0x980056d0, + 0x56d01f06, + 0x1030f440, + 0x579800f8, + 0x6879c70a, + 0xb66478c7, + 0x77c70280, + 0x0e76b060, + 0xf0091bf4, + 0x0ef40477, + 0x027cf00f, + 0xfd1170b6, + 0x77f00947, + 0x0f5a9806, + 0xfd115b98, + 0xb7f000ab, + 0x04b7bb01, + 0xff01b2b6, + 0xa7bbc4ab, + 0x105d9805, + 0xbb01e7f0, + 0xe2b604e8, + 0xb4deff01, + 0xb605d8bb, + 0xef9401e0, + 0x02ebbb0c, + 0xf005fefd, + 0x60b7026c, + 0x64b60208, + 0x006fd008, + 0xbb04b7bb, + 0x5f9800cb, + 0x115b980b, + 0xf000fbfd, + 0xb7bb01b7, + 0x01b2b604, + 0xbb00fbbb, + 0xf0f905f7, + 0xf00c5f98, + 0xb8bb01b7, + 0x01b2b604, + 0xbb00fbbb, + 0xf0f905f8, + 0xb60078bb, + 0xb7f00282, + 0x04b8bb01, + 0x9804b9bb, + 0xe7f00e58, + 0x04e9bb01, + 0xff01e2b6, + 0xf7bbf48e, + 0x00cfbb04, + 0xbb0079bb, + 0xf0fc0589, + 0xd9fd90fc, + 0x00adbb00, + 0xfd0089fd, + 0xa8bb008f, + 0x04a7bb00, + 0xbb0192b6, + 0x69d00497, + 0x08579880, + 0xbb075898, + 0x7abb00ac, + 0x0081b600, + 0xfd1084b6, + 0x62b7058b, + 0x67d00600, + 0x0060b700, + 0x0068d004, + 0x6cf000f8, + 0x0260b702, + 0x0864b602, + 0xd0085798, + 0x60b70067, + 0x57980400, + 0x1074b607, + 0xb70067d0, + 0x98040060, + 0x67d00957, + 0xf900f800, + 0xf110f900, + 0xb6080007, + 0x01cf0604, + 0x0114f000, + 0xfcfa1bf4, + 0xf800fc10, + 0x0d34c800, + 0xf5701bf4, + 0xf103ab21, + 0xb6080c47, + 0x05980644, + 0x0450b605, + 0xd00045d0, + 0x57f04040, + 0x8045d00c, + 0x040040b7, + 0xb6040598, + 0x45d01054, + 0x0040b700, + 0x0057f105, + 0x0153f00b, + 0xf10045d0, + 0xb6404057, + 0x53f10154, + 0x45d08080, + 0x1057f140, + 0x1253f111, + 0x8045d013, + 0x151457f1, + 0x171653f1, + 0xf1c045d0, + 0xf0260157, + 0x47f10153, + 0x44b60800, + 0x0045d006, + 0x03ab21f5, + 0x080c47f1, + 0x980644b6, + 0x45d00505, + 0x4040d000, + 0xd00457f0, + 0x40b78045, + 0x05980400, + 0x1054b604, + 0xb70045d0, + 0xf1050040, + 0xd0030057, + 0x57f10045, + 0x53f11110, + 0x45d01312, + 0x06059840, + 0x050040b7, + 0xf10045d0, + 0xf0260157, + 0x47f10153, + 0x44b60800, + 0x0045d006, + 0x21f500f8, + 0x3fc803ab, + 0x0e0bf400, + 0x018921f5, + 0x020047f1, + 0xf11e0ef4, + 0xb6081067, + 0x77f00664, + 0x11078001, + 0x981c0780, + 0x67d02007, + 0x4067d000, + 0x32f444bd, + 0xc854bd02, + 0x0bf4043f, + 0x8221f50a, + 0x0a0ef403, + 0x027621f5, + 0xf40749f0, + 0x57f00231, + 0x083fc82c, + 0xf50a0bf4, + 0xf4038221, + 0x21f50a0e, + 0x49f00276, + 0x0057f108, + 0x0654b608, + 0xd0210698, + 0x67f04056, + 0x0063f141, + 0x0546fd44, + 0xc80054d0, + 0x0bf40c3f, + 0xc521f507, + 0xf100f803, + 0xbd220027, + 0x0133f034, + 0xf80023d0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; diff --git a/drivers/gpu/drm/nouveau/nva3_pm.c b/drivers/gpu/drm/nouveau/nva3_pm.c new file mode 100644 index 00000000..9e636e6e --- /dev/null +++ b/drivers/gpu/drm/nouveau/nva3_pm.c @@ -0,0 +1,346 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_bios.h" +#include "nouveau_pm.h" + +static u32 read_clk(struct drm_device *, int, bool); +static u32 read_pll(struct drm_device *, int, u32); + +static u32 +read_vco(struct drm_device *dev, int clk) +{ + u32 sctl = nv_rd32(dev, 0x4120 + (clk * 4)); + if ((sctl & 0x00000030) != 0x00000030) + return read_pll(dev, 0x41, 0x00e820); + return read_pll(dev, 0x42, 0x00e8a0); +} + +static u32 +read_clk(struct drm_device *dev, int clk, bool ignore_en) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + u32 sctl, sdiv, sclk; + + /* refclk for the 0xe8xx plls is a fixed frequency */ + if (clk >= 0x40) { + if (dev_priv->chipset == 0xaf) { + /* no joke.. seriously.. sigh.. */ + return nv_rd32(dev, 0x00471c) * 1000; + } + + return dev_priv->crystal; + } + + sctl = nv_rd32(dev, 0x4120 + (clk * 4)); + if (!ignore_en && !(sctl & 0x00000100)) + return 0; + + switch (sctl & 0x00003000) { + case 0x00000000: + return dev_priv->crystal; + case 0x00002000: + if (sctl & 0x00000040) + return 108000; + return 100000; + case 0x00003000: + sclk = read_vco(dev, clk); + sdiv = ((sctl & 0x003f0000) >> 16) + 2; + return (sclk * 2) / sdiv; + default: + return 0; + } +} + +static u32 +read_pll(struct drm_device *dev, int clk, u32 pll) +{ + u32 ctrl = nv_rd32(dev, pll + 0); + u32 sclk = 0, P = 1, N = 1, M = 1; + + if (!(ctrl & 0x00000008)) { + if (ctrl & 0x00000001) { + u32 coef = nv_rd32(dev, pll + 4); + M = (coef & 0x000000ff) >> 0; + N = (coef & 0x0000ff00) >> 8; + P = (coef & 0x003f0000) >> 16; + + /* no post-divider on these.. */ + if ((pll & 0x00ff00) == 0x00e800) + P = 1; + + sclk = read_clk(dev, 0x00 + clk, false); + } + } else { + sclk = read_clk(dev, 0x10 + clk, false); + } + + return sclk * N / (M * P); +} + +struct creg { + u32 clk; + u32 pll; +}; + +static int +calc_clk(struct drm_device *dev, int clk, u32 pll, u32 khz, struct creg *reg) +{ + struct pll_lims limits; + u32 oclk, sclk, sdiv; + int P, N, M, diff; + int ret; + + reg->pll = 0; + reg->clk = 0; + if (!khz) { + NV_DEBUG(dev, "no clock for 0x%04x/0x%02x\n", pll, clk); + return 0; + } + + switch (khz) { + case 27000: + reg->clk = 0x00000100; + return khz; + case 100000: + reg->clk = 0x00002100; + return khz; + case 108000: + reg->clk = 0x00002140; + return khz; + default: + sclk = read_vco(dev, clk); + sdiv = min((sclk * 2) / (khz - 2999), (u32)65); + /* if the clock has a PLL attached, and we can get a within + * [-2, 3) MHz of a divider, we'll disable the PLL and use + * the divider instead. + * + * divider can go as low as 2, limited here because NVIDIA + * and the VBIOS on my NVA8 seem to prefer using the PLL + * for 810MHz - is there a good reason? + */ + if (sdiv > 4) { + oclk = (sclk * 2) / sdiv; + diff = khz - oclk; + if (!pll || (diff >= -2000 && diff < 3000)) { + reg->clk = (((sdiv - 2) << 16) | 0x00003100); + return oclk; + } + } + + if (!pll) { + NV_ERROR(dev, "bad freq %02x: %d %d\n", clk, khz, sclk); + return -ERANGE; + } + + break; + } + + ret = get_pll_limits(dev, pll, &limits); + if (ret) + return ret; + + limits.refclk = read_clk(dev, clk - 0x10, true); + if (!limits.refclk) + return -EINVAL; + + ret = nva3_calc_pll(dev, &limits, khz, &N, NULL, &M, &P); + if (ret >= 0) { + reg->clk = nv_rd32(dev, 0x4120 + (clk * 4)); + reg->pll = (P << 16) | (N << 8) | M; + } + return ret; +} + +static void +prog_pll(struct drm_device *dev, int clk, u32 pll, struct creg *reg) +{ + const u32 src0 = 0x004120 + (clk * 4); + const u32 src1 = 0x004160 + (clk * 4); + const u32 ctrl = pll + 0; + const u32 coef = pll + 4; + u32 cntl; + + if (!reg->clk && !reg->pll) { + NV_DEBUG(dev, "no clock for %02x\n", clk); + return; + } + + cntl = nv_rd32(dev, ctrl) & 0xfffffff2; + if (reg->pll) { + nv_mask(dev, src0, 0x00000101, 0x00000101); + nv_wr32(dev, coef, reg->pll); + nv_wr32(dev, ctrl, cntl | 0x00000015); + nv_mask(dev, src1, 0x00000100, 0x00000000); + nv_mask(dev, src1, 0x00000001, 0x00000000); + } else { + nv_mask(dev, src1, 0x003f3141, 0x00000101 | reg->clk); + nv_wr32(dev, ctrl, cntl | 0x0000001d); + nv_mask(dev, ctrl, 0x00000001, 0x00000000); + nv_mask(dev, src0, 0x00000100, 0x00000000); + nv_mask(dev, src0, 0x00000001, 0x00000000); + } +} + +static void +prog_clk(struct drm_device *dev, int clk, struct creg *reg) +{ + if (!reg->clk) { + NV_DEBUG(dev, "no clock for %02x\n", clk); + return; + } + + nv_mask(dev, 0x004120 + (clk * 4), 0x003f3141, 0x00000101 | reg->clk); +} + +int +nva3_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + perflvl->core = read_pll(dev, 0x00, 0x4200); + perflvl->shader = read_pll(dev, 0x01, 0x4220); + perflvl->memory = read_pll(dev, 0x02, 0x4000); + perflvl->unka0 = read_clk(dev, 0x20, false); + perflvl->vdec = read_clk(dev, 0x21, false); + perflvl->daemon = read_clk(dev, 0x25, false); + perflvl->copy = perflvl->core; + return 0; +} + +struct nva3_pm_state { + struct creg nclk; + struct creg sclk; + struct creg mclk; + struct creg vdec; + struct creg unka0; +}; + +void * +nva3_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct nva3_pm_state *info; + int ret; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + ret = calc_clk(dev, 0x10, 0x4200, perflvl->core, &info->nclk); + if (ret < 0) + goto out; + + ret = calc_clk(dev, 0x11, 0x4220, perflvl->shader, &info->sclk); + if (ret < 0) + goto out; + + ret = calc_clk(dev, 0x12, 0x4000, perflvl->memory, &info->mclk); + if (ret < 0) + goto out; + + ret = calc_clk(dev, 0x20, 0x0000, perflvl->unka0, &info->unka0); + if (ret < 0) + goto out; + + ret = calc_clk(dev, 0x21, 0x0000, perflvl->vdec, &info->vdec); + if (ret < 0) + goto out; + +out: + if (ret < 0) { + kfree(info); + info = ERR_PTR(ret); + } + return info; +} + +static bool +nva3_pm_grcp_idle(void *data) +{ + struct drm_device *dev = data; + + if (!(nv_rd32(dev, 0x400304) & 0x00000001)) + return true; + if (nv_rd32(dev, 0x400308) == 0x0050001c) + return true; + return false; +} + +int +nva3_pm_clocks_set(struct drm_device *dev, void *pre_state) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nva3_pm_state *info = pre_state; + unsigned long flags; + int ret = -EAGAIN; + + /* prevent any new grctx switches from starting */ + spin_lock_irqsave(&dev_priv->context_switch_lock, flags); + nv_wr32(dev, 0x400324, 0x00000000); + nv_wr32(dev, 0x400328, 0x0050001c); /* wait flag 0x1c */ + /* wait for any pending grctx switches to complete */ + if (!nv_wait_cb(dev, nva3_pm_grcp_idle, dev)) { + NV_ERROR(dev, "pm: ctxprog didn't go idle\n"); + goto cleanup; + } + /* freeze PFIFO */ + nv_mask(dev, 0x002504, 0x00000001, 0x00000001); + if (!nv_wait(dev, 0x002504, 0x00000010, 0x00000010)) { + NV_ERROR(dev, "pm: fifo didn't go idle\n"); + goto cleanup; + } + + prog_pll(dev, 0x00, 0x004200, &info->nclk); + prog_pll(dev, 0x01, 0x004220, &info->sclk); + prog_clk(dev, 0x20, &info->unka0); + prog_clk(dev, 0x21, &info->vdec); + + if (info->mclk.clk || info->mclk.pll) { + nv_wr32(dev, 0x100210, 0); + nv_wr32(dev, 0x1002dc, 1); + nv_wr32(dev, 0x004018, 0x00001000); + prog_pll(dev, 0x02, 0x004000, &info->mclk); + if (nv_rd32(dev, 0x4000) & 0x00000008) + nv_wr32(dev, 0x004018, 0x1000d000); + else + nv_wr32(dev, 0x004018, 0x10005000); + nv_wr32(dev, 0x1002dc, 0); + nv_wr32(dev, 0x100210, 0x80000000); + } + + ret = 0; + +cleanup: + /* unfreeze PFIFO */ + nv_mask(dev, 0x002504, 0x00000001, 0x00000000); + /* restore ctxprog to normal */ + nv_wr32(dev, 0x400324, 0x00000000); + nv_wr32(dev, 0x400328, 0x0070009c); /* set flag 0x1c */ + /* unblock it if necessary */ + if (nv_rd32(dev, 0x400308) == 0x0050001c) + nv_mask(dev, 0x400824, 0x10000000, 0x10000000); + spin_unlock_irqrestore(&dev_priv->context_switch_lock, flags); + kfree(info); + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nvc0_copy.c b/drivers/gpu/drm/nouveau/nvc0_copy.c new file mode 100644 index 00000000..dddf006f --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_copy.c @@ -0,0 +1,243 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <linux/firmware.h> +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_util.h" +#include "nouveau_vm.h" +#include "nouveau_ramht.h" +#include "nvc0_copy.fuc.h" + +struct nvc0_copy_engine { + struct nouveau_exec_engine base; + u32 irq; + u32 pmc; + u32 fuc; + u32 ctx; +}; + +static int +nvc0_copy_context_new(struct nouveau_channel *chan, int engine) +{ + struct nvc0_copy_engine *pcopy = nv_engine(chan->dev, engine); + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_gpuobj *ramin = chan->ramin; + struct nouveau_gpuobj *ctx = NULL; + int ret; + + ret = nouveau_gpuobj_new(dev, chan, 256, 256, + NVOBJ_FLAG_VM | NVOBJ_FLAG_VM_USER | + NVOBJ_FLAG_ZERO_ALLOC, &ctx); + if (ret) + return ret; + + nv_wo32(ramin, pcopy->ctx + 0, lower_32_bits(ctx->linst)); + nv_wo32(ramin, pcopy->ctx + 4, upper_32_bits(ctx->linst)); + dev_priv->engine.instmem.flush(dev); + + chan->engctx[engine] = ctx; + return 0; +} + +static int +nvc0_copy_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + return 0; +} + +static void +nvc0_copy_context_del(struct nouveau_channel *chan, int engine) +{ + struct nvc0_copy_engine *pcopy = nv_engine(chan->dev, engine); + struct nouveau_gpuobj *ctx = chan->engctx[engine]; + struct drm_device *dev = chan->dev; + u32 inst; + + inst = (chan->ramin->vinst >> 12); + inst |= 0x40000000; + + /* disable fifo access */ + nv_wr32(dev, pcopy->fuc + 0x048, 0x00000000); + /* mark channel as unloaded if it's currently active */ + if (nv_rd32(dev, pcopy->fuc + 0x050) == inst) + nv_mask(dev, pcopy->fuc + 0x050, 0x40000000, 0x00000000); + /* mark next channel as invalid if it's about to be loaded */ + if (nv_rd32(dev, pcopy->fuc + 0x054) == inst) + nv_mask(dev, pcopy->fuc + 0x054, 0x40000000, 0x00000000); + /* restore fifo access */ + nv_wr32(dev, pcopy->fuc + 0x048, 0x00000003); + + nv_wo32(chan->ramin, pcopy->ctx + 0, 0x00000000); + nv_wo32(chan->ramin, pcopy->ctx + 4, 0x00000000); + nouveau_gpuobj_ref(NULL, &ctx); + + chan->engctx[engine] = ctx; +} + +static int +nvc0_copy_init(struct drm_device *dev, int engine) +{ + struct nvc0_copy_engine *pcopy = nv_engine(dev, engine); + int i; + + nv_mask(dev, 0x000200, pcopy->pmc, 0x00000000); + nv_mask(dev, 0x000200, pcopy->pmc, pcopy->pmc); + nv_wr32(dev, pcopy->fuc + 0x014, 0xffffffff); + + nv_wr32(dev, pcopy->fuc + 0x1c0, 0x01000000); + for (i = 0; i < sizeof(nvc0_pcopy_data) / 4; i++) + nv_wr32(dev, pcopy->fuc + 0x1c4, nvc0_pcopy_data[i]); + + nv_wr32(dev, pcopy->fuc + 0x180, 0x01000000); + for (i = 0; i < sizeof(nvc0_pcopy_code) / 4; i++) { + if ((i & 0x3f) == 0) + nv_wr32(dev, pcopy->fuc + 0x188, i >> 6); + nv_wr32(dev, pcopy->fuc + 0x184, nvc0_pcopy_code[i]); + } + + nv_wr32(dev, pcopy->fuc + 0x084, engine - NVOBJ_ENGINE_COPY0); + nv_wr32(dev, pcopy->fuc + 0x10c, 0x00000000); + nv_wr32(dev, pcopy->fuc + 0x104, 0x00000000); /* ENTRY */ + nv_wr32(dev, pcopy->fuc + 0x100, 0x00000002); /* TRIGGER */ + return 0; +} + +static int +nvc0_copy_fini(struct drm_device *dev, int engine, bool suspend) +{ + struct nvc0_copy_engine *pcopy = nv_engine(dev, engine); + + nv_mask(dev, pcopy->fuc + 0x048, 0x00000003, 0x00000000); + + /* trigger fuc context unload */ + nv_wait(dev, pcopy->fuc + 0x008, 0x0000000c, 0x00000000); + nv_mask(dev, pcopy->fuc + 0x054, 0x40000000, 0x00000000); + nv_wr32(dev, pcopy->fuc + 0x000, 0x00000008); + nv_wait(dev, pcopy->fuc + 0x008, 0x00000008, 0x00000000); + + nv_wr32(dev, pcopy->fuc + 0x014, 0xffffffff); + return 0; +} + +static struct nouveau_enum nvc0_copy_isr_error_name[] = { + { 0x0001, "ILLEGAL_MTHD" }, + { 0x0002, "INVALID_ENUM" }, + { 0x0003, "INVALID_BITFIELD" }, + {} +}; + +static void +nvc0_copy_isr(struct drm_device *dev, int engine) +{ + struct nvc0_copy_engine *pcopy = nv_engine(dev, engine); + u32 disp = nv_rd32(dev, pcopy->fuc + 0x01c); + u32 stat = nv_rd32(dev, pcopy->fuc + 0x008) & disp & ~(disp >> 16); + u64 inst = (u64)(nv_rd32(dev, pcopy->fuc + 0x050) & 0x0fffffff) << 12; + u32 chid = nvc0_graph_isr_chid(dev, inst); + u32 ssta = nv_rd32(dev, pcopy->fuc + 0x040) & 0x0000ffff; + u32 addr = nv_rd32(dev, pcopy->fuc + 0x040) >> 16; + u32 mthd = (addr & 0x07ff) << 2; + u32 subc = (addr & 0x3800) >> 11; + u32 data = nv_rd32(dev, pcopy->fuc + 0x044); + + if (stat & 0x00000040) { + NV_INFO(dev, "PCOPY: DISPATCH_ERROR ["); + nouveau_enum_print(nvc0_copy_isr_error_name, ssta); + printk("] ch %d [0x%010llx] subc %d mthd 0x%04x data 0x%08x\n", + chid, inst, subc, mthd, data); + nv_wr32(dev, pcopy->fuc + 0x004, 0x00000040); + stat &= ~0x00000040; + } + + if (stat) { + NV_INFO(dev, "PCOPY: unhandled intr 0x%08x\n", stat); + nv_wr32(dev, pcopy->fuc + 0x004, stat); + } +} + +static void +nvc0_copy_isr_0(struct drm_device *dev) +{ + nvc0_copy_isr(dev, NVOBJ_ENGINE_COPY0); +} + +static void +nvc0_copy_isr_1(struct drm_device *dev) +{ + nvc0_copy_isr(dev, NVOBJ_ENGINE_COPY1); +} + +static void +nvc0_copy_destroy(struct drm_device *dev, int engine) +{ + struct nvc0_copy_engine *pcopy = nv_engine(dev, engine); + + nouveau_irq_unregister(dev, pcopy->irq); + + if (engine == NVOBJ_ENGINE_COPY0) + NVOBJ_ENGINE_DEL(dev, COPY0); + else + NVOBJ_ENGINE_DEL(dev, COPY1); + kfree(pcopy); +} + +int +nvc0_copy_create(struct drm_device *dev, int engine) +{ + struct nvc0_copy_engine *pcopy; + + pcopy = kzalloc(sizeof(*pcopy), GFP_KERNEL); + if (!pcopy) + return -ENOMEM; + + pcopy->base.destroy = nvc0_copy_destroy; + pcopy->base.init = nvc0_copy_init; + pcopy->base.fini = nvc0_copy_fini; + pcopy->base.context_new = nvc0_copy_context_new; + pcopy->base.context_del = nvc0_copy_context_del; + pcopy->base.object_new = nvc0_copy_object_new; + + if (engine == 0) { + pcopy->irq = 5; + pcopy->pmc = 0x00000040; + pcopy->fuc = 0x104000; + pcopy->ctx = 0x0230; + nouveau_irq_register(dev, pcopy->irq, nvc0_copy_isr_0); + NVOBJ_ENGINE_ADD(dev, COPY0, &pcopy->base); + NVOBJ_CLASS(dev, 0x90b5, COPY0); + } else { + pcopy->irq = 6; + pcopy->pmc = 0x00000080; + pcopy->fuc = 0x105000; + pcopy->ctx = 0x0240; + nouveau_irq_register(dev, pcopy->irq, nvc0_copy_isr_1); + NVOBJ_ENGINE_ADD(dev, COPY1, &pcopy->base); + NVOBJ_CLASS(dev, 0x90b8, COPY1); + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nvc0_copy.fuc.h b/drivers/gpu/drm/nouveau/nvc0_copy.fuc.h new file mode 100644 index 00000000..a8d17458 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_copy.fuc.h @@ -0,0 +1,527 @@ +uint32_t nvc0_pcopy_data[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00010000, + 0x00000000, + 0x00000000, + 0x00010040, + 0x0001019f, + 0x00000000, + 0x00010050, + 0x000101a1, + 0x00000000, + 0x00070080, + 0x0000001c, + 0xfffff000, + 0x00000020, + 0xfff80000, + 0x00000024, + 0xffffe000, + 0x00000028, + 0xfffff800, + 0x0000002c, + 0xfffff000, + 0x00000030, + 0xfff80000, + 0x00000034, + 0xffffe000, + 0x00070088, + 0x00000048, + 0xfffff000, + 0x0000004c, + 0xfff80000, + 0x00000050, + 0xffffe000, + 0x00000054, + 0xfffff800, + 0x00000058, + 0xfffff000, + 0x0000005c, + 0xfff80000, + 0x00000060, + 0xffffe000, + 0x000200c0, + 0x000104b8, + 0x00000000, + 0x00010541, + 0x00000000, + 0x000e00c3, + 0x00000010, + 0xffffff00, + 0x00000014, + 0x0000000f, + 0x0000003c, + 0xffffff00, + 0x00000040, + 0x0000000f, + 0x00000018, + 0xfff80000, + 0x00000044, + 0xfff80000, + 0x00000074, + 0xffff0000, + 0x00000078, + 0xffffe000, + 0x00000068, + 0xfccc0000, + 0x0000006c, + 0x00000000, + 0x00000070, + 0x00000000, + 0x00000004, + 0xffffff00, + 0x00000008, + 0x00000000, + 0x0000000c, + 0x00000000, + 0x00000800, +}; + +uint32_t nvc0_pcopy_code[] = { + 0x04fe04bd, + 0x3517f000, + 0xf10010fe, + 0xf1040017, + 0xf0fff327, + 0x12d00023, + 0x0c25f0c0, + 0xf40012d0, + 0x17f11031, + 0x27f01200, + 0x0012d003, + 0xf40031f4, + 0x0ef40028, + 0x8001cffd, + 0xf40812c4, + 0x21f4060b, + 0x0412c4ca, + 0xf5070bf4, + 0xc4010221, + 0x01d00c11, + 0xf101f840, + 0xfe770047, + 0x47f1004b, + 0x44cf2100, + 0x0144f000, + 0xb60444b6, + 0xf7f13040, + 0xf4b6061c, + 0x1457f106, + 0x00f5d101, + 0xb6043594, + 0x57fe0250, + 0x0145fe00, + 0x010052b7, + 0x00ff67f1, + 0x56fd60bd, + 0x0253f004, + 0xf80545fa, + 0x0053f003, + 0xd100e7f0, + 0x549800fe, + 0x0845b600, + 0xb6015698, + 0x46fd1864, + 0x0047fe05, + 0xf00204b9, + 0x01f40643, + 0x0604fa09, + 0xfa060ef4, + 0x03f80504, + 0x27f100f8, + 0x23cf1400, + 0x1e3fc800, + 0xf4170bf4, + 0x21f40132, + 0x1e3af053, + 0xf00023d0, + 0x24d00147, + 0xcf00f880, + 0x3dc84023, + 0x090bf41e, + 0xf40131f4, + 0x37f05321, + 0x8023d002, + 0x37f100f8, + 0x32cf1900, + 0x0033cf40, + 0x07ff24e4, + 0xf11024b6, + 0xbd010057, + 0x5874bd64, + 0x57580056, + 0x0450b601, + 0xf40446b8, + 0x76bb4d08, + 0x0447b800, + 0xbb0f08f4, + 0x74b60276, + 0x0057bb03, + 0xbbdf0ef4, + 0x44b60246, + 0x0045bb03, + 0xfd014598, + 0x54b00453, + 0x201bf400, + 0x58004558, + 0x64b00146, + 0x091bf400, + 0xf4005380, + 0x32f4300e, + 0xf455f901, + 0x0ef40c01, + 0x0225f025, + 0xf10125f0, + 0xd0100047, + 0x43d00042, + 0x4027f040, + 0xcf0002d0, + 0x24f08002, + 0x0024b040, + 0xf1f71bf4, + 0xf01d0027, + 0x23d00137, + 0xf800f800, + 0x0027f100, + 0xf034bd22, + 0x23d00233, + 0xf400f800, + 0x01b0f030, + 0x0101b000, + 0xb00201b0, + 0x04980301, + 0x3045c71a, + 0xc70150b6, + 0x60b63446, + 0x3847c701, + 0xf40170b6, + 0x84bd0232, + 0x4ac494bd, + 0x0445b60f, + 0xa430b4bd, + 0x0f18f404, + 0xbbc0a5ff, + 0x31f400cb, + 0x220ef402, + 0xf00c1bf4, + 0xcbbb10c7, + 0x160ef400, + 0xf406a430, + 0xc7f00c18, + 0x00cbbb14, + 0xf1070ef4, + 0x380080c7, + 0x80b601c8, + 0x01b0b601, + 0xf404b5b8, + 0x90b6c308, + 0x0497b801, + 0xfdb208f4, + 0x06800065, + 0x1d08980e, + 0xf40068fd, + 0x64bd0502, + 0x800075fd, + 0x78fd1907, + 0x1057f100, + 0x0654b608, + 0xd00056d0, + 0x50b74057, + 0x06980800, + 0x0162b619, + 0x980864b6, + 0x72b60e07, + 0x0567fd01, + 0xb70056d0, + 0xb4010050, + 0x56d00060, + 0x0160b400, + 0xb44056d0, + 0x56d00260, + 0x0360b480, + 0xb7c056d0, + 0x98040050, + 0x56d01b06, + 0x1c069800, + 0xf44056d0, + 0x00f81030, + 0xc7075798, + 0x78c76879, + 0x0380b664, + 0xb06077c7, + 0x1bf40e76, + 0x0477f009, + 0xf00f0ef4, + 0x70b6027c, + 0x0947fd11, + 0x980677f0, + 0x5b980c5a, + 0x00abfd0e, + 0xbb01b7f0, + 0xb2b604b7, + 0xc4abff01, + 0x9805a7bb, + 0xe7f00d5d, + 0x04e8bb01, + 0xff01e2b6, + 0xd8bbb4de, + 0x01e0b605, + 0xbb0cef94, + 0xfefd02eb, + 0x026cf005, + 0x020860b7, + 0xd00864b6, + 0xb7bb006f, + 0x00cbbb04, + 0x98085f98, + 0xfbfd0e5b, + 0x01b7f000, + 0xb604b7bb, + 0xfbbb01b2, + 0x05f7bb00, + 0x5f98f0f9, + 0x01b7f009, + 0xb604b8bb, + 0xfbbb01b2, + 0x05f8bb00, + 0x78bbf0f9, + 0x0282b600, + 0xbb01b7f0, + 0xb9bb04b8, + 0x0b589804, + 0xbb01e7f0, + 0xe2b604e9, + 0xf48eff01, + 0xbb04f7bb, + 0x79bb00cf, + 0x0589bb00, + 0x90fcf0fc, + 0xbb00d9fd, + 0x89fd00ad, + 0x008ffd00, + 0xbb00a8bb, + 0x92b604a7, + 0x0497bb01, + 0x988069d0, + 0x58980557, + 0x00acbb04, + 0xb6007abb, + 0x84b60081, + 0x058bfd10, + 0x060062b7, + 0xb70067d0, + 0xd0040060, + 0x00f80068, + 0xb7026cf0, + 0xb6020260, + 0x57980864, + 0x0067d005, + 0x040060b7, + 0xb6045798, + 0x67d01074, + 0x0060b700, + 0x06579804, + 0xf80067d0, + 0xf900f900, + 0x0007f110, + 0x0604b608, + 0xf00001cf, + 0x1bf40114, + 0xfc10fcfa, + 0xc800f800, + 0x1bf40d34, + 0xd121f570, + 0x0c47f103, + 0x0644b608, + 0xb6020598, + 0x45d00450, + 0x4040d000, + 0xd00c57f0, + 0x40b78045, + 0x05980400, + 0x1054b601, + 0xb70045d0, + 0xf1050040, + 0xf00b0057, + 0x45d00153, + 0x4057f100, + 0x0154b640, + 0x808053f1, + 0xf14045d0, + 0xf1111057, + 0xd0131253, + 0x57f18045, + 0x53f11514, + 0x45d01716, + 0x0157f1c0, + 0x0153f026, + 0x080047f1, + 0xd00644b6, + 0x21f50045, + 0x47f103d1, + 0x44b6080c, + 0x02059806, + 0xd00045d0, + 0x57f04040, + 0x8045d004, + 0x040040b7, + 0xb6010598, + 0x45d01054, + 0x0040b700, + 0x0057f105, + 0x0045d003, + 0x111057f1, + 0x131253f1, + 0x984045d0, + 0x40b70305, + 0x45d00500, + 0x0157f100, + 0x0153f026, + 0x080047f1, + 0xd00644b6, + 0x00f80045, + 0x03d121f5, + 0xf4003fc8, + 0x21f50e0b, + 0x47f101af, + 0x0ef40200, + 0x1067f11e, + 0x0664b608, + 0x800177f0, + 0x07800e07, + 0x1d079819, + 0xd00067d0, + 0x44bd4067, + 0xbd0232f4, + 0x043fc854, + 0xf50a0bf4, + 0xf403a821, + 0x21f50a0e, + 0x49f0029c, + 0x0231f407, + 0xc82c57f0, + 0x0bf4083f, + 0xa821f50a, + 0x0a0ef403, + 0x029c21f5, + 0xf10849f0, + 0xb6080057, + 0x06980654, + 0x4056d01e, + 0xf14167f0, + 0xfd440063, + 0x54d00546, + 0x0c3fc800, + 0xf5070bf4, + 0xf803eb21, + 0x0027f100, + 0xf034bd22, + 0x23d00133, + 0x0000f800, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; diff --git a/drivers/gpu/drm/nouveau/nvc0_fb.c b/drivers/gpu/drm/nouveau/nvc0_fb.c new file mode 100644 index 00000000..f704e942 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_fb.c @@ -0,0 +1,134 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "drm.h" +#include "nouveau_drv.h" +#include "nouveau_drm.h" + +struct nvc0_fb_priv { + struct page *r100c10_page; + dma_addr_t r100c10; +}; + +static inline void +nvc0_mfb_subp_isr(struct drm_device *dev, int unit, int subp) +{ + u32 subp_base = 0x141000 + (unit * 0x2000) + (subp * 0x400); + u32 stat = nv_rd32(dev, subp_base + 0x020); + + if (stat) { + NV_INFO(dev, "PMFB%d_SUBP%d: 0x%08x\n", unit, subp, stat); + nv_wr32(dev, subp_base + 0x020, stat); + } +} + +static void +nvc0_mfb_isr(struct drm_device *dev) +{ + u32 units = nv_rd32(dev, 0x00017c); + while (units) { + u32 subp, unit = ffs(units) - 1; + for (subp = 0; subp < 2; subp++) + nvc0_mfb_subp_isr(dev, unit, subp); + units &= ~(1 << unit); + } + + /* we do something horribly wrong and upset PMFB a lot, so mask off + * interrupts from it after the first one until it's fixed + */ + nv_mask(dev, 0x000640, 0x02000000, 0x00000000); +} + +static void +nvc0_fb_destroy(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + struct nvc0_fb_priv *priv = pfb->priv; + + nouveau_irq_unregister(dev, 25); + + if (priv->r100c10_page) { + pci_unmap_page(dev->pdev, priv->r100c10, PAGE_SIZE, + PCI_DMA_BIDIRECTIONAL); + __free_page(priv->r100c10_page); + } + + kfree(priv); + pfb->priv = NULL; +} + +static int +nvc0_fb_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fb_engine *pfb = &dev_priv->engine.fb; + struct nvc0_fb_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + pfb->priv = priv; + + priv->r100c10_page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!priv->r100c10_page) { + nvc0_fb_destroy(dev); + return -ENOMEM; + } + + priv->r100c10 = pci_map_page(dev->pdev, priv->r100c10_page, 0, + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(dev->pdev, priv->r100c10)) { + nvc0_fb_destroy(dev); + return -EFAULT; + } + + nouveau_irq_register(dev, 25, nvc0_mfb_isr); + return 0; +} + +int +nvc0_fb_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvc0_fb_priv *priv; + int ret; + + if (!dev_priv->engine.fb.priv) { + ret = nvc0_fb_create(dev); + if (ret) + return ret; + } + priv = dev_priv->engine.fb.priv; + + nv_wr32(dev, 0x100c10, priv->r100c10 >> 8); + return 0; +} + +void +nvc0_fb_takedown(struct drm_device *dev) +{ + nvc0_fb_destroy(dev); +} diff --git a/drivers/gpu/drm/nouveau/nvc0_fbcon.c b/drivers/gpu/drm/nouveau/nvc0_fbcon.c new file mode 100644 index 00000000..a495e481 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_fbcon.c @@ -0,0 +1,269 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_dma.h" +#include "nouveau_ramht.h" +#include "nouveau_fbcon.h" +#include "nouveau_mm.h" + +int +nvc0_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + int ret; + + ret = RING_SPACE(chan, rect->rop == ROP_COPY ? 7 : 11); + if (ret) + return ret; + + if (rect->rop != ROP_COPY) { + BEGIN_NVC0(chan, 2, NvSub2D, 0x02ac, 1); + OUT_RING (chan, 1); + } + BEGIN_NVC0(chan, 2, NvSub2D, 0x0588, 1); + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) + OUT_RING (chan, ((uint32_t *)info->pseudo_palette)[rect->color]); + else + OUT_RING (chan, rect->color); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0600, 4); + OUT_RING (chan, rect->dx); + OUT_RING (chan, rect->dy); + OUT_RING (chan, rect->dx + rect->width); + OUT_RING (chan, rect->dy + rect->height); + if (rect->rop != ROP_COPY) { + BEGIN_NVC0(chan, 2, NvSub2D, 0x02ac, 1); + OUT_RING (chan, 3); + } + FIRE_RING(chan); + return 0; +} + +int +nvc0_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *region) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + int ret; + + ret = RING_SPACE(chan, 12); + if (ret) + return ret; + + BEGIN_NVC0(chan, 2, NvSub2D, 0x0110, 1); + OUT_RING (chan, 0); + BEGIN_NVC0(chan, 2, NvSub2D, 0x08b0, 4); + OUT_RING (chan, region->dx); + OUT_RING (chan, region->dy); + OUT_RING (chan, region->width); + OUT_RING (chan, region->height); + BEGIN_NVC0(chan, 2, NvSub2D, 0x08d0, 4); + OUT_RING (chan, 0); + OUT_RING (chan, region->sx); + OUT_RING (chan, 0); + OUT_RING (chan, region->sy); + FIRE_RING(chan); + return 0; +} + +int +nvc0_fbcon_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + uint32_t width, dwords, *data = (uint32_t *)image->data; + uint32_t mask = ~(~0 >> (32 - info->var.bits_per_pixel)); + uint32_t *palette = info->pseudo_palette; + int ret; + + if (image->depth != 1) + return -ENODEV; + + ret = RING_SPACE(chan, 11); + if (ret) + return ret; + + width = ALIGN(image->width, 32); + dwords = (width * image->height) >> 5; + + BEGIN_NVC0(chan, 2, NvSub2D, 0x0814, 2); + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + OUT_RING (chan, palette[image->bg_color] | mask); + OUT_RING (chan, palette[image->fg_color] | mask); + } else { + OUT_RING (chan, image->bg_color); + OUT_RING (chan, image->fg_color); + } + BEGIN_NVC0(chan, 2, NvSub2D, 0x0838, 2); + OUT_RING (chan, image->width); + OUT_RING (chan, image->height); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0850, 4); + OUT_RING (chan, 0); + OUT_RING (chan, image->dx); + OUT_RING (chan, 0); + OUT_RING (chan, image->dy); + + while (dwords) { + int push = dwords > 2047 ? 2047 : dwords; + + ret = RING_SPACE(chan, push + 1); + if (ret) + return ret; + + dwords -= push; + + BEGIN_NVC0(chan, 6, NvSub2D, 0x0860, push); + OUT_RINGp(chan, data, push); + data += push; + } + + FIRE_RING(chan); + return 0; +} + +int +nvc0_fbcon_accel_init(struct fb_info *info) +{ + struct nouveau_fbdev *nfbdev = info->par; + struct drm_device *dev = nfbdev->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = dev_priv->channel; + struct nouveau_framebuffer *fb = &nfbdev->nouveau_fb; + int ret, format; + + ret = nouveau_gpuobj_gr_new(chan, 0x902d, 0x902d); + if (ret) + return ret; + + switch (info->var.bits_per_pixel) { + case 8: + format = 0xf3; + break; + case 15: + format = 0xf8; + break; + case 16: + format = 0xe8; + break; + case 32: + switch (info->var.transp.length) { + case 0: /* depth 24 */ + case 8: /* depth 32, just use 24.. */ + format = 0xe6; + break; + case 2: /* depth 30 */ + format = 0xd1; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + ret = RING_SPACE(chan, 60); + if (ret) { + WARN_ON(1); + nouveau_fbcon_gpu_lockup(info); + return ret; + } + + BEGIN_NVC0(chan, 2, NvSub2D, 0x0000, 1); + OUT_RING (chan, 0x0000902d); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0104, 2); + OUT_RING (chan, upper_32_bits(chan->notifier_vma.offset)); + OUT_RING (chan, lower_32_bits(chan->notifier_vma.offset)); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0290, 1); + OUT_RING (chan, 0); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0888, 1); + OUT_RING (chan, 1); + BEGIN_NVC0(chan, 2, NvSub2D, 0x02ac, 1); + OUT_RING (chan, 3); + BEGIN_NVC0(chan, 2, NvSub2D, 0x02a0, 1); + OUT_RING (chan, 0x55); + BEGIN_NVC0(chan, 2, NvSub2D, 0x08c0, 4); + OUT_RING (chan, 0); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, 1); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0580, 2); + OUT_RING (chan, 4); + OUT_RING (chan, format); + BEGIN_NVC0(chan, 2, NvSub2D, 0x02e8, 2); + OUT_RING (chan, 2); + OUT_RING (chan, 1); + + BEGIN_NVC0(chan, 2, NvSub2D, 0x0804, 1); + OUT_RING (chan, format); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0800, 1); + OUT_RING (chan, 1); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0808, 3); + OUT_RING (chan, 0); + OUT_RING (chan, 0); + OUT_RING (chan, 1); + BEGIN_NVC0(chan, 2, NvSub2D, 0x081c, 1); + OUT_RING (chan, 1); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0840, 4); + OUT_RING (chan, 0); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, 1); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0200, 10); + OUT_RING (chan, format); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, info->fix.line_length); + OUT_RING (chan, info->var.xres_virtual); + OUT_RING (chan, info->var.yres_virtual); + OUT_RING (chan, upper_32_bits(fb->vma.offset)); + OUT_RING (chan, lower_32_bits(fb->vma.offset)); + BEGIN_NVC0(chan, 2, NvSub2D, 0x0230, 10); + OUT_RING (chan, format); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, 1); + OUT_RING (chan, 0); + OUT_RING (chan, info->fix.line_length); + OUT_RING (chan, info->var.xres_virtual); + OUT_RING (chan, info->var.yres_virtual); + OUT_RING (chan, upper_32_bits(fb->vma.offset)); + OUT_RING (chan, lower_32_bits(fb->vma.offset)); + FIRE_RING (chan); + + return 0; +} + diff --git a/drivers/gpu/drm/nouveau/nvc0_fifo.c b/drivers/gpu/drm/nouveau/nvc0_fifo.c new file mode 100644 index 00000000..50d68a7a --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_fifo.c @@ -0,0 +1,536 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_mm.h" + +static void nvc0_fifo_isr(struct drm_device *); + +struct nvc0_fifo_priv { + struct nouveau_gpuobj *playlist[2]; + int cur_playlist; + struct nouveau_vma user_vma; + int spoon_nr; +}; + +struct nvc0_fifo_chan { + struct nouveau_gpuobj *user; + struct nouveau_gpuobj *ramfc; +}; + +static void +nvc0_fifo_playlist_update(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nvc0_fifo_priv *priv = pfifo->priv; + struct nouveau_gpuobj *cur; + int i, p; + + cur = priv->playlist[priv->cur_playlist]; + priv->cur_playlist = !priv->cur_playlist; + + for (i = 0, p = 0; i < 128; i++) { + if (!(nv_rd32(dev, 0x3004 + (i * 8)) & 1)) + continue; + nv_wo32(cur, p + 0, i); + nv_wo32(cur, p + 4, 0x00000004); + p += 8; + } + pinstmem->flush(dev); + + nv_wr32(dev, 0x002270, cur->vinst >> 12); + nv_wr32(dev, 0x002274, 0x01f00000 | (p >> 3)); + if (!nv_wait(dev, 0x00227c, 0x00100000, 0x00000000)) + NV_ERROR(dev, "PFIFO - playlist update failed\n"); +} + +void +nvc0_fifo_disable(struct drm_device *dev) +{ +} + +void +nvc0_fifo_enable(struct drm_device *dev) +{ +} + +bool +nvc0_fifo_reassign(struct drm_device *dev, bool enable) +{ + return false; +} + +bool +nvc0_fifo_cache_pull(struct drm_device *dev, bool enable) +{ + return false; +} + +int +nvc0_fifo_channel_id(struct drm_device *dev) +{ + return 127; +} + +int +nvc0_fifo_create_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nvc0_fifo_priv *priv = pfifo->priv; + struct nvc0_fifo_chan *fifoch; + u64 ib_virt = chan->pushbuf_base + chan->dma.ib_base * 4; + int ret; + + chan->fifo_priv = kzalloc(sizeof(*fifoch), GFP_KERNEL); + if (!chan->fifo_priv) + return -ENOMEM; + fifoch = chan->fifo_priv; + + /* allocate vram for control regs, map into polling area */ + ret = nouveau_gpuobj_new(dev, NULL, 0x1000, 0x1000, + NVOBJ_FLAG_ZERO_ALLOC, &fifoch->user); + if (ret) + goto error; + + nouveau_vm_map_at(&priv->user_vma, chan->id * 0x1000, + *(struct nouveau_mem **)fifoch->user->node); + + chan->user = ioremap_wc(pci_resource_start(dev->pdev, 1) + + priv->user_vma.offset + (chan->id * 0x1000), + PAGE_SIZE); + if (!chan->user) { + ret = -ENOMEM; + goto error; + } + + /* ramfc */ + ret = nouveau_gpuobj_new_fake(dev, chan->ramin->pinst, + chan->ramin->vinst, 0x100, + NVOBJ_FLAG_ZERO_ALLOC, &fifoch->ramfc); + if (ret) + goto error; + + nv_wo32(fifoch->ramfc, 0x08, lower_32_bits(fifoch->user->vinst)); + nv_wo32(fifoch->ramfc, 0x0c, upper_32_bits(fifoch->user->vinst)); + nv_wo32(fifoch->ramfc, 0x10, 0x0000face); + nv_wo32(fifoch->ramfc, 0x30, 0xfffff902); + nv_wo32(fifoch->ramfc, 0x48, lower_32_bits(ib_virt)); + nv_wo32(fifoch->ramfc, 0x4c, drm_order(chan->dma.ib_max + 1) << 16 | + upper_32_bits(ib_virt)); + nv_wo32(fifoch->ramfc, 0x54, 0x00000002); + nv_wo32(fifoch->ramfc, 0x84, 0x20400000); + nv_wo32(fifoch->ramfc, 0x94, 0x30000001); + nv_wo32(fifoch->ramfc, 0x9c, 0x00000100); + nv_wo32(fifoch->ramfc, 0xa4, 0x1f1f1f1f); + nv_wo32(fifoch->ramfc, 0xa8, 0x1f1f1f1f); + nv_wo32(fifoch->ramfc, 0xac, 0x0000001f); + nv_wo32(fifoch->ramfc, 0xb8, 0xf8000000); + nv_wo32(fifoch->ramfc, 0xf8, 0x10003080); /* 0x002310 */ + nv_wo32(fifoch->ramfc, 0xfc, 0x10000010); /* 0x002350 */ + pinstmem->flush(dev); + + nv_wr32(dev, 0x003000 + (chan->id * 8), 0xc0000000 | + (chan->ramin->vinst >> 12)); + nv_wr32(dev, 0x003004 + (chan->id * 8), 0x001f0001); + nvc0_fifo_playlist_update(dev); + return 0; + +error: + pfifo->destroy_context(chan); + return ret; +} + +void +nvc0_fifo_destroy_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + struct nvc0_fifo_chan *fifoch; + + nv_mask(dev, 0x003004 + (chan->id * 8), 0x00000001, 0x00000000); + nv_wr32(dev, 0x002634, chan->id); + if (!nv_wait(dev, 0x0002634, 0xffffffff, chan->id)) + NV_WARN(dev, "0x2634 != chid: 0x%08x\n", nv_rd32(dev, 0x2634)); + + nvc0_fifo_playlist_update(dev); + + nv_wr32(dev, 0x003000 + (chan->id * 8), 0x00000000); + + if (chan->user) { + iounmap(chan->user); + chan->user = NULL; + } + + fifoch = chan->fifo_priv; + chan->fifo_priv = NULL; + if (!fifoch) + return; + + nouveau_gpuobj_ref(NULL, &fifoch->ramfc); + nouveau_gpuobj_ref(NULL, &fifoch->user); + kfree(fifoch); +} + +int +nvc0_fifo_load_context(struct nouveau_channel *chan) +{ + return 0; +} + +int +nvc0_fifo_unload_context(struct drm_device *dev) +{ + int i; + + for (i = 0; i < 128; i++) { + if (!(nv_rd32(dev, 0x003004 + (i * 8)) & 1)) + continue; + + nv_mask(dev, 0x003004 + (i * 8), 0x00000001, 0x00000000); + nv_wr32(dev, 0x002634, i); + if (!nv_wait(dev, 0x002634, 0xffffffff, i)) { + NV_INFO(dev, "PFIFO: kick ch %d failed: 0x%08x\n", + i, nv_rd32(dev, 0x002634)); + return -EBUSY; + } + } + + return 0; +} + +static void +nvc0_fifo_destroy(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nvc0_fifo_priv *priv; + + priv = pfifo->priv; + if (!priv) + return; + + nouveau_vm_put(&priv->user_vma); + nouveau_gpuobj_ref(NULL, &priv->playlist[1]); + nouveau_gpuobj_ref(NULL, &priv->playlist[0]); + kfree(priv); +} + +void +nvc0_fifo_takedown(struct drm_device *dev) +{ + nv_wr32(dev, 0x002140, 0x00000000); + nvc0_fifo_destroy(dev); +} + +static int +nvc0_fifo_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nvc0_fifo_priv *priv; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + pfifo->priv = priv; + + ret = nouveau_gpuobj_new(dev, NULL, 0x1000, 0x1000, 0, + &priv->playlist[0]); + if (ret) + goto error; + + ret = nouveau_gpuobj_new(dev, NULL, 0x1000, 0x1000, 0, + &priv->playlist[1]); + if (ret) + goto error; + + ret = nouveau_vm_get(dev_priv->bar1_vm, pfifo->channels * 0x1000, + 12, NV_MEM_ACCESS_RW, &priv->user_vma); + if (ret) + goto error; + + nouveau_irq_register(dev, 8, nvc0_fifo_isr); + NVOBJ_CLASS(dev, 0x506e, SW); /* nvsw */ + return 0; + +error: + nvc0_fifo_destroy(dev); + return ret; +} + +int +nvc0_fifo_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_fifo_engine *pfifo = &dev_priv->engine.fifo; + struct nouveau_channel *chan; + struct nvc0_fifo_priv *priv; + int ret, i; + + if (!pfifo->priv) { + ret = nvc0_fifo_create(dev); + if (ret) + return ret; + } + priv = pfifo->priv; + + /* reset PFIFO, enable all available PSUBFIFO areas */ + nv_mask(dev, 0x000200, 0x00000100, 0x00000000); + nv_mask(dev, 0x000200, 0x00000100, 0x00000100); + nv_wr32(dev, 0x000204, 0xffffffff); + nv_wr32(dev, 0x002204, 0xffffffff); + + priv->spoon_nr = hweight32(nv_rd32(dev, 0x002204)); + NV_DEBUG(dev, "PFIFO: %d subfifo(s)\n", priv->spoon_nr); + + /* assign engines to subfifos */ + if (priv->spoon_nr >= 3) { + nv_wr32(dev, 0x002208, ~(1 << 0)); /* PGRAPH */ + nv_wr32(dev, 0x00220c, ~(1 << 1)); /* PVP */ + nv_wr32(dev, 0x002210, ~(1 << 1)); /* PPP */ + nv_wr32(dev, 0x002214, ~(1 << 1)); /* PBSP */ + nv_wr32(dev, 0x002218, ~(1 << 2)); /* PCE0 */ + nv_wr32(dev, 0x00221c, ~(1 << 1)); /* PCE1 */ + } + + /* PSUBFIFO[n] */ + for (i = 0; i < priv->spoon_nr; i++) { + nv_mask(dev, 0x04013c + (i * 0x2000), 0x10000100, 0x00000000); + nv_wr32(dev, 0x040108 + (i * 0x2000), 0xffffffff); /* INTR */ + nv_wr32(dev, 0x04010c + (i * 0x2000), 0xfffffeff); /* INTR_EN */ + } + + nv_mask(dev, 0x002200, 0x00000001, 0x00000001); + nv_wr32(dev, 0x002254, 0x10000000 | priv->user_vma.offset >> 12); + + nv_wr32(dev, 0x002a00, 0xffffffff); /* clears PFIFO.INTR bit 30 */ + nv_wr32(dev, 0x002100, 0xffffffff); + nv_wr32(dev, 0x002140, 0xbfffffff); + + /* restore PFIFO context table */ + for (i = 0; i < 128; i++) { + chan = dev_priv->channels.ptr[i]; + if (!chan || !chan->fifo_priv) + continue; + + nv_wr32(dev, 0x003000 + (i * 8), 0xc0000000 | + (chan->ramin->vinst >> 12)); + nv_wr32(dev, 0x003004 + (i * 8), 0x001f0001); + } + nvc0_fifo_playlist_update(dev); + + return 0; +} + +struct nouveau_enum nvc0_fifo_fault_unit[] = { + { 0x00, "PGRAPH" }, + { 0x03, "PEEPHOLE" }, + { 0x04, "BAR1" }, + { 0x05, "BAR3" }, + { 0x07, "PFIFO" }, + { 0x10, "PBSP" }, + { 0x11, "PPPP" }, + { 0x13, "PCOUNTER" }, + { 0x14, "PVP" }, + { 0x15, "PCOPY0" }, + { 0x16, "PCOPY1" }, + { 0x17, "PDAEMON" }, + {} +}; + +struct nouveau_enum nvc0_fifo_fault_reason[] = { + { 0x00, "PT_NOT_PRESENT" }, + { 0x01, "PT_TOO_SHORT" }, + { 0x02, "PAGE_NOT_PRESENT" }, + { 0x03, "VM_LIMIT_EXCEEDED" }, + { 0x04, "NO_CHANNEL" }, + { 0x05, "PAGE_SYSTEM_ONLY" }, + { 0x06, "PAGE_READ_ONLY" }, + { 0x0a, "COMPRESSED_SYSRAM" }, + { 0x0c, "INVALID_STORAGE_TYPE" }, + {} +}; + +struct nouveau_enum nvc0_fifo_fault_hubclient[] = { + { 0x01, "PCOPY0" }, + { 0x02, "PCOPY1" }, + { 0x04, "DISPATCH" }, + { 0x05, "CTXCTL" }, + { 0x06, "PFIFO" }, + { 0x07, "BAR_READ" }, + { 0x08, "BAR_WRITE" }, + { 0x0b, "PVP" }, + { 0x0c, "PPPP" }, + { 0x0d, "PBSP" }, + { 0x11, "PCOUNTER" }, + { 0x12, "PDAEMON" }, + { 0x14, "CCACHE" }, + { 0x15, "CCACHE_POST" }, + {} +}; + +struct nouveau_enum nvc0_fifo_fault_gpcclient[] = { + { 0x01, "TEX" }, + { 0x0c, "ESETUP" }, + { 0x0e, "CTXCTL" }, + { 0x0f, "PROP" }, + {} +}; + +struct nouveau_bitfield nvc0_fifo_subfifo_intr[] = { +/* { 0x00008000, "" } seen with null ib push */ + { 0x00200000, "ILLEGAL_MTHD" }, + { 0x00800000, "EMPTY_SUBC" }, + {} +}; + +static void +nvc0_fifo_isr_vm_fault(struct drm_device *dev, int unit) +{ + u32 inst = nv_rd32(dev, 0x2800 + (unit * 0x10)); + u32 valo = nv_rd32(dev, 0x2804 + (unit * 0x10)); + u32 vahi = nv_rd32(dev, 0x2808 + (unit * 0x10)); + u32 stat = nv_rd32(dev, 0x280c + (unit * 0x10)); + u32 client = (stat & 0x00001f00) >> 8; + + NV_INFO(dev, "PFIFO: %s fault at 0x%010llx [", + (stat & 0x00000080) ? "write" : "read", (u64)vahi << 32 | valo); + nouveau_enum_print(nvc0_fifo_fault_reason, stat & 0x0000000f); + printk("] from "); + nouveau_enum_print(nvc0_fifo_fault_unit, unit); + if (stat & 0x00000040) { + printk("/"); + nouveau_enum_print(nvc0_fifo_fault_hubclient, client); + } else { + printk("/GPC%d/", (stat & 0x1f000000) >> 24); + nouveau_enum_print(nvc0_fifo_fault_gpcclient, client); + } + printk(" on channel 0x%010llx\n", (u64)inst << 12); +} + +static int +nvc0_fifo_page_flip(struct drm_device *dev, u32 chid) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan = NULL; + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&dev_priv->channels.lock, flags); + if (likely(chid >= 0 && chid < dev_priv->engine.fifo.channels)) { + chan = dev_priv->channels.ptr[chid]; + if (likely(chan)) + ret = nouveau_finish_page_flip(chan, NULL); + } + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + return ret; +} + +static void +nvc0_fifo_isr_subfifo_intr(struct drm_device *dev, int unit) +{ + u32 stat = nv_rd32(dev, 0x040108 + (unit * 0x2000)); + u32 addr = nv_rd32(dev, 0x0400c0 + (unit * 0x2000)); + u32 data = nv_rd32(dev, 0x0400c4 + (unit * 0x2000)); + u32 chid = nv_rd32(dev, 0x040120 + (unit * 0x2000)) & 0x7f; + u32 subc = (addr & 0x00070000); + u32 mthd = (addr & 0x00003ffc); + u32 show = stat; + + if (stat & 0x00200000) { + if (mthd == 0x0054) { + if (!nvc0_fifo_page_flip(dev, chid)) + show &= ~0x00200000; + } + } + + if (show) { + NV_INFO(dev, "PFIFO%d:", unit); + nouveau_bitfield_print(nvc0_fifo_subfifo_intr, show); + NV_INFO(dev, "PFIFO%d: ch %d subc %d mthd 0x%04x data 0x%08x\n", + unit, chid, subc, mthd, data); + } + + nv_wr32(dev, 0x0400c0 + (unit * 0x2000), 0x80600008); + nv_wr32(dev, 0x040108 + (unit * 0x2000), stat); +} + +static void +nvc0_fifo_isr(struct drm_device *dev) +{ + u32 stat = nv_rd32(dev, 0x002100); + + if (stat & 0x00000100) { + NV_INFO(dev, "PFIFO: unknown status 0x00000100\n"); + nv_wr32(dev, 0x002100, 0x00000100); + stat &= ~0x00000100; + } + + if (stat & 0x10000000) { + u32 units = nv_rd32(dev, 0x00259c); + u32 u = units; + + while (u) { + int i = ffs(u) - 1; + nvc0_fifo_isr_vm_fault(dev, i); + u &= ~(1 << i); + } + + nv_wr32(dev, 0x00259c, units); + stat &= ~0x10000000; + } + + if (stat & 0x20000000) { + u32 units = nv_rd32(dev, 0x0025a0); + u32 u = units; + + while (u) { + int i = ffs(u) - 1; + nvc0_fifo_isr_subfifo_intr(dev, i); + u &= ~(1 << i); + } + + nv_wr32(dev, 0x0025a0, units); + stat &= ~0x20000000; + } + + if (stat & 0x40000000) { + NV_INFO(dev, "PFIFO: unknown status 0x40000000\n"); + nv_mask(dev, 0x002a00, 0x00000000, 0x00000000); + stat &= ~0x40000000; + } + + if (stat) { + NV_INFO(dev, "PFIFO: unhandled status 0x%08x\n", stat); + nv_wr32(dev, 0x002100, stat); + nv_wr32(dev, 0x002140, 0); + } +} diff --git a/drivers/gpu/drm/nouveau/nvc0_graph.c b/drivers/gpu/drm/nouveau/nvc0_graph.c new file mode 100644 index 00000000..9066102d --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_graph.c @@ -0,0 +1,895 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <linux/firmware.h> +#include <linux/module.h> + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_mm.h" + +#include "nvc0_graph.h" +#include "nvc0_grhub.fuc.h" +#include "nvc0_grgpc.fuc.h" + +static void +nvc0_graph_ctxctl_debug_unit(struct drm_device *dev, u32 base) +{ + NV_INFO(dev, "PGRAPH: %06x - done 0x%08x\n", base, + nv_rd32(dev, base + 0x400)); + NV_INFO(dev, "PGRAPH: %06x - stat 0x%08x 0x%08x 0x%08x 0x%08x\n", base, + nv_rd32(dev, base + 0x800), nv_rd32(dev, base + 0x804), + nv_rd32(dev, base + 0x808), nv_rd32(dev, base + 0x80c)); + NV_INFO(dev, "PGRAPH: %06x - stat 0x%08x 0x%08x 0x%08x 0x%08x\n", base, + nv_rd32(dev, base + 0x810), nv_rd32(dev, base + 0x814), + nv_rd32(dev, base + 0x818), nv_rd32(dev, base + 0x81c)); +} + +static void +nvc0_graph_ctxctl_debug(struct drm_device *dev) +{ + u32 gpcnr = nv_rd32(dev, 0x409604) & 0xffff; + u32 gpc; + + nvc0_graph_ctxctl_debug_unit(dev, 0x409000); + for (gpc = 0; gpc < gpcnr; gpc++) + nvc0_graph_ctxctl_debug_unit(dev, 0x502000 + (gpc * 0x8000)); +} + +static int +nvc0_graph_load_context(struct nouveau_channel *chan) +{ + struct drm_device *dev = chan->dev; + + nv_wr32(dev, 0x409840, 0x00000030); + nv_wr32(dev, 0x409500, 0x80000000 | chan->ramin->vinst >> 12); + nv_wr32(dev, 0x409504, 0x00000003); + if (!nv_wait(dev, 0x409800, 0x00000010, 0x00000010)) + NV_ERROR(dev, "PGRAPH: load_ctx timeout\n"); + + return 0; +} + +static int +nvc0_graph_unload_context_to(struct drm_device *dev, u64 chan) +{ + nv_wr32(dev, 0x409840, 0x00000003); + nv_wr32(dev, 0x409500, 0x80000000 | chan >> 12); + nv_wr32(dev, 0x409504, 0x00000009); + if (!nv_wait(dev, 0x409800, 0x00000001, 0x00000000)) { + NV_ERROR(dev, "PGRAPH: unload_ctx timeout\n"); + return -EBUSY; + } + + return 0; +} + +static int +nvc0_graph_construct_context(struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nvc0_graph_priv *priv = nv_engine(chan->dev, NVOBJ_ENGINE_GR); + struct nvc0_graph_chan *grch = chan->engctx[NVOBJ_ENGINE_GR]; + struct drm_device *dev = chan->dev; + int ret, i; + u32 *ctx; + + ctx = kmalloc(priv->grctx_size, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (!nouveau_ctxfw) { + nv_wr32(dev, 0x409840, 0x80000000); + nv_wr32(dev, 0x409500, 0x80000000 | chan->ramin->vinst >> 12); + nv_wr32(dev, 0x409504, 0x00000001); + if (!nv_wait(dev, 0x409800, 0x80000000, 0x80000000)) { + NV_ERROR(dev, "PGRAPH: HUB_SET_CHAN timeout\n"); + nvc0_graph_ctxctl_debug(dev); + ret = -EBUSY; + goto err; + } + } else { + nvc0_graph_load_context(chan); + + nv_wo32(grch->grctx, 0x1c, 1); + nv_wo32(grch->grctx, 0x20, 0); + nv_wo32(grch->grctx, 0x28, 0); + nv_wo32(grch->grctx, 0x2c, 0); + dev_priv->engine.instmem.flush(dev); + } + + ret = nvc0_grctx_generate(chan); + if (ret) + goto err; + + if (!nouveau_ctxfw) { + nv_wr32(dev, 0x409840, 0x80000000); + nv_wr32(dev, 0x409500, 0x80000000 | chan->ramin->vinst >> 12); + nv_wr32(dev, 0x409504, 0x00000002); + if (!nv_wait(dev, 0x409800, 0x80000000, 0x80000000)) { + NV_ERROR(dev, "PGRAPH: HUB_CTX_SAVE timeout\n"); + nvc0_graph_ctxctl_debug(dev); + ret = -EBUSY; + goto err; + } + } else { + ret = nvc0_graph_unload_context_to(dev, chan->ramin->vinst); + if (ret) + goto err; + } + + for (i = 0; i < priv->grctx_size; i += 4) + ctx[i / 4] = nv_ro32(grch->grctx, i); + + priv->grctx_vals = ctx; + return 0; + +err: + kfree(ctx); + return ret; +} + +static int +nvc0_graph_create_context_mmio_list(struct nouveau_channel *chan) +{ + struct nvc0_graph_priv *priv = nv_engine(chan->dev, NVOBJ_ENGINE_GR); + struct nvc0_graph_chan *grch = chan->engctx[NVOBJ_ENGINE_GR]; + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i = 0, gpc, tp, ret; + + ret = nouveau_gpuobj_new(dev, chan, 0x2000, 256, NVOBJ_FLAG_VM, + &grch->unk408004); + if (ret) + return ret; + + ret = nouveau_gpuobj_new(dev, chan, 0x8000, 256, NVOBJ_FLAG_VM, + &grch->unk40800c); + if (ret) + return ret; + + ret = nouveau_gpuobj_new(dev, chan, 384 * 1024, 4096, + NVOBJ_FLAG_VM | NVOBJ_FLAG_VM_USER, + &grch->unk418810); + if (ret) + return ret; + + ret = nouveau_gpuobj_new(dev, chan, 0x1000, 0, NVOBJ_FLAG_VM, + &grch->mmio); + if (ret) + return ret; + + + nv_wo32(grch->mmio, i++ * 4, 0x00408004); + nv_wo32(grch->mmio, i++ * 4, grch->unk408004->linst >> 8); + nv_wo32(grch->mmio, i++ * 4, 0x00408008); + nv_wo32(grch->mmio, i++ * 4, 0x80000018); + + nv_wo32(grch->mmio, i++ * 4, 0x0040800c); + nv_wo32(grch->mmio, i++ * 4, grch->unk40800c->linst >> 8); + nv_wo32(grch->mmio, i++ * 4, 0x00408010); + nv_wo32(grch->mmio, i++ * 4, 0x80000000); + + nv_wo32(grch->mmio, i++ * 4, 0x00418810); + nv_wo32(grch->mmio, i++ * 4, 0x80000000 | grch->unk418810->linst >> 12); + nv_wo32(grch->mmio, i++ * 4, 0x00419848); + nv_wo32(grch->mmio, i++ * 4, 0x10000000 | grch->unk418810->linst >> 12); + + nv_wo32(grch->mmio, i++ * 4, 0x00419004); + nv_wo32(grch->mmio, i++ * 4, grch->unk40800c->linst >> 8); + nv_wo32(grch->mmio, i++ * 4, 0x00419008); + nv_wo32(grch->mmio, i++ * 4, 0x00000000); + + nv_wo32(grch->mmio, i++ * 4, 0x00418808); + nv_wo32(grch->mmio, i++ * 4, grch->unk408004->linst >> 8); + nv_wo32(grch->mmio, i++ * 4, 0x0041880c); + nv_wo32(grch->mmio, i++ * 4, 0x80000018); + + if (dev_priv->chipset != 0xc1) { + u32 magic = 0x02180000; + nv_wo32(grch->mmio, i++ * 4, 0x00405830); + nv_wo32(grch->mmio, i++ * 4, magic); + for (gpc = 0; gpc < priv->gpc_nr; gpc++) { + for (tp = 0; tp < priv->tp_nr[gpc]; tp++) { + u32 reg = TP_UNIT(gpc, tp, 0x520); + nv_wo32(grch->mmio, i++ * 4, reg); + nv_wo32(grch->mmio, i++ * 4, magic); + magic += 0x0324; + } + } + } else { + u32 magic = 0x02180000; + nv_wo32(grch->mmio, i++ * 4, 0x00405830); + nv_wo32(grch->mmio, i++ * 4, magic | 0x0000218); + nv_wo32(grch->mmio, i++ * 4, 0x004064c4); + nv_wo32(grch->mmio, i++ * 4, 0x0086ffff); + for (gpc = 0; gpc < priv->gpc_nr; gpc++) { + for (tp = 0; tp < priv->tp_nr[gpc]; tp++) { + u32 reg = TP_UNIT(gpc, tp, 0x520); + nv_wo32(grch->mmio, i++ * 4, reg); + nv_wo32(grch->mmio, i++ * 4, (1 << 28) | magic); + magic += 0x0324; + } + for (tp = 0; tp < priv->tp_nr[gpc]; tp++) { + u32 reg = TP_UNIT(gpc, tp, 0x544); + nv_wo32(grch->mmio, i++ * 4, reg); + nv_wo32(grch->mmio, i++ * 4, magic); + magic += 0x0324; + } + } + } + + grch->mmio_nr = i / 2; + return 0; +} + +static int +nvc0_graph_context_new(struct nouveau_channel *chan, int engine) +{ + struct drm_device *dev = chan->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct nvc0_graph_priv *priv = nv_engine(dev, engine); + struct nvc0_graph_chan *grch; + struct nouveau_gpuobj *grctx; + int ret, i; + + grch = kzalloc(sizeof(*grch), GFP_KERNEL); + if (!grch) + return -ENOMEM; + chan->engctx[NVOBJ_ENGINE_GR] = grch; + + ret = nouveau_gpuobj_new(dev, chan, priv->grctx_size, 256, + NVOBJ_FLAG_VM | NVOBJ_FLAG_ZERO_ALLOC, + &grch->grctx); + if (ret) + goto error; + grctx = grch->grctx; + + ret = nvc0_graph_create_context_mmio_list(chan); + if (ret) + goto error; + + nv_wo32(chan->ramin, 0x0210, lower_32_bits(grctx->linst) | 4); + nv_wo32(chan->ramin, 0x0214, upper_32_bits(grctx->linst)); + pinstmem->flush(dev); + + if (!priv->grctx_vals) { + ret = nvc0_graph_construct_context(chan); + if (ret) + goto error; + } + + for (i = 0; i < priv->grctx_size; i += 4) + nv_wo32(grctx, i, priv->grctx_vals[i / 4]); + + if (!nouveau_ctxfw) { + nv_wo32(grctx, 0x00, grch->mmio_nr); + nv_wo32(grctx, 0x04, grch->mmio->linst >> 8); + } else { + nv_wo32(grctx, 0xf4, 0); + nv_wo32(grctx, 0xf8, 0); + nv_wo32(grctx, 0x10, grch->mmio_nr); + nv_wo32(grctx, 0x14, lower_32_bits(grch->mmio->linst)); + nv_wo32(grctx, 0x18, upper_32_bits(grch->mmio->linst)); + nv_wo32(grctx, 0x1c, 1); + nv_wo32(grctx, 0x20, 0); + nv_wo32(grctx, 0x28, 0); + nv_wo32(grctx, 0x2c, 0); + } + pinstmem->flush(dev); + return 0; + +error: + priv->base.context_del(chan, engine); + return ret; +} + +static void +nvc0_graph_context_del(struct nouveau_channel *chan, int engine) +{ + struct nvc0_graph_chan *grch = chan->engctx[engine]; + + nouveau_gpuobj_ref(NULL, &grch->mmio); + nouveau_gpuobj_ref(NULL, &grch->unk418810); + nouveau_gpuobj_ref(NULL, &grch->unk40800c); + nouveau_gpuobj_ref(NULL, &grch->unk408004); + nouveau_gpuobj_ref(NULL, &grch->grctx); + chan->engctx[engine] = NULL; +} + +static int +nvc0_graph_object_new(struct nouveau_channel *chan, int engine, + u32 handle, u16 class) +{ + return 0; +} + +static int +nvc0_graph_fini(struct drm_device *dev, int engine, bool suspend) +{ + return 0; +} + +static void +nvc0_graph_init_obj418880(struct drm_device *dev) +{ + struct nvc0_graph_priv *priv = nv_engine(dev, NVOBJ_ENGINE_GR); + int i; + + nv_wr32(dev, GPC_BCAST(0x0880), 0x00000000); + nv_wr32(dev, GPC_BCAST(0x08a4), 0x00000000); + for (i = 0; i < 4; i++) + nv_wr32(dev, GPC_BCAST(0x0888) + (i * 4), 0x00000000); + nv_wr32(dev, GPC_BCAST(0x08b4), priv->unk4188b4->vinst >> 8); + nv_wr32(dev, GPC_BCAST(0x08b8), priv->unk4188b8->vinst >> 8); +} + +static void +nvc0_graph_init_regs(struct drm_device *dev) +{ + nv_wr32(dev, 0x400080, 0x003083c2); + nv_wr32(dev, 0x400088, 0x00006fe7); + nv_wr32(dev, 0x40008c, 0x00000000); + nv_wr32(dev, 0x400090, 0x00000030); + nv_wr32(dev, 0x40013c, 0x013901f7); + nv_wr32(dev, 0x400140, 0x00000100); + nv_wr32(dev, 0x400144, 0x00000000); + nv_wr32(dev, 0x400148, 0x00000110); + nv_wr32(dev, 0x400138, 0x00000000); + nv_wr32(dev, 0x400130, 0x00000000); + nv_wr32(dev, 0x400134, 0x00000000); + nv_wr32(dev, 0x400124, 0x00000002); +} + +static void +nvc0_graph_init_gpc_0(struct drm_device *dev) +{ + struct nvc0_graph_priv *priv = nv_engine(dev, NVOBJ_ENGINE_GR); + const u32 magicgpc918 = DIV_ROUND_UP(0x00800000, priv->tp_total); + u32 data[TP_MAX / 8]; + u8 tpnr[GPC_MAX]; + int i, gpc, tpc; + + nv_wr32(dev, TP_UNIT(0, 0, 0x5c), 1); /* affects TFB offset queries */ + + /* + * TP ROP UNKVAL(magic_not_rop_nr) + * 450: 4/0/0/0 2 3 + * 460: 3/4/0/0 4 1 + * 465: 3/4/4/0 4 7 + * 470: 3/3/4/4 5 5 + * 480: 3/4/4/4 6 6 + */ + + memset(data, 0x00, sizeof(data)); + memcpy(tpnr, priv->tp_nr, sizeof(priv->tp_nr)); + for (i = 0, gpc = -1; i < priv->tp_total; i++) { + do { + gpc = (gpc + 1) % priv->gpc_nr; + } while (!tpnr[gpc]); + tpc = priv->tp_nr[gpc] - tpnr[gpc]--; + + data[i / 8] |= tpc << ((i % 8) * 4); + } + + nv_wr32(dev, GPC_BCAST(0x0980), data[0]); + nv_wr32(dev, GPC_BCAST(0x0984), data[1]); + nv_wr32(dev, GPC_BCAST(0x0988), data[2]); + nv_wr32(dev, GPC_BCAST(0x098c), data[3]); + + for (gpc = 0; gpc < priv->gpc_nr; gpc++) { + nv_wr32(dev, GPC_UNIT(gpc, 0x0914), priv->magic_not_rop_nr << 8 | + priv->tp_nr[gpc]); + nv_wr32(dev, GPC_UNIT(gpc, 0x0910), 0x00040000 | priv->tp_total); + nv_wr32(dev, GPC_UNIT(gpc, 0x0918), magicgpc918); + } + + nv_wr32(dev, GPC_BCAST(0x1bd4), magicgpc918); + nv_wr32(dev, GPC_BCAST(0x08ac), nv_rd32(dev, 0x100800)); +} + +static void +nvc0_graph_init_units(struct drm_device *dev) +{ + nv_wr32(dev, 0x409c24, 0x000f0000); + nv_wr32(dev, 0x404000, 0xc0000000); /* DISPATCH */ + nv_wr32(dev, 0x404600, 0xc0000000); /* M2MF */ + nv_wr32(dev, 0x408030, 0xc0000000); + nv_wr32(dev, 0x40601c, 0xc0000000); + nv_wr32(dev, 0x404490, 0xc0000000); /* MACRO */ + nv_wr32(dev, 0x406018, 0xc0000000); + nv_wr32(dev, 0x405840, 0xc0000000); + nv_wr32(dev, 0x405844, 0x00ffffff); + nv_mask(dev, 0x419cc0, 0x00000008, 0x00000008); + nv_mask(dev, 0x419eb4, 0x00001000, 0x00001000); +} + +static void +nvc0_graph_init_gpc_1(struct drm_device *dev) +{ + struct nvc0_graph_priv *priv = nv_engine(dev, NVOBJ_ENGINE_GR); + int gpc, tp; + + for (gpc = 0; gpc < priv->gpc_nr; gpc++) { + nv_wr32(dev, GPC_UNIT(gpc, 0x0420), 0xc0000000); + nv_wr32(dev, GPC_UNIT(gpc, 0x0900), 0xc0000000); + nv_wr32(dev, GPC_UNIT(gpc, 0x1028), 0xc0000000); + nv_wr32(dev, GPC_UNIT(gpc, 0x0824), 0xc0000000); + for (tp = 0; tp < priv->tp_nr[gpc]; tp++) { + nv_wr32(dev, TP_UNIT(gpc, tp, 0x508), 0xffffffff); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x50c), 0xffffffff); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x224), 0xc0000000); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x48c), 0xc0000000); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x084), 0xc0000000); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x644), 0x001ffffe); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x64c), 0x0000000f); + } + nv_wr32(dev, GPC_UNIT(gpc, 0x2c90), 0xffffffff); + nv_wr32(dev, GPC_UNIT(gpc, 0x2c94), 0xffffffff); + } +} + +static void +nvc0_graph_init_rop(struct drm_device *dev) +{ + struct nvc0_graph_priv *priv = nv_engine(dev, NVOBJ_ENGINE_GR); + int rop; + + for (rop = 0; rop < priv->rop_nr; rop++) { + nv_wr32(dev, ROP_UNIT(rop, 0x144), 0xc0000000); + nv_wr32(dev, ROP_UNIT(rop, 0x070), 0xc0000000); + nv_wr32(dev, ROP_UNIT(rop, 0x204), 0xffffffff); + nv_wr32(dev, ROP_UNIT(rop, 0x208), 0xffffffff); + } +} + +static void +nvc0_graph_init_fuc(struct drm_device *dev, u32 fuc_base, + struct nvc0_graph_fuc *code, struct nvc0_graph_fuc *data) +{ + int i; + + nv_wr32(dev, fuc_base + 0x01c0, 0x01000000); + for (i = 0; i < data->size / 4; i++) + nv_wr32(dev, fuc_base + 0x01c4, data->data[i]); + + nv_wr32(dev, fuc_base + 0x0180, 0x01000000); + for (i = 0; i < code->size / 4; i++) { + if ((i & 0x3f) == 0) + nv_wr32(dev, fuc_base + 0x0188, i >> 6); + nv_wr32(dev, fuc_base + 0x0184, code->data[i]); + } +} + +static int +nvc0_graph_init_ctxctl(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvc0_graph_priv *priv = nv_engine(dev, NVOBJ_ENGINE_GR); + u32 r000260; + int i; + + if (!nouveau_ctxfw) { + /* load HUB microcode */ + r000260 = nv_mask(dev, 0x000260, 0x00000001, 0x00000000); + nv_wr32(dev, 0x4091c0, 0x01000000); + for (i = 0; i < sizeof(nvc0_grhub_data) / 4; i++) + nv_wr32(dev, 0x4091c4, nvc0_grhub_data[i]); + + nv_wr32(dev, 0x409180, 0x01000000); + for (i = 0; i < sizeof(nvc0_grhub_code) / 4; i++) { + if ((i & 0x3f) == 0) + nv_wr32(dev, 0x409188, i >> 6); + nv_wr32(dev, 0x409184, nvc0_grhub_code[i]); + } + + /* load GPC microcode */ + nv_wr32(dev, 0x41a1c0, 0x01000000); + for (i = 0; i < sizeof(nvc0_grgpc_data) / 4; i++) + nv_wr32(dev, 0x41a1c4, nvc0_grgpc_data[i]); + + nv_wr32(dev, 0x41a180, 0x01000000); + for (i = 0; i < sizeof(nvc0_grgpc_code) / 4; i++) { + if ((i & 0x3f) == 0) + nv_wr32(dev, 0x41a188, i >> 6); + nv_wr32(dev, 0x41a184, nvc0_grgpc_code[i]); + } + nv_wr32(dev, 0x000260, r000260); + + /* start HUB ucode running, it'll init the GPCs */ + nv_wr32(dev, 0x409800, dev_priv->chipset); + nv_wr32(dev, 0x40910c, 0x00000000); + nv_wr32(dev, 0x409100, 0x00000002); + if (!nv_wait(dev, 0x409800, 0x80000000, 0x80000000)) { + NV_ERROR(dev, "PGRAPH: HUB_INIT timed out\n"); + nvc0_graph_ctxctl_debug(dev); + return -EBUSY; + } + + priv->grctx_size = nv_rd32(dev, 0x409804); + return 0; + } + + /* load fuc microcode */ + r000260 = nv_mask(dev, 0x000260, 0x00000001, 0x00000000); + nvc0_graph_init_fuc(dev, 0x409000, &priv->fuc409c, &priv->fuc409d); + nvc0_graph_init_fuc(dev, 0x41a000, &priv->fuc41ac, &priv->fuc41ad); + nv_wr32(dev, 0x000260, r000260); + + /* start both of them running */ + nv_wr32(dev, 0x409840, 0xffffffff); + nv_wr32(dev, 0x41a10c, 0x00000000); + nv_wr32(dev, 0x40910c, 0x00000000); + nv_wr32(dev, 0x41a100, 0x00000002); + nv_wr32(dev, 0x409100, 0x00000002); + if (!nv_wait(dev, 0x409800, 0x00000001, 0x00000001)) + NV_INFO(dev, "0x409800 wait failed\n"); + + nv_wr32(dev, 0x409840, 0xffffffff); + nv_wr32(dev, 0x409500, 0x7fffffff); + nv_wr32(dev, 0x409504, 0x00000021); + + nv_wr32(dev, 0x409840, 0xffffffff); + nv_wr32(dev, 0x409500, 0x00000000); + nv_wr32(dev, 0x409504, 0x00000010); + if (!nv_wait_ne(dev, 0x409800, 0xffffffff, 0x00000000)) { + NV_ERROR(dev, "fuc09 req 0x10 timeout\n"); + return -EBUSY; + } + priv->grctx_size = nv_rd32(dev, 0x409800); + + nv_wr32(dev, 0x409840, 0xffffffff); + nv_wr32(dev, 0x409500, 0x00000000); + nv_wr32(dev, 0x409504, 0x00000016); + if (!nv_wait_ne(dev, 0x409800, 0xffffffff, 0x00000000)) { + NV_ERROR(dev, "fuc09 req 0x16 timeout\n"); + return -EBUSY; + } + + nv_wr32(dev, 0x409840, 0xffffffff); + nv_wr32(dev, 0x409500, 0x00000000); + nv_wr32(dev, 0x409504, 0x00000025); + if (!nv_wait_ne(dev, 0x409800, 0xffffffff, 0x00000000)) { + NV_ERROR(dev, "fuc09 req 0x25 timeout\n"); + return -EBUSY; + } + + return 0; +} + +static int +nvc0_graph_init(struct drm_device *dev, int engine) +{ + int ret; + + nv_mask(dev, 0x000200, 0x18001000, 0x00000000); + nv_mask(dev, 0x000200, 0x18001000, 0x18001000); + + nvc0_graph_init_obj418880(dev); + nvc0_graph_init_regs(dev); + /*nvc0_graph_init_unitplemented_magics(dev);*/ + nvc0_graph_init_gpc_0(dev); + /*nvc0_graph_init_unitplemented_c242(dev);*/ + + nv_wr32(dev, 0x400500, 0x00010001); + nv_wr32(dev, 0x400100, 0xffffffff); + nv_wr32(dev, 0x40013c, 0xffffffff); + + nvc0_graph_init_units(dev); + nvc0_graph_init_gpc_1(dev); + nvc0_graph_init_rop(dev); + + nv_wr32(dev, 0x400108, 0xffffffff); + nv_wr32(dev, 0x400138, 0xffffffff); + nv_wr32(dev, 0x400118, 0xffffffff); + nv_wr32(dev, 0x400130, 0xffffffff); + nv_wr32(dev, 0x40011c, 0xffffffff); + nv_wr32(dev, 0x400134, 0xffffffff); + nv_wr32(dev, 0x400054, 0x34ce3464); + + ret = nvc0_graph_init_ctxctl(dev); + if (ret) + return ret; + + return 0; +} + +int +nvc0_graph_isr_chid(struct drm_device *dev, u64 inst) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_channel *chan; + unsigned long flags; + int i; + + spin_lock_irqsave(&dev_priv->channels.lock, flags); + for (i = 0; i < dev_priv->engine.fifo.channels; i++) { + chan = dev_priv->channels.ptr[i]; + if (!chan || !chan->ramin) + continue; + + if (inst == chan->ramin->vinst) + break; + } + spin_unlock_irqrestore(&dev_priv->channels.lock, flags); + return i; +} + +static void +nvc0_graph_ctxctl_isr(struct drm_device *dev) +{ + u32 ustat = nv_rd32(dev, 0x409c18); + + if (ustat & 0x00000001) + NV_INFO(dev, "PGRAPH: CTXCTRL ucode error\n"); + if (ustat & 0x00080000) + NV_INFO(dev, "PGRAPH: CTXCTRL watchdog timeout\n"); + if (ustat & ~0x00080001) + NV_INFO(dev, "PGRAPH: CTXCTRL 0x%08x\n", ustat); + + nvc0_graph_ctxctl_debug(dev); + nv_wr32(dev, 0x409c20, ustat); +} + +static void +nvc0_graph_isr(struct drm_device *dev) +{ + u64 inst = (u64)(nv_rd32(dev, 0x409b00) & 0x0fffffff) << 12; + u32 chid = nvc0_graph_isr_chid(dev, inst); + u32 stat = nv_rd32(dev, 0x400100); + u32 addr = nv_rd32(dev, 0x400704); + u32 mthd = (addr & 0x00003ffc); + u32 subc = (addr & 0x00070000) >> 16; + u32 data = nv_rd32(dev, 0x400708); + u32 code = nv_rd32(dev, 0x400110); + u32 class = nv_rd32(dev, 0x404200 + (subc * 4)); + + if (stat & 0x00000010) { + if (nouveau_gpuobj_mthd_call2(dev, chid, class, mthd, data)) { + NV_INFO(dev, "PGRAPH: ILLEGAL_MTHD ch %d [0x%010llx] " + "subc %d class 0x%04x mthd 0x%04x " + "data 0x%08x\n", + chid, inst, subc, class, mthd, data); + } + nv_wr32(dev, 0x400100, 0x00000010); + stat &= ~0x00000010; + } + + if (stat & 0x00000020) { + NV_INFO(dev, "PGRAPH: ILLEGAL_CLASS ch %d [0x%010llx] subc %d " + "class 0x%04x mthd 0x%04x data 0x%08x\n", + chid, inst, subc, class, mthd, data); + nv_wr32(dev, 0x400100, 0x00000020); + stat &= ~0x00000020; + } + + if (stat & 0x00100000) { + NV_INFO(dev, "PGRAPH: DATA_ERROR ["); + nouveau_enum_print(nv50_data_error_names, code); + printk("] ch %d [0x%010llx] subc %d class 0x%04x " + "mthd 0x%04x data 0x%08x\n", + chid, inst, subc, class, mthd, data); + nv_wr32(dev, 0x400100, 0x00100000); + stat &= ~0x00100000; + } + + if (stat & 0x00200000) { + u32 trap = nv_rd32(dev, 0x400108); + NV_INFO(dev, "PGRAPH: TRAP ch %d status 0x%08x\n", chid, trap); + nv_wr32(dev, 0x400108, trap); + nv_wr32(dev, 0x400100, 0x00200000); + stat &= ~0x00200000; + } + + if (stat & 0x00080000) { + nvc0_graph_ctxctl_isr(dev); + nv_wr32(dev, 0x400100, 0x00080000); + stat &= ~0x00080000; + } + + if (stat) { + NV_INFO(dev, "PGRAPH: unknown stat 0x%08x\n", stat); + nv_wr32(dev, 0x400100, stat); + } + + nv_wr32(dev, 0x400500, 0x00010001); +} + +static int +nvc0_graph_create_fw(struct drm_device *dev, const char *fwname, + struct nvc0_graph_fuc *fuc) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + const struct firmware *fw; + char f[32]; + int ret; + + snprintf(f, sizeof(f), "nouveau/nv%02x_%s", dev_priv->chipset, fwname); + ret = request_firmware(&fw, f, &dev->pdev->dev); + if (ret) { + snprintf(f, sizeof(f), "nouveau/%s", fwname); + ret = request_firmware(&fw, f, &dev->pdev->dev); + if (ret) { + NV_ERROR(dev, "failed to load %s\n", fwname); + return ret; + } + } + + fuc->size = fw->size; + fuc->data = kmemdup(fw->data, fuc->size, GFP_KERNEL); + release_firmware(fw); + return (fuc->data != NULL) ? 0 : -ENOMEM; +} + +static void +nvc0_graph_destroy_fw(struct nvc0_graph_fuc *fuc) +{ + if (fuc->data) { + kfree(fuc->data); + fuc->data = NULL; + } +} + +static void +nvc0_graph_destroy(struct drm_device *dev, int engine) +{ + struct nvc0_graph_priv *priv = nv_engine(dev, engine); + + if (nouveau_ctxfw) { + nvc0_graph_destroy_fw(&priv->fuc409c); + nvc0_graph_destroy_fw(&priv->fuc409d); + nvc0_graph_destroy_fw(&priv->fuc41ac); + nvc0_graph_destroy_fw(&priv->fuc41ad); + } + + nouveau_irq_unregister(dev, 12); + + nouveau_gpuobj_ref(NULL, &priv->unk4188b8); + nouveau_gpuobj_ref(NULL, &priv->unk4188b4); + + if (priv->grctx_vals) + kfree(priv->grctx_vals); + + NVOBJ_ENGINE_DEL(dev, GR); + kfree(priv); +} + +int +nvc0_graph_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvc0_graph_priv *priv; + int ret, gpc, i; + u32 fermi; + + fermi = nvc0_graph_class(dev); + if (!fermi) { + NV_ERROR(dev, "PGRAPH: unsupported chipset, please report!\n"); + return 0; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base.destroy = nvc0_graph_destroy; + priv->base.init = nvc0_graph_init; + priv->base.fini = nvc0_graph_fini; + priv->base.context_new = nvc0_graph_context_new; + priv->base.context_del = nvc0_graph_context_del; + priv->base.object_new = nvc0_graph_object_new; + + NVOBJ_ENGINE_ADD(dev, GR, &priv->base); + nouveau_irq_register(dev, 12, nvc0_graph_isr); + + if (nouveau_ctxfw) { + NV_INFO(dev, "PGRAPH: using external firmware\n"); + if (nvc0_graph_create_fw(dev, "fuc409c", &priv->fuc409c) || + nvc0_graph_create_fw(dev, "fuc409d", &priv->fuc409d) || + nvc0_graph_create_fw(dev, "fuc41ac", &priv->fuc41ac) || + nvc0_graph_create_fw(dev, "fuc41ad", &priv->fuc41ad)) { + ret = 0; + goto error; + } + } + + ret = nouveau_gpuobj_new(dev, NULL, 0x1000, 256, 0, &priv->unk4188b4); + if (ret) + goto error; + + ret = nouveau_gpuobj_new(dev, NULL, 0x1000, 256, 0, &priv->unk4188b8); + if (ret) + goto error; + + for (i = 0; i < 0x1000; i += 4) { + nv_wo32(priv->unk4188b4, i, 0x00000010); + nv_wo32(priv->unk4188b8, i, 0x00000010); + } + + priv->gpc_nr = nv_rd32(dev, 0x409604) & 0x0000001f; + priv->rop_nr = (nv_rd32(dev, 0x409604) & 0x001f0000) >> 16; + for (gpc = 0; gpc < priv->gpc_nr; gpc++) { + priv->tp_nr[gpc] = nv_rd32(dev, GPC_UNIT(gpc, 0x2608)); + priv->tp_total += priv->tp_nr[gpc]; + } + + /*XXX: these need figuring out... */ + switch (dev_priv->chipset) { + case 0xc0: + if (priv->tp_total == 11) { /* 465, 3/4/4/0, 4 */ + priv->magic_not_rop_nr = 0x07; + } else + if (priv->tp_total == 14) { /* 470, 3/3/4/4, 5 */ + priv->magic_not_rop_nr = 0x05; + } else + if (priv->tp_total == 15) { /* 480, 3/4/4/4, 6 */ + priv->magic_not_rop_nr = 0x06; + } + break; + case 0xc3: /* 450, 4/0/0/0, 2 */ + priv->magic_not_rop_nr = 0x03; + break; + case 0xc4: /* 460, 3/4/0/0, 4 */ + priv->magic_not_rop_nr = 0x01; + break; + case 0xc1: /* 2/0/0/0, 1 */ + priv->magic_not_rop_nr = 0x01; + break; + case 0xc8: /* 4/4/3/4, 5 */ + priv->magic_not_rop_nr = 0x06; + break; + case 0xce: /* 4/4/0/0, 4 */ + priv->magic_not_rop_nr = 0x03; + break; + case 0xcf: /* 4/0/0/0, 3 */ + priv->magic_not_rop_nr = 0x03; + break; + case 0xd9: /* 1/0/0/0, 1 */ + priv->magic_not_rop_nr = 0x01; + break; + } + + if (!priv->magic_not_rop_nr) { + NV_ERROR(dev, "PGRAPH: unknown config: %d/%d/%d/%d, %d\n", + priv->tp_nr[0], priv->tp_nr[1], priv->tp_nr[2], + priv->tp_nr[3], priv->rop_nr); + priv->magic_not_rop_nr = 0x00; + } + + NVOBJ_CLASS(dev, 0x902d, GR); /* 2D */ + NVOBJ_CLASS(dev, 0x9039, GR); /* M2MF */ + NVOBJ_CLASS(dev, 0x9097, GR); /* 3D */ + if (fermi >= 0x9197) + NVOBJ_CLASS(dev, 0x9197, GR); /* 3D (NVC1-) */ + if (fermi >= 0x9297) + NVOBJ_CLASS(dev, 0x9297, GR); /* 3D (NVC8-) */ + NVOBJ_CLASS(dev, 0x90c0, GR); /* COMPUTE */ + return 0; + +error: + nvc0_graph_destroy(dev, NVOBJ_ENGINE_GR); + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nvc0_graph.fuc b/drivers/gpu/drm/nouveau/nvc0_graph.fuc new file mode 100644 index 00000000..e6b22884 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_graph.fuc @@ -0,0 +1,400 @@ +/* fuc microcode util functions for nvc0 PGRAPH + * + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +define(`mmctx_data', `.b32 eval((($2 - 1) << 26) | $1)') +define(`queue_init', `.skip eval((2 * 4) + ((8 * 4) * 2))') + +ifdef(`include_code', ` +// Error codes +define(`E_BAD_COMMAND', 0x01) +define(`E_CMD_OVERFLOW', 0x02) + +// Util macros to help with debugging ucode hangs etc +define(`T_WAIT', 0) +define(`T_MMCTX', 1) +define(`T_STRWAIT', 2) +define(`T_STRINIT', 3) +define(`T_AUTO', 4) +define(`T_CHAN', 5) +define(`T_LOAD', 6) +define(`T_SAVE', 7) +define(`T_LCHAN', 8) +define(`T_LCTXH', 9) + +define(`trace_set', ` + mov $r8 0x83c + shl b32 $r8 6 + clear b32 $r9 + bset $r9 $1 + iowr I[$r8 + 0x000] $r9 // CC_SCRATCH[7] +') + +define(`trace_clr', ` + mov $r8 0x85c + shl b32 $r8 6 + clear b32 $r9 + bset $r9 $1 + iowr I[$r8 + 0x000] $r9 // CC_SCRATCH[7] +') + +// queue_put - add request to queue +// +// In : $r13 queue pointer +// $r14 command +// $r15 data +// +queue_put: + // make sure we have space.. + ld b32 $r8 D[$r13 + 0x0] // GET + ld b32 $r9 D[$r13 + 0x4] // PUT + xor $r8 8 + cmpu b32 $r8 $r9 + bra ne #queue_put_next + mov $r15 E_CMD_OVERFLOW + call #error + ret + + // store cmd/data on queue + queue_put_next: + and $r8 $r9 7 + shl b32 $r8 3 + add b32 $r8 $r13 + add b32 $r8 8 + st b32 D[$r8 + 0x0] $r14 + st b32 D[$r8 + 0x4] $r15 + + // update PUT + add b32 $r9 1 + and $r9 0xf + st b32 D[$r13 + 0x4] $r9 + ret + +// queue_get - fetch request from queue +// +// In : $r13 queue pointer +// +// Out: $p1 clear on success (data available) +// $r14 command +// $r15 data +// +queue_get: + bset $flags $p1 + ld b32 $r8 D[$r13 + 0x0] // GET + ld b32 $r9 D[$r13 + 0x4] // PUT + cmpu b32 $r8 $r9 + bra e #queue_get_done + // fetch first cmd/data pair + and $r9 $r8 7 + shl b32 $r9 3 + add b32 $r9 $r13 + add b32 $r9 8 + ld b32 $r14 D[$r9 + 0x0] + ld b32 $r15 D[$r9 + 0x4] + + // update GET + add b32 $r8 1 + and $r8 0xf + st b32 D[$r13 + 0x0] $r8 + bclr $flags $p1 +queue_get_done: + ret + +// nv_rd32 - read 32-bit value from nv register +// +// In : $r14 register +// Out: $r15 value +// +nv_rd32: + mov $r11 0x728 + shl b32 $r11 6 + mov b32 $r12 $r14 + bset $r12 31 // MMIO_CTRL_PENDING + iowr I[$r11 + 0x000] $r12 // MMIO_CTRL + nv_rd32_wait: + iord $r12 I[$r11 + 0x000] + xbit $r12 $r12 31 + bra ne #nv_rd32_wait + mov $r10 6 // DONE_MMIO_RD + call #wait_doneo + iord $r15 I[$r11 + 0x100] // MMIO_RDVAL + ret + +// nv_wr32 - write 32-bit value to nv register +// +// In : $r14 register +// $r15 value +// +nv_wr32: + mov $r11 0x728 + shl b32 $r11 6 + iowr I[$r11 + 0x200] $r15 // MMIO_WRVAL + mov b32 $r12 $r14 + bset $r12 31 // MMIO_CTRL_PENDING + bset $r12 30 // MMIO_CTRL_WRITE + iowr I[$r11 + 0x000] $r12 // MMIO_CTRL + nv_wr32_wait: + iord $r12 I[$r11 + 0x000] + xbit $r12 $r12 31 + bra ne #nv_wr32_wait + ret + +// (re)set watchdog timer +// +// In : $r15 timeout +// +watchdog_reset: + mov $r8 0x430 + shl b32 $r8 6 + bset $r15 31 + iowr I[$r8 + 0x000] $r15 + ret + +// clear watchdog timer +watchdog_clear: + mov $r8 0x430 + shl b32 $r8 6 + iowr I[$r8 + 0x000] $r0 + ret + +// wait_done{z,o} - wait on FUC_DONE bit to become clear/set +// +// In : $r10 bit to wait on +// +define(`wait_done', ` +$1: + trace_set(T_WAIT); + mov $r8 0x818 + shl b32 $r8 6 + iowr I[$r8 + 0x000] $r10 // CC_SCRATCH[6] = wait bit + wait_done_$1: + mov $r8 0x400 + shl b32 $r8 6 + iord $r8 I[$r8 + 0x000] // DONE + xbit $r8 $r8 $r10 + bra $2 #wait_done_$1 + trace_clr(T_WAIT) + ret +') +wait_done(wait_donez, ne) +wait_done(wait_doneo, e) + +// mmctx_size - determine size of a mmio list transfer +// +// In : $r14 mmio list head +// $r15 mmio list tail +// Out: $r15 transfer size (in bytes) +// +mmctx_size: + clear b32 $r9 + nv_mmctx_size_loop: + ld b32 $r8 D[$r14] + shr b32 $r8 26 + add b32 $r8 1 + shl b32 $r8 2 + add b32 $r9 $r8 + add b32 $r14 4 + cmpu b32 $r14 $r15 + bra ne #nv_mmctx_size_loop + mov b32 $r15 $r9 + ret + +// mmctx_xfer - execute a list of mmio transfers +// +// In : $r10 flags +// bit 0: direction (0 = save, 1 = load) +// bit 1: set if first transfer +// bit 2: set if last transfer +// $r11 base +// $r12 mmio list head +// $r13 mmio list tail +// $r14 multi_stride +// $r15 multi_mask +// +mmctx_xfer: + trace_set(T_MMCTX) + mov $r8 0x710 + shl b32 $r8 6 + clear b32 $r9 + or $r11 $r11 + bra e #mmctx_base_disabled + iowr I[$r8 + 0x000] $r11 // MMCTX_BASE + bset $r9 0 // BASE_EN + mmctx_base_disabled: + or $r14 $r14 + bra e #mmctx_multi_disabled + iowr I[$r8 + 0x200] $r14 // MMCTX_MULTI_STRIDE + iowr I[$r8 + 0x300] $r15 // MMCTX_MULTI_MASK + bset $r9 1 // MULTI_EN + mmctx_multi_disabled: + add b32 $r8 0x100 + + xbit $r11 $r10 0 + shl b32 $r11 16 // DIR + bset $r11 12 // QLIMIT = 0x10 + xbit $r14 $r10 1 + shl b32 $r14 17 + or $r11 $r14 // START_TRIGGER + iowr I[$r8 + 0x000] $r11 // MMCTX_CTRL + + // loop over the mmio list, and send requests to the hw + mmctx_exec_loop: + // wait for space in mmctx queue + mmctx_wait_free: + iord $r14 I[$r8 + 0x000] // MMCTX_CTRL + and $r14 0x1f + bra e #mmctx_wait_free + + // queue up an entry + ld b32 $r14 D[$r12] + or $r14 $r9 + iowr I[$r8 + 0x300] $r14 + add b32 $r12 4 + cmpu b32 $r12 $r13 + bra ne #mmctx_exec_loop + + xbit $r11 $r10 2 + bra ne #mmctx_stop + // wait for queue to empty + mmctx_fini_wait: + iord $r11 I[$r8 + 0x000] // MMCTX_CTRL + and $r11 0x1f + cmpu b32 $r11 0x10 + bra ne #mmctx_fini_wait + mov $r10 2 // DONE_MMCTX + call #wait_donez + bra #mmctx_done + mmctx_stop: + xbit $r11 $r10 0 + shl b32 $r11 16 // DIR + bset $r11 12 // QLIMIT = 0x10 + bset $r11 18 // STOP_TRIGGER + iowr I[$r8 + 0x000] $r11 // MMCTX_CTRL + mmctx_stop_wait: + // wait for STOP_TRIGGER to clear + iord $r11 I[$r8 + 0x000] // MMCTX_CTRL + xbit $r11 $r11 18 + bra ne #mmctx_stop_wait + mmctx_done: + trace_clr(T_MMCTX) + ret + +// Wait for DONE_STRAND +// +strand_wait: + push $r10 + mov $r10 2 + call #wait_donez + pop $r10 + ret + +// unknown - call before issuing strand commands +// +strand_pre: + mov $r8 0x4afc + sethi $r8 0x20000 + mov $r9 0xc + iowr I[$r8] $r9 + call #strand_wait + ret + +// unknown - call after issuing strand commands +// +strand_post: + mov $r8 0x4afc + sethi $r8 0x20000 + mov $r9 0xd + iowr I[$r8] $r9 + call #strand_wait + ret + +// Selects strand set?! +// +// In: $r14 id +// +strand_set: + mov $r10 0x4ffc + sethi $r10 0x20000 + sub b32 $r11 $r10 0x500 + mov $r12 0xf + iowr I[$r10 + 0x000] $r12 // 0x93c = 0xf + mov $r12 0xb + iowr I[$r11 + 0x000] $r12 // 0x928 = 0xb + call #strand_wait + iowr I[$r10 + 0x000] $r14 // 0x93c = <id> + mov $r12 0xa + iowr I[$r11 + 0x000] $r12 // 0x928 = 0xa + call #strand_wait + ret + +// Initialise strand context data +// +// In : $r15 context base +// Out: $r15 context size (in bytes) +// +// Strandset(?) 3 hardcoded currently +// +strand_ctx_init: + trace_set(T_STRINIT) + call #strand_pre + mov $r14 3 + call #strand_set + mov $r10 0x46fc + sethi $r10 0x20000 + add b32 $r11 $r10 0x400 + iowr I[$r10 + 0x100] $r0 // STRAND_FIRST_GENE = 0 + mov $r12 1 + iowr I[$r11 + 0x000] $r12 // STRAND_CMD = LATCH_FIRST_GENE + call #strand_wait + sub b32 $r12 $r0 1 + iowr I[$r10 + 0x000] $r12 // STRAND_GENE_CNT = 0xffffffff + mov $r12 2 + iowr I[$r11 + 0x000] $r12 // STRAND_CMD = LATCH_GENE_CNT + call #strand_wait + call #strand_post + + // read the size of each strand, poke the context offset of + // each into STRAND_{SAVE,LOAD}_SWBASE now, no need to worry + // about it later then. + mov $r8 0x880 + shl b32 $r8 6 + iord $r9 I[$r8 + 0x000] // STRANDS + add b32 $r8 0x2200 + shr b32 $r14 $r15 8 + ctx_init_strand_loop: + iowr I[$r8 + 0x000] $r14 // STRAND_SAVE_SWBASE + iowr I[$r8 + 0x100] $r14 // STRAND_LOAD_SWBASE + iord $r10 I[$r8 + 0x200] // STRAND_SIZE + shr b32 $r10 6 + add b32 $r10 1 + add b32 $r14 $r10 + add b32 $r8 4 + sub b32 $r9 1 + bra ne #ctx_init_strand_loop + + shl b32 $r14 8 + sub b32 $r15 $r14 $r15 + trace_clr(T_STRINIT) + ret +') diff --git a/drivers/gpu/drm/nouveau/nvc0_graph.h b/drivers/gpu/drm/nouveau/nvc0_graph.h new file mode 100644 index 00000000..91d44ea6 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_graph.h @@ -0,0 +1,97 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#ifndef __NVC0_GRAPH_H__ +#define __NVC0_GRAPH_H__ + +#define GPC_MAX 4 +#define TP_MAX 32 + +#define ROP_BCAST(r) (0x408800 + (r)) +#define ROP_UNIT(u, r) (0x410000 + (u) * 0x400 + (r)) +#define GPC_BCAST(r) (0x418000 + (r)) +#define GPC_UNIT(t, r) (0x500000 + (t) * 0x8000 + (r)) +#define TP_UNIT(t, m, r) (0x504000 + (t) * 0x8000 + (m) * 0x800 + (r)) + +struct nvc0_graph_fuc { + u32 *data; + u32 size; +}; + +struct nvc0_graph_priv { + struct nouveau_exec_engine base; + + struct nvc0_graph_fuc fuc409c; + struct nvc0_graph_fuc fuc409d; + struct nvc0_graph_fuc fuc41ac; + struct nvc0_graph_fuc fuc41ad; + + u8 gpc_nr; + u8 rop_nr; + u8 tp_nr[GPC_MAX]; + u8 tp_total; + + u32 grctx_size; + u32 *grctx_vals; + struct nouveau_gpuobj *unk4188b4; + struct nouveau_gpuobj *unk4188b8; + + u8 magic_not_rop_nr; +}; + +struct nvc0_graph_chan { + struct nouveau_gpuobj *grctx; + struct nouveau_gpuobj *unk408004; /* 0x418810 too */ + struct nouveau_gpuobj *unk40800c; /* 0x419004 too */ + struct nouveau_gpuobj *unk418810; /* 0x419848 too */ + struct nouveau_gpuobj *mmio; + int mmio_nr; +}; + +int nvc0_grctx_generate(struct nouveau_channel *); + +/* nvc0_graph.c uses this also to determine supported chipsets */ +static inline u32 +nvc0_graph_class(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + switch (dev_priv->chipset) { + case 0xc0: + case 0xc3: + case 0xc4: + case 0xce: /* guess, mmio trace shows only 0x9097 state */ + case 0xcf: /* guess, mmio trace shows only 0x9097 state */ + return 0x9097; + case 0xc1: + return 0x9197; + case 0xc8: + case 0xd9: + return 0x9297; + default: + return 0; + } +} + +#endif diff --git a/drivers/gpu/drm/nouveau/nvc0_grctx.c b/drivers/gpu/drm/nouveau/nvc0_grctx.c new file mode 100644 index 00000000..de77842b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_grctx.c @@ -0,0 +1,2878 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_mm.h" +#include "nvc0_graph.h" + +static void +nv_icmd(struct drm_device *dev, u32 icmd, u32 data) +{ + nv_wr32(dev, 0x400204, data); + nv_wr32(dev, 0x400200, icmd); + while (nv_rd32(dev, 0x400700) & 2) {} +} + +static void +nv_mthd(struct drm_device *dev, u32 class, u32 mthd, u32 data) +{ + nv_wr32(dev, 0x40448c, data); + nv_wr32(dev, 0x404488, 0x80000000 | (mthd << 14) | class); +} + +static void +nvc0_grctx_generate_9097(struct drm_device *dev) +{ + u32 fermi = nvc0_graph_class(dev); + u32 mthd; + + nv_mthd(dev, 0x9097, 0x0800, 0x00000000); + nv_mthd(dev, 0x9097, 0x0840, 0x00000000); + nv_mthd(dev, 0x9097, 0x0880, 0x00000000); + nv_mthd(dev, 0x9097, 0x08c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0900, 0x00000000); + nv_mthd(dev, 0x9097, 0x0940, 0x00000000); + nv_mthd(dev, 0x9097, 0x0980, 0x00000000); + nv_mthd(dev, 0x9097, 0x09c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0804, 0x00000000); + nv_mthd(dev, 0x9097, 0x0844, 0x00000000); + nv_mthd(dev, 0x9097, 0x0884, 0x00000000); + nv_mthd(dev, 0x9097, 0x08c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0904, 0x00000000); + nv_mthd(dev, 0x9097, 0x0944, 0x00000000); + nv_mthd(dev, 0x9097, 0x0984, 0x00000000); + nv_mthd(dev, 0x9097, 0x09c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0808, 0x00000400); + nv_mthd(dev, 0x9097, 0x0848, 0x00000400); + nv_mthd(dev, 0x9097, 0x0888, 0x00000400); + nv_mthd(dev, 0x9097, 0x08c8, 0x00000400); + nv_mthd(dev, 0x9097, 0x0908, 0x00000400); + nv_mthd(dev, 0x9097, 0x0948, 0x00000400); + nv_mthd(dev, 0x9097, 0x0988, 0x00000400); + nv_mthd(dev, 0x9097, 0x09c8, 0x00000400); + nv_mthd(dev, 0x9097, 0x080c, 0x00000300); + nv_mthd(dev, 0x9097, 0x084c, 0x00000300); + nv_mthd(dev, 0x9097, 0x088c, 0x00000300); + nv_mthd(dev, 0x9097, 0x08cc, 0x00000300); + nv_mthd(dev, 0x9097, 0x090c, 0x00000300); + nv_mthd(dev, 0x9097, 0x094c, 0x00000300); + nv_mthd(dev, 0x9097, 0x098c, 0x00000300); + nv_mthd(dev, 0x9097, 0x09cc, 0x00000300); + nv_mthd(dev, 0x9097, 0x0810, 0x000000cf); + nv_mthd(dev, 0x9097, 0x0850, 0x00000000); + nv_mthd(dev, 0x9097, 0x0890, 0x00000000); + nv_mthd(dev, 0x9097, 0x08d0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0910, 0x00000000); + nv_mthd(dev, 0x9097, 0x0950, 0x00000000); + nv_mthd(dev, 0x9097, 0x0990, 0x00000000); + nv_mthd(dev, 0x9097, 0x09d0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0814, 0x00000040); + nv_mthd(dev, 0x9097, 0x0854, 0x00000040); + nv_mthd(dev, 0x9097, 0x0894, 0x00000040); + nv_mthd(dev, 0x9097, 0x08d4, 0x00000040); + nv_mthd(dev, 0x9097, 0x0914, 0x00000040); + nv_mthd(dev, 0x9097, 0x0954, 0x00000040); + nv_mthd(dev, 0x9097, 0x0994, 0x00000040); + nv_mthd(dev, 0x9097, 0x09d4, 0x00000040); + nv_mthd(dev, 0x9097, 0x0818, 0x00000001); + nv_mthd(dev, 0x9097, 0x0858, 0x00000001); + nv_mthd(dev, 0x9097, 0x0898, 0x00000001); + nv_mthd(dev, 0x9097, 0x08d8, 0x00000001); + nv_mthd(dev, 0x9097, 0x0918, 0x00000001); + nv_mthd(dev, 0x9097, 0x0958, 0x00000001); + nv_mthd(dev, 0x9097, 0x0998, 0x00000001); + nv_mthd(dev, 0x9097, 0x09d8, 0x00000001); + nv_mthd(dev, 0x9097, 0x081c, 0x00000000); + nv_mthd(dev, 0x9097, 0x085c, 0x00000000); + nv_mthd(dev, 0x9097, 0x089c, 0x00000000); + nv_mthd(dev, 0x9097, 0x08dc, 0x00000000); + nv_mthd(dev, 0x9097, 0x091c, 0x00000000); + nv_mthd(dev, 0x9097, 0x095c, 0x00000000); + nv_mthd(dev, 0x9097, 0x099c, 0x00000000); + nv_mthd(dev, 0x9097, 0x09dc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0820, 0x00000000); + nv_mthd(dev, 0x9097, 0x0860, 0x00000000); + nv_mthd(dev, 0x9097, 0x08a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x08e0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0920, 0x00000000); + nv_mthd(dev, 0x9097, 0x0960, 0x00000000); + nv_mthd(dev, 0x9097, 0x09a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x09e0, 0x00000000); + nv_mthd(dev, 0x9097, 0x2700, 0x00000000); + nv_mthd(dev, 0x9097, 0x2720, 0x00000000); + nv_mthd(dev, 0x9097, 0x2740, 0x00000000); + nv_mthd(dev, 0x9097, 0x2760, 0x00000000); + nv_mthd(dev, 0x9097, 0x2780, 0x00000000); + nv_mthd(dev, 0x9097, 0x27a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x27c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x27e0, 0x00000000); + nv_mthd(dev, 0x9097, 0x2704, 0x00000000); + nv_mthd(dev, 0x9097, 0x2724, 0x00000000); + nv_mthd(dev, 0x9097, 0x2744, 0x00000000); + nv_mthd(dev, 0x9097, 0x2764, 0x00000000); + nv_mthd(dev, 0x9097, 0x2784, 0x00000000); + nv_mthd(dev, 0x9097, 0x27a4, 0x00000000); + nv_mthd(dev, 0x9097, 0x27c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x27e4, 0x00000000); + nv_mthd(dev, 0x9097, 0x2708, 0x00000000); + nv_mthd(dev, 0x9097, 0x2728, 0x00000000); + nv_mthd(dev, 0x9097, 0x2748, 0x00000000); + nv_mthd(dev, 0x9097, 0x2768, 0x00000000); + nv_mthd(dev, 0x9097, 0x2788, 0x00000000); + nv_mthd(dev, 0x9097, 0x27a8, 0x00000000); + nv_mthd(dev, 0x9097, 0x27c8, 0x00000000); + nv_mthd(dev, 0x9097, 0x27e8, 0x00000000); + nv_mthd(dev, 0x9097, 0x270c, 0x00000000); + nv_mthd(dev, 0x9097, 0x272c, 0x00000000); + nv_mthd(dev, 0x9097, 0x274c, 0x00000000); + nv_mthd(dev, 0x9097, 0x276c, 0x00000000); + nv_mthd(dev, 0x9097, 0x278c, 0x00000000); + nv_mthd(dev, 0x9097, 0x27ac, 0x00000000); + nv_mthd(dev, 0x9097, 0x27cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x27ec, 0x00000000); + nv_mthd(dev, 0x9097, 0x2710, 0x00014000); + nv_mthd(dev, 0x9097, 0x2730, 0x00014000); + nv_mthd(dev, 0x9097, 0x2750, 0x00014000); + nv_mthd(dev, 0x9097, 0x2770, 0x00014000); + nv_mthd(dev, 0x9097, 0x2790, 0x00014000); + nv_mthd(dev, 0x9097, 0x27b0, 0x00014000); + nv_mthd(dev, 0x9097, 0x27d0, 0x00014000); + nv_mthd(dev, 0x9097, 0x27f0, 0x00014000); + nv_mthd(dev, 0x9097, 0x2714, 0x00000040); + nv_mthd(dev, 0x9097, 0x2734, 0x00000040); + nv_mthd(dev, 0x9097, 0x2754, 0x00000040); + nv_mthd(dev, 0x9097, 0x2774, 0x00000040); + nv_mthd(dev, 0x9097, 0x2794, 0x00000040); + nv_mthd(dev, 0x9097, 0x27b4, 0x00000040); + nv_mthd(dev, 0x9097, 0x27d4, 0x00000040); + nv_mthd(dev, 0x9097, 0x27f4, 0x00000040); + nv_mthd(dev, 0x9097, 0x1c00, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c10, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c20, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c30, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c40, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c50, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c60, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c70, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c80, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c90, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ca0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cb0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cc0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cd0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ce0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cf0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c04, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c14, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c24, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c34, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c44, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c54, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c64, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c74, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c84, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c94, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ca4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cb4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cc4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cd4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ce4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cf4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c08, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c18, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c28, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c38, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c48, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c58, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c68, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c78, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c88, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c98, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ca8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cb8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cc8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cd8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ce8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cf8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c0c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c1c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c2c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c3c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c4c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c5c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c6c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c7c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c8c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1c9c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cac, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cbc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ccc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cdc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cec, 0x00000000); + nv_mthd(dev, 0x9097, 0x1cfc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d00, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d10, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d20, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d30, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d40, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d50, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d60, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d70, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d80, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d90, 0x00000000); + nv_mthd(dev, 0x9097, 0x1da0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1db0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dc0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dd0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1de0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1df0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d04, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d14, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d24, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d34, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d44, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d54, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d64, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d74, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d84, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d94, 0x00000000); + nv_mthd(dev, 0x9097, 0x1da4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1db4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dc4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dd4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1de4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1df4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d08, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d18, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d28, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d38, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d48, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d58, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d68, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d78, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d88, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d98, 0x00000000); + nv_mthd(dev, 0x9097, 0x1da8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1db8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dc8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dd8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1de8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1df8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d0c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d1c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d2c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d3c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d4c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d5c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d6c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d7c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d8c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1d9c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dac, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dbc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dcc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ddc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dec, 0x00000000); + nv_mthd(dev, 0x9097, 0x1dfc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f00, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f08, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f10, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f18, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f20, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f28, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f30, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f38, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f40, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f48, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f50, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f58, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f60, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f68, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f70, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f78, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f04, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f0c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f14, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f1c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f24, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f2c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f34, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f3c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f44, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f4c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f54, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f5c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f64, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f6c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f74, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f7c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f80, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f88, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f90, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f98, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fa0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fa8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fb0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fb8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fc0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fc8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fd0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fd8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fe0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fe8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ff0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ff8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f84, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f8c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f94, 0x00000000); + nv_mthd(dev, 0x9097, 0x1f9c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fa4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fac, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fb4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fbc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fc4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fcc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fd4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fdc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fe4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1fec, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ff4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1ffc, 0x00000000); + nv_mthd(dev, 0x9097, 0x2200, 0x00000022); + nv_mthd(dev, 0x9097, 0x2210, 0x00000022); + nv_mthd(dev, 0x9097, 0x2220, 0x00000022); + nv_mthd(dev, 0x9097, 0x2230, 0x00000022); + nv_mthd(dev, 0x9097, 0x2240, 0x00000022); + nv_mthd(dev, 0x9097, 0x2000, 0x00000000); + nv_mthd(dev, 0x9097, 0x2040, 0x00000011); + nv_mthd(dev, 0x9097, 0x2080, 0x00000020); + nv_mthd(dev, 0x9097, 0x20c0, 0x00000030); + nv_mthd(dev, 0x9097, 0x2100, 0x00000040); + nv_mthd(dev, 0x9097, 0x2140, 0x00000051); + nv_mthd(dev, 0x9097, 0x200c, 0x00000001); + nv_mthd(dev, 0x9097, 0x204c, 0x00000001); + nv_mthd(dev, 0x9097, 0x208c, 0x00000001); + nv_mthd(dev, 0x9097, 0x20cc, 0x00000001); + nv_mthd(dev, 0x9097, 0x210c, 0x00000001); + nv_mthd(dev, 0x9097, 0x214c, 0x00000001); + nv_mthd(dev, 0x9097, 0x2010, 0x00000000); + nv_mthd(dev, 0x9097, 0x2050, 0x00000000); + nv_mthd(dev, 0x9097, 0x2090, 0x00000001); + nv_mthd(dev, 0x9097, 0x20d0, 0x00000002); + nv_mthd(dev, 0x9097, 0x2110, 0x00000003); + nv_mthd(dev, 0x9097, 0x2150, 0x00000004); + nv_mthd(dev, 0x9097, 0x0380, 0x00000000); + nv_mthd(dev, 0x9097, 0x03a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x03c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x03e0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0384, 0x00000000); + nv_mthd(dev, 0x9097, 0x03a4, 0x00000000); + nv_mthd(dev, 0x9097, 0x03c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x03e4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0388, 0x00000000); + nv_mthd(dev, 0x9097, 0x03a8, 0x00000000); + nv_mthd(dev, 0x9097, 0x03c8, 0x00000000); + nv_mthd(dev, 0x9097, 0x03e8, 0x00000000); + nv_mthd(dev, 0x9097, 0x038c, 0x00000000); + nv_mthd(dev, 0x9097, 0x03ac, 0x00000000); + nv_mthd(dev, 0x9097, 0x03cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x03ec, 0x00000000); + nv_mthd(dev, 0x9097, 0x0700, 0x00000000); + nv_mthd(dev, 0x9097, 0x0710, 0x00000000); + nv_mthd(dev, 0x9097, 0x0720, 0x00000000); + nv_mthd(dev, 0x9097, 0x0730, 0x00000000); + nv_mthd(dev, 0x9097, 0x0704, 0x00000000); + nv_mthd(dev, 0x9097, 0x0714, 0x00000000); + nv_mthd(dev, 0x9097, 0x0724, 0x00000000); + nv_mthd(dev, 0x9097, 0x0734, 0x00000000); + nv_mthd(dev, 0x9097, 0x0708, 0x00000000); + nv_mthd(dev, 0x9097, 0x0718, 0x00000000); + nv_mthd(dev, 0x9097, 0x0728, 0x00000000); + nv_mthd(dev, 0x9097, 0x0738, 0x00000000); + nv_mthd(dev, 0x9097, 0x2800, 0x00000000); + nv_mthd(dev, 0x9097, 0x2804, 0x00000000); + nv_mthd(dev, 0x9097, 0x2808, 0x00000000); + nv_mthd(dev, 0x9097, 0x280c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2810, 0x00000000); + nv_mthd(dev, 0x9097, 0x2814, 0x00000000); + nv_mthd(dev, 0x9097, 0x2818, 0x00000000); + nv_mthd(dev, 0x9097, 0x281c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2820, 0x00000000); + nv_mthd(dev, 0x9097, 0x2824, 0x00000000); + nv_mthd(dev, 0x9097, 0x2828, 0x00000000); + nv_mthd(dev, 0x9097, 0x282c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2830, 0x00000000); + nv_mthd(dev, 0x9097, 0x2834, 0x00000000); + nv_mthd(dev, 0x9097, 0x2838, 0x00000000); + nv_mthd(dev, 0x9097, 0x283c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2840, 0x00000000); + nv_mthd(dev, 0x9097, 0x2844, 0x00000000); + nv_mthd(dev, 0x9097, 0x2848, 0x00000000); + nv_mthd(dev, 0x9097, 0x284c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2850, 0x00000000); + nv_mthd(dev, 0x9097, 0x2854, 0x00000000); + nv_mthd(dev, 0x9097, 0x2858, 0x00000000); + nv_mthd(dev, 0x9097, 0x285c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2860, 0x00000000); + nv_mthd(dev, 0x9097, 0x2864, 0x00000000); + nv_mthd(dev, 0x9097, 0x2868, 0x00000000); + nv_mthd(dev, 0x9097, 0x286c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2870, 0x00000000); + nv_mthd(dev, 0x9097, 0x2874, 0x00000000); + nv_mthd(dev, 0x9097, 0x2878, 0x00000000); + nv_mthd(dev, 0x9097, 0x287c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2880, 0x00000000); + nv_mthd(dev, 0x9097, 0x2884, 0x00000000); + nv_mthd(dev, 0x9097, 0x2888, 0x00000000); + nv_mthd(dev, 0x9097, 0x288c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2890, 0x00000000); + nv_mthd(dev, 0x9097, 0x2894, 0x00000000); + nv_mthd(dev, 0x9097, 0x2898, 0x00000000); + nv_mthd(dev, 0x9097, 0x289c, 0x00000000); + nv_mthd(dev, 0x9097, 0x28a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x28a4, 0x00000000); + nv_mthd(dev, 0x9097, 0x28a8, 0x00000000); + nv_mthd(dev, 0x9097, 0x28ac, 0x00000000); + nv_mthd(dev, 0x9097, 0x28b0, 0x00000000); + nv_mthd(dev, 0x9097, 0x28b4, 0x00000000); + nv_mthd(dev, 0x9097, 0x28b8, 0x00000000); + nv_mthd(dev, 0x9097, 0x28bc, 0x00000000); + nv_mthd(dev, 0x9097, 0x28c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x28c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x28c8, 0x00000000); + nv_mthd(dev, 0x9097, 0x28cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x28d0, 0x00000000); + nv_mthd(dev, 0x9097, 0x28d4, 0x00000000); + nv_mthd(dev, 0x9097, 0x28d8, 0x00000000); + nv_mthd(dev, 0x9097, 0x28dc, 0x00000000); + nv_mthd(dev, 0x9097, 0x28e0, 0x00000000); + nv_mthd(dev, 0x9097, 0x28e4, 0x00000000); + nv_mthd(dev, 0x9097, 0x28e8, 0x00000000); + nv_mthd(dev, 0x9097, 0x28ec, 0x00000000); + nv_mthd(dev, 0x9097, 0x28f0, 0x00000000); + nv_mthd(dev, 0x9097, 0x28f4, 0x00000000); + nv_mthd(dev, 0x9097, 0x28f8, 0x00000000); + nv_mthd(dev, 0x9097, 0x28fc, 0x00000000); + nv_mthd(dev, 0x9097, 0x2900, 0x00000000); + nv_mthd(dev, 0x9097, 0x2904, 0x00000000); + nv_mthd(dev, 0x9097, 0x2908, 0x00000000); + nv_mthd(dev, 0x9097, 0x290c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2910, 0x00000000); + nv_mthd(dev, 0x9097, 0x2914, 0x00000000); + nv_mthd(dev, 0x9097, 0x2918, 0x00000000); + nv_mthd(dev, 0x9097, 0x291c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2920, 0x00000000); + nv_mthd(dev, 0x9097, 0x2924, 0x00000000); + nv_mthd(dev, 0x9097, 0x2928, 0x00000000); + nv_mthd(dev, 0x9097, 0x292c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2930, 0x00000000); + nv_mthd(dev, 0x9097, 0x2934, 0x00000000); + nv_mthd(dev, 0x9097, 0x2938, 0x00000000); + nv_mthd(dev, 0x9097, 0x293c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2940, 0x00000000); + nv_mthd(dev, 0x9097, 0x2944, 0x00000000); + nv_mthd(dev, 0x9097, 0x2948, 0x00000000); + nv_mthd(dev, 0x9097, 0x294c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2950, 0x00000000); + nv_mthd(dev, 0x9097, 0x2954, 0x00000000); + nv_mthd(dev, 0x9097, 0x2958, 0x00000000); + nv_mthd(dev, 0x9097, 0x295c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2960, 0x00000000); + nv_mthd(dev, 0x9097, 0x2964, 0x00000000); + nv_mthd(dev, 0x9097, 0x2968, 0x00000000); + nv_mthd(dev, 0x9097, 0x296c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2970, 0x00000000); + nv_mthd(dev, 0x9097, 0x2974, 0x00000000); + nv_mthd(dev, 0x9097, 0x2978, 0x00000000); + nv_mthd(dev, 0x9097, 0x297c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2980, 0x00000000); + nv_mthd(dev, 0x9097, 0x2984, 0x00000000); + nv_mthd(dev, 0x9097, 0x2988, 0x00000000); + nv_mthd(dev, 0x9097, 0x298c, 0x00000000); + nv_mthd(dev, 0x9097, 0x2990, 0x00000000); + nv_mthd(dev, 0x9097, 0x2994, 0x00000000); + nv_mthd(dev, 0x9097, 0x2998, 0x00000000); + nv_mthd(dev, 0x9097, 0x299c, 0x00000000); + nv_mthd(dev, 0x9097, 0x29a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x29a4, 0x00000000); + nv_mthd(dev, 0x9097, 0x29a8, 0x00000000); + nv_mthd(dev, 0x9097, 0x29ac, 0x00000000); + nv_mthd(dev, 0x9097, 0x29b0, 0x00000000); + nv_mthd(dev, 0x9097, 0x29b4, 0x00000000); + nv_mthd(dev, 0x9097, 0x29b8, 0x00000000); + nv_mthd(dev, 0x9097, 0x29bc, 0x00000000); + nv_mthd(dev, 0x9097, 0x29c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x29c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x29c8, 0x00000000); + nv_mthd(dev, 0x9097, 0x29cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x29d0, 0x00000000); + nv_mthd(dev, 0x9097, 0x29d4, 0x00000000); + nv_mthd(dev, 0x9097, 0x29d8, 0x00000000); + nv_mthd(dev, 0x9097, 0x29dc, 0x00000000); + nv_mthd(dev, 0x9097, 0x29e0, 0x00000000); + nv_mthd(dev, 0x9097, 0x29e4, 0x00000000); + nv_mthd(dev, 0x9097, 0x29e8, 0x00000000); + nv_mthd(dev, 0x9097, 0x29ec, 0x00000000); + nv_mthd(dev, 0x9097, 0x29f0, 0x00000000); + nv_mthd(dev, 0x9097, 0x29f4, 0x00000000); + nv_mthd(dev, 0x9097, 0x29f8, 0x00000000); + nv_mthd(dev, 0x9097, 0x29fc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a00, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a20, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a40, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a60, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a80, 0x00000000); + nv_mthd(dev, 0x9097, 0x0aa0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ac0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ae0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b00, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b20, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b40, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b60, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b80, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ba0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bc0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0be0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a04, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a24, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a44, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a64, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a84, 0x00000000); + nv_mthd(dev, 0x9097, 0x0aa4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ac4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ae4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b04, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b24, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b44, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b64, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b84, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ba4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bc4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0be4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a08, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a28, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a48, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a68, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a88, 0x00000000); + nv_mthd(dev, 0x9097, 0x0aa8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ac8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ae8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b08, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b28, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b48, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b68, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b88, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ba8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bc8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0be8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a0c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a2c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a4c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a6c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a8c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0aac, 0x00000000); + nv_mthd(dev, 0x9097, 0x0acc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0aec, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b0c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b2c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b4c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b6c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b8c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bac, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bcc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bec, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a10, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a30, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a50, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a70, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a90, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ab0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ad0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0af0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b10, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b30, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b50, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b70, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b90, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bb0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bd0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bf0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a14, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a34, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a54, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a74, 0x00000000); + nv_mthd(dev, 0x9097, 0x0a94, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ab4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ad4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0af4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b14, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b34, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b54, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b74, 0x00000000); + nv_mthd(dev, 0x9097, 0x0b94, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bb4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bd4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0bf4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c00, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c10, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c20, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c30, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c40, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c50, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c60, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c70, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c80, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c90, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ca0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cb0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cc0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cd0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ce0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cf0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c04, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c14, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c24, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c34, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c44, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c54, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c64, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c74, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c84, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c94, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ca4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cb4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cc4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cd4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ce4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cf4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c08, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c18, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c28, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c38, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c48, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c58, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c68, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c78, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c88, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c98, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ca8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cb8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cc8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cd8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ce8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0cf8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0c0c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c1c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c2c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c3c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c4c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c5c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c6c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c7c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c8c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0c9c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0cac, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0cbc, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0ccc, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0cdc, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0cec, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0cfc, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0d00, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d08, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d10, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d18, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d20, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d28, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d30, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d38, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d04, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d0c, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d14, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d1c, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d24, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d2c, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d34, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d3c, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e00, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e10, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e20, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e30, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e40, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e50, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e60, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e70, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e80, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e90, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ea0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0eb0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ec0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ed0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ee0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ef0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0e04, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e14, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e24, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e34, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e44, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e54, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e64, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e74, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e84, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e94, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ea4, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0eb4, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ec4, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ed4, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ee4, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ef4, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e08, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e18, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e28, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e38, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e48, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e58, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e68, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e78, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e88, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0e98, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ea8, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0eb8, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ec8, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ed8, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ee8, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0ef8, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d40, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d48, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d50, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d58, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d44, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d4c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d54, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d5c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1e00, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e20, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e40, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e60, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e80, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ea0, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ec0, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ee0, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e04, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e24, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e44, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e64, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e84, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ea4, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ec4, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ee4, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e08, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e28, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e48, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e68, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e88, 0x00000002); + nv_mthd(dev, 0x9097, 0x1ea8, 0x00000002); + nv_mthd(dev, 0x9097, 0x1ec8, 0x00000002); + nv_mthd(dev, 0x9097, 0x1ee8, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e0c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e2c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e4c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e6c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e8c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1eac, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ecc, 0x00000001); + nv_mthd(dev, 0x9097, 0x1eec, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e10, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e30, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e50, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e70, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e90, 0x00000001); + nv_mthd(dev, 0x9097, 0x1eb0, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ed0, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ef0, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e14, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e34, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e54, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e74, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e94, 0x00000002); + nv_mthd(dev, 0x9097, 0x1eb4, 0x00000002); + nv_mthd(dev, 0x9097, 0x1ed4, 0x00000002); + nv_mthd(dev, 0x9097, 0x1ef4, 0x00000002); + nv_mthd(dev, 0x9097, 0x1e18, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e38, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e58, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e78, 0x00000001); + nv_mthd(dev, 0x9097, 0x1e98, 0x00000001); + nv_mthd(dev, 0x9097, 0x1eb8, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ed8, 0x00000001); + nv_mthd(dev, 0x9097, 0x1ef8, 0x00000001); + if (fermi == 0x9097) { + for (mthd = 0x3400; mthd <= 0x35fc; mthd += 4) + nv_mthd(dev, 0x9097, mthd, 0x00000000); + } + nv_mthd(dev, 0x9097, 0x030c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1944, 0x00000000); + nv_mthd(dev, 0x9097, 0x1514, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d68, 0x0000ffff); + nv_mthd(dev, 0x9097, 0x121c, 0x0fac6881); + nv_mthd(dev, 0x9097, 0x0fac, 0x00000001); + nv_mthd(dev, 0x9097, 0x1538, 0x00000001); + nv_mthd(dev, 0x9097, 0x0fe0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0fe4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0fe8, 0x00000014); + nv_mthd(dev, 0x9097, 0x0fec, 0x00000040); + nv_mthd(dev, 0x9097, 0x0ff0, 0x00000000); + nv_mthd(dev, 0x9097, 0x179c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1228, 0x00000400); + nv_mthd(dev, 0x9097, 0x122c, 0x00000300); + nv_mthd(dev, 0x9097, 0x1230, 0x00010001); + nv_mthd(dev, 0x9097, 0x07f8, 0x00000000); + nv_mthd(dev, 0x9097, 0x15b4, 0x00000001); + nv_mthd(dev, 0x9097, 0x15cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1534, 0x00000000); + nv_mthd(dev, 0x9097, 0x0fb0, 0x00000000); + nv_mthd(dev, 0x9097, 0x15d0, 0x00000000); + nv_mthd(dev, 0x9097, 0x153c, 0x00000000); + nv_mthd(dev, 0x9097, 0x16b4, 0x00000003); + nv_mthd(dev, 0x9097, 0x0fbc, 0x0000ffff); + nv_mthd(dev, 0x9097, 0x0fc0, 0x0000ffff); + nv_mthd(dev, 0x9097, 0x0fc4, 0x0000ffff); + nv_mthd(dev, 0x9097, 0x0fc8, 0x0000ffff); + nv_mthd(dev, 0x9097, 0x0df8, 0x00000000); + nv_mthd(dev, 0x9097, 0x0dfc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1948, 0x00000000); + nv_mthd(dev, 0x9097, 0x1970, 0x00000001); + nv_mthd(dev, 0x9097, 0x161c, 0x000009f0); + nv_mthd(dev, 0x9097, 0x0dcc, 0x00000010); + nv_mthd(dev, 0x9097, 0x163c, 0x00000000); + nv_mthd(dev, 0x9097, 0x15e4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1160, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1164, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1168, 0x25e00040); + nv_mthd(dev, 0x9097, 0x116c, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1170, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1174, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1178, 0x25e00040); + nv_mthd(dev, 0x9097, 0x117c, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1180, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1184, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1188, 0x25e00040); + nv_mthd(dev, 0x9097, 0x118c, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1190, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1194, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1198, 0x25e00040); + nv_mthd(dev, 0x9097, 0x119c, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11a0, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11a4, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11a8, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11ac, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11b0, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11b4, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11b8, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11bc, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11c0, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11c4, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11c8, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11cc, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11d0, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11d4, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11d8, 0x25e00040); + nv_mthd(dev, 0x9097, 0x11dc, 0x25e00040); + nv_mthd(dev, 0x9097, 0x1880, 0x00000000); + nv_mthd(dev, 0x9097, 0x1884, 0x00000000); + nv_mthd(dev, 0x9097, 0x1888, 0x00000000); + nv_mthd(dev, 0x9097, 0x188c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1890, 0x00000000); + nv_mthd(dev, 0x9097, 0x1894, 0x00000000); + nv_mthd(dev, 0x9097, 0x1898, 0x00000000); + nv_mthd(dev, 0x9097, 0x189c, 0x00000000); + nv_mthd(dev, 0x9097, 0x18a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x18a4, 0x00000000); + nv_mthd(dev, 0x9097, 0x18a8, 0x00000000); + nv_mthd(dev, 0x9097, 0x18ac, 0x00000000); + nv_mthd(dev, 0x9097, 0x18b0, 0x00000000); + nv_mthd(dev, 0x9097, 0x18b4, 0x00000000); + nv_mthd(dev, 0x9097, 0x18b8, 0x00000000); + nv_mthd(dev, 0x9097, 0x18bc, 0x00000000); + nv_mthd(dev, 0x9097, 0x18c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x18c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x18c8, 0x00000000); + nv_mthd(dev, 0x9097, 0x18cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x18d0, 0x00000000); + nv_mthd(dev, 0x9097, 0x18d4, 0x00000000); + nv_mthd(dev, 0x9097, 0x18d8, 0x00000000); + nv_mthd(dev, 0x9097, 0x18dc, 0x00000000); + nv_mthd(dev, 0x9097, 0x18e0, 0x00000000); + nv_mthd(dev, 0x9097, 0x18e4, 0x00000000); + nv_mthd(dev, 0x9097, 0x18e8, 0x00000000); + nv_mthd(dev, 0x9097, 0x18ec, 0x00000000); + nv_mthd(dev, 0x9097, 0x18f0, 0x00000000); + nv_mthd(dev, 0x9097, 0x18f4, 0x00000000); + nv_mthd(dev, 0x9097, 0x18f8, 0x00000000); + nv_mthd(dev, 0x9097, 0x18fc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f84, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f88, 0x00000000); + nv_mthd(dev, 0x9097, 0x17c8, 0x00000000); + nv_mthd(dev, 0x9097, 0x17cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x17d0, 0x000000ff); + nv_mthd(dev, 0x9097, 0x17d4, 0xffffffff); + nv_mthd(dev, 0x9097, 0x17d8, 0x00000002); + nv_mthd(dev, 0x9097, 0x17dc, 0x00000000); + nv_mthd(dev, 0x9097, 0x15f4, 0x00000000); + nv_mthd(dev, 0x9097, 0x15f8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1434, 0x00000000); + nv_mthd(dev, 0x9097, 0x1438, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d74, 0x00000000); + nv_mthd(dev, 0x9097, 0x0dec, 0x00000001); + nv_mthd(dev, 0x9097, 0x13a4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1318, 0x00000001); + nv_mthd(dev, 0x9097, 0x1644, 0x00000000); + nv_mthd(dev, 0x9097, 0x0748, 0x00000000); + nv_mthd(dev, 0x9097, 0x0de8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1648, 0x00000000); + nv_mthd(dev, 0x9097, 0x12a4, 0x00000000); + nv_mthd(dev, 0x9097, 0x1120, 0x00000000); + nv_mthd(dev, 0x9097, 0x1124, 0x00000000); + nv_mthd(dev, 0x9097, 0x1128, 0x00000000); + nv_mthd(dev, 0x9097, 0x112c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1118, 0x00000000); + nv_mthd(dev, 0x9097, 0x164c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1658, 0x00000000); + nv_mthd(dev, 0x9097, 0x1910, 0x00000290); + nv_mthd(dev, 0x9097, 0x1518, 0x00000000); + nv_mthd(dev, 0x9097, 0x165c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1520, 0x00000000); + nv_mthd(dev, 0x9097, 0x1604, 0x00000000); + nv_mthd(dev, 0x9097, 0x1570, 0x00000000); + nv_mthd(dev, 0x9097, 0x13b0, 0x3f800000); + nv_mthd(dev, 0x9097, 0x13b4, 0x3f800000); + nv_mthd(dev, 0x9097, 0x020c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1670, 0x30201000); + nv_mthd(dev, 0x9097, 0x1674, 0x70605040); + nv_mthd(dev, 0x9097, 0x1678, 0xb8a89888); + nv_mthd(dev, 0x9097, 0x167c, 0xf8e8d8c8); + nv_mthd(dev, 0x9097, 0x166c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1680, 0x00ffff00); + nv_mthd(dev, 0x9097, 0x12d0, 0x00000003); + nv_mthd(dev, 0x9097, 0x12d4, 0x00000002); + nv_mthd(dev, 0x9097, 0x1684, 0x00000000); + nv_mthd(dev, 0x9097, 0x1688, 0x00000000); + nv_mthd(dev, 0x9097, 0x0dac, 0x00001b02); + nv_mthd(dev, 0x9097, 0x0db0, 0x00001b02); + nv_mthd(dev, 0x9097, 0x0db4, 0x00000000); + nv_mthd(dev, 0x9097, 0x168c, 0x00000000); + nv_mthd(dev, 0x9097, 0x15bc, 0x00000000); + nv_mthd(dev, 0x9097, 0x156c, 0x00000000); + nv_mthd(dev, 0x9097, 0x187c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1110, 0x00000001); + nv_mthd(dev, 0x9097, 0x0dc0, 0x00000000); + nv_mthd(dev, 0x9097, 0x0dc4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0dc8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1234, 0x00000000); + nv_mthd(dev, 0x9097, 0x1690, 0x00000000); + nv_mthd(dev, 0x9097, 0x12ac, 0x00000001); + nv_mthd(dev, 0x9097, 0x02c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0790, 0x00000000); + nv_mthd(dev, 0x9097, 0x0794, 0x00000000); + nv_mthd(dev, 0x9097, 0x0798, 0x00000000); + nv_mthd(dev, 0x9097, 0x079c, 0x00000000); + nv_mthd(dev, 0x9097, 0x07a0, 0x00000000); + nv_mthd(dev, 0x9097, 0x077c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1000, 0x00000010); + nv_mthd(dev, 0x9097, 0x10fc, 0x00000000); + nv_mthd(dev, 0x9097, 0x1290, 0x00000000); + nv_mthd(dev, 0x9097, 0x0218, 0x00000010); + nv_mthd(dev, 0x9097, 0x12d8, 0x00000000); + nv_mthd(dev, 0x9097, 0x12dc, 0x00000010); + nv_mthd(dev, 0x9097, 0x0d94, 0x00000001); + nv_mthd(dev, 0x9097, 0x155c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1560, 0x00000000); + nv_mthd(dev, 0x9097, 0x1564, 0x00001fff); + nv_mthd(dev, 0x9097, 0x1574, 0x00000000); + nv_mthd(dev, 0x9097, 0x1578, 0x00000000); + nv_mthd(dev, 0x9097, 0x157c, 0x003fffff); + nv_mthd(dev, 0x9097, 0x1354, 0x00000000); + nv_mthd(dev, 0x9097, 0x1664, 0x00000000); + nv_mthd(dev, 0x9097, 0x1610, 0x00000012); + nv_mthd(dev, 0x9097, 0x1608, 0x00000000); + nv_mthd(dev, 0x9097, 0x160c, 0x00000000); + nv_mthd(dev, 0x9097, 0x162c, 0x00000003); + nv_mthd(dev, 0x9097, 0x0210, 0x00000000); + nv_mthd(dev, 0x9097, 0x0320, 0x00000000); + nv_mthd(dev, 0x9097, 0x0324, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0328, 0x3f800000); + nv_mthd(dev, 0x9097, 0x032c, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0330, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0334, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0338, 0x3f800000); + nv_mthd(dev, 0x9097, 0x0750, 0x00000000); + nv_mthd(dev, 0x9097, 0x0760, 0x39291909); + nv_mthd(dev, 0x9097, 0x0764, 0x79695949); + nv_mthd(dev, 0x9097, 0x0768, 0xb9a99989); + nv_mthd(dev, 0x9097, 0x076c, 0xf9e9d9c9); + nv_mthd(dev, 0x9097, 0x0770, 0x30201000); + nv_mthd(dev, 0x9097, 0x0774, 0x70605040); + nv_mthd(dev, 0x9097, 0x0778, 0x00009080); + nv_mthd(dev, 0x9097, 0x0780, 0x39291909); + nv_mthd(dev, 0x9097, 0x0784, 0x79695949); + nv_mthd(dev, 0x9097, 0x0788, 0xb9a99989); + nv_mthd(dev, 0x9097, 0x078c, 0xf9e9d9c9); + nv_mthd(dev, 0x9097, 0x07d0, 0x30201000); + nv_mthd(dev, 0x9097, 0x07d4, 0x70605040); + nv_mthd(dev, 0x9097, 0x07d8, 0x00009080); + nv_mthd(dev, 0x9097, 0x037c, 0x00000001); + nv_mthd(dev, 0x9097, 0x0740, 0x00000000); + nv_mthd(dev, 0x9097, 0x0744, 0x00000000); + nv_mthd(dev, 0x9097, 0x2600, 0x00000000); + nv_mthd(dev, 0x9097, 0x1918, 0x00000000); + nv_mthd(dev, 0x9097, 0x191c, 0x00000900); + nv_mthd(dev, 0x9097, 0x1920, 0x00000405); + nv_mthd(dev, 0x9097, 0x1308, 0x00000001); + nv_mthd(dev, 0x9097, 0x1924, 0x00000000); + nv_mthd(dev, 0x9097, 0x13ac, 0x00000000); + nv_mthd(dev, 0x9097, 0x192c, 0x00000001); + nv_mthd(dev, 0x9097, 0x193c, 0x00002c1c); + nv_mthd(dev, 0x9097, 0x0d7c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f8c, 0x00000000); + nv_mthd(dev, 0x9097, 0x02c0, 0x00000001); + nv_mthd(dev, 0x9097, 0x1510, 0x00000000); + nv_mthd(dev, 0x9097, 0x1940, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ff4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0ff8, 0x00000000); + nv_mthd(dev, 0x9097, 0x194c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1950, 0x00000000); + nv_mthd(dev, 0x9097, 0x1968, 0x00000000); + nv_mthd(dev, 0x9097, 0x1590, 0x0000003f); + nv_mthd(dev, 0x9097, 0x07e8, 0x00000000); + nv_mthd(dev, 0x9097, 0x07ec, 0x00000000); + nv_mthd(dev, 0x9097, 0x07f0, 0x00000000); + nv_mthd(dev, 0x9097, 0x07f4, 0x00000000); + nv_mthd(dev, 0x9097, 0x196c, 0x00000011); + nv_mthd(dev, 0x9097, 0x197c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0fcc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0fd0, 0x00000000); + nv_mthd(dev, 0x9097, 0x02d8, 0x00000040); + nv_mthd(dev, 0x9097, 0x1980, 0x00000080); + nv_mthd(dev, 0x9097, 0x1504, 0x00000080); + nv_mthd(dev, 0x9097, 0x1984, 0x00000000); + nv_mthd(dev, 0x9097, 0x0300, 0x00000001); + nv_mthd(dev, 0x9097, 0x13a8, 0x00000000); + nv_mthd(dev, 0x9097, 0x12ec, 0x00000000); + nv_mthd(dev, 0x9097, 0x1310, 0x00000000); + nv_mthd(dev, 0x9097, 0x1314, 0x00000001); + nv_mthd(dev, 0x9097, 0x1380, 0x00000000); + nv_mthd(dev, 0x9097, 0x1384, 0x00000001); + nv_mthd(dev, 0x9097, 0x1388, 0x00000001); + nv_mthd(dev, 0x9097, 0x138c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1390, 0x00000001); + nv_mthd(dev, 0x9097, 0x1394, 0x00000000); + nv_mthd(dev, 0x9097, 0x139c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1398, 0x00000000); + nv_mthd(dev, 0x9097, 0x1594, 0x00000000); + nv_mthd(dev, 0x9097, 0x1598, 0x00000001); + nv_mthd(dev, 0x9097, 0x159c, 0x00000001); + nv_mthd(dev, 0x9097, 0x15a0, 0x00000001); + nv_mthd(dev, 0x9097, 0x15a4, 0x00000001); + nv_mthd(dev, 0x9097, 0x0f54, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f58, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f5c, 0x00000000); + nv_mthd(dev, 0x9097, 0x19bc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f9c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0fa0, 0x00000000); + nv_mthd(dev, 0x9097, 0x12cc, 0x00000000); + nv_mthd(dev, 0x9097, 0x12e8, 0x00000000); + nv_mthd(dev, 0x9097, 0x130c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1360, 0x00000000); + nv_mthd(dev, 0x9097, 0x1364, 0x00000000); + nv_mthd(dev, 0x9097, 0x1368, 0x00000000); + nv_mthd(dev, 0x9097, 0x136c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1370, 0x00000000); + nv_mthd(dev, 0x9097, 0x1374, 0x00000000); + nv_mthd(dev, 0x9097, 0x1378, 0x00000000); + nv_mthd(dev, 0x9097, 0x137c, 0x00000000); + nv_mthd(dev, 0x9097, 0x133c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1340, 0x00000001); + nv_mthd(dev, 0x9097, 0x1344, 0x00000002); + nv_mthd(dev, 0x9097, 0x1348, 0x00000001); + nv_mthd(dev, 0x9097, 0x134c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1350, 0x00000002); + nv_mthd(dev, 0x9097, 0x1358, 0x00000001); + nv_mthd(dev, 0x9097, 0x12e4, 0x00000000); + nv_mthd(dev, 0x9097, 0x131c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1320, 0x00000000); + nv_mthd(dev, 0x9097, 0x1324, 0x00000000); + nv_mthd(dev, 0x9097, 0x1328, 0x00000000); + nv_mthd(dev, 0x9097, 0x19c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1140, 0x00000000); + nv_mthd(dev, 0x9097, 0x19c4, 0x00000000); + nv_mthd(dev, 0x9097, 0x19c8, 0x00001500); + nv_mthd(dev, 0x9097, 0x135c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f90, 0x00000000); + nv_mthd(dev, 0x9097, 0x19e0, 0x00000001); + nv_mthd(dev, 0x9097, 0x19e4, 0x00000001); + nv_mthd(dev, 0x9097, 0x19e8, 0x00000001); + nv_mthd(dev, 0x9097, 0x19ec, 0x00000001); + nv_mthd(dev, 0x9097, 0x19f0, 0x00000001); + nv_mthd(dev, 0x9097, 0x19f4, 0x00000001); + nv_mthd(dev, 0x9097, 0x19f8, 0x00000001); + nv_mthd(dev, 0x9097, 0x19fc, 0x00000001); + nv_mthd(dev, 0x9097, 0x19cc, 0x00000001); + nv_mthd(dev, 0x9097, 0x15b8, 0x00000000); + nv_mthd(dev, 0x9097, 0x1a00, 0x00001111); + nv_mthd(dev, 0x9097, 0x1a04, 0x00000000); + nv_mthd(dev, 0x9097, 0x1a08, 0x00000000); + nv_mthd(dev, 0x9097, 0x1a0c, 0x00000000); + nv_mthd(dev, 0x9097, 0x1a10, 0x00000000); + nv_mthd(dev, 0x9097, 0x1a14, 0x00000000); + nv_mthd(dev, 0x9097, 0x1a18, 0x00000000); + nv_mthd(dev, 0x9097, 0x1a1c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d6c, 0xffff0000); + nv_mthd(dev, 0x9097, 0x0d70, 0xffff0000); + nv_mthd(dev, 0x9097, 0x10f8, 0x00001010); + nv_mthd(dev, 0x9097, 0x0d80, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d84, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d88, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d8c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0d90, 0x00000000); + nv_mthd(dev, 0x9097, 0x0da0, 0x00000000); + nv_mthd(dev, 0x9097, 0x1508, 0x80000000); + nv_mthd(dev, 0x9097, 0x150c, 0x40000000); + nv_mthd(dev, 0x9097, 0x1668, 0x00000000); + nv_mthd(dev, 0x9097, 0x0318, 0x00000008); + nv_mthd(dev, 0x9097, 0x031c, 0x00000008); + nv_mthd(dev, 0x9097, 0x0d9c, 0x00000001); + nv_mthd(dev, 0x9097, 0x07dc, 0x00000000); + nv_mthd(dev, 0x9097, 0x074c, 0x00000055); + nv_mthd(dev, 0x9097, 0x1420, 0x00000003); + nv_mthd(dev, 0x9097, 0x17bc, 0x00000000); + nv_mthd(dev, 0x9097, 0x17c0, 0x00000000); + nv_mthd(dev, 0x9097, 0x17c4, 0x00000001); + nv_mthd(dev, 0x9097, 0x1008, 0x00000008); + nv_mthd(dev, 0x9097, 0x100c, 0x00000040); + nv_mthd(dev, 0x9097, 0x1010, 0x0000012c); + nv_mthd(dev, 0x9097, 0x0d60, 0x00000040); + nv_mthd(dev, 0x9097, 0x075c, 0x00000003); + nv_mthd(dev, 0x9097, 0x1018, 0x00000020); + nv_mthd(dev, 0x9097, 0x101c, 0x00000001); + nv_mthd(dev, 0x9097, 0x1020, 0x00000020); + nv_mthd(dev, 0x9097, 0x1024, 0x00000001); + nv_mthd(dev, 0x9097, 0x1444, 0x00000000); + nv_mthd(dev, 0x9097, 0x1448, 0x00000000); + nv_mthd(dev, 0x9097, 0x144c, 0x00000000); + nv_mthd(dev, 0x9097, 0x0360, 0x20164010); + nv_mthd(dev, 0x9097, 0x0364, 0x00000020); + nv_mthd(dev, 0x9097, 0x0368, 0x00000000); + nv_mthd(dev, 0x9097, 0x0de4, 0x00000000); + nv_mthd(dev, 0x9097, 0x0204, 0x00000006); + nv_mthd(dev, 0x9097, 0x0208, 0x00000000); + nv_mthd(dev, 0x9097, 0x02cc, 0x003fffff); + nv_mthd(dev, 0x9097, 0x02d0, 0x00000c48); + nv_mthd(dev, 0x9097, 0x1220, 0x00000005); + nv_mthd(dev, 0x9097, 0x0fdc, 0x00000000); + nv_mthd(dev, 0x9097, 0x0f98, 0x00300008); + nv_mthd(dev, 0x9097, 0x1284, 0x04000080); + nv_mthd(dev, 0x9097, 0x1450, 0x00300008); + nv_mthd(dev, 0x9097, 0x1454, 0x04000080); + nv_mthd(dev, 0x9097, 0x0214, 0x00000000); + /* in trace, right after 0x90c0, not here */ + nv_mthd(dev, 0x9097, 0x3410, 0x80002006); +} + +static void +nvc0_grctx_generate_9197(struct drm_device *dev) +{ + u32 fermi = nvc0_graph_class(dev); + u32 mthd; + + if (fermi == 0x9197) { + for (mthd = 0x3400; mthd <= 0x35fc; mthd += 4) + nv_mthd(dev, 0x9197, mthd, 0x00000000); + } + nv_mthd(dev, 0x9197, 0x02e4, 0x0000b001); +} + +static void +nvc0_grctx_generate_9297(struct drm_device *dev) +{ + u32 fermi = nvc0_graph_class(dev); + u32 mthd; + + if (fermi == 0x9297) { + for (mthd = 0x3400; mthd <= 0x35fc; mthd += 4) + nv_mthd(dev, 0x9297, mthd, 0x00000000); + } + nv_mthd(dev, 0x9297, 0x036c, 0x00000000); + nv_mthd(dev, 0x9297, 0x0370, 0x00000000); + nv_mthd(dev, 0x9297, 0x07a4, 0x00000000); + nv_mthd(dev, 0x9297, 0x07a8, 0x00000000); + nv_mthd(dev, 0x9297, 0x0374, 0x00000000); + nv_mthd(dev, 0x9297, 0x0378, 0x00000020); +} + +static void +nvc0_grctx_generate_902d(struct drm_device *dev) +{ + nv_mthd(dev, 0x902d, 0x0200, 0x000000cf); + nv_mthd(dev, 0x902d, 0x0204, 0x00000001); + nv_mthd(dev, 0x902d, 0x0208, 0x00000020); + nv_mthd(dev, 0x902d, 0x020c, 0x00000001); + nv_mthd(dev, 0x902d, 0x0210, 0x00000000); + nv_mthd(dev, 0x902d, 0x0214, 0x00000080); + nv_mthd(dev, 0x902d, 0x0218, 0x00000100); + nv_mthd(dev, 0x902d, 0x021c, 0x00000100); + nv_mthd(dev, 0x902d, 0x0220, 0x00000000); + nv_mthd(dev, 0x902d, 0x0224, 0x00000000); + nv_mthd(dev, 0x902d, 0x0230, 0x000000cf); + nv_mthd(dev, 0x902d, 0x0234, 0x00000001); + nv_mthd(dev, 0x902d, 0x0238, 0x00000020); + nv_mthd(dev, 0x902d, 0x023c, 0x00000001); + nv_mthd(dev, 0x902d, 0x0244, 0x00000080); + nv_mthd(dev, 0x902d, 0x0248, 0x00000100); + nv_mthd(dev, 0x902d, 0x024c, 0x00000100); +} + +static void +nvc0_grctx_generate_9039(struct drm_device *dev) +{ + nv_mthd(dev, 0x9039, 0x030c, 0x00000000); + nv_mthd(dev, 0x9039, 0x0310, 0x00000000); + nv_mthd(dev, 0x9039, 0x0314, 0x00000000); + nv_mthd(dev, 0x9039, 0x0320, 0x00000000); + nv_mthd(dev, 0x9039, 0x0238, 0x00000000); + nv_mthd(dev, 0x9039, 0x023c, 0x00000000); + nv_mthd(dev, 0x9039, 0x0318, 0x00000000); + nv_mthd(dev, 0x9039, 0x031c, 0x00000000); +} + +static void +nvc0_grctx_generate_90c0(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int i; + + for (i = 0; dev_priv->chipset == 0xd9 && i < 4; i++) { + nv_mthd(dev, 0x90c0, 0x2700 + (i * 0x40), 0x00000000); + nv_mthd(dev, 0x90c0, 0x2720 + (i * 0x40), 0x00000000); + nv_mthd(dev, 0x90c0, 0x2704 + (i * 0x40), 0x00000000); + nv_mthd(dev, 0x90c0, 0x2724 + (i * 0x40), 0x00000000); + nv_mthd(dev, 0x90c0, 0x2708 + (i * 0x40), 0x00000000); + nv_mthd(dev, 0x90c0, 0x2728 + (i * 0x40), 0x00000000); + } + nv_mthd(dev, 0x90c0, 0x270c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x272c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x274c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x276c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x278c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x27ac, 0x00000000); + nv_mthd(dev, 0x90c0, 0x27cc, 0x00000000); + nv_mthd(dev, 0x90c0, 0x27ec, 0x00000000); + for (i = 0; dev_priv->chipset == 0xd9 && i < 4; i++) { + nv_mthd(dev, 0x90c0, 0x2710 + (i * 0x40), 0x00014000); + nv_mthd(dev, 0x90c0, 0x2730 + (i * 0x40), 0x00014000); + nv_mthd(dev, 0x90c0, 0x2714 + (i * 0x40), 0x00000040); + nv_mthd(dev, 0x90c0, 0x2734 + (i * 0x40), 0x00000040); + } + nv_mthd(dev, 0x90c0, 0x030c, 0x00000001); + nv_mthd(dev, 0x90c0, 0x1944, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0758, 0x00000100); + nv_mthd(dev, 0x90c0, 0x02c4, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0790, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0794, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0798, 0x00000000); + nv_mthd(dev, 0x90c0, 0x079c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x07a0, 0x00000000); + nv_mthd(dev, 0x90c0, 0x077c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0204, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0208, 0x00000000); + nv_mthd(dev, 0x90c0, 0x020c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0214, 0x00000000); + nv_mthd(dev, 0x90c0, 0x024c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x0d94, 0x00000001); + nv_mthd(dev, 0x90c0, 0x1608, 0x00000000); + nv_mthd(dev, 0x90c0, 0x160c, 0x00000000); + nv_mthd(dev, 0x90c0, 0x1664, 0x00000000); +} + +static void +nvc0_grctx_generate_dispatch(struct drm_device *dev) +{ + int i; + + nv_wr32(dev, 0x404004, 0x00000000); + nv_wr32(dev, 0x404008, 0x00000000); + nv_wr32(dev, 0x40400c, 0x00000000); + nv_wr32(dev, 0x404010, 0x00000000); + nv_wr32(dev, 0x404014, 0x00000000); + nv_wr32(dev, 0x404018, 0x00000000); + nv_wr32(dev, 0x40401c, 0x00000000); + nv_wr32(dev, 0x404020, 0x00000000); + nv_wr32(dev, 0x404024, 0x00000000); + nv_wr32(dev, 0x404028, 0x00000000); + nv_wr32(dev, 0x40402c, 0x00000000); + nv_wr32(dev, 0x404044, 0x00000000); + nv_wr32(dev, 0x404094, 0x00000000); + nv_wr32(dev, 0x404098, 0x00000000); + nv_wr32(dev, 0x40409c, 0x00000000); + nv_wr32(dev, 0x4040a0, 0x00000000); + nv_wr32(dev, 0x4040a4, 0x00000000); + nv_wr32(dev, 0x4040a8, 0x00000000); + nv_wr32(dev, 0x4040ac, 0x00000000); + nv_wr32(dev, 0x4040b0, 0x00000000); + nv_wr32(dev, 0x4040b4, 0x00000000); + nv_wr32(dev, 0x4040b8, 0x00000000); + nv_wr32(dev, 0x4040bc, 0x00000000); + nv_wr32(dev, 0x4040c0, 0x00000000); + nv_wr32(dev, 0x4040c4, 0x00000000); + nv_wr32(dev, 0x4040c8, 0xf0000087); + nv_wr32(dev, 0x4040d4, 0x00000000); + nv_wr32(dev, 0x4040d8, 0x00000000); + nv_wr32(dev, 0x4040dc, 0x00000000); + nv_wr32(dev, 0x4040e0, 0x00000000); + nv_wr32(dev, 0x4040e4, 0x00000000); + nv_wr32(dev, 0x4040e8, 0x00001000); + nv_wr32(dev, 0x4040f8, 0x00000000); + nv_wr32(dev, 0x404130, 0x00000000); + nv_wr32(dev, 0x404134, 0x00000000); + nv_wr32(dev, 0x404138, 0x20000040); + nv_wr32(dev, 0x404150, 0x0000002e); + nv_wr32(dev, 0x404154, 0x00000400); + nv_wr32(dev, 0x404158, 0x00000200); + nv_wr32(dev, 0x404164, 0x00000055); + nv_wr32(dev, 0x404168, 0x00000000); + nv_wr32(dev, 0x404174, 0x00000000); + nv_wr32(dev, 0x404178, 0x00000000); + nv_wr32(dev, 0x40417c, 0x00000000); + for (i = 0; i < 8; i++) + nv_wr32(dev, 0x404200 + (i * 4), 0x00000000); /* subc */ +} + +static void +nvc0_grctx_generate_macro(struct drm_device *dev) +{ + nv_wr32(dev, 0x404404, 0x00000000); + nv_wr32(dev, 0x404408, 0x00000000); + nv_wr32(dev, 0x40440c, 0x00000000); + nv_wr32(dev, 0x404410, 0x00000000); + nv_wr32(dev, 0x404414, 0x00000000); + nv_wr32(dev, 0x404418, 0x00000000); + nv_wr32(dev, 0x40441c, 0x00000000); + nv_wr32(dev, 0x404420, 0x00000000); + nv_wr32(dev, 0x404424, 0x00000000); + nv_wr32(dev, 0x404428, 0x00000000); + nv_wr32(dev, 0x40442c, 0x00000000); + nv_wr32(dev, 0x404430, 0x00000000); + nv_wr32(dev, 0x404434, 0x00000000); + nv_wr32(dev, 0x404438, 0x00000000); + nv_wr32(dev, 0x404460, 0x00000000); + nv_wr32(dev, 0x404464, 0x00000000); + nv_wr32(dev, 0x404468, 0x00ffffff); + nv_wr32(dev, 0x40446c, 0x00000000); + nv_wr32(dev, 0x404480, 0x00000001); + nv_wr32(dev, 0x404498, 0x00000001); +} + +static void +nvc0_grctx_generate_m2mf(struct drm_device *dev) +{ + nv_wr32(dev, 0x404604, 0x00000015); + nv_wr32(dev, 0x404608, 0x00000000); + nv_wr32(dev, 0x40460c, 0x00002e00); + nv_wr32(dev, 0x404610, 0x00000100); + nv_wr32(dev, 0x404618, 0x00000000); + nv_wr32(dev, 0x40461c, 0x00000000); + nv_wr32(dev, 0x404620, 0x00000000); + nv_wr32(dev, 0x404624, 0x00000000); + nv_wr32(dev, 0x404628, 0x00000000); + nv_wr32(dev, 0x40462c, 0x00000000); + nv_wr32(dev, 0x404630, 0x00000000); + nv_wr32(dev, 0x404634, 0x00000000); + nv_wr32(dev, 0x404638, 0x00000004); + nv_wr32(dev, 0x40463c, 0x00000000); + nv_wr32(dev, 0x404640, 0x00000000); + nv_wr32(dev, 0x404644, 0x00000000); + nv_wr32(dev, 0x404648, 0x00000000); + nv_wr32(dev, 0x40464c, 0x00000000); + nv_wr32(dev, 0x404650, 0x00000000); + nv_wr32(dev, 0x404654, 0x00000000); + nv_wr32(dev, 0x404658, 0x00000000); + nv_wr32(dev, 0x40465c, 0x007f0100); + nv_wr32(dev, 0x404660, 0x00000000); + nv_wr32(dev, 0x404664, 0x00000000); + nv_wr32(dev, 0x404668, 0x00000000); + nv_wr32(dev, 0x40466c, 0x00000000); + nv_wr32(dev, 0x404670, 0x00000000); + nv_wr32(dev, 0x404674, 0x00000000); + nv_wr32(dev, 0x404678, 0x00000000); + nv_wr32(dev, 0x40467c, 0x00000002); + nv_wr32(dev, 0x404680, 0x00000000); + nv_wr32(dev, 0x404684, 0x00000000); + nv_wr32(dev, 0x404688, 0x00000000); + nv_wr32(dev, 0x40468c, 0x00000000); + nv_wr32(dev, 0x404690, 0x00000000); + nv_wr32(dev, 0x404694, 0x00000000); + nv_wr32(dev, 0x404698, 0x00000000); + nv_wr32(dev, 0x40469c, 0x00000000); + nv_wr32(dev, 0x4046a0, 0x007f0080); + nv_wr32(dev, 0x4046a4, 0x00000000); + nv_wr32(dev, 0x4046a8, 0x00000000); + nv_wr32(dev, 0x4046ac, 0x00000000); + nv_wr32(dev, 0x4046b0, 0x00000000); + nv_wr32(dev, 0x4046b4, 0x00000000); + nv_wr32(dev, 0x4046b8, 0x00000000); + nv_wr32(dev, 0x4046bc, 0x00000000); + nv_wr32(dev, 0x4046c0, 0x00000000); + nv_wr32(dev, 0x4046c4, 0x00000000); + nv_wr32(dev, 0x4046c8, 0x00000000); + nv_wr32(dev, 0x4046cc, 0x00000000); + nv_wr32(dev, 0x4046d0, 0x00000000); + nv_wr32(dev, 0x4046d4, 0x00000000); + nv_wr32(dev, 0x4046d8, 0x00000000); + nv_wr32(dev, 0x4046dc, 0x00000000); + nv_wr32(dev, 0x4046e0, 0x00000000); + nv_wr32(dev, 0x4046e4, 0x00000000); + nv_wr32(dev, 0x4046e8, 0x00000000); + nv_wr32(dev, 0x4046f0, 0x00000000); + nv_wr32(dev, 0x4046f4, 0x00000000); +} + +static void +nvc0_grctx_generate_unk47xx(struct drm_device *dev) +{ + nv_wr32(dev, 0x404700, 0x00000000); + nv_wr32(dev, 0x404704, 0x00000000); + nv_wr32(dev, 0x404708, 0x00000000); + nv_wr32(dev, 0x40470c, 0x00000000); + nv_wr32(dev, 0x404710, 0x00000000); + nv_wr32(dev, 0x404714, 0x00000000); + nv_wr32(dev, 0x404718, 0x00000000); + nv_wr32(dev, 0x40471c, 0x00000000); + nv_wr32(dev, 0x404720, 0x00000000); + nv_wr32(dev, 0x404724, 0x00000000); + nv_wr32(dev, 0x404728, 0x00000000); + nv_wr32(dev, 0x40472c, 0x00000000); + nv_wr32(dev, 0x404730, 0x00000000); + nv_wr32(dev, 0x404734, 0x00000100); + nv_wr32(dev, 0x404738, 0x00000000); + nv_wr32(dev, 0x40473c, 0x00000000); + nv_wr32(dev, 0x404740, 0x00000000); + nv_wr32(dev, 0x404744, 0x00000000); + nv_wr32(dev, 0x404748, 0x00000000); + nv_wr32(dev, 0x40474c, 0x00000000); + nv_wr32(dev, 0x404750, 0x00000000); + nv_wr32(dev, 0x404754, 0x00000000); +} + +static void +nvc0_grctx_generate_shaders(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + if (dev_priv->chipset == 0xd9) { + nv_wr32(dev, 0x405800, 0x0f8000bf); + nv_wr32(dev, 0x405830, 0x02180218); + nv_wr32(dev, 0x405834, 0x08000000); + } else + if (dev_priv->chipset == 0xc1) { + nv_wr32(dev, 0x405800, 0x0f8000bf); + nv_wr32(dev, 0x405830, 0x02180218); + nv_wr32(dev, 0x405834, 0x00000000); + } else { + nv_wr32(dev, 0x405800, 0x078000bf); + nv_wr32(dev, 0x405830, 0x02180000); + nv_wr32(dev, 0x405834, 0x00000000); + } + nv_wr32(dev, 0x405838, 0x00000000); + nv_wr32(dev, 0x405854, 0x00000000); + nv_wr32(dev, 0x405870, 0x00000001); + nv_wr32(dev, 0x405874, 0x00000001); + nv_wr32(dev, 0x405878, 0x00000001); + nv_wr32(dev, 0x40587c, 0x00000001); + nv_wr32(dev, 0x405a00, 0x00000000); + nv_wr32(dev, 0x405a04, 0x00000000); + nv_wr32(dev, 0x405a18, 0x00000000); +} + +static void +nvc0_grctx_generate_unk60xx(struct drm_device *dev) +{ + nv_wr32(dev, 0x406020, 0x000103c1); + nv_wr32(dev, 0x406028, 0x00000001); + nv_wr32(dev, 0x40602c, 0x00000001); + nv_wr32(dev, 0x406030, 0x00000001); + nv_wr32(dev, 0x406034, 0x00000001); +} + +static void +nvc0_grctx_generate_unk64xx(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + nv_wr32(dev, 0x4064a8, 0x00000000); + nv_wr32(dev, 0x4064ac, 0x00003fff); + nv_wr32(dev, 0x4064b4, 0x00000000); + nv_wr32(dev, 0x4064b8, 0x00000000); + if (dev_priv->chipset == 0xd9) + nv_wr32(dev, 0x4064bc, 0x00000000); + if (dev_priv->chipset == 0xc1 || + dev_priv->chipset == 0xd9) { + nv_wr32(dev, 0x4064c0, 0x80140078); + nv_wr32(dev, 0x4064c4, 0x0086ffff); + } +} + +static void +nvc0_grctx_generate_tpbus(struct drm_device *dev) +{ + nv_wr32(dev, 0x407804, 0x00000023); + nv_wr32(dev, 0x40780c, 0x0a418820); + nv_wr32(dev, 0x407810, 0x062080e6); + nv_wr32(dev, 0x407814, 0x020398a4); + nv_wr32(dev, 0x407818, 0x0e629062); + nv_wr32(dev, 0x40781c, 0x0a418820); + nv_wr32(dev, 0x407820, 0x000000e6); + nv_wr32(dev, 0x4078bc, 0x00000103); +} + +static void +nvc0_grctx_generate_ccache(struct drm_device *dev) +{ + nv_wr32(dev, 0x408000, 0x00000000); + nv_wr32(dev, 0x408004, 0x00000000); + nv_wr32(dev, 0x408008, 0x00000018); + nv_wr32(dev, 0x40800c, 0x00000000); + nv_wr32(dev, 0x408010, 0x00000000); + nv_wr32(dev, 0x408014, 0x00000069); + nv_wr32(dev, 0x408018, 0xe100e100); + nv_wr32(dev, 0x408064, 0x00000000); +} + +static void +nvc0_grctx_generate_rop(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chipset = dev_priv->chipset; + + /* ROPC_BROADCAST */ + nv_wr32(dev, 0x408800, 0x02802a3c); + nv_wr32(dev, 0x408804, 0x00000040); + if (chipset == 0xd9) { + nv_wr32(dev, 0x408808, 0x1043e005); + nv_wr32(dev, 0x408900, 0x3080b801); + nv_wr32(dev, 0x408904, 0x1043e005); + nv_wr32(dev, 0x408908, 0x00c8102f); + } else + if (chipset == 0xc1) { + nv_wr32(dev, 0x408808, 0x1003e005); + nv_wr32(dev, 0x408900, 0x3080b801); + nv_wr32(dev, 0x408904, 0x62000001); + nv_wr32(dev, 0x408908, 0x00c80929); + } else { + nv_wr32(dev, 0x408808, 0x0003e00d); + nv_wr32(dev, 0x408900, 0x3080b801); + nv_wr32(dev, 0x408904, 0x02000001); + nv_wr32(dev, 0x408908, 0x00c80929); + } + nv_wr32(dev, 0x40890c, 0x00000000); + nv_wr32(dev, 0x408980, 0x0000011d); +} + +static void +nvc0_grctx_generate_gpc(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chipset = dev_priv->chipset; + int i; + + /* GPC_BROADCAST */ + nv_wr32(dev, 0x418380, 0x00000016); + nv_wr32(dev, 0x418400, 0x38004e00); + nv_wr32(dev, 0x418404, 0x71e0ffff); + nv_wr32(dev, 0x418408, 0x00000000); + nv_wr32(dev, 0x41840c, 0x00001008); + nv_wr32(dev, 0x418410, 0x0fff0fff); + nv_wr32(dev, 0x418414, chipset != 0xd9 ? 0x00200fff : 0x02200fff); + nv_wr32(dev, 0x418450, 0x00000000); + nv_wr32(dev, 0x418454, 0x00000000); + nv_wr32(dev, 0x418458, 0x00000000); + nv_wr32(dev, 0x41845c, 0x00000000); + nv_wr32(dev, 0x418460, 0x00000000); + nv_wr32(dev, 0x418464, 0x00000000); + nv_wr32(dev, 0x418468, 0x00000001); + nv_wr32(dev, 0x41846c, 0x00000000); + nv_wr32(dev, 0x418470, 0x00000000); + nv_wr32(dev, 0x418600, 0x0000001f); + nv_wr32(dev, 0x418684, 0x0000000f); + nv_wr32(dev, 0x418700, 0x00000002); + nv_wr32(dev, 0x418704, 0x00000080); + nv_wr32(dev, 0x418708, 0x00000000); + nv_wr32(dev, 0x41870c, chipset != 0xd9 ? 0x07c80000 : 0x00000000); + nv_wr32(dev, 0x418710, 0x00000000); + nv_wr32(dev, 0x418800, chipset != 0xd9 ? 0x0006860a : 0x7006860a); + nv_wr32(dev, 0x418808, 0x00000000); + nv_wr32(dev, 0x41880c, 0x00000000); + nv_wr32(dev, 0x418810, 0x00000000); + nv_wr32(dev, 0x418828, 0x00008442); + if (chipset == 0xc1 || chipset == 0xd9) + nv_wr32(dev, 0x418830, 0x10000001); + else + nv_wr32(dev, 0x418830, 0x00000001); + nv_wr32(dev, 0x4188d8, 0x00000008); + nv_wr32(dev, 0x4188e0, 0x01000000); + nv_wr32(dev, 0x4188e8, 0x00000000); + nv_wr32(dev, 0x4188ec, 0x00000000); + nv_wr32(dev, 0x4188f0, 0x00000000); + nv_wr32(dev, 0x4188f4, 0x00000000); + nv_wr32(dev, 0x4188f8, 0x00000000); + if (chipset == 0xd9) + nv_wr32(dev, 0x4188fc, 0x20100008); + else if (chipset == 0xc1) + nv_wr32(dev, 0x4188fc, 0x00100018); + else + nv_wr32(dev, 0x4188fc, 0x00100000); + nv_wr32(dev, 0x41891c, 0x00ff00ff); + nv_wr32(dev, 0x418924, 0x00000000); + nv_wr32(dev, 0x418928, 0x00ffff00); + nv_wr32(dev, 0x41892c, 0x0000ff00); + for (i = 0; i < 8; i++) { + nv_wr32(dev, 0x418a00 + (i * 0x20), 0x00000000); + nv_wr32(dev, 0x418a04 + (i * 0x20), 0x00000000); + nv_wr32(dev, 0x418a08 + (i * 0x20), 0x00000000); + nv_wr32(dev, 0x418a0c + (i * 0x20), 0x00010000); + nv_wr32(dev, 0x418a10 + (i * 0x20), 0x00000000); + nv_wr32(dev, 0x418a14 + (i * 0x20), 0x00000000); + nv_wr32(dev, 0x418a18 + (i * 0x20), 0x00000000); + } + nv_wr32(dev, 0x418b00, chipset != 0xd9 ? 0x00000000 : 0x00000006); + nv_wr32(dev, 0x418b08, 0x0a418820); + nv_wr32(dev, 0x418b0c, 0x062080e6); + nv_wr32(dev, 0x418b10, 0x020398a4); + nv_wr32(dev, 0x418b14, 0x0e629062); + nv_wr32(dev, 0x418b18, 0x0a418820); + nv_wr32(dev, 0x418b1c, 0x000000e6); + nv_wr32(dev, 0x418bb8, 0x00000103); + nv_wr32(dev, 0x418c08, 0x00000001); + nv_wr32(dev, 0x418c10, 0x00000000); + nv_wr32(dev, 0x418c14, 0x00000000); + nv_wr32(dev, 0x418c18, 0x00000000); + nv_wr32(dev, 0x418c1c, 0x00000000); + nv_wr32(dev, 0x418c20, 0x00000000); + nv_wr32(dev, 0x418c24, 0x00000000); + nv_wr32(dev, 0x418c28, 0x00000000); + nv_wr32(dev, 0x418c2c, 0x00000000); + if (chipset == 0xc1 || chipset == 0xd9) + nv_wr32(dev, 0x418c6c, 0x00000001); + nv_wr32(dev, 0x418c80, 0x20200004); + nv_wr32(dev, 0x418c8c, 0x00000001); + nv_wr32(dev, 0x419000, 0x00000780); + nv_wr32(dev, 0x419004, 0x00000000); + nv_wr32(dev, 0x419008, 0x00000000); + nv_wr32(dev, 0x419014, 0x00000004); +} + +static void +nvc0_grctx_generate_tp(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int chipset = dev_priv->chipset; + + /* GPC_BROADCAST.TP_BROADCAST */ + nv_wr32(dev, 0x419818, 0x00000000); + nv_wr32(dev, 0x41983c, 0x00038bc7); + nv_wr32(dev, 0x419848, 0x00000000); + if (chipset == 0xc1 || chipset == 0xd9) + nv_wr32(dev, 0x419864, 0x00000129); + else + nv_wr32(dev, 0x419864, 0x0000012a); + nv_wr32(dev, 0x419888, 0x00000000); + nv_wr32(dev, 0x419a00, 0x000001f0); + nv_wr32(dev, 0x419a04, 0x00000001); + nv_wr32(dev, 0x419a08, 0x00000023); + nv_wr32(dev, 0x419a0c, 0x00020000); + nv_wr32(dev, 0x419a10, 0x00000000); + nv_wr32(dev, 0x419a14, 0x00000200); + nv_wr32(dev, 0x419a1c, 0x00000000); + nv_wr32(dev, 0x419a20, 0x00000800); + if (chipset == 0xd9) + nv_wr32(dev, 0x00419ac4, 0x0017f440); + else if (chipset != 0xc0 && chipset != 0xc8) + nv_wr32(dev, 0x00419ac4, 0x0007f440); + nv_wr32(dev, 0x419b00, 0x0a418820); + nv_wr32(dev, 0x419b04, 0x062080e6); + nv_wr32(dev, 0x419b08, 0x020398a4); + nv_wr32(dev, 0x419b0c, 0x0e629062); + nv_wr32(dev, 0x419b10, 0x0a418820); + nv_wr32(dev, 0x419b14, 0x000000e6); + nv_wr32(dev, 0x419bd0, 0x00900103); + if (chipset == 0xc1 || chipset == 0xd9) + nv_wr32(dev, 0x419be0, 0x00400001); + else + nv_wr32(dev, 0x419be0, 0x00000001); + nv_wr32(dev, 0x419be4, 0x00000000); + nv_wr32(dev, 0x419c00, chipset != 0xd9 ? 0x00000002 : 0x0000000a); + nv_wr32(dev, 0x419c04, 0x00000006); + nv_wr32(dev, 0x419c08, 0x00000002); + nv_wr32(dev, 0x419c20, 0x00000000); + if (dev_priv->chipset == 0xd9) { + nv_wr32(dev, 0x419c24, 0x00084210); + nv_wr32(dev, 0x419c28, 0x3cf3cf3c); + nv_wr32(dev, 0x419cb0, 0x00020048); + } else + if (chipset == 0xce || chipset == 0xcf) { + nv_wr32(dev, 0x419cb0, 0x00020048); + } else { + nv_wr32(dev, 0x419cb0, 0x00060048); + } + nv_wr32(dev, 0x419ce8, 0x00000000); + nv_wr32(dev, 0x419cf4, 0x00000183); + if (chipset == 0xc1 || chipset == 0xd9) + nv_wr32(dev, 0x419d20, 0x12180000); + else + nv_wr32(dev, 0x419d20, 0x02180000); + nv_wr32(dev, 0x419d24, 0x00001fff); + if (chipset == 0xc1 || chipset == 0xd9) + nv_wr32(dev, 0x419d44, 0x02180218); + nv_wr32(dev, 0x419e04, 0x00000000); + nv_wr32(dev, 0x419e08, 0x00000000); + nv_wr32(dev, 0x419e0c, 0x00000000); + nv_wr32(dev, 0x419e10, 0x00000002); + nv_wr32(dev, 0x419e44, 0x001beff2); + nv_wr32(dev, 0x419e48, 0x00000000); + nv_wr32(dev, 0x419e4c, 0x0000000f); + nv_wr32(dev, 0x419e50, 0x00000000); + nv_wr32(dev, 0x419e54, 0x00000000); + nv_wr32(dev, 0x419e58, 0x00000000); + nv_wr32(dev, 0x419e5c, 0x00000000); + nv_wr32(dev, 0x419e60, 0x00000000); + nv_wr32(dev, 0x419e64, 0x00000000); + nv_wr32(dev, 0x419e68, 0x00000000); + nv_wr32(dev, 0x419e6c, 0x00000000); + nv_wr32(dev, 0x419e70, 0x00000000); + nv_wr32(dev, 0x419e74, 0x00000000); + nv_wr32(dev, 0x419e78, 0x00000000); + nv_wr32(dev, 0x419e7c, 0x00000000); + nv_wr32(dev, 0x419e80, 0x00000000); + nv_wr32(dev, 0x419e84, 0x00000000); + nv_wr32(dev, 0x419e88, 0x00000000); + nv_wr32(dev, 0x419e8c, 0x00000000); + nv_wr32(dev, 0x419e90, 0x00000000); + nv_wr32(dev, 0x419e98, 0x00000000); + if (chipset != 0xc0 && chipset != 0xc8) + nv_wr32(dev, 0x419ee0, 0x00011110); + nv_wr32(dev, 0x419f50, 0x00000000); + nv_wr32(dev, 0x419f54, 0x00000000); + if (chipset != 0xc0 && chipset != 0xc8) + nv_wr32(dev, 0x419f58, 0x00000000); +} + +int +nvc0_grctx_generate(struct nouveau_channel *chan) +{ + struct drm_nouveau_private *dev_priv = chan->dev->dev_private; + struct nvc0_graph_priv *priv = nv_engine(chan->dev, NVOBJ_ENGINE_GR); + struct nvc0_graph_chan *grch = chan->engctx[NVOBJ_ENGINE_GR]; + struct drm_device *dev = chan->dev; + int i, gpc, tp, id; + u32 fermi = nvc0_graph_class(dev); + u32 r000260, tmp; + + r000260 = nv_rd32(dev, 0x000260); + nv_wr32(dev, 0x000260, r000260 & ~1); + nv_wr32(dev, 0x400208, 0x00000000); + + nvc0_grctx_generate_dispatch(dev); + nvc0_grctx_generate_macro(dev); + nvc0_grctx_generate_m2mf(dev); + nvc0_grctx_generate_unk47xx(dev); + nvc0_grctx_generate_shaders(dev); + nvc0_grctx_generate_unk60xx(dev); + nvc0_grctx_generate_unk64xx(dev); + nvc0_grctx_generate_tpbus(dev); + nvc0_grctx_generate_ccache(dev); + nvc0_grctx_generate_rop(dev); + nvc0_grctx_generate_gpc(dev); + nvc0_grctx_generate_tp(dev); + + nv_wr32(dev, 0x404154, 0x00000000); + + /* fuc "mmio list" writes */ + for (i = 0; i < grch->mmio_nr * 8; i += 8) { + u32 reg = nv_ro32(grch->mmio, i + 0); + nv_wr32(dev, reg, nv_ro32(grch->mmio, i + 4)); + } + + for (tp = 0, id = 0; tp < 4; tp++) { + for (gpc = 0; gpc < priv->gpc_nr; gpc++) { + if (tp < priv->tp_nr[gpc]) { + nv_wr32(dev, TP_UNIT(gpc, tp, 0x698), id); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x4e8), id); + nv_wr32(dev, GPC_UNIT(gpc, 0x0c10 + tp * 4), id); + nv_wr32(dev, TP_UNIT(gpc, tp, 0x088), id); + id++; + } + + nv_wr32(dev, GPC_UNIT(gpc, 0x0c08), priv->tp_nr[gpc]); + nv_wr32(dev, GPC_UNIT(gpc, 0x0c8c), priv->tp_nr[gpc]); + } + } + + tmp = 0; + for (i = 0; i < priv->gpc_nr; i++) + tmp |= priv->tp_nr[i] << (i * 4); + nv_wr32(dev, 0x406028, tmp); + nv_wr32(dev, 0x405870, tmp); + + nv_wr32(dev, 0x40602c, 0x00000000); + nv_wr32(dev, 0x405874, 0x00000000); + nv_wr32(dev, 0x406030, 0x00000000); + nv_wr32(dev, 0x405878, 0x00000000); + nv_wr32(dev, 0x406034, 0x00000000); + nv_wr32(dev, 0x40587c, 0x00000000); + + if (1) { + u8 tpnr[GPC_MAX], data[TP_MAX]; + + memcpy(tpnr, priv->tp_nr, sizeof(priv->tp_nr)); + memset(data, 0x1f, sizeof(data)); + + gpc = -1; + for (tp = 0; tp < priv->tp_total; tp++) { + do { + gpc = (gpc + 1) % priv->gpc_nr; + } while (!tpnr[gpc]); + tpnr[gpc]--; + data[tp] = gpc; + } + + for (i = 0; i < 4; i++) + nv_wr32(dev, 0x4060a8 + (i * 4), ((u32 *)data)[i]); + } + + if (1) { + u32 data[6] = {}, data2[2] = {}; + u8 tpnr[GPC_MAX]; + u8 shift, ntpcv; + + /* calculate first set of magics */ + memcpy(tpnr, priv->tp_nr, sizeof(priv->tp_nr)); + + gpc = -1; + for (tp = 0; tp < priv->tp_total; tp++) { + do { + gpc = (gpc + 1) % priv->gpc_nr; + } while (!tpnr[gpc]); + tpnr[gpc]--; + + data[tp / 6] |= gpc << ((tp % 6) * 5); + } + + for (; tp < 32; tp++) + data[tp / 6] |= 7 << ((tp % 6) * 5); + + /* and the second... */ + shift = 0; + ntpcv = priv->tp_total; + while (!(ntpcv & (1 << 4))) { + ntpcv <<= 1; + shift++; + } + + data2[0] = (ntpcv << 16); + data2[0] |= (shift << 21); + data2[0] |= (((1 << (0 + 5)) % ntpcv) << 24); + for (i = 1; i < 7; i++) + data2[1] |= ((1 << (i + 5)) % ntpcv) << ((i - 1) * 5); + + /* GPC_BROADCAST */ + nv_wr32(dev, 0x418bb8, (priv->tp_total << 8) | + priv->magic_not_rop_nr); + for (i = 0; i < 6; i++) + nv_wr32(dev, 0x418b08 + (i * 4), data[i]); + + /* GPC_BROADCAST.TP_BROADCAST */ + nv_wr32(dev, 0x419bd0, (priv->tp_total << 8) | + priv->magic_not_rop_nr | + data2[0]); + nv_wr32(dev, 0x419be4, data2[1]); + for (i = 0; i < 6; i++) + nv_wr32(dev, 0x419b00 + (i * 4), data[i]); + + /* UNK78xx */ + nv_wr32(dev, 0x4078bc, (priv->tp_total << 8) | + priv->magic_not_rop_nr); + for (i = 0; i < 6; i++) + nv_wr32(dev, 0x40780c + (i * 4), data[i]); + } + + if (1) { + u32 tp_mask = 0, tp_set = 0; + u8 tpnr[GPC_MAX], a, b; + + memcpy(tpnr, priv->tp_nr, sizeof(priv->tp_nr)); + for (gpc = 0; gpc < priv->gpc_nr; gpc++) + tp_mask |= ((1 << priv->tp_nr[gpc]) - 1) << (gpc * 8); + + for (i = 0, gpc = -1, b = -1; i < 32; i++) { + a = (i * (priv->tp_total - 1)) / 32; + if (a != b) { + b = a; + do { + gpc = (gpc + 1) % priv->gpc_nr; + } while (!tpnr[gpc]); + tp = priv->tp_nr[gpc] - tpnr[gpc]--; + + tp_set |= 1 << ((gpc * 8) + tp); + } + + nv_wr32(dev, 0x406800 + (i * 0x20), tp_set); + nv_wr32(dev, 0x406c00 + (i * 0x20), tp_set ^ tp_mask); + } + } + + nv_wr32(dev, 0x400208, 0x80000000); + + nv_icmd(dev, 0x00001000, 0x00000004); + nv_icmd(dev, 0x000000a9, 0x0000ffff); + nv_icmd(dev, 0x00000038, 0x0fac6881); + nv_icmd(dev, 0x0000003d, 0x00000001); + nv_icmd(dev, 0x000000e8, 0x00000400); + nv_icmd(dev, 0x000000e9, 0x00000400); + nv_icmd(dev, 0x000000ea, 0x00000400); + nv_icmd(dev, 0x000000eb, 0x00000400); + nv_icmd(dev, 0x000000ec, 0x00000400); + nv_icmd(dev, 0x000000ed, 0x00000400); + nv_icmd(dev, 0x000000ee, 0x00000400); + nv_icmd(dev, 0x000000ef, 0x00000400); + nv_icmd(dev, 0x00000078, 0x00000300); + nv_icmd(dev, 0x00000079, 0x00000300); + nv_icmd(dev, 0x0000007a, 0x00000300); + nv_icmd(dev, 0x0000007b, 0x00000300); + nv_icmd(dev, 0x0000007c, 0x00000300); + nv_icmd(dev, 0x0000007d, 0x00000300); + nv_icmd(dev, 0x0000007e, 0x00000300); + nv_icmd(dev, 0x0000007f, 0x00000300); + nv_icmd(dev, 0x00000050, 0x00000011); + nv_icmd(dev, 0x00000058, 0x00000008); + nv_icmd(dev, 0x00000059, 0x00000008); + nv_icmd(dev, 0x0000005a, 0x00000008); + nv_icmd(dev, 0x0000005b, 0x00000008); + nv_icmd(dev, 0x0000005c, 0x00000008); + nv_icmd(dev, 0x0000005d, 0x00000008); + nv_icmd(dev, 0x0000005e, 0x00000008); + nv_icmd(dev, 0x0000005f, 0x00000008); + nv_icmd(dev, 0x00000208, 0x00000001); + nv_icmd(dev, 0x00000209, 0x00000001); + nv_icmd(dev, 0x0000020a, 0x00000001); + nv_icmd(dev, 0x0000020b, 0x00000001); + nv_icmd(dev, 0x0000020c, 0x00000001); + nv_icmd(dev, 0x0000020d, 0x00000001); + nv_icmd(dev, 0x0000020e, 0x00000001); + nv_icmd(dev, 0x0000020f, 0x00000001); + nv_icmd(dev, 0x00000081, 0x00000001); + nv_icmd(dev, 0x00000085, 0x00000004); + nv_icmd(dev, 0x00000088, 0x00000400); + nv_icmd(dev, 0x00000090, 0x00000300); + nv_icmd(dev, 0x00000098, 0x00001001); + nv_icmd(dev, 0x000000e3, 0x00000001); + nv_icmd(dev, 0x000000da, 0x00000001); + nv_icmd(dev, 0x000000f8, 0x00000003); + nv_icmd(dev, 0x000000fa, 0x00000001); + nv_icmd(dev, 0x0000009f, 0x0000ffff); + nv_icmd(dev, 0x000000a0, 0x0000ffff); + nv_icmd(dev, 0x000000a1, 0x0000ffff); + nv_icmd(dev, 0x000000a2, 0x0000ffff); + nv_icmd(dev, 0x000000b1, 0x00000001); + nv_icmd(dev, 0x000000b2, 0x00000000); + nv_icmd(dev, 0x000000b3, 0x00000000); + nv_icmd(dev, 0x000000b4, 0x00000000); + nv_icmd(dev, 0x000000b5, 0x00000000); + nv_icmd(dev, 0x000000b6, 0x00000000); + nv_icmd(dev, 0x000000b7, 0x00000000); + nv_icmd(dev, 0x000000b8, 0x00000000); + nv_icmd(dev, 0x000000b9, 0x00000000); + nv_icmd(dev, 0x000000ba, 0x00000000); + nv_icmd(dev, 0x000000bb, 0x00000000); + nv_icmd(dev, 0x000000bc, 0x00000000); + nv_icmd(dev, 0x000000bd, 0x00000000); + nv_icmd(dev, 0x000000be, 0x00000000); + nv_icmd(dev, 0x000000bf, 0x00000000); + nv_icmd(dev, 0x000000c0, 0x00000000); + nv_icmd(dev, 0x000000c1, 0x00000000); + nv_icmd(dev, 0x000000c2, 0x00000000); + nv_icmd(dev, 0x000000c3, 0x00000000); + nv_icmd(dev, 0x000000c4, 0x00000000); + nv_icmd(dev, 0x000000c5, 0x00000000); + nv_icmd(dev, 0x000000c6, 0x00000000); + nv_icmd(dev, 0x000000c7, 0x00000000); + nv_icmd(dev, 0x000000c8, 0x00000000); + nv_icmd(dev, 0x000000c9, 0x00000000); + nv_icmd(dev, 0x000000ca, 0x00000000); + nv_icmd(dev, 0x000000cb, 0x00000000); + nv_icmd(dev, 0x000000cc, 0x00000000); + nv_icmd(dev, 0x000000cd, 0x00000000); + nv_icmd(dev, 0x000000ce, 0x00000000); + nv_icmd(dev, 0x000000cf, 0x00000000); + nv_icmd(dev, 0x000000d0, 0x00000000); + nv_icmd(dev, 0x000000d1, 0x00000000); + nv_icmd(dev, 0x000000d2, 0x00000000); + nv_icmd(dev, 0x000000d3, 0x00000000); + nv_icmd(dev, 0x000000d4, 0x00000000); + nv_icmd(dev, 0x000000d5, 0x00000000); + nv_icmd(dev, 0x000000d6, 0x00000000); + nv_icmd(dev, 0x000000d7, 0x00000000); + nv_icmd(dev, 0x000000d8, 0x00000000); + nv_icmd(dev, 0x000000d9, 0x00000000); + nv_icmd(dev, 0x00000210, 0x00000040); + nv_icmd(dev, 0x00000211, 0x00000040); + nv_icmd(dev, 0x00000212, 0x00000040); + nv_icmd(dev, 0x00000213, 0x00000040); + nv_icmd(dev, 0x00000214, 0x00000040); + nv_icmd(dev, 0x00000215, 0x00000040); + nv_icmd(dev, 0x00000216, 0x00000040); + nv_icmd(dev, 0x00000217, 0x00000040); + if (dev_priv->chipset == 0xd9) { + for (i = 0x0400; i <= 0x0417; i++) + nv_icmd(dev, i, 0x00000040); + } + nv_icmd(dev, 0x00000218, 0x0000c080); + nv_icmd(dev, 0x00000219, 0x0000c080); + nv_icmd(dev, 0x0000021a, 0x0000c080); + nv_icmd(dev, 0x0000021b, 0x0000c080); + nv_icmd(dev, 0x0000021c, 0x0000c080); + nv_icmd(dev, 0x0000021d, 0x0000c080); + nv_icmd(dev, 0x0000021e, 0x0000c080); + nv_icmd(dev, 0x0000021f, 0x0000c080); + if (dev_priv->chipset == 0xd9) { + for (i = 0x0440; i <= 0x0457; i++) + nv_icmd(dev, i, 0x0000c080); + } + nv_icmd(dev, 0x000000ad, 0x0000013e); + nv_icmd(dev, 0x000000e1, 0x00000010); + nv_icmd(dev, 0x00000290, 0x00000000); + nv_icmd(dev, 0x00000291, 0x00000000); + nv_icmd(dev, 0x00000292, 0x00000000); + nv_icmd(dev, 0x00000293, 0x00000000); + nv_icmd(dev, 0x00000294, 0x00000000); + nv_icmd(dev, 0x00000295, 0x00000000); + nv_icmd(dev, 0x00000296, 0x00000000); + nv_icmd(dev, 0x00000297, 0x00000000); + nv_icmd(dev, 0x00000298, 0x00000000); + nv_icmd(dev, 0x00000299, 0x00000000); + nv_icmd(dev, 0x0000029a, 0x00000000); + nv_icmd(dev, 0x0000029b, 0x00000000); + nv_icmd(dev, 0x0000029c, 0x00000000); + nv_icmd(dev, 0x0000029d, 0x00000000); + nv_icmd(dev, 0x0000029e, 0x00000000); + nv_icmd(dev, 0x0000029f, 0x00000000); + nv_icmd(dev, 0x000003b0, 0x00000000); + nv_icmd(dev, 0x000003b1, 0x00000000); + nv_icmd(dev, 0x000003b2, 0x00000000); + nv_icmd(dev, 0x000003b3, 0x00000000); + nv_icmd(dev, 0x000003b4, 0x00000000); + nv_icmd(dev, 0x000003b5, 0x00000000); + nv_icmd(dev, 0x000003b6, 0x00000000); + nv_icmd(dev, 0x000003b7, 0x00000000); + nv_icmd(dev, 0x000003b8, 0x00000000); + nv_icmd(dev, 0x000003b9, 0x00000000); + nv_icmd(dev, 0x000003ba, 0x00000000); + nv_icmd(dev, 0x000003bb, 0x00000000); + nv_icmd(dev, 0x000003bc, 0x00000000); + nv_icmd(dev, 0x000003bd, 0x00000000); + nv_icmd(dev, 0x000003be, 0x00000000); + nv_icmd(dev, 0x000003bf, 0x00000000); + nv_icmd(dev, 0x000002a0, 0x00000000); + nv_icmd(dev, 0x000002a1, 0x00000000); + nv_icmd(dev, 0x000002a2, 0x00000000); + nv_icmd(dev, 0x000002a3, 0x00000000); + nv_icmd(dev, 0x000002a4, 0x00000000); + nv_icmd(dev, 0x000002a5, 0x00000000); + nv_icmd(dev, 0x000002a6, 0x00000000); + nv_icmd(dev, 0x000002a7, 0x00000000); + nv_icmd(dev, 0x000002a8, 0x00000000); + nv_icmd(dev, 0x000002a9, 0x00000000); + nv_icmd(dev, 0x000002aa, 0x00000000); + nv_icmd(dev, 0x000002ab, 0x00000000); + nv_icmd(dev, 0x000002ac, 0x00000000); + nv_icmd(dev, 0x000002ad, 0x00000000); + nv_icmd(dev, 0x000002ae, 0x00000000); + nv_icmd(dev, 0x000002af, 0x00000000); + nv_icmd(dev, 0x00000420, 0x00000000); + nv_icmd(dev, 0x00000421, 0x00000000); + nv_icmd(dev, 0x00000422, 0x00000000); + nv_icmd(dev, 0x00000423, 0x00000000); + nv_icmd(dev, 0x00000424, 0x00000000); + nv_icmd(dev, 0x00000425, 0x00000000); + nv_icmd(dev, 0x00000426, 0x00000000); + nv_icmd(dev, 0x00000427, 0x00000000); + nv_icmd(dev, 0x00000428, 0x00000000); + nv_icmd(dev, 0x00000429, 0x00000000); + nv_icmd(dev, 0x0000042a, 0x00000000); + nv_icmd(dev, 0x0000042b, 0x00000000); + nv_icmd(dev, 0x0000042c, 0x00000000); + nv_icmd(dev, 0x0000042d, 0x00000000); + nv_icmd(dev, 0x0000042e, 0x00000000); + nv_icmd(dev, 0x0000042f, 0x00000000); + nv_icmd(dev, 0x000002b0, 0x00000000); + nv_icmd(dev, 0x000002b1, 0x00000000); + nv_icmd(dev, 0x000002b2, 0x00000000); + nv_icmd(dev, 0x000002b3, 0x00000000); + nv_icmd(dev, 0x000002b4, 0x00000000); + nv_icmd(dev, 0x000002b5, 0x00000000); + nv_icmd(dev, 0x000002b6, 0x00000000); + nv_icmd(dev, 0x000002b7, 0x00000000); + nv_icmd(dev, 0x000002b8, 0x00000000); + nv_icmd(dev, 0x000002b9, 0x00000000); + nv_icmd(dev, 0x000002ba, 0x00000000); + nv_icmd(dev, 0x000002bb, 0x00000000); + nv_icmd(dev, 0x000002bc, 0x00000000); + nv_icmd(dev, 0x000002bd, 0x00000000); + nv_icmd(dev, 0x000002be, 0x00000000); + nv_icmd(dev, 0x000002bf, 0x00000000); + nv_icmd(dev, 0x00000430, 0x00000000); + nv_icmd(dev, 0x00000431, 0x00000000); + nv_icmd(dev, 0x00000432, 0x00000000); + nv_icmd(dev, 0x00000433, 0x00000000); + nv_icmd(dev, 0x00000434, 0x00000000); + nv_icmd(dev, 0x00000435, 0x00000000); + nv_icmd(dev, 0x00000436, 0x00000000); + nv_icmd(dev, 0x00000437, 0x00000000); + nv_icmd(dev, 0x00000438, 0x00000000); + nv_icmd(dev, 0x00000439, 0x00000000); + nv_icmd(dev, 0x0000043a, 0x00000000); + nv_icmd(dev, 0x0000043b, 0x00000000); + nv_icmd(dev, 0x0000043c, 0x00000000); + nv_icmd(dev, 0x0000043d, 0x00000000); + nv_icmd(dev, 0x0000043e, 0x00000000); + nv_icmd(dev, 0x0000043f, 0x00000000); + nv_icmd(dev, 0x000002c0, 0x00000000); + nv_icmd(dev, 0x000002c1, 0x00000000); + nv_icmd(dev, 0x000002c2, 0x00000000); + nv_icmd(dev, 0x000002c3, 0x00000000); + nv_icmd(dev, 0x000002c4, 0x00000000); + nv_icmd(dev, 0x000002c5, 0x00000000); + nv_icmd(dev, 0x000002c6, 0x00000000); + nv_icmd(dev, 0x000002c7, 0x00000000); + nv_icmd(dev, 0x000002c8, 0x00000000); + nv_icmd(dev, 0x000002c9, 0x00000000); + nv_icmd(dev, 0x000002ca, 0x00000000); + nv_icmd(dev, 0x000002cb, 0x00000000); + nv_icmd(dev, 0x000002cc, 0x00000000); + nv_icmd(dev, 0x000002cd, 0x00000000); + nv_icmd(dev, 0x000002ce, 0x00000000); + nv_icmd(dev, 0x000002cf, 0x00000000); + nv_icmd(dev, 0x000004d0, 0x00000000); + nv_icmd(dev, 0x000004d1, 0x00000000); + nv_icmd(dev, 0x000004d2, 0x00000000); + nv_icmd(dev, 0x000004d3, 0x00000000); + nv_icmd(dev, 0x000004d4, 0x00000000); + nv_icmd(dev, 0x000004d5, 0x00000000); + nv_icmd(dev, 0x000004d6, 0x00000000); + nv_icmd(dev, 0x000004d7, 0x00000000); + nv_icmd(dev, 0x000004d8, 0x00000000); + nv_icmd(dev, 0x000004d9, 0x00000000); + nv_icmd(dev, 0x000004da, 0x00000000); + nv_icmd(dev, 0x000004db, 0x00000000); + nv_icmd(dev, 0x000004dc, 0x00000000); + nv_icmd(dev, 0x000004dd, 0x00000000); + nv_icmd(dev, 0x000004de, 0x00000000); + nv_icmd(dev, 0x000004df, 0x00000000); + nv_icmd(dev, 0x00000720, 0x00000000); + nv_icmd(dev, 0x00000721, 0x00000000); + nv_icmd(dev, 0x00000722, 0x00000000); + nv_icmd(dev, 0x00000723, 0x00000000); + nv_icmd(dev, 0x00000724, 0x00000000); + nv_icmd(dev, 0x00000725, 0x00000000); + nv_icmd(dev, 0x00000726, 0x00000000); + nv_icmd(dev, 0x00000727, 0x00000000); + nv_icmd(dev, 0x00000728, 0x00000000); + nv_icmd(dev, 0x00000729, 0x00000000); + nv_icmd(dev, 0x0000072a, 0x00000000); + nv_icmd(dev, 0x0000072b, 0x00000000); + nv_icmd(dev, 0x0000072c, 0x00000000); + nv_icmd(dev, 0x0000072d, 0x00000000); + nv_icmd(dev, 0x0000072e, 0x00000000); + nv_icmd(dev, 0x0000072f, 0x00000000); + nv_icmd(dev, 0x000008c0, 0x00000000); + nv_icmd(dev, 0x000008c1, 0x00000000); + nv_icmd(dev, 0x000008c2, 0x00000000); + nv_icmd(dev, 0x000008c3, 0x00000000); + nv_icmd(dev, 0x000008c4, 0x00000000); + nv_icmd(dev, 0x000008c5, 0x00000000); + nv_icmd(dev, 0x000008c6, 0x00000000); + nv_icmd(dev, 0x000008c7, 0x00000000); + nv_icmd(dev, 0x000008c8, 0x00000000); + nv_icmd(dev, 0x000008c9, 0x00000000); + nv_icmd(dev, 0x000008ca, 0x00000000); + nv_icmd(dev, 0x000008cb, 0x00000000); + nv_icmd(dev, 0x000008cc, 0x00000000); + nv_icmd(dev, 0x000008cd, 0x00000000); + nv_icmd(dev, 0x000008ce, 0x00000000); + nv_icmd(dev, 0x000008cf, 0x00000000); + nv_icmd(dev, 0x00000890, 0x00000000); + nv_icmd(dev, 0x00000891, 0x00000000); + nv_icmd(dev, 0x00000892, 0x00000000); + nv_icmd(dev, 0x00000893, 0x00000000); + nv_icmd(dev, 0x00000894, 0x00000000); + nv_icmd(dev, 0x00000895, 0x00000000); + nv_icmd(dev, 0x00000896, 0x00000000); + nv_icmd(dev, 0x00000897, 0x00000000); + nv_icmd(dev, 0x00000898, 0x00000000); + nv_icmd(dev, 0x00000899, 0x00000000); + nv_icmd(dev, 0x0000089a, 0x00000000); + nv_icmd(dev, 0x0000089b, 0x00000000); + nv_icmd(dev, 0x0000089c, 0x00000000); + nv_icmd(dev, 0x0000089d, 0x00000000); + nv_icmd(dev, 0x0000089e, 0x00000000); + nv_icmd(dev, 0x0000089f, 0x00000000); + nv_icmd(dev, 0x000008e0, 0x00000000); + nv_icmd(dev, 0x000008e1, 0x00000000); + nv_icmd(dev, 0x000008e2, 0x00000000); + nv_icmd(dev, 0x000008e3, 0x00000000); + nv_icmd(dev, 0x000008e4, 0x00000000); + nv_icmd(dev, 0x000008e5, 0x00000000); + nv_icmd(dev, 0x000008e6, 0x00000000); + nv_icmd(dev, 0x000008e7, 0x00000000); + nv_icmd(dev, 0x000008e8, 0x00000000); + nv_icmd(dev, 0x000008e9, 0x00000000); + nv_icmd(dev, 0x000008ea, 0x00000000); + nv_icmd(dev, 0x000008eb, 0x00000000); + nv_icmd(dev, 0x000008ec, 0x00000000); + nv_icmd(dev, 0x000008ed, 0x00000000); + nv_icmd(dev, 0x000008ee, 0x00000000); + nv_icmd(dev, 0x000008ef, 0x00000000); + nv_icmd(dev, 0x000008a0, 0x00000000); + nv_icmd(dev, 0x000008a1, 0x00000000); + nv_icmd(dev, 0x000008a2, 0x00000000); + nv_icmd(dev, 0x000008a3, 0x00000000); + nv_icmd(dev, 0x000008a4, 0x00000000); + nv_icmd(dev, 0x000008a5, 0x00000000); + nv_icmd(dev, 0x000008a6, 0x00000000); + nv_icmd(dev, 0x000008a7, 0x00000000); + nv_icmd(dev, 0x000008a8, 0x00000000); + nv_icmd(dev, 0x000008a9, 0x00000000); + nv_icmd(dev, 0x000008aa, 0x00000000); + nv_icmd(dev, 0x000008ab, 0x00000000); + nv_icmd(dev, 0x000008ac, 0x00000000); + nv_icmd(dev, 0x000008ad, 0x00000000); + nv_icmd(dev, 0x000008ae, 0x00000000); + nv_icmd(dev, 0x000008af, 0x00000000); + nv_icmd(dev, 0x000008f0, 0x00000000); + nv_icmd(dev, 0x000008f1, 0x00000000); + nv_icmd(dev, 0x000008f2, 0x00000000); + nv_icmd(dev, 0x000008f3, 0x00000000); + nv_icmd(dev, 0x000008f4, 0x00000000); + nv_icmd(dev, 0x000008f5, 0x00000000); + nv_icmd(dev, 0x000008f6, 0x00000000); + nv_icmd(dev, 0x000008f7, 0x00000000); + nv_icmd(dev, 0x000008f8, 0x00000000); + nv_icmd(dev, 0x000008f9, 0x00000000); + nv_icmd(dev, 0x000008fa, 0x00000000); + nv_icmd(dev, 0x000008fb, 0x00000000); + nv_icmd(dev, 0x000008fc, 0x00000000); + nv_icmd(dev, 0x000008fd, 0x00000000); + nv_icmd(dev, 0x000008fe, 0x00000000); + nv_icmd(dev, 0x000008ff, 0x00000000); + nv_icmd(dev, 0x0000094c, 0x000000ff); + nv_icmd(dev, 0x0000094d, 0xffffffff); + nv_icmd(dev, 0x0000094e, 0x00000002); + nv_icmd(dev, 0x000002ec, 0x00000001); + nv_icmd(dev, 0x00000303, 0x00000001); + nv_icmd(dev, 0x000002e6, 0x00000001); + nv_icmd(dev, 0x00000466, 0x00000052); + nv_icmd(dev, 0x00000301, 0x3f800000); + nv_icmd(dev, 0x00000304, 0x30201000); + nv_icmd(dev, 0x00000305, 0x70605040); + nv_icmd(dev, 0x00000306, 0xb8a89888); + nv_icmd(dev, 0x00000307, 0xf8e8d8c8); + nv_icmd(dev, 0x0000030a, 0x00ffff00); + nv_icmd(dev, 0x0000030b, 0x0000001a); + nv_icmd(dev, 0x0000030c, 0x00000001); + nv_icmd(dev, 0x00000318, 0x00000001); + nv_icmd(dev, 0x00000340, 0x00000000); + nv_icmd(dev, 0x00000375, 0x00000001); + nv_icmd(dev, 0x00000351, 0x00000100); + nv_icmd(dev, 0x0000037d, 0x00000006); + nv_icmd(dev, 0x000003a0, 0x00000002); + nv_icmd(dev, 0x000003aa, 0x00000001); + nv_icmd(dev, 0x000003a9, 0x00000001); + nv_icmd(dev, 0x00000380, 0x00000001); + nv_icmd(dev, 0x00000360, 0x00000040); + nv_icmd(dev, 0x00000366, 0x00000000); + nv_icmd(dev, 0x00000367, 0x00000000); + nv_icmd(dev, 0x00000368, 0x00001fff); + nv_icmd(dev, 0x00000370, 0x00000000); + nv_icmd(dev, 0x00000371, 0x00000000); + nv_icmd(dev, 0x00000372, 0x003fffff); + nv_icmd(dev, 0x0000037a, 0x00000012); + nv_icmd(dev, 0x000005e0, 0x00000022); + nv_icmd(dev, 0x000005e1, 0x00000022); + nv_icmd(dev, 0x000005e2, 0x00000022); + nv_icmd(dev, 0x000005e3, 0x00000022); + nv_icmd(dev, 0x000005e4, 0x00000022); + nv_icmd(dev, 0x00000619, 0x00000003); + nv_icmd(dev, 0x00000811, 0x00000003); + nv_icmd(dev, 0x00000812, 0x00000004); + nv_icmd(dev, 0x00000813, 0x00000006); + nv_icmd(dev, 0x00000814, 0x00000008); + nv_icmd(dev, 0x00000815, 0x0000000b); + nv_icmd(dev, 0x00000800, 0x00000001); + nv_icmd(dev, 0x00000801, 0x00000001); + nv_icmd(dev, 0x00000802, 0x00000001); + nv_icmd(dev, 0x00000803, 0x00000001); + nv_icmd(dev, 0x00000804, 0x00000001); + nv_icmd(dev, 0x00000805, 0x00000001); + nv_icmd(dev, 0x00000632, 0x00000001); + nv_icmd(dev, 0x00000633, 0x00000002); + nv_icmd(dev, 0x00000634, 0x00000003); + nv_icmd(dev, 0x00000635, 0x00000004); + nv_icmd(dev, 0x00000654, 0x3f800000); + nv_icmd(dev, 0x00000657, 0x3f800000); + nv_icmd(dev, 0x00000655, 0x3f800000); + nv_icmd(dev, 0x00000656, 0x3f800000); + nv_icmd(dev, 0x000006cd, 0x3f800000); + nv_icmd(dev, 0x000007f5, 0x3f800000); + nv_icmd(dev, 0x000007dc, 0x39291909); + nv_icmd(dev, 0x000007dd, 0x79695949); + nv_icmd(dev, 0x000007de, 0xb9a99989); + nv_icmd(dev, 0x000007df, 0xf9e9d9c9); + nv_icmd(dev, 0x000007e8, 0x00003210); + nv_icmd(dev, 0x000007e9, 0x00007654); + nv_icmd(dev, 0x000007ea, 0x00000098); + nv_icmd(dev, 0x000007ec, 0x39291909); + nv_icmd(dev, 0x000007ed, 0x79695949); + nv_icmd(dev, 0x000007ee, 0xb9a99989); + nv_icmd(dev, 0x000007ef, 0xf9e9d9c9); + nv_icmd(dev, 0x000007f0, 0x00003210); + nv_icmd(dev, 0x000007f1, 0x00007654); + nv_icmd(dev, 0x000007f2, 0x00000098); + nv_icmd(dev, 0x000005a5, 0x00000001); + nv_icmd(dev, 0x00000980, 0x00000000); + nv_icmd(dev, 0x00000981, 0x00000000); + nv_icmd(dev, 0x00000982, 0x00000000); + nv_icmd(dev, 0x00000983, 0x00000000); + nv_icmd(dev, 0x00000984, 0x00000000); + nv_icmd(dev, 0x00000985, 0x00000000); + nv_icmd(dev, 0x00000986, 0x00000000); + nv_icmd(dev, 0x00000987, 0x00000000); + nv_icmd(dev, 0x00000988, 0x00000000); + nv_icmd(dev, 0x00000989, 0x00000000); + nv_icmd(dev, 0x0000098a, 0x00000000); + nv_icmd(dev, 0x0000098b, 0x00000000); + nv_icmd(dev, 0x0000098c, 0x00000000); + nv_icmd(dev, 0x0000098d, 0x00000000); + nv_icmd(dev, 0x0000098e, 0x00000000); + nv_icmd(dev, 0x0000098f, 0x00000000); + nv_icmd(dev, 0x00000990, 0x00000000); + nv_icmd(dev, 0x00000991, 0x00000000); + nv_icmd(dev, 0x00000992, 0x00000000); + nv_icmd(dev, 0x00000993, 0x00000000); + nv_icmd(dev, 0x00000994, 0x00000000); + nv_icmd(dev, 0x00000995, 0x00000000); + nv_icmd(dev, 0x00000996, 0x00000000); + nv_icmd(dev, 0x00000997, 0x00000000); + nv_icmd(dev, 0x00000998, 0x00000000); + nv_icmd(dev, 0x00000999, 0x00000000); + nv_icmd(dev, 0x0000099a, 0x00000000); + nv_icmd(dev, 0x0000099b, 0x00000000); + nv_icmd(dev, 0x0000099c, 0x00000000); + nv_icmd(dev, 0x0000099d, 0x00000000); + nv_icmd(dev, 0x0000099e, 0x00000000); + nv_icmd(dev, 0x0000099f, 0x00000000); + nv_icmd(dev, 0x000009a0, 0x00000000); + nv_icmd(dev, 0x000009a1, 0x00000000); + nv_icmd(dev, 0x000009a2, 0x00000000); + nv_icmd(dev, 0x000009a3, 0x00000000); + nv_icmd(dev, 0x000009a4, 0x00000000); + nv_icmd(dev, 0x000009a5, 0x00000000); + nv_icmd(dev, 0x000009a6, 0x00000000); + nv_icmd(dev, 0x000009a7, 0x00000000); + nv_icmd(dev, 0x000009a8, 0x00000000); + nv_icmd(dev, 0x000009a9, 0x00000000); + nv_icmd(dev, 0x000009aa, 0x00000000); + nv_icmd(dev, 0x000009ab, 0x00000000); + nv_icmd(dev, 0x000009ac, 0x00000000); + nv_icmd(dev, 0x000009ad, 0x00000000); + nv_icmd(dev, 0x000009ae, 0x00000000); + nv_icmd(dev, 0x000009af, 0x00000000); + nv_icmd(dev, 0x000009b0, 0x00000000); + nv_icmd(dev, 0x000009b1, 0x00000000); + nv_icmd(dev, 0x000009b2, 0x00000000); + nv_icmd(dev, 0x000009b3, 0x00000000); + nv_icmd(dev, 0x000009b4, 0x00000000); + nv_icmd(dev, 0x000009b5, 0x00000000); + nv_icmd(dev, 0x000009b6, 0x00000000); + nv_icmd(dev, 0x000009b7, 0x00000000); + nv_icmd(dev, 0x000009b8, 0x00000000); + nv_icmd(dev, 0x000009b9, 0x00000000); + nv_icmd(dev, 0x000009ba, 0x00000000); + nv_icmd(dev, 0x000009bb, 0x00000000); + nv_icmd(dev, 0x000009bc, 0x00000000); + nv_icmd(dev, 0x000009bd, 0x00000000); + nv_icmd(dev, 0x000009be, 0x00000000); + nv_icmd(dev, 0x000009bf, 0x00000000); + nv_icmd(dev, 0x000009c0, 0x00000000); + nv_icmd(dev, 0x000009c1, 0x00000000); + nv_icmd(dev, 0x000009c2, 0x00000000); + nv_icmd(dev, 0x000009c3, 0x00000000); + nv_icmd(dev, 0x000009c4, 0x00000000); + nv_icmd(dev, 0x000009c5, 0x00000000); + nv_icmd(dev, 0x000009c6, 0x00000000); + nv_icmd(dev, 0x000009c7, 0x00000000); + nv_icmd(dev, 0x000009c8, 0x00000000); + nv_icmd(dev, 0x000009c9, 0x00000000); + nv_icmd(dev, 0x000009ca, 0x00000000); + nv_icmd(dev, 0x000009cb, 0x00000000); + nv_icmd(dev, 0x000009cc, 0x00000000); + nv_icmd(dev, 0x000009cd, 0x00000000); + nv_icmd(dev, 0x000009ce, 0x00000000); + nv_icmd(dev, 0x000009cf, 0x00000000); + nv_icmd(dev, 0x000009d0, 0x00000000); + nv_icmd(dev, 0x000009d1, 0x00000000); + nv_icmd(dev, 0x000009d2, 0x00000000); + nv_icmd(dev, 0x000009d3, 0x00000000); + nv_icmd(dev, 0x000009d4, 0x00000000); + nv_icmd(dev, 0x000009d5, 0x00000000); + nv_icmd(dev, 0x000009d6, 0x00000000); + nv_icmd(dev, 0x000009d7, 0x00000000); + nv_icmd(dev, 0x000009d8, 0x00000000); + nv_icmd(dev, 0x000009d9, 0x00000000); + nv_icmd(dev, 0x000009da, 0x00000000); + nv_icmd(dev, 0x000009db, 0x00000000); + nv_icmd(dev, 0x000009dc, 0x00000000); + nv_icmd(dev, 0x000009dd, 0x00000000); + nv_icmd(dev, 0x000009de, 0x00000000); + nv_icmd(dev, 0x000009df, 0x00000000); + nv_icmd(dev, 0x000009e0, 0x00000000); + nv_icmd(dev, 0x000009e1, 0x00000000); + nv_icmd(dev, 0x000009e2, 0x00000000); + nv_icmd(dev, 0x000009e3, 0x00000000); + nv_icmd(dev, 0x000009e4, 0x00000000); + nv_icmd(dev, 0x000009e5, 0x00000000); + nv_icmd(dev, 0x000009e6, 0x00000000); + nv_icmd(dev, 0x000009e7, 0x00000000); + nv_icmd(dev, 0x000009e8, 0x00000000); + nv_icmd(dev, 0x000009e9, 0x00000000); + nv_icmd(dev, 0x000009ea, 0x00000000); + nv_icmd(dev, 0x000009eb, 0x00000000); + nv_icmd(dev, 0x000009ec, 0x00000000); + nv_icmd(dev, 0x000009ed, 0x00000000); + nv_icmd(dev, 0x000009ee, 0x00000000); + nv_icmd(dev, 0x000009ef, 0x00000000); + nv_icmd(dev, 0x000009f0, 0x00000000); + nv_icmd(dev, 0x000009f1, 0x00000000); + nv_icmd(dev, 0x000009f2, 0x00000000); + nv_icmd(dev, 0x000009f3, 0x00000000); + nv_icmd(dev, 0x000009f4, 0x00000000); + nv_icmd(dev, 0x000009f5, 0x00000000); + nv_icmd(dev, 0x000009f6, 0x00000000); + nv_icmd(dev, 0x000009f7, 0x00000000); + nv_icmd(dev, 0x000009f8, 0x00000000); + nv_icmd(dev, 0x000009f9, 0x00000000); + nv_icmd(dev, 0x000009fa, 0x00000000); + nv_icmd(dev, 0x000009fb, 0x00000000); + nv_icmd(dev, 0x000009fc, 0x00000000); + nv_icmd(dev, 0x000009fd, 0x00000000); + nv_icmd(dev, 0x000009fe, 0x00000000); + nv_icmd(dev, 0x000009ff, 0x00000000); + nv_icmd(dev, 0x00000468, 0x00000004); + nv_icmd(dev, 0x0000046c, 0x00000001); + nv_icmd(dev, 0x00000470, 0x00000000); + nv_icmd(dev, 0x00000471, 0x00000000); + nv_icmd(dev, 0x00000472, 0x00000000); + nv_icmd(dev, 0x00000473, 0x00000000); + nv_icmd(dev, 0x00000474, 0x00000000); + nv_icmd(dev, 0x00000475, 0x00000000); + nv_icmd(dev, 0x00000476, 0x00000000); + nv_icmd(dev, 0x00000477, 0x00000000); + nv_icmd(dev, 0x00000478, 0x00000000); + nv_icmd(dev, 0x00000479, 0x00000000); + nv_icmd(dev, 0x0000047a, 0x00000000); + nv_icmd(dev, 0x0000047b, 0x00000000); + nv_icmd(dev, 0x0000047c, 0x00000000); + nv_icmd(dev, 0x0000047d, 0x00000000); + nv_icmd(dev, 0x0000047e, 0x00000000); + nv_icmd(dev, 0x0000047f, 0x00000000); + nv_icmd(dev, 0x00000480, 0x00000000); + nv_icmd(dev, 0x00000481, 0x00000000); + nv_icmd(dev, 0x00000482, 0x00000000); + nv_icmd(dev, 0x00000483, 0x00000000); + nv_icmd(dev, 0x00000484, 0x00000000); + nv_icmd(dev, 0x00000485, 0x00000000); + nv_icmd(dev, 0x00000486, 0x00000000); + nv_icmd(dev, 0x00000487, 0x00000000); + nv_icmd(dev, 0x00000488, 0x00000000); + nv_icmd(dev, 0x00000489, 0x00000000); + nv_icmd(dev, 0x0000048a, 0x00000000); + nv_icmd(dev, 0x0000048b, 0x00000000); + nv_icmd(dev, 0x0000048c, 0x00000000); + nv_icmd(dev, 0x0000048d, 0x00000000); + nv_icmd(dev, 0x0000048e, 0x00000000); + nv_icmd(dev, 0x0000048f, 0x00000000); + nv_icmd(dev, 0x00000490, 0x00000000); + nv_icmd(dev, 0x00000491, 0x00000000); + nv_icmd(dev, 0x00000492, 0x00000000); + nv_icmd(dev, 0x00000493, 0x00000000); + nv_icmd(dev, 0x00000494, 0x00000000); + nv_icmd(dev, 0x00000495, 0x00000000); + nv_icmd(dev, 0x00000496, 0x00000000); + nv_icmd(dev, 0x00000497, 0x00000000); + nv_icmd(dev, 0x00000498, 0x00000000); + nv_icmd(dev, 0x00000499, 0x00000000); + nv_icmd(dev, 0x0000049a, 0x00000000); + nv_icmd(dev, 0x0000049b, 0x00000000); + nv_icmd(dev, 0x0000049c, 0x00000000); + nv_icmd(dev, 0x0000049d, 0x00000000); + nv_icmd(dev, 0x0000049e, 0x00000000); + nv_icmd(dev, 0x0000049f, 0x00000000); + nv_icmd(dev, 0x000004a0, 0x00000000); + nv_icmd(dev, 0x000004a1, 0x00000000); + nv_icmd(dev, 0x000004a2, 0x00000000); + nv_icmd(dev, 0x000004a3, 0x00000000); + nv_icmd(dev, 0x000004a4, 0x00000000); + nv_icmd(dev, 0x000004a5, 0x00000000); + nv_icmd(dev, 0x000004a6, 0x00000000); + nv_icmd(dev, 0x000004a7, 0x00000000); + nv_icmd(dev, 0x000004a8, 0x00000000); + nv_icmd(dev, 0x000004a9, 0x00000000); + nv_icmd(dev, 0x000004aa, 0x00000000); + nv_icmd(dev, 0x000004ab, 0x00000000); + nv_icmd(dev, 0x000004ac, 0x00000000); + nv_icmd(dev, 0x000004ad, 0x00000000); + nv_icmd(dev, 0x000004ae, 0x00000000); + nv_icmd(dev, 0x000004af, 0x00000000); + nv_icmd(dev, 0x000004b0, 0x00000000); + nv_icmd(dev, 0x000004b1, 0x00000000); + nv_icmd(dev, 0x000004b2, 0x00000000); + nv_icmd(dev, 0x000004b3, 0x00000000); + nv_icmd(dev, 0x000004b4, 0x00000000); + nv_icmd(dev, 0x000004b5, 0x00000000); + nv_icmd(dev, 0x000004b6, 0x00000000); + nv_icmd(dev, 0x000004b7, 0x00000000); + nv_icmd(dev, 0x000004b8, 0x00000000); + nv_icmd(dev, 0x000004b9, 0x00000000); + nv_icmd(dev, 0x000004ba, 0x00000000); + nv_icmd(dev, 0x000004bb, 0x00000000); + nv_icmd(dev, 0x000004bc, 0x00000000); + nv_icmd(dev, 0x000004bd, 0x00000000); + nv_icmd(dev, 0x000004be, 0x00000000); + nv_icmd(dev, 0x000004bf, 0x00000000); + nv_icmd(dev, 0x000004c0, 0x00000000); + nv_icmd(dev, 0x000004c1, 0x00000000); + nv_icmd(dev, 0x000004c2, 0x00000000); + nv_icmd(dev, 0x000004c3, 0x00000000); + nv_icmd(dev, 0x000004c4, 0x00000000); + nv_icmd(dev, 0x000004c5, 0x00000000); + nv_icmd(dev, 0x000004c6, 0x00000000); + nv_icmd(dev, 0x000004c7, 0x00000000); + nv_icmd(dev, 0x000004c8, 0x00000000); + nv_icmd(dev, 0x000004c9, 0x00000000); + nv_icmd(dev, 0x000004ca, 0x00000000); + nv_icmd(dev, 0x000004cb, 0x00000000); + nv_icmd(dev, 0x000004cc, 0x00000000); + nv_icmd(dev, 0x000004cd, 0x00000000); + nv_icmd(dev, 0x000004ce, 0x00000000); + nv_icmd(dev, 0x000004cf, 0x00000000); + nv_icmd(dev, 0x00000510, 0x3f800000); + nv_icmd(dev, 0x00000511, 0x3f800000); + nv_icmd(dev, 0x00000512, 0x3f800000); + nv_icmd(dev, 0x00000513, 0x3f800000); + nv_icmd(dev, 0x00000514, 0x3f800000); + nv_icmd(dev, 0x00000515, 0x3f800000); + nv_icmd(dev, 0x00000516, 0x3f800000); + nv_icmd(dev, 0x00000517, 0x3f800000); + nv_icmd(dev, 0x00000518, 0x3f800000); + nv_icmd(dev, 0x00000519, 0x3f800000); + nv_icmd(dev, 0x0000051a, 0x3f800000); + nv_icmd(dev, 0x0000051b, 0x3f800000); + nv_icmd(dev, 0x0000051c, 0x3f800000); + nv_icmd(dev, 0x0000051d, 0x3f800000); + nv_icmd(dev, 0x0000051e, 0x3f800000); + nv_icmd(dev, 0x0000051f, 0x3f800000); + nv_icmd(dev, 0x00000520, 0x000002b6); + nv_icmd(dev, 0x00000529, 0x00000001); + nv_icmd(dev, 0x00000530, 0xffff0000); + nv_icmd(dev, 0x00000531, 0xffff0000); + nv_icmd(dev, 0x00000532, 0xffff0000); + nv_icmd(dev, 0x00000533, 0xffff0000); + nv_icmd(dev, 0x00000534, 0xffff0000); + nv_icmd(dev, 0x00000535, 0xffff0000); + nv_icmd(dev, 0x00000536, 0xffff0000); + nv_icmd(dev, 0x00000537, 0xffff0000); + nv_icmd(dev, 0x00000538, 0xffff0000); + nv_icmd(dev, 0x00000539, 0xffff0000); + nv_icmd(dev, 0x0000053a, 0xffff0000); + nv_icmd(dev, 0x0000053b, 0xffff0000); + nv_icmd(dev, 0x0000053c, 0xffff0000); + nv_icmd(dev, 0x0000053d, 0xffff0000); + nv_icmd(dev, 0x0000053e, 0xffff0000); + nv_icmd(dev, 0x0000053f, 0xffff0000); + nv_icmd(dev, 0x00000585, 0x0000003f); + nv_icmd(dev, 0x00000576, 0x00000003); + if (dev_priv->chipset == 0xc1 || + dev_priv->chipset == 0xd9) + nv_icmd(dev, 0x0000057b, 0x00000059); + nv_icmd(dev, 0x00000586, 0x00000040); + nv_icmd(dev, 0x00000582, 0x00000080); + nv_icmd(dev, 0x00000583, 0x00000080); + nv_icmd(dev, 0x000005c2, 0x00000001); + nv_icmd(dev, 0x00000638, 0x00000001); + nv_icmd(dev, 0x00000639, 0x00000001); + nv_icmd(dev, 0x0000063a, 0x00000002); + nv_icmd(dev, 0x0000063b, 0x00000001); + nv_icmd(dev, 0x0000063c, 0x00000001); + nv_icmd(dev, 0x0000063d, 0x00000002); + nv_icmd(dev, 0x0000063e, 0x00000001); + nv_icmd(dev, 0x000008b8, 0x00000001); + nv_icmd(dev, 0x000008b9, 0x00000001); + nv_icmd(dev, 0x000008ba, 0x00000001); + nv_icmd(dev, 0x000008bb, 0x00000001); + nv_icmd(dev, 0x000008bc, 0x00000001); + nv_icmd(dev, 0x000008bd, 0x00000001); + nv_icmd(dev, 0x000008be, 0x00000001); + nv_icmd(dev, 0x000008bf, 0x00000001); + nv_icmd(dev, 0x00000900, 0x00000001); + nv_icmd(dev, 0x00000901, 0x00000001); + nv_icmd(dev, 0x00000902, 0x00000001); + nv_icmd(dev, 0x00000903, 0x00000001); + nv_icmd(dev, 0x00000904, 0x00000001); + nv_icmd(dev, 0x00000905, 0x00000001); + nv_icmd(dev, 0x00000906, 0x00000001); + nv_icmd(dev, 0x00000907, 0x00000001); + nv_icmd(dev, 0x00000908, 0x00000002); + nv_icmd(dev, 0x00000909, 0x00000002); + nv_icmd(dev, 0x0000090a, 0x00000002); + nv_icmd(dev, 0x0000090b, 0x00000002); + nv_icmd(dev, 0x0000090c, 0x00000002); + nv_icmd(dev, 0x0000090d, 0x00000002); + nv_icmd(dev, 0x0000090e, 0x00000002); + nv_icmd(dev, 0x0000090f, 0x00000002); + nv_icmd(dev, 0x00000910, 0x00000001); + nv_icmd(dev, 0x00000911, 0x00000001); + nv_icmd(dev, 0x00000912, 0x00000001); + nv_icmd(dev, 0x00000913, 0x00000001); + nv_icmd(dev, 0x00000914, 0x00000001); + nv_icmd(dev, 0x00000915, 0x00000001); + nv_icmd(dev, 0x00000916, 0x00000001); + nv_icmd(dev, 0x00000917, 0x00000001); + nv_icmd(dev, 0x00000918, 0x00000001); + nv_icmd(dev, 0x00000919, 0x00000001); + nv_icmd(dev, 0x0000091a, 0x00000001); + nv_icmd(dev, 0x0000091b, 0x00000001); + nv_icmd(dev, 0x0000091c, 0x00000001); + nv_icmd(dev, 0x0000091d, 0x00000001); + nv_icmd(dev, 0x0000091e, 0x00000001); + nv_icmd(dev, 0x0000091f, 0x00000001); + nv_icmd(dev, 0x00000920, 0x00000002); + nv_icmd(dev, 0x00000921, 0x00000002); + nv_icmd(dev, 0x00000922, 0x00000002); + nv_icmd(dev, 0x00000923, 0x00000002); + nv_icmd(dev, 0x00000924, 0x00000002); + nv_icmd(dev, 0x00000925, 0x00000002); + nv_icmd(dev, 0x00000926, 0x00000002); + nv_icmd(dev, 0x00000927, 0x00000002); + nv_icmd(dev, 0x00000928, 0x00000001); + nv_icmd(dev, 0x00000929, 0x00000001); + nv_icmd(dev, 0x0000092a, 0x00000001); + nv_icmd(dev, 0x0000092b, 0x00000001); + nv_icmd(dev, 0x0000092c, 0x00000001); + nv_icmd(dev, 0x0000092d, 0x00000001); + nv_icmd(dev, 0x0000092e, 0x00000001); + nv_icmd(dev, 0x0000092f, 0x00000001); + nv_icmd(dev, 0x00000648, 0x00000001); + nv_icmd(dev, 0x00000649, 0x00000001); + nv_icmd(dev, 0x0000064a, 0x00000001); + nv_icmd(dev, 0x0000064b, 0x00000001); + nv_icmd(dev, 0x0000064c, 0x00000001); + nv_icmd(dev, 0x0000064d, 0x00000001); + nv_icmd(dev, 0x0000064e, 0x00000001); + nv_icmd(dev, 0x0000064f, 0x00000001); + nv_icmd(dev, 0x00000650, 0x00000001); + nv_icmd(dev, 0x00000658, 0x0000000f); + nv_icmd(dev, 0x000007ff, 0x0000000a); + nv_icmd(dev, 0x0000066a, 0x40000000); + nv_icmd(dev, 0x0000066b, 0x10000000); + nv_icmd(dev, 0x0000066c, 0xffff0000); + nv_icmd(dev, 0x0000066d, 0xffff0000); + nv_icmd(dev, 0x000007af, 0x00000008); + nv_icmd(dev, 0x000007b0, 0x00000008); + nv_icmd(dev, 0x000007f6, 0x00000001); + nv_icmd(dev, 0x000006b2, 0x00000055); + nv_icmd(dev, 0x000007ad, 0x00000003); + nv_icmd(dev, 0x00000937, 0x00000001); + nv_icmd(dev, 0x00000971, 0x00000008); + nv_icmd(dev, 0x00000972, 0x00000040); + nv_icmd(dev, 0x00000973, 0x0000012c); + nv_icmd(dev, 0x0000097c, 0x00000040); + nv_icmd(dev, 0x00000979, 0x00000003); + nv_icmd(dev, 0x00000975, 0x00000020); + nv_icmd(dev, 0x00000976, 0x00000001); + nv_icmd(dev, 0x00000977, 0x00000020); + nv_icmd(dev, 0x00000978, 0x00000001); + nv_icmd(dev, 0x00000957, 0x00000003); + nv_icmd(dev, 0x0000095e, 0x20164010); + nv_icmd(dev, 0x0000095f, 0x00000020); + if (dev_priv->chipset == 0xd9) + nv_icmd(dev, 0x0000097d, 0x00000020); + nv_icmd(dev, 0x00000683, 0x00000006); + nv_icmd(dev, 0x00000685, 0x003fffff); + nv_icmd(dev, 0x00000687, 0x00000c48); + nv_icmd(dev, 0x000006a0, 0x00000005); + nv_icmd(dev, 0x00000840, 0x00300008); + nv_icmd(dev, 0x00000841, 0x04000080); + nv_icmd(dev, 0x00000842, 0x00300008); + nv_icmd(dev, 0x00000843, 0x04000080); + nv_icmd(dev, 0x00000818, 0x00000000); + nv_icmd(dev, 0x00000819, 0x00000000); + nv_icmd(dev, 0x0000081a, 0x00000000); + nv_icmd(dev, 0x0000081b, 0x00000000); + nv_icmd(dev, 0x0000081c, 0x00000000); + nv_icmd(dev, 0x0000081d, 0x00000000); + nv_icmd(dev, 0x0000081e, 0x00000000); + nv_icmd(dev, 0x0000081f, 0x00000000); + nv_icmd(dev, 0x00000848, 0x00000000); + nv_icmd(dev, 0x00000849, 0x00000000); + nv_icmd(dev, 0x0000084a, 0x00000000); + nv_icmd(dev, 0x0000084b, 0x00000000); + nv_icmd(dev, 0x0000084c, 0x00000000); + nv_icmd(dev, 0x0000084d, 0x00000000); + nv_icmd(dev, 0x0000084e, 0x00000000); + nv_icmd(dev, 0x0000084f, 0x00000000); + nv_icmd(dev, 0x00000850, 0x00000000); + nv_icmd(dev, 0x00000851, 0x00000000); + nv_icmd(dev, 0x00000852, 0x00000000); + nv_icmd(dev, 0x00000853, 0x00000000); + nv_icmd(dev, 0x00000854, 0x00000000); + nv_icmd(dev, 0x00000855, 0x00000000); + nv_icmd(dev, 0x00000856, 0x00000000); + nv_icmd(dev, 0x00000857, 0x00000000); + nv_icmd(dev, 0x00000738, 0x00000000); + nv_icmd(dev, 0x000006aa, 0x00000001); + nv_icmd(dev, 0x000006ab, 0x00000002); + nv_icmd(dev, 0x000006ac, 0x00000080); + nv_icmd(dev, 0x000006ad, 0x00000100); + nv_icmd(dev, 0x000006ae, 0x00000100); + nv_icmd(dev, 0x000006b1, 0x00000011); + nv_icmd(dev, 0x000006bb, 0x000000cf); + nv_icmd(dev, 0x000006ce, 0x2a712488); + nv_icmd(dev, 0x00000739, 0x4085c000); + nv_icmd(dev, 0x0000073a, 0x00000080); + nv_icmd(dev, 0x00000786, 0x80000100); + nv_icmd(dev, 0x0000073c, 0x00010100); + nv_icmd(dev, 0x0000073d, 0x02800000); + nv_icmd(dev, 0x00000787, 0x000000cf); + nv_icmd(dev, 0x0000078c, 0x00000008); + nv_icmd(dev, 0x00000792, 0x00000001); + nv_icmd(dev, 0x00000794, 0x00000001); + nv_icmd(dev, 0x00000795, 0x00000001); + nv_icmd(dev, 0x00000796, 0x00000001); + nv_icmd(dev, 0x00000797, 0x000000cf); + nv_icmd(dev, 0x00000836, 0x00000001); + nv_icmd(dev, 0x0000079a, 0x00000002); + nv_icmd(dev, 0x00000833, 0x04444480); + nv_icmd(dev, 0x000007a1, 0x00000001); + nv_icmd(dev, 0x000007a3, 0x00000001); + nv_icmd(dev, 0x000007a4, 0x00000001); + nv_icmd(dev, 0x000007a5, 0x00000001); + nv_icmd(dev, 0x00000831, 0x00000004); + nv_icmd(dev, 0x0000080c, 0x00000002); + nv_icmd(dev, 0x0000080d, 0x00000100); + nv_icmd(dev, 0x0000080e, 0x00000100); + nv_icmd(dev, 0x0000080f, 0x00000001); + nv_icmd(dev, 0x00000823, 0x00000002); + nv_icmd(dev, 0x00000824, 0x00000100); + nv_icmd(dev, 0x00000825, 0x00000100); + nv_icmd(dev, 0x00000826, 0x00000001); + nv_icmd(dev, 0x0000095d, 0x00000001); + nv_icmd(dev, 0x0000082b, 0x00000004); + nv_icmd(dev, 0x00000942, 0x00010001); + nv_icmd(dev, 0x00000943, 0x00000001); + nv_icmd(dev, 0x00000944, 0x00000022); + nv_icmd(dev, 0x000007c5, 0x00010001); + nv_icmd(dev, 0x00000834, 0x00000001); + nv_icmd(dev, 0x000007c7, 0x00000001); + nv_icmd(dev, 0x0000c1b0, 0x0000000f); + nv_icmd(dev, 0x0000c1b1, 0x0000000f); + nv_icmd(dev, 0x0000c1b2, 0x0000000f); + nv_icmd(dev, 0x0000c1b3, 0x0000000f); + nv_icmd(dev, 0x0000c1b4, 0x0000000f); + nv_icmd(dev, 0x0000c1b5, 0x0000000f); + nv_icmd(dev, 0x0000c1b6, 0x0000000f); + nv_icmd(dev, 0x0000c1b7, 0x0000000f); + nv_icmd(dev, 0x0000c1b8, 0x0fac6881); + nv_icmd(dev, 0x0000c1b9, 0x00fac688); + nv_icmd(dev, 0x0001e100, 0x00000001); + nv_icmd(dev, 0x00001000, 0x00000002); + nv_icmd(dev, 0x000006aa, 0x00000001); + nv_icmd(dev, 0x000006ad, 0x00000100); + nv_icmd(dev, 0x000006ae, 0x00000100); + nv_icmd(dev, 0x000006b1, 0x00000011); + nv_icmd(dev, 0x0000078c, 0x00000008); + nv_icmd(dev, 0x00000792, 0x00000001); + nv_icmd(dev, 0x00000794, 0x00000001); + nv_icmd(dev, 0x00000795, 0x00000001); + nv_icmd(dev, 0x00000796, 0x00000001); + nv_icmd(dev, 0x00000797, 0x000000cf); + nv_icmd(dev, 0x0000079a, 0x00000002); + nv_icmd(dev, 0x00000833, 0x04444480); + nv_icmd(dev, 0x000007a1, 0x00000001); + nv_icmd(dev, 0x000007a3, 0x00000001); + nv_icmd(dev, 0x000007a4, 0x00000001); + nv_icmd(dev, 0x000007a5, 0x00000001); + nv_icmd(dev, 0x00000831, 0x00000004); + nv_icmd(dev, 0x0001e100, 0x00000001); + nv_icmd(dev, 0x00001000, 0x00000014); + nv_icmd(dev, 0x00000351, 0x00000100); + nv_icmd(dev, 0x00000957, 0x00000003); + nv_icmd(dev, 0x0000095d, 0x00000001); + nv_icmd(dev, 0x0000082b, 0x00000004); + nv_icmd(dev, 0x00000942, 0x00010001); + nv_icmd(dev, 0x00000943, 0x00000001); + nv_icmd(dev, 0x000007c5, 0x00010001); + nv_icmd(dev, 0x00000834, 0x00000001); + nv_icmd(dev, 0x000007c7, 0x00000001); + nv_icmd(dev, 0x0001e100, 0x00000001); + nv_icmd(dev, 0x00001000, 0x00000001); + nv_icmd(dev, 0x0000080c, 0x00000002); + nv_icmd(dev, 0x0000080d, 0x00000100); + nv_icmd(dev, 0x0000080e, 0x00000100); + nv_icmd(dev, 0x0000080f, 0x00000001); + nv_icmd(dev, 0x00000823, 0x00000002); + nv_icmd(dev, 0x00000824, 0x00000100); + nv_icmd(dev, 0x00000825, 0x00000100); + nv_icmd(dev, 0x00000826, 0x00000001); + nv_icmd(dev, 0x0001e100, 0x00000001); + nv_wr32(dev, 0x400208, 0x00000000); + nv_wr32(dev, 0x404154, 0x00000400); + + nvc0_grctx_generate_9097(dev); + if (fermi >= 0x9197) + nvc0_grctx_generate_9197(dev); + if (fermi >= 0x9297) + nvc0_grctx_generate_9297(dev); + nvc0_grctx_generate_902d(dev); + nvc0_grctx_generate_9039(dev); + nvc0_grctx_generate_90c0(dev); + + nv_wr32(dev, 0x000260, r000260); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nvc0_grgpc.fuc b/drivers/gpu/drm/nouveau/nvc0_grgpc.fuc new file mode 100644 index 00000000..15272be3 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_grgpc.fuc @@ -0,0 +1,539 @@ +/* fuc microcode for nvc0 PGRAPH/GPC + * + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +/* To build: + * m4 nvc0_grgpc.fuc | envyas -a -w -m fuc -V nva3 -o nvc0_grgpc.fuc.h + */ + +/* TODO + * - bracket certain functions with scratch writes, useful for debugging + * - watchdog timer around ctx operations + */ + +.section #nvc0_grgpc_data +include(`nvc0_graph.fuc') +gpc_id: .b32 0 +gpc_mmio_list_head: .b32 0 +gpc_mmio_list_tail: .b32 0 + +tpc_count: .b32 0 +tpc_mask: .b32 0 +tpc_mmio_list_head: .b32 0 +tpc_mmio_list_tail: .b32 0 + +cmd_queue: queue_init + +// chipset descriptions +chipsets: +.b8 0xc0 0 0 0 +.b16 #nvc0_gpc_mmio_head +.b16 #nvc0_gpc_mmio_tail +.b16 #nvc0_tpc_mmio_head +.b16 #nvc0_tpc_mmio_tail +.b8 0xc1 0 0 0 +.b16 #nvc0_gpc_mmio_head +.b16 #nvc1_gpc_mmio_tail +.b16 #nvc0_tpc_mmio_head +.b16 #nvc1_tpc_mmio_tail +.b8 0xc3 0 0 0 +.b16 #nvc0_gpc_mmio_head +.b16 #nvc0_gpc_mmio_tail +.b16 #nvc0_tpc_mmio_head +.b16 #nvc3_tpc_mmio_tail +.b8 0xc4 0 0 0 +.b16 #nvc0_gpc_mmio_head +.b16 #nvc0_gpc_mmio_tail +.b16 #nvc0_tpc_mmio_head +.b16 #nvc3_tpc_mmio_tail +.b8 0xc8 0 0 0 +.b16 #nvc0_gpc_mmio_head +.b16 #nvc0_gpc_mmio_tail +.b16 #nvc0_tpc_mmio_head +.b16 #nvc0_tpc_mmio_tail +.b8 0xce 0 0 0 +.b16 #nvc0_gpc_mmio_head +.b16 #nvc0_gpc_mmio_tail +.b16 #nvc0_tpc_mmio_head +.b16 #nvc3_tpc_mmio_tail +.b8 0xcf 0 0 0 +.b16 #nvc0_gpc_mmio_head +.b16 #nvc0_gpc_mmio_tail +.b16 #nvc0_tpc_mmio_head +.b16 #nvcf_tpc_mmio_tail +.b8 0xd9 0 0 0 +.b16 #nvd9_gpc_mmio_head +.b16 #nvd9_gpc_mmio_tail +.b16 #nvd9_tpc_mmio_head +.b16 #nvd9_tpc_mmio_tail +.b8 0 0 0 0 + +// GPC mmio lists +nvc0_gpc_mmio_head: +mmctx_data(0x000380, 1) +mmctx_data(0x000400, 6) +mmctx_data(0x000450, 9) +mmctx_data(0x000600, 1) +mmctx_data(0x000684, 1) +mmctx_data(0x000700, 5) +mmctx_data(0x000800, 1) +mmctx_data(0x000808, 3) +mmctx_data(0x000828, 1) +mmctx_data(0x000830, 1) +mmctx_data(0x0008d8, 1) +mmctx_data(0x0008e0, 1) +mmctx_data(0x0008e8, 6) +mmctx_data(0x00091c, 1) +mmctx_data(0x000924, 3) +mmctx_data(0x000b00, 1) +mmctx_data(0x000b08, 6) +mmctx_data(0x000bb8, 1) +mmctx_data(0x000c08, 1) +mmctx_data(0x000c10, 8) +mmctx_data(0x000c80, 1) +mmctx_data(0x000c8c, 1) +mmctx_data(0x001000, 3) +mmctx_data(0x001014, 1) +nvc0_gpc_mmio_tail: +mmctx_data(0x000c6c, 1); +nvc1_gpc_mmio_tail: + +nvd9_gpc_mmio_head: +mmctx_data(0x000380, 1) +mmctx_data(0x000400, 2) +mmctx_data(0x00040c, 3) +mmctx_data(0x000450, 9) +mmctx_data(0x000600, 1) +mmctx_data(0x000684, 1) +mmctx_data(0x000700, 5) +mmctx_data(0x000800, 1) +mmctx_data(0x000808, 3) +mmctx_data(0x000828, 1) +mmctx_data(0x000830, 1) +mmctx_data(0x0008d8, 1) +mmctx_data(0x0008e0, 1) +mmctx_data(0x0008e8, 6) +mmctx_data(0x00091c, 1) +mmctx_data(0x000924, 3) +mmctx_data(0x000b00, 1) +mmctx_data(0x000b08, 6) +mmctx_data(0x000bb8, 1) +mmctx_data(0x000c08, 1) +mmctx_data(0x000c10, 8) +mmctx_data(0x000c6c, 1) +mmctx_data(0x000c80, 1) +mmctx_data(0x000c8c, 1) +mmctx_data(0x001000, 3) +mmctx_data(0x001014, 1) +nvd9_gpc_mmio_tail: + +// TPC mmio lists +nvc0_tpc_mmio_head: +mmctx_data(0x000018, 1) +mmctx_data(0x00003c, 1) +mmctx_data(0x000048, 1) +mmctx_data(0x000064, 1) +mmctx_data(0x000088, 1) +mmctx_data(0x000200, 6) +mmctx_data(0x00021c, 2) +mmctx_data(0x000300, 6) +mmctx_data(0x0003d0, 1) +mmctx_data(0x0003e0, 2) +mmctx_data(0x000400, 3) +mmctx_data(0x000420, 1) +mmctx_data(0x0004b0, 1) +mmctx_data(0x0004e8, 1) +mmctx_data(0x0004f4, 1) +mmctx_data(0x000520, 2) +mmctx_data(0x000604, 4) +mmctx_data(0x000644, 20) +mmctx_data(0x000698, 1) +mmctx_data(0x000750, 2) +nvc0_tpc_mmio_tail: +mmctx_data(0x000758, 1) +mmctx_data(0x0002c4, 1) +mmctx_data(0x0006e0, 1) +nvcf_tpc_mmio_tail: +mmctx_data(0x0004bc, 1) +nvc3_tpc_mmio_tail: +mmctx_data(0x000544, 1) +nvc1_tpc_mmio_tail: + +nvd9_tpc_mmio_head: +mmctx_data(0x000018, 1) +mmctx_data(0x00003c, 1) +mmctx_data(0x000048, 1) +mmctx_data(0x000064, 1) +mmctx_data(0x000088, 1) +mmctx_data(0x000200, 6) +mmctx_data(0x00021c, 2) +mmctx_data(0x0002c4, 1) +mmctx_data(0x000300, 6) +mmctx_data(0x0003d0, 1) +mmctx_data(0x0003e0, 2) +mmctx_data(0x000400, 3) +mmctx_data(0x000420, 3) +mmctx_data(0x0004b0, 1) +mmctx_data(0x0004e8, 1) +mmctx_data(0x0004f4, 1) +mmctx_data(0x000520, 2) +mmctx_data(0x000544, 1) +mmctx_data(0x000604, 4) +mmctx_data(0x000644, 20) +mmctx_data(0x000698, 1) +mmctx_data(0x0006e0, 1) +mmctx_data(0x000750, 3) +nvd9_tpc_mmio_tail: + +.section #nvc0_grgpc_code +bra #init +define(`include_code') +include(`nvc0_graph.fuc') + +// reports an exception to the host +// +// In: $r15 error code (see nvc0_graph.fuc) +// +error: + push $r14 + mov $r14 -0x67ec // 0x9814 + sethi $r14 0x400000 + call #nv_wr32 // HUB_CTXCTL_CC_SCRATCH[5] = error code + add b32 $r14 0x41c + mov $r15 1 + call #nv_wr32 // HUB_CTXCTL_INTR_UP_SET + pop $r14 + ret + +// GPC fuc initialisation, executed by triggering ucode start, will +// fall through to main loop after completion. +// +// Input: +// CC_SCRATCH[0]: chipset (PMC_BOOT_0 read returns 0x0bad0bad... sigh) +// CC_SCRATCH[1]: context base +// +// Output: +// CC_SCRATCH[0]: +// 31:31: set to signal completion +// CC_SCRATCH[1]: +// 31:0: GPC context size +// +init: + clear b32 $r0 + mov $sp $r0 + + // enable fifo access + mov $r1 0x1200 + mov $r2 2 + iowr I[$r1 + 0x000] $r2 // FIFO_ENABLE + + // setup i0 handler, and route all interrupts to it + mov $r1 #ih + mov $iv0 $r1 + mov $r1 0x400 + iowr I[$r1 + 0x300] $r0 // INTR_DISPATCH + + // enable fifo interrupt + mov $r2 4 + iowr I[$r1 + 0x000] $r2 // INTR_EN_SET + + // enable interrupts + bset $flags ie0 + + // figure out which GPC we are, and how many TPCs we have + mov $r1 0x608 + shl b32 $r1 6 + iord $r2 I[$r1 + 0x000] // UNITS + mov $r3 1 + and $r2 0x1f + shl b32 $r3 $r2 + sub b32 $r3 1 + st b32 D[$r0 + #tpc_count] $r2 + st b32 D[$r0 + #tpc_mask] $r3 + add b32 $r1 0x400 + iord $r2 I[$r1 + 0x000] // MYINDEX + st b32 D[$r0 + #gpc_id] $r2 + + // find context data for this chipset + mov $r2 0x800 + shl b32 $r2 6 + iord $r2 I[$r2 + 0x000] // CC_SCRATCH[0] + mov $r1 #chipsets - 12 + init_find_chipset: + add b32 $r1 12 + ld b32 $r3 D[$r1 + 0x00] + cmpu b32 $r3 $r2 + bra e #init_context + cmpu b32 $r3 0 + bra ne #init_find_chipset + // unknown chipset + ret + + // initialise context base, and size tracking + init_context: + mov $r2 0x800 + shl b32 $r2 6 + iord $r2 I[$r2 + 0x100] // CC_SCRATCH[1], initial base + clear b32 $r3 // track GPC context size here + + // set mmctx base addresses now so we don't have to do it later, + // they don't currently ever change + mov $r4 0x700 + shl b32 $r4 6 + shr b32 $r5 $r2 8 + iowr I[$r4 + 0x000] $r5 // MMCTX_SAVE_SWBASE + iowr I[$r4 + 0x100] $r5 // MMCTX_LOAD_SWBASE + + // calculate GPC mmio context size, store the chipset-specific + // mmio list pointers somewhere we can get at them later without + // re-parsing the chipset list + clear b32 $r14 + clear b32 $r15 + ld b16 $r14 D[$r1 + 4] + ld b16 $r15 D[$r1 + 6] + st b16 D[$r0 + #gpc_mmio_list_head] $r14 + st b16 D[$r0 + #gpc_mmio_list_tail] $r15 + call #mmctx_size + add b32 $r2 $r15 + add b32 $r3 $r15 + + // calculate per-TPC mmio context size, store the list pointers + ld b16 $r14 D[$r1 + 8] + ld b16 $r15 D[$r1 + 10] + st b16 D[$r0 + #tpc_mmio_list_head] $r14 + st b16 D[$r0 + #tpc_mmio_list_tail] $r15 + call #mmctx_size + ld b32 $r14 D[$r0 + #tpc_count] + mulu $r14 $r15 + add b32 $r2 $r14 + add b32 $r3 $r14 + + // round up base/size to 256 byte boundary (for strand SWBASE) + add b32 $r4 0x1300 + shr b32 $r3 2 + iowr I[$r4 + 0x000] $r3 // MMCTX_LOAD_COUNT, wtf for?!? + shr b32 $r2 8 + shr b32 $r3 6 + add b32 $r2 1 + add b32 $r3 1 + shl b32 $r2 8 + shl b32 $r3 8 + + // calculate size of strand context data + mov b32 $r15 $r2 + call #strand_ctx_init + add b32 $r3 $r15 + + // save context size, and tell HUB we're done + mov $r1 0x800 + shl b32 $r1 6 + iowr I[$r1 + 0x100] $r3 // CC_SCRATCH[1] = context size + add b32 $r1 0x800 + clear b32 $r2 + bset $r2 31 + iowr I[$r1 + 0x000] $r2 // CC_SCRATCH[0] |= 0x80000000 + +// Main program loop, very simple, sleeps until woken up by the interrupt +// handler, pulls a command from the queue and executes its handler +// +main: + bset $flags $p0 + sleep $p0 + mov $r13 #cmd_queue + call #queue_get + bra $p1 #main + + // 0x0000-0x0003 are all context transfers + cmpu b32 $r14 0x04 + bra nc #main_not_ctx_xfer + // fetch $flags and mask off $p1/$p2 + mov $r1 $flags + mov $r2 0x0006 + not b32 $r2 + and $r1 $r2 + // set $p1/$p2 according to transfer type + shl b32 $r14 1 + or $r1 $r14 + mov $flags $r1 + // transfer context data + call #ctx_xfer + bra #main + + main_not_ctx_xfer: + shl b32 $r15 $r14 16 + or $r15 E_BAD_COMMAND + call #error + bra #main + +// interrupt handler +ih: + push $r8 + mov $r8 $flags + push $r8 + push $r9 + push $r10 + push $r11 + push $r13 + push $r14 + push $r15 + + // incoming fifo command? + iord $r10 I[$r0 + 0x200] // INTR + and $r11 $r10 0x00000004 + bra e #ih_no_fifo + // queue incoming fifo command for later processing + mov $r11 0x1900 + mov $r13 #cmd_queue + iord $r14 I[$r11 + 0x100] // FIFO_CMD + iord $r15 I[$r11 + 0x000] // FIFO_DATA + call #queue_put + add b32 $r11 0x400 + mov $r14 1 + iowr I[$r11 + 0x000] $r14 // FIFO_ACK + + // ack, and wake up main() + ih_no_fifo: + iowr I[$r0 + 0x100] $r10 // INTR_ACK + + pop $r15 + pop $r14 + pop $r13 + pop $r11 + pop $r10 + pop $r9 + pop $r8 + mov $flags $r8 + pop $r8 + bclr $flags $p0 + iret + +// Set this GPC's bit in HUB_BAR, used to signal completion of various +// activities to the HUB fuc +// +hub_barrier_done: + mov $r15 1 + ld b32 $r14 D[$r0 + #gpc_id] + shl b32 $r15 $r14 + mov $r14 -0x6be8 // 0x409418 - HUB_BAR_SET + sethi $r14 0x400000 + call #nv_wr32 + ret + +// Disables various things, waits a bit, and re-enables them.. +// +// Not sure how exactly this helps, perhaps "ENABLE" is not such a +// good description for the bits we turn off? Anyways, without this, +// funny things happen. +// +ctx_redswitch: + mov $r14 0x614 + shl b32 $r14 6 + mov $r15 0x020 + iowr I[$r14] $r15 // GPC_RED_SWITCH = POWER + mov $r15 8 + ctx_redswitch_delay: + sub b32 $r15 1 + bra ne #ctx_redswitch_delay + mov $r15 0xa20 + iowr I[$r14] $r15 // GPC_RED_SWITCH = UNK11, ENABLE, POWER + ret + +// Transfer GPC context data between GPU and storage area +// +// In: $r15 context base address +// $p1 clear on save, set on load +// $p2 set if opposite direction done/will be done, so: +// on save it means: "a load will follow this save" +// on load it means: "a save preceeded this load" +// +ctx_xfer: + // set context base address + mov $r1 0xa04 + shl b32 $r1 6 + iowr I[$r1 + 0x000] $r15// MEM_BASE + bra not $p1 #ctx_xfer_not_load + call #ctx_redswitch + ctx_xfer_not_load: + + // strands + mov $r1 0x4afc + sethi $r1 0x20000 + mov $r2 0xc + iowr I[$r1] $r2 // STRAND_CMD(0x3f) = 0x0c + call #strand_wait + mov $r2 0x47fc + sethi $r2 0x20000 + iowr I[$r2] $r0 // STRAND_FIRST_GENE(0x3f) = 0x00 + xbit $r2 $flags $p1 + add b32 $r2 3 + iowr I[$r1] $r2 // STRAND_CMD(0x3f) = 0x03/0x04 (SAVE/LOAD) + + // mmio context + xbit $r10 $flags $p1 // direction + or $r10 2 // first + mov $r11 0x0000 + sethi $r11 0x500000 + ld b32 $r12 D[$r0 + #gpc_id] + shl b32 $r12 15 + add b32 $r11 $r12 // base = NV_PGRAPH_GPCn + ld b32 $r12 D[$r0 + #gpc_mmio_list_head] + ld b32 $r13 D[$r0 + #gpc_mmio_list_tail] + mov $r14 0 // not multi + call #mmctx_xfer + + // per-TPC mmio context + xbit $r10 $flags $p1 // direction + or $r10 4 // last + mov $r11 0x4000 + sethi $r11 0x500000 // base = NV_PGRAPH_GPC0_TPC0 + ld b32 $r12 D[$r0 + #gpc_id] + shl b32 $r12 15 + add b32 $r11 $r12 // base = NV_PGRAPH_GPCn_TPC0 + ld b32 $r12 D[$r0 + #tpc_mmio_list_head] + ld b32 $r13 D[$r0 + #tpc_mmio_list_tail] + ld b32 $r15 D[$r0 + #tpc_mask] + mov $r14 0x800 // stride = 0x800 + call #mmctx_xfer + + // wait for strands to finish + call #strand_wait + + // if load, or a save without a load following, do some + // unknown stuff that's done after finishing a block of + // strand commands + bra $p1 #ctx_xfer_post + bra not $p2 #ctx_xfer_done + ctx_xfer_post: + mov $r1 0x4afc + sethi $r1 0x20000 + mov $r2 0xd + iowr I[$r1] $r2 // STRAND_CMD(0x3f) = 0x0d + call #strand_wait + + // mark completion in HUB's barrier + ctx_xfer_done: + call #hub_barrier_done + ret + +.align 256 diff --git a/drivers/gpu/drm/nouveau/nvc0_grgpc.fuc.h b/drivers/gpu/drm/nouveau/nvc0_grgpc.fuc.h new file mode 100644 index 00000000..a988b8ad --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_grgpc.fuc.h @@ -0,0 +1,538 @@ +uint32_t nvc0_grgpc_data[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x000000c0, + 0x012800c8, + 0x01e40194, + 0x000000c1, + 0x012c00c8, + 0x01f80194, + 0x000000c3, + 0x012800c8, + 0x01f40194, + 0x000000c4, + 0x012800c8, + 0x01f40194, + 0x000000c8, + 0x012800c8, + 0x01e40194, + 0x000000ce, + 0x012800c8, + 0x01f40194, + 0x000000cf, + 0x012800c8, + 0x01f00194, + 0x000000d9, + 0x0194012c, + 0x025401f8, + 0x00000000, + 0x00000380, + 0x14000400, + 0x20000450, + 0x00000600, + 0x00000684, + 0x10000700, + 0x00000800, + 0x08000808, + 0x00000828, + 0x00000830, + 0x000008d8, + 0x000008e0, + 0x140008e8, + 0x0000091c, + 0x08000924, + 0x00000b00, + 0x14000b08, + 0x00000bb8, + 0x00000c08, + 0x1c000c10, + 0x00000c80, + 0x00000c8c, + 0x08001000, + 0x00001014, + 0x00000c6c, + 0x00000380, + 0x04000400, + 0x0800040c, + 0x20000450, + 0x00000600, + 0x00000684, + 0x10000700, + 0x00000800, + 0x08000808, + 0x00000828, + 0x00000830, + 0x000008d8, + 0x000008e0, + 0x140008e8, + 0x0000091c, + 0x08000924, + 0x00000b00, + 0x14000b08, + 0x00000bb8, + 0x00000c08, + 0x1c000c10, + 0x00000c6c, + 0x00000c80, + 0x00000c8c, + 0x08001000, + 0x00001014, + 0x00000018, + 0x0000003c, + 0x00000048, + 0x00000064, + 0x00000088, + 0x14000200, + 0x0400021c, + 0x14000300, + 0x000003d0, + 0x040003e0, + 0x08000400, + 0x00000420, + 0x000004b0, + 0x000004e8, + 0x000004f4, + 0x04000520, + 0x0c000604, + 0x4c000644, + 0x00000698, + 0x04000750, + 0x00000758, + 0x000002c4, + 0x000006e0, + 0x000004bc, + 0x00000544, + 0x00000018, + 0x0000003c, + 0x00000048, + 0x00000064, + 0x00000088, + 0x14000200, + 0x0400021c, + 0x000002c4, + 0x14000300, + 0x000003d0, + 0x040003e0, + 0x08000400, + 0x08000420, + 0x000004b0, + 0x000004e8, + 0x000004f4, + 0x04000520, + 0x00000544, + 0x0c000604, + 0x4c000644, + 0x00000698, + 0x000006e0, + 0x08000750, +}; + +uint32_t nvc0_grgpc_code[] = { + 0x03060ef5, + 0x9800d898, + 0x86f001d9, + 0x0489b808, + 0xf00c1bf4, + 0x21f502f7, + 0x00f802ec, + 0xb60798c4, + 0x8dbb0384, + 0x0880b600, + 0x80008e80, + 0x90b6018f, + 0x0f94f001, + 0xf801d980, + 0x0131f400, + 0x9800d898, + 0x89b801d9, + 0x210bf404, + 0xb60789c4, + 0x9dbb0394, + 0x0890b600, + 0x98009e98, + 0x80b6019f, + 0x0f84f001, + 0xf400d880, + 0x00f80132, + 0x0728b7f1, + 0xb906b4b6, + 0xc9f002ec, + 0x00bcd01f, + 0xc800bccf, + 0x1bf41fcc, + 0x06a7f0fa, + 0x010321f5, + 0xf840bfcf, + 0x28b7f100, + 0x06b4b607, + 0xb980bfd0, + 0xc9f002ec, + 0x1ec9f01f, + 0xcf00bcd0, + 0xccc800bc, + 0xfa1bf41f, + 0x87f100f8, + 0x84b60430, + 0x1ff9f006, + 0xf8008fd0, + 0x3087f100, + 0x0684b604, + 0xf80080d0, + 0x3c87f100, + 0x0684b608, + 0x99f094bd, + 0x0089d000, + 0x081887f1, + 0xd00684b6, + 0x87f1008a, + 0x84b60400, + 0x0088cf06, + 0xf4888aff, + 0x87f1f31b, + 0x84b6085c, + 0xf094bd06, + 0x89d00099, + 0xf100f800, + 0xb6083c87, + 0x94bd0684, + 0xd00099f0, + 0x87f10089, + 0x84b60818, + 0x008ad006, + 0x040087f1, + 0xcf0684b6, + 0x8aff0088, + 0xf30bf488, + 0x085c87f1, + 0xbd0684b6, + 0x0099f094, + 0xf80089d0, + 0x9894bd00, + 0x85b600e8, + 0x0180b61a, + 0xbb0284b6, + 0xe0b60098, + 0x04efb804, + 0xb9eb1bf4, + 0x00f8029f, + 0x083c87f1, + 0xbd0684b6, + 0x0199f094, + 0xf10089d0, + 0xb6071087, + 0x94bd0684, + 0xf405bbfd, + 0x8bd0090b, + 0x0099f000, + 0xf405eefd, + 0x8ed00c0b, + 0xc08fd080, + 0xb70199f0, + 0xc8010080, + 0xb4b600ab, + 0x0cb9f010, + 0xb601aec8, + 0xbefd11e4, + 0x008bd005, + 0xf0008ecf, + 0x0bf41fe4, + 0x00ce98fa, + 0xd005e9fd, + 0xc0b6c08e, + 0x04cdb804, + 0xc8e81bf4, + 0x1bf402ab, + 0x008bcf18, + 0xb01fb4f0, + 0x1bf410b4, + 0x02a7f0f7, + 0xf4c921f4, + 0xabc81b0e, + 0x10b4b600, + 0xf00cb9f0, + 0x8bd012b9, + 0x008bcf00, + 0xf412bbc8, + 0x87f1fa1b, + 0x84b6085c, + 0xf094bd06, + 0x89d00199, + 0xf900f800, + 0x02a7f0a0, + 0xfcc921f4, + 0xf100f8a0, + 0xf04afc87, + 0x97f00283, + 0x0089d00c, + 0x020721f5, + 0x87f100f8, + 0x83f04afc, + 0x0d97f002, + 0xf50089d0, + 0xf8020721, + 0xfca7f100, + 0x02a3f04f, + 0x0500aba2, + 0xd00fc7f0, + 0xc7f000ac, + 0x00bcd00b, + 0x020721f5, + 0xf000aed0, + 0xbcd00ac7, + 0x0721f500, + 0xf100f802, + 0xb6083c87, + 0x94bd0684, + 0xd00399f0, + 0x21f50089, + 0xe7f00213, + 0x3921f503, + 0xfca7f102, + 0x02a3f046, + 0x0400aba0, + 0xf040a0d0, + 0xbcd001c7, + 0x0721f500, + 0x010c9202, + 0xf000acd0, + 0xbcd002c7, + 0x0721f500, + 0x2621f502, + 0x8087f102, + 0x0684b608, + 0xb70089cf, + 0x95220080, + 0x8ed008fe, + 0x408ed000, + 0xb6808acf, + 0xa0b606a5, + 0x00eabb01, + 0xb60480b6, + 0x1bf40192, + 0x08e4b6e8, + 0xf1f2efbc, + 0xb6085c87, + 0x94bd0684, + 0xd00399f0, + 0x00f80089, + 0xe7f1e0f9, + 0xe3f09814, + 0x8d21f440, + 0x041ce0b7, + 0xf401f7f0, + 0xe0fc8d21, + 0x04bd00f8, + 0xf10004fe, + 0xf0120017, + 0x12d00227, + 0x3e17f100, + 0x0010fe04, + 0x040017f1, + 0xf0c010d0, + 0x12d00427, + 0x1031f400, + 0x060817f1, + 0xcf0614b6, + 0x37f00012, + 0x1f24f001, + 0xb60432bb, + 0x02800132, + 0x04038003, + 0x040010b7, + 0x800012cf, + 0x27f10002, + 0x24b60800, + 0x0022cf06, + 0xb65817f0, + 0x13980c10, + 0x0432b800, + 0xb00b0bf4, + 0x1bf40034, + 0xf100f8f1, + 0xb6080027, + 0x22cf0624, + 0xf134bd40, + 0xb6070047, + 0x25950644, + 0x0045d008, + 0xbd4045d0, + 0x58f4bde4, + 0x1f58021e, + 0x020e4003, + 0xf5040f40, + 0xbb013d21, + 0x3fbb002f, + 0x041e5800, + 0x40051f58, + 0x0f400a0e, + 0x3d21f50c, + 0x030e9801, + 0xbb00effd, + 0x3ebb002e, + 0x0040b700, + 0x0235b613, + 0xb60043d0, + 0x35b60825, + 0x0120b606, + 0xb60130b6, + 0x34b60824, + 0x022fb908, + 0x026321f5, + 0xf1003fbb, + 0xb6080017, + 0x13d00614, + 0x0010b740, + 0xf024bd08, + 0x12d01f29, + 0x0031f400, + 0xf00028f4, + 0x21f41cd7, + 0xf401f439, + 0xf404e4b0, + 0x81fe1e18, + 0x0627f001, + 0x12fd20bd, + 0x01e4b604, + 0xfe051efd, + 0x21f50018, + 0x0ef404c3, + 0x10ef94d3, + 0xf501f5f0, + 0xf402ec21, + 0x80f9c60e, + 0xf90188fe, + 0xf990f980, + 0xf9b0f9a0, + 0xf9e0f9d0, + 0x800acff0, + 0xf404abc4, + 0xb7f11d0b, + 0xd7f01900, + 0x40becf1c, + 0xf400bfcf, + 0xb0b70421, + 0xe7f00400, + 0x00bed001, + 0xfc400ad0, + 0xfce0fcf0, + 0xfcb0fcd0, + 0xfc90fca0, + 0x0088fe80, + 0x32f480fc, + 0xf001f800, + 0x0e9801f7, + 0x04febb00, + 0x9418e7f1, + 0xf440e3f0, + 0x00f88d21, + 0x0614e7f1, + 0xf006e4b6, + 0xefd020f7, + 0x08f7f000, + 0xf401f2b6, + 0xf7f1fd1b, + 0xefd00a20, + 0xf100f800, + 0xb60a0417, + 0x1fd00614, + 0x0711f400, + 0x04a421f5, + 0x4afc17f1, + 0xf00213f0, + 0x12d00c27, + 0x0721f500, + 0xfc27f102, + 0x0223f047, + 0xf00020d0, + 0x20b6012c, + 0x0012d003, + 0xf001acf0, + 0xb7f002a5, + 0x50b3f000, + 0xb6000c98, + 0xbcbb0fc4, + 0x010c9800, + 0xf0020d98, + 0x21f500e7, + 0xacf0015c, + 0x04a5f001, + 0x4000b7f1, + 0x9850b3f0, + 0xc4b6000c, + 0x00bcbb0f, + 0x98050c98, + 0x0f98060d, + 0x00e7f104, + 0x5c21f508, + 0x0721f501, + 0x0601f402, + 0xf11412f4, + 0xf04afc17, + 0x27f00213, + 0x0012d00d, + 0x020721f5, + 0x048f21f5, + 0x000000f8, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; diff --git a/drivers/gpu/drm/nouveau/nvc0_grhub.fuc b/drivers/gpu/drm/nouveau/nvc0_grhub.fuc new file mode 100644 index 00000000..98acddb2 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_grhub.fuc @@ -0,0 +1,856 @@ +/* fuc microcode for nvc0 PGRAPH/HUB + * + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +/* To build: + * m4 nvc0_grhub.fuc | envyas -a -w -m fuc -V nva3 -o nvc0_grhub.fuc.h + */ + +.section #nvc0_grhub_data +include(`nvc0_graph.fuc') +gpc_count: .b32 0 +rop_count: .b32 0 +cmd_queue: queue_init +hub_mmio_list_head: .b32 0 +hub_mmio_list_tail: .b32 0 + +ctx_current: .b32 0 + +chipsets: +.b8 0xc0 0 0 0 +.b16 #nvc0_hub_mmio_head +.b16 #nvc0_hub_mmio_tail +.b8 0xc1 0 0 0 +.b16 #nvc0_hub_mmio_head +.b16 #nvc1_hub_mmio_tail +.b8 0xc3 0 0 0 +.b16 #nvc0_hub_mmio_head +.b16 #nvc0_hub_mmio_tail +.b8 0xc4 0 0 0 +.b16 #nvc0_hub_mmio_head +.b16 #nvc0_hub_mmio_tail +.b8 0xc8 0 0 0 +.b16 #nvc0_hub_mmio_head +.b16 #nvc0_hub_mmio_tail +.b8 0xce 0 0 0 +.b16 #nvc0_hub_mmio_head +.b16 #nvc0_hub_mmio_tail +.b8 0xcf 0 0 0 +.b16 #nvc0_hub_mmio_head +.b16 #nvc0_hub_mmio_tail +.b8 0xd9 0 0 0 +.b16 #nvd9_hub_mmio_head +.b16 #nvd9_hub_mmio_tail +.b8 0 0 0 0 + +nvc0_hub_mmio_head: +mmctx_data(0x17e91c, 2) +mmctx_data(0x400204, 2) +mmctx_data(0x404004, 11) +mmctx_data(0x404044, 1) +mmctx_data(0x404094, 14) +mmctx_data(0x4040d0, 7) +mmctx_data(0x4040f8, 1) +mmctx_data(0x404130, 3) +mmctx_data(0x404150, 3) +mmctx_data(0x404164, 2) +mmctx_data(0x404174, 3) +mmctx_data(0x404200, 8) +mmctx_data(0x404404, 14) +mmctx_data(0x404460, 4) +mmctx_data(0x404480, 1) +mmctx_data(0x404498, 1) +mmctx_data(0x404604, 4) +mmctx_data(0x404618, 32) +mmctx_data(0x404698, 21) +mmctx_data(0x4046f0, 2) +mmctx_data(0x404700, 22) +mmctx_data(0x405800, 1) +mmctx_data(0x405830, 3) +mmctx_data(0x405854, 1) +mmctx_data(0x405870, 4) +mmctx_data(0x405a00, 2) +mmctx_data(0x405a18, 1) +mmctx_data(0x406020, 1) +mmctx_data(0x406028, 4) +mmctx_data(0x4064a8, 2) +mmctx_data(0x4064b4, 2) +mmctx_data(0x407804, 1) +mmctx_data(0x40780c, 6) +mmctx_data(0x4078bc, 1) +mmctx_data(0x408000, 7) +mmctx_data(0x408064, 1) +mmctx_data(0x408800, 3) +mmctx_data(0x408900, 4) +mmctx_data(0x408980, 1) +nvc0_hub_mmio_tail: +mmctx_data(0x4064c0, 2) +nvc1_hub_mmio_tail: + +nvd9_hub_mmio_head: +mmctx_data(0x17e91c, 2) +mmctx_data(0x400204, 2) +mmctx_data(0x404004, 10) +mmctx_data(0x404044, 1) +mmctx_data(0x404094, 14) +mmctx_data(0x4040d0, 7) +mmctx_data(0x4040f8, 1) +mmctx_data(0x404130, 3) +mmctx_data(0x404150, 3) +mmctx_data(0x404164, 2) +mmctx_data(0x404178, 2) +mmctx_data(0x404200, 8) +mmctx_data(0x404404, 14) +mmctx_data(0x404460, 4) +mmctx_data(0x404480, 1) +mmctx_data(0x404498, 1) +mmctx_data(0x404604, 4) +mmctx_data(0x404618, 32) +mmctx_data(0x404698, 21) +mmctx_data(0x4046f0, 2) +mmctx_data(0x404700, 22) +mmctx_data(0x405800, 1) +mmctx_data(0x405830, 3) +mmctx_data(0x405854, 1) +mmctx_data(0x405870, 4) +mmctx_data(0x405a00, 2) +mmctx_data(0x405a18, 1) +mmctx_data(0x406020, 1) +mmctx_data(0x406028, 4) +mmctx_data(0x4064a8, 2) +mmctx_data(0x4064b4, 5) +mmctx_data(0x407804, 1) +mmctx_data(0x40780c, 6) +mmctx_data(0x4078bc, 1) +mmctx_data(0x408000, 7) +mmctx_data(0x408064, 1) +mmctx_data(0x408800, 3) +mmctx_data(0x408900, 4) +mmctx_data(0x408980, 1) +nvd9_hub_mmio_tail: + +.align 256 +chan_data: +chan_mmio_count: .b32 0 +chan_mmio_address: .b32 0 + +.align 256 +xfer_data: .b32 0 + +.section #nvc0_grhub_code +bra #init +define(`include_code') +include(`nvc0_graph.fuc') + +// reports an exception to the host +// +// In: $r15 error code (see nvc0_graph.fuc) +// +error: + push $r14 + mov $r14 0x814 + shl b32 $r14 6 + iowr I[$r14 + 0x000] $r15 // CC_SCRATCH[5] = error code + mov $r14 0xc1c + shl b32 $r14 6 + mov $r15 1 + iowr I[$r14 + 0x000] $r15 // INTR_UP_SET + pop $r14 + ret + +// HUB fuc initialisation, executed by triggering ucode start, will +// fall through to main loop after completion. +// +// Input: +// CC_SCRATCH[0]: chipset (PMC_BOOT_0 read returns 0x0bad0bad... sigh) +// +// Output: +// CC_SCRATCH[0]: +// 31:31: set to signal completion +// CC_SCRATCH[1]: +// 31:0: total PGRAPH context size +// +init: + clear b32 $r0 + mov $sp $r0 + mov $xdbase $r0 + + // enable fifo access + mov $r1 0x1200 + mov $r2 2 + iowr I[$r1 + 0x000] $r2 // FIFO_ENABLE + + // setup i0 handler, and route all interrupts to it + mov $r1 #ih + mov $iv0 $r1 + mov $r1 0x400 + iowr I[$r1 + 0x300] $r0 // INTR_DISPATCH + + // route HUB_CHANNEL_SWITCH to fuc interrupt 8 + mov $r3 0x404 + shl b32 $r3 6 + mov $r2 0x2003 // { HUB_CHANNEL_SWITCH, ZERO } -> intr 8 + iowr I[$r3 + 0x000] $r2 + + // not sure what these are, route them because NVIDIA does, and + // the IRQ handler will signal the host if we ever get one.. we + // may find out if/why we need to handle these if so.. + // + mov $r2 0x2004 + iowr I[$r3 + 0x004] $r2 // { 0x04, ZERO } -> intr 9 + mov $r2 0x200b + iowr I[$r3 + 0x008] $r2 // { 0x0b, ZERO } -> intr 10 + mov $r2 0x200c + iowr I[$r3 + 0x01c] $r2 // { 0x0c, ZERO } -> intr 15 + + // enable all INTR_UP interrupts + mov $r2 0xc24 + shl b32 $r2 6 + not b32 $r3 $r0 + iowr I[$r2] $r3 + + // enable fifo, ctxsw, 9, 10, 15 interrupts + mov $r2 -0x78fc // 0x8704 + sethi $r2 0 + iowr I[$r1 + 0x000] $r2 // INTR_EN_SET + + // fifo level triggered, rest edge + sub b32 $r1 0x100 + mov $r2 4 + iowr I[$r1] $r2 + + // enable interrupts + bset $flags ie0 + + // fetch enabled GPC/ROP counts + mov $r14 -0x69fc // 0x409604 + sethi $r14 0x400000 + call #nv_rd32 + extr $r1 $r15 16:20 + st b32 D[$r0 + #rop_count] $r1 + and $r15 0x1f + st b32 D[$r0 + #gpc_count] $r15 + + // set BAR_REQMASK to GPC mask + mov $r1 1 + shl b32 $r1 $r15 + sub b32 $r1 1 + mov $r2 0x40c + shl b32 $r2 6 + iowr I[$r2 + 0x000] $r1 + iowr I[$r2 + 0x100] $r1 + + // find context data for this chipset + mov $r2 0x800 + shl b32 $r2 6 + iord $r2 I[$r2 + 0x000] // CC_SCRATCH[0] + mov $r15 #chipsets - 8 + init_find_chipset: + add b32 $r15 8 + ld b32 $r3 D[$r15 + 0x00] + cmpu b32 $r3 $r2 + bra e #init_context + cmpu b32 $r3 0 + bra ne #init_find_chipset + // unknown chipset + ret + + // context size calculation, reserve first 256 bytes for use by fuc + init_context: + mov $r1 256 + + // calculate size of mmio context data + ld b16 $r14 D[$r15 + 4] + ld b16 $r15 D[$r15 + 6] + sethi $r14 0 + st b32 D[$r0 + #hub_mmio_list_head] $r14 + st b32 D[$r0 + #hub_mmio_list_tail] $r15 + call #mmctx_size + + // set mmctx base addresses now so we don't have to do it later, + // they don't (currently) ever change + mov $r3 0x700 + shl b32 $r3 6 + shr b32 $r4 $r1 8 + iowr I[$r3 + 0x000] $r4 // MMCTX_SAVE_SWBASE + iowr I[$r3 + 0x100] $r4 // MMCTX_LOAD_SWBASE + add b32 $r3 0x1300 + add b32 $r1 $r15 + shr b32 $r15 2 + iowr I[$r3 + 0x000] $r15 // MMCTX_LOAD_COUNT, wtf for?!? + + // strands, base offset needs to be aligned to 256 bytes + shr b32 $r1 8 + add b32 $r1 1 + shl b32 $r1 8 + mov b32 $r15 $r1 + call #strand_ctx_init + add b32 $r1 $r15 + + // initialise each GPC in sequence by passing in the offset of its + // context data in GPCn_CC_SCRATCH[1], and starting its FUC (which + // has previously been uploaded by the host) running. + // + // the GPC fuc init sequence will set GPCn_CC_SCRATCH[0] bit 31 + // when it has completed, and return the size of its context data + // in GPCn_CC_SCRATCH[1] + // + ld b32 $r3 D[$r0 + #gpc_count] + mov $r4 0x2000 + sethi $r4 0x500000 + init_gpc: + // setup, and start GPC ucode running + add b32 $r14 $r4 0x804 + mov b32 $r15 $r1 + call #nv_wr32 // CC_SCRATCH[1] = ctx offset + add b32 $r14 $r4 0x800 + mov b32 $r15 $r2 + call #nv_wr32 // CC_SCRATCH[0] = chipset + add b32 $r14 $r4 0x10c + clear b32 $r15 + call #nv_wr32 + add b32 $r14 $r4 0x104 + call #nv_wr32 // ENTRY + add b32 $r14 $r4 0x100 + mov $r15 2 // CTRL_START_TRIGGER + call #nv_wr32 // CTRL + + // wait for it to complete, and adjust context size + add b32 $r14 $r4 0x800 + init_gpc_wait: + call #nv_rd32 + xbit $r15 $r15 31 + bra e #init_gpc_wait + add b32 $r14 $r4 0x804 + call #nv_rd32 + add b32 $r1 $r15 + + // next! + add b32 $r4 0x8000 + sub b32 $r3 1 + bra ne #init_gpc + + // save context size, and tell host we're ready + mov $r2 0x800 + shl b32 $r2 6 + iowr I[$r2 + 0x100] $r1 // CC_SCRATCH[1] = context size + add b32 $r2 0x800 + clear b32 $r1 + bset $r1 31 + iowr I[$r2 + 0x000] $r1 // CC_SCRATCH[0] |= 0x80000000 + +// Main program loop, very simple, sleeps until woken up by the interrupt +// handler, pulls a command from the queue and executes its handler +// +main: + // sleep until we have something to do + bset $flags $p0 + sleep $p0 + mov $r13 #cmd_queue + call #queue_get + bra $p1 #main + + // context switch, requested by GPU? + cmpu b32 $r14 0x4001 + bra ne #main_not_ctx_switch + trace_set(T_AUTO) + mov $r1 0xb00 + shl b32 $r1 6 + iord $r2 I[$r1 + 0x100] // CHAN_NEXT + iord $r1 I[$r1 + 0x000] // CHAN_CUR + + xbit $r3 $r1 31 + bra e #chsw_no_prev + xbit $r3 $r2 31 + bra e #chsw_prev_no_next + push $r2 + mov b32 $r2 $r1 + trace_set(T_SAVE) + bclr $flags $p1 + bset $flags $p2 + call #ctx_xfer + trace_clr(T_SAVE); + pop $r2 + trace_set(T_LOAD); + bset $flags $p1 + call #ctx_xfer + trace_clr(T_LOAD); + bra #chsw_done + chsw_prev_no_next: + push $r2 + mov b32 $r2 $r1 + bclr $flags $p1 + bclr $flags $p2 + call #ctx_xfer + pop $r2 + mov $r1 0xb00 + shl b32 $r1 6 + iowr I[$r1] $r2 + bra #chsw_done + chsw_no_prev: + xbit $r3 $r2 31 + bra e #chsw_done + bset $flags $p1 + bclr $flags $p2 + call #ctx_xfer + + // ack the context switch request + chsw_done: + mov $r1 0xb0c + shl b32 $r1 6 + mov $r2 1 + iowr I[$r1 + 0x000] $r2 // 0x409b0c + trace_clr(T_AUTO) + bra #main + + // request to set current channel? (*not* a context switch) + main_not_ctx_switch: + cmpu b32 $r14 0x0001 + bra ne #main_not_ctx_chan + mov b32 $r2 $r15 + call #ctx_chan + bra #main_done + + // request to store current channel context? + main_not_ctx_chan: + cmpu b32 $r14 0x0002 + bra ne #main_not_ctx_save + trace_set(T_SAVE) + bclr $flags $p1 + bclr $flags $p2 + call #ctx_xfer + trace_clr(T_SAVE) + bra #main_done + + main_not_ctx_save: + shl b32 $r15 $r14 16 + or $r15 E_BAD_COMMAND + call #error + bra #main + + main_done: + mov $r1 0x820 + shl b32 $r1 6 + clear b32 $r2 + bset $r2 31 + iowr I[$r1 + 0x000] $r2 // CC_SCRATCH[0] |= 0x80000000 + bra #main + +// interrupt handler +ih: + push $r8 + mov $r8 $flags + push $r8 + push $r9 + push $r10 + push $r11 + push $r13 + push $r14 + push $r15 + + // incoming fifo command? + iord $r10 I[$r0 + 0x200] // INTR + and $r11 $r10 0x00000004 + bra e #ih_no_fifo + // queue incoming fifo command for later processing + mov $r11 0x1900 + mov $r13 #cmd_queue + iord $r14 I[$r11 + 0x100] // FIFO_CMD + iord $r15 I[$r11 + 0x000] // FIFO_DATA + call #queue_put + add b32 $r11 0x400 + mov $r14 1 + iowr I[$r11 + 0x000] $r14 // FIFO_ACK + + // context switch request? + ih_no_fifo: + and $r11 $r10 0x00000100 + bra e #ih_no_ctxsw + // enqueue a context switch for later processing + mov $r13 #cmd_queue + mov $r14 0x4001 + call #queue_put + + // anything we didn't handle, bring it to the host's attention + ih_no_ctxsw: + mov $r11 0x104 + not b32 $r11 + and $r11 $r10 $r11 + bra e #ih_no_other + mov $r10 0xc1c + shl b32 $r10 6 + iowr I[$r10] $r11 // INTR_UP_SET + + // ack, and wake up main() + ih_no_other: + iowr I[$r0 + 0x100] $r10 // INTR_ACK + + pop $r15 + pop $r14 + pop $r13 + pop $r11 + pop $r10 + pop $r9 + pop $r8 + mov $flags $r8 + pop $r8 + bclr $flags $p0 + iret + +// Not real sure, but, MEM_CMD 7 will hang forever if this isn't done +ctx_4160s: + mov $r14 0x4160 + sethi $r14 0x400000 + mov $r15 1 + call #nv_wr32 + ctx_4160s_wait: + call #nv_rd32 + xbit $r15 $r15 4 + bra e #ctx_4160s_wait + ret + +// Without clearing again at end of xfer, some things cause PGRAPH +// to hang with STATUS=0x00000007 until it's cleared.. fbcon can +// still function with it set however... +ctx_4160c: + mov $r14 0x4160 + sethi $r14 0x400000 + clear b32 $r15 + call #nv_wr32 + ret + +// Again, not real sure +// +// In: $r15 value to set 0x404170 to +// +ctx_4170s: + mov $r14 0x4170 + sethi $r14 0x400000 + or $r15 0x10 + call #nv_wr32 + ret + +// Waits for a ctx_4170s() call to complete +// +ctx_4170w: + mov $r14 0x4170 + sethi $r14 0x400000 + call #nv_rd32 + and $r15 0x10 + bra ne #ctx_4170w + ret + +// Disables various things, waits a bit, and re-enables them.. +// +// Not sure how exactly this helps, perhaps "ENABLE" is not such a +// good description for the bits we turn off? Anyways, without this, +// funny things happen. +// +ctx_redswitch: + mov $r14 0x614 + shl b32 $r14 6 + mov $r15 0x270 + iowr I[$r14] $r15 // HUB_RED_SWITCH = ENABLE_GPC, POWER_ALL + mov $r15 8 + ctx_redswitch_delay: + sub b32 $r15 1 + bra ne #ctx_redswitch_delay + mov $r15 0x770 + iowr I[$r14] $r15 // HUB_RED_SWITCH = ENABLE_ALL, POWER_ALL + ret + +// Not a clue what this is for, except that unless the value is 0x10, the +// strand context is saved (and presumably restored) incorrectly.. +// +// In: $r15 value to set to (0x00/0x10 are used) +// +ctx_86c: + mov $r14 0x86c + shl b32 $r14 6 + iowr I[$r14] $r15 // HUB(0x86c) = val + mov $r14 -0x75ec + sethi $r14 0x400000 + call #nv_wr32 // ROP(0xa14) = val + mov $r14 -0x5794 + sethi $r14 0x410000 + call #nv_wr32 // GPC(0x86c) = val + ret + +// ctx_load - load's a channel's ctxctl data, and selects its vm +// +// In: $r2 channel address +// +ctx_load: + trace_set(T_CHAN) + + // switch to channel, somewhat magic in parts.. + mov $r10 12 // DONE_UNK12 + call #wait_donez + mov $r1 0xa24 + shl b32 $r1 6 + iowr I[$r1 + 0x000] $r0 // 0x409a24 + mov $r3 0xb00 + shl b32 $r3 6 + iowr I[$r3 + 0x100] $r2 // CHAN_NEXT + mov $r1 0xa0c + shl b32 $r1 6 + mov $r4 7 + iowr I[$r1 + 0x000] $r2 // MEM_CHAN + iowr I[$r1 + 0x100] $r4 // MEM_CMD + ctx_chan_wait_0: + iord $r4 I[$r1 + 0x100] + and $r4 0x1f + bra ne #ctx_chan_wait_0 + iowr I[$r3 + 0x000] $r2 // CHAN_CUR + + // load channel header, fetch PGRAPH context pointer + mov $xtargets $r0 + bclr $r2 31 + shl b32 $r2 4 + add b32 $r2 2 + + trace_set(T_LCHAN) + mov $r1 0xa04 + shl b32 $r1 6 + iowr I[$r1 + 0x000] $r2 // MEM_BASE + mov $r1 0xa20 + shl b32 $r1 6 + mov $r2 0x0002 + sethi $r2 0x80000000 + iowr I[$r1 + 0x000] $r2 // MEM_TARGET = vram + mov $r1 0x10 // chan + 0x0210 + mov $r2 #xfer_data + sethi $r2 0x00020000 // 16 bytes + xdld $r1 $r2 + xdwait + trace_clr(T_LCHAN) + + // update current context + ld b32 $r1 D[$r0 + #xfer_data + 4] + shl b32 $r1 24 + ld b32 $r2 D[$r0 + #xfer_data + 0] + shr b32 $r2 8 + or $r1 $r2 + st b32 D[$r0 + #ctx_current] $r1 + + // set transfer base to start of context, and fetch context header + trace_set(T_LCTXH) + mov $r2 0xa04 + shl b32 $r2 6 + iowr I[$r2 + 0x000] $r1 // MEM_BASE + mov $r2 1 + mov $r1 0xa20 + shl b32 $r1 6 + iowr I[$r1 + 0x000] $r2 // MEM_TARGET = vm + mov $r1 #chan_data + sethi $r1 0x00060000 // 256 bytes + xdld $r0 $r1 + xdwait + trace_clr(T_LCTXH) + + trace_clr(T_CHAN) + ret + +// ctx_chan - handler for HUB_SET_CHAN command, will set a channel as +// the active channel for ctxctl, but not actually transfer +// any context data. intended for use only during initial +// context construction. +// +// In: $r2 channel address +// +ctx_chan: + call #ctx_4160s + call #ctx_load + mov $r10 12 // DONE_UNK12 + call #wait_donez + mov $r1 0xa10 + shl b32 $r1 6 + mov $r2 5 + iowr I[$r1 + 0x000] $r2 // MEM_CMD = 5 (???) + ctx_chan_wait: + iord $r2 I[$r1 + 0x000] + or $r2 $r2 + bra ne #ctx_chan_wait + call #ctx_4160c + ret + +// Execute per-context state overrides list +// +// Only executed on the first load of a channel. Might want to look into +// removing this and having the host directly modify the channel's context +// to change this state... The nouveau DRM already builds this list as +// it's definitely needed for NVIDIA's, so we may as well use it for now +// +// Input: $r1 mmio list length +// +ctx_mmio_exec: + // set transfer base to be the mmio list + ld b32 $r3 D[$r0 + #chan_mmio_address] + mov $r2 0xa04 + shl b32 $r2 6 + iowr I[$r2 + 0x000] $r3 // MEM_BASE + + clear b32 $r3 + ctx_mmio_loop: + // fetch next 256 bytes of mmio list if necessary + and $r4 $r3 0xff + bra ne #ctx_mmio_pull + mov $r5 #xfer_data + sethi $r5 0x00060000 // 256 bytes + xdld $r3 $r5 + xdwait + + // execute a single list entry + ctx_mmio_pull: + ld b32 $r14 D[$r4 + #xfer_data + 0x00] + ld b32 $r15 D[$r4 + #xfer_data + 0x04] + call #nv_wr32 + + // next! + add b32 $r3 8 + sub b32 $r1 1 + bra ne #ctx_mmio_loop + + // set transfer base back to the current context + ctx_mmio_done: + ld b32 $r3 D[$r0 + #ctx_current] + iowr I[$r2 + 0x000] $r3 // MEM_BASE + + // disable the mmio list now, we don't need/want to execute it again + st b32 D[$r0 + #chan_mmio_count] $r0 + mov $r1 #chan_data + sethi $r1 0x00060000 // 256 bytes + xdst $r0 $r1 + xdwait + ret + +// Transfer HUB context data between GPU and storage area +// +// In: $r2 channel address +// $p1 clear on save, set on load +// $p2 set if opposite direction done/will be done, so: +// on save it means: "a load will follow this save" +// on load it means: "a save preceeded this load" +// +ctx_xfer: + bra not $p1 #ctx_xfer_pre + bra $p2 #ctx_xfer_pre_load + ctx_xfer_pre: + mov $r15 0x10 + call #ctx_86c + call #ctx_4160s + bra not $p1 #ctx_xfer_exec + + ctx_xfer_pre_load: + mov $r15 2 + call #ctx_4170s + call #ctx_4170w + call #ctx_redswitch + clear b32 $r15 + call #ctx_4170s + call #ctx_load + + // fetch context pointer, and initiate xfer on all GPCs + ctx_xfer_exec: + ld b32 $r1 D[$r0 + #ctx_current] + mov $r2 0x414 + shl b32 $r2 6 + iowr I[$r2 + 0x000] $r0 // BAR_STATUS = reset + mov $r14 -0x5b00 + sethi $r14 0x410000 + mov b32 $r15 $r1 + call #nv_wr32 // GPC_BCAST_WRCMD_DATA = ctx pointer + add b32 $r14 4 + xbit $r15 $flags $p1 + xbit $r2 $flags $p2 + shl b32 $r2 1 + or $r15 $r2 + call #nv_wr32 // GPC_BCAST_WRCMD_CMD = GPC_XFER(type) + + // strands + mov $r1 0x4afc + sethi $r1 0x20000 + mov $r2 0xc + iowr I[$r1] $r2 // STRAND_CMD(0x3f) = 0x0c + call #strand_wait + mov $r2 0x47fc + sethi $r2 0x20000 + iowr I[$r2] $r0 // STRAND_FIRST_GENE(0x3f) = 0x00 + xbit $r2 $flags $p1 + add b32 $r2 3 + iowr I[$r1] $r2 // STRAND_CMD(0x3f) = 0x03/0x04 (SAVE/LOAD) + + // mmio context + xbit $r10 $flags $p1 // direction + or $r10 6 // first, last + mov $r11 0 // base = 0 + ld b32 $r12 D[$r0 + #hub_mmio_list_head] + ld b32 $r13 D[$r0 + #hub_mmio_list_tail] + mov $r14 0 // not multi + call #mmctx_xfer + + // wait for GPCs to all complete + mov $r10 8 // DONE_BAR + call #wait_doneo + + // wait for strand xfer to complete + call #strand_wait + + // post-op + bra $p1 #ctx_xfer_post + mov $r10 12 // DONE_UNK12 + call #wait_donez + mov $r1 0xa10 + shl b32 $r1 6 + mov $r2 5 + iowr I[$r1] $r2 // MEM_CMD + ctx_xfer_post_save_wait: + iord $r2 I[$r1] + or $r2 $r2 + bra ne #ctx_xfer_post_save_wait + + bra $p2 #ctx_xfer_done + ctx_xfer_post: + mov $r15 2 + call #ctx_4170s + clear b32 $r15 + call #ctx_86c + call #strand_post + call #ctx_4170w + clear b32 $r15 + call #ctx_4170s + + bra not $p1 #ctx_xfer_no_post_mmio + ld b32 $r1 D[$r0 + #chan_mmio_count] + or $r1 $r1 + bra e #ctx_xfer_no_post_mmio + call #ctx_mmio_exec + + ctx_xfer_no_post_mmio: + call #ctx_4160c + + ctx_xfer_done: + ret + +.align 256 diff --git a/drivers/gpu/drm/nouveau/nvc0_grhub.fuc.h b/drivers/gpu/drm/nouveau/nvc0_grhub.fuc.h new file mode 100644 index 00000000..c5ed307a --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_grhub.fuc.h @@ -0,0 +1,838 @@ +uint32_t nvc0_grhub_data[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x000000c0, + 0x013c00a0, + 0x000000c1, + 0x014000a0, + 0x000000c3, + 0x013c00a0, + 0x000000c4, + 0x013c00a0, + 0x000000c8, + 0x013c00a0, + 0x000000ce, + 0x013c00a0, + 0x000000cf, + 0x013c00a0, + 0x000000d9, + 0x01dc0140, + 0x00000000, + 0x0417e91c, + 0x04400204, + 0x28404004, + 0x00404044, + 0x34404094, + 0x184040d0, + 0x004040f8, + 0x08404130, + 0x08404150, + 0x04404164, + 0x08404174, + 0x1c404200, + 0x34404404, + 0x0c404460, + 0x00404480, + 0x00404498, + 0x0c404604, + 0x7c404618, + 0x50404698, + 0x044046f0, + 0x54404700, + 0x00405800, + 0x08405830, + 0x00405854, + 0x0c405870, + 0x04405a00, + 0x00405a18, + 0x00406020, + 0x0c406028, + 0x044064a8, + 0x044064b4, + 0x00407804, + 0x1440780c, + 0x004078bc, + 0x18408000, + 0x00408064, + 0x08408800, + 0x0c408900, + 0x00408980, + 0x044064c0, + 0x0417e91c, + 0x04400204, + 0x24404004, + 0x00404044, + 0x34404094, + 0x184040d0, + 0x004040f8, + 0x08404130, + 0x08404150, + 0x04404164, + 0x04404178, + 0x1c404200, + 0x34404404, + 0x0c404460, + 0x00404480, + 0x00404498, + 0x0c404604, + 0x7c404618, + 0x50404698, + 0x044046f0, + 0x54404700, + 0x00405800, + 0x08405830, + 0x00405854, + 0x0c405870, + 0x04405a00, + 0x00405a18, + 0x00406020, + 0x0c406028, + 0x044064a8, + 0x104064b4, + 0x00407804, + 0x1440780c, + 0x004078bc, + 0x18408000, + 0x00408064, + 0x08408800, + 0x0c408900, + 0x00408980, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +uint32_t nvc0_grhub_code[] = { + 0x03090ef5, + 0x9800d898, + 0x86f001d9, + 0x0489b808, + 0xf00c1bf4, + 0x21f502f7, + 0x00f802ec, + 0xb60798c4, + 0x8dbb0384, + 0x0880b600, + 0x80008e80, + 0x90b6018f, + 0x0f94f001, + 0xf801d980, + 0x0131f400, + 0x9800d898, + 0x89b801d9, + 0x210bf404, + 0xb60789c4, + 0x9dbb0394, + 0x0890b600, + 0x98009e98, + 0x80b6019f, + 0x0f84f001, + 0xf400d880, + 0x00f80132, + 0x0728b7f1, + 0xb906b4b6, + 0xc9f002ec, + 0x00bcd01f, + 0xc800bccf, + 0x1bf41fcc, + 0x06a7f0fa, + 0x010321f5, + 0xf840bfcf, + 0x28b7f100, + 0x06b4b607, + 0xb980bfd0, + 0xc9f002ec, + 0x1ec9f01f, + 0xcf00bcd0, + 0xccc800bc, + 0xfa1bf41f, + 0x87f100f8, + 0x84b60430, + 0x1ff9f006, + 0xf8008fd0, + 0x3087f100, + 0x0684b604, + 0xf80080d0, + 0x3c87f100, + 0x0684b608, + 0x99f094bd, + 0x0089d000, + 0x081887f1, + 0xd00684b6, + 0x87f1008a, + 0x84b60400, + 0x0088cf06, + 0xf4888aff, + 0x87f1f31b, + 0x84b6085c, + 0xf094bd06, + 0x89d00099, + 0xf100f800, + 0xb6083c87, + 0x94bd0684, + 0xd00099f0, + 0x87f10089, + 0x84b60818, + 0x008ad006, + 0x040087f1, + 0xcf0684b6, + 0x8aff0088, + 0xf30bf488, + 0x085c87f1, + 0xbd0684b6, + 0x0099f094, + 0xf80089d0, + 0x9894bd00, + 0x85b600e8, + 0x0180b61a, + 0xbb0284b6, + 0xe0b60098, + 0x04efb804, + 0xb9eb1bf4, + 0x00f8029f, + 0x083c87f1, + 0xbd0684b6, + 0x0199f094, + 0xf10089d0, + 0xb6071087, + 0x94bd0684, + 0xf405bbfd, + 0x8bd0090b, + 0x0099f000, + 0xf405eefd, + 0x8ed00c0b, + 0xc08fd080, + 0xb70199f0, + 0xc8010080, + 0xb4b600ab, + 0x0cb9f010, + 0xb601aec8, + 0xbefd11e4, + 0x008bd005, + 0xf0008ecf, + 0x0bf41fe4, + 0x00ce98fa, + 0xd005e9fd, + 0xc0b6c08e, + 0x04cdb804, + 0xc8e81bf4, + 0x1bf402ab, + 0x008bcf18, + 0xb01fb4f0, + 0x1bf410b4, + 0x02a7f0f7, + 0xf4c921f4, + 0xabc81b0e, + 0x10b4b600, + 0xf00cb9f0, + 0x8bd012b9, + 0x008bcf00, + 0xf412bbc8, + 0x87f1fa1b, + 0x84b6085c, + 0xf094bd06, + 0x89d00199, + 0xf900f800, + 0x02a7f0a0, + 0xfcc921f4, + 0xf100f8a0, + 0xf04afc87, + 0x97f00283, + 0x0089d00c, + 0x020721f5, + 0x87f100f8, + 0x83f04afc, + 0x0d97f002, + 0xf50089d0, + 0xf8020721, + 0xfca7f100, + 0x02a3f04f, + 0x0500aba2, + 0xd00fc7f0, + 0xc7f000ac, + 0x00bcd00b, + 0x020721f5, + 0xf000aed0, + 0xbcd00ac7, + 0x0721f500, + 0xf100f802, + 0xb6083c87, + 0x94bd0684, + 0xd00399f0, + 0x21f50089, + 0xe7f00213, + 0x3921f503, + 0xfca7f102, + 0x02a3f046, + 0x0400aba0, + 0xf040a0d0, + 0xbcd001c7, + 0x0721f500, + 0x010c9202, + 0xf000acd0, + 0xbcd002c7, + 0x0721f500, + 0x2621f502, + 0x8087f102, + 0x0684b608, + 0xb70089cf, + 0x95220080, + 0x8ed008fe, + 0x408ed000, + 0xb6808acf, + 0xa0b606a5, + 0x00eabb01, + 0xb60480b6, + 0x1bf40192, + 0x08e4b6e8, + 0xf1f2efbc, + 0xb6085c87, + 0x94bd0684, + 0xd00399f0, + 0x00f80089, + 0xe7f1e0f9, + 0xe4b60814, + 0x00efd006, + 0x0c1ce7f1, + 0xf006e4b6, + 0xefd001f7, + 0xf8e0fc00, + 0xfe04bd00, + 0x07fe0004, + 0x0017f100, + 0x0227f012, + 0xf10012d0, + 0xfe05b917, + 0x17f10010, + 0x10d00400, + 0x0437f1c0, + 0x0634b604, + 0x200327f1, + 0xf10032d0, + 0xd0200427, + 0x27f10132, + 0x32d0200b, + 0x0c27f102, + 0x0732d020, + 0x0c2427f1, + 0xb90624b6, + 0x23d00003, + 0x0427f100, + 0x0023f087, + 0xb70012d0, + 0xf0010012, + 0x12d00427, + 0x1031f400, + 0x9604e7f1, + 0xf440e3f0, + 0xf1c76821, + 0x01018090, + 0x801ff4f0, + 0x17f0000f, + 0x041fbb01, + 0xf10112b6, + 0xb6040c27, + 0x21d00624, + 0x4021d000, + 0x080027f1, + 0xcf0624b6, + 0xf7f00022, + 0x08f0b654, + 0xb800f398, + 0x0bf40432, + 0x0034b00b, + 0xf8f11bf4, + 0x0017f100, + 0x02fe5801, + 0xf003ff58, + 0x0e8000e3, + 0x150f8014, + 0x013d21f5, + 0x070037f1, + 0x950634b6, + 0x34d00814, + 0x4034d000, + 0x130030b7, + 0xb6001fbb, + 0x3fd002f5, + 0x0815b600, + 0xb60110b6, + 0x1fb90814, + 0x6321f502, + 0x001fbb02, + 0xf1000398, + 0xf0200047, + 0x4ea05043, + 0x1fb90804, + 0x8d21f402, + 0x08004ea0, + 0xf4022fb9, + 0x4ea08d21, + 0xf4bd010c, + 0xa08d21f4, + 0xf401044e, + 0x4ea08d21, + 0xf7f00100, + 0x8d21f402, + 0x08004ea0, + 0xc86821f4, + 0x0bf41fff, + 0x044ea0fa, + 0x6821f408, + 0xb7001fbb, + 0xb6800040, + 0x1bf40132, + 0x0027f1b4, + 0x0624b608, + 0xb74021d0, + 0xbd080020, + 0x1f19f014, + 0xf40021d0, + 0x28f40031, + 0x08d7f000, + 0xf43921f4, + 0xe4b1f401, + 0x1bf54001, + 0x87f100d1, + 0x84b6083c, + 0xf094bd06, + 0x89d00499, + 0x0017f100, + 0x0614b60b, + 0xcf4012cf, + 0x13c80011, + 0x7e0bf41f, + 0xf41f23c8, + 0x20f95a0b, + 0xf10212b9, + 0xb6083c87, + 0x94bd0684, + 0xd00799f0, + 0x32f40089, + 0x0231f401, + 0x082921f5, + 0x085c87f1, + 0xbd0684b6, + 0x0799f094, + 0xfc0089d0, + 0x3c87f120, + 0x0684b608, + 0x99f094bd, + 0x0089d006, + 0xf50131f4, + 0xf1082921, + 0xb6085c87, + 0x94bd0684, + 0xd00699f0, + 0x0ef40089, + 0xb920f931, + 0x32f40212, + 0x0232f401, + 0x082921f5, + 0x17f120fc, + 0x14b60b00, + 0x0012d006, + 0xc8130ef4, + 0x0bf41f23, + 0x0131f40d, + 0xf50232f4, + 0xf1082921, + 0xb60b0c17, + 0x27f00614, + 0x0012d001, + 0x085c87f1, + 0xbd0684b6, + 0x0499f094, + 0xf50089d0, + 0xb0ff200e, + 0x1bf401e4, + 0x02f2b90d, + 0x07b521f5, + 0xb0420ef4, + 0x1bf402e4, + 0x3c87f12e, + 0x0684b608, + 0x99f094bd, + 0x0089d007, + 0xf40132f4, + 0x21f50232, + 0x87f10829, + 0x84b6085c, + 0xf094bd06, + 0x89d00799, + 0x110ef400, + 0xf010ef94, + 0x21f501f5, + 0x0ef502ec, + 0x17f1fed1, + 0x14b60820, + 0xf024bd06, + 0x12d01f29, + 0xbe0ef500, + 0xfe80f9fe, + 0x80f90188, + 0xa0f990f9, + 0xd0f9b0f9, + 0xf0f9e0f9, + 0xc4800acf, + 0x0bf404ab, + 0x00b7f11d, + 0x08d7f019, + 0xcf40becf, + 0x21f400bf, + 0x00b0b704, + 0x01e7f004, + 0xe400bed0, + 0xf40100ab, + 0xd7f00d0b, + 0x01e7f108, + 0x0421f440, + 0x0104b7f1, + 0xabffb0bd, + 0x0d0bf4b4, + 0x0c1ca7f1, + 0xd006a4b6, + 0x0ad000ab, + 0xfcf0fc40, + 0xfcd0fce0, + 0xfca0fcb0, + 0xfe80fc90, + 0x80fc0088, + 0xf80032f4, + 0x60e7f101, + 0x40e3f041, + 0xf401f7f0, + 0x21f48d21, + 0x04ffc868, + 0xf8fa0bf4, + 0x60e7f100, + 0x40e3f041, + 0x21f4f4bd, + 0xf100f88d, + 0xf04170e7, + 0xf5f040e3, + 0x8d21f410, + 0xe7f100f8, + 0xe3f04170, + 0x6821f440, + 0xf410f4f0, + 0x00f8f31b, + 0x0614e7f1, + 0xf106e4b6, + 0xd00270f7, + 0xf7f000ef, + 0x01f2b608, + 0xf1fd1bf4, + 0xd00770f7, + 0x00f800ef, + 0x086ce7f1, + 0xd006e4b6, + 0xe7f100ef, + 0xe3f08a14, + 0x8d21f440, + 0xa86ce7f1, + 0xf441e3f0, + 0x00f88d21, + 0x083c87f1, + 0xbd0684b6, + 0x0599f094, + 0xf00089d0, + 0x21f40ca7, + 0x2417f1c9, + 0x0614b60a, + 0xf10010d0, + 0xb60b0037, + 0x32d00634, + 0x0c17f140, + 0x0614b60a, + 0xd00747f0, + 0x14d00012, + 0x4014cf40, + 0xf41f44f0, + 0x32d0fa1b, + 0x000bfe00, + 0xb61f2af0, + 0x20b60424, + 0x3c87f102, + 0x0684b608, + 0x99f094bd, + 0x0089d008, + 0x0a0417f1, + 0xd00614b6, + 0x17f10012, + 0x14b60a20, + 0x0227f006, + 0x800023f1, + 0xf00012d0, + 0x27f11017, + 0x23f00300, + 0x0512fa02, + 0x87f103f8, + 0x84b6085c, + 0xf094bd06, + 0x89d00899, + 0xc1019800, + 0x981814b6, + 0x25b6c002, + 0x0512fd08, + 0xf1160180, + 0xb6083c87, + 0x94bd0684, + 0xd00999f0, + 0x27f10089, + 0x24b60a04, + 0x0021d006, + 0xf10127f0, + 0xb60a2017, + 0x12d00614, + 0x0017f100, + 0x0613f002, + 0xf80501fa, + 0x5c87f103, + 0x0684b608, + 0x99f094bd, + 0x0089d009, + 0x085c87f1, + 0xbd0684b6, + 0x0599f094, + 0xf80089d0, + 0x3121f500, + 0xb821f506, + 0x0ca7f006, + 0xf1c921f4, + 0xb60a1017, + 0x27f00614, + 0x0012d005, + 0xfd0012cf, + 0x1bf40522, + 0x4921f5fa, + 0x9800f806, + 0x27f18103, + 0x24b60a04, + 0x0023d006, + 0x34c434bd, + 0x0f1bf4ff, + 0x030057f1, + 0xfa0653f0, + 0x03f80535, + 0x98c04e98, + 0x21f4c14f, + 0x0830b68d, + 0xf40112b6, + 0x0398df1b, + 0x0023d016, + 0xf1800080, + 0xf0020017, + 0x01fa0613, + 0xf803f806, + 0x0611f400, + 0xf01102f4, + 0x21f510f7, + 0x21f50698, + 0x11f40631, + 0x02f7f01c, + 0x065721f5, + 0x066621f5, + 0x067821f5, + 0x21f5f4bd, + 0x21f50657, + 0x019806b8, + 0x1427f116, + 0x0624b604, + 0xf10020d0, + 0xf0a500e7, + 0x1fb941e3, + 0x8d21f402, + 0xf004e0b6, + 0x2cf001fc, + 0x0124b602, + 0xf405f2fd, + 0x17f18d21, + 0x13f04afc, + 0x0c27f002, + 0xf50012d0, + 0xf1020721, + 0xf047fc27, + 0x20d00223, + 0x012cf000, + 0xd00320b6, + 0xacf00012, + 0x06a5f001, + 0x9800b7f0, + 0x0d98140c, + 0x00e7f015, + 0x015c21f5, + 0xf508a7f0, + 0xf5010321, + 0xf4020721, + 0xa7f02201, + 0xc921f40c, + 0x0a1017f1, + 0xf00614b6, + 0x12d00527, + 0x0012cf00, + 0xf40522fd, + 0x02f4fa1b, + 0x02f7f032, + 0x065721f5, + 0x21f5f4bd, + 0x21f50698, + 0x21f50226, + 0xf4bd0666, + 0x065721f5, + 0x981011f4, + 0x11fd8001, + 0x070bf405, + 0x07df21f5, + 0x064921f5, + 0x000000f8, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; diff --git a/drivers/gpu/drm/nouveau/nvc0_instmem.c b/drivers/gpu/drm/nouveau/nvc0_instmem.c new file mode 100644 index 00000000..b701c439 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_instmem.c @@ -0,0 +1,223 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_vm.h" + +struct nvc0_instmem_priv { + struct nouveau_gpuobj *bar1_pgd; + struct nouveau_channel *bar1; + struct nouveau_gpuobj *bar3_pgd; + struct nouveau_channel *bar3; +}; + +int +nvc0_instmem_suspend(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + + dev_priv->ramin_available = false; + return 0; +} + +void +nvc0_instmem_resume(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvc0_instmem_priv *priv = dev_priv->engine.instmem.priv; + + nv_mask(dev, 0x100c80, 0x00000001, 0x00000000); + nv_wr32(dev, 0x001704, 0x80000000 | priv->bar1->ramin->vinst >> 12); + nv_wr32(dev, 0x001714, 0xc0000000 | priv->bar3->ramin->vinst >> 12); + dev_priv->ramin_available = true; +} + +static void +nvc0_channel_del(struct nouveau_channel **pchan) +{ + struct nouveau_channel *chan; + + chan = *pchan; + *pchan = NULL; + if (!chan) + return; + + nouveau_vm_ref(NULL, &chan->vm, NULL); + if (drm_mm_initialized(&chan->ramin_heap)) + drm_mm_takedown(&chan->ramin_heap); + nouveau_gpuobj_ref(NULL, &chan->ramin); + kfree(chan); +} + +static int +nvc0_channel_new(struct drm_device *dev, u32 size, struct nouveau_vm *vm, + struct nouveau_channel **pchan, + struct nouveau_gpuobj *pgd, u64 vm_size) +{ + struct nouveau_channel *chan; + int ret; + + chan = kzalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + chan->dev = dev; + + ret = nouveau_gpuobj_new(dev, NULL, size, 0x1000, 0, &chan->ramin); + if (ret) { + nvc0_channel_del(&chan); + return ret; + } + + ret = drm_mm_init(&chan->ramin_heap, 0x1000, size - 0x1000); + if (ret) { + nvc0_channel_del(&chan); + return ret; + } + + ret = nouveau_vm_ref(vm, &chan->vm, NULL); + if (ret) { + nvc0_channel_del(&chan); + return ret; + } + + nv_wo32(chan->ramin, 0x0200, lower_32_bits(pgd->vinst)); + nv_wo32(chan->ramin, 0x0204, upper_32_bits(pgd->vinst)); + nv_wo32(chan->ramin, 0x0208, lower_32_bits(vm_size - 1)); + nv_wo32(chan->ramin, 0x020c, upper_32_bits(vm_size - 1)); + + *pchan = chan; + return 0; +} + +int +nvc0_instmem_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct pci_dev *pdev = dev->pdev; + struct nvc0_instmem_priv *priv; + struct nouveau_vm *vm = NULL; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + pinstmem->priv = priv; + + /* BAR3 VM */ + ret = nouveau_vm_new(dev, 0, pci_resource_len(pdev, 3), 0, + &dev_priv->bar3_vm); + if (ret) + goto error; + + ret = nouveau_gpuobj_new(dev, NULL, + (pci_resource_len(pdev, 3) >> 12) * 8, 0, + NVOBJ_FLAG_DONT_MAP | + NVOBJ_FLAG_ZERO_ALLOC, + &dev_priv->bar3_vm->pgt[0].obj[0]); + if (ret) + goto error; + dev_priv->bar3_vm->pgt[0].refcount[0] = 1; + + nv50_instmem_map(dev_priv->bar3_vm->pgt[0].obj[0]); + + ret = nouveau_gpuobj_new(dev, NULL, 0x8000, 4096, + NVOBJ_FLAG_ZERO_ALLOC, &priv->bar3_pgd); + if (ret) + goto error; + + ret = nouveau_vm_ref(dev_priv->bar3_vm, &vm, priv->bar3_pgd); + if (ret) + goto error; + nouveau_vm_ref(NULL, &vm, NULL); + + ret = nvc0_channel_new(dev, 8192, dev_priv->bar3_vm, &priv->bar3, + priv->bar3_pgd, pci_resource_len(dev->pdev, 3)); + if (ret) + goto error; + + /* BAR1 VM */ + ret = nouveau_vm_new(dev, 0, pci_resource_len(pdev, 1), 0, &vm); + if (ret) + goto error; + + ret = nouveau_gpuobj_new(dev, NULL, 0x8000, 4096, + NVOBJ_FLAG_ZERO_ALLOC, &priv->bar1_pgd); + if (ret) + goto error; + + ret = nouveau_vm_ref(vm, &dev_priv->bar1_vm, priv->bar1_pgd); + if (ret) + goto error; + nouveau_vm_ref(NULL, &vm, NULL); + + ret = nvc0_channel_new(dev, 8192, dev_priv->bar1_vm, &priv->bar1, + priv->bar1_pgd, pci_resource_len(dev->pdev, 1)); + if (ret) + goto error; + + /* channel vm */ + ret = nouveau_vm_new(dev, 0, (1ULL << 40), 0x0008000000ULL, + &dev_priv->chan_vm); + if (ret) + goto error; + + nvc0_instmem_resume(dev); + return 0; +error: + nvc0_instmem_takedown(dev); + return ret; +} + +void +nvc0_instmem_takedown(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvc0_instmem_priv *priv = dev_priv->engine.instmem.priv; + struct nouveau_vm *vm = NULL; + + nvc0_instmem_suspend(dev); + + nv_wr32(dev, 0x1704, 0x00000000); + nv_wr32(dev, 0x1714, 0x00000000); + + nouveau_vm_ref(NULL, &dev_priv->chan_vm, NULL); + + nvc0_channel_del(&priv->bar1); + nouveau_vm_ref(NULL, &dev_priv->bar1_vm, priv->bar1_pgd); + nouveau_gpuobj_ref(NULL, &priv->bar1_pgd); + + nvc0_channel_del(&priv->bar3); + nouveau_vm_ref(dev_priv->bar3_vm, &vm, NULL); + nouveau_vm_ref(NULL, &vm, priv->bar3_pgd); + nouveau_gpuobj_ref(NULL, &priv->bar3_pgd); + nouveau_gpuobj_ref(NULL, &dev_priv->bar3_vm->pgt[0].obj[0]); + nouveau_vm_ref(NULL, &dev_priv->bar3_vm, NULL); + + dev_priv->engine.instmem.priv = NULL; + kfree(priv); +} + diff --git a/drivers/gpu/drm/nouveau/nvc0_pm.c b/drivers/gpu/drm/nouveau/nvc0_pm.c new file mode 100644 index 00000000..ce65f81b --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_pm.c @@ -0,0 +1,392 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_bios.h" +#include "nouveau_pm.h" + +static u32 read_div(struct drm_device *, int, u32, u32); +static u32 read_pll(struct drm_device *, u32); + +static u32 +read_vco(struct drm_device *dev, u32 dsrc) +{ + u32 ssrc = nv_rd32(dev, dsrc); + if (!(ssrc & 0x00000100)) + return read_pll(dev, 0x00e800); + return read_pll(dev, 0x00e820); +} + +static u32 +read_pll(struct drm_device *dev, u32 pll) +{ + u32 ctrl = nv_rd32(dev, pll + 0); + u32 coef = nv_rd32(dev, pll + 4); + u32 P = (coef & 0x003f0000) >> 16; + u32 N = (coef & 0x0000ff00) >> 8; + u32 M = (coef & 0x000000ff) >> 0; + u32 sclk, doff; + + if (!(ctrl & 0x00000001)) + return 0; + + switch (pll & 0xfff000) { + case 0x00e000: + sclk = 27000; + P = 1; + break; + case 0x137000: + doff = (pll - 0x137000) / 0x20; + sclk = read_div(dev, doff, 0x137120, 0x137140); + break; + case 0x132000: + switch (pll) { + case 0x132000: + sclk = read_pll(dev, 0x132020); + break; + case 0x132020: + sclk = read_div(dev, 0, 0x137320, 0x137330); + break; + default: + return 0; + } + break; + default: + return 0; + } + + return sclk * N / M / P; +} + +static u32 +read_div(struct drm_device *dev, int doff, u32 dsrc, u32 dctl) +{ + u32 ssrc = nv_rd32(dev, dsrc + (doff * 4)); + u32 sctl = nv_rd32(dev, dctl + (doff * 4)); + + switch (ssrc & 0x00000003) { + case 0: + if ((ssrc & 0x00030000) != 0x00030000) + return 27000; + return 108000; + case 2: + return 100000; + case 3: + if (sctl & 0x80000000) { + u32 sclk = read_vco(dev, dsrc + (doff * 4)); + u32 sdiv = (sctl & 0x0000003f) + 2; + return (sclk * 2) / sdiv; + } + + return read_vco(dev, dsrc + (doff * 4)); + default: + return 0; + } +} + +static u32 +read_mem(struct drm_device *dev) +{ + u32 ssel = nv_rd32(dev, 0x1373f0); + if (ssel & 0x00000001) + return read_div(dev, 0, 0x137300, 0x137310); + return read_pll(dev, 0x132000); +} + +static u32 +read_clk(struct drm_device *dev, int clk) +{ + u32 sctl = nv_rd32(dev, 0x137250 + (clk * 4)); + u32 ssel = nv_rd32(dev, 0x137100); + u32 sclk, sdiv; + + if (ssel & (1 << clk)) { + if (clk < 7) + sclk = read_pll(dev, 0x137000 + (clk * 0x20)); + else + sclk = read_pll(dev, 0x1370e0); + sdiv = ((sctl & 0x00003f00) >> 8) + 2; + } else { + sclk = read_div(dev, clk, 0x137160, 0x1371d0); + sdiv = ((sctl & 0x0000003f) >> 0) + 2; + } + + if (sctl & 0x80000000) + return (sclk * 2) / sdiv; + return sclk; +} + +int +nvc0_pm_clocks_get(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + perflvl->shader = read_clk(dev, 0x00); + perflvl->core = perflvl->shader / 2; + perflvl->memory = read_mem(dev); + perflvl->rop = read_clk(dev, 0x01); + perflvl->hub07 = read_clk(dev, 0x02); + perflvl->hub06 = read_clk(dev, 0x07); + perflvl->hub01 = read_clk(dev, 0x08); + perflvl->copy = read_clk(dev, 0x09); + perflvl->daemon = read_clk(dev, 0x0c); + perflvl->vdec = read_clk(dev, 0x0e); + return 0; +} + +struct nvc0_pm_clock { + u32 freq; + u32 ssel; + u32 mdiv; + u32 dsrc; + u32 ddiv; + u32 coef; +}; + +struct nvc0_pm_state { + struct nvc0_pm_clock eng[16]; +}; + +static u32 +calc_div(struct drm_device *dev, int clk, u32 ref, u32 freq, u32 *ddiv) +{ + u32 div = min((ref * 2) / freq, (u32)65); + if (div < 2) + div = 2; + + *ddiv = div - 2; + return (ref * 2) / div; +} + +static u32 +calc_src(struct drm_device *dev, int clk, u32 freq, u32 *dsrc, u32 *ddiv) +{ + u32 sclk; + + /* use one of the fixed frequencies if possible */ + *ddiv = 0x00000000; + switch (freq) { + case 27000: + case 108000: + *dsrc = 0x00000000; + if (freq == 108000) + *dsrc |= 0x00030000; + return freq; + case 100000: + *dsrc = 0x00000002; + return freq; + default: + *dsrc = 0x00000003; + break; + } + + /* otherwise, calculate the closest divider */ + sclk = read_vco(dev, clk); + if (clk < 7) + sclk = calc_div(dev, clk, sclk, freq, ddiv); + return sclk; +} + +static u32 +calc_pll(struct drm_device *dev, int clk, u32 freq, u32 *coef) +{ + struct pll_lims limits; + int N, M, P, ret; + + ret = get_pll_limits(dev, 0x137000 + (clk * 0x20), &limits); + if (ret) + return 0; + + limits.refclk = read_div(dev, clk, 0x137120, 0x137140); + if (!limits.refclk) + return 0; + + ret = nva3_calc_pll(dev, &limits, freq, &N, NULL, &M, &P); + if (ret <= 0) + return 0; + + *coef = (P << 16) | (N << 8) | M; + return ret; +} + +/* A (likely rather simplified and incomplete) view of the clock tree + * + * Key: + * + * S: source select + * D: divider + * P: pll + * F: switch + * + * Engine clocks: + * + * 137250(D) ---- 137100(F0) ---- 137160(S)/1371d0(D) ------------------- ref + * (F1) ---- 1370X0(P) ---- 137120(S)/137140(D) ---- ref + * + * Not all registers exist for all clocks. For example: clocks >= 8 don't + * have their own PLL (all tied to clock 7's PLL when in PLL mode), nor do + * they have the divider at 1371d0, though the source selection at 137160 + * still exists. You must use the divider at 137250 for these instead. + * + * Memory clock: + * + * TBD, read_mem() above is likely very wrong... + * + */ + +static int +calc_clk(struct drm_device *dev, int clk, struct nvc0_pm_clock *info, u32 freq) +{ + u32 src0, div0, div1D, div1P = 0; + u32 clk0, clk1 = 0; + + /* invalid clock domain */ + if (!freq) + return 0; + + /* first possible path, using only dividers */ + clk0 = calc_src(dev, clk, freq, &src0, &div0); + clk0 = calc_div(dev, clk, clk0, freq, &div1D); + + /* see if we can get any closer using PLLs */ + if (clk0 != freq && (0x00004387 & (1 << clk))) { + if (clk < 7) + clk1 = calc_pll(dev, clk, freq, &info->coef); + else + clk1 = read_pll(dev, 0x1370e0); + clk1 = calc_div(dev, clk, clk1, freq, &div1P); + } + + /* select the method which gets closest to target freq */ + if (abs((int)freq - clk0) <= abs((int)freq - clk1)) { + info->dsrc = src0; + if (div0) { + info->ddiv |= 0x80000000; + info->ddiv |= div0 << 8; + info->ddiv |= div0; + } + if (div1D) { + info->mdiv |= 0x80000000; + info->mdiv |= div1D; + } + info->ssel = 0; + info->freq = clk0; + } else { + if (div1P) { + info->mdiv |= 0x80000000; + info->mdiv |= div1P << 8; + } + info->ssel = (1 << clk); + info->freq = clk1; + } + + return 0; +} + +void * +nvc0_pm_clocks_pre(struct drm_device *dev, struct nouveau_pm_level *perflvl) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvc0_pm_state *info; + int ret; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + /* NFI why this is still in the performance table, the ROPCs appear + * to get their clock from clock 2 ("hub07", actually hub05 on this + * chip, but, anyway...) as well. nvatiming confirms hub05 and ROP + * are always the same freq with the binary driver even when the + * performance table says they should differ. + */ + if (dev_priv->chipset == 0xd9) + perflvl->rop = 0; + + if ((ret = calc_clk(dev, 0x00, &info->eng[0x00], perflvl->shader)) || + (ret = calc_clk(dev, 0x01, &info->eng[0x01], perflvl->rop)) || + (ret = calc_clk(dev, 0x02, &info->eng[0x02], perflvl->hub07)) || + (ret = calc_clk(dev, 0x07, &info->eng[0x07], perflvl->hub06)) || + (ret = calc_clk(dev, 0x08, &info->eng[0x08], perflvl->hub01)) || + (ret = calc_clk(dev, 0x09, &info->eng[0x09], perflvl->copy)) || + (ret = calc_clk(dev, 0x0c, &info->eng[0x0c], perflvl->daemon)) || + (ret = calc_clk(dev, 0x0e, &info->eng[0x0e], perflvl->vdec))) { + kfree(info); + return ERR_PTR(ret); + } + + return info; +} + +static void +prog_clk(struct drm_device *dev, int clk, struct nvc0_pm_clock *info) +{ + /* program dividers at 137160/1371d0 first */ + if (clk < 7 && !info->ssel) { + nv_mask(dev, 0x1371d0 + (clk * 0x04), 0x80003f3f, info->ddiv); + nv_wr32(dev, 0x137160 + (clk * 0x04), info->dsrc); + } + + /* switch clock to non-pll mode */ + nv_mask(dev, 0x137100, (1 << clk), 0x00000000); + nv_wait(dev, 0x137100, (1 << clk), 0x00000000); + + /* reprogram pll */ + if (clk < 7) { + /* make sure it's disabled first... */ + u32 base = 0x137000 + (clk * 0x20); + u32 ctrl = nv_rd32(dev, base + 0x00); + if (ctrl & 0x00000001) { + nv_mask(dev, base + 0x00, 0x00000004, 0x00000000); + nv_mask(dev, base + 0x00, 0x00000001, 0x00000000); + } + /* program it to new values, if necessary */ + if (info->ssel) { + nv_wr32(dev, base + 0x04, info->coef); + nv_mask(dev, base + 0x00, 0x00000001, 0x00000001); + nv_wait(dev, base + 0x00, 0x00020000, 0x00020000); + nv_mask(dev, base + 0x00, 0x00020004, 0x00000004); + } + } + + /* select pll/non-pll mode, and program final clock divider */ + nv_mask(dev, 0x137100, (1 << clk), info->ssel); + nv_wait(dev, 0x137100, (1 << clk), info->ssel); + nv_mask(dev, 0x137250 + (clk * 0x04), 0x00003f3f, info->mdiv); +} + +int +nvc0_pm_clocks_set(struct drm_device *dev, void *data) +{ + struct nvc0_pm_state *info = data; + int i; + + for (i = 0; i < 16; i++) { + if (!info->eng[i].freq) + continue; + prog_clk(dev, i, &info->eng[i]); + } + + kfree(info); + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nvc0_vm.c b/drivers/gpu/drm/nouveau/nvc0_vm.c new file mode 100644 index 00000000..30d2bd58 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_vm.c @@ -0,0 +1,136 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" + +#include "nouveau_drv.h" +#include "nouveau_vm.h" + +void +nvc0_vm_map_pgt(struct nouveau_gpuobj *pgd, u32 index, + struct nouveau_gpuobj *pgt[2]) +{ + u32 pde[2] = { 0, 0 }; + + if (pgt[0]) + pde[1] = 0x00000001 | (pgt[0]->vinst >> 8); + if (pgt[1]) + pde[0] = 0x00000001 | (pgt[1]->vinst >> 8); + + nv_wo32(pgd, (index * 8) + 0, pde[0]); + nv_wo32(pgd, (index * 8) + 4, pde[1]); +} + +static inline u64 +nvc0_vm_addr(struct nouveau_vma *vma, u64 phys, u32 memtype, u32 target) +{ + phys >>= 8; + + phys |= 0x00000001; /* present */ + if (vma->access & NV_MEM_ACCESS_SYS) + phys |= 0x00000002; + + phys |= ((u64)target << 32); + phys |= ((u64)memtype << 36); + + return phys; +} + +void +nvc0_vm_map(struct nouveau_vma *vma, struct nouveau_gpuobj *pgt, + struct nouveau_mem *mem, u32 pte, u32 cnt, u64 phys, u64 delta) +{ + u32 next = 1 << (vma->node->type - 8); + + phys = nvc0_vm_addr(vma, phys, mem->memtype, 0); + pte <<= 3; + while (cnt--) { + nv_wo32(pgt, pte + 0, lower_32_bits(phys)); + nv_wo32(pgt, pte + 4, upper_32_bits(phys)); + phys += next; + pte += 8; + } +} + +void +nvc0_vm_map_sg(struct nouveau_vma *vma, struct nouveau_gpuobj *pgt, + struct nouveau_mem *mem, u32 pte, u32 cnt, dma_addr_t *list) +{ + u32 target = (vma->access & NV_MEM_ACCESS_NOSNOOP) ? 7 : 5; + + pte <<= 3; + while (cnt--) { + u64 phys = nvc0_vm_addr(vma, *list++, mem->memtype, target); + nv_wo32(pgt, pte + 0, lower_32_bits(phys)); + nv_wo32(pgt, pte + 4, upper_32_bits(phys)); + pte += 8; + } +} + +void +nvc0_vm_unmap(struct nouveau_gpuobj *pgt, u32 pte, u32 cnt) +{ + pte <<= 3; + while (cnt--) { + nv_wo32(pgt, pte + 0, 0x00000000); + nv_wo32(pgt, pte + 4, 0x00000000); + pte += 8; + } +} + +void +nvc0_vm_flush(struct nouveau_vm *vm) +{ + struct drm_nouveau_private *dev_priv = vm->dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct drm_device *dev = vm->dev; + struct nouveau_vm_pgd *vpgd; + unsigned long flags; + u32 engine; + + engine = 1; + if (vm == dev_priv->bar1_vm || vm == dev_priv->bar3_vm) + engine |= 4; + + pinstmem->flush(vm->dev); + + spin_lock_irqsave(&dev_priv->vm_lock, flags); + list_for_each_entry(vpgd, &vm->pgd_list, head) { + /* looks like maybe a "free flush slots" counter, the + * faster you write to 0x100cbc to more it decreases + */ + if (!nv_wait_ne(dev, 0x100c80, 0x00ff0000, 0x00000000)) { + NV_ERROR(dev, "vm timeout 0: 0x%08x %d\n", + nv_rd32(dev, 0x100c80), engine); + } + nv_wr32(dev, 0x100cb8, vpgd->obj->vinst >> 8); + nv_wr32(dev, 0x100cbc, 0x80000000 | engine); + /* wait for flush to be queued? */ + if (!nv_wait(dev, 0x100c80, 0x00008000, 0x00008000)) { + NV_ERROR(dev, "vm timeout 1: 0x%08x %d\n", + nv_rd32(dev, 0x100c80), engine); + } + } + spin_unlock_irqrestore(&dev_priv->vm_lock, flags); +} diff --git a/drivers/gpu/drm/nouveau/nvc0_vram.c b/drivers/gpu/drm/nouveau/nvc0_vram.c new file mode 100644 index 00000000..a7eef893 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvc0_vram.c @@ -0,0 +1,160 @@ +/* + * Copyright 2010 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include "drmP.h" +#include "nouveau_drv.h" +#include "nouveau_mm.h" + +/* 0 = unsupported + * 1 = non-compressed + * 3 = compressed + */ +static const u8 types[256] = { + 1, 1, 3, 3, 3, 3, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, + 3, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 3, 3, 3, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, + 3, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0, 3, 0, 3, + 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 0, 3, 0, 3, 3, 0, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 1, 1, 0 +}; + +bool +nvc0_vram_flags_valid(struct drm_device *dev, u32 tile_flags) +{ + u8 memtype = (tile_flags & NOUVEAU_GEM_TILE_LAYOUT_MASK) >> 8; + return likely((types[memtype] == 1)); +} + +int +nvc0_vram_new(struct drm_device *dev, u64 size, u32 align, u32 ncmin, + u32 type, struct nouveau_mem **pmem) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_mm *mm = &dev_priv->engine.vram.mm; + struct nouveau_mm_node *r; + struct nouveau_mem *mem; + int ret; + + size >>= 12; + align >>= 12; + ncmin >>= 12; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) + return -ENOMEM; + + INIT_LIST_HEAD(&mem->regions); + mem->dev = dev_priv->dev; + mem->memtype = (type & 0xff); + mem->size = size; + + mutex_lock(&mm->mutex); + do { + ret = nouveau_mm_get(mm, 1, size, ncmin, align, &r); + if (ret) { + mutex_unlock(&mm->mutex); + nv50_vram_del(dev, &mem); + return ret; + } + + list_add_tail(&r->rl_entry, &mem->regions); + size -= r->length; + } while (size); + mutex_unlock(&mm->mutex); + + r = list_first_entry(&mem->regions, struct nouveau_mm_node, rl_entry); + mem->offset = (u64)r->offset << 12; + *pmem = mem; + return 0; +} + +int +nvc0_vram_init(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_vram_engine *vram = &dev_priv->engine.vram; + const u32 rsvd_head = ( 256 * 1024) >> 12; /* vga memory */ + const u32 rsvd_tail = (1024 * 1024) >> 12; /* vbios etc */ + u32 parts = nv_rd32(dev, 0x022438); + u32 pmask = nv_rd32(dev, 0x022554); + u32 bsize = nv_rd32(dev, 0x10f20c); + u32 offset, length; + bool uniform = true; + int ret, part; + + NV_DEBUG(dev, "0x100800: 0x%08x\n", nv_rd32(dev, 0x100800)); + NV_DEBUG(dev, "parts 0x%08x mask 0x%08x\n", parts, pmask); + + dev_priv->vram_type = nouveau_mem_vbios_type(dev); + dev_priv->vram_rank_B = !!(nv_rd32(dev, 0x10f200) & 0x00000004); + + /* read amount of vram attached to each memory controller */ + for (part = 0; part < parts; part++) { + if (!(pmask & (1 << part))) { + u32 psize = nv_rd32(dev, 0x11020c + (part * 0x1000)); + if (psize != bsize) { + if (psize < bsize) + bsize = psize; + uniform = false; + } + + NV_DEBUG(dev, "%d: mem_amount 0x%08x\n", part, psize); + dev_priv->vram_size += (u64)psize << 20; + } + } + + /* if all controllers have the same amount attached, there's no holes */ + if (uniform) { + offset = rsvd_head; + length = (dev_priv->vram_size >> 12) - rsvd_head - rsvd_tail; + return nouveau_mm_init(&vram->mm, offset, length, 1); + } + + /* otherwise, address lowest common amount from 0GiB */ + ret = nouveau_mm_init(&vram->mm, rsvd_head, (bsize << 8) * parts, 1); + if (ret) + return ret; + + /* and the rest starting from (8GiB + common_size) */ + offset = (0x0200000000ULL >> 12) + (bsize << 8); + length = (dev_priv->vram_size >> 12) - (bsize << 8) - rsvd_tail; + + ret = nouveau_mm_init(&vram->mm, offset, length, 0); + if (ret) { + nouveau_mm_fini(&vram->mm); + return ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nvd0_display.c b/drivers/gpu/drm/nouveau/nvd0_display.c new file mode 100644 index 00000000..02472509 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvd0_display.c @@ -0,0 +1,2113 @@ +/* + * Copyright 2011 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ + +#include <linux/dma-mapping.h> + +#include "drmP.h" +#include "drm_crtc_helper.h" + +#include "nouveau_drv.h" +#include "nouveau_connector.h" +#include "nouveau_encoder.h" +#include "nouveau_crtc.h" +#include "nouveau_dma.h" +#include "nouveau_fb.h" +#include "nv50_display.h" + +#define EVO_DMA_NR 9 + +#define EVO_MASTER (0x00) +#define EVO_FLIP(c) (0x01 + (c)) +#define EVO_OVLY(c) (0x05 + (c)) +#define EVO_OIMM(c) (0x09 + (c)) +#define EVO_CURS(c) (0x0d + (c)) + +/* offsets in shared sync bo of various structures */ +#define EVO_SYNC(c, o) ((c) * 0x0100 + (o)) +#define EVO_MAST_NTFY EVO_SYNC( 0, 0x00) +#define EVO_FLIP_SEM0(c) EVO_SYNC((c), 0x00) +#define EVO_FLIP_SEM1(c) EVO_SYNC((c), 0x10) + +struct evo { + int idx; + dma_addr_t handle; + u32 *ptr; + struct { + u32 offset; + u16 value; + } sem; +}; + +struct nvd0_display { + struct nouveau_gpuobj *mem; + struct nouveau_bo *sync; + struct evo evo[9]; + + struct tasklet_struct tasklet; + u32 modeset; +}; + +static struct nvd0_display * +nvd0_display(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + return dev_priv->engine.display.priv; +} + +static struct drm_crtc * +nvd0_display_crtc_get(struct drm_encoder *encoder) +{ + return nouveau_encoder(encoder)->crtc; +} + +/****************************************************************************** + * EVO channel helpers + *****************************************************************************/ +static inline int +evo_icmd(struct drm_device *dev, int id, u32 mthd, u32 data) +{ + int ret = 0; + nv_mask(dev, 0x610700 + (id * 0x10), 0x00000001, 0x00000001); + nv_wr32(dev, 0x610704 + (id * 0x10), data); + nv_mask(dev, 0x610704 + (id * 0x10), 0x80000ffc, 0x80000000 | mthd); + if (!nv_wait(dev, 0x610704 + (id * 0x10), 0x80000000, 0x00000000)) + ret = -EBUSY; + nv_mask(dev, 0x610700 + (id * 0x10), 0x00000001, 0x00000000); + return ret; +} + +static u32 * +evo_wait(struct drm_device *dev, int id, int nr) +{ + struct nvd0_display *disp = nvd0_display(dev); + u32 put = nv_rd32(dev, 0x640000 + (id * 0x1000)) / 4; + + if (put + nr >= (PAGE_SIZE / 4)) { + disp->evo[id].ptr[put] = 0x20000000; + + nv_wr32(dev, 0x640000 + (id * 0x1000), 0x00000000); + if (!nv_wait(dev, 0x640004 + (id * 0x1000), ~0, 0x00000000)) { + NV_ERROR(dev, "evo %d dma stalled\n", id); + return NULL; + } + + put = 0; + } + + if (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO) + NV_INFO(dev, "Evo%d: %p START\n", id, disp->evo[id].ptr + put); + + return disp->evo[id].ptr + put; +} + +static void +evo_kick(u32 *push, struct drm_device *dev, int id) +{ + struct nvd0_display *disp = nvd0_display(dev); + + if (nouveau_reg_debug & NOUVEAU_REG_DEBUG_EVO) { + u32 curp = nv_rd32(dev, 0x640000 + (id * 0x1000)) >> 2; + u32 *cur = disp->evo[id].ptr + curp; + + while (cur < push) + NV_INFO(dev, "Evo%d: 0x%08x\n", id, *cur++); + NV_INFO(dev, "Evo%d: %p KICK!\n", id, push); + } + + nv_wr32(dev, 0x640000 + (id * 0x1000), (push - disp->evo[id].ptr) << 2); +} + +#define evo_mthd(p,m,s) *((p)++) = (((s) << 18) | (m)) +#define evo_data(p,d) *((p)++) = (d) + +static int +evo_init_dma(struct drm_device *dev, int ch) +{ + struct nvd0_display *disp = nvd0_display(dev); + u32 flags; + + flags = 0x00000000; + if (ch == EVO_MASTER) + flags |= 0x01000000; + + nv_wr32(dev, 0x610494 + (ch * 0x0010), (disp->evo[ch].handle >> 8) | 3); + nv_wr32(dev, 0x610498 + (ch * 0x0010), 0x00010000); + nv_wr32(dev, 0x61049c + (ch * 0x0010), 0x00000001); + nv_mask(dev, 0x610490 + (ch * 0x0010), 0x00000010, 0x00000010); + nv_wr32(dev, 0x640000 + (ch * 0x1000), 0x00000000); + nv_wr32(dev, 0x610490 + (ch * 0x0010), 0x00000013 | flags); + if (!nv_wait(dev, 0x610490 + (ch * 0x0010), 0x80000000, 0x00000000)) { + NV_ERROR(dev, "PDISP: ch%d 0x%08x\n", ch, + nv_rd32(dev, 0x610490 + (ch * 0x0010))); + return -EBUSY; + } + + nv_mask(dev, 0x610090, (1 << ch), (1 << ch)); + nv_mask(dev, 0x6100a0, (1 << ch), (1 << ch)); + return 0; +} + +static void +evo_fini_dma(struct drm_device *dev, int ch) +{ + if (!(nv_rd32(dev, 0x610490 + (ch * 0x0010)) & 0x00000010)) + return; + + nv_mask(dev, 0x610490 + (ch * 0x0010), 0x00000010, 0x00000000); + nv_mask(dev, 0x610490 + (ch * 0x0010), 0x00000003, 0x00000000); + nv_wait(dev, 0x610490 + (ch * 0x0010), 0x80000000, 0x00000000); + nv_mask(dev, 0x610090, (1 << ch), 0x00000000); + nv_mask(dev, 0x6100a0, (1 << ch), 0x00000000); +} + +static inline void +evo_piow(struct drm_device *dev, int ch, u16 mthd, u32 data) +{ + nv_wr32(dev, 0x640000 + (ch * 0x1000) + mthd, data); +} + +static int +evo_init_pio(struct drm_device *dev, int ch) +{ + nv_wr32(dev, 0x610490 + (ch * 0x0010), 0x00000001); + if (!nv_wait(dev, 0x610490 + (ch * 0x0010), 0x00010000, 0x00010000)) { + NV_ERROR(dev, "PDISP: ch%d 0x%08x\n", ch, + nv_rd32(dev, 0x610490 + (ch * 0x0010))); + return -EBUSY; + } + + nv_mask(dev, 0x610090, (1 << ch), (1 << ch)); + nv_mask(dev, 0x6100a0, (1 << ch), (1 << ch)); + return 0; +} + +static void +evo_fini_pio(struct drm_device *dev, int ch) +{ + if (!(nv_rd32(dev, 0x610490 + (ch * 0x0010)) & 0x00000001)) + return; + + nv_mask(dev, 0x610490 + (ch * 0x0010), 0x00000010, 0x00000010); + nv_mask(dev, 0x610490 + (ch * 0x0010), 0x00000001, 0x00000000); + nv_wait(dev, 0x610490 + (ch * 0x0010), 0x00010000, 0x00000000); + nv_mask(dev, 0x610090, (1 << ch), 0x00000000); + nv_mask(dev, 0x6100a0, (1 << ch), 0x00000000); +} + +static bool +evo_sync_wait(void *data) +{ + return nouveau_bo_rd32(data, EVO_MAST_NTFY) != 0x00000000; +} + +static int +evo_sync(struct drm_device *dev, int ch) +{ + struct nvd0_display *disp = nvd0_display(dev); + u32 *push = evo_wait(dev, ch, 8); + if (push) { + nouveau_bo_wr32(disp->sync, EVO_MAST_NTFY, 0x00000000); + evo_mthd(push, 0x0084, 1); + evo_data(push, 0x80000000 | EVO_MAST_NTFY); + evo_mthd(push, 0x0080, 2); + evo_data(push, 0x00000000); + evo_data(push, 0x00000000); + evo_kick(push, dev, ch); + if (nv_wait_cb(dev, evo_sync_wait, disp->sync)) + return 0; + } + + return -EBUSY; +} + +/****************************************************************************** + * Page flipping channel + *****************************************************************************/ +struct nouveau_bo * +nvd0_display_crtc_sema(struct drm_device *dev, int crtc) +{ + return nvd0_display(dev)->sync; +} + +void +nvd0_display_flip_stop(struct drm_crtc *crtc) +{ + struct nvd0_display *disp = nvd0_display(crtc->dev); + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct evo *evo = &disp->evo[EVO_FLIP(nv_crtc->index)]; + u32 *push; + + push = evo_wait(crtc->dev, evo->idx, 8); + if (push) { + evo_mthd(push, 0x0084, 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x0094, 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x00c0, 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x0080, 1); + evo_data(push, 0x00000000); + evo_kick(push, crtc->dev, evo->idx); + } +} + +int +nvd0_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct nouveau_channel *chan, u32 swap_interval) +{ + struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb); + struct nvd0_display *disp = nvd0_display(crtc->dev); + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct evo *evo = &disp->evo[EVO_FLIP(nv_crtc->index)]; + u64 offset; + u32 *push; + int ret; + + evo_sync(crtc->dev, EVO_MASTER); + + swap_interval <<= 4; + if (swap_interval == 0) + swap_interval |= 0x100; + + push = evo_wait(crtc->dev, evo->idx, 128); + if (unlikely(push == NULL)) + return -EBUSY; + + /* synchronise with the rendering channel, if necessary */ + if (likely(chan)) { + ret = RING_SPACE(chan, 10); + if (ret) + return ret; + + offset = chan->dispc_vma[nv_crtc->index].offset; + offset += evo->sem.offset; + + BEGIN_NVC0(chan, 2, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset)); + OUT_RING (chan, 0xf00d0000 | evo->sem.value); + OUT_RING (chan, 0x1002); + BEGIN_NVC0(chan, 2, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); + OUT_RING (chan, upper_32_bits(offset)); + OUT_RING (chan, lower_32_bits(offset ^ 0x10)); + OUT_RING (chan, 0x74b1e000); + OUT_RING (chan, 0x1001); + FIRE_RING (chan); + } else { + nouveau_bo_wr32(disp->sync, evo->sem.offset / 4, + 0xf00d0000 | evo->sem.value); + evo_sync(crtc->dev, EVO_MASTER); + } + + /* queue the flip */ + evo_mthd(push, 0x0100, 1); + evo_data(push, 0xfffe0000); + evo_mthd(push, 0x0084, 1); + evo_data(push, swap_interval); + if (!(swap_interval & 0x00000100)) { + evo_mthd(push, 0x00e0, 1); + evo_data(push, 0x40000000); + } + evo_mthd(push, 0x0088, 4); + evo_data(push, evo->sem.offset); + evo_data(push, 0xf00d0000 | evo->sem.value); + evo_data(push, 0x74b1e000); + evo_data(push, NvEvoSync); + evo_mthd(push, 0x00a0, 2); + evo_data(push, 0x00000000); + evo_data(push, 0x00000000); + evo_mthd(push, 0x00c0, 1); + evo_data(push, nv_fb->r_dma); + evo_mthd(push, 0x0110, 2); + evo_data(push, 0x00000000); + evo_data(push, 0x00000000); + evo_mthd(push, 0x0400, 5); + evo_data(push, nv_fb->nvbo->bo.offset >> 8); + evo_data(push, 0); + evo_data(push, (fb->height << 16) | fb->width); + evo_data(push, nv_fb->r_pitch); + evo_data(push, nv_fb->r_format); + evo_mthd(push, 0x0080, 1); + evo_data(push, 0x00000000); + evo_kick(push, crtc->dev, evo->idx); + + evo->sem.offset ^= 0x10; + evo->sem.value++; + return 0; +} + +/****************************************************************************** + * CRTC + *****************************************************************************/ +static int +nvd0_crtc_set_dither(struct nouveau_crtc *nv_crtc, bool update) +{ + struct drm_nouveau_private *dev_priv = nv_crtc->base.dev->dev_private; + struct drm_device *dev = nv_crtc->base.dev; + struct nouveau_connector *nv_connector; + struct drm_connector *connector; + u32 *push, mode = 0x00; + u32 mthd; + + nv_connector = nouveau_crtc_connector_get(nv_crtc); + connector = &nv_connector->base; + if (nv_connector->dithering_mode == DITHERING_MODE_AUTO) { + if (nv_crtc->base.fb->depth > connector->display_info.bpc * 3) + mode = DITHERING_MODE_DYNAMIC2X2; + } else { + mode = nv_connector->dithering_mode; + } + + if (nv_connector->dithering_depth == DITHERING_DEPTH_AUTO) { + if (connector->display_info.bpc >= 8) + mode |= DITHERING_DEPTH_8BPC; + } else { + mode |= nv_connector->dithering_depth; + } + + if (dev_priv->card_type < NV_E0) + mthd = 0x0490 + (nv_crtc->index * 0x0300); + else + mthd = 0x04a0 + (nv_crtc->index * 0x0300); + + push = evo_wait(dev, EVO_MASTER, 4); + if (push) { + evo_mthd(push, mthd, 1); + evo_data(push, mode); + if (update) { + evo_mthd(push, 0x0080, 1); + evo_data(push, 0x00000000); + } + evo_kick(push, dev, EVO_MASTER); + } + + return 0; +} + +static int +nvd0_crtc_set_scale(struct nouveau_crtc *nv_crtc, bool update) +{ + struct drm_display_mode *omode, *umode = &nv_crtc->base.mode; + struct drm_device *dev = nv_crtc->base.dev; + struct drm_crtc *crtc = &nv_crtc->base; + struct nouveau_connector *nv_connector; + int mode = DRM_MODE_SCALE_NONE; + u32 oX, oY, *push; + + /* start off at the resolution we programmed the crtc for, this + * effectively handles NONE/FULL scaling + */ + nv_connector = nouveau_crtc_connector_get(nv_crtc); + if (nv_connector && nv_connector->native_mode) + mode = nv_connector->scaling_mode; + + if (mode != DRM_MODE_SCALE_NONE) + omode = nv_connector->native_mode; + else + omode = umode; + + oX = omode->hdisplay; + oY = omode->vdisplay; + if (omode->flags & DRM_MODE_FLAG_DBLSCAN) + oY *= 2; + + /* add overscan compensation if necessary, will keep the aspect + * ratio the same as the backend mode unless overridden by the + * user setting both hborder and vborder properties. + */ + if (nv_connector && ( nv_connector->underscan == UNDERSCAN_ON || + (nv_connector->underscan == UNDERSCAN_AUTO && + nv_connector->edid && + drm_detect_hdmi_monitor(nv_connector->edid)))) { + u32 bX = nv_connector->underscan_hborder; + u32 bY = nv_connector->underscan_vborder; + u32 aspect = (oY << 19) / oX; + + if (bX) { + oX -= (bX * 2); + if (bY) oY -= (bY * 2); + else oY = ((oX * aspect) + (aspect / 2)) >> 19; + } else { + oX -= (oX >> 4) + 32; + if (bY) oY -= (bY * 2); + else oY = ((oX * aspect) + (aspect / 2)) >> 19; + } + } + + /* handle CENTER/ASPECT scaling, taking into account the areas + * removed already for overscan compensation + */ + switch (mode) { + case DRM_MODE_SCALE_CENTER: + oX = min((u32)umode->hdisplay, oX); + oY = min((u32)umode->vdisplay, oY); + /* fall-through */ + case DRM_MODE_SCALE_ASPECT: + if (oY < oX) { + u32 aspect = (umode->hdisplay << 19) / umode->vdisplay; + oX = ((oY * aspect) + (aspect / 2)) >> 19; + } else { + u32 aspect = (umode->vdisplay << 19) / umode->hdisplay; + oY = ((oX * aspect) + (aspect / 2)) >> 19; + } + break; + default: + break; + } + + push = evo_wait(dev, EVO_MASTER, 8); + if (push) { + evo_mthd(push, 0x04c0 + (nv_crtc->index * 0x300), 3); + evo_data(push, (oY << 16) | oX); + evo_data(push, (oY << 16) | oX); + evo_data(push, (oY << 16) | oX); + evo_mthd(push, 0x0494 + (nv_crtc->index * 0x300), 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x04b8 + (nv_crtc->index * 0x300), 1); + evo_data(push, (umode->vdisplay << 16) | umode->hdisplay); + evo_kick(push, dev, EVO_MASTER); + if (update) { + nvd0_display_flip_stop(crtc); + nvd0_display_flip_next(crtc, crtc->fb, NULL, 1); + } + } + + return 0; +} + +static int +nvd0_crtc_set_image(struct nouveau_crtc *nv_crtc, struct drm_framebuffer *fb, + int x, int y, bool update) +{ + struct nouveau_framebuffer *nvfb = nouveau_framebuffer(fb); + u32 *push; + + push = evo_wait(fb->dev, EVO_MASTER, 16); + if (push) { + evo_mthd(push, 0x0460 + (nv_crtc->index * 0x300), 1); + evo_data(push, nvfb->nvbo->bo.offset >> 8); + evo_mthd(push, 0x0468 + (nv_crtc->index * 0x300), 4); + evo_data(push, (fb->height << 16) | fb->width); + evo_data(push, nvfb->r_pitch); + evo_data(push, nvfb->r_format); + evo_data(push, nvfb->r_dma); + evo_mthd(push, 0x04b0 + (nv_crtc->index * 0x300), 1); + evo_data(push, (y << 16) | x); + if (update) { + evo_mthd(push, 0x0080, 1); + evo_data(push, 0x00000000); + } + evo_kick(push, fb->dev, EVO_MASTER); + } + + nv_crtc->fb.tile_flags = nvfb->r_dma; + return 0; +} + +static void +nvd0_crtc_cursor_show(struct nouveau_crtc *nv_crtc, bool show, bool update) +{ + struct drm_device *dev = nv_crtc->base.dev; + u32 *push = evo_wait(dev, EVO_MASTER, 16); + if (push) { + if (show) { + evo_mthd(push, 0x0480 + (nv_crtc->index * 0x300), 2); + evo_data(push, 0x85000000); + evo_data(push, nv_crtc->cursor.nvbo->bo.offset >> 8); + evo_mthd(push, 0x048c + (nv_crtc->index * 0x300), 1); + evo_data(push, NvEvoVRAM); + } else { + evo_mthd(push, 0x0480 + (nv_crtc->index * 0x300), 1); + evo_data(push, 0x05000000); + evo_mthd(push, 0x048c + (nv_crtc->index * 0x300), 1); + evo_data(push, 0x00000000); + } + + if (update) { + evo_mthd(push, 0x0080, 1); + evo_data(push, 0x00000000); + } + + evo_kick(push, dev, EVO_MASTER); + } +} + +static void +nvd0_crtc_dpms(struct drm_crtc *crtc, int mode) +{ +} + +static void +nvd0_crtc_prepare(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + u32 *push; + + nvd0_display_flip_stop(crtc); + + push = evo_wait(crtc->dev, EVO_MASTER, 2); + if (push) { + evo_mthd(push, 0x0474 + (nv_crtc->index * 0x300), 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x0440 + (nv_crtc->index * 0x300), 1); + evo_data(push, 0x03000000); + evo_mthd(push, 0x045c + (nv_crtc->index * 0x300), 1); + evo_data(push, 0x00000000); + evo_kick(push, crtc->dev, EVO_MASTER); + } + + nvd0_crtc_cursor_show(nv_crtc, false, false); +} + +static void +nvd0_crtc_commit(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + u32 *push; + + push = evo_wait(crtc->dev, EVO_MASTER, 32); + if (push) { + evo_mthd(push, 0x0474 + (nv_crtc->index * 0x300), 1); + evo_data(push, nv_crtc->fb.tile_flags); + evo_mthd(push, 0x0440 + (nv_crtc->index * 0x300), 4); + evo_data(push, 0x83000000); + evo_data(push, nv_crtc->lut.nvbo->bo.offset >> 8); + evo_data(push, 0x00000000); + evo_data(push, 0x00000000); + evo_mthd(push, 0x045c + (nv_crtc->index * 0x300), 1); + evo_data(push, NvEvoVRAM); + evo_mthd(push, 0x0430 + (nv_crtc->index * 0x300), 1); + evo_data(push, 0xffffff00); + evo_kick(push, crtc->dev, EVO_MASTER); + } + + nvd0_crtc_cursor_show(nv_crtc, nv_crtc->cursor.visible, true); + nvd0_display_flip_next(crtc, crtc->fb, NULL, 1); +} + +static bool +nvd0_crtc_mode_fixup(struct drm_crtc *crtc, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int +nvd0_crtc_swap_fbs(struct drm_crtc *crtc, struct drm_framebuffer *old_fb) +{ + struct nouveau_framebuffer *nvfb = nouveau_framebuffer(crtc->fb); + int ret; + + ret = nouveau_bo_pin(nvfb->nvbo, TTM_PL_FLAG_VRAM); + if (ret) + return ret; + + if (old_fb) { + nvfb = nouveau_framebuffer(old_fb); + nouveau_bo_unpin(nvfb->nvbo); + } + + return 0; +} + +static int +nvd0_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *umode, + struct drm_display_mode *mode, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct nouveau_connector *nv_connector; + u32 ilace = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 2 : 1; + u32 vscan = (mode->flags & DRM_MODE_FLAG_DBLSCAN) ? 2 : 1; + u32 hactive, hsynce, hbackp, hfrontp, hblanke, hblanks; + u32 vactive, vsynce, vbackp, vfrontp, vblanke, vblanks; + u32 vblan2e = 0, vblan2s = 1; + u32 *push; + int ret; + + hactive = mode->htotal; + hsynce = mode->hsync_end - mode->hsync_start - 1; + hbackp = mode->htotal - mode->hsync_end; + hblanke = hsynce + hbackp; + hfrontp = mode->hsync_start - mode->hdisplay; + hblanks = mode->htotal - hfrontp - 1; + + vactive = mode->vtotal * vscan / ilace; + vsynce = ((mode->vsync_end - mode->vsync_start) * vscan / ilace) - 1; + vbackp = (mode->vtotal - mode->vsync_end) * vscan / ilace; + vblanke = vsynce + vbackp; + vfrontp = (mode->vsync_start - mode->vdisplay) * vscan / ilace; + vblanks = vactive - vfrontp - 1; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + vblan2e = vactive + vsynce + vbackp; + vblan2s = vblan2e + (mode->vdisplay * vscan / ilace); + vactive = (vactive * 2) + 1; + } + + ret = nvd0_crtc_swap_fbs(crtc, old_fb); + if (ret) + return ret; + + push = evo_wait(crtc->dev, EVO_MASTER, 64); + if (push) { + evo_mthd(push, 0x0410 + (nv_crtc->index * 0x300), 6); + evo_data(push, 0x00000000); + evo_data(push, (vactive << 16) | hactive); + evo_data(push, ( vsynce << 16) | hsynce); + evo_data(push, (vblanke << 16) | hblanke); + evo_data(push, (vblanks << 16) | hblanks); + evo_data(push, (vblan2e << 16) | vblan2s); + evo_mthd(push, 0x042c + (nv_crtc->index * 0x300), 1); + evo_data(push, 0x00000000); /* ??? */ + evo_mthd(push, 0x0450 + (nv_crtc->index * 0x300), 3); + evo_data(push, mode->clock * 1000); + evo_data(push, 0x00200000); /* ??? */ + evo_data(push, mode->clock * 1000); + evo_mthd(push, 0x04d0 + (nv_crtc->index * 0x300), 2); + evo_data(push, 0x00000311); + evo_data(push, 0x00000100); + evo_kick(push, crtc->dev, EVO_MASTER); + } + + nv_connector = nouveau_crtc_connector_get(nv_crtc); + nvd0_crtc_set_dither(nv_crtc, false); + nvd0_crtc_set_scale(nv_crtc, false); + nvd0_crtc_set_image(nv_crtc, crtc->fb, x, y, false); + return 0; +} + +static int +nvd0_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + int ret; + + if (!crtc->fb) { + NV_DEBUG_KMS(crtc->dev, "No FB bound\n"); + return 0; + } + + ret = nvd0_crtc_swap_fbs(crtc, old_fb); + if (ret) + return ret; + + nvd0_display_flip_stop(crtc); + nvd0_crtc_set_image(nv_crtc, crtc->fb, x, y, true); + nvd0_display_flip_next(crtc, crtc->fb, NULL, 1); + return 0; +} + +static int +nvd0_crtc_mode_set_base_atomic(struct drm_crtc *crtc, + struct drm_framebuffer *fb, int x, int y, + enum mode_set_atomic state) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + nvd0_display_flip_stop(crtc); + nvd0_crtc_set_image(nv_crtc, fb, x, y, true); + return 0; +} + +static void +nvd0_crtc_lut_load(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + void __iomem *lut = nvbo_kmap_obj_iovirtual(nv_crtc->lut.nvbo); + int i; + + for (i = 0; i < 256; i++) { + writew(0x6000 + (nv_crtc->lut.r[i] >> 2), lut + (i * 0x20) + 0); + writew(0x6000 + (nv_crtc->lut.g[i] >> 2), lut + (i * 0x20) + 2); + writew(0x6000 + (nv_crtc->lut.b[i] >> 2), lut + (i * 0x20) + 4); + } +} + +static int +nvd0_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv, + uint32_t handle, uint32_t width, uint32_t height) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct drm_gem_object *gem; + struct nouveau_bo *nvbo; + bool visible = (handle != 0); + int i, ret = 0; + + if (visible) { + if (width != 64 || height != 64) + return -EINVAL; + + gem = drm_gem_object_lookup(dev, file_priv, handle); + if (unlikely(!gem)) + return -ENOENT; + nvbo = nouveau_gem_object(gem); + + ret = nouveau_bo_map(nvbo); + if (ret == 0) { + for (i = 0; i < 64 * 64; i++) { + u32 v = nouveau_bo_rd32(nvbo, i); + nouveau_bo_wr32(nv_crtc->cursor.nvbo, i, v); + } + nouveau_bo_unmap(nvbo); + } + + drm_gem_object_unreference_unlocked(gem); + } + + if (visible != nv_crtc->cursor.visible) { + nvd0_crtc_cursor_show(nv_crtc, visible, true); + nv_crtc->cursor.visible = visible; + } + + return ret; +} + +static int +nvd0_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + int ch = EVO_CURS(nv_crtc->index); + + evo_piow(crtc->dev, ch, 0x0084, (y << 16) | x); + evo_piow(crtc->dev, ch, 0x0080, 0x00000000); + return 0; +} + +static void +nvd0_crtc_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, + uint32_t start, uint32_t size) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + u32 end = max(start + size, (u32)256); + u32 i; + + for (i = start; i < end; i++) { + nv_crtc->lut.r[i] = r[i]; + nv_crtc->lut.g[i] = g[i]; + nv_crtc->lut.b[i] = b[i]; + } + + nvd0_crtc_lut_load(crtc); +} + +static void +nvd0_crtc_destroy(struct drm_crtc *crtc) +{ + struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); + nouveau_bo_unmap(nv_crtc->cursor.nvbo); + nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo); + nouveau_bo_unmap(nv_crtc->lut.nvbo); + nouveau_bo_ref(NULL, &nv_crtc->lut.nvbo); + drm_crtc_cleanup(crtc); + kfree(crtc); +} + +static const struct drm_crtc_helper_funcs nvd0_crtc_hfunc = { + .dpms = nvd0_crtc_dpms, + .prepare = nvd0_crtc_prepare, + .commit = nvd0_crtc_commit, + .mode_fixup = nvd0_crtc_mode_fixup, + .mode_set = nvd0_crtc_mode_set, + .mode_set_base = nvd0_crtc_mode_set_base, + .mode_set_base_atomic = nvd0_crtc_mode_set_base_atomic, + .load_lut = nvd0_crtc_lut_load, +}; + +static const struct drm_crtc_funcs nvd0_crtc_func = { + .cursor_set = nvd0_crtc_cursor_set, + .cursor_move = nvd0_crtc_cursor_move, + .gamma_set = nvd0_crtc_gamma_set, + .set_config = drm_crtc_helper_set_config, + .destroy = nvd0_crtc_destroy, + .page_flip = nouveau_crtc_page_flip, +}; + +static void +nvd0_cursor_set_pos(struct nouveau_crtc *nv_crtc, int x, int y) +{ +} + +static void +nvd0_cursor_set_offset(struct nouveau_crtc *nv_crtc, uint32_t offset) +{ +} + +static int +nvd0_crtc_create(struct drm_device *dev, int index) +{ + struct nouveau_crtc *nv_crtc; + struct drm_crtc *crtc; + int ret, i; + + nv_crtc = kzalloc(sizeof(*nv_crtc), GFP_KERNEL); + if (!nv_crtc) + return -ENOMEM; + + nv_crtc->index = index; + nv_crtc->set_dither = nvd0_crtc_set_dither; + nv_crtc->set_scale = nvd0_crtc_set_scale; + nv_crtc->cursor.set_offset = nvd0_cursor_set_offset; + nv_crtc->cursor.set_pos = nvd0_cursor_set_pos; + for (i = 0; i < 256; i++) { + nv_crtc->lut.r[i] = i << 8; + nv_crtc->lut.g[i] = i << 8; + nv_crtc->lut.b[i] = i << 8; + } + + crtc = &nv_crtc->base; + drm_crtc_init(dev, crtc, &nvd0_crtc_func); + drm_crtc_helper_add(crtc, &nvd0_crtc_hfunc); + drm_mode_crtc_set_gamma_size(crtc, 256); + + ret = nouveau_bo_new(dev, 64 * 64 * 4, 0x100, TTM_PL_FLAG_VRAM, + 0, 0x0000, &nv_crtc->cursor.nvbo); + if (!ret) { + ret = nouveau_bo_pin(nv_crtc->cursor.nvbo, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(nv_crtc->cursor.nvbo); + if (ret) + nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo); + } + + if (ret) + goto out; + + ret = nouveau_bo_new(dev, 8192, 0x100, TTM_PL_FLAG_VRAM, + 0, 0x0000, &nv_crtc->lut.nvbo); + if (!ret) { + ret = nouveau_bo_pin(nv_crtc->lut.nvbo, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(nv_crtc->lut.nvbo); + if (ret) + nouveau_bo_ref(NULL, &nv_crtc->lut.nvbo); + } + + if (ret) + goto out; + + nvd0_crtc_lut_load(crtc); + +out: + if (ret) + nvd0_crtc_destroy(crtc); + return ret; +} + +/****************************************************************************** + * DAC + *****************************************************************************/ +static void +nvd0_dac_dpms(struct drm_encoder *encoder, int mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + int or = nv_encoder->or; + u32 dpms_ctrl; + + dpms_ctrl = 0x80000000; + if (mode == DRM_MODE_DPMS_STANDBY || mode == DRM_MODE_DPMS_OFF) + dpms_ctrl |= 0x00000001; + if (mode == DRM_MODE_DPMS_SUSPEND || mode == DRM_MODE_DPMS_OFF) + dpms_ctrl |= 0x00000004; + + nv_wait(dev, 0x61a004 + (or * 0x0800), 0x80000000, 0x00000000); + nv_mask(dev, 0x61a004 + (or * 0x0800), 0xc000007f, dpms_ctrl); + nv_wait(dev, 0x61a004 + (or * 0x0800), 0x80000000, 0x00000000); +} + +static bool +nvd0_dac_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (nv_connector && nv_connector->native_mode) { + if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) { + int id = adjusted_mode->base.id; + *adjusted_mode = *nv_connector->native_mode; + adjusted_mode->base.id = id; + } + } + + return true; +} + +static void +nvd0_dac_commit(struct drm_encoder *encoder) +{ +} + +static void +nvd0_dac_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + u32 syncs, magic, *push; + + syncs = 0x00000001; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + syncs |= 0x00000008; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + syncs |= 0x00000010; + + magic = 0x31ec6000 | (nv_crtc->index << 25); + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + magic |= 0x00000001; + + nvd0_dac_dpms(encoder, DRM_MODE_DPMS_ON); + + push = evo_wait(encoder->dev, EVO_MASTER, 8); + if (push) { + evo_mthd(push, 0x0404 + (nv_crtc->index * 0x300), 2); + evo_data(push, syncs); + evo_data(push, magic); + evo_mthd(push, 0x0180 + (nv_encoder->or * 0x020), 2); + evo_data(push, 1 << nv_crtc->index); + evo_data(push, 0x00ff); + evo_kick(push, encoder->dev, EVO_MASTER); + } + + nv_encoder->crtc = encoder->crtc; +} + +static void +nvd0_dac_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + u32 *push; + + if (nv_encoder->crtc) { + nvd0_crtc_prepare(nv_encoder->crtc); + + push = evo_wait(dev, EVO_MASTER, 4); + if (push) { + evo_mthd(push, 0x0180 + (nv_encoder->or * 0x20), 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x0080, 1); + evo_data(push, 0x00000000); + evo_kick(push, dev, EVO_MASTER); + } + + nv_encoder->crtc = NULL; + } +} + +static enum drm_connector_status +nvd0_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector) +{ + enum drm_connector_status status = connector_status_disconnected; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + int or = nv_encoder->or; + u32 load; + + nv_wr32(dev, 0x61a00c + (or * 0x800), 0x00100000); + udelay(9500); + nv_wr32(dev, 0x61a00c + (or * 0x800), 0x80000000); + + load = nv_rd32(dev, 0x61a00c + (or * 0x800)); + if ((load & 0x38000000) == 0x38000000) + status = connector_status_connected; + + nv_wr32(dev, 0x61a00c + (or * 0x800), 0x00000000); + return status; +} + +static void +nvd0_dac_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_helper_funcs nvd0_dac_hfunc = { + .dpms = nvd0_dac_dpms, + .mode_fixup = nvd0_dac_mode_fixup, + .prepare = nvd0_dac_disconnect, + .commit = nvd0_dac_commit, + .mode_set = nvd0_dac_mode_set, + .disable = nvd0_dac_disconnect, + .get_crtc = nvd0_display_crtc_get, + .detect = nvd0_dac_detect +}; + +static const struct drm_encoder_funcs nvd0_dac_func = { + .destroy = nvd0_dac_destroy, +}; + +static int +nvd0_dac_create(struct drm_connector *connector, struct dcb_entry *dcbe) +{ + struct drm_device *dev = connector->dev; + struct nouveau_encoder *nv_encoder; + struct drm_encoder *encoder; + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + nv_encoder->dcb = dcbe; + nv_encoder->or = ffs(dcbe->or) - 1; + + encoder = to_drm_encoder(nv_encoder); + encoder->possible_crtcs = dcbe->heads; + encoder->possible_clones = 0; + drm_encoder_init(dev, encoder, &nvd0_dac_func, DRM_MODE_ENCODER_DAC); + drm_encoder_helper_add(encoder, &nvd0_dac_hfunc); + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} + +/****************************************************************************** + * Audio + *****************************************************************************/ +static void +nvd0_audio_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector; + struct drm_device *dev = encoder->dev; + int i, or = nv_encoder->or * 0x30; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (!drm_detect_monitor_audio(nv_connector->edid)) + return; + + nv_mask(dev, 0x10ec10 + or, 0x80000003, 0x80000001); + + drm_edid_to_eld(&nv_connector->base, nv_connector->edid); + if (nv_connector->base.eld[0]) { + u8 *eld = nv_connector->base.eld; + + for (i = 0; i < eld[2] * 4; i++) + nv_wr32(dev, 0x10ec00 + or, (i << 8) | eld[i]); + for (i = eld[2] * 4; i < 0x60; i++) + nv_wr32(dev, 0x10ec00 + or, (i << 8) | 0x00); + + nv_mask(dev, 0x10ec10 + or, 0x80000002, 0x80000002); + } +} + +static void +nvd0_audio_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + int or = nv_encoder->or * 0x30; + + nv_mask(dev, 0x10ec10 + or, 0x80000003, 0x80000000); +} + +/****************************************************************************** + * HDMI + *****************************************************************************/ +static void +nvd0_hdmi_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nouveau_connector *nv_connector; + struct drm_device *dev = encoder->dev; + int head = nv_crtc->index * 0x800; + u32 rekey = 56; /* binary driver, and tegra constant */ + u32 max_ac_packet; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (!drm_detect_hdmi_monitor(nv_connector->edid)) + return; + + max_ac_packet = mode->htotal - mode->hdisplay; + max_ac_packet -= rekey; + max_ac_packet -= 18; /* constant from tegra */ + max_ac_packet /= 32; + + /* AVI InfoFrame */ + nv_mask(dev, 0x616714 + head, 0x00000001, 0x00000000); + nv_wr32(dev, 0x61671c + head, 0x000d0282); + nv_wr32(dev, 0x616720 + head, 0x0000006f); + nv_wr32(dev, 0x616724 + head, 0x00000000); + nv_wr32(dev, 0x616728 + head, 0x00000000); + nv_wr32(dev, 0x61672c + head, 0x00000000); + nv_mask(dev, 0x616714 + head, 0x00000001, 0x00000001); + + /* ??? InfoFrame? */ + nv_mask(dev, 0x6167a4 + head, 0x00000001, 0x00000000); + nv_wr32(dev, 0x6167ac + head, 0x00000010); + nv_mask(dev, 0x6167a4 + head, 0x00000001, 0x00000001); + + /* HDMI_CTRL */ + nv_mask(dev, 0x616798 + head, 0x401f007f, 0x40000000 | rekey | + max_ac_packet << 16); + + /* NFI, audio doesn't work without it though.. */ + nv_mask(dev, 0x616548 + head, 0x00000070, 0x00000000); + + nvd0_audio_mode_set(encoder, mode); +} + +static void +nvd0_hdmi_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(nv_encoder->crtc); + struct drm_device *dev = encoder->dev; + int head = nv_crtc->index * 0x800; + + nvd0_audio_disconnect(encoder); + + nv_mask(dev, 0x616798 + head, 0x40000000, 0x00000000); + nv_mask(dev, 0x6167a4 + head, 0x00000001, 0x00000000); + nv_mask(dev, 0x616714 + head, 0x00000001, 0x00000000); +} + +/****************************************************************************** + * SOR + *****************************************************************************/ +static inline u32 +nvd0_sor_dp_lane_map(struct drm_device *dev, struct dcb_entry *dcb, u8 lane) +{ + static const u8 nvd0[] = { 16, 8, 0, 24 }; + return nvd0[lane]; +} + +static void +nvd0_sor_dp_train_set(struct drm_device *dev, struct dcb_entry *dcb, u8 pattern) +{ + const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); + const u32 loff = (or * 0x800) + (link * 0x80); + nv_mask(dev, 0x61c110 + loff, 0x0f0f0f0f, 0x01010101 * pattern); +} + +static void +nvd0_sor_dp_train_adj(struct drm_device *dev, struct dcb_entry *dcb, + u8 lane, u8 swing, u8 preem) +{ + const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); + const u32 loff = (or * 0x800) + (link * 0x80); + u32 shift = nvd0_sor_dp_lane_map(dev, dcb, lane); + u32 mask = 0x000000ff << shift; + u8 *table, *entry, *config = NULL; + + switch (swing) { + case 0: preem += 0; break; + case 1: preem += 4; break; + case 2: preem += 7; break; + case 3: preem += 9; break; + } + + table = nouveau_dp_bios_data(dev, dcb, &entry); + if (table) { + if (table[0] == 0x30) { + config = entry + table[4]; + config += table[5] * preem; + } else + if (table[0] == 0x40) { + config = table + table[1]; + config += table[2] * table[3]; + config += table[6] * preem; + } + } + + if (!config) { + NV_ERROR(dev, "PDISP: unsupported DP table for chipset\n"); + return; + } + + nv_mask(dev, 0x61c118 + loff, mask, config[1] << shift); + nv_mask(dev, 0x61c120 + loff, mask, config[2] << shift); + nv_mask(dev, 0x61c130 + loff, 0x0000ff00, config[3] << 8); + nv_mask(dev, 0x61c13c + loff, 0x00000000, 0x00000000); +} + +static void +nvd0_sor_dp_link_set(struct drm_device *dev, struct dcb_entry *dcb, int crtc, + int link_nr, u32 link_bw, bool enhframe) +{ + const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); + const u32 loff = (or * 0x800) + (link * 0x80); + const u32 soff = (or * 0x800); + u32 dpctrl = nv_rd32(dev, 0x61c10c + loff) & ~0x001f4000; + u32 clksor = nv_rd32(dev, 0x612300 + soff) & ~0x007c0000; + u32 script = 0x0000, lane_mask = 0; + u8 *table, *entry; + int i; + + link_bw /= 27000; + + table = nouveau_dp_bios_data(dev, dcb, &entry); + if (table) { + if (table[0] == 0x30) entry = ROMPTR(dev, entry[10]); + else if (table[0] == 0x40) entry = ROMPTR(dev, entry[9]); + else entry = NULL; + + while (entry) { + if (entry[0] >= link_bw) + break; + entry += 3; + } + + nouveau_bios_run_init_table(dev, script, dcb, crtc); + } + + clksor |= link_bw << 18; + dpctrl |= ((1 << link_nr) - 1) << 16; + if (enhframe) + dpctrl |= 0x00004000; + + for (i = 0; i < link_nr; i++) + lane_mask |= 1 << (nvd0_sor_dp_lane_map(dev, dcb, i) >> 3); + + nv_wr32(dev, 0x612300 + soff, clksor); + nv_wr32(dev, 0x61c10c + loff, dpctrl); + nv_mask(dev, 0x61c130 + loff, 0x0000000f, lane_mask); +} + +static void +nvd0_sor_dp_link_get(struct drm_device *dev, struct dcb_entry *dcb, + u32 *link_nr, u32 *link_bw) +{ + const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1); + const u32 loff = (or * 0x800) + (link * 0x80); + const u32 soff = (or * 0x800); + u32 dpctrl = nv_rd32(dev, 0x61c10c + loff) & 0x000f0000; + u32 clksor = nv_rd32(dev, 0x612300 + soff); + + if (dpctrl > 0x00030000) *link_nr = 4; + else if (dpctrl > 0x00010000) *link_nr = 2; + else *link_nr = 1; + + *link_bw = (clksor & 0x007c0000) >> 18; + *link_bw *= 27000; +} + +static void +nvd0_sor_dp_calc_tu(struct drm_device *dev, struct dcb_entry *dcb, + u32 crtc, u32 datarate) +{ + const u32 symbol = 100000; + const u32 TU = 64; + u32 link_nr, link_bw; + u64 ratio, value; + + nvd0_sor_dp_link_get(dev, dcb, &link_nr, &link_bw); + + ratio = datarate; + ratio *= symbol; + do_div(ratio, link_nr * link_bw); + + value = (symbol - ratio) * TU; + value *= ratio; + do_div(value, symbol); + do_div(value, symbol); + + value += 5; + value |= 0x08000000; + + nv_wr32(dev, 0x616610 + (crtc * 0x800), value); +} + +static void +nvd0_sor_dpms(struct drm_encoder *encoder, int mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct drm_encoder *partner; + int or = nv_encoder->or; + u32 dpms_ctrl; + + nv_encoder->last_dpms = mode; + + list_for_each_entry(partner, &dev->mode_config.encoder_list, head) { + struct nouveau_encoder *nv_partner = nouveau_encoder(partner); + + if (partner->encoder_type != DRM_MODE_ENCODER_TMDS) + continue; + + if (nv_partner != nv_encoder && + nv_partner->dcb->or == nv_encoder->dcb->or) { + if (nv_partner->last_dpms == DRM_MODE_DPMS_ON) + return; + break; + } + } + + dpms_ctrl = (mode == DRM_MODE_DPMS_ON); + dpms_ctrl |= 0x80000000; + + nv_wait(dev, 0x61c004 + (or * 0x0800), 0x80000000, 0x00000000); + nv_mask(dev, 0x61c004 + (or * 0x0800), 0x80000001, dpms_ctrl); + nv_wait(dev, 0x61c004 + (or * 0x0800), 0x80000000, 0x00000000); + nv_wait(dev, 0x61c030 + (or * 0x0800), 0x10000000, 0x00000000); + + if (nv_encoder->dcb->type == OUTPUT_DP) { + struct dp_train_func func = { + .link_set = nvd0_sor_dp_link_set, + .train_set = nvd0_sor_dp_train_set, + .train_adj = nvd0_sor_dp_train_adj + }; + + nouveau_dp_dpms(encoder, mode, nv_encoder->dp.datarate, &func); + } +} + +static bool +nvd0_sor_mode_fixup(struct drm_encoder *encoder, struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_connector *nv_connector; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + if (nv_connector && nv_connector->native_mode) { + if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) { + int id = adjusted_mode->base.id; + *adjusted_mode = *nv_connector->native_mode; + adjusted_mode->base.id = id; + } + } + + return true; +} + +static void +nvd0_sor_disconnect(struct drm_encoder *encoder) +{ + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct drm_device *dev = encoder->dev; + u32 *push; + + if (nv_encoder->crtc) { + nvd0_crtc_prepare(nv_encoder->crtc); + + push = evo_wait(dev, EVO_MASTER, 4); + if (push) { + evo_mthd(push, 0x0200 + (nv_encoder->or * 0x20), 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x0080, 1); + evo_data(push, 0x00000000); + evo_kick(push, dev, EVO_MASTER); + } + + nvd0_hdmi_disconnect(encoder); + + nv_encoder->crtc = NULL; + nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; + } +} + +static void +nvd0_sor_prepare(struct drm_encoder *encoder) +{ + nvd0_sor_disconnect(encoder); + if (nouveau_encoder(encoder)->dcb->type == OUTPUT_DP) + evo_sync(encoder->dev, EVO_MASTER); +} + +static void +nvd0_sor_commit(struct drm_encoder *encoder) +{ +} + +static void +nvd0_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode, + struct drm_display_mode *mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); + struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); + struct nouveau_connector *nv_connector; + struct nvbios *bios = &dev_priv->vbios; + u32 mode_ctrl = (1 << nv_crtc->index); + u32 syncs, magic, *push; + u32 or_config; + + syncs = 0x00000001; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + syncs |= 0x00000008; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + syncs |= 0x00000010; + + magic = 0x31ec6000 | (nv_crtc->index << 25); + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + magic |= 0x00000001; + + nv_connector = nouveau_encoder_connector_get(nv_encoder); + switch (nv_encoder->dcb->type) { + case OUTPUT_TMDS: + if (nv_encoder->dcb->sorconf.link & 1) { + if (mode->clock < 165000) + mode_ctrl |= 0x00000100; + else + mode_ctrl |= 0x00000500; + } else { + mode_ctrl |= 0x00000200; + } + + or_config = (mode_ctrl & 0x00000f00) >> 8; + if (mode->clock >= 165000) + or_config |= 0x0100; + + nvd0_hdmi_mode_set(encoder, mode); + break; + case OUTPUT_LVDS: + or_config = (mode_ctrl & 0x00000f00) >> 8; + if (bios->fp_no_ddc) { + if (bios->fp.dual_link) + or_config |= 0x0100; + if (bios->fp.if_is_24bit) + or_config |= 0x0200; + } else { + if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) { + if (((u8 *)nv_connector->edid)[121] == 2) + or_config |= 0x0100; + } else + if (mode->clock >= bios->fp.duallink_transition_clk) { + or_config |= 0x0100; + } + + if (or_config & 0x0100) { + if (bios->fp.strapless_is_24bit & 2) + or_config |= 0x0200; + } else { + if (bios->fp.strapless_is_24bit & 1) + or_config |= 0x0200; + } + + if (nv_connector->base.display_info.bpc == 8) + or_config |= 0x0200; + + } + break; + case OUTPUT_DP: + if (nv_connector->base.display_info.bpc == 6) { + nv_encoder->dp.datarate = mode->clock * 18 / 8; + syncs |= 0x00000140; + } else { + nv_encoder->dp.datarate = mode->clock * 24 / 8; + syncs |= 0x00000180; + } + + if (nv_encoder->dcb->sorconf.link & 1) + mode_ctrl |= 0x00000800; + else + mode_ctrl |= 0x00000900; + + or_config = (mode_ctrl & 0x00000f00) >> 8; + break; + default: + BUG_ON(1); + break; + } + + nvd0_sor_dpms(encoder, DRM_MODE_DPMS_ON); + + if (nv_encoder->dcb->type == OUTPUT_DP) { + nvd0_sor_dp_calc_tu(dev, nv_encoder->dcb, nv_crtc->index, + nv_encoder->dp.datarate); + } + + push = evo_wait(dev, EVO_MASTER, 8); + if (push) { + evo_mthd(push, 0x0404 + (nv_crtc->index * 0x300), 2); + evo_data(push, syncs); + evo_data(push, magic); + evo_mthd(push, 0x0200 + (nv_encoder->or * 0x020), 2); + evo_data(push, mode_ctrl); + evo_data(push, or_config); + evo_kick(push, dev, EVO_MASTER); + } + + nv_encoder->crtc = encoder->crtc; +} + +static void +nvd0_sor_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_helper_funcs nvd0_sor_hfunc = { + .dpms = nvd0_sor_dpms, + .mode_fixup = nvd0_sor_mode_fixup, + .prepare = nvd0_sor_prepare, + .commit = nvd0_sor_commit, + .mode_set = nvd0_sor_mode_set, + .disable = nvd0_sor_disconnect, + .get_crtc = nvd0_display_crtc_get, +}; + +static const struct drm_encoder_funcs nvd0_sor_func = { + .destroy = nvd0_sor_destroy, +}; + +static int +nvd0_sor_create(struct drm_connector *connector, struct dcb_entry *dcbe) +{ + struct drm_device *dev = connector->dev; + struct nouveau_encoder *nv_encoder; + struct drm_encoder *encoder; + + nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL); + if (!nv_encoder) + return -ENOMEM; + nv_encoder->dcb = dcbe; + nv_encoder->or = ffs(dcbe->or) - 1; + nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; + + encoder = to_drm_encoder(nv_encoder); + encoder->possible_crtcs = dcbe->heads; + encoder->possible_clones = 0; + drm_encoder_init(dev, encoder, &nvd0_sor_func, DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(encoder, &nvd0_sor_hfunc); + + drm_mode_connector_attach_encoder(connector, encoder); + return 0; +} + +/****************************************************************************** + * IRQ + *****************************************************************************/ +static struct dcb_entry * +lookup_dcb(struct drm_device *dev, int id, u32 mc) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + int type, or, i, link = -1; + + if (id < 4) { + type = OUTPUT_ANALOG; + or = id; + } else { + switch (mc & 0x00000f00) { + case 0x00000000: link = 0; type = OUTPUT_LVDS; break; + case 0x00000100: link = 0; type = OUTPUT_TMDS; break; + case 0x00000200: link = 1; type = OUTPUT_TMDS; break; + case 0x00000500: link = 0; type = OUTPUT_TMDS; break; + case 0x00000800: link = 0; type = OUTPUT_DP; break; + case 0x00000900: link = 1; type = OUTPUT_DP; break; + default: + NV_ERROR(dev, "PDISP: unknown SOR mc 0x%08x\n", mc); + return NULL; + } + + or = id - 4; + } + + for (i = 0; i < dev_priv->vbios.dcb.entries; i++) { + struct dcb_entry *dcb = &dev_priv->vbios.dcb.entry[i]; + if (dcb->type == type && (dcb->or & (1 << or)) && + (link < 0 || link == !(dcb->sorconf.link & 1))) + return dcb; + } + + NV_ERROR(dev, "PDISP: DCB for %d/0x%08x not found\n", id, mc); + return NULL; +} + +static void +nvd0_display_unk1_handler(struct drm_device *dev, u32 crtc, u32 mask) +{ + struct dcb_entry *dcb; + int i; + + for (i = 0; mask && i < 8; i++) { + u32 mcc = nv_rd32(dev, 0x640180 + (i * 0x20)); + if (!(mcc & (1 << crtc))) + continue; + + dcb = lookup_dcb(dev, i, mcc); + if (!dcb) + continue; + + nouveau_bios_run_display_table(dev, 0x0000, -1, dcb, crtc); + } + + nv_wr32(dev, 0x6101d4, 0x00000000); + nv_wr32(dev, 0x6109d4, 0x00000000); + nv_wr32(dev, 0x6101d0, 0x80000000); +} + +static void +nvd0_display_unk2_handler(struct drm_device *dev, u32 crtc, u32 mask) +{ + struct dcb_entry *dcb; + u32 or, tmp, pclk; + int i; + + for (i = 0; mask && i < 8; i++) { + u32 mcc = nv_rd32(dev, 0x640180 + (i * 0x20)); + if (!(mcc & (1 << crtc))) + continue; + + dcb = lookup_dcb(dev, i, mcc); + if (!dcb) + continue; + + nouveau_bios_run_display_table(dev, 0x0000, -2, dcb, crtc); + } + + pclk = nv_rd32(dev, 0x660450 + (crtc * 0x300)) / 1000; + NV_DEBUG_KMS(dev, "PDISP: crtc %d pclk %d mask 0x%08x\n", + crtc, pclk, mask); + if (pclk && (mask & 0x00010000)) { + nv50_crtc_set_clock(dev, crtc, pclk); + } + + for (i = 0; mask && i < 8; i++) { + u32 mcp = nv_rd32(dev, 0x660180 + (i * 0x20)); + u32 cfg = nv_rd32(dev, 0x660184 + (i * 0x20)); + if (!(mcp & (1 << crtc))) + continue; + + dcb = lookup_dcb(dev, i, mcp); + if (!dcb) + continue; + or = ffs(dcb->or) - 1; + + nouveau_bios_run_display_table(dev, cfg, pclk, dcb, crtc); + + nv_wr32(dev, 0x612200 + (crtc * 0x800), 0x00000000); + switch (dcb->type) { + case OUTPUT_ANALOG: + nv_wr32(dev, 0x612280 + (or * 0x800), 0x00000000); + break; + case OUTPUT_TMDS: + case OUTPUT_LVDS: + case OUTPUT_DP: + if (cfg & 0x00000100) + tmp = 0x00000101; + else + tmp = 0x00000000; + + nv_mask(dev, 0x612300 + (or * 0x800), 0x00000707, tmp); + break; + default: + break; + } + + break; + } + + nv_wr32(dev, 0x6101d4, 0x00000000); + nv_wr32(dev, 0x6109d4, 0x00000000); + nv_wr32(dev, 0x6101d0, 0x80000000); +} + +static void +nvd0_display_unk4_handler(struct drm_device *dev, u32 crtc, u32 mask) +{ + struct dcb_entry *dcb; + int pclk, i; + + pclk = nv_rd32(dev, 0x660450 + (crtc * 0x300)) / 1000; + + for (i = 0; mask && i < 8; i++) { + u32 mcp = nv_rd32(dev, 0x660180 + (i * 0x20)); + u32 cfg = nv_rd32(dev, 0x660184 + (i * 0x20)); + if (!(mcp & (1 << crtc))) + continue; + + dcb = lookup_dcb(dev, i, mcp); + if (!dcb) + continue; + + nouveau_bios_run_display_table(dev, cfg, -pclk, dcb, crtc); + } + + nv_wr32(dev, 0x6101d4, 0x00000000); + nv_wr32(dev, 0x6109d4, 0x00000000); + nv_wr32(dev, 0x6101d0, 0x80000000); +} + +static void +nvd0_display_bh(unsigned long data) +{ + struct drm_device *dev = (struct drm_device *)data; + struct nvd0_display *disp = nvd0_display(dev); + u32 mask = 0, crtc = ~0; + int i; + + if (drm_debug & (DRM_UT_DRIVER | DRM_UT_KMS)) { + NV_INFO(dev, "PDISP: modeset req %d\n", disp->modeset); + NV_INFO(dev, " STAT: 0x%08x 0x%08x 0x%08x\n", + nv_rd32(dev, 0x6101d0), + nv_rd32(dev, 0x6101d4), nv_rd32(dev, 0x6109d4)); + for (i = 0; i < 8; i++) { + NV_INFO(dev, " %s%d: 0x%08x 0x%08x\n", + i < 4 ? "DAC" : "SOR", i, + nv_rd32(dev, 0x640180 + (i * 0x20)), + nv_rd32(dev, 0x660180 + (i * 0x20))); + } + } + + while (!mask && ++crtc < dev->mode_config.num_crtc) + mask = nv_rd32(dev, 0x6101d4 + (crtc * 0x800)); + + if (disp->modeset & 0x00000001) + nvd0_display_unk1_handler(dev, crtc, mask); + if (disp->modeset & 0x00000002) + nvd0_display_unk2_handler(dev, crtc, mask); + if (disp->modeset & 0x00000004) + nvd0_display_unk4_handler(dev, crtc, mask); +} + +static void +nvd0_display_intr(struct drm_device *dev) +{ + struct nvd0_display *disp = nvd0_display(dev); + u32 intr = nv_rd32(dev, 0x610088); + int i; + + if (intr & 0x00000001) { + u32 stat = nv_rd32(dev, 0x61008c); + nv_wr32(dev, 0x61008c, stat); + intr &= ~0x00000001; + } + + if (intr & 0x00000002) { + u32 stat = nv_rd32(dev, 0x61009c); + int chid = ffs(stat) - 1; + if (chid >= 0) { + u32 mthd = nv_rd32(dev, 0x6101f0 + (chid * 12)); + u32 data = nv_rd32(dev, 0x6101f4 + (chid * 12)); + u32 unkn = nv_rd32(dev, 0x6101f8 + (chid * 12)); + + NV_INFO(dev, "EvoCh: chid %d mthd 0x%04x data 0x%08x " + "0x%08x 0x%08x\n", + chid, (mthd & 0x0000ffc), data, mthd, unkn); + nv_wr32(dev, 0x61009c, (1 << chid)); + nv_wr32(dev, 0x6101f0 + (chid * 12), 0x90000000); + } + + intr &= ~0x00000002; + } + + if (intr & 0x00100000) { + u32 stat = nv_rd32(dev, 0x6100ac); + + if (stat & 0x00000007) { + disp->modeset = stat; + tasklet_schedule(&disp->tasklet); + + nv_wr32(dev, 0x6100ac, (stat & 0x00000007)); + stat &= ~0x00000007; + } + + if (stat) { + NV_INFO(dev, "PDISP: unknown intr24 0x%08x\n", stat); + nv_wr32(dev, 0x6100ac, stat); + } + + intr &= ~0x00100000; + } + + for (i = 0; i < dev->mode_config.num_crtc; i++) { + u32 mask = 0x01000000 << i; + if (intr & mask) { + u32 stat = nv_rd32(dev, 0x6100bc + (i * 0x800)); + nv_wr32(dev, 0x6100bc + (i * 0x800), stat); + intr &= ~mask; + } + } + + if (intr) + NV_INFO(dev, "PDISP: unknown intr 0x%08x\n", intr); +} + +/****************************************************************************** + * Init + *****************************************************************************/ +void +nvd0_display_fini(struct drm_device *dev) +{ + int i; + + /* fini cursors + overlays + flips */ + for (i = 1; i >= 0; i--) { + evo_fini_pio(dev, EVO_CURS(i)); + evo_fini_pio(dev, EVO_OIMM(i)); + evo_fini_dma(dev, EVO_OVLY(i)); + evo_fini_dma(dev, EVO_FLIP(i)); + } + + /* fini master */ + evo_fini_dma(dev, EVO_MASTER); +} + +int +nvd0_display_init(struct drm_device *dev) +{ + struct nvd0_display *disp = nvd0_display(dev); + int ret, i; + u32 *push; + + if (nv_rd32(dev, 0x6100ac) & 0x00000100) { + nv_wr32(dev, 0x6100ac, 0x00000100); + nv_mask(dev, 0x6194e8, 0x00000001, 0x00000000); + if (!nv_wait(dev, 0x6194e8, 0x00000002, 0x00000000)) { + NV_ERROR(dev, "PDISP: 0x6194e8 0x%08x\n", + nv_rd32(dev, 0x6194e8)); + return -EBUSY; + } + } + + /* nfi what these are exactly, i do know that SOR_MODE_CTRL won't + * work at all unless you do the SOR part below. + */ + for (i = 0; i < 3; i++) { + u32 dac = nv_rd32(dev, 0x61a000 + (i * 0x800)); + nv_wr32(dev, 0x6101c0 + (i * 0x800), dac); + } + + for (i = 0; i < 4; i++) { + u32 sor = nv_rd32(dev, 0x61c000 + (i * 0x800)); + nv_wr32(dev, 0x6301c4 + (i * 0x800), sor); + } + + for (i = 0; i < dev->mode_config.num_crtc; i++) { + u32 crtc0 = nv_rd32(dev, 0x616104 + (i * 0x800)); + u32 crtc1 = nv_rd32(dev, 0x616108 + (i * 0x800)); + u32 crtc2 = nv_rd32(dev, 0x61610c + (i * 0x800)); + nv_wr32(dev, 0x6101b4 + (i * 0x800), crtc0); + nv_wr32(dev, 0x6101b8 + (i * 0x800), crtc1); + nv_wr32(dev, 0x6101bc + (i * 0x800), crtc2); + } + + /* point at our hash table / objects, enable interrupts */ + nv_wr32(dev, 0x610010, (disp->mem->vinst >> 8) | 9); + nv_mask(dev, 0x6100b0, 0x00000307, 0x00000307); + + /* init master */ + ret = evo_init_dma(dev, EVO_MASTER); + if (ret) + goto error; + + /* init flips + overlays + cursors */ + for (i = 0; i < dev->mode_config.num_crtc; i++) { + if ((ret = evo_init_dma(dev, EVO_FLIP(i))) || + (ret = evo_init_dma(dev, EVO_OVLY(i))) || + (ret = evo_init_pio(dev, EVO_OIMM(i))) || + (ret = evo_init_pio(dev, EVO_CURS(i)))) + goto error; + } + + push = evo_wait(dev, EVO_MASTER, 32); + if (!push) { + ret = -EBUSY; + goto error; + } + evo_mthd(push, 0x0088, 1); + evo_data(push, NvEvoSync); + evo_mthd(push, 0x0084, 1); + evo_data(push, 0x00000000); + evo_mthd(push, 0x0084, 1); + evo_data(push, 0x80000000); + evo_mthd(push, 0x008c, 1); + evo_data(push, 0x00000000); + evo_kick(push, dev, EVO_MASTER); + +error: + if (ret) + nvd0_display_fini(dev); + return ret; +} + +void +nvd0_display_destroy(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nvd0_display *disp = nvd0_display(dev); + struct pci_dev *pdev = dev->pdev; + int i; + + for (i = 0; i < EVO_DMA_NR; i++) { + struct evo *evo = &disp->evo[i]; + pci_free_consistent(pdev, PAGE_SIZE, evo->ptr, evo->handle); + } + + nouveau_gpuobj_ref(NULL, &disp->mem); + nouveau_bo_unmap(disp->sync); + nouveau_bo_ref(NULL, &disp->sync); + nouveau_irq_unregister(dev, 26); + + dev_priv->engine.display.priv = NULL; + kfree(disp); +} + +int +nvd0_display_create(struct drm_device *dev) +{ + struct drm_nouveau_private *dev_priv = dev->dev_private; + struct nouveau_instmem_engine *pinstmem = &dev_priv->engine.instmem; + struct dcb_table *dcb = &dev_priv->vbios.dcb; + struct drm_connector *connector, *tmp; + struct pci_dev *pdev = dev->pdev; + struct nvd0_display *disp; + struct dcb_entry *dcbe; + int crtcs, ret, i; + + disp = kzalloc(sizeof(*disp), GFP_KERNEL); + if (!disp) + return -ENOMEM; + dev_priv->engine.display.priv = disp; + + /* create crtc objects to represent the hw heads */ + crtcs = nv_rd32(dev, 0x022448); + for (i = 0; i < crtcs; i++) { + ret = nvd0_crtc_create(dev, i); + if (ret) + goto out; + } + + /* create encoder/connector objects based on VBIOS DCB table */ + for (i = 0, dcbe = &dcb->entry[0]; i < dcb->entries; i++, dcbe++) { + connector = nouveau_connector_create(dev, dcbe->connector); + if (IS_ERR(connector)) + continue; + + if (dcbe->location != DCB_LOC_ON_CHIP) { + NV_WARN(dev, "skipping off-chip encoder %d/%d\n", + dcbe->type, ffs(dcbe->or) - 1); + continue; + } + + switch (dcbe->type) { + case OUTPUT_TMDS: + case OUTPUT_LVDS: + case OUTPUT_DP: + nvd0_sor_create(connector, dcbe); + break; + case OUTPUT_ANALOG: + nvd0_dac_create(connector, dcbe); + break; + default: + NV_WARN(dev, "skipping unsupported encoder %d/%d\n", + dcbe->type, ffs(dcbe->or) - 1); + continue; + } + } + + /* cull any connectors we created that don't have an encoder */ + list_for_each_entry_safe(connector, tmp, &dev->mode_config.connector_list, head) { + if (connector->encoder_ids[0]) + continue; + + NV_WARN(dev, "%s has no encoders, removing\n", + drm_get_connector_name(connector)); + connector->funcs->destroy(connector); + } + + /* setup interrupt handling */ + tasklet_init(&disp->tasklet, nvd0_display_bh, (unsigned long)dev); + nouveau_irq_register(dev, 26, nvd0_display_intr); + + /* small shared memory area we use for notifiers and semaphores */ + ret = nouveau_bo_new(dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, + 0, 0x0000, &disp->sync); + if (!ret) { + ret = nouveau_bo_pin(disp->sync, TTM_PL_FLAG_VRAM); + if (!ret) + ret = nouveau_bo_map(disp->sync); + if (ret) + nouveau_bo_ref(NULL, &disp->sync); + } + + if (ret) + goto out; + + /* hash table and dma objects for the memory areas we care about */ + ret = nouveau_gpuobj_new(dev, NULL, 0x4000, 0x10000, + NVOBJ_FLAG_ZERO_ALLOC, &disp->mem); + if (ret) + goto out; + + /* create evo dma channels */ + for (i = 0; i < EVO_DMA_NR; i++) { + struct evo *evo = &disp->evo[i]; + u64 offset = disp->sync->bo.offset; + u32 dmao = 0x1000 + (i * 0x100); + u32 hash = 0x0000 + (i * 0x040); + + evo->idx = i; + evo->sem.offset = EVO_SYNC(evo->idx, 0x00); + evo->ptr = pci_alloc_consistent(pdev, PAGE_SIZE, &evo->handle); + if (!evo->ptr) { + ret = -ENOMEM; + goto out; + } + + nv_wo32(disp->mem, dmao + 0x00, 0x00000049); + nv_wo32(disp->mem, dmao + 0x04, (offset + 0x0000) >> 8); + nv_wo32(disp->mem, dmao + 0x08, (offset + 0x0fff) >> 8); + nv_wo32(disp->mem, dmao + 0x0c, 0x00000000); + nv_wo32(disp->mem, dmao + 0x10, 0x00000000); + nv_wo32(disp->mem, dmao + 0x14, 0x00000000); + nv_wo32(disp->mem, hash + 0x00, NvEvoSync); + nv_wo32(disp->mem, hash + 0x04, 0x00000001 | (i << 27) | + ((dmao + 0x00) << 9)); + + nv_wo32(disp->mem, dmao + 0x20, 0x00000049); + nv_wo32(disp->mem, dmao + 0x24, 0x00000000); + nv_wo32(disp->mem, dmao + 0x28, (dev_priv->vram_size - 1) >> 8); + nv_wo32(disp->mem, dmao + 0x2c, 0x00000000); + nv_wo32(disp->mem, dmao + 0x30, 0x00000000); + nv_wo32(disp->mem, dmao + 0x34, 0x00000000); + nv_wo32(disp->mem, hash + 0x08, NvEvoVRAM); + nv_wo32(disp->mem, hash + 0x0c, 0x00000001 | (i << 27) | + ((dmao + 0x20) << 9)); + + nv_wo32(disp->mem, dmao + 0x40, 0x00000009); + nv_wo32(disp->mem, dmao + 0x44, 0x00000000); + nv_wo32(disp->mem, dmao + 0x48, (dev_priv->vram_size - 1) >> 8); + nv_wo32(disp->mem, dmao + 0x4c, 0x00000000); + nv_wo32(disp->mem, dmao + 0x50, 0x00000000); + nv_wo32(disp->mem, dmao + 0x54, 0x00000000); + nv_wo32(disp->mem, hash + 0x10, NvEvoVRAM_LP); + nv_wo32(disp->mem, hash + 0x14, 0x00000001 | (i << 27) | + ((dmao + 0x40) << 9)); + + nv_wo32(disp->mem, dmao + 0x60, 0x0fe00009); + nv_wo32(disp->mem, dmao + 0x64, 0x00000000); + nv_wo32(disp->mem, dmao + 0x68, (dev_priv->vram_size - 1) >> 8); + nv_wo32(disp->mem, dmao + 0x6c, 0x00000000); + nv_wo32(disp->mem, dmao + 0x70, 0x00000000); + nv_wo32(disp->mem, dmao + 0x74, 0x00000000); + nv_wo32(disp->mem, hash + 0x18, NvEvoFB32); + nv_wo32(disp->mem, hash + 0x1c, 0x00000001 | (i << 27) | + ((dmao + 0x60) << 9)); + } + + pinstmem->flush(dev); + +out: + if (ret) + nvd0_display_destroy(dev); + return ret; +} diff --git a/drivers/gpu/drm/nouveau/nvreg.h b/drivers/gpu/drm/nouveau/nvreg.h new file mode 100644 index 00000000..bbfb1a68 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvreg.h @@ -0,0 +1,517 @@ +/* $XConsortium: nvreg.h /main/2 1996/10/28 05:13:41 kaleb $ */ +/* + * Copyright 1996-1997 David J. McKay + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * DAVID J. MCKAY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/nvreg.h,v 1.6 2002/01/25 21:56:06 tsi Exp $ */ + +#ifndef __NVREG_H_ +#define __NVREG_H_ + +#define NV_PMC_OFFSET 0x00000000 +#define NV_PMC_SIZE 0x00001000 + +#define NV_PBUS_OFFSET 0x00001000 +#define NV_PBUS_SIZE 0x00001000 + +#define NV_PFIFO_OFFSET 0x00002000 +#define NV_PFIFO_SIZE 0x00002000 + +#define NV_HDIAG_OFFSET 0x00005000 +#define NV_HDIAG_SIZE 0x00001000 + +#define NV_PRAM_OFFSET 0x00006000 +#define NV_PRAM_SIZE 0x00001000 + +#define NV_PVIDEO_OFFSET 0x00008000 +#define NV_PVIDEO_SIZE 0x00001000 + +#define NV_PTIMER_OFFSET 0x00009000 +#define NV_PTIMER_SIZE 0x00001000 + +#define NV_PPM_OFFSET 0x0000A000 +#define NV_PPM_SIZE 0x00001000 + +#define NV_PTV_OFFSET 0x0000D000 +#define NV_PTV_SIZE 0x00001000 + +#define NV_PRMVGA_OFFSET 0x000A0000 +#define NV_PRMVGA_SIZE 0x00020000 + +#define NV_PRMVIO0_OFFSET 0x000C0000 +#define NV_PRMVIO_SIZE 0x00002000 +#define NV_PRMVIO1_OFFSET 0x000C2000 + +#define NV_PFB_OFFSET 0x00100000 +#define NV_PFB_SIZE 0x00001000 + +#define NV_PEXTDEV_OFFSET 0x00101000 +#define NV_PEXTDEV_SIZE 0x00001000 + +#define NV_PME_OFFSET 0x00200000 +#define NV_PME_SIZE 0x00001000 + +#define NV_PROM_OFFSET 0x00300000 +#define NV_PROM_SIZE 0x00010000 + +#define NV_PGRAPH_OFFSET 0x00400000 +#define NV_PGRAPH_SIZE 0x00010000 + +#define NV_PCRTC0_OFFSET 0x00600000 +#define NV_PCRTC0_SIZE 0x00002000 /* empirical */ + +#define NV_PRMCIO0_OFFSET 0x00601000 +#define NV_PRMCIO_SIZE 0x00002000 +#define NV_PRMCIO1_OFFSET 0x00603000 + +#define NV50_DISPLAY_OFFSET 0x00610000 +#define NV50_DISPLAY_SIZE 0x0000FFFF + +#define NV_PRAMDAC0_OFFSET 0x00680000 +#define NV_PRAMDAC0_SIZE 0x00002000 + +#define NV_PRMDIO0_OFFSET 0x00681000 +#define NV_PRMDIO_SIZE 0x00002000 +#define NV_PRMDIO1_OFFSET 0x00683000 + +#define NV_PRAMIN_OFFSET 0x00700000 +#define NV_PRAMIN_SIZE 0x00100000 + +#define NV_FIFO_OFFSET 0x00800000 +#define NV_FIFO_SIZE 0x00800000 + +#define NV_PMC_BOOT_0 0x00000000 +#define NV_PMC_ENABLE 0x00000200 + +#define NV_VIO_VSE2 0x000003c3 +#define NV_VIO_SRX 0x000003c4 + +#define NV_CIO_CRX__COLOR 0x000003d4 +#define NV_CIO_CR__COLOR 0x000003d5 + +#define NV_PBUS_DEBUG_1 0x00001084 +#define NV_PBUS_DEBUG_4 0x00001098 +#define NV_PBUS_DEBUG_DUALHEAD_CTL 0x000010f0 +#define NV_PBUS_POWERCTRL_1 0x00001584 +#define NV_PBUS_POWERCTRL_2 0x00001588 +#define NV_PBUS_POWERCTRL_4 0x00001590 +#define NV_PBUS_PCI_NV_19 0x0000184C +#define NV_PBUS_PCI_NV_20 0x00001850 +# define NV_PBUS_PCI_NV_20_ROM_SHADOW_DISABLED (0 << 0) +# define NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED (1 << 0) + +#define NV_PFIFO_RAMHT 0x00002210 + +#define NV_PTV_TV_INDEX 0x0000d220 +#define NV_PTV_TV_DATA 0x0000d224 +#define NV_PTV_HFILTER 0x0000d310 +#define NV_PTV_HFILTER2 0x0000d390 +#define NV_PTV_VFILTER 0x0000d510 + +#define NV_PRMVIO_MISC__WRITE 0x000c03c2 +#define NV_PRMVIO_SRX 0x000c03c4 +#define NV_PRMVIO_SR 0x000c03c5 +# define NV_VIO_SR_RESET_INDEX 0x00 +# define NV_VIO_SR_CLOCK_INDEX 0x01 +# define NV_VIO_SR_PLANE_MASK_INDEX 0x02 +# define NV_VIO_SR_CHAR_MAP_INDEX 0x03 +# define NV_VIO_SR_MEM_MODE_INDEX 0x04 +#define NV_PRMVIO_MISC__READ 0x000c03cc +#define NV_PRMVIO_GRX 0x000c03ce +#define NV_PRMVIO_GX 0x000c03cf +# define NV_VIO_GX_SR_INDEX 0x00 +# define NV_VIO_GX_SREN_INDEX 0x01 +# define NV_VIO_GX_CCOMP_INDEX 0x02 +# define NV_VIO_GX_ROP_INDEX 0x03 +# define NV_VIO_GX_READ_MAP_INDEX 0x04 +# define NV_VIO_GX_MODE_INDEX 0x05 +# define NV_VIO_GX_MISC_INDEX 0x06 +# define NV_VIO_GX_DONT_CARE_INDEX 0x07 +# define NV_VIO_GX_BIT_MASK_INDEX 0x08 + +#define NV_PCRTC_INTR_0 0x00600100 +# define NV_PCRTC_INTR_0_VBLANK (1 << 0) +#define NV_PCRTC_INTR_EN_0 0x00600140 +#define NV_PCRTC_START 0x00600800 +#define NV_PCRTC_CONFIG 0x00600804 +# define NV_PCRTC_CONFIG_START_ADDRESS_NON_VGA (1 << 0) +# define NV04_PCRTC_CONFIG_START_ADDRESS_HSYNC (4 << 0) +# define NV10_PCRTC_CONFIG_START_ADDRESS_HSYNC (2 << 0) +#define NV_PCRTC_CURSOR_CONFIG 0x00600810 +# define NV_PCRTC_CURSOR_CONFIG_ENABLE_ENABLE (1 << 0) +# define NV_PCRTC_CURSOR_CONFIG_DOUBLE_SCAN_ENABLE (1 << 4) +# define NV_PCRTC_CURSOR_CONFIG_ADDRESS_SPACE_PNVM (1 << 8) +# define NV_PCRTC_CURSOR_CONFIG_CUR_BPP_32 (1 << 12) +# define NV_PCRTC_CURSOR_CONFIG_CUR_PIXELS_64 (1 << 16) +# define NV_PCRTC_CURSOR_CONFIG_CUR_LINES_32 (2 << 24) +# define NV_PCRTC_CURSOR_CONFIG_CUR_LINES_64 (4 << 24) +# define NV_PCRTC_CURSOR_CONFIG_CUR_BLEND_ALPHA (1 << 28) + +/* note: PCRTC_GPIO is not available on nv10, and in fact aliases 0x600810 */ +#define NV_PCRTC_GPIO 0x00600818 +#define NV_PCRTC_GPIO_EXT 0x0060081c +#define NV_PCRTC_830 0x00600830 +#define NV_PCRTC_834 0x00600834 +#define NV_PCRTC_850 0x00600850 +#define NV_PCRTC_ENGINE_CTRL 0x00600860 +# define NV_CRTC_FSEL_I2C (1 << 4) +# define NV_CRTC_FSEL_OVERLAY (1 << 12) + +#define NV_PRMCIO_ARX 0x006013c0 +#define NV_PRMCIO_AR__WRITE 0x006013c0 +#define NV_PRMCIO_AR__READ 0x006013c1 +# define NV_CIO_AR_MODE_INDEX 0x10 +# define NV_CIO_AR_OSCAN_INDEX 0x11 +# define NV_CIO_AR_PLANE_INDEX 0x12 +# define NV_CIO_AR_HPP_INDEX 0x13 +# define NV_CIO_AR_CSEL_INDEX 0x14 +#define NV_PRMCIO_INP0 0x006013c2 +#define NV_PRMCIO_CRX__COLOR 0x006013d4 +#define NV_PRMCIO_CR__COLOR 0x006013d5 + /* Standard VGA CRTC registers */ +# define NV_CIO_CR_HDT_INDEX 0x00 /* horizontal display total */ +# define NV_CIO_CR_HDE_INDEX 0x01 /* horizontal display end */ +# define NV_CIO_CR_HBS_INDEX 0x02 /* horizontal blanking start */ +# define NV_CIO_CR_HBE_INDEX 0x03 /* horizontal blanking end */ +# define NV_CIO_CR_HBE_4_0 4:0 +# define NV_CIO_CR_HRS_INDEX 0x04 /* horizontal retrace start */ +# define NV_CIO_CR_HRE_INDEX 0x05 /* horizontal retrace end */ +# define NV_CIO_CR_HRE_4_0 4:0 +# define NV_CIO_CR_HRE_HBE_5 7:7 +# define NV_CIO_CR_VDT_INDEX 0x06 /* vertical display total */ +# define NV_CIO_CR_OVL_INDEX 0x07 /* overflow bits */ +# define NV_CIO_CR_OVL_VDT_8 0:0 +# define NV_CIO_CR_OVL_VDE_8 1:1 +# define NV_CIO_CR_OVL_VRS_8 2:2 +# define NV_CIO_CR_OVL_VBS_8 3:3 +# define NV_CIO_CR_OVL_VDT_9 5:5 +# define NV_CIO_CR_OVL_VDE_9 6:6 +# define NV_CIO_CR_OVL_VRS_9 7:7 +# define NV_CIO_CR_RSAL_INDEX 0x08 /* normally "preset row scan" */ +# define NV_CIO_CR_CELL_HT_INDEX 0x09 /* cell height?! normally "max scan line" */ +# define NV_CIO_CR_CELL_HT_VBS_9 5:5 +# define NV_CIO_CR_CELL_HT_SCANDBL 7:7 +# define NV_CIO_CR_CURS_ST_INDEX 0x0a /* cursor start */ +# define NV_CIO_CR_CURS_END_INDEX 0x0b /* cursor end */ +# define NV_CIO_CR_SA_HI_INDEX 0x0c /* screen start address high */ +# define NV_CIO_CR_SA_LO_INDEX 0x0d /* screen start address low */ +# define NV_CIO_CR_TCOFF_HI_INDEX 0x0e /* cursor offset high */ +# define NV_CIO_CR_TCOFF_LO_INDEX 0x0f /* cursor offset low */ +# define NV_CIO_CR_VRS_INDEX 0x10 /* vertical retrace start */ +# define NV_CIO_CR_VRE_INDEX 0x11 /* vertical retrace end */ +# define NV_CIO_CR_VRE_3_0 3:0 +# define NV_CIO_CR_VDE_INDEX 0x12 /* vertical display end */ +# define NV_CIO_CR_OFFSET_INDEX 0x13 /* sets screen pitch */ +# define NV_CIO_CR_ULINE_INDEX 0x14 /* underline location */ +# define NV_CIO_CR_VBS_INDEX 0x15 /* vertical blank start */ +# define NV_CIO_CR_VBE_INDEX 0x16 /* vertical blank end */ +# define NV_CIO_CR_MODE_INDEX 0x17 /* crtc mode control */ +# define NV_CIO_CR_LCOMP_INDEX 0x18 /* line compare */ + /* Extended VGA CRTC registers */ +# define NV_CIO_CRE_RPC0_INDEX 0x19 /* repaint control 0 */ +# define NV_CIO_CRE_RPC0_OFFSET_10_8 7:5 +# define NV_CIO_CRE_RPC1_INDEX 0x1a /* repaint control 1 */ +# define NV_CIO_CRE_RPC1_LARGE 2:2 +# define NV_CIO_CRE_FF_INDEX 0x1b /* fifo control */ +# define NV_CIO_CRE_ENH_INDEX 0x1c /* enhanced? */ +# define NV_CIO_SR_LOCK_INDEX 0x1f /* crtc lock */ +# define NV_CIO_SR_UNLOCK_RW_VALUE 0x57 +# define NV_CIO_SR_LOCK_VALUE 0x99 +# define NV_CIO_CRE_FFLWM__INDEX 0x20 /* fifo low water mark */ +# define NV_CIO_CRE_21 0x21 /* vga shadow crtc lock */ +# define NV_CIO_CRE_LSR_INDEX 0x25 /* ? */ +# define NV_CIO_CRE_LSR_VDT_10 0:0 +# define NV_CIO_CRE_LSR_VDE_10 1:1 +# define NV_CIO_CRE_LSR_VRS_10 2:2 +# define NV_CIO_CRE_LSR_VBS_10 3:3 +# define NV_CIO_CRE_LSR_HBE_6 4:4 +# define NV_CIO_CR_ARX_INDEX 0x26 /* attribute index -- ro copy of 0x60.3c0 */ +# define NV_CIO_CRE_CHIP_ID_INDEX 0x27 /* chip revision */ +# define NV_CIO_CRE_PIXEL_INDEX 0x28 +# define NV_CIO_CRE_PIXEL_FORMAT 1:0 +# define NV_CIO_CRE_HEB__INDEX 0x2d /* horizontal extra bits? */ +# define NV_CIO_CRE_HEB_HDT_8 0:0 +# define NV_CIO_CRE_HEB_HDE_8 1:1 +# define NV_CIO_CRE_HEB_HBS_8 2:2 +# define NV_CIO_CRE_HEB_HRS_8 3:3 +# define NV_CIO_CRE_HEB_ILC_8 4:4 +# define NV_CIO_CRE_2E 0x2e /* some scratch or dummy reg to force writes to sink in */ +# define NV_CIO_CRE_HCUR_ADDR2_INDEX 0x2f /* cursor */ +# define NV_CIO_CRE_HCUR_ADDR0_INDEX 0x30 /* pixmap */ +# define NV_CIO_CRE_HCUR_ADDR0_ADR 6:0 +# define NV_CIO_CRE_HCUR_ASI 7:7 +# define NV_CIO_CRE_HCUR_ADDR1_INDEX 0x31 /* address */ +# define NV_CIO_CRE_HCUR_ADDR1_ENABLE 0:0 +# define NV_CIO_CRE_HCUR_ADDR1_CUR_DBL 1:1 +# define NV_CIO_CRE_HCUR_ADDR1_ADR 7:2 +# define NV_CIO_CRE_LCD__INDEX 0x33 +# define NV_CIO_CRE_LCD_LCD_SELECT 0:0 +# define NV_CIO_CRE_LCD_ROUTE_MASK 0x3b +# define NV_CIO_CRE_DDC0_STATUS__INDEX 0x36 +# define NV_CIO_CRE_DDC0_WR__INDEX 0x37 +# define NV_CIO_CRE_ILACE__INDEX 0x39 /* interlace */ +# define NV_CIO_CRE_SCRATCH3__INDEX 0x3b +# define NV_CIO_CRE_SCRATCH4__INDEX 0x3c +# define NV_CIO_CRE_DDC_STATUS__INDEX 0x3e +# define NV_CIO_CRE_DDC_WR__INDEX 0x3f +# define NV_CIO_CRE_EBR_INDEX 0x41 /* extra bits ? (vertical) */ +# define NV_CIO_CRE_EBR_VDT_11 0:0 +# define NV_CIO_CRE_EBR_VDE_11 2:2 +# define NV_CIO_CRE_EBR_VRS_11 4:4 +# define NV_CIO_CRE_EBR_VBS_11 6:6 +# define NV_CIO_CRE_42 0x42 +# define NV_CIO_CRE_42_OFFSET_11 6:6 +# define NV_CIO_CRE_43 0x43 +# define NV_CIO_CRE_44 0x44 /* head control */ +# define NV_CIO_CRE_CSB 0x45 /* colour saturation boost */ +# define NV_CIO_CRE_RCR 0x46 +# define NV_CIO_CRE_RCR_ENDIAN_BIG 7:7 +# define NV_CIO_CRE_47 0x47 /* extended fifo lwm, used on nv30+ */ +# define NV_CIO_CRE_49 0x49 +# define NV_CIO_CRE_4B 0x4b /* given patterns in 0x[2-3][a-c] regs, probably scratch 6 */ +# define NV_CIO_CRE_TVOUT_LATENCY 0x52 +# define NV_CIO_CRE_53 0x53 /* `fp_htiming' according to Haiku */ +# define NV_CIO_CRE_54 0x54 /* `fp_vtiming' according to Haiku */ +# define NV_CIO_CRE_57 0x57 /* index reg for cr58 */ +# define NV_CIO_CRE_58 0x58 /* data reg for cr57 */ +# define NV_CIO_CRE_59 0x59 /* related to on/off-chip-ness of digital outputs */ +# define NV_CIO_CRE_5B 0x5B /* newer colour saturation reg */ +# define NV_CIO_CRE_85 0x85 +# define NV_CIO_CRE_86 0x86 +#define NV_PRMCIO_INP0__COLOR 0x006013da + +#define NV_PRAMDAC_CU_START_POS 0x00680300 +# define NV_PRAMDAC_CU_START_POS_X 15:0 +# define NV_PRAMDAC_CU_START_POS_Y 31:16 +#define NV_RAMDAC_NV10_CURSYNC 0x00680404 + +#define NV_PRAMDAC_NVPLL_COEFF 0x00680500 +#define NV_PRAMDAC_MPLL_COEFF 0x00680504 +#define NV_PRAMDAC_VPLL_COEFF 0x00680508 +# define NV30_RAMDAC_ENABLE_VCO2 (8 << 4) + +#define NV_PRAMDAC_PLL_COEFF_SELECT 0x0068050c +# define NV_PRAMDAC_PLL_COEFF_SELECT_USE_VPLL2_TRUE (4 << 0) +# define NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_MPLL (1 << 8) +# define NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_VPLL (2 << 8) +# define NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_NVPLL (4 << 8) +# define NV_PRAMDAC_PLL_COEFF_SELECT_PLL_SOURCE_VPLL2 (8 << 8) +# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1 (1 << 16) +# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1 (2 << 16) +# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2 (4 << 16) +# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2 (8 << 16) +# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_CLK_SOURCE_VIP (1 << 20) +# define NV_PRAMDAC_PLL_COEFF_SELECT_VCLK_RATIO_DB2 (1 << 28) +# define NV_PRAMDAC_PLL_COEFF_SELECT_VCLK2_RATIO_DB2 (2 << 28) + +#define NV_PRAMDAC_PLL_SETUP_CONTROL 0x00680510 +#define NV_RAMDAC_VPLL2 0x00680520 +#define NV_PRAMDAC_SEL_CLK 0x00680524 +#define NV_RAMDAC_DITHER_NV11 0x00680528 +#define NV_PRAMDAC_DACCLK 0x0068052c +# define NV_PRAMDAC_DACCLK_SEL_DACCLK (1 << 0) + +#define NV_RAMDAC_NVPLL_B 0x00680570 +#define NV_RAMDAC_MPLL_B 0x00680574 +#define NV_RAMDAC_VPLL_B 0x00680578 +#define NV_RAMDAC_VPLL2_B 0x0068057c +# define NV31_RAMDAC_ENABLE_VCO2 (8 << 28) +#define NV_PRAMDAC_580 0x00680580 +# define NV_RAMDAC_580_VPLL1_ACTIVE (1 << 8) +# define NV_RAMDAC_580_VPLL2_ACTIVE (1 << 28) + +#define NV_PRAMDAC_GENERAL_CONTROL 0x00680600 +# define NV_PRAMDAC_GENERAL_CONTROL_PIXMIX_ON (3 << 4) +# define NV_PRAMDAC_GENERAL_CONTROL_VGA_STATE_SEL (1 << 8) +# define NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL (1 << 12) +# define NV_PRAMDAC_GENERAL_CONTROL_TERMINATION_75OHM (2 << 16) +# define NV_PRAMDAC_GENERAL_CONTROL_BPC_8BITS (1 << 20) +# define NV_PRAMDAC_GENERAL_CONTROL_PIPE_LONG (2 << 28) +#define NV_PRAMDAC_TEST_CONTROL 0x00680608 +# define NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED (1 << 12) +# define NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF (1 << 16) +# define NV_PRAMDAC_TEST_CONTROL_SENSEB_ALLHI (1 << 28) +#define NV_PRAMDAC_TESTPOINT_DATA 0x00680610 +# define NV_PRAMDAC_TESTPOINT_DATA_NOTBLANK (8 << 28) +#define NV_PRAMDAC_630 0x00680630 +#define NV_PRAMDAC_634 0x00680634 + +#define NV_PRAMDAC_TV_SETUP 0x00680700 +#define NV_PRAMDAC_TV_VTOTAL 0x00680720 +#define NV_PRAMDAC_TV_VSKEW 0x00680724 +#define NV_PRAMDAC_TV_VSYNC_DELAY 0x00680728 +#define NV_PRAMDAC_TV_HTOTAL 0x0068072c +#define NV_PRAMDAC_TV_HSKEW 0x00680730 +#define NV_PRAMDAC_TV_HSYNC_DELAY 0x00680734 +#define NV_PRAMDAC_TV_HSYNC_DELAY2 0x00680738 + +#define NV_PRAMDAC_TV_SETUP 0x00680700 + +#define NV_PRAMDAC_FP_VDISPLAY_END 0x00680800 +#define NV_PRAMDAC_FP_VTOTAL 0x00680804 +#define NV_PRAMDAC_FP_VCRTC 0x00680808 +#define NV_PRAMDAC_FP_VSYNC_START 0x0068080c +#define NV_PRAMDAC_FP_VSYNC_END 0x00680810 +#define NV_PRAMDAC_FP_VVALID_START 0x00680814 +#define NV_PRAMDAC_FP_VVALID_END 0x00680818 +#define NV_PRAMDAC_FP_HDISPLAY_END 0x00680820 +#define NV_PRAMDAC_FP_HTOTAL 0x00680824 +#define NV_PRAMDAC_FP_HCRTC 0x00680828 +#define NV_PRAMDAC_FP_HSYNC_START 0x0068082c +#define NV_PRAMDAC_FP_HSYNC_END 0x00680830 +#define NV_PRAMDAC_FP_HVALID_START 0x00680834 +#define NV_PRAMDAC_FP_HVALID_END 0x00680838 + +#define NV_RAMDAC_FP_DITHER 0x0068083c +#define NV_PRAMDAC_FP_TG_CONTROL 0x00680848 +# define NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS (1 << 0) +# define NV_PRAMDAC_FP_TG_CONTROL_VSYNC_DISABLE (2 << 0) +# define NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS (1 << 4) +# define NV_PRAMDAC_FP_TG_CONTROL_HSYNC_DISABLE (2 << 4) +# define NV_PRAMDAC_FP_TG_CONTROL_MODE_SCALE (0 << 8) +# define NV_PRAMDAC_FP_TG_CONTROL_MODE_CENTER (1 << 8) +# define NV_PRAMDAC_FP_TG_CONTROL_MODE_NATIVE (2 << 8) +# define NV_PRAMDAC_FP_TG_CONTROL_READ_PROG (1 << 20) +# define NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12 (1 << 24) +# define NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS (1 << 28) +# define NV_PRAMDAC_FP_TG_CONTROL_DISPEN_DISABLE (2 << 28) +#define NV_PRAMDAC_FP_MARGIN_COLOR 0x0068084c +#define NV_PRAMDAC_850 0x00680850 +#define NV_PRAMDAC_85C 0x0068085c +#define NV_PRAMDAC_FP_DEBUG_0 0x00680880 +# define NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE (1 << 0) +# define NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE (1 << 4) +/* This doesn't seem to be essential for tmds, but still often set */ +# define NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED (8 << 4) +# define NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR (1 << 8) +# define NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR (1 << 12) +# define NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND (1 << 20) +# define NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND (1 << 24) +# define NV_PRAMDAC_FP_DEBUG_0_PWRDOWN_FPCLK (1 << 28) +#define NV_PRAMDAC_FP_DEBUG_1 0x00680884 +# define NV_PRAMDAC_FP_DEBUG_1_XSCALE_VALUE 11:0 +# define NV_PRAMDAC_FP_DEBUG_1_XSCALE_TESTMODE_ENABLE (1 << 12) +# define NV_PRAMDAC_FP_DEBUG_1_YSCALE_VALUE 27:16 +# define NV_PRAMDAC_FP_DEBUG_1_YSCALE_TESTMODE_ENABLE (1 << 28) +#define NV_PRAMDAC_FP_DEBUG_2 0x00680888 +#define NV_PRAMDAC_FP_DEBUG_3 0x0068088C + +/* see NV_PRAMDAC_INDIR_TMDS in rules.xml */ +#define NV_PRAMDAC_FP_TMDS_CONTROL 0x006808b0 +# define NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE (1 << 16) +#define NV_PRAMDAC_FP_TMDS_DATA 0x006808b4 + +#define NV_PRAMDAC_8C0 0x006808c0 + +/* Some kind of switch */ +#define NV_PRAMDAC_900 0x00680900 +#define NV_PRAMDAC_A20 0x00680A20 +#define NV_PRAMDAC_A24 0x00680A24 +#define NV_PRAMDAC_A34 0x00680A34 + +#define NV_PRAMDAC_CTV 0x00680c00 + +/* names fabricated from NV_USER_DAC info */ +#define NV_PRMDIO_PIXEL_MASK 0x006813c6 +# define NV_PRMDIO_PIXEL_MASK_MASK 0xff +#define NV_PRMDIO_READ_MODE_ADDRESS 0x006813c7 +#define NV_PRMDIO_WRITE_MODE_ADDRESS 0x006813c8 +#define NV_PRMDIO_PALETTE_DATA 0x006813c9 + +#define NV_PGRAPH_DEBUG_0 0x00400080 +#define NV_PGRAPH_DEBUG_1 0x00400084 +#define NV_PGRAPH_DEBUG_2_NV04 0x00400088 +#define NV_PGRAPH_DEBUG_2 0x00400620 +#define NV_PGRAPH_DEBUG_3 0x0040008c +#define NV_PGRAPH_DEBUG_4 0x00400090 +#define NV_PGRAPH_INTR 0x00400100 +#define NV_PGRAPH_INTR_EN 0x00400140 +#define NV_PGRAPH_CTX_CONTROL 0x00400144 +#define NV_PGRAPH_CTX_CONTROL_NV04 0x00400170 +#define NV_PGRAPH_ABS_UCLIP_XMIN 0x0040053C +#define NV_PGRAPH_ABS_UCLIP_YMIN 0x00400540 +#define NV_PGRAPH_ABS_UCLIP_XMAX 0x00400544 +#define NV_PGRAPH_ABS_UCLIP_YMAX 0x00400548 +#define NV_PGRAPH_BETA_AND 0x00400608 +#define NV_PGRAPH_LIMIT_VIOL_PIX 0x00400610 +#define NV_PGRAPH_BOFFSET0 0x00400640 +#define NV_PGRAPH_BOFFSET1 0x00400644 +#define NV_PGRAPH_BOFFSET2 0x00400648 +#define NV_PGRAPH_BLIMIT0 0x00400684 +#define NV_PGRAPH_BLIMIT1 0x00400688 +#define NV_PGRAPH_BLIMIT2 0x0040068c +#define NV_PGRAPH_STATUS 0x00400700 +#define NV_PGRAPH_SURFACE 0x00400710 +#define NV_PGRAPH_STATE 0x00400714 +#define NV_PGRAPH_FIFO 0x00400720 +#define NV_PGRAPH_PATTERN_SHAPE 0x00400810 +#define NV_PGRAPH_TILE 0x00400b00 + +#define NV_PVIDEO_INTR_EN 0x00008140 +#define NV_PVIDEO_BUFFER 0x00008700 +#define NV_PVIDEO_STOP 0x00008704 +#define NV_PVIDEO_UVPLANE_BASE(buff) (0x00008800+(buff)*4) +#define NV_PVIDEO_UVPLANE_LIMIT(buff) (0x00008808+(buff)*4) +#define NV_PVIDEO_UVPLANE_OFFSET_BUFF(buff) (0x00008820+(buff)*4) +#define NV_PVIDEO_BASE(buff) (0x00008900+(buff)*4) +#define NV_PVIDEO_LIMIT(buff) (0x00008908+(buff)*4) +#define NV_PVIDEO_LUMINANCE(buff) (0x00008910+(buff)*4) +#define NV_PVIDEO_CHROMINANCE(buff) (0x00008918+(buff)*4) +#define NV_PVIDEO_OFFSET_BUFF(buff) (0x00008920+(buff)*4) +#define NV_PVIDEO_SIZE_IN(buff) (0x00008928+(buff)*4) +#define NV_PVIDEO_POINT_IN(buff) (0x00008930+(buff)*4) +#define NV_PVIDEO_DS_DX(buff) (0x00008938+(buff)*4) +#define NV_PVIDEO_DT_DY(buff) (0x00008940+(buff)*4) +#define NV_PVIDEO_POINT_OUT(buff) (0x00008948+(buff)*4) +#define NV_PVIDEO_SIZE_OUT(buff) (0x00008950+(buff)*4) +#define NV_PVIDEO_FORMAT(buff) (0x00008958+(buff)*4) +# define NV_PVIDEO_FORMAT_PLANAR (1 << 0) +# define NV_PVIDEO_FORMAT_COLOR_LE_CR8YB8CB8YA8 (1 << 16) +# define NV_PVIDEO_FORMAT_DISPLAY_COLOR_KEY (1 << 20) +# define NV_PVIDEO_FORMAT_MATRIX_ITURBT709 (1 << 24) +#define NV_PVIDEO_COLOR_KEY 0x00008B00 + +/* NV04 overlay defines from VIDIX & Haiku */ +#define NV_PVIDEO_INTR_EN_0 0x00680140 +#define NV_PVIDEO_STEP_SIZE 0x00680200 +#define NV_PVIDEO_CONTROL_Y 0x00680204 +#define NV_PVIDEO_CONTROL_X 0x00680208 +#define NV_PVIDEO_BUFF0_START_ADDRESS 0x0068020c +#define NV_PVIDEO_BUFF0_PITCH_LENGTH 0x00680214 +#define NV_PVIDEO_BUFF0_OFFSET 0x0068021c +#define NV_PVIDEO_BUFF1_START_ADDRESS 0x00680210 +#define NV_PVIDEO_BUFF1_PITCH_LENGTH 0x00680218 +#define NV_PVIDEO_BUFF1_OFFSET 0x00680220 +#define NV_PVIDEO_OE_STATE 0x00680224 +#define NV_PVIDEO_SU_STATE 0x00680228 +#define NV_PVIDEO_RM_STATE 0x0068022c +#define NV_PVIDEO_WINDOW_START 0x00680230 +#define NV_PVIDEO_WINDOW_SIZE 0x00680234 +#define NV_PVIDEO_FIFO_THRES_SIZE 0x00680238 +#define NV_PVIDEO_FIFO_BURST_LENGTH 0x0068023c +#define NV_PVIDEO_KEY 0x00680240 +#define NV_PVIDEO_OVERLAY 0x00680244 +#define NV_PVIDEO_RED_CSC_OFFSET 0x00680280 +#define NV_PVIDEO_GREEN_CSC_OFFSET 0x00680284 +#define NV_PVIDEO_BLUE_CSC_OFFSET 0x00680288 +#define NV_PVIDEO_CSC_ADJUST 0x0068028c + +#endif |