diff options
author | Alex Deucher <alexander.deucher@amd.com> | 2015-04-20 16:55:21 -0400 |
---|---|---|
committer | Alex Deucher <alexander.deucher@amd.com> | 2015-06-03 21:03:15 -0400 |
commit | d38ceaf99ed015f2a0b9af3499791bd3a3daae21 (patch) | |
tree | c8e237ea218e8ed8a5f64c1654fc01fe5d2239cb /drivers/gpu/drm/amd | |
parent | 97b2e202fba05b87d720318a6500a337100dab4d (diff) | |
download | talos-obmc-linux-d38ceaf99ed015f2a0b9af3499791bd3a3daae21.tar.gz talos-obmc-linux-d38ceaf99ed015f2a0b9af3499791bd3a3daae21.zip |
drm/amdgpu: add core driver (v4)
This adds the non-asic specific core driver code.
v2: remove extra kconfig option
v3: implement minor fixes from Fengguang Wu
v4: fix cast in amdgpu_ucode.c
Acked-by: Christian König <christian.koenig@amd.com>
Acked-by: Jammy Zhou <Jammy.Zhou@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Diffstat (limited to 'drivers/gpu/drm/amd')
76 files changed, 33574 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/amdgpu/Kconfig b/drivers/gpu/drm/amd/amdgpu/Kconfig new file mode 100644 index 000000000000..b30fcfa4b1f2 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/Kconfig @@ -0,0 +1,17 @@ +config DRM_AMDGPU_CIK + bool "Enable amdgpu support for CIK parts" + depends on DRM_AMDGPU + help + Choose this option if you want to enable experimental support + for CIK asics. + + CIK is already supported in radeon. CIK support in amdgpu + is for experimentation and testing. + +config DRM_AMDGPU_USERPTR + bool "Always enable userptr write support" + depends on DRM_AMDGPU + select MMU_NOTIFIER + help + This option selects CONFIG_MMU_NOTIFIER if it isn't already + selected to enabled full userptr support. diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile new file mode 100644 index 000000000000..01276a592bc5 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/Makefile @@ -0,0 +1,49 @@ +# +# 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 -Idrivers/gpu/drm/amd/include/asic_reg + +amdgpu-y := amdgpu_drv.o + +# add KMS driver +amdgpu-y += amdgpu_device.o amdgpu_kms.o \ + amdgpu_atombios.o atombios_crtc.o amdgpu_connectors.o \ + atom.o amdgpu_fence.o amdgpu_ttm.o amdgpu_object.o amdgpu_gart.o \ + amdgpu_encoders.o amdgpu_display.o amdgpu_i2c.o \ + amdgpu_fb.o amdgpu_gem.o amdgpu_ring.o \ + amdgpu_cs.o amdgpu_bios.o amdgpu_benchmark.o amdgpu_test.o \ + amdgpu_pm.o atombios_dp.o amdgpu_afmt.o amdgpu_trace_points.o \ + atombios_encoders.o amdgpu_semaphore.o amdgpu_sa.o atombios_i2c.o \ + amdgpu_prime.o amdgpu_vm.o amdgpu_ib.o amdgpu_pll.o \ + amdgpu_ucode.o amdgpu_bo_list.o amdgpu_ctx.o amdgpu_sync.o + +# add IH block +amdgpu-y += \ + amdgpu_irq.o \ + amdgpu_ih.o + +# add SMC block +amdgpu-y += \ + amdgpu_dpm.o + +# add GFX block +amdgpu-y += \ + amdgpu_gfx.o + +# add UVD block +amdgpu-y += \ + amdgpu_uvd.o + +# add VCE block +amdgpu-y += \ + amdgpu_vce.o + +amdgpu-$(CONFIG_COMPAT) += amdgpu_ioc32.o +amdgpu-$(CONFIG_VGA_SWITCHEROO) += amdgpu_atpx_handler.o +amdgpu-$(CONFIG_ACPI) += amdgpu_acpi.o +amdgpu-$(CONFIG_MMU_NOTIFIER) += amdgpu_mn.o + +obj-$(CONFIG_DRM_AMDGPU)+= amdgpu.o + +CFLAGS_amdgpu_trace_points.o := -I$(src) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c new file mode 100644 index 000000000000..aef4a7aac0f7 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c @@ -0,0 +1,768 @@ +/* + * Copyright 2012 Advanced Micro Devices, 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. + * + */ + +#include <linux/pci.h> +#include <linux/acpi.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/vga_switcheroo.h> +#include <acpi/video.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include "amdgpu.h" +#include "amdgpu_acpi.h" +#include "atom.h" + +#define ACPI_AC_CLASS "ac_adapter" + +extern void amdgpu_pm_acpi_event_handler(struct amdgpu_device *adev); + +struct atif_verify_interface { + u16 size; /* structure size in bytes (includes size field) */ + u16 version; /* version */ + u32 notification_mask; /* supported notifications mask */ + u32 function_bits; /* supported functions bit vector */ +} __packed; + +struct atif_system_params { + u16 size; /* structure size in bytes (includes size field) */ + u32 valid_mask; /* valid flags mask */ + u32 flags; /* flags */ + u8 command_code; /* notify command code */ +} __packed; + +struct atif_sbios_requests { + u16 size; /* structure size in bytes (includes size field) */ + u32 pending; /* pending sbios requests */ + u8 panel_exp_mode; /* panel expansion mode */ + u8 thermal_gfx; /* thermal state: target gfx controller */ + u8 thermal_state; /* thermal state: state id (0: exit state, non-0: state) */ + u8 forced_power_gfx; /* forced power state: target gfx controller */ + u8 forced_power_state; /* forced power state: state id */ + u8 system_power_src; /* system power source */ + u8 backlight_level; /* panel backlight level (0-255) */ +} __packed; + +#define ATIF_NOTIFY_MASK 0x3 +#define ATIF_NOTIFY_NONE 0 +#define ATIF_NOTIFY_81 1 +#define ATIF_NOTIFY_N 2 + +struct atcs_verify_interface { + u16 size; /* structure size in bytes (includes size field) */ + u16 version; /* version */ + u32 function_bits; /* supported functions bit vector */ +} __packed; + +#define ATCS_VALID_FLAGS_MASK 0x3 + +struct atcs_pref_req_input { + u16 size; /* structure size in bytes (includes size field) */ + u16 client_id; /* client id (bit 2-0: func num, 7-3: dev num, 15-8: bus num) */ + u16 valid_flags_mask; /* valid flags mask */ + u16 flags; /* flags */ + u8 req_type; /* request type */ + u8 perf_req; /* performance request */ +} __packed; + +struct atcs_pref_req_output { + u16 size; /* structure size in bytes (includes size field) */ + u8 ret_val; /* return value */ +} __packed; + +/* Call the ATIF method + */ +/** + * amdgpu_atif_call - call an ATIF method + * + * @handle: acpi handle + * @function: the ATIF function to execute + * @params: ATIF function params + * + * Executes the requested ATIF function (all asics). + * Returns a pointer to the acpi output buffer. + */ +static union acpi_object *amdgpu_atif_call(acpi_handle handle, int function, + struct acpi_buffer *params) +{ + acpi_status status; + union acpi_object atif_arg_elements[2]; + struct acpi_object_list atif_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + + atif_arg.count = 2; + atif_arg.pointer = &atif_arg_elements[0]; + + atif_arg_elements[0].type = ACPI_TYPE_INTEGER; + atif_arg_elements[0].integer.value = function; + + if (params) { + atif_arg_elements[1].type = ACPI_TYPE_BUFFER; + atif_arg_elements[1].buffer.length = params->length; + atif_arg_elements[1].buffer.pointer = params->pointer; + } else { + /* We need a second fake parameter */ + atif_arg_elements[1].type = ACPI_TYPE_INTEGER; + atif_arg_elements[1].integer.value = 0; + } + + status = acpi_evaluate_object(handle, "ATIF", &atif_arg, &buffer); + + /* Fail only if calling the method fails and ATIF is supported */ + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { + DRM_DEBUG_DRIVER("failed to evaluate ATIF got %s\n", + acpi_format_exception(status)); + kfree(buffer.pointer); + return NULL; + } + + return buffer.pointer; +} + +/** + * amdgpu_atif_parse_notification - parse supported notifications + * + * @n: supported notifications struct + * @mask: supported notifications mask from ATIF + * + * Use the supported notifications mask from ATIF function + * ATIF_FUNCTION_VERIFY_INTERFACE to determine what notifications + * are supported (all asics). + */ +static void amdgpu_atif_parse_notification(struct amdgpu_atif_notifications *n, u32 mask) +{ + n->display_switch = mask & ATIF_DISPLAY_SWITCH_REQUEST_SUPPORTED; + n->expansion_mode_change = mask & ATIF_EXPANSION_MODE_CHANGE_REQUEST_SUPPORTED; + n->thermal_state = mask & ATIF_THERMAL_STATE_CHANGE_REQUEST_SUPPORTED; + n->forced_power_state = mask & ATIF_FORCED_POWER_STATE_CHANGE_REQUEST_SUPPORTED; + n->system_power_state = mask & ATIF_SYSTEM_POWER_SOURCE_CHANGE_REQUEST_SUPPORTED; + n->display_conf_change = mask & ATIF_DISPLAY_CONF_CHANGE_REQUEST_SUPPORTED; + n->px_gfx_switch = mask & ATIF_PX_GFX_SWITCH_REQUEST_SUPPORTED; + n->brightness_change = mask & ATIF_PANEL_BRIGHTNESS_CHANGE_REQUEST_SUPPORTED; + n->dgpu_display_event = mask & ATIF_DGPU_DISPLAY_EVENT_SUPPORTED; +} + +/** + * amdgpu_atif_parse_functions - parse supported functions + * + * @f: supported functions struct + * @mask: supported functions mask from ATIF + * + * Use the supported functions mask from ATIF function + * ATIF_FUNCTION_VERIFY_INTERFACE to determine what functions + * are supported (all asics). + */ +static void amdgpu_atif_parse_functions(struct amdgpu_atif_functions *f, u32 mask) +{ + f->system_params = mask & ATIF_GET_SYSTEM_PARAMETERS_SUPPORTED; + f->sbios_requests = mask & ATIF_GET_SYSTEM_BIOS_REQUESTS_SUPPORTED; + f->select_active_disp = mask & ATIF_SELECT_ACTIVE_DISPLAYS_SUPPORTED; + f->lid_state = mask & ATIF_GET_LID_STATE_SUPPORTED; + f->get_tv_standard = mask & ATIF_GET_TV_STANDARD_FROM_CMOS_SUPPORTED; + f->set_tv_standard = mask & ATIF_SET_TV_STANDARD_IN_CMOS_SUPPORTED; + f->get_panel_expansion_mode = mask & ATIF_GET_PANEL_EXPANSION_MODE_FROM_CMOS_SUPPORTED; + f->set_panel_expansion_mode = mask & ATIF_SET_PANEL_EXPANSION_MODE_IN_CMOS_SUPPORTED; + f->temperature_change = mask & ATIF_TEMPERATURE_CHANGE_NOTIFICATION_SUPPORTED; + f->graphics_device_types = mask & ATIF_GET_GRAPHICS_DEVICE_TYPES_SUPPORTED; +} + +/** + * amdgpu_atif_verify_interface - verify ATIF + * + * @handle: acpi handle + * @atif: amdgpu atif struct + * + * Execute the ATIF_FUNCTION_VERIFY_INTERFACE ATIF function + * to initialize ATIF and determine what features are supported + * (all asics). + * returns 0 on success, error on failure. + */ +static int amdgpu_atif_verify_interface(acpi_handle handle, + struct amdgpu_atif *atif) +{ + union acpi_object *info; + struct atif_verify_interface output; + size_t size; + int err = 0; + + info = amdgpu_atif_call(handle, ATIF_FUNCTION_VERIFY_INTERFACE, NULL); + if (!info) + return -EIO; + + memset(&output, 0, sizeof(output)); + + size = *(u16 *) info->buffer.pointer; + if (size < 12) { + DRM_INFO("ATIF buffer is too small: %zu\n", size); + err = -EINVAL; + goto out; + } + size = min(sizeof(output), size); + + memcpy(&output, info->buffer.pointer, size); + + /* TODO: check version? */ + DRM_DEBUG_DRIVER("ATIF version %u\n", output.version); + + amdgpu_atif_parse_notification(&atif->notifications, output.notification_mask); + amdgpu_atif_parse_functions(&atif->functions, output.function_bits); + +out: + kfree(info); + return err; +} + +/** + * amdgpu_atif_get_notification_params - determine notify configuration + * + * @handle: acpi handle + * @n: atif notification configuration struct + * + * Execute the ATIF_FUNCTION_GET_SYSTEM_PARAMETERS ATIF function + * to determine if a notifier is used and if so which one + * (all asics). This is either Notify(VGA, 0x81) or Notify(VGA, n) + * where n is specified in the result if a notifier is used. + * Returns 0 on success, error on failure. + */ +static int amdgpu_atif_get_notification_params(acpi_handle handle, + struct amdgpu_atif_notification_cfg *n) +{ + union acpi_object *info; + struct atif_system_params params; + size_t size; + int err = 0; + + info = amdgpu_atif_call(handle, ATIF_FUNCTION_GET_SYSTEM_PARAMETERS, NULL); + if (!info) { + err = -EIO; + goto out; + } + + size = *(u16 *) info->buffer.pointer; + if (size < 10) { + err = -EINVAL; + goto out; + } + + memset(¶ms, 0, sizeof(params)); + size = min(sizeof(params), size); + memcpy(¶ms, info->buffer.pointer, size); + + DRM_DEBUG_DRIVER("SYSTEM_PARAMS: mask = %#x, flags = %#x\n", + params.flags, params.valid_mask); + params.flags = params.flags & params.valid_mask; + + if ((params.flags & ATIF_NOTIFY_MASK) == ATIF_NOTIFY_NONE) { + n->enabled = false; + n->command_code = 0; + } else if ((params.flags & ATIF_NOTIFY_MASK) == ATIF_NOTIFY_81) { + n->enabled = true; + n->command_code = 0x81; + } else { + if (size < 11) { + err = -EINVAL; + goto out; + } + n->enabled = true; + n->command_code = params.command_code; + } + +out: + DRM_DEBUG_DRIVER("Notification %s, command code = %#x\n", + (n->enabled ? "enabled" : "disabled"), + n->command_code); + kfree(info); + return err; +} + +/** + * amdgpu_atif_get_sbios_requests - get requested sbios event + * + * @handle: acpi handle + * @req: atif sbios request struct + * + * Execute the ATIF_FUNCTION_GET_SYSTEM_BIOS_REQUESTS ATIF function + * to determine what requests the sbios is making to the driver + * (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atif_get_sbios_requests(acpi_handle handle, + struct atif_sbios_requests *req) +{ + union acpi_object *info; + size_t size; + int count = 0; + + info = amdgpu_atif_call(handle, ATIF_FUNCTION_GET_SYSTEM_BIOS_REQUESTS, NULL); + if (!info) + return -EIO; + + size = *(u16 *)info->buffer.pointer; + if (size < 0xd) { + count = -EINVAL; + goto out; + } + memset(req, 0, sizeof(*req)); + + size = min(sizeof(*req), size); + memcpy(req, info->buffer.pointer, size); + DRM_DEBUG_DRIVER("SBIOS pending requests: %#x\n", req->pending); + + count = hweight32(req->pending); + +out: + kfree(info); + return count; +} + +/** + * amdgpu_atif_handler - handle ATIF notify requests + * + * @adev: amdgpu_device pointer + * @event: atif sbios request struct + * + * Checks the acpi event and if it matches an atif event, + * handles it. + * Returns NOTIFY code + */ +int amdgpu_atif_handler(struct amdgpu_device *adev, + struct acpi_bus_event *event) +{ + struct amdgpu_atif *atif = &adev->atif; + struct atif_sbios_requests req; + acpi_handle handle; + int count; + + DRM_DEBUG_DRIVER("event, device_class = %s, type = %#x\n", + event->device_class, event->type); + + if (strcmp(event->device_class, ACPI_VIDEO_CLASS) != 0) + return NOTIFY_DONE; + + if (!atif->notification_cfg.enabled || + event->type != atif->notification_cfg.command_code) + /* Not our event */ + return NOTIFY_DONE; + + /* Check pending SBIOS requests */ + handle = ACPI_HANDLE(&adev->pdev->dev); + count = amdgpu_atif_get_sbios_requests(handle, &req); + + if (count <= 0) + return NOTIFY_DONE; + + DRM_DEBUG_DRIVER("ATIF: %d pending SBIOS requests\n", count); + + if (req.pending & ATIF_PANEL_BRIGHTNESS_CHANGE_REQUEST) { + struct amdgpu_encoder *enc = atif->encoder_for_bl; + + if (enc) { + struct amdgpu_encoder_atom_dig *dig = enc->enc_priv; + + DRM_DEBUG_DRIVER("Changing brightness to %d\n", + req.backlight_level); + + amdgpu_display_backlight_set_level(adev, enc, req.backlight_level); + +#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) + backlight_force_update(dig->bl_dev, + BACKLIGHT_UPDATE_HOTKEY); +#endif + } + } + /* TODO: check other events */ + + /* We've handled the event, stop the notifier chain. The ACPI interface + * overloads ACPI_VIDEO_NOTIFY_PROBE, we don't want to send that to + * userspace if the event was generated only to signal a SBIOS + * request. + */ + return NOTIFY_BAD; +} + +/* Call the ATCS method + */ +/** + * amdgpu_atcs_call - call an ATCS method + * + * @handle: acpi handle + * @function: the ATCS function to execute + * @params: ATCS function params + * + * Executes the requested ATCS function (all asics). + * Returns a pointer to the acpi output buffer. + */ +static union acpi_object *amdgpu_atcs_call(acpi_handle handle, int function, + struct acpi_buffer *params) +{ + acpi_status status; + union acpi_object atcs_arg_elements[2]; + struct acpi_object_list atcs_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + + atcs_arg.count = 2; + atcs_arg.pointer = &atcs_arg_elements[0]; + + atcs_arg_elements[0].type = ACPI_TYPE_INTEGER; + atcs_arg_elements[0].integer.value = function; + + if (params) { + atcs_arg_elements[1].type = ACPI_TYPE_BUFFER; + atcs_arg_elements[1].buffer.length = params->length; + atcs_arg_elements[1].buffer.pointer = params->pointer; + } else { + /* We need a second fake parameter */ + atcs_arg_elements[1].type = ACPI_TYPE_INTEGER; + atcs_arg_elements[1].integer.value = 0; + } + + status = acpi_evaluate_object(handle, "ATCS", &atcs_arg, &buffer); + + /* Fail only if calling the method fails and ATIF is supported */ + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { + DRM_DEBUG_DRIVER("failed to evaluate ATCS got %s\n", + acpi_format_exception(status)); + kfree(buffer.pointer); + return NULL; + } + + return buffer.pointer; +} + +/** + * amdgpu_atcs_parse_functions - parse supported functions + * + * @f: supported functions struct + * @mask: supported functions mask from ATCS + * + * Use the supported functions mask from ATCS function + * ATCS_FUNCTION_VERIFY_INTERFACE to determine what functions + * are supported (all asics). + */ +static void amdgpu_atcs_parse_functions(struct amdgpu_atcs_functions *f, u32 mask) +{ + f->get_ext_state = mask & ATCS_GET_EXTERNAL_STATE_SUPPORTED; + f->pcie_perf_req = mask & ATCS_PCIE_PERFORMANCE_REQUEST_SUPPORTED; + f->pcie_dev_rdy = mask & ATCS_PCIE_DEVICE_READY_NOTIFICATION_SUPPORTED; + f->pcie_bus_width = mask & ATCS_SET_PCIE_BUS_WIDTH_SUPPORTED; +} + +/** + * amdgpu_atcs_verify_interface - verify ATCS + * + * @handle: acpi handle + * @atcs: amdgpu atcs struct + * + * Execute the ATCS_FUNCTION_VERIFY_INTERFACE ATCS function + * to initialize ATCS and determine what features are supported + * (all asics). + * returns 0 on success, error on failure. + */ +static int amdgpu_atcs_verify_interface(acpi_handle handle, + struct amdgpu_atcs *atcs) +{ + union acpi_object *info; + struct atcs_verify_interface output; + size_t size; + int err = 0; + + info = amdgpu_atcs_call(handle, ATCS_FUNCTION_VERIFY_INTERFACE, NULL); + if (!info) + return -EIO; + + memset(&output, 0, sizeof(output)); + + size = *(u16 *) info->buffer.pointer; + if (size < 8) { + DRM_INFO("ATCS buffer is too small: %zu\n", size); + err = -EINVAL; + goto out; + } + size = min(sizeof(output), size); + + memcpy(&output, info->buffer.pointer, size); + + /* TODO: check version? */ + DRM_DEBUG_DRIVER("ATCS version %u\n", output.version); + + amdgpu_atcs_parse_functions(&atcs->functions, output.function_bits); + +out: + kfree(info); + return err; +} + +/** + * amdgpu_acpi_is_pcie_performance_request_supported + * + * @adev: amdgpu_device pointer + * + * Check if the ATCS pcie_perf_req and pcie_dev_rdy methods + * are supported (all asics). + * returns true if supported, false if not. + */ +bool amdgpu_acpi_is_pcie_performance_request_supported(struct amdgpu_device *adev) +{ + struct amdgpu_atcs *atcs = &adev->atcs; + + if (atcs->functions.pcie_perf_req && atcs->functions.pcie_dev_rdy) + return true; + + return false; +} + +/** + * amdgpu_acpi_pcie_notify_device_ready + * + * @adev: amdgpu_device pointer + * + * Executes the PCIE_DEVICE_READY_NOTIFICATION method + * (all asics). + * returns 0 on success, error on failure. + */ +int amdgpu_acpi_pcie_notify_device_ready(struct amdgpu_device *adev) +{ + acpi_handle handle; + union acpi_object *info; + struct amdgpu_atcs *atcs = &adev->atcs; + + /* Get the device handle */ + handle = ACPI_HANDLE(&adev->pdev->dev); + if (!handle) + return -EINVAL; + + if (!atcs->functions.pcie_dev_rdy) + return -EINVAL; + + info = amdgpu_atcs_call(handle, ATCS_FUNCTION_PCIE_DEVICE_READY_NOTIFICATION, NULL); + if (!info) + return -EIO; + + kfree(info); + + return 0; +} + +/** + * amdgpu_acpi_pcie_performance_request + * + * @adev: amdgpu_device pointer + * @perf_req: requested perf level (pcie gen speed) + * @advertise: set advertise caps flag if set + * + * Executes the PCIE_PERFORMANCE_REQUEST method to + * change the pcie gen speed (all asics). + * returns 0 on success, error on failure. + */ +int amdgpu_acpi_pcie_performance_request(struct amdgpu_device *adev, + u8 perf_req, bool advertise) +{ + acpi_handle handle; + union acpi_object *info; + struct amdgpu_atcs *atcs = &adev->atcs; + struct atcs_pref_req_input atcs_input; + struct atcs_pref_req_output atcs_output; + struct acpi_buffer params; + size_t size; + u32 retry = 3; + + /* Get the device handle */ + handle = ACPI_HANDLE(&adev->pdev->dev); + if (!handle) + return -EINVAL; + + if (!atcs->functions.pcie_perf_req) + return -EINVAL; + + atcs_input.size = sizeof(struct atcs_pref_req_input); + /* client id (bit 2-0: func num, 7-3: dev num, 15-8: bus num) */ + atcs_input.client_id = adev->pdev->devfn | (adev->pdev->bus->number << 8); + atcs_input.valid_flags_mask = ATCS_VALID_FLAGS_MASK; + atcs_input.flags = ATCS_WAIT_FOR_COMPLETION; + if (advertise) + atcs_input.flags |= ATCS_ADVERTISE_CAPS; + atcs_input.req_type = ATCS_PCIE_LINK_SPEED; + atcs_input.perf_req = perf_req; + + params.length = sizeof(struct atcs_pref_req_input); + params.pointer = &atcs_input; + + while (retry--) { + info = amdgpu_atcs_call(handle, ATCS_FUNCTION_PCIE_PERFORMANCE_REQUEST, ¶ms); + if (!info) + return -EIO; + + memset(&atcs_output, 0, sizeof(atcs_output)); + + size = *(u16 *) info->buffer.pointer; + if (size < 3) { + DRM_INFO("ATCS buffer is too small: %zu\n", size); + kfree(info); + return -EINVAL; + } + size = min(sizeof(atcs_output), size); + + memcpy(&atcs_output, info->buffer.pointer, size); + + kfree(info); + + switch (atcs_output.ret_val) { + case ATCS_REQUEST_REFUSED: + default: + return -EINVAL; + case ATCS_REQUEST_COMPLETE: + return 0; + case ATCS_REQUEST_IN_PROGRESS: + udelay(10); + break; + } + } + + return 0; +} + +/** + * amdgpu_acpi_event - handle notify events + * + * @nb: notifier block + * @val: val + * @data: acpi event + * + * Calls relevant amdgpu functions in response to various + * acpi events. + * Returns NOTIFY code + */ +static int amdgpu_acpi_event(struct notifier_block *nb, + unsigned long val, + void *data) +{ + struct amdgpu_device *adev = container_of(nb, struct amdgpu_device, acpi_nb); + struct acpi_bus_event *entry = (struct acpi_bus_event *)data; + + if (strcmp(entry->device_class, ACPI_AC_CLASS) == 0) { + if (power_supply_is_system_supplied() > 0) + DRM_DEBUG_DRIVER("pm: AC\n"); + else + DRM_DEBUG_DRIVER("pm: DC\n"); + + amdgpu_pm_acpi_event_handler(adev); + } + + /* Check for pending SBIOS requests */ + return amdgpu_atif_handler(adev, entry); +} + +/* Call all ACPI methods here */ +/** + * amdgpu_acpi_init - init driver acpi support + * + * @adev: amdgpu_device pointer + * + * Verifies the AMD ACPI interfaces and registers with the acpi + * notifier chain (all asics). + * Returns 0 on success, error on failure. + */ +int amdgpu_acpi_init(struct amdgpu_device *adev) +{ + acpi_handle handle; + struct amdgpu_atif *atif = &adev->atif; + struct amdgpu_atcs *atcs = &adev->atcs; + int ret; + + /* Get the device handle */ + handle = ACPI_HANDLE(&adev->pdev->dev); + + if (!adev->bios || !handle) + return 0; + + /* Call the ATCS method */ + ret = amdgpu_atcs_verify_interface(handle, atcs); + if (ret) { + DRM_DEBUG_DRIVER("Call to ATCS verify_interface failed: %d\n", ret); + } + + /* Call the ATIF method */ + ret = amdgpu_atif_verify_interface(handle, atif); + if (ret) { + DRM_DEBUG_DRIVER("Call to ATIF verify_interface failed: %d\n", ret); + goto out; + } + + if (atif->notifications.brightness_change) { + struct drm_encoder *tmp; + + /* Find the encoder controlling the brightness */ + list_for_each_entry(tmp, &adev->ddev->mode_config.encoder_list, + head) { + struct amdgpu_encoder *enc = to_amdgpu_encoder(tmp); + + if ((enc->devices & (ATOM_DEVICE_LCD_SUPPORT)) && + enc->enc_priv) { + if (adev->is_atom_bios) { + struct amdgpu_encoder_atom_dig *dig = enc->enc_priv; + if (dig->bl_dev) { + atif->encoder_for_bl = enc; + break; + } + } + } + } + } + + if (atif->functions.sbios_requests && !atif->functions.system_params) { + /* XXX check this workraround, if sbios request function is + * present we have to see how it's configured in the system + * params + */ + atif->functions.system_params = true; + } + + if (atif->functions.system_params) { + ret = amdgpu_atif_get_notification_params(handle, + &atif->notification_cfg); + if (ret) { + DRM_DEBUG_DRIVER("Call to GET_SYSTEM_PARAMS failed: %d\n", + ret); + /* Disable notification */ + atif->notification_cfg.enabled = false; + } + } + +out: + adev->acpi_nb.notifier_call = amdgpu_acpi_event; + register_acpi_notifier(&adev->acpi_nb); + + return ret; +} + +/** + * amdgpu_acpi_fini - tear down driver acpi support + * + * @adev: amdgpu_device pointer + * + * Unregisters with the acpi notifier chain (all asics). + */ +void amdgpu_acpi_fini(struct amdgpu_device *adev) +{ + unregister_acpi_notifier(&adev->acpi_nb); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.h new file mode 100644 index 000000000000..01a29c3d7011 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.h @@ -0,0 +1,445 @@ +/* + * Copyright 2012 Advanced Micro Devices, 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 AMDGPU_ACPI_H +#define AMDGPU_ACPI_H + +struct amdgpu_device; +struct acpi_bus_event; + +int amdgpu_atif_handler(struct amdgpu_device *adev, + struct acpi_bus_event *event); + +/* AMD hw uses four ACPI control methods: + * 1. ATIF + * ARG0: (ACPI_INTEGER) function code + * ARG1: (ACPI_BUFFER) parameter buffer, 256 bytes + * OUTPUT: (ACPI_BUFFER) output buffer, 256 bytes + * ATIF provides an entry point for the gfx driver to interact with the sbios. + * The AMD ACPI notification mechanism uses Notify (VGA, 0x81) or a custom + * notification. Which notification is used as indicated by the ATIF Control + * Method GET_SYSTEM_PARAMETERS. When the driver receives Notify (VGA, 0x81) or + * a custom notification it invokes ATIF Control Method GET_SYSTEM_BIOS_REQUESTS + * to identify pending System BIOS requests and associated parameters. For + * example, if one of the pending requests is DISPLAY_SWITCH_REQUEST, the driver + * will perform display device detection and invoke ATIF Control Method + * SELECT_ACTIVE_DISPLAYS. + * + * 2. ATPX + * ARG0: (ACPI_INTEGER) function code + * ARG1: (ACPI_BUFFER) parameter buffer, 256 bytes + * OUTPUT: (ACPI_BUFFER) output buffer, 256 bytes + * ATPX methods are used on PowerXpress systems to handle mux switching and + * discrete GPU power control. + * + * 3. ATRM + * ARG0: (ACPI_INTEGER) offset of vbios rom data + * ARG1: (ACPI_BUFFER) size of the buffer to fill (up to 4K). + * OUTPUT: (ACPI_BUFFER) output buffer + * ATRM provides an interfacess to access the discrete GPU vbios image on + * PowerXpress systems with multiple GPUs. + * + * 4. ATCS + * ARG0: (ACPI_INTEGER) function code + * ARG1: (ACPI_BUFFER) parameter buffer, 256 bytes + * OUTPUT: (ACPI_BUFFER) output buffer, 256 bytes + * ATCS provides an interface to AMD chipset specific functionality. + * + */ +/* ATIF */ +#define ATIF_FUNCTION_VERIFY_INTERFACE 0x0 +/* ARG0: ATIF_FUNCTION_VERIFY_INTERFACE + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * WORD - version + * DWORD - supported notifications mask + * DWORD - supported functions bit vector + */ +/* Notifications mask */ +# define ATIF_DISPLAY_SWITCH_REQUEST_SUPPORTED (1 << 0) +# define ATIF_EXPANSION_MODE_CHANGE_REQUEST_SUPPORTED (1 << 1) +# define ATIF_THERMAL_STATE_CHANGE_REQUEST_SUPPORTED (1 << 2) +# define ATIF_FORCED_POWER_STATE_CHANGE_REQUEST_SUPPORTED (1 << 3) +# define ATIF_SYSTEM_POWER_SOURCE_CHANGE_REQUEST_SUPPORTED (1 << 4) +# define ATIF_DISPLAY_CONF_CHANGE_REQUEST_SUPPORTED (1 << 5) +# define ATIF_PX_GFX_SWITCH_REQUEST_SUPPORTED (1 << 6) +# define ATIF_PANEL_BRIGHTNESS_CHANGE_REQUEST_SUPPORTED (1 << 7) +# define ATIF_DGPU_DISPLAY_EVENT_SUPPORTED (1 << 8) +/* supported functions vector */ +# define ATIF_GET_SYSTEM_PARAMETERS_SUPPORTED (1 << 0) +# define ATIF_GET_SYSTEM_BIOS_REQUESTS_SUPPORTED (1 << 1) +# define ATIF_SELECT_ACTIVE_DISPLAYS_SUPPORTED (1 << 2) +# define ATIF_GET_LID_STATE_SUPPORTED (1 << 3) +# define ATIF_GET_TV_STANDARD_FROM_CMOS_SUPPORTED (1 << 4) +# define ATIF_SET_TV_STANDARD_IN_CMOS_SUPPORTED (1 << 5) +# define ATIF_GET_PANEL_EXPANSION_MODE_FROM_CMOS_SUPPORTED (1 << 6) +# define ATIF_SET_PANEL_EXPANSION_MODE_IN_CMOS_SUPPORTED (1 << 7) +# define ATIF_TEMPERATURE_CHANGE_NOTIFICATION_SUPPORTED (1 << 12) +# define ATIF_GET_GRAPHICS_DEVICE_TYPES_SUPPORTED (1 << 14) +#define ATIF_FUNCTION_GET_SYSTEM_PARAMETERS 0x1 +/* ARG0: ATIF_FUNCTION_GET_SYSTEM_PARAMETERS + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * DWORD - valid flags mask + * DWORD - flags + * + * OR + * + * WORD - structure size in bytes (includes size field) + * DWORD - valid flags mask + * DWORD - flags + * BYTE - notify command code + * + * flags + * bits 1:0: + * 0 - Notify(VGA, 0x81) is not used for notification + * 1 - Notify(VGA, 0x81) is used for notification + * 2 - Notify(VGA, n) is used for notification where + * n (0xd0-0xd9) is specified in notify command code. + * bit 2: + * 1 - lid changes not reported though int10 + */ +#define ATIF_FUNCTION_GET_SYSTEM_BIOS_REQUESTS 0x2 +/* ARG0: ATIF_FUNCTION_GET_SYSTEM_BIOS_REQUESTS + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * DWORD - pending sbios requests + * BYTE - panel expansion mode + * BYTE - thermal state: target gfx controller + * BYTE - thermal state: state id (0: exit state, non-0: state) + * BYTE - forced power state: target gfx controller + * BYTE - forced power state: state id + * BYTE - system power source + * BYTE - panel backlight level (0-255) + */ +/* pending sbios requests */ +# define ATIF_DISPLAY_SWITCH_REQUEST (1 << 0) +# define ATIF_EXPANSION_MODE_CHANGE_REQUEST (1 << 1) +# define ATIF_THERMAL_STATE_CHANGE_REQUEST (1 << 2) +# define ATIF_FORCED_POWER_STATE_CHANGE_REQUEST (1 << 3) +# define ATIF_SYSTEM_POWER_SOURCE_CHANGE_REQUEST (1 << 4) +# define ATIF_DISPLAY_CONF_CHANGE_REQUEST (1 << 5) +# define ATIF_PX_GFX_SWITCH_REQUEST (1 << 6) +# define ATIF_PANEL_BRIGHTNESS_CHANGE_REQUEST (1 << 7) +# define ATIF_DGPU_DISPLAY_EVENT (1 << 8) +/* panel expansion mode */ +# define ATIF_PANEL_EXPANSION_DISABLE 0 +# define ATIF_PANEL_EXPANSION_FULL 1 +# define ATIF_PANEL_EXPANSION_ASPECT 2 +/* target gfx controller */ +# define ATIF_TARGET_GFX_SINGLE 0 +# define ATIF_TARGET_GFX_PX_IGPU 1 +# define ATIF_TARGET_GFX_PX_DGPU 2 +/* system power source */ +# define ATIF_POWER_SOURCE_AC 1 +# define ATIF_POWER_SOURCE_DC 2 +# define ATIF_POWER_SOURCE_RESTRICTED_AC_1 3 +# define ATIF_POWER_SOURCE_RESTRICTED_AC_2 4 +#define ATIF_FUNCTION_SELECT_ACTIVE_DISPLAYS 0x3 +/* ARG0: ATIF_FUNCTION_SELECT_ACTIVE_DISPLAYS + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - selected displays + * WORD - connected displays + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * WORD - selected displays + */ +# define ATIF_LCD1 (1 << 0) +# define ATIF_CRT1 (1 << 1) +# define ATIF_TV (1 << 2) +# define ATIF_DFP1 (1 << 3) +# define ATIF_CRT2 (1 << 4) +# define ATIF_LCD2 (1 << 5) +# define ATIF_DFP2 (1 << 7) +# define ATIF_CV (1 << 8) +# define ATIF_DFP3 (1 << 9) +# define ATIF_DFP4 (1 << 10) +# define ATIF_DFP5 (1 << 11) +# define ATIF_DFP6 (1 << 12) +#define ATIF_FUNCTION_GET_LID_STATE 0x4 +/* ARG0: ATIF_FUNCTION_GET_LID_STATE + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * BYTE - lid state (0: open, 1: closed) + * + * GET_LID_STATE only works at boot and resume, for general lid + * status, use the kernel provided status + */ +#define ATIF_FUNCTION_GET_TV_STANDARD_FROM_CMOS 0x5 +/* ARG0: ATIF_FUNCTION_GET_TV_STANDARD_FROM_CMOS + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * BYTE - 0 + * BYTE - TV standard + */ +# define ATIF_TV_STD_NTSC 0 +# define ATIF_TV_STD_PAL 1 +# define ATIF_TV_STD_PALM 2 +# define ATIF_TV_STD_PAL60 3 +# define ATIF_TV_STD_NTSCJ 4 +# define ATIF_TV_STD_PALCN 5 +# define ATIF_TV_STD_PALN 6 +# define ATIF_TV_STD_SCART_RGB 9 +#define ATIF_FUNCTION_SET_TV_STANDARD_IN_CMOS 0x6 +/* ARG0: ATIF_FUNCTION_SET_TV_STANDARD_IN_CMOS + * ARG1: + * WORD - structure size in bytes (includes size field) + * BYTE - 0 + * BYTE - TV standard + * OUTPUT: none + */ +#define ATIF_FUNCTION_GET_PANEL_EXPANSION_MODE_FROM_CMOS 0x7 +/* ARG0: ATIF_FUNCTION_GET_PANEL_EXPANSION_MODE_FROM_CMOS + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * BYTE - panel expansion mode + */ +#define ATIF_FUNCTION_SET_PANEL_EXPANSION_MODE_IN_CMOS 0x8 +/* ARG0: ATIF_FUNCTION_SET_PANEL_EXPANSION_MODE_IN_CMOS + * ARG1: + * WORD - structure size in bytes (includes size field) + * BYTE - panel expansion mode + * OUTPUT: none + */ +#define ATIF_FUNCTION_TEMPERATURE_CHANGE_NOTIFICATION 0xD +/* ARG0: ATIF_FUNCTION_TEMPERATURE_CHANGE_NOTIFICATION + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - gfx controller id + * BYTE - current temperature (degress Celsius) + * OUTPUT: none + */ +#define ATIF_FUNCTION_GET_GRAPHICS_DEVICE_TYPES 0xF +/* ARG0: ATIF_FUNCTION_GET_GRAPHICS_DEVICE_TYPES + * ARG1: none + * OUTPUT: + * WORD - number of gfx devices + * WORD - device structure size in bytes (excludes device size field) + * DWORD - flags \ + * WORD - bus number } repeated structure + * WORD - device number / + */ +/* flags */ +# define ATIF_PX_REMOVABLE_GRAPHICS_DEVICE (1 << 0) +# define ATIF_XGP_PORT (1 << 1) +# define ATIF_VGA_ENABLED_GRAPHICS_DEVICE (1 << 2) +# define ATIF_XGP_PORT_IN_DOCK (1 << 3) + +/* ATPX */ +#define ATPX_FUNCTION_VERIFY_INTERFACE 0x0 +/* ARG0: ATPX_FUNCTION_VERIFY_INTERFACE + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * WORD - version + * DWORD - supported functions bit vector + */ +/* supported functions vector */ +# define ATPX_GET_PX_PARAMETERS_SUPPORTED (1 << 0) +# define ATPX_POWER_CONTROL_SUPPORTED (1 << 1) +# define ATPX_DISPLAY_MUX_CONTROL_SUPPORTED (1 << 2) +# define ATPX_I2C_MUX_CONTROL_SUPPORTED (1 << 3) +# define ATPX_GRAPHICS_DEVICE_SWITCH_START_NOTIFICATION_SUPPORTED (1 << 4) +# define ATPX_GRAPHICS_DEVICE_SWITCH_END_NOTIFICATION_SUPPORTED (1 << 5) +# define ATPX_GET_DISPLAY_CONNECTORS_MAPPING_SUPPORTED (1 << 7) +# define ATPX_GET_DISPLAY_DETECTION_PORTS_SUPPORTED (1 << 8) +#define ATPX_FUNCTION_GET_PX_PARAMETERS 0x1 +/* ARG0: ATPX_FUNCTION_GET_PX_PARAMETERS + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * DWORD - valid flags mask + * DWORD - flags + */ +/* flags */ +# define ATPX_LVDS_I2C_AVAILABLE_TO_BOTH_GPUS (1 << 0) +# define ATPX_CRT1_I2C_AVAILABLE_TO_BOTH_GPUS (1 << 1) +# define ATPX_DVI1_I2C_AVAILABLE_TO_BOTH_GPUS (1 << 2) +# define ATPX_CRT1_RGB_SIGNAL_MUXED (1 << 3) +# define ATPX_TV_SIGNAL_MUXED (1 << 4) +# define ATPX_DFP_SIGNAL_MUXED (1 << 5) +# define ATPX_SEPARATE_MUX_FOR_I2C (1 << 6) +# define ATPX_DYNAMIC_PX_SUPPORTED (1 << 7) +# define ATPX_ACF_NOT_SUPPORTED (1 << 8) +# define ATPX_FIXED_NOT_SUPPORTED (1 << 9) +# define ATPX_DYNAMIC_DGPU_POWER_OFF_SUPPORTED (1 << 10) +# define ATPX_DGPU_REQ_POWER_FOR_DISPLAYS (1 << 11) +#define ATPX_FUNCTION_POWER_CONTROL 0x2 +/* ARG0: ATPX_FUNCTION_POWER_CONTROL + * ARG1: + * WORD - structure size in bytes (includes size field) + * BYTE - dGPU power state (0: power off, 1: power on) + * OUTPUT: none + */ +#define ATPX_FUNCTION_DISPLAY_MUX_CONTROL 0x3 +/* ARG0: ATPX_FUNCTION_DISPLAY_MUX_CONTROL + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - display mux control (0: iGPU, 1: dGPU) + * OUTPUT: none + */ +# define ATPX_INTEGRATED_GPU 0 +# define ATPX_DISCRETE_GPU 1 +#define ATPX_FUNCTION_I2C_MUX_CONTROL 0x4 +/* ARG0: ATPX_FUNCTION_I2C_MUX_CONTROL + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - i2c/aux/hpd mux control (0: iGPU, 1: dGPU) + * OUTPUT: none + */ +#define ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_START_NOTIFICATION 0x5 +/* ARG0: ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_START_NOTIFICATION + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - target gpu (0: iGPU, 1: dGPU) + * OUTPUT: none + */ +#define ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_END_NOTIFICATION 0x6 +/* ARG0: ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_END_NOTIFICATION + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - target gpu (0: iGPU, 1: dGPU) + * OUTPUT: none + */ +#define ATPX_FUNCTION_GET_DISPLAY_CONNECTORS_MAPPING 0x8 +/* ARG0: ATPX_FUNCTION_GET_DISPLAY_CONNECTORS_MAPPING + * ARG1: none + * OUTPUT: + * WORD - number of display connectors + * WORD - connector structure size in bytes (excludes connector size field) + * BYTE - flags \ + * BYTE - ATIF display vector bit position } repeated + * BYTE - adapter id (0: iGPU, 1-n: dGPU ordered by pcie bus number) } structure + * WORD - connector ACPI id / + */ +/* flags */ +# define ATPX_DISPLAY_OUTPUT_SUPPORTED_BY_ADAPTER_ID_DEVICE (1 << 0) +# define ATPX_DISPLAY_HPD_SUPPORTED_BY_ADAPTER_ID_DEVICE (1 << 1) +# define ATPX_DISPLAY_I2C_SUPPORTED_BY_ADAPTER_ID_DEVICE (1 << 2) +#define ATPX_FUNCTION_GET_DISPLAY_DETECTION_PORTS 0x9 +/* ARG0: ATPX_FUNCTION_GET_DISPLAY_DETECTION_PORTS + * ARG1: none + * OUTPUT: + * WORD - number of HPD/DDC ports + * WORD - port structure size in bytes (excludes port size field) + * BYTE - ATIF display vector bit position \ + * BYTE - hpd id } reapeated structure + * BYTE - ddc id / + * + * available on A+A systems only + */ +/* hpd id */ +# define ATPX_HPD_NONE 0 +# define ATPX_HPD1 1 +# define ATPX_HPD2 2 +# define ATPX_HPD3 3 +# define ATPX_HPD4 4 +# define ATPX_HPD5 5 +# define ATPX_HPD6 6 +/* ddc id */ +# define ATPX_DDC_NONE 0 +# define ATPX_DDC1 1 +# define ATPX_DDC2 2 +# define ATPX_DDC3 3 +# define ATPX_DDC4 4 +# define ATPX_DDC5 5 +# define ATPX_DDC6 6 +# define ATPX_DDC7 7 +# define ATPX_DDC8 8 + +/* ATCS */ +#define ATCS_FUNCTION_VERIFY_INTERFACE 0x0 +/* ARG0: ATCS_FUNCTION_VERIFY_INTERFACE + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * WORD - version + * DWORD - supported functions bit vector + */ +/* supported functions vector */ +# define ATCS_GET_EXTERNAL_STATE_SUPPORTED (1 << 0) +# define ATCS_PCIE_PERFORMANCE_REQUEST_SUPPORTED (1 << 1) +# define ATCS_PCIE_DEVICE_READY_NOTIFICATION_SUPPORTED (1 << 2) +# define ATCS_SET_PCIE_BUS_WIDTH_SUPPORTED (1 << 3) +#define ATCS_FUNCTION_GET_EXTERNAL_STATE 0x1 +/* ARG0: ATCS_FUNCTION_GET_EXTERNAL_STATE + * ARG1: none + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * DWORD - valid flags mask + * DWORD - flags (0: undocked, 1: docked) + */ +/* flags */ +# define ATCS_DOCKED (1 << 0) +#define ATCS_FUNCTION_PCIE_PERFORMANCE_REQUEST 0x2 +/* ARG0: ATCS_FUNCTION_PCIE_PERFORMANCE_REQUEST + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - client id (bit 2-0: func num, 7-3: dev num, 15-8: bus num) + * WORD - valid flags mask + * WORD - flags + * BYTE - request type + * BYTE - performance request + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * BYTE - return value + */ +/* flags */ +# define ATCS_ADVERTISE_CAPS (1 << 0) +# define ATCS_WAIT_FOR_COMPLETION (1 << 1) +/* request type */ +# define ATCS_PCIE_LINK_SPEED 1 +/* performance request */ +# define ATCS_REMOVE 0 +# define ATCS_FORCE_LOW_POWER 1 +# define ATCS_PERF_LEVEL_1 2 /* PCIE Gen 1 */ +# define ATCS_PERF_LEVEL_2 3 /* PCIE Gen 2 */ +# define ATCS_PERF_LEVEL_3 4 /* PCIE Gen 3 */ +/* return value */ +# define ATCS_REQUEST_REFUSED 1 +# define ATCS_REQUEST_COMPLETE 2 +# define ATCS_REQUEST_IN_PROGRESS 3 +#define ATCS_FUNCTION_PCIE_DEVICE_READY_NOTIFICATION 0x3 +/* ARG0: ATCS_FUNCTION_PCIE_DEVICE_READY_NOTIFICATION + * ARG1: none + * OUTPUT: none + */ +#define ATCS_FUNCTION_SET_PCIE_BUS_WIDTH 0x4 +/* ARG0: ATCS_FUNCTION_SET_PCIE_BUS_WIDTH + * ARG1: + * WORD - structure size in bytes (includes size field) + * WORD - client id (bit 2-0: func num, 7-3: dev num, 15-8: bus num) + * BYTE - number of active lanes + * OUTPUT: + * WORD - structure size in bytes (includes size field) + * BYTE - number of active lanes + */ + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_afmt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_afmt.c new file mode 100644 index 000000000000..857ba0897159 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_afmt.c @@ -0,0 +1,105 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Christian König. + * + * 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: Christian König + */ +#include <linux/hdmi.h> +#include <linux/gcd.h> +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +static const struct amdgpu_afmt_acr amdgpu_afmt_predefined_acr[] = { + /* 32kHz 44.1kHz 48kHz */ + /* Clock N CTS N CTS N CTS */ + { 25175, 4096, 25175, 28224, 125875, 6144, 25175 }, /* 25,20/1.001 MHz */ + { 25200, 4096, 25200, 6272, 28000, 6144, 25200 }, /* 25.20 MHz */ + { 27000, 4096, 27000, 6272, 30000, 6144, 27000 }, /* 27.00 MHz */ + { 27027, 4096, 27027, 6272, 30030, 6144, 27027 }, /* 27.00*1.001 MHz */ + { 54000, 4096, 54000, 6272, 60000, 6144, 54000 }, /* 54.00 MHz */ + { 54054, 4096, 54054, 6272, 60060, 6144, 54054 }, /* 54.00*1.001 MHz */ + { 74176, 4096, 74176, 5733, 75335, 6144, 74176 }, /* 74.25/1.001 MHz */ + { 74250, 4096, 74250, 6272, 82500, 6144, 74250 }, /* 74.25 MHz */ + { 148352, 4096, 148352, 5733, 150670, 6144, 148352 }, /* 148.50/1.001 MHz */ + { 148500, 4096, 148500, 6272, 165000, 6144, 148500 }, /* 148.50 MHz */ +}; + + +/* + * calculate CTS and N values if they are not found in the table + */ +static void amdgpu_afmt_calc_cts(uint32_t clock, int *CTS, int *N, int freq) +{ + int n, cts; + unsigned long div, mul; + + /* Safe, but overly large values */ + n = 128 * freq; + cts = clock * 1000; + + /* Smallest valid fraction */ + div = gcd(n, cts); + + n /= div; + cts /= div; + + /* + * The optimal N is 128*freq/1000. Calculate the closest larger + * value that doesn't truncate any bits. + */ + mul = ((128*freq/1000) + (n-1))/n; + + n *= mul; + cts *= mul; + + /* Check that we are in spec (not always possible) */ + if (n < (128*freq/1500)) + printk(KERN_WARNING "Calculated ACR N value is too small. You may experience audio problems.\n"); + if (n > (128*freq/300)) + printk(KERN_WARNING "Calculated ACR N value is too large. You may experience audio problems.\n"); + + *N = n; + *CTS = cts; + + DRM_DEBUG("Calculated ACR timing N=%d CTS=%d for frequency %d\n", + *N, *CTS, freq); +} + +struct amdgpu_afmt_acr amdgpu_afmt_acr(uint32_t clock) +{ + struct amdgpu_afmt_acr res; + u8 i; + + /* Precalculated values for common clocks */ + for (i = 0; i < ARRAY_SIZE(amdgpu_afmt_predefined_acr); i++) { + if (amdgpu_afmt_predefined_acr[i].clock == clock) + return amdgpu_afmt_predefined_acr[i]; + } + + /* And odd clocks get manually calculated */ + amdgpu_afmt_calc_cts(clock, &res.cts_32khz, &res.n_32khz, 32000); + amdgpu_afmt_calc_cts(clock, &res.cts_44_1khz, &res.n_44_1khz, 44100); + amdgpu_afmt_calc_cts(clock, &res.cts_48khz, &res.n_48khz, 48000); + + return res; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c new file mode 100644 index 000000000000..6a588371d54a --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.c @@ -0,0 +1,1598 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_atombios.h" +#include "amdgpu_i2c.h" + +#include "atom.h" +#include "atom-bits.h" +#include "atombios_encoders.h" +#include "bif/bif_4_1_d.h" + +static void amdgpu_atombios_lookup_i2c_gpio_quirks(struct amdgpu_device *adev, + ATOM_GPIO_I2C_ASSIGMENT *gpio, + u8 index) +{ + +} + +static struct amdgpu_i2c_bus_rec amdgpu_atombios_get_bus_rec_for_i2c_gpio(ATOM_GPIO_I2C_ASSIGMENT *gpio) +{ + struct amdgpu_i2c_bus_rec i2c; + + memset(&i2c, 0, sizeof(struct amdgpu_i2c_bus_rec)); + + i2c.mask_clk_reg = le16_to_cpu(gpio->usClkMaskRegisterIndex); + i2c.mask_data_reg = le16_to_cpu(gpio->usDataMaskRegisterIndex); + i2c.en_clk_reg = le16_to_cpu(gpio->usClkEnRegisterIndex); + i2c.en_data_reg = le16_to_cpu(gpio->usDataEnRegisterIndex); + i2c.y_clk_reg = le16_to_cpu(gpio->usClkY_RegisterIndex); + i2c.y_data_reg = le16_to_cpu(gpio->usDataY_RegisterIndex); + i2c.a_clk_reg = le16_to_cpu(gpio->usClkA_RegisterIndex); + i2c.a_data_reg = le16_to_cpu(gpio->usDataA_RegisterIndex); + i2c.mask_clk_mask = (1 << gpio->ucClkMaskShift); + i2c.mask_data_mask = (1 << gpio->ucDataMaskShift); + i2c.en_clk_mask = (1 << gpio->ucClkEnShift); + i2c.en_data_mask = (1 << gpio->ucDataEnShift); + i2c.y_clk_mask = (1 << gpio->ucClkY_Shift); + i2c.y_data_mask = (1 << gpio->ucDataY_Shift); + i2c.a_clk_mask = (1 << gpio->ucClkA_Shift); + i2c.a_data_mask = (1 << gpio->ucDataA_Shift); + + if (gpio->sucI2cId.sbfAccess.bfHW_Capable) + i2c.hw_capable = true; + else + i2c.hw_capable = false; + + if (gpio->sucI2cId.ucAccess == 0xa0) + i2c.mm_i2c = true; + else + i2c.mm_i2c = false; + + i2c.i2c_id = gpio->sucI2cId.ucAccess; + + if (i2c.mask_clk_reg) + i2c.valid = true; + else + i2c.valid = false; + + return i2c; +} + +struct amdgpu_i2c_bus_rec amdgpu_atombios_lookup_i2c_gpio(struct amdgpu_device *adev, + uint8_t id) +{ + struct atom_context *ctx = adev->mode_info.atom_context; + ATOM_GPIO_I2C_ASSIGMENT *gpio; + struct amdgpu_i2c_bus_rec i2c; + int index = GetIndexIntoMasterTable(DATA, GPIO_I2C_Info); + struct _ATOM_GPIO_I2C_INFO *i2c_info; + uint16_t data_offset, size; + int i, num_indices; + + memset(&i2c, 0, sizeof(struct amdgpu_i2c_bus_rec)); + i2c.valid = false; + + if (amdgpu_atom_parse_data_header(ctx, index, &size, NULL, NULL, &data_offset)) { + i2c_info = (struct _ATOM_GPIO_I2C_INFO *)(ctx->bios + data_offset); + + num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / + sizeof(ATOM_GPIO_I2C_ASSIGMENT); + + gpio = &i2c_info->asGPIO_Info[0]; + for (i = 0; i < num_indices; i++) { + + amdgpu_atombios_lookup_i2c_gpio_quirks(adev, gpio, i); + + if (gpio->sucI2cId.ucAccess == id) { + i2c = amdgpu_atombios_get_bus_rec_for_i2c_gpio(gpio); + break; + } + gpio = (ATOM_GPIO_I2C_ASSIGMENT *) + ((u8 *)gpio + sizeof(ATOM_GPIO_I2C_ASSIGMENT)); + } + } + + return i2c; +} + +void amdgpu_atombios_i2c_init(struct amdgpu_device *adev) +{ + struct atom_context *ctx = adev->mode_info.atom_context; + ATOM_GPIO_I2C_ASSIGMENT *gpio; + struct amdgpu_i2c_bus_rec i2c; + int index = GetIndexIntoMasterTable(DATA, GPIO_I2C_Info); + struct _ATOM_GPIO_I2C_INFO *i2c_info; + uint16_t data_offset, size; + int i, num_indices; + char stmp[32]; + + if (amdgpu_atom_parse_data_header(ctx, index, &size, NULL, NULL, &data_offset)) { + i2c_info = (struct _ATOM_GPIO_I2C_INFO *)(ctx->bios + data_offset); + + num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / + sizeof(ATOM_GPIO_I2C_ASSIGMENT); + + gpio = &i2c_info->asGPIO_Info[0]; + for (i = 0; i < num_indices; i++) { + amdgpu_atombios_lookup_i2c_gpio_quirks(adev, gpio, i); + + i2c = amdgpu_atombios_get_bus_rec_for_i2c_gpio(gpio); + + if (i2c.valid) { + sprintf(stmp, "0x%x", i2c.i2c_id); + adev->i2c_bus[i] = amdgpu_i2c_create(adev->ddev, &i2c, stmp); + } + gpio = (ATOM_GPIO_I2C_ASSIGMENT *) + ((u8 *)gpio + sizeof(ATOM_GPIO_I2C_ASSIGMENT)); + } + } +} + +struct amdgpu_gpio_rec +amdgpu_atombios_lookup_gpio(struct amdgpu_device *adev, + u8 id) +{ + struct atom_context *ctx = adev->mode_info.atom_context; + struct amdgpu_gpio_rec gpio; + int index = GetIndexIntoMasterTable(DATA, GPIO_Pin_LUT); + struct _ATOM_GPIO_PIN_LUT *gpio_info; + ATOM_GPIO_PIN_ASSIGNMENT *pin; + u16 data_offset, size; + int i, num_indices; + + memset(&gpio, 0, sizeof(struct amdgpu_gpio_rec)); + gpio.valid = false; + + if (amdgpu_atom_parse_data_header(ctx, index, &size, NULL, NULL, &data_offset)) { + gpio_info = (struct _ATOM_GPIO_PIN_LUT *)(ctx->bios + data_offset); + + num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / + sizeof(ATOM_GPIO_PIN_ASSIGNMENT); + + pin = gpio_info->asGPIO_Pin; + for (i = 0; i < num_indices; i++) { + if (id == pin->ucGPIO_ID) { + gpio.id = pin->ucGPIO_ID; + gpio.reg = le16_to_cpu(pin->usGpioPin_AIndex); + gpio.shift = pin->ucGpioPinBitShift; + gpio.mask = (1 << pin->ucGpioPinBitShift); + gpio.valid = true; + break; + } + pin = (ATOM_GPIO_PIN_ASSIGNMENT *) + ((u8 *)pin + sizeof(ATOM_GPIO_PIN_ASSIGNMENT)); + } + } + + return gpio; +} + +static struct amdgpu_hpd +amdgpu_atombios_get_hpd_info_from_gpio(struct amdgpu_device *adev, + struct amdgpu_gpio_rec *gpio) +{ + struct amdgpu_hpd hpd; + u32 reg; + + memset(&hpd, 0, sizeof(struct amdgpu_hpd)); + + reg = amdgpu_display_hpd_get_gpio_reg(adev); + + hpd.gpio = *gpio; + if (gpio->reg == reg) { + switch(gpio->mask) { + case (1 << 0): + hpd.hpd = AMDGPU_HPD_1; + break; + case (1 << 8): + hpd.hpd = AMDGPU_HPD_2; + break; + case (1 << 16): + hpd.hpd = AMDGPU_HPD_3; + break; + case (1 << 24): + hpd.hpd = AMDGPU_HPD_4; + break; + case (1 << 26): + hpd.hpd = AMDGPU_HPD_5; + break; + case (1 << 28): + hpd.hpd = AMDGPU_HPD_6; + break; + default: + hpd.hpd = AMDGPU_HPD_NONE; + break; + } + } else + hpd.hpd = AMDGPU_HPD_NONE; + return hpd; +} + +static bool amdgpu_atombios_apply_quirks(struct amdgpu_device *adev, + uint32_t supported_device, + int *connector_type, + struct amdgpu_i2c_bus_rec *i2c_bus, + uint16_t *line_mux, + struct amdgpu_hpd *hpd) +{ + return true; +} + +static const int object_connector_convert[] = { + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_DVII, + DRM_MODE_CONNECTOR_DVII, + DRM_MODE_CONNECTOR_DVID, + DRM_MODE_CONNECTOR_DVID, + DRM_MODE_CONNECTOR_VGA, + DRM_MODE_CONNECTOR_Composite, + DRM_MODE_CONNECTOR_SVIDEO, + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_9PinDIN, + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_HDMIA, + DRM_MODE_CONNECTOR_HDMIB, + DRM_MODE_CONNECTOR_LVDS, + DRM_MODE_CONNECTOR_9PinDIN, + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_Unknown, + DRM_MODE_CONNECTOR_DisplayPort, + DRM_MODE_CONNECTOR_eDP, + DRM_MODE_CONNECTOR_Unknown +}; + +bool amdgpu_atombios_get_connector_info_from_object_table(struct amdgpu_device *adev) +{ + struct amdgpu_mode_info *mode_info = &adev->mode_info; + struct atom_context *ctx = mode_info->atom_context; + int index = GetIndexIntoMasterTable(DATA, Object_Header); + u16 size, data_offset; + u8 frev, crev; + ATOM_CONNECTOR_OBJECT_TABLE *con_obj; + ATOM_ENCODER_OBJECT_TABLE *enc_obj; + ATOM_OBJECT_TABLE *router_obj; + ATOM_DISPLAY_OBJECT_PATH_TABLE *path_obj; + ATOM_OBJECT_HEADER *obj_header; + int i, j, k, path_size, device_support; + int connector_type; + u16 conn_id, connector_object_id; + struct amdgpu_i2c_bus_rec ddc_bus; + struct amdgpu_router router; + struct amdgpu_gpio_rec gpio; + struct amdgpu_hpd hpd; + + if (!amdgpu_atom_parse_data_header(ctx, index, &size, &frev, &crev, &data_offset)) + return false; + + if (crev < 2) + return false; + + obj_header = (ATOM_OBJECT_HEADER *) (ctx->bios + data_offset); + path_obj = (ATOM_DISPLAY_OBJECT_PATH_TABLE *) + (ctx->bios + data_offset + + le16_to_cpu(obj_header->usDisplayPathTableOffset)); + con_obj = (ATOM_CONNECTOR_OBJECT_TABLE *) + (ctx->bios + data_offset + + le16_to_cpu(obj_header->usConnectorObjectTableOffset)); + enc_obj = (ATOM_ENCODER_OBJECT_TABLE *) + (ctx->bios + data_offset + + le16_to_cpu(obj_header->usEncoderObjectTableOffset)); + router_obj = (ATOM_OBJECT_TABLE *) + (ctx->bios + data_offset + + le16_to_cpu(obj_header->usRouterObjectTableOffset)); + device_support = le16_to_cpu(obj_header->usDeviceSupport); + + path_size = 0; + for (i = 0; i < path_obj->ucNumOfDispPath; i++) { + uint8_t *addr = (uint8_t *) path_obj->asDispPath; + ATOM_DISPLAY_OBJECT_PATH *path; + addr += path_size; + path = (ATOM_DISPLAY_OBJECT_PATH *) addr; + path_size += le16_to_cpu(path->usSize); + + if (device_support & le16_to_cpu(path->usDeviceTag)) { + uint8_t con_obj_id, con_obj_num, con_obj_type; + + con_obj_id = + (le16_to_cpu(path->usConnObjectId) & OBJECT_ID_MASK) + >> OBJECT_ID_SHIFT; + con_obj_num = + (le16_to_cpu(path->usConnObjectId) & ENUM_ID_MASK) + >> ENUM_ID_SHIFT; + con_obj_type = + (le16_to_cpu(path->usConnObjectId) & + OBJECT_TYPE_MASK) >> OBJECT_TYPE_SHIFT; + + connector_type = + object_connector_convert[con_obj_id]; + connector_object_id = con_obj_id; + + if (connector_type == DRM_MODE_CONNECTOR_Unknown) + continue; + + router.ddc_valid = false; + router.cd_valid = false; + for (j = 0; j < ((le16_to_cpu(path->usSize) - 8) / 2); j++) { + uint8_t grph_obj_id, grph_obj_num, grph_obj_type; + + grph_obj_id = + (le16_to_cpu(path->usGraphicObjIds[j]) & + OBJECT_ID_MASK) >> OBJECT_ID_SHIFT; + grph_obj_num = + (le16_to_cpu(path->usGraphicObjIds[j]) & + ENUM_ID_MASK) >> ENUM_ID_SHIFT; + grph_obj_type = + (le16_to_cpu(path->usGraphicObjIds[j]) & + OBJECT_TYPE_MASK) >> OBJECT_TYPE_SHIFT; + + if (grph_obj_type == GRAPH_OBJECT_TYPE_ENCODER) { + for (k = 0; k < enc_obj->ucNumberOfObjects; k++) { + u16 encoder_obj = le16_to_cpu(enc_obj->asObjects[k].usObjectID); + if (le16_to_cpu(path->usGraphicObjIds[j]) == encoder_obj) { + ATOM_COMMON_RECORD_HEADER *record = (ATOM_COMMON_RECORD_HEADER *) + (ctx->bios + data_offset + + le16_to_cpu(enc_obj->asObjects[k].usRecordOffset)); + ATOM_ENCODER_CAP_RECORD *cap_record; + u16 caps = 0; + + while (record->ucRecordSize > 0 && + record->ucRecordType > 0 && + record->ucRecordType <= ATOM_MAX_OBJECT_RECORD_NUMBER) { + switch (record->ucRecordType) { + case ATOM_ENCODER_CAP_RECORD_TYPE: + cap_record =(ATOM_ENCODER_CAP_RECORD *) + record; + caps = le16_to_cpu(cap_record->usEncoderCap); + break; + } + record = (ATOM_COMMON_RECORD_HEADER *) + ((char *)record + record->ucRecordSize); + } + amdgpu_display_add_encoder(adev, encoder_obj, + le16_to_cpu(path->usDeviceTag), + caps); + } + } + } else if (grph_obj_type == GRAPH_OBJECT_TYPE_ROUTER) { + for (k = 0; k < router_obj->ucNumberOfObjects; k++) { + u16 router_obj_id = le16_to_cpu(router_obj->asObjects[k].usObjectID); + if (le16_to_cpu(path->usGraphicObjIds[j]) == router_obj_id) { + ATOM_COMMON_RECORD_HEADER *record = (ATOM_COMMON_RECORD_HEADER *) + (ctx->bios + data_offset + + le16_to_cpu(router_obj->asObjects[k].usRecordOffset)); + ATOM_I2C_RECORD *i2c_record; + ATOM_I2C_ID_CONFIG_ACCESS *i2c_config; + ATOM_ROUTER_DDC_PATH_SELECT_RECORD *ddc_path; + ATOM_ROUTER_DATA_CLOCK_PATH_SELECT_RECORD *cd_path; + ATOM_SRC_DST_TABLE_FOR_ONE_OBJECT *router_src_dst_table = + (ATOM_SRC_DST_TABLE_FOR_ONE_OBJECT *) + (ctx->bios + data_offset + + le16_to_cpu(router_obj->asObjects[k].usSrcDstTableOffset)); + u8 *num_dst_objs = (u8 *) + ((u8 *)router_src_dst_table + 1 + + (router_src_dst_table->ucNumberOfSrc * 2)); + u16 *dst_objs = (u16 *)(num_dst_objs + 1); + int enum_id; + + router.router_id = router_obj_id; + for (enum_id = 0; enum_id < (*num_dst_objs); enum_id++) { + if (le16_to_cpu(path->usConnObjectId) == + le16_to_cpu(dst_objs[enum_id])) + break; + } + + while (record->ucRecordSize > 0 && + record->ucRecordType > 0 && + record->ucRecordType <= ATOM_MAX_OBJECT_RECORD_NUMBER) { + switch (record->ucRecordType) { + case ATOM_I2C_RECORD_TYPE: + i2c_record = + (ATOM_I2C_RECORD *) + record; + i2c_config = + (ATOM_I2C_ID_CONFIG_ACCESS *) + &i2c_record->sucI2cId; + router.i2c_info = + amdgpu_atombios_lookup_i2c_gpio(adev, + i2c_config-> + ucAccess); + router.i2c_addr = i2c_record->ucI2CAddr >> 1; + break; + case ATOM_ROUTER_DDC_PATH_SELECT_RECORD_TYPE: + ddc_path = (ATOM_ROUTER_DDC_PATH_SELECT_RECORD *) + record; + router.ddc_valid = true; + router.ddc_mux_type = ddc_path->ucMuxType; + router.ddc_mux_control_pin = ddc_path->ucMuxControlPin; + router.ddc_mux_state = ddc_path->ucMuxState[enum_id]; + break; + case ATOM_ROUTER_DATA_CLOCK_PATH_SELECT_RECORD_TYPE: + cd_path = (ATOM_ROUTER_DATA_CLOCK_PATH_SELECT_RECORD *) + record; + router.cd_valid = true; + router.cd_mux_type = cd_path->ucMuxType; + router.cd_mux_control_pin = cd_path->ucMuxControlPin; + router.cd_mux_state = cd_path->ucMuxState[enum_id]; + break; + } + record = (ATOM_COMMON_RECORD_HEADER *) + ((char *)record + record->ucRecordSize); + } + } + } + } + } + + /* look up gpio for ddc, hpd */ + ddc_bus.valid = false; + hpd.hpd = AMDGPU_HPD_NONE; + if ((le16_to_cpu(path->usDeviceTag) & + (ATOM_DEVICE_TV_SUPPORT | ATOM_DEVICE_CV_SUPPORT)) == 0) { + for (j = 0; j < con_obj->ucNumberOfObjects; j++) { + if (le16_to_cpu(path->usConnObjectId) == + le16_to_cpu(con_obj->asObjects[j]. + usObjectID)) { + ATOM_COMMON_RECORD_HEADER + *record = + (ATOM_COMMON_RECORD_HEADER + *) + (ctx->bios + data_offset + + le16_to_cpu(con_obj-> + asObjects[j]. + usRecordOffset)); + ATOM_I2C_RECORD *i2c_record; + ATOM_HPD_INT_RECORD *hpd_record; + ATOM_I2C_ID_CONFIG_ACCESS *i2c_config; + + while (record->ucRecordSize > 0 && + record->ucRecordType > 0 && + record->ucRecordType <= ATOM_MAX_OBJECT_RECORD_NUMBER) { + switch (record->ucRecordType) { + case ATOM_I2C_RECORD_TYPE: + i2c_record = + (ATOM_I2C_RECORD *) + record; + i2c_config = + (ATOM_I2C_ID_CONFIG_ACCESS *) + &i2c_record->sucI2cId; + ddc_bus = amdgpu_atombios_lookup_i2c_gpio(adev, + i2c_config-> + ucAccess); + break; + case ATOM_HPD_INT_RECORD_TYPE: + hpd_record = + (ATOM_HPD_INT_RECORD *) + record; + gpio = amdgpu_atombios_lookup_gpio(adev, + hpd_record->ucHPDIntGPIOID); + hpd = amdgpu_atombios_get_hpd_info_from_gpio(adev, &gpio); + hpd.plugged_state = hpd_record->ucPlugged_PinState; + break; + } + record = + (ATOM_COMMON_RECORD_HEADER + *) ((char *)record + + + record-> + ucRecordSize); + } + break; + } + } + } + + /* needed for aux chan transactions */ + ddc_bus.hpd = hpd.hpd; + + conn_id = le16_to_cpu(path->usConnObjectId); + + if (!amdgpu_atombios_apply_quirks + (adev, le16_to_cpu(path->usDeviceTag), &connector_type, + &ddc_bus, &conn_id, &hpd)) + continue; + + amdgpu_display_add_connector(adev, + conn_id, + le16_to_cpu(path->usDeviceTag), + connector_type, &ddc_bus, + connector_object_id, + &hpd, + &router); + + } + } + + amdgpu_link_encoder_connector(adev->ddev); + + return true; +} + +union firmware_info { + ATOM_FIRMWARE_INFO info; + ATOM_FIRMWARE_INFO_V1_2 info_12; + ATOM_FIRMWARE_INFO_V1_3 info_13; + ATOM_FIRMWARE_INFO_V1_4 info_14; + ATOM_FIRMWARE_INFO_V2_1 info_21; + ATOM_FIRMWARE_INFO_V2_2 info_22; +}; + +int amdgpu_atombios_get_clock_info(struct amdgpu_device *adev) +{ + struct amdgpu_mode_info *mode_info = &adev->mode_info; + int index = GetIndexIntoMasterTable(DATA, FirmwareInfo); + uint8_t frev, crev; + uint16_t data_offset; + int ret = -EINVAL; + + if (amdgpu_atom_parse_data_header(mode_info->atom_context, index, NULL, + &frev, &crev, &data_offset)) { + int i; + struct amdgpu_pll *ppll = &adev->clock.ppll[0]; + struct amdgpu_pll *spll = &adev->clock.spll; + struct amdgpu_pll *mpll = &adev->clock.mpll; + union firmware_info *firmware_info = + (union firmware_info *)(mode_info->atom_context->bios + + data_offset); + /* pixel clocks */ + ppll->reference_freq = + le16_to_cpu(firmware_info->info.usReferenceClock); + ppll->reference_div = 0; + + if (crev < 2) + ppll->pll_out_min = + le16_to_cpu(firmware_info->info.usMinPixelClockPLL_Output); + else + ppll->pll_out_min = + le32_to_cpu(firmware_info->info_12.ulMinPixelClockPLL_Output); + ppll->pll_out_max = + le32_to_cpu(firmware_info->info.ulMaxPixelClockPLL_Output); + + if (crev >= 4) { + ppll->lcd_pll_out_min = + le16_to_cpu(firmware_info->info_14.usLcdMinPixelClockPLL_Output) * 100; + if (ppll->lcd_pll_out_min == 0) + ppll->lcd_pll_out_min = ppll->pll_out_min; + ppll->lcd_pll_out_max = + le16_to_cpu(firmware_info->info_14.usLcdMaxPixelClockPLL_Output) * 100; + if (ppll->lcd_pll_out_max == 0) + ppll->lcd_pll_out_max = ppll->pll_out_max; + } else { + ppll->lcd_pll_out_min = ppll->pll_out_min; + ppll->lcd_pll_out_max = ppll->pll_out_max; + } + + if (ppll->pll_out_min == 0) + ppll->pll_out_min = 64800; + + ppll->pll_in_min = + le16_to_cpu(firmware_info->info.usMinPixelClockPLL_Input); + ppll->pll_in_max = + le16_to_cpu(firmware_info->info.usMaxPixelClockPLL_Input); + + ppll->min_post_div = 2; + ppll->max_post_div = 0x7f; + ppll->min_frac_feedback_div = 0; + ppll->max_frac_feedback_div = 9; + ppll->min_ref_div = 2; + ppll->max_ref_div = 0x3ff; + ppll->min_feedback_div = 4; + ppll->max_feedback_div = 0xfff; + ppll->best_vco = 0; + + for (i = 1; i < AMDGPU_MAX_PPLL; i++) + adev->clock.ppll[i] = *ppll; + + /* system clock */ + spll->reference_freq = + le16_to_cpu(firmware_info->info_21.usCoreReferenceClock); + spll->reference_div = 0; + + spll->pll_out_min = + le16_to_cpu(firmware_info->info.usMinEngineClockPLL_Output); + spll->pll_out_max = + le32_to_cpu(firmware_info->info.ulMaxEngineClockPLL_Output); + + /* ??? */ + if (spll->pll_out_min == 0) + spll->pll_out_min = 64800; + + spll->pll_in_min = + le16_to_cpu(firmware_info->info.usMinEngineClockPLL_Input); + spll->pll_in_max = + le16_to_cpu(firmware_info->info.usMaxEngineClockPLL_Input); + + spll->min_post_div = 1; + spll->max_post_div = 1; + spll->min_ref_div = 2; + spll->max_ref_div = 0xff; + spll->min_feedback_div = 4; + spll->max_feedback_div = 0xff; + spll->best_vco = 0; + + /* memory clock */ + mpll->reference_freq = + le16_to_cpu(firmware_info->info_21.usMemoryReferenceClock); + mpll->reference_div = 0; + + mpll->pll_out_min = + le16_to_cpu(firmware_info->info.usMinMemoryClockPLL_Output); + mpll->pll_out_max = + le32_to_cpu(firmware_info->info.ulMaxMemoryClockPLL_Output); + + /* ??? */ + if (mpll->pll_out_min == 0) + mpll->pll_out_min = 64800; + + mpll->pll_in_min = + le16_to_cpu(firmware_info->info.usMinMemoryClockPLL_Input); + mpll->pll_in_max = + le16_to_cpu(firmware_info->info.usMaxMemoryClockPLL_Input); + + adev->clock.default_sclk = + le32_to_cpu(firmware_info->info.ulDefaultEngineClock); + adev->clock.default_mclk = + le32_to_cpu(firmware_info->info.ulDefaultMemoryClock); + + mpll->min_post_div = 1; + mpll->max_post_div = 1; + mpll->min_ref_div = 2; + mpll->max_ref_div = 0xff; + mpll->min_feedback_div = 4; + mpll->max_feedback_div = 0xff; + mpll->best_vco = 0; + + /* disp clock */ + adev->clock.default_dispclk = + le32_to_cpu(firmware_info->info_21.ulDefaultDispEngineClkFreq); + if (adev->clock.default_dispclk == 0) + adev->clock.default_dispclk = 54000; /* 540 Mhz */ + adev->clock.dp_extclk = + le16_to_cpu(firmware_info->info_21.usUniphyDPModeExtClkFreq); + adev->clock.current_dispclk = adev->clock.default_dispclk; + + adev->clock.max_pixel_clock = le16_to_cpu(firmware_info->info.usMaxPixelClock); + if (adev->clock.max_pixel_clock == 0) + adev->clock.max_pixel_clock = 40000; + + /* not technically a clock, but... */ + adev->mode_info.firmware_flags = + le16_to_cpu(firmware_info->info.usFirmwareCapability.susAccess); + + ret = 0; + } + + adev->pm.current_sclk = adev->clock.default_sclk; + adev->pm.current_mclk = adev->clock.default_mclk; + + return ret; +} + +union igp_info { + struct _ATOM_INTEGRATED_SYSTEM_INFO info; + struct _ATOM_INTEGRATED_SYSTEM_INFO_V2 info_2; + struct _ATOM_INTEGRATED_SYSTEM_INFO_V6 info_6; + struct _ATOM_INTEGRATED_SYSTEM_INFO_V1_7 info_7; + struct _ATOM_INTEGRATED_SYSTEM_INFO_V1_8 info_8; + struct _ATOM_INTEGRATED_SYSTEM_INFO_V1_9 info_9; +}; + +static void amdgpu_atombios_get_igp_ss_overrides(struct amdgpu_device *adev, + struct amdgpu_atom_ss *ss, + int id) +{ + struct amdgpu_mode_info *mode_info = &adev->mode_info; + int index = GetIndexIntoMasterTable(DATA, IntegratedSystemInfo); + u16 data_offset, size; + union igp_info *igp_info; + u8 frev, crev; + u16 percentage = 0, rate = 0; + + /* get any igp specific overrides */ + if (amdgpu_atom_parse_data_header(mode_info->atom_context, index, &size, + &frev, &crev, &data_offset)) { + igp_info = (union igp_info *) + (mode_info->atom_context->bios + data_offset); + switch (crev) { + case 6: + switch (id) { + case ASIC_INTERNAL_SS_ON_TMDS: + percentage = le16_to_cpu(igp_info->info_6.usDVISSPercentage); + rate = le16_to_cpu(igp_info->info_6.usDVISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_HDMI: + percentage = le16_to_cpu(igp_info->info_6.usHDMISSPercentage); + rate = le16_to_cpu(igp_info->info_6.usHDMISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_LVDS: + percentage = le16_to_cpu(igp_info->info_6.usLvdsSSPercentage); + rate = le16_to_cpu(igp_info->info_6.usLvdsSSpreadRateIn10Hz); + break; + } + break; + case 7: + switch (id) { + case ASIC_INTERNAL_SS_ON_TMDS: + percentage = le16_to_cpu(igp_info->info_7.usDVISSPercentage); + rate = le16_to_cpu(igp_info->info_7.usDVISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_HDMI: + percentage = le16_to_cpu(igp_info->info_7.usHDMISSPercentage); + rate = le16_to_cpu(igp_info->info_7.usHDMISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_LVDS: + percentage = le16_to_cpu(igp_info->info_7.usLvdsSSPercentage); + rate = le16_to_cpu(igp_info->info_7.usLvdsSSpreadRateIn10Hz); + break; + } + break; + case 8: + switch (id) { + case ASIC_INTERNAL_SS_ON_TMDS: + percentage = le16_to_cpu(igp_info->info_8.usDVISSPercentage); + rate = le16_to_cpu(igp_info->info_8.usDVISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_HDMI: + percentage = le16_to_cpu(igp_info->info_8.usHDMISSPercentage); + rate = le16_to_cpu(igp_info->info_8.usHDMISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_LVDS: + percentage = le16_to_cpu(igp_info->info_8.usLvdsSSPercentage); + rate = le16_to_cpu(igp_info->info_8.usLvdsSSpreadRateIn10Hz); + break; + } + break; + case 9: + switch (id) { + case ASIC_INTERNAL_SS_ON_TMDS: + percentage = le16_to_cpu(igp_info->info_9.usDVISSPercentage); + rate = le16_to_cpu(igp_info->info_9.usDVISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_HDMI: + percentage = le16_to_cpu(igp_info->info_9.usHDMISSPercentage); + rate = le16_to_cpu(igp_info->info_9.usHDMISSpreadRateIn10Hz); + break; + case ASIC_INTERNAL_SS_ON_LVDS: + percentage = le16_to_cpu(igp_info->info_9.usLvdsSSPercentage); + rate = le16_to_cpu(igp_info->info_9.usLvdsSSpreadRateIn10Hz); + break; + } + break; + default: + DRM_ERROR("Unsupported IGP table: %d %d\n", frev, crev); + break; + } + if (percentage) + ss->percentage = percentage; + if (rate) + ss->rate = rate; + } +} + +union asic_ss_info { + struct _ATOM_ASIC_INTERNAL_SS_INFO info; + struct _ATOM_ASIC_INTERNAL_SS_INFO_V2 info_2; + struct _ATOM_ASIC_INTERNAL_SS_INFO_V3 info_3; +}; + +union asic_ss_assignment { + struct _ATOM_ASIC_SS_ASSIGNMENT v1; + struct _ATOM_ASIC_SS_ASSIGNMENT_V2 v2; + struct _ATOM_ASIC_SS_ASSIGNMENT_V3 v3; +}; + +bool amdgpu_atombios_get_asic_ss_info(struct amdgpu_device *adev, + struct amdgpu_atom_ss *ss, + int id, u32 clock) +{ + struct amdgpu_mode_info *mode_info = &adev->mode_info; + int index = GetIndexIntoMasterTable(DATA, ASIC_InternalSS_Info); + uint16_t data_offset, size; + union asic_ss_info *ss_info; + union asic_ss_assignment *ss_assign; + uint8_t frev, crev; + int i, num_indices; + + if (id == ASIC_INTERNAL_MEMORY_SS) { + if (!(adev->mode_info.firmware_flags & ATOM_BIOS_INFO_MEMORY_CLOCK_SS_SUPPORT)) + return false; + } + if (id == ASIC_INTERNAL_ENGINE_SS) { + if (!(adev->mode_info.firmware_flags & ATOM_BIOS_INFO_ENGINE_CLOCK_SS_SUPPORT)) + return false; + } + + memset(ss, 0, sizeof(struct amdgpu_atom_ss)); + if (amdgpu_atom_parse_data_header(mode_info->atom_context, index, &size, + &frev, &crev, &data_offset)) { + + ss_info = + (union asic_ss_info *)(mode_info->atom_context->bios + data_offset); + + switch (frev) { + case 1: + num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / + sizeof(ATOM_ASIC_SS_ASSIGNMENT); + + ss_assign = (union asic_ss_assignment *)((u8 *)&ss_info->info.asSpreadSpectrum[0]); + for (i = 0; i < num_indices; i++) { + if ((ss_assign->v1.ucClockIndication == id) && + (clock <= le32_to_cpu(ss_assign->v1.ulTargetClockRange))) { + ss->percentage = + le16_to_cpu(ss_assign->v1.usSpreadSpectrumPercentage); + ss->type = ss_assign->v1.ucSpreadSpectrumMode; + ss->rate = le16_to_cpu(ss_assign->v1.usSpreadRateInKhz); + ss->percentage_divider = 100; + return true; + } + ss_assign = (union asic_ss_assignment *) + ((u8 *)ss_assign + sizeof(ATOM_ASIC_SS_ASSIGNMENT)); + } + break; + case 2: + num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / + sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2); + ss_assign = (union asic_ss_assignment *)((u8 *)&ss_info->info_2.asSpreadSpectrum[0]); + for (i = 0; i < num_indices; i++) { + if ((ss_assign->v2.ucClockIndication == id) && + (clock <= le32_to_cpu(ss_assign->v2.ulTargetClockRange))) { + ss->percentage = + le16_to_cpu(ss_assign->v2.usSpreadSpectrumPercentage); + ss->type = ss_assign->v2.ucSpreadSpectrumMode; + ss->rate = le16_to_cpu(ss_assign->v2.usSpreadRateIn10Hz); + ss->percentage_divider = 100; + if ((crev == 2) && + ((id == ASIC_INTERNAL_ENGINE_SS) || + (id == ASIC_INTERNAL_MEMORY_SS))) + ss->rate /= 100; + return true; + } + ss_assign = (union asic_ss_assignment *) + ((u8 *)ss_assign + sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2)); + } + break; + case 3: + num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / + sizeof(ATOM_ASIC_SS_ASSIGNMENT_V3); + ss_assign = (union asic_ss_assignment *)((u8 *)&ss_info->info_3.asSpreadSpectrum[0]); + for (i = 0; i < num_indices; i++) { + if ((ss_assign->v3.ucClockIndication == id) && + (clock <= le32_to_cpu(ss_assign->v3.ulTargetClockRange))) { + ss->percentage = + le16_to_cpu(ss_assign->v3.usSpreadSpectrumPercentage); + ss->type = ss_assign->v3.ucSpreadSpectrumMode; + ss->rate = le16_to_cpu(ss_assign->v3.usSpreadRateIn10Hz); + if (ss_assign->v3.ucSpreadSpectrumMode & + SS_MODE_V3_PERCENTAGE_DIV_BY_1000_MASK) + ss->percentage_divider = 1000; + else + ss->percentage_divider = 100; + if ((id == ASIC_INTERNAL_ENGINE_SS) || + (id == ASIC_INTERNAL_MEMORY_SS)) + ss->rate /= 100; + if (adev->flags & AMDGPU_IS_APU) + amdgpu_atombios_get_igp_ss_overrides(adev, ss, id); + return true; + } + ss_assign = (union asic_ss_assignment *) + ((u8 *)ss_assign + sizeof(ATOM_ASIC_SS_ASSIGNMENT_V3)); + } + break; + default: + DRM_ERROR("Unsupported ASIC_InternalSS_Info table: %d %d\n", frev, crev); + break; + } + + } + return false; +} + +union get_clock_dividers { + struct _COMPUTE_MEMORY_ENGINE_PLL_PARAMETERS v1; + struct _COMPUTE_MEMORY_ENGINE_PLL_PARAMETERS_V2 v2; + struct _COMPUTE_MEMORY_ENGINE_PLL_PARAMETERS_V3 v3; + struct _COMPUTE_MEMORY_ENGINE_PLL_PARAMETERS_V4 v4; + struct _COMPUTE_MEMORY_ENGINE_PLL_PARAMETERS_V5 v5; + struct _COMPUTE_GPU_CLOCK_INPUT_PARAMETERS_V1_6 v6_in; + struct _COMPUTE_GPU_CLOCK_OUTPUT_PARAMETERS_V1_6 v6_out; +}; + +int amdgpu_atombios_get_clock_dividers(struct amdgpu_device *adev, + u8 clock_type, + u32 clock, + bool strobe_mode, + struct atom_clock_dividers *dividers) +{ + union get_clock_dividers args; + int index = GetIndexIntoMasterTable(COMMAND, ComputeMemoryEnginePLL); + u8 frev, crev; + + memset(&args, 0, sizeof(args)); + memset(dividers, 0, sizeof(struct atom_clock_dividers)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return -EINVAL; + + switch (crev) { + case 4: + /* fusion */ + args.v4.ulClock = cpu_to_le32(clock); /* 10 khz */ + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + dividers->post_divider = dividers->post_div = args.v4.ucPostDiv; + dividers->real_clock = le32_to_cpu(args.v4.ulClock); + break; + case 6: + /* CI */ + /* COMPUTE_GPUCLK_INPUT_FLAG_DEFAULT_GPUCLK, COMPUTE_GPUCLK_INPUT_FLAG_SCLK */ + args.v6_in.ulClock.ulComputeClockFlag = clock_type; + args.v6_in.ulClock.ulClockFreq = cpu_to_le32(clock); /* 10 khz */ + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + dividers->whole_fb_div = le16_to_cpu(args.v6_out.ulFbDiv.usFbDiv); + dividers->frac_fb_div = le16_to_cpu(args.v6_out.ulFbDiv.usFbDivFrac); + dividers->ref_div = args.v6_out.ucPllRefDiv; + dividers->post_div = args.v6_out.ucPllPostDiv; + dividers->flags = args.v6_out.ucPllCntlFlag; + dividers->real_clock = le32_to_cpu(args.v6_out.ulClock.ulClock); + dividers->post_divider = args.v6_out.ulClock.ucPostDiv; + break; + default: + return -EINVAL; + } + return 0; +} + +int amdgpu_atombios_get_memory_pll_dividers(struct amdgpu_device *adev, + u32 clock, + bool strobe_mode, + struct atom_mpll_param *mpll_param) +{ + COMPUTE_MEMORY_CLOCK_PARAM_PARAMETERS_V2_1 args; + int index = GetIndexIntoMasterTable(COMMAND, ComputeMemoryClockParam); + u8 frev, crev; + + memset(&args, 0, sizeof(args)); + memset(mpll_param, 0, sizeof(struct atom_mpll_param)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return -EINVAL; + + switch (frev) { + case 2: + switch (crev) { + case 1: + /* SI */ + args.ulClock = cpu_to_le32(clock); /* 10 khz */ + args.ucInputFlag = 0; + if (strobe_mode) + args.ucInputFlag |= MPLL_INPUT_FLAG_STROBE_MODE_EN; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + mpll_param->clkfrac = le16_to_cpu(args.ulFbDiv.usFbDivFrac); + mpll_param->clkf = le16_to_cpu(args.ulFbDiv.usFbDiv); + mpll_param->post_div = args.ucPostDiv; + mpll_param->dll_speed = args.ucDllSpeed; + mpll_param->bwcntl = args.ucBWCntl; + mpll_param->vco_mode = + (args.ucPllCntlFlag & MPLL_CNTL_FLAG_VCO_MODE_MASK); + mpll_param->yclk_sel = + (args.ucPllCntlFlag & MPLL_CNTL_FLAG_BYPASS_DQ_PLL) ? 1 : 0; + mpll_param->qdr = + (args.ucPllCntlFlag & MPLL_CNTL_FLAG_QDR_ENABLE) ? 1 : 0; + mpll_param->half_rate = + (args.ucPllCntlFlag & MPLL_CNTL_FLAG_AD_HALF_RATE) ? 1 : 0; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +uint32_t amdgpu_atombios_get_engine_clock(struct amdgpu_device *adev) +{ + GET_ENGINE_CLOCK_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, GetEngineClock); + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + return le32_to_cpu(args.ulReturnEngineClock); +} + +uint32_t amdgpu_atombios_get_memory_clock(struct amdgpu_device *adev) +{ + GET_MEMORY_CLOCK_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, GetMemoryClock); + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + return le32_to_cpu(args.ulReturnMemoryClock); +} + +void amdgpu_atombios_set_engine_clock(struct amdgpu_device *adev, + uint32_t eng_clock) +{ + SET_ENGINE_CLOCK_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, SetEngineClock); + + args.ulTargetEngineClock = cpu_to_le32(eng_clock); /* 10 khz */ + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_set_memory_clock(struct amdgpu_device *adev, + uint32_t mem_clock) +{ + SET_MEMORY_CLOCK_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, SetMemoryClock); + + if (adev->flags & AMDGPU_IS_APU) + return; + + args.ulTargetMemoryClock = cpu_to_le32(mem_clock); /* 10 khz */ + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_set_engine_dram_timings(struct amdgpu_device *adev, + u32 eng_clock, u32 mem_clock) +{ + SET_ENGINE_CLOCK_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, DynamicMemorySettings); + u32 tmp; + + memset(&args, 0, sizeof(args)); + + tmp = eng_clock & SET_CLOCK_FREQ_MASK; + tmp |= (COMPUTE_ENGINE_PLL_PARAM << 24); + + args.ulTargetEngineClock = cpu_to_le32(tmp); + if (mem_clock) + args.sReserved.ulClock = cpu_to_le32(mem_clock & SET_CLOCK_FREQ_MASK); + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +union set_voltage { + struct _SET_VOLTAGE_PS_ALLOCATION alloc; + struct _SET_VOLTAGE_PARAMETERS v1; + struct _SET_VOLTAGE_PARAMETERS_V2 v2; + struct _SET_VOLTAGE_PARAMETERS_V1_3 v3; +}; + +void amdgpu_atombios_set_voltage(struct amdgpu_device *adev, + u16 voltage_level, + u8 voltage_type) +{ + union set_voltage args; + int index = GetIndexIntoMasterTable(COMMAND, SetVoltage); + u8 frev, crev, volt_index = voltage_level; + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return; + + /* 0xff01 is a flag rather then an actual voltage */ + if (voltage_level == 0xff01) + return; + + switch (crev) { + case 1: + args.v1.ucVoltageType = voltage_type; + args.v1.ucVoltageMode = SET_ASIC_VOLTAGE_MODE_ALL_SOURCE; + args.v1.ucVoltageIndex = volt_index; + break; + case 2: + args.v2.ucVoltageType = voltage_type; + args.v2.ucVoltageMode = SET_ASIC_VOLTAGE_MODE_SET_VOLTAGE; + args.v2.usVoltageLevel = cpu_to_le16(voltage_level); + break; + case 3: + args.v3.ucVoltageType = voltage_type; + args.v3.ucVoltageMode = ATOM_SET_VOLTAGE; + args.v3.usVoltageLevel = cpu_to_le16(voltage_level); + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + return; + } + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +int amdgpu_atombios_get_leakage_id_from_vbios(struct amdgpu_device *adev, + u16 *leakage_id) +{ + union set_voltage args; + int index = GetIndexIntoMasterTable(COMMAND, SetVoltage); + u8 frev, crev; + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return -EINVAL; + + switch (crev) { + case 3: + case 4: + args.v3.ucVoltageType = 0; + args.v3.ucVoltageMode = ATOM_GET_LEAKAGE_ID; + args.v3.usVoltageLevel = 0; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + *leakage_id = le16_to_cpu(args.v3.usVoltageLevel); + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + return -EINVAL; + } + + return 0; +} + +int amdgpu_atombios_get_leakage_vddc_based_on_leakage_params(struct amdgpu_device *adev, + u16 *vddc, u16 *vddci, + u16 virtual_voltage_id, + u16 vbios_voltage_id) +{ + int index = GetIndexIntoMasterTable(DATA, ASIC_ProfilingInfo); + u8 frev, crev; + u16 data_offset, size; + int i, j; + ATOM_ASIC_PROFILING_INFO_V2_1 *profile; + u16 *leakage_bin, *vddc_id_buf, *vddc_buf, *vddci_id_buf, *vddci_buf; + + *vddc = 0; + *vddci = 0; + + if (!amdgpu_atom_parse_data_header(adev->mode_info.atom_context, index, &size, + &frev, &crev, &data_offset)) + return -EINVAL; + + profile = (ATOM_ASIC_PROFILING_INFO_V2_1 *) + (adev->mode_info.atom_context->bios + data_offset); + + switch (frev) { + case 1: + return -EINVAL; + case 2: + switch (crev) { + case 1: + if (size < sizeof(ATOM_ASIC_PROFILING_INFO_V2_1)) + return -EINVAL; + leakage_bin = (u16 *) + (adev->mode_info.atom_context->bios + data_offset + + le16_to_cpu(profile->usLeakageBinArrayOffset)); + vddc_id_buf = (u16 *) + (adev->mode_info.atom_context->bios + data_offset + + le16_to_cpu(profile->usElbVDDC_IdArrayOffset)); + vddc_buf = (u16 *) + (adev->mode_info.atom_context->bios + data_offset + + le16_to_cpu(profile->usElbVDDC_LevelArrayOffset)); + vddci_id_buf = (u16 *) + (adev->mode_info.atom_context->bios + data_offset + + le16_to_cpu(profile->usElbVDDCI_IdArrayOffset)); + vddci_buf = (u16 *) + (adev->mode_info.atom_context->bios + data_offset + + le16_to_cpu(profile->usElbVDDCI_LevelArrayOffset)); + + if (profile->ucElbVDDC_Num > 0) { + for (i = 0; i < profile->ucElbVDDC_Num; i++) { + if (vddc_id_buf[i] == virtual_voltage_id) { + for (j = 0; j < profile->ucLeakageBinNum; j++) { + if (vbios_voltage_id <= leakage_bin[j]) { + *vddc = vddc_buf[j * profile->ucElbVDDC_Num + i]; + break; + } + } + break; + } + } + } + if (profile->ucElbVDDCI_Num > 0) { + for (i = 0; i < profile->ucElbVDDCI_Num; i++) { + if (vddci_id_buf[i] == virtual_voltage_id) { + for (j = 0; j < profile->ucLeakageBinNum; j++) { + if (vbios_voltage_id <= leakage_bin[j]) { + *vddci = vddci_buf[j * profile->ucElbVDDCI_Num + i]; + break; + } + } + break; + } + } + } + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + return -EINVAL; + } + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + return -EINVAL; + } + + return 0; +} + +union get_voltage_info { + struct _GET_VOLTAGE_INFO_INPUT_PARAMETER_V1_2 in; + struct _GET_EVV_VOLTAGE_INFO_OUTPUT_PARAMETER_V1_2 evv_out; +}; + +int amdgpu_atombios_get_voltage_evv(struct amdgpu_device *adev, + u16 virtual_voltage_id, + u16 *voltage) +{ + int index = GetIndexIntoMasterTable(COMMAND, GetVoltageInfo); + u32 entry_id; + u32 count = adev->pm.dpm.dyn_state.vddc_dependency_on_sclk.count; + union get_voltage_info args; + + for (entry_id = 0; entry_id < count; entry_id++) { + if (adev->pm.dpm.dyn_state.vddc_dependency_on_sclk.entries[entry_id].v == + virtual_voltage_id) + break; + } + + if (entry_id >= count) + return -EINVAL; + + args.in.ucVoltageType = VOLTAGE_TYPE_VDDC; + args.in.ucVoltageMode = ATOM_GET_VOLTAGE_EVV_VOLTAGE; + args.in.usVoltageLevel = cpu_to_le16(virtual_voltage_id); + args.in.ulSCLKFreq = + cpu_to_le32(adev->pm.dpm.dyn_state.vddc_dependency_on_sclk.entries[entry_id].clk); + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + *voltage = le16_to_cpu(args.evv_out.usVoltageLevel); + + return 0; +} + +union voltage_object_info { + struct _ATOM_VOLTAGE_OBJECT_INFO v1; + struct _ATOM_VOLTAGE_OBJECT_INFO_V2 v2; + struct _ATOM_VOLTAGE_OBJECT_INFO_V3_1 v3; +}; + +union voltage_object { + struct _ATOM_VOLTAGE_OBJECT v1; + struct _ATOM_VOLTAGE_OBJECT_V2 v2; + union _ATOM_VOLTAGE_OBJECT_V3 v3; +}; + + +static ATOM_VOLTAGE_OBJECT_V3 *amdgpu_atombios_lookup_voltage_object_v3(ATOM_VOLTAGE_OBJECT_INFO_V3_1 *v3, + u8 voltage_type, u8 voltage_mode) +{ + u32 size = le16_to_cpu(v3->sHeader.usStructureSize); + u32 offset = offsetof(ATOM_VOLTAGE_OBJECT_INFO_V3_1, asVoltageObj[0]); + u8 *start = (u8*)v3; + + while (offset < size) { + ATOM_VOLTAGE_OBJECT_V3 *vo = (ATOM_VOLTAGE_OBJECT_V3 *)(start + offset); + if ((vo->asGpioVoltageObj.sHeader.ucVoltageType == voltage_type) && + (vo->asGpioVoltageObj.sHeader.ucVoltageMode == voltage_mode)) + return vo; + offset += le16_to_cpu(vo->asGpioVoltageObj.sHeader.usSize); + } + return NULL; +} + +bool +amdgpu_atombios_is_voltage_gpio(struct amdgpu_device *adev, + u8 voltage_type, u8 voltage_mode) +{ + int index = GetIndexIntoMasterTable(DATA, VoltageObjectInfo); + u8 frev, crev; + u16 data_offset, size; + union voltage_object_info *voltage_info; + + if (amdgpu_atom_parse_data_header(adev->mode_info.atom_context, index, &size, + &frev, &crev, &data_offset)) { + voltage_info = (union voltage_object_info *) + (adev->mode_info.atom_context->bios + data_offset); + + switch (frev) { + case 3: + switch (crev) { + case 1: + if (amdgpu_atombios_lookup_voltage_object_v3(&voltage_info->v3, + voltage_type, voltage_mode)) + return true; + break; + default: + DRM_ERROR("unknown voltage object table\n"); + return false; + } + break; + default: + DRM_ERROR("unknown voltage object table\n"); + return false; + } + + } + return false; +} + +int amdgpu_atombios_get_voltage_table(struct amdgpu_device *adev, + u8 voltage_type, u8 voltage_mode, + struct atom_voltage_table *voltage_table) +{ + int index = GetIndexIntoMasterTable(DATA, VoltageObjectInfo); + u8 frev, crev; + u16 data_offset, size; + int i; + union voltage_object_info *voltage_info; + union voltage_object *voltage_object = NULL; + + if (amdgpu_atom_parse_data_header(adev->mode_info.atom_context, index, &size, + &frev, &crev, &data_offset)) { + voltage_info = (union voltage_object_info *) + (adev->mode_info.atom_context->bios + data_offset); + + switch (frev) { + case 3: + switch (crev) { + case 1: + voltage_object = (union voltage_object *) + amdgpu_atombios_lookup_voltage_object_v3(&voltage_info->v3, + voltage_type, voltage_mode); + if (voltage_object) { + ATOM_GPIO_VOLTAGE_OBJECT_V3 *gpio = + &voltage_object->v3.asGpioVoltageObj; + VOLTAGE_LUT_ENTRY_V2 *lut; + if (gpio->ucGpioEntryNum > MAX_VOLTAGE_ENTRIES) + return -EINVAL; + lut = &gpio->asVolGpioLut[0]; + for (i = 0; i < gpio->ucGpioEntryNum; i++) { + voltage_table->entries[i].value = + le16_to_cpu(lut->usVoltageValue); + voltage_table->entries[i].smio_low = + le32_to_cpu(lut->ulVoltageId); + lut = (VOLTAGE_LUT_ENTRY_V2 *) + ((u8 *)lut + sizeof(VOLTAGE_LUT_ENTRY_V2)); + } + voltage_table->mask_low = le32_to_cpu(gpio->ulGpioMaskVal); + voltage_table->count = gpio->ucGpioEntryNum; + voltage_table->phase_delay = gpio->ucPhaseDelay; + return 0; + } + break; + default: + DRM_ERROR("unknown voltage object table\n"); + return -EINVAL; + } + break; + default: + DRM_ERROR("unknown voltage object table\n"); + return -EINVAL; + } + } + return -EINVAL; +} + +union vram_info { + struct _ATOM_VRAM_INFO_V3 v1_3; + struct _ATOM_VRAM_INFO_V4 v1_4; + struct _ATOM_VRAM_INFO_HEADER_V2_1 v2_1; +}; + +#define MEM_ID_MASK 0xff000000 +#define MEM_ID_SHIFT 24 +#define CLOCK_RANGE_MASK 0x00ffffff +#define CLOCK_RANGE_SHIFT 0 +#define LOW_NIBBLE_MASK 0xf +#define DATA_EQU_PREV 0 +#define DATA_FROM_TABLE 4 + +int amdgpu_atombios_init_mc_reg_table(struct amdgpu_device *adev, + u8 module_index, + struct atom_mc_reg_table *reg_table) +{ + int index = GetIndexIntoMasterTable(DATA, VRAM_Info); + u8 frev, crev, num_entries, t_mem_id, num_ranges = 0; + u32 i = 0, j; + u16 data_offset, size; + union vram_info *vram_info; + + memset(reg_table, 0, sizeof(struct atom_mc_reg_table)); + + if (amdgpu_atom_parse_data_header(adev->mode_info.atom_context, index, &size, + &frev, &crev, &data_offset)) { + vram_info = (union vram_info *) + (adev->mode_info.atom_context->bios + data_offset); + switch (frev) { + case 1: + DRM_ERROR("old table version %d, %d\n", frev, crev); + return -EINVAL; + case 2: + switch (crev) { + case 1: + if (module_index < vram_info->v2_1.ucNumOfVRAMModule) { + ATOM_INIT_REG_BLOCK *reg_block = + (ATOM_INIT_REG_BLOCK *) + ((u8 *)vram_info + le16_to_cpu(vram_info->v2_1.usMemClkPatchTblOffset)); + ATOM_MEMORY_SETTING_DATA_BLOCK *reg_data = + (ATOM_MEMORY_SETTING_DATA_BLOCK *) + ((u8 *)reg_block + (2 * sizeof(u16)) + + le16_to_cpu(reg_block->usRegIndexTblSize)); + ATOM_INIT_REG_INDEX_FORMAT *format = ®_block->asRegIndexBuf[0]; + num_entries = (u8)((le16_to_cpu(reg_block->usRegIndexTblSize)) / + sizeof(ATOM_INIT_REG_INDEX_FORMAT)) - 1; + if (num_entries > VBIOS_MC_REGISTER_ARRAY_SIZE) + return -EINVAL; + while (i < num_entries) { + if (format->ucPreRegDataLength & ACCESS_PLACEHOLDER) + break; + reg_table->mc_reg_address[i].s1 = + (u16)(le16_to_cpu(format->usRegIndex)); + reg_table->mc_reg_address[i].pre_reg_data = + (u8)(format->ucPreRegDataLength); + i++; + format = (ATOM_INIT_REG_INDEX_FORMAT *) + ((u8 *)format + sizeof(ATOM_INIT_REG_INDEX_FORMAT)); + } + reg_table->last = i; + while ((le32_to_cpu(*(u32 *)reg_data) != END_OF_REG_DATA_BLOCK) && + (num_ranges < VBIOS_MAX_AC_TIMING_ENTRIES)) { + t_mem_id = (u8)((le32_to_cpu(*(u32 *)reg_data) & MEM_ID_MASK) + >> MEM_ID_SHIFT); + if (module_index == t_mem_id) { + reg_table->mc_reg_table_entry[num_ranges].mclk_max = + (u32)((le32_to_cpu(*(u32 *)reg_data) & CLOCK_RANGE_MASK) + >> CLOCK_RANGE_SHIFT); + for (i = 0, j = 1; i < reg_table->last; i++) { + if ((reg_table->mc_reg_address[i].pre_reg_data & LOW_NIBBLE_MASK) == DATA_FROM_TABLE) { + reg_table->mc_reg_table_entry[num_ranges].mc_data[i] = + (u32)le32_to_cpu(*((u32 *)reg_data + j)); + j++; + } else if ((reg_table->mc_reg_address[i].pre_reg_data & LOW_NIBBLE_MASK) == DATA_EQU_PREV) { + reg_table->mc_reg_table_entry[num_ranges].mc_data[i] = + reg_table->mc_reg_table_entry[num_ranges].mc_data[i - 1]; + } + } + num_ranges++; + } + reg_data = (ATOM_MEMORY_SETTING_DATA_BLOCK *) + ((u8 *)reg_data + le16_to_cpu(reg_block->usRegDataBlkSize)); + } + if (le32_to_cpu(*(u32 *)reg_data) != END_OF_REG_DATA_BLOCK) + return -EINVAL; + reg_table->num_entries = num_ranges; + } else + return -EINVAL; + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + return -EINVAL; + } + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + return -EINVAL; + } + return 0; + } + return -EINVAL; +} + +void amdgpu_atombios_scratch_regs_lock(struct amdgpu_device *adev, bool lock) +{ + uint32_t bios_6_scratch; + + bios_6_scratch = RREG32(mmBIOS_SCRATCH_6); + + if (lock) { + bios_6_scratch |= ATOM_S6_CRITICAL_STATE; + bios_6_scratch &= ~ATOM_S6_ACC_MODE; + } else { + bios_6_scratch &= ~ATOM_S6_CRITICAL_STATE; + bios_6_scratch |= ATOM_S6_ACC_MODE; + } + + WREG32(mmBIOS_SCRATCH_6, bios_6_scratch); +} + +void amdgpu_atombios_scratch_regs_init(struct amdgpu_device *adev) +{ + uint32_t bios_2_scratch, bios_6_scratch; + + bios_2_scratch = RREG32(mmBIOS_SCRATCH_2); + bios_6_scratch = RREG32(mmBIOS_SCRATCH_6); + + /* let the bios control the backlight */ + bios_2_scratch &= ~ATOM_S2_VRI_BRIGHT_ENABLE; + + /* tell the bios not to handle mode switching */ + bios_6_scratch |= ATOM_S6_ACC_BLOCK_DISPLAY_SWITCH; + + /* clear the vbios dpms state */ + bios_2_scratch &= ~ATOM_S2_DEVICE_DPMS_STATE; + + WREG32(mmBIOS_SCRATCH_2, bios_2_scratch); + WREG32(mmBIOS_SCRATCH_6, bios_6_scratch); +} + +void amdgpu_atombios_scratch_regs_save(struct amdgpu_device *adev) +{ + int i; + + for (i = 0; i < AMDGPU_BIOS_NUM_SCRATCH; i++) + adev->bios_scratch[i] = RREG32(mmBIOS_SCRATCH_0 + i); +} + +void amdgpu_atombios_scratch_regs_restore(struct amdgpu_device *adev) +{ + int i; + + for (i = 0; i < AMDGPU_BIOS_NUM_SCRATCH; i++) + WREG32(mmBIOS_SCRATCH_0 + i, adev->bios_scratch[i]); +} + +/* Atom needs data in little endian format + * so swap as appropriate when copying data to + * or from atom. Note that atom operates on + * dw units. + */ +void amdgpu_atombios_copy_swap(u8 *dst, u8 *src, u8 num_bytes, bool to_le) +{ +#ifdef __BIG_ENDIAN + u8 src_tmp[20], dst_tmp[20]; /* used for byteswapping */ + u32 *dst32, *src32; + int i; + + memcpy(src_tmp, src, num_bytes); + src32 = (u32 *)src_tmp; + dst32 = (u32 *)dst_tmp; + if (to_le) { + for (i = 0; i < ((num_bytes + 3) / 4); i++) + dst32[i] = cpu_to_le32(src32[i]); + memcpy(dst, dst_tmp, num_bytes); + } else { + u8 dws = num_bytes & ~3; + for (i = 0; i < ((num_bytes + 3) / 4); i++) + dst32[i] = le32_to_cpu(src32[i]); + memcpy(dst, dst_tmp, dws); + if (num_bytes % 4) { + for (i = 0; i < (num_bytes % 4); i++) + dst[dws+i] = dst_tmp[dws+i]; + } + } +#else + memcpy(dst, src, num_bytes); +#endif +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.h new file mode 100644 index 000000000000..0ebb959ea435 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_atombios.h @@ -0,0 +1,206 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_ATOMBIOS_H__ +#define __AMDGPU_ATOMBIOS_H__ + +struct atom_clock_dividers { + u32 post_div; + union { + struct { +#ifdef __BIG_ENDIAN + u32 reserved : 6; + u32 whole_fb_div : 12; + u32 frac_fb_div : 14; +#else + u32 frac_fb_div : 14; + u32 whole_fb_div : 12; + u32 reserved : 6; +#endif + }; + u32 fb_div; + }; + u32 ref_div; + bool enable_post_div; + bool enable_dithen; + u32 vco_mode; + u32 real_clock; + /* added for CI */ + u32 post_divider; + u32 flags; +}; + +struct atom_mpll_param { + union { + struct { +#ifdef __BIG_ENDIAN + u32 reserved : 8; + u32 clkfrac : 12; + u32 clkf : 12; +#else + u32 clkf : 12; + u32 clkfrac : 12; + u32 reserved : 8; +#endif + }; + u32 fb_div; + }; + u32 post_div; + u32 bwcntl; + u32 dll_speed; + u32 vco_mode; + u32 yclk_sel; + u32 qdr; + u32 half_rate; +}; + +#define MEM_TYPE_GDDR5 0x50 +#define MEM_TYPE_GDDR4 0x40 +#define MEM_TYPE_GDDR3 0x30 +#define MEM_TYPE_DDR2 0x20 +#define MEM_TYPE_GDDR1 0x10 +#define MEM_TYPE_DDR3 0xb0 +#define MEM_TYPE_MASK 0xf0 + +struct atom_memory_info { + u8 mem_vendor; + u8 mem_type; +}; + +#define MAX_AC_TIMING_ENTRIES 16 + +struct atom_memory_clock_range_table +{ + u8 num_entries; + u8 rsv[3]; + u32 mclk[MAX_AC_TIMING_ENTRIES]; +}; + +#define VBIOS_MC_REGISTER_ARRAY_SIZE 32 +#define VBIOS_MAX_AC_TIMING_ENTRIES 20 + +struct atom_mc_reg_entry { + u32 mclk_max; + u32 mc_data[VBIOS_MC_REGISTER_ARRAY_SIZE]; +}; + +struct atom_mc_register_address { + u16 s1; + u8 pre_reg_data; +}; + +struct atom_mc_reg_table { + u8 last; + u8 num_entries; + struct atom_mc_reg_entry mc_reg_table_entry[VBIOS_MAX_AC_TIMING_ENTRIES]; + struct atom_mc_register_address mc_reg_address[VBIOS_MC_REGISTER_ARRAY_SIZE]; +}; + +#define MAX_VOLTAGE_ENTRIES 32 + +struct atom_voltage_table_entry +{ + u16 value; + u32 smio_low; +}; + +struct atom_voltage_table +{ + u32 count; + u32 mask_low; + u32 phase_delay; + struct atom_voltage_table_entry entries[MAX_VOLTAGE_ENTRIES]; +}; + +struct amdgpu_gpio_rec +amdgpu_atombios_lookup_gpio(struct amdgpu_device *adev, + u8 id); + +struct amdgpu_i2c_bus_rec amdgpu_atombios_lookup_i2c_gpio(struct amdgpu_device *adev, + uint8_t id); +void amdgpu_atombios_i2c_init(struct amdgpu_device *adev); + +bool amdgpu_atombios_get_connector_info_from_object_table(struct amdgpu_device *adev); + +int amdgpu_atombios_get_clock_info(struct amdgpu_device *adev); + +bool amdgpu_atombios_get_asic_ss_info(struct amdgpu_device *adev, + struct amdgpu_atom_ss *ss, + int id, u32 clock); + +int amdgpu_atombios_get_clock_dividers(struct amdgpu_device *adev, + u8 clock_type, + u32 clock, + bool strobe_mode, + struct atom_clock_dividers *dividers); + +int amdgpu_atombios_get_memory_pll_dividers(struct amdgpu_device *adev, + u32 clock, + bool strobe_mode, + struct atom_mpll_param *mpll_param); + +uint32_t amdgpu_atombios_get_engine_clock(struct amdgpu_device *adev); +uint32_t amdgpu_atombios_get_memory_clock(struct amdgpu_device *adev); +void amdgpu_atombios_set_engine_clock(struct amdgpu_device *adev, + uint32_t eng_clock); +void amdgpu_atombios_set_memory_clock(struct amdgpu_device *adev, + uint32_t mem_clock); +void amdgpu_atombios_set_voltage(struct amdgpu_device *adev, + u16 voltage_level, + u8 voltage_type); + +void amdgpu_atombios_set_engine_dram_timings(struct amdgpu_device *adev, + u32 eng_clock, u32 mem_clock); + +int amdgpu_atombios_get_leakage_id_from_vbios(struct amdgpu_device *adev, + u16 *leakage_id); + +int amdgpu_atombios_get_leakage_vddc_based_on_leakage_params(struct amdgpu_device *adev, + u16 *vddc, u16 *vddci, + u16 virtual_voltage_id, + u16 vbios_voltage_id); + +int amdgpu_atombios_get_voltage_evv(struct amdgpu_device *adev, + u16 virtual_voltage_id, + u16 *voltage); + +bool +amdgpu_atombios_is_voltage_gpio(struct amdgpu_device *adev, + u8 voltage_type, u8 voltage_mode); + +int amdgpu_atombios_get_voltage_table(struct amdgpu_device *adev, + u8 voltage_type, u8 voltage_mode, + struct atom_voltage_table *voltage_table); + +int amdgpu_atombios_init_mc_reg_table(struct amdgpu_device *adev, + u8 module_index, + struct atom_mc_reg_table *reg_table); + +void amdgpu_atombios_scratch_regs_lock(struct amdgpu_device *adev, bool lock); +void amdgpu_atombios_scratch_regs_init(struct amdgpu_device *adev); +void amdgpu_atombios_scratch_regs_save(struct amdgpu_device *adev); +void amdgpu_atombios_scratch_regs_restore(struct amdgpu_device *adev); + +void amdgpu_atombios_copy_swap(u8 *dst, u8 *src, u8 num_bytes, bool to_le); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c new file mode 100644 index 000000000000..3f7aaa45bf8e --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_atpx_handler.c @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2010 Red Hat Inc. + * Author : Dave Airlie <airlied@redhat.com> + * + * Licensed under GPLv2 + * + * ATPX support for both Intel/ATI + */ +#include <linux/vga_switcheroo.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/pci.h> + +#include "amdgpu_acpi.h" + +struct amdgpu_atpx_functions { + bool px_params; + bool power_cntl; + bool disp_mux_cntl; + bool i2c_mux_cntl; + bool switch_start; + bool switch_end; + bool disp_connectors_mapping; + bool disp_detetion_ports; +}; + +struct amdgpu_atpx { + acpi_handle handle; + struct amdgpu_atpx_functions functions; +}; + +static struct amdgpu_atpx_priv { + bool atpx_detected; + /* handle for device - and atpx */ + acpi_handle dhandle; + acpi_handle other_handle; + struct amdgpu_atpx atpx; +} amdgpu_atpx_priv; + +struct atpx_verify_interface { + u16 size; /* structure size in bytes (includes size field) */ + u16 version; /* version */ + u32 function_bits; /* supported functions bit vector */ +} __packed; + +struct atpx_px_params { + u16 size; /* structure size in bytes (includes size field) */ + u32 valid_flags; /* which flags are valid */ + u32 flags; /* flags */ +} __packed; + +struct atpx_power_control { + u16 size; + u8 dgpu_state; +} __packed; + +struct atpx_mux { + u16 size; + u16 mux; +} __packed; + +bool amdgpu_has_atpx(void) { + return amdgpu_atpx_priv.atpx_detected; +} + +/** + * amdgpu_atpx_call - call an ATPX method + * + * @handle: acpi handle + * @function: the ATPX function to execute + * @params: ATPX function params + * + * Executes the requested ATPX function (all asics). + * Returns a pointer to the acpi output buffer. + */ +static union acpi_object *amdgpu_atpx_call(acpi_handle handle, int function, + struct acpi_buffer *params) +{ + acpi_status status; + union acpi_object atpx_arg_elements[2]; + struct acpi_object_list atpx_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + + atpx_arg.count = 2; + atpx_arg.pointer = &atpx_arg_elements[0]; + + atpx_arg_elements[0].type = ACPI_TYPE_INTEGER; + atpx_arg_elements[0].integer.value = function; + + if (params) { + atpx_arg_elements[1].type = ACPI_TYPE_BUFFER; + atpx_arg_elements[1].buffer.length = params->length; + atpx_arg_elements[1].buffer.pointer = params->pointer; + } else { + /* We need a second fake parameter */ + atpx_arg_elements[1].type = ACPI_TYPE_INTEGER; + atpx_arg_elements[1].integer.value = 0; + } + + status = acpi_evaluate_object(handle, NULL, &atpx_arg, &buffer); + + /* Fail only if calling the method fails and ATPX is supported */ + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { + printk("failed to evaluate ATPX got %s\n", + acpi_format_exception(status)); + kfree(buffer.pointer); + return NULL; + } + + return buffer.pointer; +} + +/** + * amdgpu_atpx_parse_functions - parse supported functions + * + * @f: supported functions struct + * @mask: supported functions mask from ATPX + * + * Use the supported functions mask from ATPX function + * ATPX_FUNCTION_VERIFY_INTERFACE to determine what functions + * are supported (all asics). + */ +static void amdgpu_atpx_parse_functions(struct amdgpu_atpx_functions *f, u32 mask) +{ + f->px_params = mask & ATPX_GET_PX_PARAMETERS_SUPPORTED; + f->power_cntl = mask & ATPX_POWER_CONTROL_SUPPORTED; + f->disp_mux_cntl = mask & ATPX_DISPLAY_MUX_CONTROL_SUPPORTED; + f->i2c_mux_cntl = mask & ATPX_I2C_MUX_CONTROL_SUPPORTED; + f->switch_start = mask & ATPX_GRAPHICS_DEVICE_SWITCH_START_NOTIFICATION_SUPPORTED; + f->switch_end = mask & ATPX_GRAPHICS_DEVICE_SWITCH_END_NOTIFICATION_SUPPORTED; + f->disp_connectors_mapping = mask & ATPX_GET_DISPLAY_CONNECTORS_MAPPING_SUPPORTED; + f->disp_detetion_ports = mask & ATPX_GET_DISPLAY_DETECTION_PORTS_SUPPORTED; +} + +/** + * amdgpu_atpx_validate_functions - validate ATPX functions + * + * @atpx: amdgpu atpx struct + * + * Validate that required functions are enabled (all asics). + * returns 0 on success, error on failure. + */ +static int amdgpu_atpx_validate(struct amdgpu_atpx *atpx) +{ + /* make sure required functions are enabled */ + /* dGPU power control is required */ + atpx->functions.power_cntl = true; + + if (atpx->functions.px_params) { + union acpi_object *info; + struct atpx_px_params output; + size_t size; + u32 valid_bits; + + info = amdgpu_atpx_call(atpx->handle, ATPX_FUNCTION_GET_PX_PARAMETERS, NULL); + if (!info) + return -EIO; + + memset(&output, 0, sizeof(output)); + + size = *(u16 *) info->buffer.pointer; + if (size < 10) { + printk("ATPX buffer is too small: %zu\n", size); + kfree(info); + return -EINVAL; + } + size = min(sizeof(output), size); + + memcpy(&output, info->buffer.pointer, size); + + valid_bits = output.flags & output.valid_flags; + /* if separate mux flag is set, mux controls are required */ + if (valid_bits & ATPX_SEPARATE_MUX_FOR_I2C) { + atpx->functions.i2c_mux_cntl = true; + atpx->functions.disp_mux_cntl = true; + } + /* if any outputs are muxed, mux controls are required */ + if (valid_bits & (ATPX_CRT1_RGB_SIGNAL_MUXED | + ATPX_TV_SIGNAL_MUXED | + ATPX_DFP_SIGNAL_MUXED)) + atpx->functions.disp_mux_cntl = true; + + kfree(info); + } + return 0; +} + +/** + * amdgpu_atpx_verify_interface - verify ATPX + * + * @atpx: amdgpu atpx struct + * + * Execute the ATPX_FUNCTION_VERIFY_INTERFACE ATPX function + * to initialize ATPX and determine what features are supported + * (all asics). + * returns 0 on success, error on failure. + */ +static int amdgpu_atpx_verify_interface(struct amdgpu_atpx *atpx) +{ + union acpi_object *info; + struct atpx_verify_interface output; + size_t size; + int err = 0; + + info = amdgpu_atpx_call(atpx->handle, ATPX_FUNCTION_VERIFY_INTERFACE, NULL); + if (!info) + return -EIO; + + memset(&output, 0, sizeof(output)); + + size = *(u16 *) info->buffer.pointer; + if (size < 8) { + printk("ATPX buffer is too small: %zu\n", size); + err = -EINVAL; + goto out; + } + size = min(sizeof(output), size); + + memcpy(&output, info->buffer.pointer, size); + + /* TODO: check version? */ + printk("ATPX version %u, functions 0x%08x\n", + output.version, output.function_bits); + + amdgpu_atpx_parse_functions(&atpx->functions, output.function_bits); + +out: + kfree(info); + return err; +} + +/** + * amdgpu_atpx_set_discrete_state - power up/down discrete GPU + * + * @atpx: atpx info struct + * @state: discrete GPU state (0 = power down, 1 = power up) + * + * Execute the ATPX_FUNCTION_POWER_CONTROL ATPX function to + * power down/up the discrete GPU (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_set_discrete_state(struct amdgpu_atpx *atpx, u8 state) +{ + struct acpi_buffer params; + union acpi_object *info; + struct atpx_power_control input; + + if (atpx->functions.power_cntl) { + input.size = 3; + input.dgpu_state = state; + params.length = input.size; + params.pointer = &input; + info = amdgpu_atpx_call(atpx->handle, + ATPX_FUNCTION_POWER_CONTROL, + ¶ms); + if (!info) + return -EIO; + kfree(info); + } + return 0; +} + +/** + * amdgpu_atpx_switch_disp_mux - switch display mux + * + * @atpx: atpx info struct + * @mux_id: mux state (0 = integrated GPU, 1 = discrete GPU) + * + * Execute the ATPX_FUNCTION_DISPLAY_MUX_CONTROL ATPX function to + * switch the display mux between the discrete GPU and integrated GPU + * (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_switch_disp_mux(struct amdgpu_atpx *atpx, u16 mux_id) +{ + struct acpi_buffer params; + union acpi_object *info; + struct atpx_mux input; + + if (atpx->functions.disp_mux_cntl) { + input.size = 4; + input.mux = mux_id; + params.length = input.size; + params.pointer = &input; + info = amdgpu_atpx_call(atpx->handle, + ATPX_FUNCTION_DISPLAY_MUX_CONTROL, + ¶ms); + if (!info) + return -EIO; + kfree(info); + } + return 0; +} + +/** + * amdgpu_atpx_switch_i2c_mux - switch i2c/hpd mux + * + * @atpx: atpx info struct + * @mux_id: mux state (0 = integrated GPU, 1 = discrete GPU) + * + * Execute the ATPX_FUNCTION_I2C_MUX_CONTROL ATPX function to + * switch the i2c/hpd mux between the discrete GPU and integrated GPU + * (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_switch_i2c_mux(struct amdgpu_atpx *atpx, u16 mux_id) +{ + struct acpi_buffer params; + union acpi_object *info; + struct atpx_mux input; + + if (atpx->functions.i2c_mux_cntl) { + input.size = 4; + input.mux = mux_id; + params.length = input.size; + params.pointer = &input; + info = amdgpu_atpx_call(atpx->handle, + ATPX_FUNCTION_I2C_MUX_CONTROL, + ¶ms); + if (!info) + return -EIO; + kfree(info); + } + return 0; +} + +/** + * amdgpu_atpx_switch_start - notify the sbios of a GPU switch + * + * @atpx: atpx info struct + * @mux_id: mux state (0 = integrated GPU, 1 = discrete GPU) + * + * Execute the ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_START_NOTIFICATION ATPX + * function to notify the sbios that a switch between the discrete GPU and + * integrated GPU has begun (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_switch_start(struct amdgpu_atpx *atpx, u16 mux_id) +{ + struct acpi_buffer params; + union acpi_object *info; + struct atpx_mux input; + + if (atpx->functions.switch_start) { + input.size = 4; + input.mux = mux_id; + params.length = input.size; + params.pointer = &input; + info = amdgpu_atpx_call(atpx->handle, + ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_START_NOTIFICATION, + ¶ms); + if (!info) + return -EIO; + kfree(info); + } + return 0; +} + +/** + * amdgpu_atpx_switch_end - notify the sbios of a GPU switch + * + * @atpx: atpx info struct + * @mux_id: mux state (0 = integrated GPU, 1 = discrete GPU) + * + * Execute the ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_END_NOTIFICATION ATPX + * function to notify the sbios that a switch between the discrete GPU and + * integrated GPU has ended (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_switch_end(struct amdgpu_atpx *atpx, u16 mux_id) +{ + struct acpi_buffer params; + union acpi_object *info; + struct atpx_mux input; + + if (atpx->functions.switch_end) { + input.size = 4; + input.mux = mux_id; + params.length = input.size; + params.pointer = &input; + info = amdgpu_atpx_call(atpx->handle, + ATPX_FUNCTION_GRAPHICS_DEVICE_SWITCH_END_NOTIFICATION, + ¶ms); + if (!info) + return -EIO; + kfree(info); + } + return 0; +} + +/** + * amdgpu_atpx_switchto - switch to the requested GPU + * + * @id: GPU to switch to + * + * Execute the necessary ATPX functions to switch between the discrete GPU and + * integrated GPU (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_switchto(enum vga_switcheroo_client_id id) +{ + u16 gpu_id; + + if (id == VGA_SWITCHEROO_IGD) + gpu_id = ATPX_INTEGRATED_GPU; + else + gpu_id = ATPX_DISCRETE_GPU; + + amdgpu_atpx_switch_start(&amdgpu_atpx_priv.atpx, gpu_id); + amdgpu_atpx_switch_disp_mux(&amdgpu_atpx_priv.atpx, gpu_id); + amdgpu_atpx_switch_i2c_mux(&amdgpu_atpx_priv.atpx, gpu_id); + amdgpu_atpx_switch_end(&amdgpu_atpx_priv.atpx, gpu_id); + + return 0; +} + +/** + * amdgpu_atpx_power_state - power down/up the requested GPU + * + * @id: GPU to power down/up + * @state: requested power state (0 = off, 1 = on) + * + * Execute the necessary ATPX function to power down/up the discrete GPU + * (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_power_state(enum vga_switcheroo_client_id id, + enum vga_switcheroo_state state) +{ + /* on w500 ACPI can't change intel gpu state */ + if (id == VGA_SWITCHEROO_IGD) + return 0; + + amdgpu_atpx_set_discrete_state(&amdgpu_atpx_priv.atpx, state); + return 0; +} + +/** + * amdgpu_atpx_pci_probe_handle - look up the ATPX handle + * + * @pdev: pci device + * + * Look up the ATPX handles (all asics). + * Returns true if the handles are found, false if not. + */ +static bool amdgpu_atpx_pci_probe_handle(struct pci_dev *pdev) +{ + acpi_handle dhandle, atpx_handle; + acpi_status status; + + dhandle = ACPI_HANDLE(&pdev->dev); + if (!dhandle) + return false; + + status = acpi_get_handle(dhandle, "ATPX", &atpx_handle); + if (ACPI_FAILURE(status)) { + amdgpu_atpx_priv.other_handle = dhandle; + return false; + } + amdgpu_atpx_priv.dhandle = dhandle; + amdgpu_atpx_priv.atpx.handle = atpx_handle; + return true; +} + +/** + * amdgpu_atpx_init - verify the ATPX interface + * + * Verify the ATPX interface (all asics). + * Returns 0 on success, error on failure. + */ +static int amdgpu_atpx_init(void) +{ + int r; + + /* set up the ATPX handle */ + r = amdgpu_atpx_verify_interface(&amdgpu_atpx_priv.atpx); + if (r) + return r; + + /* validate the atpx setup */ + r = amdgpu_atpx_validate(&amdgpu_atpx_priv.atpx); + if (r) + return r; + + return 0; +} + +/** + * amdgpu_atpx_get_client_id - get the client id + * + * @pdev: pci device + * + * look up whether we are the integrated or discrete GPU (all asics). + * Returns the client id. + */ +static int amdgpu_atpx_get_client_id(struct pci_dev *pdev) +{ + if (amdgpu_atpx_priv.dhandle == ACPI_HANDLE(&pdev->dev)) + return VGA_SWITCHEROO_IGD; + else + return VGA_SWITCHEROO_DIS; +} + +static struct vga_switcheroo_handler amdgpu_atpx_handler = { + .switchto = amdgpu_atpx_switchto, + .power_state = amdgpu_atpx_power_state, + .init = amdgpu_atpx_init, + .get_client_id = amdgpu_atpx_get_client_id, +}; + +/** + * amdgpu_atpx_detect - detect whether we have PX + * + * Check if we have a PX system (all asics). + * Returns true if we have a PX system, false if not. + */ +static bool amdgpu_atpx_detect(void) +{ + char acpi_method_name[255] = { 0 }; + struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; + struct pci_dev *pdev = NULL; + bool has_atpx = false; + int vga_count = 0; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { + vga_count++; + + has_atpx |= (amdgpu_atpx_pci_probe_handle(pdev) == true); + } + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_OTHER << 8, pdev)) != NULL) { + vga_count++; + + has_atpx |= (amdgpu_atpx_pci_probe_handle(pdev) == true); + } + + if (has_atpx && vga_count == 2) { + acpi_get_name(amdgpu_atpx_priv.atpx.handle, ACPI_FULL_PATHNAME, &buffer); + printk(KERN_INFO "VGA switcheroo: detected switching method %s handle\n", + acpi_method_name); + amdgpu_atpx_priv.atpx_detected = true; + return true; + } + return false; +} + +/** + * amdgpu_register_atpx_handler - register with vga_switcheroo + * + * Register the PX callbacks with vga_switcheroo (all asics). + */ +void amdgpu_register_atpx_handler(void) +{ + bool r; + + /* detect if we have any ATPX + 2 VGA in the system */ + r = amdgpu_atpx_detect(); + if (!r) + return; + + vga_switcheroo_register_handler(&amdgpu_atpx_handler); +} + +/** + * amdgpu_unregister_atpx_handler - unregister with vga_switcheroo + * + * Unregister the PX callbacks with vga_switcheroo (all asics). + */ +void amdgpu_unregister_atpx_handler(void) +{ + vga_switcheroo_unregister_handler(); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_benchmark.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_benchmark.c new file mode 100644 index 000000000000..2742b9a35cbc --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_benchmark.c @@ -0,0 +1,221 @@ +/* + * Copyright 2009 Jerome Glisse. + * + * 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: Jerome Glisse + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +#define AMDGPU_BENCHMARK_ITERATIONS 1024 +#define AMDGPU_BENCHMARK_COMMON_MODES_N 17 + +static int amdgpu_benchmark_do_move(struct amdgpu_device *adev, unsigned size, + uint64_t saddr, uint64_t daddr, int n) +{ + unsigned long start_jiffies; + unsigned long end_jiffies; + struct amdgpu_fence *fence = NULL; + int i, r; + + start_jiffies = jiffies; + for (i = 0; i < n; i++) { + struct amdgpu_ring *ring = adev->mman.buffer_funcs_ring; + r = amdgpu_copy_buffer(ring, saddr, daddr, size, NULL, &fence); + if (r) + goto exit_do_move; + r = amdgpu_fence_wait(fence, false); + if (r) + goto exit_do_move; + amdgpu_fence_unref(&fence); + } + end_jiffies = jiffies; + r = jiffies_to_msecs(end_jiffies - start_jiffies); + +exit_do_move: + if (fence) + amdgpu_fence_unref(&fence); + return r; +} + + +static void amdgpu_benchmark_log_results(int n, unsigned size, + unsigned int time, + unsigned sdomain, unsigned ddomain, + char *kind) +{ + unsigned int throughput = (n * (size >> 10)) / time; + DRM_INFO("amdgpu: %s %u bo moves of %u kB from" + " %d to %d in %u ms, throughput: %u Mb/s or %u MB/s\n", + kind, n, size >> 10, sdomain, ddomain, time, + throughput * 8, throughput); +} + +static void amdgpu_benchmark_move(struct amdgpu_device *adev, unsigned size, + unsigned sdomain, unsigned ddomain) +{ + struct amdgpu_bo *dobj = NULL; + struct amdgpu_bo *sobj = NULL; + uint64_t saddr, daddr; + int r, n; + int time; + + n = AMDGPU_BENCHMARK_ITERATIONS; + r = amdgpu_bo_create(adev, size, PAGE_SIZE, true, sdomain, 0, NULL, &sobj); + if (r) { + goto out_cleanup; + } + r = amdgpu_bo_reserve(sobj, false); + if (unlikely(r != 0)) + goto out_cleanup; + r = amdgpu_bo_pin(sobj, sdomain, &saddr); + amdgpu_bo_unreserve(sobj); + if (r) { + goto out_cleanup; + } + r = amdgpu_bo_create(adev, size, PAGE_SIZE, true, ddomain, 0, NULL, &dobj); + if (r) { + goto out_cleanup; + } + r = amdgpu_bo_reserve(dobj, false); + if (unlikely(r != 0)) + goto out_cleanup; + r = amdgpu_bo_pin(dobj, ddomain, &daddr); + amdgpu_bo_unreserve(dobj); + if (r) { + goto out_cleanup; + } + + if (adev->mman.buffer_funcs) { + time = amdgpu_benchmark_do_move(adev, size, saddr, daddr, n); + if (time < 0) + goto out_cleanup; + if (time > 0) + amdgpu_benchmark_log_results(n, size, time, + sdomain, ddomain, "dma"); + } + +out_cleanup: + if (sobj) { + r = amdgpu_bo_reserve(sobj, false); + if (likely(r == 0)) { + amdgpu_bo_unpin(sobj); + amdgpu_bo_unreserve(sobj); + } + amdgpu_bo_unref(&sobj); + } + if (dobj) { + r = amdgpu_bo_reserve(dobj, false); + if (likely(r == 0)) { + amdgpu_bo_unpin(dobj); + amdgpu_bo_unreserve(dobj); + } + amdgpu_bo_unref(&dobj); + } + + if (r) { + DRM_ERROR("Error while benchmarking BO move.\n"); + } +} + +void amdgpu_benchmark(struct amdgpu_device *adev, int test_number) +{ + int i; + int common_modes[AMDGPU_BENCHMARK_COMMON_MODES_N] = { + 640 * 480 * 4, + 720 * 480 * 4, + 800 * 600 * 4, + 848 * 480 * 4, + 1024 * 768 * 4, + 1152 * 768 * 4, + 1280 * 720 * 4, + 1280 * 800 * 4, + 1280 * 854 * 4, + 1280 * 960 * 4, + 1280 * 1024 * 4, + 1440 * 900 * 4, + 1400 * 1050 * 4, + 1680 * 1050 * 4, + 1600 * 1200 * 4, + 1920 * 1080 * 4, + 1920 * 1200 * 4 + }; + + switch (test_number) { + case 1: + /* simple test, VRAM to GTT and GTT to VRAM */ + amdgpu_benchmark_move(adev, 1024*1024, AMDGPU_GEM_DOMAIN_GTT, + AMDGPU_GEM_DOMAIN_VRAM); + amdgpu_benchmark_move(adev, 1024*1024, AMDGPU_GEM_DOMAIN_VRAM, + AMDGPU_GEM_DOMAIN_GTT); + break; + case 2: + /* simple test, VRAM to VRAM */ + amdgpu_benchmark_move(adev, 1024*1024, AMDGPU_GEM_DOMAIN_VRAM, + AMDGPU_GEM_DOMAIN_VRAM); + break; + case 3: + /* GTT to VRAM, buffer size sweep, powers of 2 */ + for (i = 1; i <= 16384; i <<= 1) + amdgpu_benchmark_move(adev, i * AMDGPU_GPU_PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, + AMDGPU_GEM_DOMAIN_VRAM); + break; + case 4: + /* VRAM to GTT, buffer size sweep, powers of 2 */ + for (i = 1; i <= 16384; i <<= 1) + amdgpu_benchmark_move(adev, i * AMDGPU_GPU_PAGE_SIZE, + AMDGPU_GEM_DOMAIN_VRAM, + AMDGPU_GEM_DOMAIN_GTT); + break; + case 5: + /* VRAM to VRAM, buffer size sweep, powers of 2 */ + for (i = 1; i <= 16384; i <<= 1) + amdgpu_benchmark_move(adev, i * AMDGPU_GPU_PAGE_SIZE, + AMDGPU_GEM_DOMAIN_VRAM, + AMDGPU_GEM_DOMAIN_VRAM); + break; + case 6: + /* GTT to VRAM, buffer size sweep, common modes */ + for (i = 0; i < AMDGPU_BENCHMARK_COMMON_MODES_N; i++) + amdgpu_benchmark_move(adev, common_modes[i], + AMDGPU_GEM_DOMAIN_GTT, + AMDGPU_GEM_DOMAIN_VRAM); + break; + case 7: + /* VRAM to GTT, buffer size sweep, common modes */ + for (i = 0; i < AMDGPU_BENCHMARK_COMMON_MODES_N; i++) + amdgpu_benchmark_move(adev, common_modes[i], + AMDGPU_GEM_DOMAIN_VRAM, + AMDGPU_GEM_DOMAIN_GTT); + break; + case 8: + /* VRAM to VRAM, buffer size sweep, common modes */ + for (i = 0; i < AMDGPU_BENCHMARK_COMMON_MODES_N; i++) + amdgpu_benchmark_move(adev, common_modes[i], + AMDGPU_GEM_DOMAIN_VRAM, + AMDGPU_GEM_DOMAIN_VRAM); + break; + + default: + DRM_ERROR("Unknown benchmark\n"); + } +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c new file mode 100644 index 000000000000..d7a3ab2624f0 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bios.c @@ -0,0 +1,359 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <drm/drmP.h> +#include "amdgpu.h" +#include "atom.h" + +#include <linux/vga_switcheroo.h> +#include <linux/slab.h> +#include <linux/acpi.h> +/* + * BIOS. + */ + +/* If you boot an IGP board with a discrete card as the primary, + * the IGP rom is not accessible via the rom bar as the IGP rom is + * part of the system bios. On boot, the system bios puts a + * copy of the igp rom at the start of vram if a discrete card is + * present. + */ +static bool igp_read_bios_from_vram(struct amdgpu_device *adev) +{ + uint8_t __iomem *bios; + resource_size_t vram_base; + resource_size_t size = 256 * 1024; /* ??? */ + + if (!(adev->flags & AMDGPU_IS_APU)) + if (!amdgpu_card_posted(adev)) + return false; + + adev->bios = NULL; + vram_base = pci_resource_start(adev->pdev, 0); + bios = ioremap(vram_base, size); + if (!bios) { + return false; + } + + if (size == 0 || bios[0] != 0x55 || bios[1] != 0xaa) { + iounmap(bios); + return false; + } + adev->bios = kmalloc(size, GFP_KERNEL); + if (adev->bios == NULL) { + iounmap(bios); + return false; + } + memcpy_fromio(adev->bios, bios, size); + iounmap(bios); + return true; +} + +bool amdgpu_read_bios(struct amdgpu_device *adev) +{ + uint8_t __iomem *bios; + size_t size; + + adev->bios = NULL; + /* XXX: some cards may return 0 for rom size? ddx has a workaround */ + bios = pci_map_rom(adev->pdev, &size); + if (!bios) { + return false; + } + + if (size == 0 || bios[0] != 0x55 || bios[1] != 0xaa) { + pci_unmap_rom(adev->pdev, bios); + return false; + } + adev->bios = kmemdup(bios, size, GFP_KERNEL); + if (adev->bios == NULL) { + pci_unmap_rom(adev->pdev, bios); + return false; + } + pci_unmap_rom(adev->pdev, bios); + return true; +} + +static bool amdgpu_read_platform_bios(struct amdgpu_device *adev) +{ + uint8_t __iomem *bios; + size_t size; + + adev->bios = NULL; + + bios = pci_platform_rom(adev->pdev, &size); + if (!bios) { + return false; + } + + if (size == 0 || bios[0] != 0x55 || bios[1] != 0xaa) { + return false; + } + adev->bios = kmemdup(bios, size, GFP_KERNEL); + if (adev->bios == NULL) { + return false; + } + + return true; +} + +#ifdef CONFIG_ACPI +/* ATRM is used to get the BIOS on the discrete cards in + * dual-gpu systems. + */ +/* retrieve the ROM in 4k blocks */ +#define ATRM_BIOS_PAGE 4096 +/** + * amdgpu_atrm_call - fetch a chunk of the vbios + * + * @atrm_handle: acpi ATRM handle + * @bios: vbios image pointer + * @offset: offset of vbios image data to fetch + * @len: length of vbios image data to fetch + * + * Executes ATRM to fetch a chunk of the discrete + * vbios image on PX systems (all asics). + * Returns the length of the buffer fetched. + */ +static int amdgpu_atrm_call(acpi_handle atrm_handle, uint8_t *bios, + int offset, int len) +{ + acpi_status status; + union acpi_object atrm_arg_elements[2], *obj; + struct acpi_object_list atrm_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL}; + + atrm_arg.count = 2; + atrm_arg.pointer = &atrm_arg_elements[0]; + + atrm_arg_elements[0].type = ACPI_TYPE_INTEGER; + atrm_arg_elements[0].integer.value = offset; + + atrm_arg_elements[1].type = ACPI_TYPE_INTEGER; + atrm_arg_elements[1].integer.value = len; + + status = acpi_evaluate_object(atrm_handle, NULL, &atrm_arg, &buffer); + if (ACPI_FAILURE(status)) { + printk("failed to evaluate ATRM got %s\n", acpi_format_exception(status)); + return -ENODEV; + } + + obj = (union acpi_object *)buffer.pointer; + memcpy(bios+offset, obj->buffer.pointer, obj->buffer.length); + len = obj->buffer.length; + kfree(buffer.pointer); + return len; +} + +static bool amdgpu_atrm_get_bios(struct amdgpu_device *adev) +{ + int ret; + int size = 256 * 1024; + int i; + struct pci_dev *pdev = NULL; + acpi_handle dhandle, atrm_handle; + acpi_status status; + bool found = false; + + /* ATRM is for the discrete card only */ + if (adev->flags & AMDGPU_IS_APU) + return false; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { + dhandle = ACPI_HANDLE(&pdev->dev); + if (!dhandle) + continue; + + status = acpi_get_handle(dhandle, "ATRM", &atrm_handle); + if (!ACPI_FAILURE(status)) { + found = true; + break; + } + } + + if (!found) { + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_OTHER << 8, pdev)) != NULL) { + dhandle = ACPI_HANDLE(&pdev->dev); + if (!dhandle) + continue; + + status = acpi_get_handle(dhandle, "ATRM", &atrm_handle); + if (!ACPI_FAILURE(status)) { + found = true; + break; + } + } + } + + if (!found) + return false; + + adev->bios = kmalloc(size, GFP_KERNEL); + if (!adev->bios) { + DRM_ERROR("Unable to allocate bios\n"); + return false; + } + + for (i = 0; i < size / ATRM_BIOS_PAGE; i++) { + ret = amdgpu_atrm_call(atrm_handle, + adev->bios, + (i * ATRM_BIOS_PAGE), + ATRM_BIOS_PAGE); + if (ret < ATRM_BIOS_PAGE) + break; + } + + if (i == 0 || adev->bios[0] != 0x55 || adev->bios[1] != 0xaa) { + kfree(adev->bios); + return false; + } + return true; +} +#else +static inline bool amdgpu_atrm_get_bios(struct amdgpu_device *adev) +{ + return false; +} +#endif + +static bool amdgpu_read_disabled_bios(struct amdgpu_device *adev) +{ + if (adev->flags & AMDGPU_IS_APU) + return igp_read_bios_from_vram(adev); + else + return amdgpu_asic_read_disabled_bios(adev); +} + +#ifdef CONFIG_ACPI +static bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) +{ + bool ret = false; + struct acpi_table_header *hdr; + acpi_size tbl_size; + UEFI_ACPI_VFCT *vfct; + GOP_VBIOS_CONTENT *vbios; + VFCT_IMAGE_HEADER *vhdr; + + if (!ACPI_SUCCESS(acpi_get_table_with_size("VFCT", 1, &hdr, &tbl_size))) + return false; + if (tbl_size < sizeof(UEFI_ACPI_VFCT)) { + DRM_ERROR("ACPI VFCT table present but broken (too short #1)\n"); + goto out_unmap; + } + + vfct = (UEFI_ACPI_VFCT *)hdr; + if (vfct->VBIOSImageOffset + sizeof(VFCT_IMAGE_HEADER) > tbl_size) { + DRM_ERROR("ACPI VFCT table present but broken (too short #2)\n"); + goto out_unmap; + } + + vbios = (GOP_VBIOS_CONTENT *)((char *)hdr + vfct->VBIOSImageOffset); + vhdr = &vbios->VbiosHeader; + DRM_INFO("ACPI VFCT contains a BIOS for %02x:%02x.%d %04x:%04x, size %d\n", + vhdr->PCIBus, vhdr->PCIDevice, vhdr->PCIFunction, + vhdr->VendorID, vhdr->DeviceID, vhdr->ImageLength); + + if (vhdr->PCIBus != adev->pdev->bus->number || + vhdr->PCIDevice != PCI_SLOT(adev->pdev->devfn) || + vhdr->PCIFunction != PCI_FUNC(adev->pdev->devfn) || + vhdr->VendorID != adev->pdev->vendor || + vhdr->DeviceID != adev->pdev->device) { + DRM_INFO("ACPI VFCT table is not for this card\n"); + goto out_unmap; + } + + if (vfct->VBIOSImageOffset + sizeof(VFCT_IMAGE_HEADER) + vhdr->ImageLength > tbl_size) { + DRM_ERROR("ACPI VFCT image truncated\n"); + goto out_unmap; + } + + adev->bios = kmemdup(&vbios->VbiosContent, vhdr->ImageLength, GFP_KERNEL); + ret = !!adev->bios; + +out_unmap: + return ret; +} +#else +static inline bool amdgpu_acpi_vfct_bios(struct amdgpu_device *adev) +{ + return false; +} +#endif + +bool amdgpu_get_bios(struct amdgpu_device *adev) +{ + bool r; + uint16_t tmp; + + r = amdgpu_atrm_get_bios(adev); + if (r == false) + r = amdgpu_acpi_vfct_bios(adev); + if (r == false) + r = igp_read_bios_from_vram(adev); + if (r == false) + r = amdgpu_read_bios(adev); + if (r == false) { + r = amdgpu_read_disabled_bios(adev); + } + if (r == false) { + r = amdgpu_read_platform_bios(adev); + } + if (r == false || adev->bios == NULL) { + DRM_ERROR("Unable to locate a BIOS ROM\n"); + adev->bios = NULL; + return false; + } + if (adev->bios[0] != 0x55 || adev->bios[1] != 0xaa) { + printk("BIOS signature incorrect %x %x\n", adev->bios[0], adev->bios[1]); + goto free_bios; + } + + tmp = RBIOS16(0x18); + if (RBIOS8(tmp + 0x14) != 0x0) { + DRM_INFO("Not an x86 BIOS ROM, not using.\n"); + goto free_bios; + } + + adev->bios_header_start = RBIOS16(0x48); + if (!adev->bios_header_start) { + goto free_bios; + } + tmp = adev->bios_header_start + 4; + if (!memcmp(adev->bios + tmp, "ATOM", 4) || + !memcmp(adev->bios + tmp, "MOTA", 4)) { + adev->is_atom_bios = true; + } else { + adev->is_atom_bios = false; + } + + DRM_DEBUG("%sBIOS detected\n", adev->is_atom_bios ? "ATOM" : "COM"); + return true; +free_bios: + kfree(adev->bios); + adev->bios = NULL; + return false; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c new file mode 100644 index 000000000000..819fb861ac04 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_bo_list.c @@ -0,0 +1,268 @@ +/* + * Copyright 2015 Advanced Micro Devices, Inc. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Christian König <deathsimple@vodafone.de> + */ + +#include <drm/drmP.h> +#include "amdgpu.h" + +static int amdgpu_bo_list_create(struct amdgpu_fpriv *fpriv, + struct amdgpu_bo_list **result, + int *id) +{ + int r; + + *result = kzalloc(sizeof(struct amdgpu_bo_list), GFP_KERNEL); + if (!*result) + return -ENOMEM; + + mutex_lock(&fpriv->bo_list_lock); + r = idr_alloc(&fpriv->bo_list_handles, *result, + 0, 0, GFP_KERNEL); + if (r < 0) { + mutex_unlock(&fpriv->bo_list_lock); + kfree(*result); + return r; + } + *id = r; + + mutex_init(&(*result)->lock); + (*result)->num_entries = 0; + (*result)->array = NULL; + + mutex_lock(&(*result)->lock); + mutex_unlock(&fpriv->bo_list_lock); + + return 0; +} + +static void amdgpu_bo_list_destroy(struct amdgpu_fpriv *fpriv, int id) +{ + struct amdgpu_bo_list *list; + + mutex_lock(&fpriv->bo_list_lock); + list = idr_find(&fpriv->bo_list_handles, id); + if (list) { + mutex_lock(&list->lock); + idr_remove(&fpriv->bo_list_handles, id); + mutex_unlock(&list->lock); + amdgpu_bo_list_free(list); + } + mutex_unlock(&fpriv->bo_list_lock); +} + +static int amdgpu_bo_list_set(struct amdgpu_device *adev, + struct drm_file *filp, + struct amdgpu_bo_list *list, + struct drm_amdgpu_bo_list_entry *info, + unsigned num_entries) +{ + struct amdgpu_bo_list_entry *array; + struct amdgpu_bo *gds_obj = adev->gds.gds_gfx_bo; + struct amdgpu_bo *gws_obj = adev->gds.gws_gfx_bo; + struct amdgpu_bo *oa_obj = adev->gds.oa_gfx_bo; + + bool has_userptr = false; + unsigned i; + + array = drm_malloc_ab(num_entries, sizeof(struct amdgpu_bo_list_entry)); + if (!array) + return -ENOMEM; + memset(array, 0, num_entries * sizeof(struct amdgpu_bo_list_entry)); + + for (i = 0; i < num_entries; ++i) { + struct amdgpu_bo_list_entry *entry = &array[i]; + struct drm_gem_object *gobj; + + gobj = drm_gem_object_lookup(adev->ddev, filp, info[i].bo_handle); + if (!gobj) + goto error_free; + + entry->robj = amdgpu_bo_ref(gem_to_amdgpu_bo(gobj)); + drm_gem_object_unreference_unlocked(gobj); + entry->priority = info[i].bo_priority; + entry->prefered_domains = entry->robj->initial_domain; + entry->allowed_domains = entry->prefered_domains; + if (entry->allowed_domains == AMDGPU_GEM_DOMAIN_VRAM) + entry->allowed_domains |= AMDGPU_GEM_DOMAIN_GTT; + if (amdgpu_ttm_tt_has_userptr(entry->robj->tbo.ttm)) { + has_userptr = true; + entry->prefered_domains = AMDGPU_GEM_DOMAIN_GTT; + entry->allowed_domains = AMDGPU_GEM_DOMAIN_GTT; + } + entry->tv.bo = &entry->robj->tbo; + entry->tv.shared = true; + + if (entry->prefered_domains == AMDGPU_GEM_DOMAIN_GDS) + gds_obj = entry->robj; + if (entry->prefered_domains == AMDGPU_GEM_DOMAIN_GWS) + gws_obj = entry->robj; + if (entry->prefered_domains == AMDGPU_GEM_DOMAIN_OA) + oa_obj = entry->robj; + } + + for (i = 0; i < list->num_entries; ++i) + amdgpu_bo_unref(&list->array[i].robj); + + drm_free_large(list->array); + + list->gds_obj = gds_obj; + list->gws_obj = gws_obj; + list->oa_obj = oa_obj; + list->has_userptr = has_userptr; + list->array = array; + list->num_entries = num_entries; + + return 0; + +error_free: + drm_free_large(array); + return -ENOENT; +} + +struct amdgpu_bo_list * +amdgpu_bo_list_get(struct amdgpu_fpriv *fpriv, int id) +{ + struct amdgpu_bo_list *result; + + mutex_lock(&fpriv->bo_list_lock); + result = idr_find(&fpriv->bo_list_handles, id); + if (result) + mutex_lock(&result->lock); + mutex_unlock(&fpriv->bo_list_lock); + return result; +} + +void amdgpu_bo_list_put(struct amdgpu_bo_list *list) +{ + mutex_unlock(&list->lock); +} + +void amdgpu_bo_list_free(struct amdgpu_bo_list *list) +{ + unsigned i; + + for (i = 0; i < list->num_entries; ++i) + amdgpu_bo_unref(&list->array[i].robj); + + mutex_destroy(&list->lock); + drm_free_large(list->array); + kfree(list); +} + +int amdgpu_bo_list_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + const uint32_t info_size = sizeof(struct drm_amdgpu_bo_list_entry); + + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_fpriv *fpriv = filp->driver_priv; + union drm_amdgpu_bo_list *args = data; + uint32_t handle = args->in.list_handle; + const void __user *uptr = (const void*)(long)args->in.bo_info_ptr; + + struct drm_amdgpu_bo_list_entry *info; + struct amdgpu_bo_list *list; + + int r; + + info = drm_malloc_ab(args->in.bo_number, + sizeof(struct drm_amdgpu_bo_list_entry)); + if (!info) + return -ENOMEM; + + /* copy the handle array from userspace to a kernel buffer */ + r = -EFAULT; + if (likely(info_size == args->in.bo_info_size)) { + unsigned long bytes = args->in.bo_number * + args->in.bo_info_size; + + if (copy_from_user(info, uptr, bytes)) + goto error_free; + + } else { + unsigned long bytes = min(args->in.bo_info_size, info_size); + unsigned i; + + memset(info, 0, args->in.bo_number * info_size); + for (i = 0; i < args->in.bo_number; ++i) { + if (copy_from_user(&info[i], uptr, bytes)) + goto error_free; + + uptr += args->in.bo_info_size; + } + } + + switch (args->in.operation) { + case AMDGPU_BO_LIST_OP_CREATE: + r = amdgpu_bo_list_create(fpriv, &list, &handle); + if (r) + goto error_free; + + r = amdgpu_bo_list_set(adev, filp, list, info, + args->in.bo_number); + amdgpu_bo_list_put(list); + if (r) + goto error_free; + + break; + + case AMDGPU_BO_LIST_OP_DESTROY: + amdgpu_bo_list_destroy(fpriv, handle); + handle = 0; + break; + + case AMDGPU_BO_LIST_OP_UPDATE: + r = -ENOENT; + list = amdgpu_bo_list_get(fpriv, handle); + if (!list) + goto error_free; + + r = amdgpu_bo_list_set(adev, filp, list, info, + args->in.bo_number); + amdgpu_bo_list_put(list); + if (r) + goto error_free; + + break; + + default: + r = -EINVAL; + goto error_free; + } + + memset(args, 0, sizeof(*args)); + args->out.list_handle = handle; + drm_free_large(info); + + return 0; + +error_free: + drm_free_large(info); + return r; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c new file mode 100644 index 000000000000..6a8d28f81780 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c @@ -0,0 +1,1907 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include <drm/drmP.h> +#include <drm/drm_edid.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "atom.h" +#include "atombios_encoders.h" +#include "atombios_dp.h" +#include "amdgpu_connectors.h" +#include "amdgpu_i2c.h" + +#include <linux/pm_runtime.h> + +void amdgpu_connector_hotplug(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + + /* bail if the connector does not have hpd pin, e.g., + * VGA, TV, etc. + */ + if (amdgpu_connector->hpd.hpd == AMDGPU_HPD_NONE) + return; + + amdgpu_display_hpd_set_polarity(adev, amdgpu_connector->hpd.hpd); + + /* if the connector is already off, don't turn it back on */ + if (connector->dpms != DRM_MODE_DPMS_ON) + return; + + /* just deal with DP (not eDP) here. */ + if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + struct amdgpu_connector_atom_dig *dig_connector = + amdgpu_connector->con_priv; + + /* if existing sink type was not DP no need to retrain */ + if (dig_connector->dp_sink_type != CONNECTOR_OBJECT_ID_DISPLAYPORT) + return; + + /* first get sink type as it may be reset after (un)plug */ + dig_connector->dp_sink_type = amdgpu_atombios_dp_get_sinktype(amdgpu_connector); + /* don't do anything if sink is not display port, i.e., + * passive dp->(dvi|hdmi) adaptor + */ + if (dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) { + int saved_dpms = connector->dpms; + /* Only turn off the display if it's physically disconnected */ + if (!amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) { + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + } else if (amdgpu_atombios_dp_needs_link_train(amdgpu_connector)) { + /* set it to OFF so that drm_helper_connector_dpms() + * won't return immediately since the current state + * is ON at this point. + */ + connector->dpms = DRM_MODE_DPMS_OFF; + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); + } + connector->dpms = saved_dpms; + } + } +} + +static void amdgpu_connector_property_change_mode(struct drm_encoder *encoder) +{ + struct drm_crtc *crtc = encoder->crtc; + + if (crtc && crtc->enabled) { + drm_crtc_helper_set_mode(crtc, &crtc->mode, + crtc->x, crtc->y, crtc->primary->fb); + } +} + +int amdgpu_connector_get_monitor_bpc(struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector; + int bpc = 8; + unsigned mode_clock, max_tmds_clock; + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_HDMIB: + if (amdgpu_connector->use_digital) { + if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + if (connector->display_info.bpc) + bpc = connector->display_info.bpc; + } + } + break; + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_HDMIA: + if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + if (connector->display_info.bpc) + bpc = connector->display_info.bpc; + } + break; + case DRM_MODE_CONNECTOR_DisplayPort: + dig_connector = amdgpu_connector->con_priv; + if ((dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) || + (dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) || + drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + if (connector->display_info.bpc) + bpc = connector->display_info.bpc; + } + break; + case DRM_MODE_CONNECTOR_eDP: + case DRM_MODE_CONNECTOR_LVDS: + if (connector->display_info.bpc) + bpc = connector->display_info.bpc; + else { + struct drm_connector_helper_funcs *connector_funcs = + connector->helper_private; + struct drm_encoder *encoder = connector_funcs->best_encoder(connector); + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; + + if (dig->lcd_misc & ATOM_PANEL_MISC_V13_6BIT_PER_COLOR) + bpc = 6; + else if (dig->lcd_misc & ATOM_PANEL_MISC_V13_8BIT_PER_COLOR) + bpc = 8; + } + break; + } + + if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + /* + * Pre DCE-8 hw can't handle > 12 bpc, and more than 12 bpc doesn't make + * much sense without support for > 12 bpc framebuffers. RGB 4:4:4 at + * 12 bpc is always supported on hdmi deep color sinks, as this is + * required by the HDMI-1.3 spec. Clamp to a safe 12 bpc maximum. + */ + if (bpc > 12) { + DRM_DEBUG("%s: HDMI deep color %d bpc unsupported. Using 12 bpc.\n", + connector->name, bpc); + bpc = 12; + } + + /* Any defined maximum tmds clock limit we must not exceed? */ + if (connector->max_tmds_clock > 0) { + /* mode_clock is clock in kHz for mode to be modeset on this connector */ + mode_clock = amdgpu_connector->pixelclock_for_modeset; + + /* Maximum allowable input clock in kHz */ + max_tmds_clock = connector->max_tmds_clock * 1000; + + DRM_DEBUG("%s: hdmi mode dotclock %d kHz, max tmds input clock %d kHz.\n", + connector->name, mode_clock, max_tmds_clock); + + /* Check if bpc is within clock limit. Try to degrade gracefully otherwise */ + if ((bpc == 12) && (mode_clock * 3/2 > max_tmds_clock)) { + if ((connector->display_info.edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30) && + (mode_clock * 5/4 <= max_tmds_clock)) + bpc = 10; + else + bpc = 8; + + DRM_DEBUG("%s: HDMI deep color 12 bpc exceeds max tmds clock. Using %d bpc.\n", + connector->name, bpc); + } + + if ((bpc == 10) && (mode_clock * 5/4 > max_tmds_clock)) { + bpc = 8; + DRM_DEBUG("%s: HDMI deep color 10 bpc exceeds max tmds clock. Using %d bpc.\n", + connector->name, bpc); + } else if (bpc > 8) { + /* max_tmds_clock missing, but hdmi spec mandates it for deep color. */ + DRM_DEBUG("%s: Required max tmds clock for HDMI deep color missing. Using 8 bpc.\n", + connector->name); + bpc = 8; + } + } + } + + if ((amdgpu_deep_color == 0) && (bpc > 8)) { + DRM_DEBUG("%s: Deep color disabled. Set amdgpu module param deep_color=1 to enable.\n", + connector->name); + bpc = 8; + } + + DRM_DEBUG("%s: Display bpc=%d, returned bpc=%d\n", + connector->name, connector->display_info.bpc, bpc); + + return bpc; +} + +static void +amdgpu_connector_update_scratch_regs(struct drm_connector *connector, + enum drm_connector_status status) +{ + struct drm_encoder *best_encoder = NULL; + struct drm_encoder *encoder = NULL; + struct drm_connector_helper_funcs *connector_funcs = connector->helper_private; + bool connected; + int i; + + best_encoder = connector_funcs->best_encoder(connector); + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + if (connector->encoder_ids[i] == 0) + break; + + encoder = drm_encoder_find(connector->dev, + connector->encoder_ids[i]); + if (!encoder) + continue; + + if ((encoder == best_encoder) && (status == connector_status_connected)) + connected = true; + else + connected = false; + + amdgpu_atombios_encoder_set_bios_scratch_regs(connector, encoder, connected); + + } +} + +static struct drm_encoder * +amdgpu_connector_find_encoder(struct drm_connector *connector, + int encoder_type) +{ + struct drm_encoder *encoder; + int i; + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + if (connector->encoder_ids[i] == 0) + break; + encoder = drm_encoder_find(connector->dev, + connector->encoder_ids[i]); + if (!encoder) + continue; + + if (encoder->encoder_type == encoder_type) + return encoder; + } + return NULL; +} + +struct edid *amdgpu_connector_edid(struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct drm_property_blob *edid_blob = connector->edid_blob_ptr; + + if (amdgpu_connector->edid) { + return amdgpu_connector->edid; + } else if (edid_blob) { + struct edid *edid = kmemdup(edid_blob->data, edid_blob->length, GFP_KERNEL); + if (edid) + amdgpu_connector->edid = edid; + } + return amdgpu_connector->edid; +} + +static struct edid * +amdgpu_connector_get_hardcoded_edid(struct amdgpu_device *adev) +{ + struct edid *edid; + + if (adev->mode_info.bios_hardcoded_edid) { + edid = kmalloc(adev->mode_info.bios_hardcoded_edid_size, GFP_KERNEL); + if (edid) { + memcpy((unsigned char *)edid, + (unsigned char *)adev->mode_info.bios_hardcoded_edid, + adev->mode_info.bios_hardcoded_edid_size); + return edid; + } + } + return NULL; +} + +static void amdgpu_connector_get_edid(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + + if (amdgpu_connector->edid) + return; + + /* on hw with routers, select right port */ + if (amdgpu_connector->router.ddc_valid) + amdgpu_i2c_router_select_ddc_port(amdgpu_connector); + + if ((amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) != + ENCODER_OBJECT_ID_NONE) && + amdgpu_connector->ddc_bus->has_aux) { + amdgpu_connector->edid = drm_get_edid(connector, + &amdgpu_connector->ddc_bus->aux.ddc); + } else if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) || + (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) { + struct amdgpu_connector_atom_dig *dig = amdgpu_connector->con_priv; + + if ((dig->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT || + dig->dp_sink_type == CONNECTOR_OBJECT_ID_eDP) && + amdgpu_connector->ddc_bus->has_aux) + amdgpu_connector->edid = drm_get_edid(connector, + &amdgpu_connector->ddc_bus->aux.ddc); + else if (amdgpu_connector->ddc_bus) + amdgpu_connector->edid = drm_get_edid(connector, + &amdgpu_connector->ddc_bus->adapter); + } else if (amdgpu_connector->ddc_bus) { + amdgpu_connector->edid = drm_get_edid(connector, + &amdgpu_connector->ddc_bus->adapter); + } + + if (!amdgpu_connector->edid) { + /* some laptops provide a hardcoded edid in rom for LCDs */ + if (((connector->connector_type == DRM_MODE_CONNECTOR_LVDS) || + (connector->connector_type == DRM_MODE_CONNECTOR_eDP))) + amdgpu_connector->edid = amdgpu_connector_get_hardcoded_edid(adev); + } +} + +static void amdgpu_connector_free_edid(struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + + if (amdgpu_connector->edid) { + kfree(amdgpu_connector->edid); + amdgpu_connector->edid = NULL; + } +} + +static int amdgpu_connector_ddc_get_modes(struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + int ret; + + if (amdgpu_connector->edid) { + drm_mode_connector_update_edid_property(connector, amdgpu_connector->edid); + ret = drm_add_edid_modes(connector, amdgpu_connector->edid); + drm_edid_to_eld(connector, amdgpu_connector->edid); + return ret; + } + drm_mode_connector_update_edid_property(connector, NULL); + return 0; +} + +static struct drm_encoder * +amdgpu_connector_best_single_encoder(struct drm_connector *connector) +{ + int enc_id = connector->encoder_ids[0]; + + /* pick the encoder ids */ + if (enc_id) + return drm_encoder_find(connector->dev, enc_id); + return NULL; +} + +static void amdgpu_get_native_mode(struct drm_connector *connector) +{ + struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector); + struct amdgpu_encoder *amdgpu_encoder; + + if (encoder == NULL) + return; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + if (!list_empty(&connector->probed_modes)) { + struct drm_display_mode *preferred_mode = + list_first_entry(&connector->probed_modes, + struct drm_display_mode, head); + + amdgpu_encoder->native_mode = *preferred_mode; + } else { + amdgpu_encoder->native_mode.clock = 0; + } +} + +static struct drm_display_mode * +amdgpu_connector_lcd_native_mode(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *mode = NULL; + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + + if (native_mode->hdisplay != 0 && + native_mode->vdisplay != 0 && + native_mode->clock != 0) { + mode = drm_mode_duplicate(dev, native_mode); + mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + drm_mode_set_name(mode); + + DRM_DEBUG_KMS("Adding native panel mode %s\n", mode->name); + } else if (native_mode->hdisplay != 0 && + native_mode->vdisplay != 0) { + /* mac laptops without an edid */ + /* Note that this is not necessarily the exact panel mode, + * but an approximation based on the cvt formula. For these + * systems we should ideally read the mode info out of the + * registers or add a mode table, but this works and is much + * simpler. + */ + mode = drm_cvt_mode(dev, native_mode->hdisplay, native_mode->vdisplay, 60, true, false, false); + mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + DRM_DEBUG_KMS("Adding cvt approximation of native panel mode %s\n", mode->name); + } + return mode; +} + +static void amdgpu_connector_add_common_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *mode = NULL; + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + int i; + struct mode_size { + int w; + int h; + } common_modes[17] = { + { 640, 480}, + { 720, 480}, + { 800, 600}, + { 848, 480}, + {1024, 768}, + {1152, 768}, + {1280, 720}, + {1280, 800}, + {1280, 854}, + {1280, 960}, + {1280, 1024}, + {1440, 900}, + {1400, 1050}, + {1680, 1050}, + {1600, 1200}, + {1920, 1080}, + {1920, 1200} + }; + + for (i = 0; i < 17; i++) { + if (amdgpu_encoder->devices & (ATOM_DEVICE_TV_SUPPORT)) { + if (common_modes[i].w > 1024 || + common_modes[i].h > 768) + continue; + } + if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) { + if (common_modes[i].w > native_mode->hdisplay || + common_modes[i].h > native_mode->vdisplay || + (common_modes[i].w == native_mode->hdisplay && + common_modes[i].h == native_mode->vdisplay)) + continue; + } + if (common_modes[i].w < 320 || common_modes[i].h < 200) + continue; + + mode = drm_cvt_mode(dev, common_modes[i].w, common_modes[i].h, 60, false, false, false); + drm_mode_probed_add(connector, mode); + } +} + +static int amdgpu_connector_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + + if (property == adev->mode_info.coherent_mode_property) { + struct amdgpu_encoder_atom_dig *dig; + bool new_coherent_mode; + + /* need to find digital encoder on connector */ + encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS); + if (!encoder) + return 0; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + if (!amdgpu_encoder->enc_priv) + return 0; + + dig = amdgpu_encoder->enc_priv; + new_coherent_mode = val ? true : false; + if (dig->coherent_mode != new_coherent_mode) { + dig->coherent_mode = new_coherent_mode; + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + } + } + + if (property == adev->mode_info.audio_property) { + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + /* need to find digital encoder on connector */ + encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS); + if (!encoder) + return 0; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + if (amdgpu_connector->audio != val) { + amdgpu_connector->audio = val; + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + } + } + + if (property == adev->mode_info.dither_property) { + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + /* need to find digital encoder on connector */ + encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS); + if (!encoder) + return 0; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + if (amdgpu_connector->dither != val) { + amdgpu_connector->dither = val; + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + } + } + + if (property == adev->mode_info.underscan_property) { + /* need to find digital encoder on connector */ + encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS); + if (!encoder) + return 0; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + if (amdgpu_encoder->underscan_type != val) { + amdgpu_encoder->underscan_type = val; + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + } + } + + if (property == adev->mode_info.underscan_hborder_property) { + /* need to find digital encoder on connector */ + encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS); + if (!encoder) + return 0; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + if (amdgpu_encoder->underscan_hborder != val) { + amdgpu_encoder->underscan_hborder = val; + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + } + } + + if (property == adev->mode_info.underscan_vborder_property) { + /* need to find digital encoder on connector */ + encoder = amdgpu_connector_find_encoder(connector, DRM_MODE_ENCODER_TMDS); + if (!encoder) + return 0; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + if (amdgpu_encoder->underscan_vborder != val) { + amdgpu_encoder->underscan_vborder = val; + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + } + } + + if (property == adev->mode_info.load_detect_property) { + struct amdgpu_connector *amdgpu_connector = + to_amdgpu_connector(connector); + + if (val == 0) + amdgpu_connector->dac_load_detect = false; + else + amdgpu_connector->dac_load_detect = true; + } + + if (property == dev->mode_config.scaling_mode_property) { + enum amdgpu_rmx_type rmx_type; + + if (connector->encoder) { + amdgpu_encoder = to_amdgpu_encoder(connector->encoder); + } else { + struct drm_connector_helper_funcs *connector_funcs = connector->helper_private; + amdgpu_encoder = to_amdgpu_encoder(connector_funcs->best_encoder(connector)); + } + + switch (val) { + default: + case DRM_MODE_SCALE_NONE: rmx_type = RMX_OFF; break; + case DRM_MODE_SCALE_CENTER: rmx_type = RMX_CENTER; break; + case DRM_MODE_SCALE_ASPECT: rmx_type = RMX_ASPECT; break; + case DRM_MODE_SCALE_FULLSCREEN: rmx_type = RMX_FULL; break; + } + if (amdgpu_encoder->rmx_type == rmx_type) + return 0; + + if ((rmx_type != DRM_MODE_SCALE_NONE) && + (amdgpu_encoder->native_mode.clock == 0)) + return 0; + + amdgpu_encoder->rmx_type = rmx_type; + + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + } + + return 0; +} + +static void +amdgpu_connector_fixup_lcd_native_mode(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + struct drm_display_mode *t, *mode; + + /* If the EDID preferred mode doesn't match the native mode, use it */ + list_for_each_entry_safe(mode, t, &connector->probed_modes, head) { + if (mode->type & DRM_MODE_TYPE_PREFERRED) { + if (mode->hdisplay != native_mode->hdisplay || + mode->vdisplay != native_mode->vdisplay) + memcpy(native_mode, mode, sizeof(*mode)); + } + } + + /* Try to get native mode details from EDID if necessary */ + if (!native_mode->clock) { + list_for_each_entry_safe(mode, t, &connector->probed_modes, head) { + if (mode->hdisplay == native_mode->hdisplay && + mode->vdisplay == native_mode->vdisplay) { + *native_mode = *mode; + drm_mode_set_crtcinfo(native_mode, CRTC_INTERLACE_HALVE_V); + DRM_DEBUG_KMS("Determined LVDS native mode details from EDID\n"); + break; + } + } + } + + if (!native_mode->clock) { + DRM_DEBUG_KMS("No LVDS native mode details, disabling RMX\n"); + amdgpu_encoder->rmx_type = RMX_OFF; + } +} + +static int amdgpu_connector_lvds_get_modes(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + int ret = 0; + struct drm_display_mode *mode; + + amdgpu_connector_get_edid(connector); + ret = amdgpu_connector_ddc_get_modes(connector); + if (ret > 0) { + encoder = amdgpu_connector_best_single_encoder(connector); + if (encoder) { + amdgpu_connector_fixup_lcd_native_mode(encoder, connector); + /* add scaled modes */ + amdgpu_connector_add_common_modes(encoder, connector); + } + return ret; + } + + encoder = amdgpu_connector_best_single_encoder(connector); + if (!encoder) + return 0; + + /* we have no EDID modes */ + mode = amdgpu_connector_lcd_native_mode(encoder); + if (mode) { + ret = 1; + drm_mode_probed_add(connector, mode); + /* add the width/height from vbios tables if available */ + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + /* add scaled modes */ + amdgpu_connector_add_common_modes(encoder, connector); + } + + return ret; +} + +static int amdgpu_connector_lvds_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector); + + if ((mode->hdisplay < 320) || (mode->vdisplay < 240)) + return MODE_PANEL; + + if (encoder) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + + /* AVIVO hardware supports downscaling modes larger than the panel + * to the panel size, but I'm not sure this is desirable. + */ + if ((mode->hdisplay > native_mode->hdisplay) || + (mode->vdisplay > native_mode->vdisplay)) + return MODE_PANEL; + + /* if scaling is disabled, block non-native modes */ + if (amdgpu_encoder->rmx_type == RMX_OFF) { + if ((mode->hdisplay != native_mode->hdisplay) || + (mode->vdisplay != native_mode->vdisplay)) + return MODE_PANEL; + } + } + + return MODE_OK; +} + +static enum drm_connector_status +amdgpu_connector_lvds_detect(struct drm_connector *connector, bool force) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector); + enum drm_connector_status ret = connector_status_disconnected; + int r; + + r = pm_runtime_get_sync(connector->dev->dev); + if (r < 0) + return connector_status_disconnected; + + if (encoder) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + + /* check if panel is valid */ + if (native_mode->hdisplay >= 320 && native_mode->vdisplay >= 240) + ret = connector_status_connected; + + } + + /* check for edid as well */ + amdgpu_connector_get_edid(connector); + if (amdgpu_connector->edid) + ret = connector_status_connected; + /* check acpi lid status ??? */ + + amdgpu_connector_update_scratch_regs(connector, ret); + pm_runtime_mark_last_busy(connector->dev->dev); + pm_runtime_put_autosuspend(connector->dev->dev); + return ret; +} + +static void amdgpu_connector_destroy(struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + + if (amdgpu_connector->ddc_bus->has_aux) + drm_dp_aux_unregister(&amdgpu_connector->ddc_bus->aux); + amdgpu_connector_free_edid(connector); + kfree(amdgpu_connector->con_priv); + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + kfree(connector); +} + +static int amdgpu_connector_set_lcd_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t value) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_encoder *amdgpu_encoder; + enum amdgpu_rmx_type rmx_type; + + DRM_DEBUG_KMS("\n"); + if (property != dev->mode_config.scaling_mode_property) + return 0; + + if (connector->encoder) + amdgpu_encoder = to_amdgpu_encoder(connector->encoder); + else { + struct drm_connector_helper_funcs *connector_funcs = connector->helper_private; + amdgpu_encoder = to_amdgpu_encoder(connector_funcs->best_encoder(connector)); + } + + switch (value) { + case DRM_MODE_SCALE_NONE: rmx_type = RMX_OFF; break; + case DRM_MODE_SCALE_CENTER: rmx_type = RMX_CENTER; break; + case DRM_MODE_SCALE_ASPECT: rmx_type = RMX_ASPECT; break; + default: + case DRM_MODE_SCALE_FULLSCREEN: rmx_type = RMX_FULL; break; + } + if (amdgpu_encoder->rmx_type == rmx_type) + return 0; + + amdgpu_encoder->rmx_type = rmx_type; + + amdgpu_connector_property_change_mode(&amdgpu_encoder->base); + return 0; +} + + +static const struct drm_connector_helper_funcs amdgpu_connector_lvds_helper_funcs = { + .get_modes = amdgpu_connector_lvds_get_modes, + .mode_valid = amdgpu_connector_lvds_mode_valid, + .best_encoder = amdgpu_connector_best_single_encoder, +}; + +static const struct drm_connector_funcs amdgpu_connector_lvds_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = amdgpu_connector_lvds_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = amdgpu_connector_destroy, + .set_property = amdgpu_connector_set_lcd_property, +}; + +static int amdgpu_connector_vga_get_modes(struct drm_connector *connector) +{ + int ret; + + amdgpu_connector_get_edid(connector); + ret = amdgpu_connector_ddc_get_modes(connector); + + return ret; +} + +static int amdgpu_connector_vga_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + + /* XXX check mode bandwidth */ + + if ((mode->clock / 10) > adev->clock.max_pixel_clock) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static enum drm_connector_status +amdgpu_connector_vga_detect(struct drm_connector *connector, bool force) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct drm_encoder *encoder; + struct drm_encoder_helper_funcs *encoder_funcs; + bool dret = false; + enum drm_connector_status ret = connector_status_disconnected; + int r; + + r = pm_runtime_get_sync(connector->dev->dev); + if (r < 0) + return connector_status_disconnected; + + encoder = amdgpu_connector_best_single_encoder(connector); + if (!encoder) + ret = connector_status_disconnected; + + if (amdgpu_connector->ddc_bus) + dret = amdgpu_ddc_probe(amdgpu_connector, false); + if (dret) { + amdgpu_connector->detected_by_load = false; + amdgpu_connector_free_edid(connector); + amdgpu_connector_get_edid(connector); + + if (!amdgpu_connector->edid) { + DRM_ERROR("%s: probed a monitor but no|invalid EDID\n", + connector->name); + ret = connector_status_connected; + } else { + amdgpu_connector->use_digital = + !!(amdgpu_connector->edid->input & DRM_EDID_INPUT_DIGITAL); + + /* some oems have boards with separate digital and analog connectors + * with a shared ddc line (often vga + hdmi) + */ + if (amdgpu_connector->use_digital && amdgpu_connector->shared_ddc) { + amdgpu_connector_free_edid(connector); + ret = connector_status_disconnected; + } else { + ret = connector_status_connected; + } + } + } else { + + /* if we aren't forcing don't do destructive polling */ + if (!force) { + /* only return the previous status if we last + * detected a monitor via load. + */ + if (amdgpu_connector->detected_by_load) + ret = connector->status; + goto out; + } + + if (amdgpu_connector->dac_load_detect && encoder) { + encoder_funcs = encoder->helper_private; + ret = encoder_funcs->detect(encoder, connector); + if (ret != connector_status_disconnected) + amdgpu_connector->detected_by_load = true; + } + } + + amdgpu_connector_update_scratch_regs(connector, ret); + +out: + pm_runtime_mark_last_busy(connector->dev->dev); + pm_runtime_put_autosuspend(connector->dev->dev); + + return ret; +} + +static const struct drm_connector_helper_funcs amdgpu_connector_vga_helper_funcs = { + .get_modes = amdgpu_connector_vga_get_modes, + .mode_valid = amdgpu_connector_vga_mode_valid, + .best_encoder = amdgpu_connector_best_single_encoder, +}; + +static const struct drm_connector_funcs amdgpu_connector_vga_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = amdgpu_connector_vga_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = amdgpu_connector_destroy, + .set_property = amdgpu_connector_set_property, +}; + +static bool +amdgpu_connector_check_hpd_status_unchanged(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + enum drm_connector_status status; + + if (amdgpu_connector->hpd.hpd != AMDGPU_HPD_NONE) { + if (amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) + status = connector_status_connected; + else + status = connector_status_disconnected; + if (connector->status == status) + return true; + } + + return false; +} + +/* + * DVI is complicated + * Do a DDC probe, if DDC probe passes, get the full EDID so + * we can do analog/digital monitor detection at this point. + * If the monitor is an analog monitor or we got no DDC, + * we need to find the DAC encoder object for this connector. + * If we got no DDC, we do load detection on the DAC encoder object. + * If we got analog DDC or load detection passes on the DAC encoder + * we have to check if this analog encoder is shared with anyone else (TV) + * if its shared we have to set the other connector to disconnected. + */ +static enum drm_connector_status +amdgpu_connector_dvi_detect(struct drm_connector *connector, bool force) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct drm_encoder *encoder = NULL; + struct drm_encoder_helper_funcs *encoder_funcs; + int i, r; + enum drm_connector_status ret = connector_status_disconnected; + bool dret = false, broken_edid = false; + + r = pm_runtime_get_sync(connector->dev->dev); + if (r < 0) + return connector_status_disconnected; + + if (!force && amdgpu_connector_check_hpd_status_unchanged(connector)) { + ret = connector->status; + goto exit; + } + + if (amdgpu_connector->ddc_bus) + dret = amdgpu_ddc_probe(amdgpu_connector, false); + if (dret) { + amdgpu_connector->detected_by_load = false; + amdgpu_connector_free_edid(connector); + amdgpu_connector_get_edid(connector); + + if (!amdgpu_connector->edid) { + DRM_ERROR("%s: probed a monitor but no|invalid EDID\n", + connector->name); + ret = connector_status_connected; + broken_edid = true; /* defer use_digital to later */ + } else { + amdgpu_connector->use_digital = + !!(amdgpu_connector->edid->input & DRM_EDID_INPUT_DIGITAL); + + /* some oems have boards with separate digital and analog connectors + * with a shared ddc line (often vga + hdmi) + */ + if ((!amdgpu_connector->use_digital) && amdgpu_connector->shared_ddc) { + amdgpu_connector_free_edid(connector); + ret = connector_status_disconnected; + } else { + ret = connector_status_connected; + } + + /* This gets complicated. We have boards with VGA + HDMI with a + * shared DDC line and we have boards with DVI-D + HDMI with a shared + * DDC line. The latter is more complex because with DVI<->HDMI adapters + * you don't really know what's connected to which port as both are digital. + */ + if (amdgpu_connector->shared_ddc && (ret == connector_status_connected)) { + struct drm_connector *list_connector; + struct amdgpu_connector *list_amdgpu_connector; + list_for_each_entry(list_connector, &dev->mode_config.connector_list, head) { + if (connector == list_connector) + continue; + list_amdgpu_connector = to_amdgpu_connector(list_connector); + if (list_amdgpu_connector->shared_ddc && + (list_amdgpu_connector->ddc_bus->rec.i2c_id == + amdgpu_connector->ddc_bus->rec.i2c_id)) { + /* cases where both connectors are digital */ + if (list_connector->connector_type != DRM_MODE_CONNECTOR_VGA) { + /* hpd is our only option in this case */ + if (!amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) { + amdgpu_connector_free_edid(connector); + ret = connector_status_disconnected; + } + } + } + } + } + } + } + + if ((ret == connector_status_connected) && (amdgpu_connector->use_digital == true)) + goto out; + + /* DVI-D and HDMI-A are digital only */ + if ((connector->connector_type == DRM_MODE_CONNECTOR_DVID) || + (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA)) + goto out; + + /* if we aren't forcing don't do destructive polling */ + if (!force) { + /* only return the previous status if we last + * detected a monitor via load. + */ + if (amdgpu_connector->detected_by_load) + ret = connector->status; + goto out; + } + + /* find analog encoder */ + if (amdgpu_connector->dac_load_detect) { + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + if (connector->encoder_ids[i] == 0) + break; + + encoder = drm_encoder_find(connector->dev, connector->encoder_ids[i]); + if (!encoder) + continue; + + if (encoder->encoder_type != DRM_MODE_ENCODER_DAC && + encoder->encoder_type != DRM_MODE_ENCODER_TVDAC) + continue; + + encoder_funcs = encoder->helper_private; + if (encoder_funcs->detect) { + if (!broken_edid) { + if (ret != connector_status_connected) { + /* deal with analog monitors without DDC */ + ret = encoder_funcs->detect(encoder, connector); + if (ret == connector_status_connected) { + amdgpu_connector->use_digital = false; + } + if (ret != connector_status_disconnected) + amdgpu_connector->detected_by_load = true; + } + } else { + enum drm_connector_status lret; + /* assume digital unless load detected otherwise */ + amdgpu_connector->use_digital = true; + lret = encoder_funcs->detect(encoder, connector); + DRM_DEBUG_KMS("load_detect %x returned: %x\n",encoder->encoder_type,lret); + if (lret == connector_status_connected) + amdgpu_connector->use_digital = false; + } + break; + } + } + } + +out: + /* updated in get modes as well since we need to know if it's analog or digital */ + amdgpu_connector_update_scratch_regs(connector, ret); + +exit: + pm_runtime_mark_last_busy(connector->dev->dev); + pm_runtime_put_autosuspend(connector->dev->dev); + + return ret; +} + +/* okay need to be smart in here about which encoder to pick */ +static struct drm_encoder * +amdgpu_connector_dvi_encoder(struct drm_connector *connector) +{ + int enc_id = connector->encoder_ids[0]; + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct drm_encoder *encoder; + int i; + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + if (connector->encoder_ids[i] == 0) + break; + + encoder = drm_encoder_find(connector->dev, connector->encoder_ids[i]); + if (!encoder) + continue; + + if (amdgpu_connector->use_digital == true) { + if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS) + return encoder; + } else { + if (encoder->encoder_type == DRM_MODE_ENCODER_DAC || + encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) + return encoder; + } + } + + /* see if we have a default encoder TODO */ + + /* then check use digitial */ + /* pick the first one */ + if (enc_id) + return drm_encoder_find(connector->dev, enc_id); + return NULL; +} + +static void amdgpu_connector_dvi_force(struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + if (connector->force == DRM_FORCE_ON) + amdgpu_connector->use_digital = false; + if (connector->force == DRM_FORCE_ON_DIGITAL) + amdgpu_connector->use_digital = true; +} + +static int amdgpu_connector_dvi_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + + /* XXX check mode bandwidth */ + + if (amdgpu_connector->use_digital && (mode->clock > 165000)) { + if ((amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_DUAL_LINK_DVI_I) || + (amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_DUAL_LINK_DVI_D) || + (amdgpu_connector->connector_object_id == CONNECTOR_OBJECT_ID_HDMI_TYPE_B)) { + return MODE_OK; + } else if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + /* HDMI 1.3+ supports max clock of 340 Mhz */ + if (mode->clock > 340000) + return MODE_CLOCK_HIGH; + else + return MODE_OK; + } else { + return MODE_CLOCK_HIGH; + } + } + + /* check against the max pixel clock */ + if ((mode->clock / 10) > adev->clock.max_pixel_clock) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs amdgpu_connector_dvi_helper_funcs = { + .get_modes = amdgpu_connector_vga_get_modes, + .mode_valid = amdgpu_connector_dvi_mode_valid, + .best_encoder = amdgpu_connector_dvi_encoder, +}; + +static const struct drm_connector_funcs amdgpu_connector_dvi_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = amdgpu_connector_dvi_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = amdgpu_connector_set_property, + .destroy = amdgpu_connector_destroy, + .force = amdgpu_connector_dvi_force, +}; + +static int amdgpu_connector_dp_get_modes(struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *amdgpu_dig_connector = amdgpu_connector->con_priv; + struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector); + int ret; + + if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || + (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { + struct drm_display_mode *mode; + + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + if (!amdgpu_dig_connector->edp_on) + amdgpu_atombios_encoder_set_edp_panel_power(connector, + ATOM_TRANSMITTER_ACTION_POWER_ON); + amdgpu_connector_get_edid(connector); + ret = amdgpu_connector_ddc_get_modes(connector); + if (!amdgpu_dig_connector->edp_on) + amdgpu_atombios_encoder_set_edp_panel_power(connector, + ATOM_TRANSMITTER_ACTION_POWER_OFF); + } else { + /* need to setup ddc on the bridge */ + if (amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) != + ENCODER_OBJECT_ID_NONE) { + if (encoder) + amdgpu_atombios_encoder_setup_ext_encoder_ddc(encoder); + } + amdgpu_connector_get_edid(connector); + ret = amdgpu_connector_ddc_get_modes(connector); + } + + if (ret > 0) { + if (encoder) { + amdgpu_connector_fixup_lcd_native_mode(encoder, connector); + /* add scaled modes */ + amdgpu_connector_add_common_modes(encoder, connector); + } + return ret; + } + + if (!encoder) + return 0; + + /* we have no EDID modes */ + mode = amdgpu_connector_lcd_native_mode(encoder); + if (mode) { + ret = 1; + drm_mode_probed_add(connector, mode); + /* add the width/height from vbios tables if available */ + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + /* add scaled modes */ + amdgpu_connector_add_common_modes(encoder, connector); + } + } else { + /* need to setup ddc on the bridge */ + if (amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) != + ENCODER_OBJECT_ID_NONE) { + if (encoder) + amdgpu_atombios_encoder_setup_ext_encoder_ddc(encoder); + } + amdgpu_connector_get_edid(connector); + ret = amdgpu_connector_ddc_get_modes(connector); + + amdgpu_get_native_mode(connector); + } + + return ret; +} + +u16 amdgpu_connector_encoder_get_dp_bridge_encoder_id(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + int i; + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + if (connector->encoder_ids[i] == 0) + break; + + encoder = drm_encoder_find(connector->dev, + connector->encoder_ids[i]); + if (!encoder) + continue; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_TRAVIS: + case ENCODER_OBJECT_ID_NUTMEG: + return amdgpu_encoder->encoder_id; + default: + break; + } + } + + return ENCODER_OBJECT_ID_NONE; +} + +static bool amdgpu_connector_encoder_is_hbr2(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + int i; + bool found = false; + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + if (connector->encoder_ids[i] == 0) + break; + encoder = drm_encoder_find(connector->dev, + connector->encoder_ids[i]); + if (!encoder) + continue; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + if (amdgpu_encoder->caps & ATOM_ENCODER_CAP_RECORD_HBR2) + found = true; + } + + return found; +} + +bool amdgpu_connector_is_dp12_capable(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + + if ((adev->clock.default_dispclk >= 53900) && + amdgpu_connector_encoder_is_hbr2(connector)) { + return true; + } + + return false; +} + +static enum drm_connector_status +amdgpu_connector_dp_detect(struct drm_connector *connector, bool force) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + enum drm_connector_status ret = connector_status_disconnected; + struct amdgpu_connector_atom_dig *amdgpu_dig_connector = amdgpu_connector->con_priv; + struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector); + int r; + + r = pm_runtime_get_sync(connector->dev->dev); + if (r < 0) + return connector_status_disconnected; + + if (!force && amdgpu_connector_check_hpd_status_unchanged(connector)) { + ret = connector->status; + goto out; + } + + amdgpu_connector_free_edid(connector); + + if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || + (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { + if (encoder) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + + /* check if panel is valid */ + if (native_mode->hdisplay >= 320 && native_mode->vdisplay >= 240) + ret = connector_status_connected; + } + /* eDP is always DP */ + amdgpu_dig_connector->dp_sink_type = CONNECTOR_OBJECT_ID_DISPLAYPORT; + if (!amdgpu_dig_connector->edp_on) + amdgpu_atombios_encoder_set_edp_panel_power(connector, + ATOM_TRANSMITTER_ACTION_POWER_ON); + if (!amdgpu_atombios_dp_get_dpcd(amdgpu_connector)) + ret = connector_status_connected; + if (!amdgpu_dig_connector->edp_on) + amdgpu_atombios_encoder_set_edp_panel_power(connector, + ATOM_TRANSMITTER_ACTION_POWER_OFF); + } else if (amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) != + ENCODER_OBJECT_ID_NONE) { + /* DP bridges are always DP */ + amdgpu_dig_connector->dp_sink_type = CONNECTOR_OBJECT_ID_DISPLAYPORT; + /* get the DPCD from the bridge */ + amdgpu_atombios_dp_get_dpcd(amdgpu_connector); + + if (encoder) { + /* setup ddc on the bridge */ + amdgpu_atombios_encoder_setup_ext_encoder_ddc(encoder); + /* bridge chips are always aux */ + if (amdgpu_ddc_probe(amdgpu_connector, true)) /* try DDC */ + ret = connector_status_connected; + else if (amdgpu_connector->dac_load_detect) { /* try load detection */ + struct drm_encoder_helper_funcs *encoder_funcs = encoder->helper_private; + ret = encoder_funcs->detect(encoder, connector); + } + } + } else { + amdgpu_dig_connector->dp_sink_type = + amdgpu_atombios_dp_get_sinktype(amdgpu_connector); + if (amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) { + ret = connector_status_connected; + if (amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) + amdgpu_atombios_dp_get_dpcd(amdgpu_connector); + } else { + if (amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) { + if (!amdgpu_atombios_dp_get_dpcd(amdgpu_connector)) + ret = connector_status_connected; + } else { + /* try non-aux ddc (DP to DVI/HDMI/etc. adapter) */ + if (amdgpu_ddc_probe(amdgpu_connector, false)) + ret = connector_status_connected; + } + } + } + + amdgpu_connector_update_scratch_regs(connector, ret); +out: + pm_runtime_mark_last_busy(connector->dev->dev); + pm_runtime_put_autosuspend(connector->dev->dev); + + return ret; +} + +static int amdgpu_connector_dp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *amdgpu_dig_connector = amdgpu_connector->con_priv; + + /* XXX check mode bandwidth */ + + if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || + (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { + struct drm_encoder *encoder = amdgpu_connector_best_single_encoder(connector); + + if ((mode->hdisplay < 320) || (mode->vdisplay < 240)) + return MODE_PANEL; + + if (encoder) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + + /* AVIVO hardware supports downscaling modes larger than the panel + * to the panel size, but I'm not sure this is desirable. + */ + if ((mode->hdisplay > native_mode->hdisplay) || + (mode->vdisplay > native_mode->vdisplay)) + return MODE_PANEL; + + /* if scaling is disabled, block non-native modes */ + if (amdgpu_encoder->rmx_type == RMX_OFF) { + if ((mode->hdisplay != native_mode->hdisplay) || + (mode->vdisplay != native_mode->vdisplay)) + return MODE_PANEL; + } + } + return MODE_OK; + } else { + if ((amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) || + (amdgpu_dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP)) { + return amdgpu_atombios_dp_mode_valid_helper(connector, mode); + } else { + if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + /* HDMI 1.3+ supports max clock of 340 Mhz */ + if (mode->clock > 340000) + return MODE_CLOCK_HIGH; + } else { + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + } + } + } + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs amdgpu_connector_dp_helper_funcs = { + .get_modes = amdgpu_connector_dp_get_modes, + .mode_valid = amdgpu_connector_dp_mode_valid, + .best_encoder = amdgpu_connector_dvi_encoder, +}; + +static const struct drm_connector_funcs amdgpu_connector_dp_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = amdgpu_connector_dp_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = amdgpu_connector_set_property, + .destroy = amdgpu_connector_destroy, + .force = amdgpu_connector_dvi_force, +}; + +static const struct drm_connector_funcs amdgpu_connector_edp_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = amdgpu_connector_dp_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = amdgpu_connector_set_lcd_property, + .destroy = amdgpu_connector_destroy, + .force = amdgpu_connector_dvi_force, +}; + +void +amdgpu_connector_add(struct amdgpu_device *adev, + uint32_t connector_id, + uint32_t supported_device, + int connector_type, + struct amdgpu_i2c_bus_rec *i2c_bus, + uint16_t connector_object_id, + struct amdgpu_hpd *hpd, + struct amdgpu_router *router) +{ + struct drm_device *dev = adev->ddev; + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + struct amdgpu_connector_atom_dig *amdgpu_dig_connector; + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + uint32_t subpixel_order = SubPixelNone; + bool shared_ddc = false; + bool is_dp_bridge = false; + bool has_aux = false; + + if (connector_type == DRM_MODE_CONNECTOR_Unknown) + return; + + /* see if we already added it */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + amdgpu_connector = to_amdgpu_connector(connector); + if (amdgpu_connector->connector_id == connector_id) { + amdgpu_connector->devices |= supported_device; + return; + } + if (amdgpu_connector->ddc_bus && i2c_bus->valid) { + if (amdgpu_connector->ddc_bus->rec.i2c_id == i2c_bus->i2c_id) { + amdgpu_connector->shared_ddc = true; + shared_ddc = true; + } + if (amdgpu_connector->router_bus && router->ddc_valid && + (amdgpu_connector->router.router_id == router->router_id)) { + amdgpu_connector->shared_ddc = false; + shared_ddc = false; + } + } + } + + /* check if it's a dp bridge */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + amdgpu_encoder = to_amdgpu_encoder(encoder); + if (amdgpu_encoder->devices & supported_device) { + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_TRAVIS: + case ENCODER_OBJECT_ID_NUTMEG: + is_dp_bridge = true; + break; + default: + break; + } + } + } + + amdgpu_connector = kzalloc(sizeof(struct amdgpu_connector), GFP_KERNEL); + if (!amdgpu_connector) + return; + + connector = &amdgpu_connector->base; + + amdgpu_connector->connector_id = connector_id; + amdgpu_connector->devices = supported_device; + amdgpu_connector->shared_ddc = shared_ddc; + amdgpu_connector->connector_object_id = connector_object_id; + amdgpu_connector->hpd = *hpd; + + amdgpu_connector->router = *router; + if (router->ddc_valid || router->cd_valid) { + amdgpu_connector->router_bus = amdgpu_i2c_lookup(adev, &router->i2c_info); + if (!amdgpu_connector->router_bus) + DRM_ERROR("Failed to assign router i2c bus! Check dmesg for i2c errors.\n"); + } + + if (is_dp_bridge) { + amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL); + if (!amdgpu_dig_connector) + goto failed; + amdgpu_connector->con_priv = amdgpu_dig_connector; + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (amdgpu_connector->ddc_bus) + has_aux = true; + else + DRM_ERROR("DP: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + switch (connector_type) { + case DRM_MODE_CONNECTOR_VGA: + case DRM_MODE_CONNECTOR_DVIA: + default: + drm_connector_init(dev, &amdgpu_connector->base, + &amdgpu_connector_dp_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, + &amdgpu_connector_dp_helper_funcs); + connector->interlace_allowed = true; + connector->doublescan_allowed = true; + amdgpu_connector->dac_load_detect = true; + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.load_detect_property, + 1); + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + break; + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_HDMIB: + case DRM_MODE_CONNECTOR_DisplayPort: + drm_connector_init(dev, &amdgpu_connector->base, + &amdgpu_connector_dp_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, + &amdgpu_connector_dp_helper_funcs); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_property, + UNDERSCAN_OFF); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_hborder_property, + 0); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_vborder_property, + 0); + + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.dither_property, + AMDGPU_FMT_DITHER_DISABLE); + + if (amdgpu_audio != 0) + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.audio_property, + AMDGPU_AUDIO_AUTO); + + subpixel_order = SubPixelHorizontalRGB; + connector->interlace_allowed = true; + if (connector_type == DRM_MODE_CONNECTOR_HDMIB) + connector->doublescan_allowed = true; + else + connector->doublescan_allowed = false; + if (connector_type == DRM_MODE_CONNECTOR_DVII) { + amdgpu_connector->dac_load_detect = true; + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.load_detect_property, + 1); + } + break; + case DRM_MODE_CONNECTOR_LVDS: + case DRM_MODE_CONNECTOR_eDP: + drm_connector_init(dev, &amdgpu_connector->base, + &amdgpu_connector_edp_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, + &amdgpu_connector_dp_helper_funcs); + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_FULLSCREEN); + subpixel_order = SubPixelHorizontalRGB; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + break; + } + } else { + switch (connector_type) { + case DRM_MODE_CONNECTOR_VGA: + drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_vga_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_vga_helper_funcs); + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (!amdgpu_connector->ddc_bus) + DRM_ERROR("VGA: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + amdgpu_connector->dac_load_detect = true; + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.load_detect_property, + 1); + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + /* no HPD on analog connectors */ + amdgpu_connector->hpd.hpd = AMDGPU_HPD_NONE; + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + connector->interlace_allowed = true; + connector->doublescan_allowed = true; + break; + case DRM_MODE_CONNECTOR_DVIA: + drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_vga_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_vga_helper_funcs); + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (!amdgpu_connector->ddc_bus) + DRM_ERROR("DVIA: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + amdgpu_connector->dac_load_detect = true; + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.load_detect_property, + 1); + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + /* no HPD on analog connectors */ + amdgpu_connector->hpd.hpd = AMDGPU_HPD_NONE; + connector->interlace_allowed = true; + connector->doublescan_allowed = true; + break; + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_DVID: + amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL); + if (!amdgpu_dig_connector) + goto failed; + amdgpu_connector->con_priv = amdgpu_dig_connector; + drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_dvi_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dvi_helper_funcs); + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (!amdgpu_connector->ddc_bus) + DRM_ERROR("DVI: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + subpixel_order = SubPixelHorizontalRGB; + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.coherent_mode_property, + 1); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_property, + UNDERSCAN_OFF); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_hborder_property, + 0); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_vborder_property, + 0); + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + + if (amdgpu_audio != 0) { + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.audio_property, + AMDGPU_AUDIO_AUTO); + } + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.dither_property, + AMDGPU_FMT_DITHER_DISABLE); + if (connector_type == DRM_MODE_CONNECTOR_DVII) { + amdgpu_connector->dac_load_detect = true; + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.load_detect_property, + 1); + } + connector->interlace_allowed = true; + if (connector_type == DRM_MODE_CONNECTOR_DVII) + connector->doublescan_allowed = true; + else + connector->doublescan_allowed = false; + break; + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_HDMIB: + amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL); + if (!amdgpu_dig_connector) + goto failed; + amdgpu_connector->con_priv = amdgpu_dig_connector; + drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_dvi_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dvi_helper_funcs); + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (!amdgpu_connector->ddc_bus) + DRM_ERROR("HDMI: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.coherent_mode_property, + 1); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_property, + UNDERSCAN_OFF); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_hborder_property, + 0); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_vborder_property, + 0); + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + if (amdgpu_audio != 0) { + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.audio_property, + AMDGPU_AUDIO_AUTO); + } + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.dither_property, + AMDGPU_FMT_DITHER_DISABLE); + subpixel_order = SubPixelHorizontalRGB; + connector->interlace_allowed = true; + if (connector_type == DRM_MODE_CONNECTOR_HDMIB) + connector->doublescan_allowed = true; + else + connector->doublescan_allowed = false; + break; + case DRM_MODE_CONNECTOR_DisplayPort: + amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL); + if (!amdgpu_dig_connector) + goto failed; + amdgpu_connector->con_priv = amdgpu_dig_connector; + drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_dp_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dp_helper_funcs); + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (amdgpu_connector->ddc_bus) + has_aux = true; + else + DRM_ERROR("DP: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + subpixel_order = SubPixelHorizontalRGB; + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.coherent_mode_property, + 1); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_property, + UNDERSCAN_OFF); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_hborder_property, + 0); + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.underscan_vborder_property, + 0); + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + if (amdgpu_audio != 0) { + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.audio_property, + AMDGPU_AUDIO_AUTO); + } + drm_object_attach_property(&amdgpu_connector->base.base, + adev->mode_info.dither_property, + AMDGPU_FMT_DITHER_DISABLE); + connector->interlace_allowed = true; + /* in theory with a DP to VGA converter... */ + connector->doublescan_allowed = false; + break; + case DRM_MODE_CONNECTOR_eDP: + amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL); + if (!amdgpu_dig_connector) + goto failed; + amdgpu_connector->con_priv = amdgpu_dig_connector; + drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_edp_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_dp_helper_funcs); + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (amdgpu_connector->ddc_bus) + has_aux = true; + else + DRM_ERROR("DP: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_FULLSCREEN); + subpixel_order = SubPixelHorizontalRGB; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + break; + case DRM_MODE_CONNECTOR_LVDS: + amdgpu_dig_connector = kzalloc(sizeof(struct amdgpu_connector_atom_dig), GFP_KERNEL); + if (!amdgpu_dig_connector) + goto failed; + amdgpu_connector->con_priv = amdgpu_dig_connector; + drm_connector_init(dev, &amdgpu_connector->base, &amdgpu_connector_lvds_funcs, connector_type); + drm_connector_helper_add(&amdgpu_connector->base, &amdgpu_connector_lvds_helper_funcs); + if (i2c_bus->valid) { + amdgpu_connector->ddc_bus = amdgpu_i2c_lookup(adev, i2c_bus); + if (!amdgpu_connector->ddc_bus) + DRM_ERROR("LVDS: Failed to assign ddc bus! Check dmesg for i2c errors.\n"); + } + drm_object_attach_property(&amdgpu_connector->base.base, + dev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_FULLSCREEN); + subpixel_order = SubPixelHorizontalRGB; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + break; + } + } + + if (amdgpu_connector->hpd.hpd == AMDGPU_HPD_NONE) { + if (i2c_bus->valid) + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + } else + connector->polled = DRM_CONNECTOR_POLL_HPD; + + connector->display_info.subpixel_order = subpixel_order; + drm_connector_register(connector); + + if (has_aux) + amdgpu_atombios_dp_aux_init(amdgpu_connector); + + return; + +failed: + drm_connector_cleanup(connector); + kfree(connector); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.h new file mode 100644 index 000000000000..61fcef15ad72 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.h @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_CONNECTORS_H__ +#define __AMDGPU_CONNECTORS_H__ + +struct edid *amdgpu_connector_edid(struct drm_connector *connector); +void amdgpu_connector_hotplug(struct drm_connector *connector); +int amdgpu_connector_get_monitor_bpc(struct drm_connector *connector); +u16 amdgpu_connector_encoder_get_dp_bridge_encoder_id(struct drm_connector *connector); +bool amdgpu_connector_is_dp12_capable(struct drm_connector *connector); +void +amdgpu_connector_add(struct amdgpu_device *adev, + uint32_t connector_id, + uint32_t supported_device, + int connector_type, + struct amdgpu_i2c_bus_rec *i2c_bus, + uint16_t connector_object_id, + struct amdgpu_hpd *hpd, + struct amdgpu_router *router); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c new file mode 100644 index 000000000000..70a90312d0a4 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c @@ -0,0 +1,825 @@ +/* + * Copyright 2008 Jerome Glisse. + * 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. + * + * Authors: + * Jerome Glisse <glisse@freedesktop.org> + */ +#include <linux/list_sort.h> +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_trace.h" + +#define AMDGPU_CS_MAX_PRIORITY 32u +#define AMDGPU_CS_NUM_BUCKETS (AMDGPU_CS_MAX_PRIORITY + 1) + +/* This is based on the bucket sort with O(n) time complexity. + * An item with priority "i" is added to bucket[i]. The lists are then + * concatenated in descending order. + */ +struct amdgpu_cs_buckets { + struct list_head bucket[AMDGPU_CS_NUM_BUCKETS]; +}; + +static void amdgpu_cs_buckets_init(struct amdgpu_cs_buckets *b) +{ + unsigned i; + + for (i = 0; i < AMDGPU_CS_NUM_BUCKETS; i++) + INIT_LIST_HEAD(&b->bucket[i]); +} + +static void amdgpu_cs_buckets_add(struct amdgpu_cs_buckets *b, + struct list_head *item, unsigned priority) +{ + /* Since buffers which appear sooner in the relocation list are + * likely to be used more often than buffers which appear later + * in the list, the sort mustn't change the ordering of buffers + * with the same priority, i.e. it must be stable. + */ + list_add_tail(item, &b->bucket[min(priority, AMDGPU_CS_MAX_PRIORITY)]); +} + +static void amdgpu_cs_buckets_get_list(struct amdgpu_cs_buckets *b, + struct list_head *out_list) +{ + unsigned i; + + /* Connect the sorted buckets in the output list. */ + for (i = 0; i < AMDGPU_CS_NUM_BUCKETS; i++) { + list_splice(&b->bucket[i], out_list); + } +} + +int amdgpu_cs_get_ring(struct amdgpu_device *adev, u32 ip_type, + u32 ip_instance, u32 ring, + struct amdgpu_ring **out_ring) +{ + /* Right now all IPs have only one instance - multiple rings. */ + if (ip_instance != 0) { + DRM_ERROR("invalid ip instance: %d\n", ip_instance); + return -EINVAL; + } + + switch (ip_type) { + default: + DRM_ERROR("unknown ip type: %d\n", ip_type); + return -EINVAL; + case AMDGPU_HW_IP_GFX: + if (ring < adev->gfx.num_gfx_rings) { + *out_ring = &adev->gfx.gfx_ring[ring]; + } else { + DRM_ERROR("only %d gfx rings are supported now\n", + adev->gfx.num_gfx_rings); + return -EINVAL; + } + break; + case AMDGPU_HW_IP_COMPUTE: + if (ring < adev->gfx.num_compute_rings) { + *out_ring = &adev->gfx.compute_ring[ring]; + } else { + DRM_ERROR("only %d compute rings are supported now\n", + adev->gfx.num_compute_rings); + return -EINVAL; + } + break; + case AMDGPU_HW_IP_DMA: + if (ring < 2) { + *out_ring = &adev->sdma[ring].ring; + } else { + DRM_ERROR("only two SDMA rings are supported\n"); + return -EINVAL; + } + break; + case AMDGPU_HW_IP_UVD: + *out_ring = &adev->uvd.ring; + break; + case AMDGPU_HW_IP_VCE: + if (ring < 2){ + *out_ring = &adev->vce.ring[ring]; + } else { + DRM_ERROR("only two VCE rings are supported\n"); + return -EINVAL; + } + break; + } + return 0; +} + +int amdgpu_cs_parser_init(struct amdgpu_cs_parser *p, void *data) +{ + union drm_amdgpu_cs *cs = data; + uint64_t *chunk_array_user; + uint64_t *chunk_array = NULL; + struct amdgpu_fpriv *fpriv = p->filp->driver_priv; + unsigned size, i; + int r = 0; + + if (!cs->in.num_chunks) + goto out; + + p->ctx_id = cs->in.ctx_id; + p->bo_list = amdgpu_bo_list_get(fpriv, cs->in.bo_list_handle); + + /* get chunks */ + INIT_LIST_HEAD(&p->validated); + chunk_array = kcalloc(cs->in.num_chunks, sizeof(uint64_t), GFP_KERNEL); + if (chunk_array == NULL) { + r = -ENOMEM; + goto out; + } + + chunk_array_user = (uint64_t *)(unsigned long)(cs->in.chunks); + if (copy_from_user(chunk_array, chunk_array_user, + sizeof(uint64_t)*cs->in.num_chunks)) { + r = -EFAULT; + goto out; + } + + p->nchunks = cs->in.num_chunks; + p->chunks = kcalloc(p->nchunks, sizeof(struct amdgpu_cs_chunk), + GFP_KERNEL); + if (p->chunks == NULL) { + r = -ENOMEM; + goto out; + } + + for (i = 0; i < p->nchunks; i++) { + struct drm_amdgpu_cs_chunk __user **chunk_ptr = NULL; + struct drm_amdgpu_cs_chunk user_chunk; + uint32_t __user *cdata; + + chunk_ptr = (void __user *)(unsigned long)chunk_array[i]; + if (copy_from_user(&user_chunk, chunk_ptr, + sizeof(struct drm_amdgpu_cs_chunk))) { + r = -EFAULT; + goto out; + } + p->chunks[i].chunk_id = user_chunk.chunk_id; + p->chunks[i].length_dw = user_chunk.length_dw; + if (p->chunks[i].chunk_id == AMDGPU_CHUNK_ID_IB) + p->num_ibs++; + + size = p->chunks[i].length_dw; + cdata = (void __user *)(unsigned long)user_chunk.chunk_data; + p->chunks[i].user_ptr = cdata; + + p->chunks[i].kdata = drm_malloc_ab(size, sizeof(uint32_t)); + if (p->chunks[i].kdata == NULL) { + r = -ENOMEM; + goto out; + } + size *= sizeof(uint32_t); + if (copy_from_user(p->chunks[i].kdata, cdata, size)) { + r = -EFAULT; + goto out; + } + + if (p->chunks[i].chunk_id == AMDGPU_CHUNK_ID_FENCE) { + size = sizeof(struct drm_amdgpu_cs_chunk_fence); + if (p->chunks[i].length_dw * sizeof(uint32_t) >= size) { + uint32_t handle; + struct drm_gem_object *gobj; + struct drm_amdgpu_cs_chunk_fence *fence_data; + + fence_data = (void *)p->chunks[i].kdata; + handle = fence_data->handle; + gobj = drm_gem_object_lookup(p->adev->ddev, + p->filp, handle); + if (gobj == NULL) { + r = -EINVAL; + goto out; + } + + p->uf.bo = gem_to_amdgpu_bo(gobj); + p->uf.offset = fence_data->offset; + } else { + r = -EINVAL; + goto out; + } + } + } + + p->ibs = kcalloc(p->num_ibs, sizeof(struct amdgpu_ib), GFP_KERNEL); + if (!p->ibs) { + r = -ENOMEM; + goto out; + } + + p->ib_bos = kcalloc(p->num_ibs, sizeof(struct amdgpu_bo_list_entry), + GFP_KERNEL); + if (!p->ib_bos) + r = -ENOMEM; + +out: + kfree(chunk_array); + return r; +} + +/* Returns how many bytes TTM can move per IB. + */ +static u64 amdgpu_cs_get_threshold_for_moves(struct amdgpu_device *adev) +{ + u64 real_vram_size = adev->mc.real_vram_size; + u64 vram_usage = atomic64_read(&adev->vram_usage); + + /* This function is based on the current VRAM usage. + * + * - If all of VRAM is free, allow relocating the number of bytes that + * is equal to 1/4 of the size of VRAM for this IB. + + * - If more than one half of VRAM is occupied, only allow relocating + * 1 MB of data for this IB. + * + * - From 0 to one half of used VRAM, the threshold decreases + * linearly. + * __________________ + * 1/4 of -|\ | + * VRAM | \ | + * | \ | + * | \ | + * | \ | + * | \ | + * | \ | + * | \________|1 MB + * |----------------| + * VRAM 0 % 100 % + * used used + * + * Note: It's a threshold, not a limit. The threshold must be crossed + * for buffer relocations to stop, so any buffer of an arbitrary size + * can be moved as long as the threshold isn't crossed before + * the relocation takes place. We don't want to disable buffer + * relocations completely. + * + * The idea is that buffers should be placed in VRAM at creation time + * and TTM should only do a minimum number of relocations during + * command submission. In practice, you need to submit at least + * a dozen IBs to move all buffers to VRAM if they are in GTT. + * + * Also, things can get pretty crazy under memory pressure and actual + * VRAM usage can change a lot, so playing safe even at 50% does + * consistently increase performance. + */ + + u64 half_vram = real_vram_size >> 1; + u64 half_free_vram = vram_usage >= half_vram ? 0 : half_vram - vram_usage; + u64 bytes_moved_threshold = half_free_vram >> 1; + return max(bytes_moved_threshold, 1024*1024ull); +} + +int amdgpu_cs_list_validate(struct amdgpu_cs_parser *p) +{ + struct amdgpu_fpriv *fpriv = p->filp->driver_priv; + struct amdgpu_vm *vm = &fpriv->vm; + struct amdgpu_device *adev = p->adev; + struct amdgpu_bo_list_entry *lobj; + struct list_head duplicates; + struct amdgpu_bo *bo; + u64 bytes_moved = 0, initial_bytes_moved; + u64 bytes_moved_threshold = amdgpu_cs_get_threshold_for_moves(adev); + int r; + + INIT_LIST_HEAD(&duplicates); + r = ttm_eu_reserve_buffers(&p->ticket, &p->validated, true, &duplicates); + if (unlikely(r != 0)) { + return r; + } + + list_for_each_entry(lobj, &p->validated, tv.head) { + bo = lobj->robj; + if (!bo->pin_count) { + u32 domain = lobj->prefered_domains; + u32 current_domain = + amdgpu_mem_type_to_domain(bo->tbo.mem.mem_type); + + /* Check if this buffer will be moved and don't move it + * if we have moved too many buffers for this IB already. + * + * Note that this allows moving at least one buffer of + * any size, because it doesn't take the current "bo" + * into account. We don't want to disallow buffer moves + * completely. + */ + if (current_domain != AMDGPU_GEM_DOMAIN_CPU && + (domain & current_domain) == 0 && /* will be moved */ + bytes_moved > bytes_moved_threshold) { + /* don't move it */ + domain = current_domain; + } + + retry: + amdgpu_ttm_placement_from_domain(bo, domain); + initial_bytes_moved = atomic64_read(&adev->num_bytes_moved); + r = ttm_bo_validate(&bo->tbo, &bo->placement, true, false); + bytes_moved += atomic64_read(&adev->num_bytes_moved) - + initial_bytes_moved; + + if (unlikely(r)) { + if (r != -ERESTARTSYS && domain != lobj->allowed_domains) { + domain = lobj->allowed_domains; + goto retry; + } + ttm_eu_backoff_reservation(&p->ticket, &p->validated); + return r; + } + } + lobj->bo_va = amdgpu_vm_bo_find(vm, bo); + } + return 0; +} + +static int amdgpu_cs_parser_relocs(struct amdgpu_cs_parser *p) +{ + struct amdgpu_fpriv *fpriv = p->filp->driver_priv; + struct amdgpu_cs_buckets buckets; + bool need_mmap_lock; + int i, r; + + if (p->bo_list == NULL) + return 0; + + need_mmap_lock = p->bo_list->has_userptr; + amdgpu_cs_buckets_init(&buckets); + for (i = 0; i < p->bo_list->num_entries; i++) + amdgpu_cs_buckets_add(&buckets, &p->bo_list->array[i].tv.head, + p->bo_list->array[i].priority); + + amdgpu_cs_buckets_get_list(&buckets, &p->validated); + p->vm_bos = amdgpu_vm_get_bos(p->adev, &fpriv->vm, + &p->validated); + + for (i = 0; i < p->num_ibs; i++) { + if (!p->ib_bos[i].robj) + continue; + + list_add(&p->ib_bos[i].tv.head, &p->validated); + } + + if (need_mmap_lock) + down_read(¤t->mm->mmap_sem); + + r = amdgpu_cs_list_validate(p); + + if (need_mmap_lock) + up_read(¤t->mm->mmap_sem); + + return r; +} + +static int amdgpu_cs_sync_rings(struct amdgpu_cs_parser *p) +{ + struct amdgpu_bo_list_entry *e; + int r; + + list_for_each_entry(e, &p->validated, tv.head) { + struct reservation_object *resv = e->robj->tbo.resv; + r = amdgpu_sync_resv(p->adev, &p->ibs[0].sync, resv, p->filp); + + if (r) + return r; + } + return 0; +} + +static int cmp_size_smaller_first(void *priv, struct list_head *a, + struct list_head *b) +{ + struct amdgpu_bo_list_entry *la = list_entry(a, struct amdgpu_bo_list_entry, tv.head); + struct amdgpu_bo_list_entry *lb = list_entry(b, struct amdgpu_bo_list_entry, tv.head); + + /* Sort A before B if A is smaller. */ + return (int)la->robj->tbo.num_pages - (int)lb->robj->tbo.num_pages; +} + +/** + * cs_parser_fini() - clean parser states + * @parser: parser structure holding parsing context. + * @error: error number + * + * If error is set than unvalidate buffer, otherwise just free memory + * used by parsing context. + **/ +static void amdgpu_cs_parser_fini(struct amdgpu_cs_parser *parser, int error, bool backoff) +{ + unsigned i; + + if (!error) { + /* Sort the buffer list from the smallest to largest buffer, + * which affects the order of buffers in the LRU list. + * This assures that the smallest buffers are added first + * to the LRU list, so they are likely to be later evicted + * first, instead of large buffers whose eviction is more + * expensive. + * + * This slightly lowers the number of bytes moved by TTM + * per frame under memory pressure. + */ + list_sort(NULL, &parser->validated, cmp_size_smaller_first); + + ttm_eu_fence_buffer_objects(&parser->ticket, + &parser->validated, + &parser->ibs[parser->num_ibs-1].fence->base); + } else if (backoff) { + ttm_eu_backoff_reservation(&parser->ticket, + &parser->validated); + } + + if (parser->bo_list) + amdgpu_bo_list_put(parser->bo_list); + drm_free_large(parser->vm_bos); + for (i = 0; i < parser->nchunks; i++) + drm_free_large(parser->chunks[i].kdata); + kfree(parser->chunks); + for (i = 0; i < parser->num_ibs; i++) { + struct amdgpu_bo *bo = parser->ib_bos[i].robj; + amdgpu_ib_free(parser->adev, &parser->ibs[i]); + + if (bo) + drm_gem_object_unreference_unlocked(&bo->gem_base); + } + kfree(parser->ibs); + kfree(parser->ib_bos); + if (parser->uf.bo) + drm_gem_object_unreference_unlocked(&parser->uf.bo->gem_base); +} + +static int amdgpu_bo_vm_update_pte(struct amdgpu_cs_parser *p, + struct amdgpu_vm *vm) +{ + struct amdgpu_device *adev = p->adev; + struct amdgpu_bo_va *bo_va; + struct amdgpu_bo *bo; + int i, r; + + r = amdgpu_vm_update_page_directory(adev, vm); + if (r) + return r; + + r = amdgpu_vm_clear_freed(adev, vm); + if (r) + return r; + + if (p->bo_list) { + for (i = 0; i < p->bo_list->num_entries; i++) { + /* ignore duplicates */ + bo = p->bo_list->array[i].robj; + if (!bo) + continue; + + bo_va = p->bo_list->array[i].bo_va; + if (bo_va == NULL) + continue; + + r = amdgpu_vm_bo_update(adev, bo_va, &bo->tbo.mem); + if (r) + return r; + + amdgpu_sync_fence(&p->ibs[0].sync, bo_va->last_pt_update); + } + } + + for (i = 0; i < p->num_ibs; i++) { + bo = p->ib_bos[i].robj; + if (!bo) + continue; + + bo_va = p->ib_bos[i].bo_va; + if (!bo_va) + continue; + + r = amdgpu_vm_bo_update(adev, bo_va, &bo->tbo.mem); + if (r) + return r; + + amdgpu_sync_fence(&p->ibs[0].sync, bo_va->last_pt_update); + } + return amdgpu_vm_clear_invalids(adev, vm); +} + +static int amdgpu_cs_ib_vm_chunk(struct amdgpu_device *adev, + struct amdgpu_cs_parser *parser) +{ + struct amdgpu_fpriv *fpriv = parser->filp->driver_priv; + struct amdgpu_vm *vm = &fpriv->vm; + struct amdgpu_ring *ring; + int i, r; + + if (parser->num_ibs == 0) + return 0; + + /* Only for UVD/VCE VM emulation */ + for (i = 0; i < parser->num_ibs; i++) { + ring = parser->ibs[i].ring; + if (ring->funcs->parse_cs) { + r = amdgpu_ring_parse_cs(ring, parser, i); + if (r) + return r; + } + } + + mutex_lock(&vm->mutex); + r = amdgpu_bo_vm_update_pte(parser, vm); + if (r) { + goto out; + } + amdgpu_cs_sync_rings(parser); + + r = amdgpu_ib_schedule(adev, parser->num_ibs, parser->ibs, + parser->filp); + +out: + mutex_unlock(&vm->mutex); + return r; +} + +static int amdgpu_cs_handle_lockup(struct amdgpu_device *adev, int r) +{ + if (r == -EDEADLK) { + r = amdgpu_gpu_reset(adev); + if (!r) + r = -EAGAIN; + } + return r; +} + +static int amdgpu_cs_ib_fill(struct amdgpu_device *adev, + struct amdgpu_cs_parser *parser) +{ + struct amdgpu_fpriv *fpriv = parser->filp->driver_priv; + struct amdgpu_vm *vm = &fpriv->vm; + int i, j; + int r; + + for (i = 0, j = 0; i < parser->nchunks && j < parser->num_ibs; i++) { + struct amdgpu_cs_chunk *chunk; + struct amdgpu_ib *ib; + struct drm_amdgpu_cs_chunk_ib *chunk_ib; + struct amdgpu_bo_list_entry *ib_bo; + struct amdgpu_ring *ring; + struct drm_gem_object *gobj; + struct amdgpu_bo *aobj; + void *kptr; + + chunk = &parser->chunks[i]; + ib = &parser->ibs[j]; + chunk_ib = (struct drm_amdgpu_cs_chunk_ib *)chunk->kdata; + + if (chunk->chunk_id != AMDGPU_CHUNK_ID_IB) + continue; + + gobj = drm_gem_object_lookup(adev->ddev, parser->filp, chunk_ib->handle); + if (gobj == NULL) + return -ENOENT; + aobj = gem_to_amdgpu_bo(gobj); + + r = amdgpu_cs_get_ring(adev, chunk_ib->ip_type, + chunk_ib->ip_instance, chunk_ib->ring, + &ring); + if (r) { + drm_gem_object_unreference_unlocked(gobj); + return r; + } + + if (ring->funcs->parse_cs) { + r = amdgpu_bo_reserve(aobj, false); + if (r) { + drm_gem_object_unreference_unlocked(gobj); + return r; + } + + r = amdgpu_bo_kmap(aobj, &kptr); + if (r) { + amdgpu_bo_unreserve(aobj); + drm_gem_object_unreference_unlocked(gobj); + return r; + } + + r = amdgpu_ib_get(ring, NULL, chunk_ib->ib_bytes, ib); + if (r) { + DRM_ERROR("Failed to get ib !\n"); + amdgpu_bo_unreserve(aobj); + drm_gem_object_unreference_unlocked(gobj); + return r; + } + + memcpy(ib->ptr, kptr, chunk_ib->ib_bytes); + amdgpu_bo_kunmap(aobj); + amdgpu_bo_unreserve(aobj); + } else { + r = amdgpu_ib_get(ring, vm, 0, ib); + if (r) { + DRM_ERROR("Failed to get ib !\n"); + drm_gem_object_unreference_unlocked(gobj); + return r; + } + + ib->gpu_addr = chunk_ib->va_start; + } + ib->length_dw = chunk_ib->ib_bytes / 4; + + if (chunk_ib->flags & AMDGPU_IB_FLAG_CE) + ib->is_const_ib = true; + if (chunk_ib->flags & AMDGPU_IB_FLAG_GDS) + ib->gds_needed = true; + if (ib->ring->current_filp != parser->filp) { + ib->ring->need_ctx_switch = true; + ib->ring->current_filp = parser->filp; + } + + ib_bo = &parser->ib_bos[j]; + ib_bo->robj = aobj; + ib_bo->prefered_domains = aobj->initial_domain; + ib_bo->allowed_domains = aobj->initial_domain; + ib_bo->priority = 0; + ib_bo->tv.bo = &aobj->tbo; + ib_bo->tv.shared = true; + j++; + } + + if (!parser->num_ibs) + return 0; + + /* add GDS resources to first IB */ + if (parser->bo_list) { + struct amdgpu_bo *gds = parser->bo_list->gds_obj; + struct amdgpu_bo *gws = parser->bo_list->gws_obj; + struct amdgpu_bo *oa = parser->bo_list->oa_obj; + struct amdgpu_ib *ib = &parser->ibs[0]; + + if (gds) { + ib->gds_base = amdgpu_bo_gpu_offset(gds); + ib->gds_size = amdgpu_bo_size(gds); + } + if (gws) { + ib->gws_base = amdgpu_bo_gpu_offset(gws); + ib->gws_size = amdgpu_bo_size(gws); + } + if (oa) { + ib->oa_base = amdgpu_bo_gpu_offset(oa); + ib->oa_size = amdgpu_bo_size(oa); + } + } + + /* wrap the last IB with user fence */ + if (parser->uf.bo) { + struct amdgpu_ib *ib = &parser->ibs[parser->num_ibs - 1]; + + /* UVD & VCE fw doesn't support user fences */ + if (ib->ring->type == AMDGPU_RING_TYPE_UVD || + ib->ring->type == AMDGPU_RING_TYPE_VCE) + return -EINVAL; + + ib->user = &parser->uf; + } + + return 0; +} + +int amdgpu_cs_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) +{ + struct amdgpu_device *adev = dev->dev_private; + union drm_amdgpu_cs *cs = data; + struct amdgpu_cs_parser parser; + int r, i; + + down_read(&adev->exclusive_lock); + if (!adev->accel_working) { + up_read(&adev->exclusive_lock); + return -EBUSY; + } + /* initialize parser */ + memset(&parser, 0, sizeof(struct amdgpu_cs_parser)); + parser.filp = filp; + parser.adev = adev; + r = amdgpu_cs_parser_init(&parser, data); + if (r) { + DRM_ERROR("Failed to initialize parser !\n"); + amdgpu_cs_parser_fini(&parser, r, false); + up_read(&adev->exclusive_lock); + r = amdgpu_cs_handle_lockup(adev, r); + return r; + } + + r = amdgpu_cs_ib_fill(adev, &parser); + if (!r) { + r = amdgpu_cs_parser_relocs(&parser); + if (r && r != -ERESTARTSYS) + DRM_ERROR("Failed to parse relocation %d!\n", r); + } + + if (r) { + amdgpu_cs_parser_fini(&parser, r, false); + up_read(&adev->exclusive_lock); + r = amdgpu_cs_handle_lockup(adev, r); + return r; + } + + for (i = 0; i < parser.num_ibs; i++) + trace_amdgpu_cs(&parser, i); + + r = amdgpu_cs_ib_vm_chunk(adev, &parser); + if (r) { + goto out; + } + + cs->out.handle = parser.ibs[parser.num_ibs - 1].fence->seq; +out: + amdgpu_cs_parser_fini(&parser, r, true); + up_read(&adev->exclusive_lock); + r = amdgpu_cs_handle_lockup(adev, r); + return r; +} + +/** + * amdgpu_cs_wait_ioctl - wait for a command submission to finish + * + * @dev: drm device + * @data: data from userspace + * @filp: file private + * + * Wait for the command submission identified by handle to finish. + */ +int amdgpu_cs_wait_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + union drm_amdgpu_wait_cs *wait = data; + struct amdgpu_device *adev = dev->dev_private; + uint64_t seq[AMDGPU_MAX_RINGS] = {0}; + struct amdgpu_ring *ring = NULL; + unsigned long timeout = amdgpu_gem_timeout(wait->in.timeout); + long r; + + r = amdgpu_cs_get_ring(adev, wait->in.ip_type, wait->in.ip_instance, + wait->in.ring, &ring); + if (r) + return r; + + seq[ring->idx] = wait->in.handle; + + r = amdgpu_fence_wait_seq_timeout(adev, seq, true, timeout); + if (r < 0) + return r; + + memset(wait, 0, sizeof(*wait)); + wait->out.status = (r == 0); + + return 0; +} + +/** + * amdgpu_cs_find_bo_va - find bo_va for VM address + * + * @parser: command submission parser context + * @addr: VM address + * @bo: resulting BO of the mapping found + * + * Search the buffer objects in the command submission context for a certain + * virtual memory address. Returns allocation structure when found, NULL + * otherwise. + */ +struct amdgpu_bo_va_mapping * +amdgpu_cs_find_mapping(struct amdgpu_cs_parser *parser, + uint64_t addr, struct amdgpu_bo **bo) +{ + struct amdgpu_bo_list_entry *reloc; + struct amdgpu_bo_va_mapping *mapping; + + addr /= AMDGPU_GPU_PAGE_SIZE; + + list_for_each_entry(reloc, &parser->validated, tv.head) { + if (!reloc->bo_va) + continue; + + list_for_each_entry(mapping, &reloc->bo_va->mappings, list) { + if (mapping->it.start > addr || + addr > mapping->it.last) + continue; + + *bo = reloc->bo_va->bo; + return mapping; + } + } + + return NULL; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c new file mode 100644 index 000000000000..235010a83f8f --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c @@ -0,0 +1,161 @@ +/* + * Copyright 2015 Advanced Micro Devices, 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: monk liu <monk.liu@amd.com> + */ + +#include <drm/drmP.h> +#include "amdgpu.h" + +static void amdgpu_ctx_do_release(struct kref *ref) +{ + struct amdgpu_ctx *ctx; + struct amdgpu_ctx_mgr *mgr; + + ctx = container_of(ref, struct amdgpu_ctx, refcount); + mgr = &ctx->fpriv->ctx_mgr; + + mutex_lock(&mgr->hlock); + idr_remove(&mgr->ctx_handles, ctx->id); + mutex_unlock(&mgr->hlock); + kfree(ctx); +} + +int amdgpu_ctx_alloc(struct amdgpu_device *adev, struct amdgpu_fpriv *fpriv, uint32_t *id, uint32_t flags) +{ + int r; + struct amdgpu_ctx *ctx; + struct amdgpu_ctx_mgr *mgr = &fpriv->ctx_mgr; + + ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_lock(&mgr->hlock); + r = idr_alloc(&mgr->ctx_handles, ctx, 0, 0, GFP_KERNEL); + if (r < 0) { + mutex_unlock(&mgr->hlock); + kfree(ctx); + return r; + } + mutex_unlock(&mgr->hlock); + *id = (uint32_t)r; + + memset(ctx, 0, sizeof(*ctx)); + ctx->id = *id; + ctx->fpriv = fpriv; + kref_init(&ctx->refcount); + + return 0; +} + +int amdgpu_ctx_free(struct amdgpu_device *adev, struct amdgpu_fpriv *fpriv, uint32_t id) +{ + int r; + struct amdgpu_ctx *ctx; + struct amdgpu_ctx_mgr *mgr = &fpriv->ctx_mgr; + + rcu_read_lock(); + ctx = idr_find(&mgr->ctx_handles, id); + rcu_read_unlock(); + if (ctx) { + /* if no task is pending on this context, free it */ + r = kref_put(&ctx->refcount, amdgpu_ctx_do_release); + if (r == 1) + return 0;//context is removed successfully + else { + /* context is still in using */ + kref_get(&ctx->refcount); + return -ERESTARTSYS; + } + } + return -EINVAL; +} + +int amdgpu_ctx_query(struct amdgpu_device *adev, struct amdgpu_fpriv *fpriv, uint32_t id, struct amdgpu_ctx_state *state) +{ + struct amdgpu_ctx *ctx; + struct amdgpu_ctx_mgr *mgr = &fpriv->ctx_mgr; + + rcu_read_lock(); + ctx = idr_find(&mgr->ctx_handles, id); + rcu_read_unlock(); + if (ctx) { + /* state should alter with CS activity */ + *state = ctx->state; + return 0; + } + return -EINVAL; +} + +void amdgpu_ctx_fini(struct amdgpu_fpriv *fpriv) +{ + struct idr *idp; + struct amdgpu_ctx *ctx; + uint32_t id; + struct amdgpu_ctx_mgr *mgr = &fpriv->ctx_mgr; + idp = &mgr->ctx_handles; + + idr_for_each_entry(idp,ctx,id) { + if (kref_put(&ctx->refcount, amdgpu_ctx_do_release) != 1) + DRM_ERROR("ctx (id=%ul) is still alive\n",ctx->id); + } + + mutex_destroy(&mgr->hlock); +} + +int amdgpu_ctx_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + int r; + uint32_t id; + uint32_t flags; + struct amdgpu_ctx_state state; + + union drm_amdgpu_ctx *args = data; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_fpriv *fpriv = filp->driver_priv; + + r = 0; + id = args->in.ctx_id; + flags = args->in.flags; + + switch (args->in.op) { + case AMDGPU_CTX_OP_ALLOC_CTX: + r = amdgpu_ctx_alloc(adev, fpriv, &id, flags); + args->out.alloc.ctx_id = id; + break; + case AMDGPU_CTX_OP_FREE_CTX: + r = amdgpu_ctx_free(adev, fpriv, id); + break; + case AMDGPU_CTX_OP_QUERY_STATE: + r = amdgpu_ctx_query(adev, fpriv, id, &state); + if (r == 0) { + args->out.state.flags = state.flags; + args->out.state.hangs = state.hangs; + } + break; + default: + return -EINVAL; + } + + return r; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c new file mode 100644 index 000000000000..cd4bb90fa85c --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -0,0 +1,1971 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <linux/console.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/amdgpu_drm.h> +#include <linux/vgaarb.h> +#include <linux/vga_switcheroo.h> +#include <linux/efi.h> +#include "amdgpu.h" +#include "amdgpu_i2c.h" +#include "atom.h" +#include "amdgpu_atombios.h" +#include "bif/bif_4_1_d.h" + +static int amdgpu_debugfs_regs_init(struct amdgpu_device *adev); +static void amdgpu_debugfs_regs_cleanup(struct amdgpu_device *adev); + +static const char *amdgpu_asic_name[] = { + "BONAIRE", + "KAVERI", + "KABINI", + "HAWAII", + "MULLINS", + "TOPAZ", + "TONGA", + "CARRIZO", + "LAST", +}; + +bool amdgpu_device_is_px(struct drm_device *dev) +{ + struct amdgpu_device *adev = dev->dev_private; + + if (adev->flags & AMDGPU_IS_PX) + return true; + return false; +} + +/* + * MMIO register access helper functions. + */ +uint32_t amdgpu_mm_rreg(struct amdgpu_device *adev, uint32_t reg, + bool always_indirect) +{ + if ((reg * 4) < adev->rmmio_size && !always_indirect) + return readl(((void __iomem *)adev->rmmio) + (reg * 4)); + else { + unsigned long flags; + uint32_t ret; + + spin_lock_irqsave(&adev->mmio_idx_lock, flags); + writel((reg * 4), ((void __iomem *)adev->rmmio) + (mmMM_INDEX * 4)); + ret = readl(((void __iomem *)adev->rmmio) + (mmMM_DATA * 4)); + spin_unlock_irqrestore(&adev->mmio_idx_lock, flags); + + return ret; + } +} + +void amdgpu_mm_wreg(struct amdgpu_device *adev, uint32_t reg, uint32_t v, + bool always_indirect) +{ + if ((reg * 4) < adev->rmmio_size && !always_indirect) + writel(v, ((void __iomem *)adev->rmmio) + (reg * 4)); + else { + unsigned long flags; + + spin_lock_irqsave(&adev->mmio_idx_lock, flags); + writel((reg * 4), ((void __iomem *)adev->rmmio) + (mmMM_INDEX * 4)); + writel(v, ((void __iomem *)adev->rmmio) + (mmMM_DATA * 4)); + spin_unlock_irqrestore(&adev->mmio_idx_lock, flags); + } +} + +u32 amdgpu_io_rreg(struct amdgpu_device *adev, u32 reg) +{ + if ((reg * 4) < adev->rio_mem_size) + return ioread32(adev->rio_mem + (reg * 4)); + else { + iowrite32((reg * 4), adev->rio_mem + (mmMM_INDEX * 4)); + return ioread32(adev->rio_mem + (mmMM_DATA * 4)); + } +} + +void amdgpu_io_wreg(struct amdgpu_device *adev, u32 reg, u32 v) +{ + + if ((reg * 4) < adev->rio_mem_size) + iowrite32(v, adev->rio_mem + (reg * 4)); + else { + iowrite32((reg * 4), adev->rio_mem + (mmMM_INDEX * 4)); + iowrite32(v, adev->rio_mem + (mmMM_DATA * 4)); + } +} + +/** + * amdgpu_mm_rdoorbell - read a doorbell dword + * + * @adev: amdgpu_device pointer + * @index: doorbell index + * + * Returns the value in the doorbell aperture at the + * requested doorbell index (CIK). + */ +u32 amdgpu_mm_rdoorbell(struct amdgpu_device *adev, u32 index) +{ + if (index < adev->doorbell.num_doorbells) { + return readl(adev->doorbell.ptr + index); + } else { + DRM_ERROR("reading beyond doorbell aperture: 0x%08x!\n", index); + return 0; + } +} + +/** + * amdgpu_mm_wdoorbell - write a doorbell dword + * + * @adev: amdgpu_device pointer + * @index: doorbell index + * @v: value to write + * + * Writes @v to the doorbell aperture at the + * requested doorbell index (CIK). + */ +void amdgpu_mm_wdoorbell(struct amdgpu_device *adev, u32 index, u32 v) +{ + if (index < adev->doorbell.num_doorbells) { + writel(v, adev->doorbell.ptr + index); + } else { + DRM_ERROR("writing beyond doorbell aperture: 0x%08x!\n", index); + } +} + +/** + * amdgpu_invalid_rreg - dummy reg read function + * + * @adev: amdgpu device pointer + * @reg: offset of register + * + * Dummy register read function. Used for register blocks + * that certain asics don't have (all asics). + * Returns the value in the register. + */ +static uint32_t amdgpu_invalid_rreg(struct amdgpu_device *adev, uint32_t reg) +{ + DRM_ERROR("Invalid callback to read register 0x%04X\n", reg); + BUG(); + return 0; +} + +/** + * amdgpu_invalid_wreg - dummy reg write function + * + * @adev: amdgpu device pointer + * @reg: offset of register + * @v: value to write to the register + * + * Dummy register read function. Used for register blocks + * that certain asics don't have (all asics). + */ +static void amdgpu_invalid_wreg(struct amdgpu_device *adev, uint32_t reg, uint32_t v) +{ + DRM_ERROR("Invalid callback to write register 0x%04X with 0x%08X\n", + reg, v); + BUG(); +} + +/** + * amdgpu_block_invalid_rreg - dummy reg read function + * + * @adev: amdgpu device pointer + * @block: offset of instance + * @reg: offset of register + * + * Dummy register read function. Used for register blocks + * that certain asics don't have (all asics). + * Returns the value in the register. + */ +static uint32_t amdgpu_block_invalid_rreg(struct amdgpu_device *adev, + uint32_t block, uint32_t reg) +{ + DRM_ERROR("Invalid callback to read register 0x%04X in block 0x%04X\n", + reg, block); + BUG(); + return 0; +} + +/** + * amdgpu_block_invalid_wreg - dummy reg write function + * + * @adev: amdgpu device pointer + * @block: offset of instance + * @reg: offset of register + * @v: value to write to the register + * + * Dummy register read function. Used for register blocks + * that certain asics don't have (all asics). + */ +static void amdgpu_block_invalid_wreg(struct amdgpu_device *adev, + uint32_t block, + uint32_t reg, uint32_t v) +{ + DRM_ERROR("Invalid block callback to write register 0x%04X in block 0x%04X with 0x%08X\n", + reg, block, v); + BUG(); +} + +static int amdgpu_vram_scratch_init(struct amdgpu_device *adev) +{ + int r; + + if (adev->vram_scratch.robj == NULL) { + r = amdgpu_bo_create(adev, AMDGPU_GPU_PAGE_SIZE, + PAGE_SIZE, true, AMDGPU_GEM_DOMAIN_VRAM, 0, + NULL, &adev->vram_scratch.robj); + if (r) { + return r; + } + } + + r = amdgpu_bo_reserve(adev->vram_scratch.robj, false); + if (unlikely(r != 0)) + return r; + r = amdgpu_bo_pin(adev->vram_scratch.robj, + AMDGPU_GEM_DOMAIN_VRAM, &adev->vram_scratch.gpu_addr); + if (r) { + amdgpu_bo_unreserve(adev->vram_scratch.robj); + return r; + } + r = amdgpu_bo_kmap(adev->vram_scratch.robj, + (void **)&adev->vram_scratch.ptr); + if (r) + amdgpu_bo_unpin(adev->vram_scratch.robj); + amdgpu_bo_unreserve(adev->vram_scratch.robj); + + return r; +} + +static void amdgpu_vram_scratch_fini(struct amdgpu_device *adev) +{ + int r; + + if (adev->vram_scratch.robj == NULL) { + return; + } + r = amdgpu_bo_reserve(adev->vram_scratch.robj, false); + if (likely(r == 0)) { + amdgpu_bo_kunmap(adev->vram_scratch.robj); + amdgpu_bo_unpin(adev->vram_scratch.robj); + amdgpu_bo_unreserve(adev->vram_scratch.robj); + } + amdgpu_bo_unref(&adev->vram_scratch.robj); +} + +/** + * amdgpu_program_register_sequence - program an array of registers. + * + * @adev: amdgpu_device pointer + * @registers: pointer to the register array + * @array_size: size of the register array + * + * Programs an array or registers with and and or masks. + * This is a helper for setting golden registers. + */ +void amdgpu_program_register_sequence(struct amdgpu_device *adev, + const u32 *registers, + const u32 array_size) +{ + u32 tmp, reg, and_mask, or_mask; + int i; + + if (array_size % 3) + return; + + for (i = 0; i < array_size; i +=3) { + reg = registers[i + 0]; + and_mask = registers[i + 1]; + or_mask = registers[i + 2]; + + if (and_mask == 0xffffffff) { + tmp = or_mask; + } else { + tmp = RREG32(reg); + tmp &= ~and_mask; + tmp |= or_mask; + } + WREG32(reg, tmp); + } +} + +void amdgpu_pci_config_reset(struct amdgpu_device *adev) +{ + pci_write_config_dword(adev->pdev, 0x7c, AMDGPU_ASIC_RESET_DATA); +} + +/* + * GPU doorbell aperture helpers function. + */ +/** + * amdgpu_doorbell_init - Init doorbell driver information. + * + * @adev: amdgpu_device pointer + * + * Init doorbell driver information (CIK) + * Returns 0 on success, error on failure. + */ +static int amdgpu_doorbell_init(struct amdgpu_device *adev) +{ + /* doorbell bar mapping */ + adev->doorbell.base = pci_resource_start(adev->pdev, 2); + adev->doorbell.size = pci_resource_len(adev->pdev, 2); + + adev->doorbell.num_doorbells = min_t(u32, adev->doorbell.size / sizeof(u32), + AMDGPU_DOORBELL_MAX_ASSIGNMENT+1); + if (adev->doorbell.num_doorbells == 0) + return -EINVAL; + + adev->doorbell.ptr = ioremap(adev->doorbell.base, adev->doorbell.num_doorbells * sizeof(u32)); + if (adev->doorbell.ptr == NULL) { + return -ENOMEM; + } + DRM_INFO("doorbell mmio base: 0x%08X\n", (uint32_t)adev->doorbell.base); + DRM_INFO("doorbell mmio size: %u\n", (unsigned)adev->doorbell.size); + + return 0; +} + +/** + * amdgpu_doorbell_fini - Tear down doorbell driver information. + * + * @adev: amdgpu_device pointer + * + * Tear down doorbell driver information (CIK) + */ +static void amdgpu_doorbell_fini(struct amdgpu_device *adev) +{ + iounmap(adev->doorbell.ptr); + adev->doorbell.ptr = NULL; +} + +/** + * amdgpu_doorbell_get_kfd_info - Report doorbell configuration required to + * setup amdkfd + * + * @adev: amdgpu_device pointer + * @aperture_base: output returning doorbell aperture base physical address + * @aperture_size: output returning doorbell aperture size in bytes + * @start_offset: output returning # of doorbell bytes reserved for amdgpu. + * + * amdgpu and amdkfd share the doorbell aperture. amdgpu sets it up, + * takes doorbells required for its own rings and reports the setup to amdkfd. + * amdgpu reserved doorbells are at the start of the doorbell aperture. + */ +void amdgpu_doorbell_get_kfd_info(struct amdgpu_device *adev, + phys_addr_t *aperture_base, + size_t *aperture_size, + size_t *start_offset) +{ + /* + * The first num_doorbells are used by amdgpu. + * amdkfd takes whatever's left in the aperture. + */ + if (adev->doorbell.size > adev->doorbell.num_doorbells * sizeof(u32)) { + *aperture_base = adev->doorbell.base; + *aperture_size = adev->doorbell.size; + *start_offset = adev->doorbell.num_doorbells * sizeof(u32); + } else { + *aperture_base = 0; + *aperture_size = 0; + *start_offset = 0; + } +} + +/* + * amdgpu_wb_*() + * Writeback is the the method by which the the GPU updates special pages + * in memory with the status of certain GPU events (fences, ring pointers, + * etc.). + */ + +/** + * amdgpu_wb_fini - Disable Writeback and free memory + * + * @adev: amdgpu_device pointer + * + * Disables Writeback and frees the Writeback memory (all asics). + * Used at driver shutdown. + */ +static void amdgpu_wb_fini(struct amdgpu_device *adev) +{ + if (adev->wb.wb_obj) { + if (!amdgpu_bo_reserve(adev->wb.wb_obj, false)) { + amdgpu_bo_kunmap(adev->wb.wb_obj); + amdgpu_bo_unpin(adev->wb.wb_obj); + amdgpu_bo_unreserve(adev->wb.wb_obj); + } + amdgpu_bo_unref(&adev->wb.wb_obj); + adev->wb.wb = NULL; + adev->wb.wb_obj = NULL; + } +} + +/** + * amdgpu_wb_init- Init Writeback driver info and allocate memory + * + * @adev: amdgpu_device pointer + * + * Disables Writeback and frees the Writeback memory (all asics). + * Used at driver startup. + * Returns 0 on success or an -error on failure. + */ +static int amdgpu_wb_init(struct amdgpu_device *adev) +{ + int r; + + if (adev->wb.wb_obj == NULL) { + r = amdgpu_bo_create(adev, AMDGPU_MAX_WB * 4, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_GTT, 0, NULL, &adev->wb.wb_obj); + if (r) { + dev_warn(adev->dev, "(%d) create WB bo failed\n", r); + return r; + } + r = amdgpu_bo_reserve(adev->wb.wb_obj, false); + if (unlikely(r != 0)) { + amdgpu_wb_fini(adev); + return r; + } + r = amdgpu_bo_pin(adev->wb.wb_obj, AMDGPU_GEM_DOMAIN_GTT, + &adev->wb.gpu_addr); + if (r) { + amdgpu_bo_unreserve(adev->wb.wb_obj); + dev_warn(adev->dev, "(%d) pin WB bo failed\n", r); + amdgpu_wb_fini(adev); + return r; + } + r = amdgpu_bo_kmap(adev->wb.wb_obj, (void **)&adev->wb.wb); + amdgpu_bo_unreserve(adev->wb.wb_obj); + if (r) { + dev_warn(adev->dev, "(%d) map WB bo failed\n", r); + amdgpu_wb_fini(adev); + return r; + } + + adev->wb.num_wb = AMDGPU_MAX_WB; + memset(&adev->wb.used, 0, sizeof(adev->wb.used)); + + /* clear wb memory */ + memset((char *)adev->wb.wb, 0, AMDGPU_GPU_PAGE_SIZE); + } + + return 0; +} + +/** + * amdgpu_wb_get - Allocate a wb entry + * + * @adev: amdgpu_device pointer + * @wb: wb index + * + * Allocate a wb slot for use by the driver (all asics). + * Returns 0 on success or -EINVAL on failure. + */ +int amdgpu_wb_get(struct amdgpu_device *adev, u32 *wb) +{ + unsigned long offset = find_first_zero_bit(adev->wb.used, adev->wb.num_wb); + if (offset < adev->wb.num_wb) { + __set_bit(offset, adev->wb.used); + *wb = offset; + return 0; + } else { + return -EINVAL; + } +} + +/** + * amdgpu_wb_free - Free a wb entry + * + * @adev: amdgpu_device pointer + * @wb: wb index + * + * Free a wb slot allocated for use by the driver (all asics) + */ +void amdgpu_wb_free(struct amdgpu_device *adev, u32 wb) +{ + if (wb < adev->wb.num_wb) + __clear_bit(wb, adev->wb.used); +} + +/** + * amdgpu_vram_location - try to find VRAM location + * @adev: amdgpu device structure holding all necessary informations + * @mc: memory controller structure holding memory informations + * @base: base address at which to put VRAM + * + * Function will place try to place VRAM at base address provided + * as parameter (which is so far either PCI aperture address or + * for IGP TOM base address). + * + * If there is not enough space to fit the unvisible VRAM in the 32bits + * address space then we limit the VRAM size to the aperture. + * + * Note: We don't explicitly enforce VRAM start to be aligned on VRAM size, + * this shouldn't be a problem as we are using the PCI aperture as a reference. + * Otherwise this would be needed for rv280, all r3xx, and all r4xx, but + * not IGP. + * + * Note: we use mc_vram_size as on some board we need to program the mc to + * cover the whole aperture even if VRAM size is inferior to aperture size + * Novell bug 204882 + along with lots of ubuntu ones + * + * Note: when limiting vram it's safe to overwritte real_vram_size because + * we are not in case where real_vram_size is inferior to mc_vram_size (ie + * note afected by bogus hw of Novell bug 204882 + along with lots of ubuntu + * ones) + * + * Note: IGP TOM addr should be the same as the aperture addr, we don't + * explicitly check for that thought. + * + * FIXME: when reducing VRAM size align new size on power of 2. + */ +void amdgpu_vram_location(struct amdgpu_device *adev, struct amdgpu_mc *mc, u64 base) +{ + uint64_t limit = (uint64_t)amdgpu_vram_limit << 20; + + mc->vram_start = base; + if (mc->mc_vram_size > (adev->mc.mc_mask - base + 1)) { + dev_warn(adev->dev, "limiting VRAM to PCI aperture size\n"); + mc->real_vram_size = mc->aper_size; + mc->mc_vram_size = mc->aper_size; + } + mc->vram_end = mc->vram_start + mc->mc_vram_size - 1; + if (limit && limit < mc->real_vram_size) + mc->real_vram_size = limit; + dev_info(adev->dev, "VRAM: %lluM 0x%016llX - 0x%016llX (%lluM used)\n", + mc->mc_vram_size >> 20, mc->vram_start, + mc->vram_end, mc->real_vram_size >> 20); +} + +/** + * amdgpu_gtt_location - try to find GTT location + * @adev: amdgpu device structure holding all necessary informations + * @mc: memory controller structure holding memory informations + * + * Function will place try to place GTT before or after VRAM. + * + * If GTT size is bigger than space left then we ajust GTT size. + * Thus function will never fails. + * + * FIXME: when reducing GTT size align new size on power of 2. + */ +void amdgpu_gtt_location(struct amdgpu_device *adev, struct amdgpu_mc *mc) +{ + u64 size_af, size_bf; + + size_af = ((adev->mc.mc_mask - mc->vram_end) + mc->gtt_base_align) & ~mc->gtt_base_align; + size_bf = mc->vram_start & ~mc->gtt_base_align; + if (size_bf > size_af) { + if (mc->gtt_size > size_bf) { + dev_warn(adev->dev, "limiting GTT\n"); + mc->gtt_size = size_bf; + } + mc->gtt_start = (mc->vram_start & ~mc->gtt_base_align) - mc->gtt_size; + } else { + if (mc->gtt_size > size_af) { + dev_warn(adev->dev, "limiting GTT\n"); + mc->gtt_size = size_af; + } + mc->gtt_start = (mc->vram_end + 1 + mc->gtt_base_align) & ~mc->gtt_base_align; + } + mc->gtt_end = mc->gtt_start + mc->gtt_size - 1; + dev_info(adev->dev, "GTT: %lluM 0x%016llX - 0x%016llX\n", + mc->gtt_size >> 20, mc->gtt_start, mc->gtt_end); +} + +/* + * GPU helpers function. + */ +/** + * amdgpu_card_posted - check if the hw has already been initialized + * + * @adev: amdgpu_device pointer + * + * Check if the asic has been initialized (all asics). + * Used at driver startup. + * Returns true if initialized or false if not. + */ +bool amdgpu_card_posted(struct amdgpu_device *adev) +{ + uint32_t reg; + + /* then check MEM_SIZE, in case the crtcs are off */ + reg = RREG32(mmCONFIG_MEMSIZE); + + if (reg) + return true; + + return false; + +} + +/** + * amdgpu_boot_test_post_card - check and possibly initialize the hw + * + * @adev: amdgpu_device pointer + * + * Check if the asic is initialized and if not, attempt to initialize + * it (all asics). + * Returns true if initialized or false if not. + */ +bool amdgpu_boot_test_post_card(struct amdgpu_device *adev) +{ + if (amdgpu_card_posted(adev)) + return true; + + if (adev->bios) { + DRM_INFO("GPU not posted. posting now...\n"); + if (adev->is_atom_bios) + amdgpu_atom_asic_init(adev->mode_info.atom_context); + return true; + } else { + dev_err(adev->dev, "Card not posted and no BIOS - ignoring\n"); + return false; + } +} + +/** + * amdgpu_dummy_page_init - init dummy page used by the driver + * + * @adev: amdgpu_device pointer + * + * Allocate the dummy page used by the driver (all asics). + * This dummy page is used by the driver as a filler for gart entries + * when pages are taken out of the GART + * Returns 0 on sucess, -ENOMEM on failure. + */ +int amdgpu_dummy_page_init(struct amdgpu_device *adev) +{ + if (adev->dummy_page.page) + return 0; + adev->dummy_page.page = alloc_page(GFP_DMA32 | GFP_KERNEL | __GFP_ZERO); + if (adev->dummy_page.page == NULL) + return -ENOMEM; + adev->dummy_page.addr = pci_map_page(adev->pdev, adev->dummy_page.page, + 0, PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(adev->pdev, adev->dummy_page.addr)) { + dev_err(&adev->pdev->dev, "Failed to DMA MAP the dummy page\n"); + __free_page(adev->dummy_page.page); + adev->dummy_page.page = NULL; + return -ENOMEM; + } + return 0; +} + +/** + * amdgpu_dummy_page_fini - free dummy page used by the driver + * + * @adev: amdgpu_device pointer + * + * Frees the dummy page used by the driver (all asics). + */ +void amdgpu_dummy_page_fini(struct amdgpu_device *adev) +{ + if (adev->dummy_page.page == NULL) + return; + pci_unmap_page(adev->pdev, adev->dummy_page.addr, + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + __free_page(adev->dummy_page.page); + adev->dummy_page.page = NULL; +} + + +/* ATOM accessor methods */ +/* + * ATOM is an interpreted byte code stored in tables in the vbios. The + * driver registers callbacks to access registers and the interpreter + * in the driver parses the tables and executes then to program specific + * actions (set display modes, asic init, etc.). See amdgpu_atombios.c, + * atombios.h, and atom.c + */ + +/** + * cail_pll_read - read PLL register + * + * @info: atom card_info pointer + * @reg: PLL register offset + * + * Provides a PLL register accessor for the atom interpreter (r4xx+). + * Returns the value of the PLL register. + */ +static uint32_t cail_pll_read(struct card_info *info, uint32_t reg) +{ + return 0; +} + +/** + * cail_pll_write - write PLL register + * + * @info: atom card_info pointer + * @reg: PLL register offset + * @val: value to write to the pll register + * + * Provides a PLL register accessor for the atom interpreter (r4xx+). + */ +static void cail_pll_write(struct card_info *info, uint32_t reg, uint32_t val) +{ + +} + +/** + * cail_mc_read - read MC (Memory Controller) register + * + * @info: atom card_info pointer + * @reg: MC register offset + * + * Provides an MC register accessor for the atom interpreter (r4xx+). + * Returns the value of the MC register. + */ +static uint32_t cail_mc_read(struct card_info *info, uint32_t reg) +{ + return 0; +} + +/** + * cail_mc_write - write MC (Memory Controller) register + * + * @info: atom card_info pointer + * @reg: MC register offset + * @val: value to write to the pll register + * + * Provides a MC register accessor for the atom interpreter (r4xx+). + */ +static void cail_mc_write(struct card_info *info, uint32_t reg, uint32_t val) +{ + +} + +/** + * cail_reg_write - write MMIO register + * + * @info: atom card_info pointer + * @reg: MMIO register offset + * @val: value to write to the pll register + * + * Provides a MMIO register accessor for the atom interpreter (r4xx+). + */ +static void cail_reg_write(struct card_info *info, uint32_t reg, uint32_t val) +{ + struct amdgpu_device *adev = info->dev->dev_private; + + WREG32(reg, val); +} + +/** + * cail_reg_read - read MMIO register + * + * @info: atom card_info pointer + * @reg: MMIO register offset + * + * Provides an MMIO register accessor for the atom interpreter (r4xx+). + * Returns the value of the MMIO register. + */ +static uint32_t cail_reg_read(struct card_info *info, uint32_t reg) +{ + struct amdgpu_device *adev = info->dev->dev_private; + uint32_t r; + + r = RREG32(reg); + return r; +} + +/** + * cail_ioreg_write - write IO register + * + * @info: atom card_info pointer + * @reg: IO register offset + * @val: value to write to the pll register + * + * Provides a IO register accessor for the atom interpreter (r4xx+). + */ +static void cail_ioreg_write(struct card_info *info, uint32_t reg, uint32_t val) +{ + struct amdgpu_device *adev = info->dev->dev_private; + + WREG32_IO(reg, val); +} + +/** + * cail_ioreg_read - read IO register + * + * @info: atom card_info pointer + * @reg: IO register offset + * + * Provides an IO register accessor for the atom interpreter (r4xx+). + * Returns the value of the IO register. + */ +static uint32_t cail_ioreg_read(struct card_info *info, uint32_t reg) +{ + struct amdgpu_device *adev = info->dev->dev_private; + uint32_t r; + + r = RREG32_IO(reg); + return r; +} + +/** + * amdgpu_atombios_fini - free the driver info and callbacks for atombios + * + * @adev: amdgpu_device pointer + * + * Frees the driver info and register access callbacks for the ATOM + * interpreter (r4xx+). + * Called at driver shutdown. + */ +static void amdgpu_atombios_fini(struct amdgpu_device *adev) +{ + if (adev->mode_info.atom_context) + kfree(adev->mode_info.atom_context->scratch); + kfree(adev->mode_info.atom_context); + adev->mode_info.atom_context = NULL; + kfree(adev->mode_info.atom_card_info); + adev->mode_info.atom_card_info = NULL; +} + +/** + * amdgpu_atombios_init - init the driver info and callbacks for atombios + * + * @adev: amdgpu_device pointer + * + * Initializes the driver info and register access callbacks for the + * ATOM interpreter (r4xx+). + * Returns 0 on sucess, -ENOMEM on failure. + * Called at driver startup. + */ +static int amdgpu_atombios_init(struct amdgpu_device *adev) +{ + struct card_info *atom_card_info = + kzalloc(sizeof(struct card_info), GFP_KERNEL); + + if (!atom_card_info) + return -ENOMEM; + + adev->mode_info.atom_card_info = atom_card_info; + atom_card_info->dev = adev->ddev; + atom_card_info->reg_read = cail_reg_read; + atom_card_info->reg_write = cail_reg_write; + /* needed for iio ops */ + if (adev->rio_mem) { + atom_card_info->ioreg_read = cail_ioreg_read; + atom_card_info->ioreg_write = cail_ioreg_write; + } else { + DRM_ERROR("Unable to find PCI I/O BAR; using MMIO for ATOM IIO\n"); + atom_card_info->ioreg_read = cail_reg_read; + atom_card_info->ioreg_write = cail_reg_write; + } + atom_card_info->mc_read = cail_mc_read; + atom_card_info->mc_write = cail_mc_write; + atom_card_info->pll_read = cail_pll_read; + atom_card_info->pll_write = cail_pll_write; + + adev->mode_info.atom_context = amdgpu_atom_parse(atom_card_info, adev->bios); + if (!adev->mode_info.atom_context) { + amdgpu_atombios_fini(adev); + return -ENOMEM; + } + + mutex_init(&adev->mode_info.atom_context->mutex); + amdgpu_atombios_scratch_regs_init(adev); + amdgpu_atom_allocate_fb_scratch(adev->mode_info.atom_context); + return 0; +} + +/* if we get transitioned to only one device, take VGA back */ +/** + * amdgpu_vga_set_decode - enable/disable vga decode + * + * @cookie: amdgpu_device pointer + * @state: enable/disable vga decode + * + * Enable/disable vga decode (all asics). + * Returns VGA resource flags. + */ +static unsigned int amdgpu_vga_set_decode(void *cookie, bool state) +{ + struct amdgpu_device *adev = cookie; + amdgpu_asic_set_vga_state(adev, 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; +} + +/** + * amdgpu_check_pot_argument - check that argument is a power of two + * + * @arg: value to check + * + * Validates that a certain argument is a power of two (all asics). + * Returns true if argument is valid. + */ +static bool amdgpu_check_pot_argument(int arg) +{ + return (arg & (arg - 1)) == 0; +} + +/** + * amdgpu_check_arguments - validate module params + * + * @adev: amdgpu_device pointer + * + * Validates certain module parameters and updates + * the associated values used by the driver (all asics). + */ +static void amdgpu_check_arguments(struct amdgpu_device *adev) +{ + /* vramlimit must be a power of two */ + if (!amdgpu_check_pot_argument(amdgpu_vram_limit)) { + dev_warn(adev->dev, "vram limit (%d) must be a power of 2\n", + amdgpu_vram_limit); + amdgpu_vram_limit = 0; + } + + if (amdgpu_gart_size != -1) { + /* gtt size must be power of two and greater or equal to 32M */ + if (amdgpu_gart_size < 32) { + dev_warn(adev->dev, "gart size (%d) too small\n", + amdgpu_gart_size); + amdgpu_gart_size = -1; + } else if (!amdgpu_check_pot_argument(amdgpu_gart_size)) { + dev_warn(adev->dev, "gart size (%d) must be a power of 2\n", + amdgpu_gart_size); + amdgpu_gart_size = -1; + } + } + + if (!amdgpu_check_pot_argument(amdgpu_vm_size)) { + dev_warn(adev->dev, "VM size (%d) must be a power of 2\n", + amdgpu_vm_size); + amdgpu_vm_size = 4; + } + + if (amdgpu_vm_size < 1) { + dev_warn(adev->dev, "VM size (%d) too small, min is 1GB\n", + amdgpu_vm_size); + amdgpu_vm_size = 4; + } + + /* + * Max GPUVM size for Cayman, SI and CI are 40 bits. + */ + if (amdgpu_vm_size > 1024) { + dev_warn(adev->dev, "VM size (%d) too large, max is 1TB\n", + amdgpu_vm_size); + amdgpu_vm_size = 4; + } + + /* defines number of bits in page table versus page directory, + * a page is 4KB so we have 12 bits offset, minimum 9 bits in the + * page table and the remaining bits are in the page directory */ + if (amdgpu_vm_block_size == -1) { + + /* Total bits covered by PD + PTs */ + unsigned bits = ilog2(amdgpu_vm_size) + 18; + + /* Make sure the PD is 4K in size up to 8GB address space. + Above that split equal between PD and PTs */ + if (amdgpu_vm_size <= 8) + amdgpu_vm_block_size = bits - 9; + else + amdgpu_vm_block_size = (bits + 3) / 2; + + } else if (amdgpu_vm_block_size < 9) { + dev_warn(adev->dev, "VM page table size (%d) too small\n", + amdgpu_vm_block_size); + amdgpu_vm_block_size = 9; + } + + if (amdgpu_vm_block_size > 24 || + (amdgpu_vm_size * 1024) < (1ull << amdgpu_vm_block_size)) { + dev_warn(adev->dev, "VM page table size (%d) too large\n", + amdgpu_vm_block_size); + amdgpu_vm_block_size = 9; + } +} + +/** + * amdgpu_switcheroo_set_state - set switcheroo state + * + * @pdev: pci dev pointer + * @state: vga switcheroo state + * + * Callback for the switcheroo driver. Suspends or resumes the + * the asics before or after it is powered up using ACPI methods. + */ +static void amdgpu_switcheroo_set_state(struct pci_dev *pdev, enum vga_switcheroo_state state) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + if (amdgpu_device_is_px(dev) && state == VGA_SWITCHEROO_OFF) + return; + + if (state == VGA_SWITCHEROO_ON) { + unsigned d3_delay = dev->pdev->d3_delay; + + printk(KERN_INFO "amdgpu: switched on\n"); + /* don't suspend or resume card normally */ + dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; + + amdgpu_resume_kms(dev, true, true); + + dev->pdev->d3_delay = d3_delay; + + dev->switch_power_state = DRM_SWITCH_POWER_ON; + drm_kms_helper_poll_enable(dev); + } else { + printk(KERN_INFO "amdgpu: switched off\n"); + drm_kms_helper_poll_disable(dev); + dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; + amdgpu_suspend_kms(dev, true, true); + dev->switch_power_state = DRM_SWITCH_POWER_OFF; + } +} + +/** + * amdgpu_switcheroo_can_switch - see if switcheroo state can change + * + * @pdev: pci dev pointer + * + * Callback for the switcheroo driver. Check of the switcheroo + * state can be changed. + * Returns true if the state can be changed, false if not. + */ +static bool amdgpu_switcheroo_can_switch(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + /* + * FIXME: open_count is protected by drm_global_mutex but that would lead to + * locking inversion with the driver load path. And the access here is + * completely racy anyway. So don't bother with locking for now. + */ + return dev->open_count == 0; +} + +static const struct vga_switcheroo_client_ops amdgpu_switcheroo_ops = { + .set_gpu_state = amdgpu_switcheroo_set_state, + .reprobe = NULL, + .can_switch = amdgpu_switcheroo_can_switch, +}; + +int amdgpu_set_clockgating_state(struct amdgpu_device *adev, + enum amdgpu_ip_block_type block_type, + enum amdgpu_clockgating_state state) +{ + int i, r = 0; + + for (i = 0; i < adev->num_ip_blocks; i++) { + if (adev->ip_blocks[i].type == block_type) { + r = adev->ip_blocks[i].funcs->set_clockgating_state(adev, + state); + if (r) + return r; + } + } + return r; +} + +int amdgpu_set_powergating_state(struct amdgpu_device *adev, + enum amdgpu_ip_block_type block_type, + enum amdgpu_powergating_state state) +{ + int i, r = 0; + + for (i = 0; i < adev->num_ip_blocks; i++) { + if (adev->ip_blocks[i].type == block_type) { + r = adev->ip_blocks[i].funcs->set_powergating_state(adev, + state); + if (r) + return r; + } + } + return r; +} + +const struct amdgpu_ip_block_version * amdgpu_get_ip_block( + struct amdgpu_device *adev, + enum amdgpu_ip_block_type type) +{ + int i; + + for (i = 0; i < adev->num_ip_blocks; i++) + if (adev->ip_blocks[i].type == type) + return &adev->ip_blocks[i]; + + return NULL; +} + +/** + * amdgpu_ip_block_version_cmp + * + * @adev: amdgpu_device pointer + * @type: enum amdgpu_ip_block_type + * @major: major version + * @minor: minor version + * + * return 0 if equal or greater + * return 1 if smaller or the ip_block doesn't exist + */ +int amdgpu_ip_block_version_cmp(struct amdgpu_device *adev, + enum amdgpu_ip_block_type type, + u32 major, u32 minor) +{ + const struct amdgpu_ip_block_version *ip_block; + ip_block = amdgpu_get_ip_block(adev, type); + + if (ip_block && ((ip_block->major > major) || + ((ip_block->major == major) && + (ip_block->minor >= minor)))) + return 0; + + return 1; +} + +static int amdgpu_early_init(struct amdgpu_device *adev) +{ + int i, r = -EINVAL; + + switch (adev->asic_type) { + default: + /* FIXME: not supported yet */ + return -EINVAL; + } + + + + if (adev->ip_blocks == NULL) { + DRM_ERROR("No IP blocks found!\n"); + return r; + } + + for (i = 0; i < adev->num_ip_blocks; i++) { + if ((amdgpu_ip_block_mask & (1 << i)) == 0) { + DRM_ERROR("disabled ip block: %d\n", i); + adev->ip_block_enabled[i] = false; + } else { + if (adev->ip_blocks[i].funcs->early_init) { + r = adev->ip_blocks[i].funcs->early_init(adev); + if (r) + return r; + } + adev->ip_block_enabled[i] = true; + } + } + + return 0; +} + +static int amdgpu_init(struct amdgpu_device *adev) +{ + int i, r; + + for (i = 0; i < adev->num_ip_blocks; i++) { + if (!adev->ip_block_enabled[i]) + continue; + r = adev->ip_blocks[i].funcs->sw_init(adev); + if (r) + return r; + /* need to do gmc hw init early so we can allocate gpu mem */ + if (adev->ip_blocks[i].type == AMDGPU_IP_BLOCK_TYPE_GMC) { + r = amdgpu_vram_scratch_init(adev); + if (r) + return r; + r = adev->ip_blocks[i].funcs->hw_init(adev); + if (r) + return r; + r = amdgpu_wb_init(adev); + if (r) + return r; + } + } + + for (i = 0; i < adev->num_ip_blocks; i++) { + if (!adev->ip_block_enabled[i]) + continue; + /* gmc hw init is done early */ + if (adev->ip_blocks[i].type == AMDGPU_IP_BLOCK_TYPE_GMC) + continue; + r = adev->ip_blocks[i].funcs->hw_init(adev); + if (r) + return r; + } + + return 0; +} + +static int amdgpu_late_init(struct amdgpu_device *adev) +{ + int i = 0, r; + + for (i = 0; i < adev->num_ip_blocks; i++) { + if (!adev->ip_block_enabled[i]) + continue; + /* enable clockgating to save power */ + r = adev->ip_blocks[i].funcs->set_clockgating_state(adev, + AMDGPU_CG_STATE_GATE); + if (r) + return r; + if (adev->ip_blocks[i].funcs->late_init) { + r = adev->ip_blocks[i].funcs->late_init(adev); + if (r) + return r; + } + } + + return 0; +} + +static int amdgpu_fini(struct amdgpu_device *adev) +{ + int i, r; + + for (i = adev->num_ip_blocks - 1; i >= 0; i--) { + if (!adev->ip_block_enabled[i]) + continue; + if (adev->ip_blocks[i].type == AMDGPU_IP_BLOCK_TYPE_GMC) { + amdgpu_wb_fini(adev); + amdgpu_vram_scratch_fini(adev); + } + /* ungate blocks before hw fini so that we can shutdown the blocks safely */ + r = adev->ip_blocks[i].funcs->set_clockgating_state(adev, + AMDGPU_CG_STATE_UNGATE); + if (r) + return r; + r = adev->ip_blocks[i].funcs->hw_fini(adev); + /* XXX handle errors */ + } + + for (i = adev->num_ip_blocks - 1; i >= 0; i--) { + if (!adev->ip_block_enabled[i]) + continue; + r = adev->ip_blocks[i].funcs->sw_fini(adev); + /* XXX handle errors */ + adev->ip_block_enabled[i] = false; + } + + return 0; +} + +static int amdgpu_suspend(struct amdgpu_device *adev) +{ + int i, r; + + for (i = adev->num_ip_blocks - 1; i >= 0; i--) { + if (!adev->ip_block_enabled[i]) + continue; + /* ungate blocks so that suspend can properly shut them down */ + r = adev->ip_blocks[i].funcs->set_clockgating_state(adev, + AMDGPU_CG_STATE_UNGATE); + /* XXX handle errors */ + r = adev->ip_blocks[i].funcs->suspend(adev); + /* XXX handle errors */ + } + + return 0; +} + +static int amdgpu_resume(struct amdgpu_device *adev) +{ + int i, r; + + for (i = 0; i < adev->num_ip_blocks; i++) { + if (!adev->ip_block_enabled[i]) + continue; + r = adev->ip_blocks[i].funcs->resume(adev); + if (r) + return r; + } + + return 0; +} + +/** + * amdgpu_device_init - initialize the driver + * + * @adev: amdgpu_device pointer + * @pdev: drm dev pointer + * @pdev: pci dev pointer + * @flags: driver flags + * + * Initializes the driver info and hw (all asics). + * Returns 0 for success or an error on failure. + * Called at driver startup. + */ +int amdgpu_device_init(struct amdgpu_device *adev, + struct drm_device *ddev, + struct pci_dev *pdev, + uint32_t flags) +{ + int r, i; + bool runtime = false; + + adev->shutdown = false; + adev->dev = &pdev->dev; + adev->ddev = ddev; + adev->pdev = pdev; + adev->flags = flags; + adev->asic_type = flags & AMDGPU_ASIC_MASK; + adev->is_atom_bios = false; + adev->usec_timeout = AMDGPU_MAX_USEC_TIMEOUT; + adev->mc.gtt_size = 512 * 1024 * 1024; + adev->accel_working = false; + adev->num_rings = 0; + adev->mman.buffer_funcs = NULL; + adev->mman.buffer_funcs_ring = NULL; + adev->vm_manager.vm_pte_funcs = NULL; + adev->vm_manager.vm_pte_funcs_ring = NULL; + adev->gart.gart_funcs = NULL; + adev->fence_context = fence_context_alloc(AMDGPU_MAX_RINGS); + + adev->smc_rreg = &amdgpu_invalid_rreg; + adev->smc_wreg = &amdgpu_invalid_wreg; + adev->pcie_rreg = &amdgpu_invalid_rreg; + adev->pcie_wreg = &amdgpu_invalid_wreg; + adev->uvd_ctx_rreg = &amdgpu_invalid_rreg; + adev->uvd_ctx_wreg = &amdgpu_invalid_wreg; + adev->didt_rreg = &amdgpu_invalid_rreg; + adev->didt_wreg = &amdgpu_invalid_wreg; + adev->audio_endpt_rreg = &amdgpu_block_invalid_rreg; + adev->audio_endpt_wreg = &amdgpu_block_invalid_wreg; + + DRM_INFO("initializing kernel modesetting (%s 0x%04X:0x%04X 0x%04X:0x%04X).\n", + amdgpu_asic_name[adev->asic_type], pdev->vendor, pdev->device, + pdev->subsystem_vendor, pdev->subsystem_device); + + /* mutex initialization are all done here so we + * can recall function without having locking issues */ + mutex_init(&adev->ring_lock); + atomic_set(&adev->irq.ih.lock, 0); + mutex_init(&adev->gem.mutex); + mutex_init(&adev->pm.mutex); + mutex_init(&adev->gfx.gpu_clock_mutex); + mutex_init(&adev->srbm_mutex); + mutex_init(&adev->grbm_idx_mutex); + init_rwsem(&adev->pm.mclk_lock); + init_rwsem(&adev->exclusive_lock); + mutex_init(&adev->mn_lock); + hash_init(adev->mn_hash); + + amdgpu_check_arguments(adev); + + /* Registers mapping */ + /* TODO: block userspace mapping of io register */ + spin_lock_init(&adev->mmio_idx_lock); + spin_lock_init(&adev->smc_idx_lock); + spin_lock_init(&adev->pcie_idx_lock); + spin_lock_init(&adev->uvd_ctx_idx_lock); + spin_lock_init(&adev->didt_idx_lock); + spin_lock_init(&adev->audio_endpt_idx_lock); + + adev->rmmio_base = pci_resource_start(adev->pdev, 5); + adev->rmmio_size = pci_resource_len(adev->pdev, 5); + adev->rmmio = ioremap(adev->rmmio_base, adev->rmmio_size); + if (adev->rmmio == NULL) { + return -ENOMEM; + } + DRM_INFO("register mmio base: 0x%08X\n", (uint32_t)adev->rmmio_base); + DRM_INFO("register mmio size: %u\n", (unsigned)adev->rmmio_size); + + /* doorbell bar mapping */ + amdgpu_doorbell_init(adev); + + /* io port mapping */ + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { + if (pci_resource_flags(adev->pdev, i) & IORESOURCE_IO) { + adev->rio_mem_size = pci_resource_len(adev->pdev, i); + adev->rio_mem = pci_iomap(adev->pdev, i, adev->rio_mem_size); + break; + } + } + if (adev->rio_mem == NULL) + DRM_ERROR("Unable to find PCI I/O BAR\n"); + + /* early init functions */ + r = amdgpu_early_init(adev); + if (r) + return r; + + /* if we have > 1 VGA cards, then disable the amdgpu VGA resources */ + /* this will fail for cards that aren't VGA class devices, just + * ignore it */ + vga_client_register(adev->pdev, adev, NULL, amdgpu_vga_set_decode); + + if (amdgpu_runtime_pm == 1) + runtime = true; + if (amdgpu_device_is_px(ddev)) + runtime = true; + vga_switcheroo_register_client(adev->pdev, &amdgpu_switcheroo_ops, runtime); + if (runtime) + vga_switcheroo_init_domain_pm_ops(adev->dev, &adev->vga_pm_domain); + + /* Read BIOS */ + if (!amdgpu_get_bios(adev)) + return -EINVAL; + /* Must be an ATOMBIOS */ + if (!adev->is_atom_bios) { + dev_err(adev->dev, "Expecting atombios for GPU\n"); + return -EINVAL; + } + r = amdgpu_atombios_init(adev); + if (r) + return r; + + /* Post card if necessary */ + if (!amdgpu_card_posted(adev)) { + if (!adev->bios) { + dev_err(adev->dev, "Card not posted and no BIOS - ignoring\n"); + return -EINVAL; + } + DRM_INFO("GPU not posted. posting now...\n"); + amdgpu_atom_asic_init(adev->mode_info.atom_context); + } + + /* Initialize clocks */ + r = amdgpu_atombios_get_clock_info(adev); + if (r) + return r; + /* init i2c buses */ + amdgpu_atombios_i2c_init(adev); + + /* Fence driver */ + r = amdgpu_fence_driver_init(adev); + if (r) + return r; + + /* init the mode config */ + drm_mode_config_init(adev->ddev); + + r = amdgpu_init(adev); + if (r) { + amdgpu_fini(adev); + return r; + } + + adev->accel_working = true; + + amdgpu_fbdev_init(adev); + + r = amdgpu_ib_pool_init(adev); + if (r) { + dev_err(adev->dev, "IB initialization failed (%d).\n", r); + return r; + } + + r = amdgpu_ib_ring_tests(adev); + if (r) + DRM_ERROR("ib ring test failed (%d).\n", r); + + r = amdgpu_gem_debugfs_init(adev); + if (r) { + DRM_ERROR("registering gem debugfs failed (%d).\n", r); + } + + r = amdgpu_debugfs_regs_init(adev); + if (r) { + DRM_ERROR("registering register debugfs failed (%d).\n", r); + } + + if ((amdgpu_testing & 1)) { + if (adev->accel_working) + amdgpu_test_moves(adev); + else + DRM_INFO("amdgpu: acceleration disabled, skipping move tests\n"); + } + if ((amdgpu_testing & 2)) { + if (adev->accel_working) + amdgpu_test_syncing(adev); + else + DRM_INFO("amdgpu: acceleration disabled, skipping sync tests\n"); + } + if (amdgpu_benchmarking) { + if (adev->accel_working) + amdgpu_benchmark(adev, amdgpu_benchmarking); + else + DRM_INFO("amdgpu: acceleration disabled, skipping benchmarks\n"); + } + + /* enable clockgating, etc. after ib tests, etc. since some blocks require + * explicit gating rather than handling it automatically. + */ + r = amdgpu_late_init(adev); + if (r) + return r; + + return 0; +} + +static void amdgpu_debugfs_remove_files(struct amdgpu_device *adev); + +/** + * amdgpu_device_fini - tear down the driver + * + * @adev: amdgpu_device pointer + * + * Tear down the driver info (all asics). + * Called at driver shutdown. + */ +void amdgpu_device_fini(struct amdgpu_device *adev) +{ + int r; + + DRM_INFO("amdgpu: finishing device.\n"); + adev->shutdown = true; + /* evict vram memory */ + amdgpu_bo_evict_vram(adev); + amdgpu_ib_pool_fini(adev); + amdgpu_fence_driver_fini(adev); + amdgpu_fbdev_fini(adev); + r = amdgpu_fini(adev); + if (adev->ip_block_enabled) + kfree(adev->ip_block_enabled); + adev->ip_block_enabled = NULL; + adev->accel_working = false; + /* free i2c buses */ + amdgpu_i2c_fini(adev); + amdgpu_atombios_fini(adev); + kfree(adev->bios); + adev->bios = NULL; + vga_switcheroo_unregister_client(adev->pdev); + vga_client_register(adev->pdev, NULL, NULL, NULL); + if (adev->rio_mem) + pci_iounmap(adev->pdev, adev->rio_mem); + adev->rio_mem = NULL; + iounmap(adev->rmmio); + adev->rmmio = NULL; + amdgpu_doorbell_fini(adev); + amdgpu_debugfs_regs_cleanup(adev); + amdgpu_debugfs_remove_files(adev); +} + + +/* + * Suspend & resume. + */ +/** + * amdgpu_suspend_kms - initiate device suspend + * + * @pdev: drm dev pointer + * @state: suspend state + * + * Puts the hw in the suspend state (all asics). + * Returns 0 for success or an error on failure. + * Called at driver suspend. + */ +int amdgpu_suspend_kms(struct drm_device *dev, bool suspend, bool fbcon) +{ + struct amdgpu_device *adev; + struct drm_crtc *crtc; + struct drm_connector *connector; + int i, r; + bool force_completion = false; + + if (dev == NULL || dev->dev_private == NULL) { + return -ENODEV; + } + + adev = dev->dev_private; + + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + + drm_kms_helper_poll_disable(dev); + + /* turn off display hw */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + } + + /* unpin the front buffers */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + struct amdgpu_framebuffer *rfb = to_amdgpu_framebuffer(crtc->primary->fb); + struct amdgpu_bo *robj; + + if (rfb == NULL || rfb->obj == NULL) { + continue; + } + robj = gem_to_amdgpu_bo(rfb->obj); + /* don't unpin kernel fb objects */ + if (!amdgpu_fbdev_robj_is_fb(adev, robj)) { + r = amdgpu_bo_reserve(robj, false); + if (r == 0) { + amdgpu_bo_unpin(robj); + amdgpu_bo_unreserve(robj); + } + } + } + /* evict vram memory */ + amdgpu_bo_evict_vram(adev); + + /* wait for gpu to finish processing current batch */ + for (i = 0; i < AMDGPU_MAX_RINGS; i++) { + struct amdgpu_ring *ring = adev->rings[i]; + if (!ring) + continue; + + r = amdgpu_fence_wait_empty(ring); + if (r) { + /* delay GPU reset to resume */ + force_completion = true; + } + } + if (force_completion) { + amdgpu_fence_driver_force_completion(adev); + } + + r = amdgpu_suspend(adev); + + /* evict remaining vram memory */ + amdgpu_bo_evict_vram(adev); + + pci_save_state(dev->pdev); + if (suspend) { + /* Shut down the device */ + pci_disable_device(dev->pdev); + pci_set_power_state(dev->pdev, PCI_D3hot); + } + + if (fbcon) { + console_lock(); + amdgpu_fbdev_set_suspend(adev, 1); + console_unlock(); + } + return 0; +} + +/** + * amdgpu_resume_kms - initiate device resume + * + * @pdev: drm dev pointer + * + * Bring the hw back to operating state (all asics). + * Returns 0 for success or an error on failure. + * Called at driver resume. + */ +int amdgpu_resume_kms(struct drm_device *dev, bool resume, bool fbcon) +{ + struct drm_connector *connector; + struct amdgpu_device *adev = dev->dev_private; + int r; + + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + + if (fbcon) { + console_lock(); + } + if (resume) { + pci_set_power_state(dev->pdev, PCI_D0); + pci_restore_state(dev->pdev); + if (pci_enable_device(dev->pdev)) { + if (fbcon) + console_unlock(); + return -1; + } + } + + /* post card */ + amdgpu_atom_asic_init(adev->mode_info.atom_context); + + r = amdgpu_resume(adev); + + r = amdgpu_ib_ring_tests(adev); + if (r) + DRM_ERROR("ib ring test failed (%d).\n", r); + + r = amdgpu_late_init(adev); + if (r) + return r; + + /* blat the mode back in */ + if (fbcon) { + drm_helper_resume_force_mode(dev); + /* turn on display hw */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON); + } + } + + drm_kms_helper_poll_enable(dev); + + if (fbcon) { + amdgpu_fbdev_set_suspend(adev, 0); + console_unlock(); + } + + return 0; +} + +/** + * amdgpu_gpu_reset - reset the asic + * + * @adev: amdgpu device pointer + * + * Attempt the reset the GPU if it has hung (all asics). + * Returns 0 for success or an error on failure. + */ +int amdgpu_gpu_reset(struct amdgpu_device *adev) +{ + unsigned ring_sizes[AMDGPU_MAX_RINGS]; + uint32_t *ring_data[AMDGPU_MAX_RINGS]; + + bool saved = false; + + int i, r; + int resched; + + down_write(&adev->exclusive_lock); + + if (!adev->needs_reset) { + up_write(&adev->exclusive_lock); + return 0; + } + + adev->needs_reset = false; + + /* block TTM */ + resched = ttm_bo_lock_delayed_workqueue(&adev->mman.bdev); + + r = amdgpu_suspend(adev); + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + if (!ring) + continue; + + ring_sizes[i] = amdgpu_ring_backup(ring, &ring_data[i]); + if (ring_sizes[i]) { + saved = true; + dev_info(adev->dev, "Saved %d dwords of commands " + "on ring %d.\n", ring_sizes[i], i); + } + } + +retry: + r = amdgpu_asic_reset(adev); + if (!r) { + dev_info(adev->dev, "GPU reset succeeded, trying to resume\n"); + r = amdgpu_resume(adev); + } + + if (!r) { + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + if (!ring) + continue; + + amdgpu_ring_restore(ring, ring_sizes[i], ring_data[i]); + ring_sizes[i] = 0; + ring_data[i] = NULL; + } + + r = amdgpu_ib_ring_tests(adev); + if (r) { + dev_err(adev->dev, "ib ring test failed (%d).\n", r); + if (saved) { + saved = false; + r = amdgpu_suspend(adev); + goto retry; + } + } + } else { + amdgpu_fence_driver_force_completion(adev); + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + if (adev->rings[i]) + kfree(ring_data[i]); + } + } + + drm_helper_resume_force_mode(adev->ddev); + + ttm_bo_unlock_delayed_workqueue(&adev->mman.bdev, resched); + if (r) { + /* bad news, how to tell it to userspace ? */ + dev_info(adev->dev, "GPU reset failed\n"); + } + + up_write(&adev->exclusive_lock); + return r; +} + + +/* + * Debugfs + */ +int amdgpu_debugfs_add_files(struct amdgpu_device *adev, + struct drm_info_list *files, + unsigned nfiles) +{ + unsigned i; + + for (i = 0; i < adev->debugfs_count; i++) { + if (adev->debugfs[i].files == files) { + /* Already registered */ + return 0; + } + } + + i = adev->debugfs_count + 1; + if (i > AMDGPU_DEBUGFS_MAX_COMPONENTS) { + DRM_ERROR("Reached maximum number of debugfs components.\n"); + DRM_ERROR("Report so we increase " + "AMDGPU_DEBUGFS_MAX_COMPONENTS.\n"); + return -EINVAL; + } + adev->debugfs[adev->debugfs_count].files = files; + adev->debugfs[adev->debugfs_count].num_files = nfiles; + adev->debugfs_count = i; +#if defined(CONFIG_DEBUG_FS) + drm_debugfs_create_files(files, nfiles, + adev->ddev->control->debugfs_root, + adev->ddev->control); + drm_debugfs_create_files(files, nfiles, + adev->ddev->primary->debugfs_root, + adev->ddev->primary); +#endif + return 0; +} + +static void amdgpu_debugfs_remove_files(struct amdgpu_device *adev) +{ +#if defined(CONFIG_DEBUG_FS) + unsigned i; + + for (i = 0; i < adev->debugfs_count; i++) { + drm_debugfs_remove_files(adev->debugfs[i].files, + adev->debugfs[i].num_files, + adev->ddev->control); + drm_debugfs_remove_files(adev->debugfs[i].files, + adev->debugfs[i].num_files, + adev->ddev->primary); + } +#endif +} + +#if defined(CONFIG_DEBUG_FS) + +static ssize_t amdgpu_debugfs_regs_read(struct file *f, char __user *buf, + size_t size, loff_t *pos) +{ + struct amdgpu_device *adev = f->f_inode->i_private; + ssize_t result = 0; + int r; + + if (size & 0x3 || *pos & 0x3) + return -EINVAL; + + while (size) { + uint32_t value; + + if (*pos > adev->rmmio_size) + return result; + + value = RREG32(*pos >> 2); + r = put_user(value, (uint32_t *)buf); + if (r) + return r; + + result += 4; + buf += 4; + *pos += 4; + size -= 4; + } + + return result; +} + +static ssize_t amdgpu_debugfs_regs_write(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + struct amdgpu_device *adev = f->f_inode->i_private; + ssize_t result = 0; + int r; + + if (size & 0x3 || *pos & 0x3) + return -EINVAL; + + while (size) { + uint32_t value; + + if (*pos > adev->rmmio_size) + return result; + + r = get_user(value, (uint32_t *)buf); + if (r) + return r; + + WREG32(*pos >> 2, value); + + result += 4; + buf += 4; + *pos += 4; + size -= 4; + } + + return result; +} + +static const struct file_operations amdgpu_debugfs_regs_fops = { + .owner = THIS_MODULE, + .read = amdgpu_debugfs_regs_read, + .write = amdgpu_debugfs_regs_write, + .llseek = default_llseek +}; + +static int amdgpu_debugfs_regs_init(struct amdgpu_device *adev) +{ + struct drm_minor *minor = adev->ddev->primary; + struct dentry *ent, *root = minor->debugfs_root; + + ent = debugfs_create_file("amdgpu_regs", S_IFREG | S_IRUGO, root, + adev, &amdgpu_debugfs_regs_fops); + if (IS_ERR(ent)) + return PTR_ERR(ent); + i_size_write(ent->d_inode, adev->rmmio_size); + adev->debugfs_regs = ent; + + return 0; +} + +static void amdgpu_debugfs_regs_cleanup(struct amdgpu_device *adev) +{ + debugfs_remove(adev->debugfs_regs); + adev->debugfs_regs = NULL; +} + +int amdgpu_debugfs_init(struct drm_minor *minor) +{ + return 0; +} + +void amdgpu_debugfs_cleanup(struct drm_minor *minor) +{ +} +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c new file mode 100644 index 000000000000..f22c0671c3eb --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -0,0 +1,832 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_i2c.h" +#include "atom.h" +#include "amdgpu_connectors.h" +#include <asm/div64.h> + +#include <linux/pm_runtime.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> + + +static void amdgpu_flip_work_func(struct work_struct *__work) +{ + struct amdgpu_flip_work *work = + container_of(__work, struct amdgpu_flip_work, flip_work); + struct amdgpu_device *adev = work->adev; + struct amdgpu_crtc *amdgpuCrtc = adev->mode_info.crtcs[work->crtc_id]; + + struct drm_crtc *crtc = &amdgpuCrtc->base; + struct amdgpu_fence *fence; + unsigned long flags; + int r; + + down_read(&adev->exclusive_lock); + if (work->fence) { + fence = to_amdgpu_fence(work->fence); + if (fence) { + r = amdgpu_fence_wait(fence, false); + if (r == -EDEADLK) { + up_read(&adev->exclusive_lock); + r = amdgpu_gpu_reset(adev); + down_read(&adev->exclusive_lock); + } + } else + r = fence_wait(work->fence, false); + + if (r) + DRM_ERROR("failed to wait on page flip fence (%d)!\n", r); + + /* We continue with the page flip even if we failed to wait on + * the fence, otherwise the DRM core and userspace will be + * confused about which BO the CRTC is scanning out + */ + + fence_put(work->fence); + work->fence = NULL; + } + + /* We borrow the event spin lock for protecting flip_status */ + spin_lock_irqsave(&crtc->dev->event_lock, flags); + + /* set the proper interrupt */ + amdgpu_irq_get(adev, &adev->pageflip_irq, work->crtc_id); + /* do the flip (mmio) */ + adev->mode_info.funcs->page_flip(adev, work->crtc_id, work->base); + /* set the flip status */ + amdgpuCrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; + + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + up_read(&adev->exclusive_lock); +} + +/* + * Handle unpin events outside the interrupt handler proper. + */ +static void amdgpu_unpin_work_func(struct work_struct *__work) +{ + struct amdgpu_flip_work *work = + container_of(__work, struct amdgpu_flip_work, unpin_work); + int r; + + /* unpin of the old buffer */ + r = amdgpu_bo_reserve(work->old_rbo, false); + if (likely(r == 0)) { + r = amdgpu_bo_unpin(work->old_rbo); + if (unlikely(r != 0)) { + DRM_ERROR("failed to unpin buffer after flip\n"); + } + amdgpu_bo_unreserve(work->old_rbo); + } else + DRM_ERROR("failed to reserve buffer after flip\n"); + + drm_gem_object_unreference_unlocked(&work->old_rbo->gem_base); + kfree(work); +} + +int amdgpu_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t page_flip_flags) +{ + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct amdgpu_framebuffer *old_amdgpu_fb; + struct amdgpu_framebuffer *new_amdgpu_fb; + struct drm_gem_object *obj; + struct amdgpu_flip_work *work; + struct amdgpu_bo *new_rbo; + unsigned long flags; + u64 tiling_flags; + u64 base; + int r; + + work = kzalloc(sizeof *work, GFP_KERNEL); + if (work == NULL) + return -ENOMEM; + + INIT_WORK(&work->flip_work, amdgpu_flip_work_func); + INIT_WORK(&work->unpin_work, amdgpu_unpin_work_func); + + work->event = event; + work->adev = adev; + work->crtc_id = amdgpu_crtc->crtc_id; + + /* schedule unpin of the old buffer */ + old_amdgpu_fb = to_amdgpu_framebuffer(crtc->primary->fb); + obj = old_amdgpu_fb->obj; + + /* take a reference to the old object */ + drm_gem_object_reference(obj); + work->old_rbo = gem_to_amdgpu_bo(obj); + + new_amdgpu_fb = to_amdgpu_framebuffer(fb); + obj = new_amdgpu_fb->obj; + new_rbo = gem_to_amdgpu_bo(obj); + + /* pin the new buffer */ + r = amdgpu_bo_reserve(new_rbo, false); + if (unlikely(r != 0)) { + DRM_ERROR("failed to reserve new rbo buffer before flip\n"); + goto cleanup; + } + + r = amdgpu_bo_pin_restricted(new_rbo, AMDGPU_GEM_DOMAIN_VRAM, 0, &base); + if (unlikely(r != 0)) { + amdgpu_bo_unreserve(new_rbo); + r = -EINVAL; + DRM_ERROR("failed to pin new rbo buffer before flip\n"); + goto cleanup; + } + + work->fence = fence_get(reservation_object_get_excl(new_rbo->tbo.resv)); + amdgpu_bo_get_tiling_flags(new_rbo, &tiling_flags); + amdgpu_bo_unreserve(new_rbo); + + work->base = base; + + r = drm_vblank_get(crtc->dev, amdgpu_crtc->crtc_id); + if (r) { + DRM_ERROR("failed to get vblank before flip\n"); + goto pflip_cleanup; + } + + /* we borrow the event spin lock for protecting flip_wrok */ + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_NONE) { + DRM_DEBUG_DRIVER("flip queue: crtc already busy\n"); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + r = -EBUSY; + goto vblank_cleanup; + } + + amdgpu_crtc->pflip_status = AMDGPU_FLIP_PENDING; + amdgpu_crtc->pflip_works = work; + + /* update crtc fb */ + crtc->primary->fb = fb; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + queue_work(amdgpu_crtc->pflip_queue, &work->flip_work); + return 0; + +vblank_cleanup: + drm_vblank_put(crtc->dev, amdgpu_crtc->crtc_id); + +pflip_cleanup: + if (unlikely(amdgpu_bo_reserve(new_rbo, false) != 0)) { + DRM_ERROR("failed to reserve new rbo in error path\n"); + goto cleanup; + } + if (unlikely(amdgpu_bo_unpin(new_rbo) != 0)) { + DRM_ERROR("failed to unpin new rbo in error path\n"); + } + amdgpu_bo_unreserve(new_rbo); + +cleanup: + drm_gem_object_unreference_unlocked(&work->old_rbo->gem_base); + fence_put(work->fence); + kfree(work); + + return r; +} + +int amdgpu_crtc_set_config(struct drm_mode_set *set) +{ + struct drm_device *dev; + struct amdgpu_device *adev; + struct drm_crtc *crtc; + bool active = false; + int ret; + + if (!set || !set->crtc) + return -EINVAL; + + dev = set->crtc->dev; + + ret = pm_runtime_get_sync(dev->dev); + if (ret < 0) + return ret; + + ret = drm_crtc_helper_set_config(set); + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) + if (crtc->enabled) + active = true; + + pm_runtime_mark_last_busy(dev->dev); + + adev = dev->dev_private; + /* if we have active crtcs and we don't have a power ref, + take the current one */ + if (active && !adev->have_disp_power_ref) { + adev->have_disp_power_ref = true; + return ret; + } + /* if we have no active crtcs, then drop the power ref + we got before */ + if (!active && adev->have_disp_power_ref) { + pm_runtime_put_autosuspend(dev->dev); + adev->have_disp_power_ref = false; + } + + /* drop the power reference we got coming in here */ + pm_runtime_put_autosuspend(dev->dev); + return ret; +} + +static const char *encoder_names[38] = { + "NONE", + "INTERNAL_LVDS", + "INTERNAL_TMDS1", + "INTERNAL_TMDS2", + "INTERNAL_DAC1", + "INTERNAL_DAC2", + "INTERNAL_SDVOA", + "INTERNAL_SDVOB", + "SI170B", + "CH7303", + "CH7301", + "INTERNAL_DVO1", + "EXTERNAL_SDVOA", + "EXTERNAL_SDVOB", + "TITFP513", + "INTERNAL_LVTM1", + "VT1623", + "HDMI_SI1930", + "HDMI_INTERNAL", + "INTERNAL_KLDSCP_TMDS1", + "INTERNAL_KLDSCP_DVO1", + "INTERNAL_KLDSCP_DAC1", + "INTERNAL_KLDSCP_DAC2", + "SI178", + "MVPU_FPGA", + "INTERNAL_DDI", + "VT1625", + "HDMI_SI1932", + "DP_AN9801", + "DP_DP501", + "INTERNAL_UNIPHY", + "INTERNAL_KLDSCP_LVTMA", + "INTERNAL_UNIPHY1", + "INTERNAL_UNIPHY2", + "NUTMEG", + "TRAVIS", + "INTERNAL_VCE", + "INTERNAL_UNIPHY3", +}; + +static const char *hpd_names[6] = { + "HPD1", + "HPD2", + "HPD3", + "HPD4", + "HPD5", + "HPD6", +}; + +void amdgpu_print_display_setup(struct drm_device *dev) +{ + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + uint32_t devices; + int i = 0; + + DRM_INFO("AMDGPU Display Connectors\n"); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + amdgpu_connector = to_amdgpu_connector(connector); + DRM_INFO("Connector %d:\n", i); + DRM_INFO(" %s\n", connector->name); + if (amdgpu_connector->hpd.hpd != AMDGPU_HPD_NONE) + DRM_INFO(" %s\n", hpd_names[amdgpu_connector->hpd.hpd]); + if (amdgpu_connector->ddc_bus) { + DRM_INFO(" DDC: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", + amdgpu_connector->ddc_bus->rec.mask_clk_reg, + amdgpu_connector->ddc_bus->rec.mask_data_reg, + amdgpu_connector->ddc_bus->rec.a_clk_reg, + amdgpu_connector->ddc_bus->rec.a_data_reg, + amdgpu_connector->ddc_bus->rec.en_clk_reg, + amdgpu_connector->ddc_bus->rec.en_data_reg, + amdgpu_connector->ddc_bus->rec.y_clk_reg, + amdgpu_connector->ddc_bus->rec.y_data_reg); + if (amdgpu_connector->router.ddc_valid) + DRM_INFO(" DDC Router 0x%x/0x%x\n", + amdgpu_connector->router.ddc_mux_control_pin, + amdgpu_connector->router.ddc_mux_state); + if (amdgpu_connector->router.cd_valid) + DRM_INFO(" Clock/Data Router 0x%x/0x%x\n", + amdgpu_connector->router.cd_mux_control_pin, + amdgpu_connector->router.cd_mux_state); + } else { + if (connector->connector_type == DRM_MODE_CONNECTOR_VGA || + connector->connector_type == DRM_MODE_CONNECTOR_DVII || + connector->connector_type == DRM_MODE_CONNECTOR_DVID || + connector->connector_type == DRM_MODE_CONNECTOR_DVIA || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) + DRM_INFO(" DDC: no ddc bus - possible BIOS bug - please report to xorg-driver-ati@lists.x.org\n"); + } + DRM_INFO(" Encoders:\n"); + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + amdgpu_encoder = to_amdgpu_encoder(encoder); + devices = amdgpu_encoder->devices & amdgpu_connector->devices; + if (devices) { + if (devices & ATOM_DEVICE_CRT1_SUPPORT) + DRM_INFO(" CRT1: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_CRT2_SUPPORT) + DRM_INFO(" CRT2: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_LCD1_SUPPORT) + DRM_INFO(" LCD1: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_DFP1_SUPPORT) + DRM_INFO(" DFP1: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_DFP2_SUPPORT) + DRM_INFO(" DFP2: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_DFP3_SUPPORT) + DRM_INFO(" DFP3: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_DFP4_SUPPORT) + DRM_INFO(" DFP4: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_DFP5_SUPPORT) + DRM_INFO(" DFP5: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_DFP6_SUPPORT) + DRM_INFO(" DFP6: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_TV1_SUPPORT) + DRM_INFO(" TV1: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + if (devices & ATOM_DEVICE_CV_SUPPORT) + DRM_INFO(" CV: %s\n", encoder_names[amdgpu_encoder->encoder_id]); + } + } + i++; + } +} + +/** + * amdgpu_ddc_probe + * + */ +bool amdgpu_ddc_probe(struct amdgpu_connector *amdgpu_connector, + bool use_aux) +{ + u8 out = 0x0; + u8 buf[8]; + int ret; + struct i2c_msg msgs[] = { + { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &out, + }, + { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = 8, + .buf = buf, + } + }; + + /* on hw with routers, select right port */ + if (amdgpu_connector->router.ddc_valid) + amdgpu_i2c_router_select_ddc_port(amdgpu_connector); + + if (use_aux) { + ret = i2c_transfer(&amdgpu_connector->ddc_bus->aux.ddc, msgs, 2); + } else { + ret = i2c_transfer(&amdgpu_connector->ddc_bus->adapter, msgs, 2); + } + + if (ret != 2) + /* Couldn't find an accessible DDC on this connector */ + return false; + /* Probe also for valid EDID header + * EDID header starts with: + * 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00. + * Only the first 6 bytes must be valid as + * drm_edid_block_valid() can fix the last 2 bytes */ + if (drm_edid_header_is_valid(buf) < 6) { + /* Couldn't find an accessible EDID on this + * connector */ + return false; + } + return true; +} + +static void amdgpu_user_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct amdgpu_framebuffer *amdgpu_fb = to_amdgpu_framebuffer(fb); + + if (amdgpu_fb->obj) { + drm_gem_object_unreference_unlocked(amdgpu_fb->obj); + } + drm_framebuffer_cleanup(fb); + kfree(amdgpu_fb); +} + +static int amdgpu_user_framebuffer_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct amdgpu_framebuffer *amdgpu_fb = to_amdgpu_framebuffer(fb); + + return drm_gem_handle_create(file_priv, amdgpu_fb->obj, handle); +} + +static const struct drm_framebuffer_funcs amdgpu_fb_funcs = { + .destroy = amdgpu_user_framebuffer_destroy, + .create_handle = amdgpu_user_framebuffer_create_handle, +}; + +int +amdgpu_framebuffer_init(struct drm_device *dev, + struct amdgpu_framebuffer *rfb, + struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object *obj) +{ + int ret; + rfb->obj = obj; + drm_helper_mode_fill_fb_struct(&rfb->base, mode_cmd); + ret = drm_framebuffer_init(dev, &rfb->base, &amdgpu_fb_funcs); + if (ret) { + rfb->obj = NULL; + return ret; + } + return 0; +} + +static struct drm_framebuffer * +amdgpu_user_framebuffer_create(struct drm_device *dev, + struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *obj; + struct amdgpu_framebuffer *amdgpu_fb; + int ret; + + obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); + if (obj == NULL) { + dev_err(&dev->pdev->dev, "No GEM object associated to handle 0x%08X, " + "can't create framebuffer\n", mode_cmd->handles[0]); + return ERR_PTR(-ENOENT); + } + + amdgpu_fb = kzalloc(sizeof(*amdgpu_fb), GFP_KERNEL); + if (amdgpu_fb == NULL) { + drm_gem_object_unreference_unlocked(obj); + return ERR_PTR(-ENOMEM); + } + + ret = amdgpu_framebuffer_init(dev, amdgpu_fb, mode_cmd, obj); + if (ret) { + kfree(amdgpu_fb); + drm_gem_object_unreference_unlocked(obj); + return ERR_PTR(ret); + } + + return &amdgpu_fb->base; +} + +static void amdgpu_output_poll_changed(struct drm_device *dev) +{ + struct amdgpu_device *adev = dev->dev_private; + amdgpu_fb_output_poll_changed(adev); +} + +const struct drm_mode_config_funcs amdgpu_mode_funcs = { + .fb_create = amdgpu_user_framebuffer_create, + .output_poll_changed = amdgpu_output_poll_changed +}; + +static struct drm_prop_enum_list amdgpu_underscan_enum_list[] = +{ { UNDERSCAN_OFF, "off" }, + { UNDERSCAN_ON, "on" }, + { UNDERSCAN_AUTO, "auto" }, +}; + +static struct drm_prop_enum_list amdgpu_audio_enum_list[] = +{ { AMDGPU_AUDIO_DISABLE, "off" }, + { AMDGPU_AUDIO_ENABLE, "on" }, + { AMDGPU_AUDIO_AUTO, "auto" }, +}; + +/* XXX support different dither options? spatial, temporal, both, etc. */ +static struct drm_prop_enum_list amdgpu_dither_enum_list[] = +{ { AMDGPU_FMT_DITHER_DISABLE, "off" }, + { AMDGPU_FMT_DITHER_ENABLE, "on" }, +}; + +int amdgpu_modeset_create_props(struct amdgpu_device *adev) +{ + int sz; + + if (adev->is_atom_bios) { + adev->mode_info.coherent_mode_property = + drm_property_create_range(adev->ddev, 0 , "coherent", 0, 1); + if (!adev->mode_info.coherent_mode_property) + return -ENOMEM; + } + + adev->mode_info.load_detect_property = + drm_property_create_range(adev->ddev, 0, "load detection", 0, 1); + if (!adev->mode_info.load_detect_property) + return -ENOMEM; + + drm_mode_create_scaling_mode_property(adev->ddev); + + sz = ARRAY_SIZE(amdgpu_underscan_enum_list); + adev->mode_info.underscan_property = + drm_property_create_enum(adev->ddev, 0, + "underscan", + amdgpu_underscan_enum_list, sz); + + adev->mode_info.underscan_hborder_property = + drm_property_create_range(adev->ddev, 0, + "underscan hborder", 0, 128); + if (!adev->mode_info.underscan_hborder_property) + return -ENOMEM; + + adev->mode_info.underscan_vborder_property = + drm_property_create_range(adev->ddev, 0, + "underscan vborder", 0, 128); + if (!adev->mode_info.underscan_vborder_property) + return -ENOMEM; + + sz = ARRAY_SIZE(amdgpu_audio_enum_list); + adev->mode_info.audio_property = + drm_property_create_enum(adev->ddev, 0, + "audio", + amdgpu_audio_enum_list, sz); + + sz = ARRAY_SIZE(amdgpu_dither_enum_list); + adev->mode_info.dither_property = + drm_property_create_enum(adev->ddev, 0, + "dither", + amdgpu_dither_enum_list, sz); + + return 0; +} + +void amdgpu_update_display_priority(struct amdgpu_device *adev) +{ + /* adjustment options for the display watermarks */ + if ((amdgpu_disp_priority == 0) || (amdgpu_disp_priority > 2)) + adev->mode_info.disp_priority = 0; + else + adev->mode_info.disp_priority = amdgpu_disp_priority; + +} + +static bool is_hdtv_mode(const struct drm_display_mode *mode) +{ + /* try and guess if this is a tv or a monitor */ + if ((mode->vdisplay == 480 && mode->hdisplay == 720) || /* 480p */ + (mode->vdisplay == 576) || /* 576p */ + (mode->vdisplay == 720) || /* 720p */ + (mode->vdisplay == 1080)) /* 1080p */ + return true; + else + return false; +} + +bool amdgpu_crtc_scaling_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = crtc->dev; + struct drm_encoder *encoder; + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct amdgpu_encoder *amdgpu_encoder; + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + u32 src_v = 1, dst_v = 1; + u32 src_h = 1, dst_h = 1; + + amdgpu_crtc->h_border = 0; + amdgpu_crtc->v_border = 0; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + if (encoder->crtc != crtc) + continue; + amdgpu_encoder = to_amdgpu_encoder(encoder); + connector = amdgpu_get_connector_for_encoder(encoder); + amdgpu_connector = to_amdgpu_connector(connector); + + /* set scaling */ + if (amdgpu_encoder->rmx_type == RMX_OFF) + amdgpu_crtc->rmx_type = RMX_OFF; + else if (mode->hdisplay < amdgpu_encoder->native_mode.hdisplay || + mode->vdisplay < amdgpu_encoder->native_mode.vdisplay) + amdgpu_crtc->rmx_type = amdgpu_encoder->rmx_type; + else + amdgpu_crtc->rmx_type = RMX_OFF; + /* copy native mode */ + memcpy(&amdgpu_crtc->native_mode, + &amdgpu_encoder->native_mode, + sizeof(struct drm_display_mode)); + src_v = crtc->mode.vdisplay; + dst_v = amdgpu_crtc->native_mode.vdisplay; + src_h = crtc->mode.hdisplay; + dst_h = amdgpu_crtc->native_mode.hdisplay; + + /* fix up for overscan on hdmi */ + if ((!(mode->flags & DRM_MODE_FLAG_INTERLACE)) && + ((amdgpu_encoder->underscan_type == UNDERSCAN_ON) || + ((amdgpu_encoder->underscan_type == UNDERSCAN_AUTO) && + drm_detect_hdmi_monitor(amdgpu_connector_edid(connector)) && + is_hdtv_mode(mode)))) { + if (amdgpu_encoder->underscan_hborder != 0) + amdgpu_crtc->h_border = amdgpu_encoder->underscan_hborder; + else + amdgpu_crtc->h_border = (mode->hdisplay >> 5) + 16; + if (amdgpu_encoder->underscan_vborder != 0) + amdgpu_crtc->v_border = amdgpu_encoder->underscan_vborder; + else + amdgpu_crtc->v_border = (mode->vdisplay >> 5) + 16; + amdgpu_crtc->rmx_type = RMX_FULL; + src_v = crtc->mode.vdisplay; + dst_v = crtc->mode.vdisplay - (amdgpu_crtc->v_border * 2); + src_h = crtc->mode.hdisplay; + dst_h = crtc->mode.hdisplay - (amdgpu_crtc->h_border * 2); + } + } + if (amdgpu_crtc->rmx_type != RMX_OFF) { + fixed20_12 a, b; + a.full = dfixed_const(src_v); + b.full = dfixed_const(dst_v); + amdgpu_crtc->vsc.full = dfixed_div(a, b); + a.full = dfixed_const(src_h); + b.full = dfixed_const(dst_h); + amdgpu_crtc->hsc.full = dfixed_div(a, b); + } else { + amdgpu_crtc->vsc.full = dfixed_const(1); + amdgpu_crtc->hsc.full = dfixed_const(1); + } + return true; +} + +/* + * Retrieve current video scanout position of crtc on a given gpu, and + * an optional accurate timestamp of when query happened. + * + * \param dev Device to query. + * \param crtc Crtc to query. + * \param flags Flags from caller (DRM_CALLED_FROM_VBLIRQ or 0). + * \param *vpos Location where vertical scanout position should be stored. + * \param *hpos Location where horizontal scanout position should go. + * \param *stime Target location for timestamp taken immediately before + * scanout position query. Can be NULL to skip timestamp. + * \param *etime Target location for timestamp taken immediately after + * scanout position query. Can be NULL to skip timestamp. + * + * Returns vpos as a positive number while in active scanout area. + * Returns vpos as a negative number inside vblank, counting the number + * of scanlines to go until end of vblank, e.g., -1 means "one scanline + * until start of active scanout / end of vblank." + * + * \return Flags, or'ed together as follows: + * + * DRM_SCANOUTPOS_VALID = Query successful. + * DRM_SCANOUTPOS_INVBL = Inside vblank. + * DRM_SCANOUTPOS_ACCURATE = Returned position is accurate. A lack of + * this flag means that returned position may be offset by a constant but + * unknown small number of scanlines wrt. real scanout position. + * + */ +int amdgpu_get_crtc_scanoutpos(struct drm_device *dev, int crtc, unsigned int flags, + int *vpos, int *hpos, ktime_t *stime, ktime_t *etime) +{ + u32 vbl = 0, position = 0; + int vbl_start, vbl_end, vtotal, ret = 0; + bool in_vbl = true; + + struct amdgpu_device *adev = dev->dev_private; + + /* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */ + + /* Get optional system timestamp before query. */ + if (stime) + *stime = ktime_get(); + + if (amdgpu_display_page_flip_get_scanoutpos(adev, crtc, &vbl, &position) == 0) + ret |= DRM_SCANOUTPOS_VALID; + + /* Get optional system timestamp after query. */ + if (etime) + *etime = ktime_get(); + + /* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */ + + /* Decode into vertical and horizontal scanout position. */ + *vpos = position & 0x1fff; + *hpos = (position >> 16) & 0x1fff; + + /* Valid vblank area boundaries from gpu retrieved? */ + if (vbl > 0) { + /* Yes: Decode. */ + ret |= DRM_SCANOUTPOS_ACCURATE; + vbl_start = vbl & 0x1fff; + vbl_end = (vbl >> 16) & 0x1fff; + } + else { + /* No: Fake something reasonable which gives at least ok results. */ + vbl_start = adev->mode_info.crtcs[crtc]->base.hwmode.crtc_vdisplay; + vbl_end = 0; + } + + /* Test scanout position against vblank region. */ + if ((*vpos < vbl_start) && (*vpos >= vbl_end)) + in_vbl = false; + + /* Check if inside vblank area and apply corrective offsets: + * vpos will then be >=0 in video scanout area, but negative + * within vblank area, counting down the number of lines until + * start of scanout. + */ + + /* Inside "upper part" of vblank area? Apply corrective offset if so: */ + if (in_vbl && (*vpos >= vbl_start)) { + vtotal = adev->mode_info.crtcs[crtc]->base.hwmode.crtc_vtotal; + *vpos = *vpos - vtotal; + } + + /* Correct for shifted end of vbl at vbl_end. */ + *vpos = *vpos - vbl_end; + + /* In vblank? */ + if (in_vbl) + ret |= DRM_SCANOUTPOS_IN_VBLANK; + + /* Is vpos outside nominal vblank area, but less than + * 1/100 of a frame height away from start of vblank? + * If so, assume this isn't a massively delayed vblank + * interrupt, but a vblank interrupt that fired a few + * microseconds before true start of vblank. Compensate + * by adding a full frame duration to the final timestamp. + * Happens, e.g., on ATI R500, R600. + * + * We only do this if DRM_CALLED_FROM_VBLIRQ. + */ + if ((flags & DRM_CALLED_FROM_VBLIRQ) && !in_vbl) { + vbl_start = adev->mode_info.crtcs[crtc]->base.hwmode.crtc_vdisplay; + vtotal = adev->mode_info.crtcs[crtc]->base.hwmode.crtc_vtotal; + + if (vbl_start - *vpos < vtotal / 100) { + *vpos -= vtotal; + + /* Signal this correction as "applied". */ + ret |= 0x8; + } + } + + return ret; +} + +int amdgpu_crtc_idx_to_irq_type(struct amdgpu_device *adev, int crtc) +{ + if (crtc < 0 || crtc >= adev->mode_info.num_crtc) + return AMDGPU_CRTC_IRQ_NONE; + + switch (crtc) { + case 0: + return AMDGPU_CRTC_IRQ_VBLANK1; + case 1: + return AMDGPU_CRTC_IRQ_VBLANK2; + case 2: + return AMDGPU_CRTC_IRQ_VBLANK3; + case 3: + return AMDGPU_CRTC_IRQ_VBLANK4; + case 4: + return AMDGPU_CRTC_IRQ_VBLANK5; + case 5: + return AMDGPU_CRTC_IRQ_VBLANK6; + default: + return AMDGPU_CRTC_IRQ_NONE; + } +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.c new file mode 100644 index 000000000000..7b7f4aba60c0 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.c @@ -0,0 +1,955 @@ +/* + * Copyright 2011 Advanced Micro Devices, 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: Alex Deucher + */ + +#include "drmP.h" +#include "amdgpu.h" +#include "amdgpu_atombios.h" +#include "amdgpu_i2c.h" +#include "amdgpu_dpm.h" +#include "atom.h" + +void amdgpu_dpm_print_class_info(u32 class, u32 class2) +{ + printk("\tui class: "); + switch (class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) { + case ATOM_PPLIB_CLASSIFICATION_UI_NONE: + default: + printk("none\n"); + break; + case ATOM_PPLIB_CLASSIFICATION_UI_BATTERY: + printk("battery\n"); + break; + case ATOM_PPLIB_CLASSIFICATION_UI_BALANCED: + printk("balanced\n"); + break; + case ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE: + printk("performance\n"); + break; + } + printk("\tinternal class: "); + if (((class & ~ATOM_PPLIB_CLASSIFICATION_UI_MASK) == 0) && + (class2 == 0)) + printk("none"); + else { + if (class & ATOM_PPLIB_CLASSIFICATION_BOOT) + printk("boot "); + if (class & ATOM_PPLIB_CLASSIFICATION_THERMAL) + printk("thermal "); + if (class & ATOM_PPLIB_CLASSIFICATION_LIMITEDPOWERSOURCE) + printk("limited_pwr "); + if (class & ATOM_PPLIB_CLASSIFICATION_REST) + printk("rest "); + if (class & ATOM_PPLIB_CLASSIFICATION_FORCED) + printk("forced "); + if (class & ATOM_PPLIB_CLASSIFICATION_3DPERFORMANCE) + printk("3d_perf "); + if (class & ATOM_PPLIB_CLASSIFICATION_OVERDRIVETEMPLATE) + printk("ovrdrv "); + if (class & ATOM_PPLIB_CLASSIFICATION_UVDSTATE) + printk("uvd "); + if (class & ATOM_PPLIB_CLASSIFICATION_3DLOW) + printk("3d_low "); + if (class & ATOM_PPLIB_CLASSIFICATION_ACPI) + printk("acpi "); + if (class & ATOM_PPLIB_CLASSIFICATION_HD2STATE) + printk("uvd_hd2 "); + if (class & ATOM_PPLIB_CLASSIFICATION_HDSTATE) + printk("uvd_hd "); + if (class & ATOM_PPLIB_CLASSIFICATION_SDSTATE) + printk("uvd_sd "); + if (class2 & ATOM_PPLIB_CLASSIFICATION2_LIMITEDPOWERSOURCE_2) + printk("limited_pwr2 "); + if (class2 & ATOM_PPLIB_CLASSIFICATION2_ULV) + printk("ulv "); + if (class2 & ATOM_PPLIB_CLASSIFICATION2_MVC) + printk("uvd_mvc "); + } + printk("\n"); +} + +void amdgpu_dpm_print_cap_info(u32 caps) +{ + printk("\tcaps: "); + if (caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) + printk("single_disp "); + if (caps & ATOM_PPLIB_SUPPORTS_VIDEO_PLAYBACK) + printk("video "); + if (caps & ATOM_PPLIB_DISALLOW_ON_DC) + printk("no_dc "); + printk("\n"); +} + +void amdgpu_dpm_print_ps_status(struct amdgpu_device *adev, + struct amdgpu_ps *rps) +{ + printk("\tstatus: "); + if (rps == adev->pm.dpm.current_ps) + printk("c "); + if (rps == adev->pm.dpm.requested_ps) + printk("r "); + if (rps == adev->pm.dpm.boot_ps) + printk("b "); + printk("\n"); +} + +u32 amdgpu_dpm_get_vblank_time(struct amdgpu_device *adev) +{ + struct drm_device *dev = adev->ddev; + struct drm_crtc *crtc; + struct amdgpu_crtc *amdgpu_crtc; + u32 line_time_us, vblank_lines; + u32 vblank_time_us = 0xffffffff; /* if the displays are off, vblank time is max */ + + if (adev->mode_info.num_crtc && adev->mode_info.mode_config_initialized) { + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + amdgpu_crtc = to_amdgpu_crtc(crtc); + if (crtc->enabled && amdgpu_crtc->enabled && amdgpu_crtc->hw_mode.clock) { + line_time_us = (amdgpu_crtc->hw_mode.crtc_htotal * 1000) / + amdgpu_crtc->hw_mode.clock; + vblank_lines = amdgpu_crtc->hw_mode.crtc_vblank_end - + amdgpu_crtc->hw_mode.crtc_vdisplay + + (amdgpu_crtc->v_border * 2); + vblank_time_us = vblank_lines * line_time_us; + break; + } + } + } + + return vblank_time_us; +} + +u32 amdgpu_dpm_get_vrefresh(struct amdgpu_device *adev) +{ + struct drm_device *dev = adev->ddev; + struct drm_crtc *crtc; + struct amdgpu_crtc *amdgpu_crtc; + u32 vrefresh = 0; + + if (adev->mode_info.num_crtc && adev->mode_info.mode_config_initialized) { + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + amdgpu_crtc = to_amdgpu_crtc(crtc); + if (crtc->enabled && amdgpu_crtc->enabled && amdgpu_crtc->hw_mode.clock) { + vrefresh = amdgpu_crtc->hw_mode.vrefresh; + break; + } + } + } + + return vrefresh; +} + +void amdgpu_calculate_u_and_p(u32 i, u32 r_c, u32 p_b, + u32 *p, u32 *u) +{ + u32 b_c = 0; + u32 i_c; + u32 tmp; + + i_c = (i * r_c) / 100; + tmp = i_c >> p_b; + + while (tmp) { + b_c++; + tmp >>= 1; + } + + *u = (b_c + 1) / 2; + *p = i_c / (1 << (2 * (*u))); +} + +int amdgpu_calculate_at(u32 t, u32 h, u32 fh, u32 fl, u32 *tl, u32 *th) +{ + u32 k, a, ah, al; + u32 t1; + + if ((fl == 0) || (fh == 0) || (fl > fh)) + return -EINVAL; + + k = (100 * fh) / fl; + t1 = (t * (k - 100)); + a = (1000 * (100 * h + t1)) / (10000 + (t1 / 100)); + a = (a + 5) / 10; + ah = ((a * t) + 5000) / 10000; + al = a - ah; + + *th = t - ah; + *tl = t + al; + + return 0; +} + +bool amdgpu_is_uvd_state(u32 class, u32 class2) +{ + if (class & ATOM_PPLIB_CLASSIFICATION_UVDSTATE) + return true; + if (class & ATOM_PPLIB_CLASSIFICATION_HD2STATE) + return true; + if (class & ATOM_PPLIB_CLASSIFICATION_HDSTATE) + return true; + if (class & ATOM_PPLIB_CLASSIFICATION_SDSTATE) + return true; + if (class2 & ATOM_PPLIB_CLASSIFICATION2_MVC) + return true; + return false; +} + +bool amdgpu_is_internal_thermal_sensor(enum amdgpu_int_thermal_type sensor) +{ + switch (sensor) { + case THERMAL_TYPE_RV6XX: + case THERMAL_TYPE_RV770: + case THERMAL_TYPE_EVERGREEN: + case THERMAL_TYPE_SUMO: + case THERMAL_TYPE_NI: + case THERMAL_TYPE_SI: + case THERMAL_TYPE_CI: + case THERMAL_TYPE_KV: + return true; + case THERMAL_TYPE_ADT7473_WITH_INTERNAL: + case THERMAL_TYPE_EMC2103_WITH_INTERNAL: + return false; /* need special handling */ + case THERMAL_TYPE_NONE: + case THERMAL_TYPE_EXTERNAL: + case THERMAL_TYPE_EXTERNAL_GPIO: + default: + return false; + } +} + +union power_info { + struct _ATOM_POWERPLAY_INFO info; + struct _ATOM_POWERPLAY_INFO_V2 info_2; + struct _ATOM_POWERPLAY_INFO_V3 info_3; + struct _ATOM_PPLIB_POWERPLAYTABLE pplib; + struct _ATOM_PPLIB_POWERPLAYTABLE2 pplib2; + struct _ATOM_PPLIB_POWERPLAYTABLE3 pplib3; + struct _ATOM_PPLIB_POWERPLAYTABLE4 pplib4; + struct _ATOM_PPLIB_POWERPLAYTABLE5 pplib5; +}; + +union fan_info { + struct _ATOM_PPLIB_FANTABLE fan; + struct _ATOM_PPLIB_FANTABLE2 fan2; + struct _ATOM_PPLIB_FANTABLE3 fan3; +}; + +static int amdgpu_parse_clk_voltage_dep_table(struct amdgpu_clock_voltage_dependency_table *amdgpu_table, + ATOM_PPLIB_Clock_Voltage_Dependency_Table *atom_table) +{ + u32 size = atom_table->ucNumEntries * + sizeof(struct amdgpu_clock_voltage_dependency_entry); + int i; + ATOM_PPLIB_Clock_Voltage_Dependency_Record *entry; + + amdgpu_table->entries = kzalloc(size, GFP_KERNEL); + if (!amdgpu_table->entries) + return -ENOMEM; + + entry = &atom_table->entries[0]; + for (i = 0; i < atom_table->ucNumEntries; i++) { + amdgpu_table->entries[i].clk = le16_to_cpu(entry->usClockLow) | + (entry->ucClockHigh << 16); + amdgpu_table->entries[i].v = le16_to_cpu(entry->usVoltage); + entry = (ATOM_PPLIB_Clock_Voltage_Dependency_Record *) + ((u8 *)entry + sizeof(ATOM_PPLIB_Clock_Voltage_Dependency_Record)); + } + amdgpu_table->count = atom_table->ucNumEntries; + + return 0; +} + +int amdgpu_get_platform_caps(struct amdgpu_device *adev) +{ + struct amdgpu_mode_info *mode_info = &adev->mode_info; + union power_info *power_info; + int index = GetIndexIntoMasterTable(DATA, PowerPlayInfo); + u16 data_offset; + u8 frev, crev; + + if (!amdgpu_atom_parse_data_header(mode_info->atom_context, index, NULL, + &frev, &crev, &data_offset)) + return -EINVAL; + power_info = (union power_info *)(mode_info->atom_context->bios + data_offset); + + adev->pm.dpm.platform_caps = le32_to_cpu(power_info->pplib.ulPlatformCaps); + adev->pm.dpm.backbias_response_time = le16_to_cpu(power_info->pplib.usBackbiasTime); + adev->pm.dpm.voltage_response_time = le16_to_cpu(power_info->pplib.usVoltageTime); + + return 0; +} + +/* sizeof(ATOM_PPLIB_EXTENDEDHEADER) */ +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V2 12 +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V3 14 +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V4 16 +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V5 18 +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V6 20 +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V7 22 +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V8 24 +#define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V9 26 + +int amdgpu_parse_extended_power_table(struct amdgpu_device *adev) +{ + struct amdgpu_mode_info *mode_info = &adev->mode_info; + union power_info *power_info; + union fan_info *fan_info; + ATOM_PPLIB_Clock_Voltage_Dependency_Table *dep_table; + int index = GetIndexIntoMasterTable(DATA, PowerPlayInfo); + u16 data_offset; + u8 frev, crev; + int ret, i; + + if (!amdgpu_atom_parse_data_header(mode_info->atom_context, index, NULL, + &frev, &crev, &data_offset)) + return -EINVAL; + power_info = (union power_info *)(mode_info->atom_context->bios + data_offset); + + /* fan table */ + if (le16_to_cpu(power_info->pplib.usTableSize) >= + sizeof(struct _ATOM_PPLIB_POWERPLAYTABLE3)) { + if (power_info->pplib3.usFanTableOffset) { + fan_info = (union fan_info *)(mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib3.usFanTableOffset)); + adev->pm.dpm.fan.t_hyst = fan_info->fan.ucTHyst; + adev->pm.dpm.fan.t_min = le16_to_cpu(fan_info->fan.usTMin); + adev->pm.dpm.fan.t_med = le16_to_cpu(fan_info->fan.usTMed); + adev->pm.dpm.fan.t_high = le16_to_cpu(fan_info->fan.usTHigh); + adev->pm.dpm.fan.pwm_min = le16_to_cpu(fan_info->fan.usPWMMin); + adev->pm.dpm.fan.pwm_med = le16_to_cpu(fan_info->fan.usPWMMed); + adev->pm.dpm.fan.pwm_high = le16_to_cpu(fan_info->fan.usPWMHigh); + if (fan_info->fan.ucFanTableFormat >= 2) + adev->pm.dpm.fan.t_max = le16_to_cpu(fan_info->fan2.usTMax); + else + adev->pm.dpm.fan.t_max = 10900; + adev->pm.dpm.fan.cycle_delay = 100000; + if (fan_info->fan.ucFanTableFormat >= 3) { + adev->pm.dpm.fan.control_mode = fan_info->fan3.ucFanControlMode; + adev->pm.dpm.fan.default_max_fan_pwm = + le16_to_cpu(fan_info->fan3.usFanPWMMax); + adev->pm.dpm.fan.default_fan_output_sensitivity = 4836; + adev->pm.dpm.fan.fan_output_sensitivity = + le16_to_cpu(fan_info->fan3.usFanOutputSensitivity); + } + adev->pm.dpm.fan.ucode_fan_control = true; + } + } + + /* clock dependancy tables, shedding tables */ + if (le16_to_cpu(power_info->pplib.usTableSize) >= + sizeof(struct _ATOM_PPLIB_POWERPLAYTABLE4)) { + if (power_info->pplib4.usVddcDependencyOnSCLKOffset) { + dep_table = (ATOM_PPLIB_Clock_Voltage_Dependency_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib4.usVddcDependencyOnSCLKOffset)); + ret = amdgpu_parse_clk_voltage_dep_table(&adev->pm.dpm.dyn_state.vddc_dependency_on_sclk, + dep_table); + if (ret) { + amdgpu_free_extended_power_table(adev); + return ret; + } + } + if (power_info->pplib4.usVddciDependencyOnMCLKOffset) { + dep_table = (ATOM_PPLIB_Clock_Voltage_Dependency_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib4.usVddciDependencyOnMCLKOffset)); + ret = amdgpu_parse_clk_voltage_dep_table(&adev->pm.dpm.dyn_state.vddci_dependency_on_mclk, + dep_table); + if (ret) { + amdgpu_free_extended_power_table(adev); + return ret; + } + } + if (power_info->pplib4.usVddcDependencyOnMCLKOffset) { + dep_table = (ATOM_PPLIB_Clock_Voltage_Dependency_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib4.usVddcDependencyOnMCLKOffset)); + ret = amdgpu_parse_clk_voltage_dep_table(&adev->pm.dpm.dyn_state.vddc_dependency_on_mclk, + dep_table); + if (ret) { + amdgpu_free_extended_power_table(adev); + return ret; + } + } + if (power_info->pplib4.usMvddDependencyOnMCLKOffset) { + dep_table = (ATOM_PPLIB_Clock_Voltage_Dependency_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib4.usMvddDependencyOnMCLKOffset)); + ret = amdgpu_parse_clk_voltage_dep_table(&adev->pm.dpm.dyn_state.mvdd_dependency_on_mclk, + dep_table); + if (ret) { + amdgpu_free_extended_power_table(adev); + return ret; + } + } + if (power_info->pplib4.usMaxClockVoltageOnDCOffset) { + ATOM_PPLIB_Clock_Voltage_Limit_Table *clk_v = + (ATOM_PPLIB_Clock_Voltage_Limit_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib4.usMaxClockVoltageOnDCOffset)); + if (clk_v->ucNumEntries) { + adev->pm.dpm.dyn_state.max_clock_voltage_on_dc.sclk = + le16_to_cpu(clk_v->entries[0].usSclkLow) | + (clk_v->entries[0].ucSclkHigh << 16); + adev->pm.dpm.dyn_state.max_clock_voltage_on_dc.mclk = + le16_to_cpu(clk_v->entries[0].usMclkLow) | + (clk_v->entries[0].ucMclkHigh << 16); + adev->pm.dpm.dyn_state.max_clock_voltage_on_dc.vddc = + le16_to_cpu(clk_v->entries[0].usVddc); + adev->pm.dpm.dyn_state.max_clock_voltage_on_dc.vddci = + le16_to_cpu(clk_v->entries[0].usVddci); + } + } + if (power_info->pplib4.usVddcPhaseShedLimitsTableOffset) { + ATOM_PPLIB_PhaseSheddingLimits_Table *psl = + (ATOM_PPLIB_PhaseSheddingLimits_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib4.usVddcPhaseShedLimitsTableOffset)); + ATOM_PPLIB_PhaseSheddingLimits_Record *entry; + + adev->pm.dpm.dyn_state.phase_shedding_limits_table.entries = + kzalloc(psl->ucNumEntries * + sizeof(struct amdgpu_phase_shedding_limits_entry), + GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.phase_shedding_limits_table.entries) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + + entry = &psl->entries[0]; + for (i = 0; i < psl->ucNumEntries; i++) { + adev->pm.dpm.dyn_state.phase_shedding_limits_table.entries[i].sclk = + le16_to_cpu(entry->usSclkLow) | (entry->ucSclkHigh << 16); + adev->pm.dpm.dyn_state.phase_shedding_limits_table.entries[i].mclk = + le16_to_cpu(entry->usMclkLow) | (entry->ucMclkHigh << 16); + adev->pm.dpm.dyn_state.phase_shedding_limits_table.entries[i].voltage = + le16_to_cpu(entry->usVoltage); + entry = (ATOM_PPLIB_PhaseSheddingLimits_Record *) + ((u8 *)entry + sizeof(ATOM_PPLIB_PhaseSheddingLimits_Record)); + } + adev->pm.dpm.dyn_state.phase_shedding_limits_table.count = + psl->ucNumEntries; + } + } + + /* cac data */ + if (le16_to_cpu(power_info->pplib.usTableSize) >= + sizeof(struct _ATOM_PPLIB_POWERPLAYTABLE5)) { + adev->pm.dpm.tdp_limit = le32_to_cpu(power_info->pplib5.ulTDPLimit); + adev->pm.dpm.near_tdp_limit = le32_to_cpu(power_info->pplib5.ulNearTDPLimit); + adev->pm.dpm.near_tdp_limit_adjusted = adev->pm.dpm.near_tdp_limit; + adev->pm.dpm.tdp_od_limit = le16_to_cpu(power_info->pplib5.usTDPODLimit); + if (adev->pm.dpm.tdp_od_limit) + adev->pm.dpm.power_control = true; + else + adev->pm.dpm.power_control = false; + adev->pm.dpm.tdp_adjustment = 0; + adev->pm.dpm.sq_ramping_threshold = le32_to_cpu(power_info->pplib5.ulSQRampingThreshold); + adev->pm.dpm.cac_leakage = le32_to_cpu(power_info->pplib5.ulCACLeakage); + adev->pm.dpm.load_line_slope = le16_to_cpu(power_info->pplib5.usLoadLineSlope); + if (power_info->pplib5.usCACLeakageTableOffset) { + ATOM_PPLIB_CAC_Leakage_Table *cac_table = + (ATOM_PPLIB_CAC_Leakage_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib5.usCACLeakageTableOffset)); + ATOM_PPLIB_CAC_Leakage_Record *entry; + u32 size = cac_table->ucNumEntries * sizeof(struct amdgpu_cac_leakage_table); + adev->pm.dpm.dyn_state.cac_leakage_table.entries = kzalloc(size, GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.cac_leakage_table.entries) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + entry = &cac_table->entries[0]; + for (i = 0; i < cac_table->ucNumEntries; i++) { + if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_EVV) { + adev->pm.dpm.dyn_state.cac_leakage_table.entries[i].vddc1 = + le16_to_cpu(entry->usVddc1); + adev->pm.dpm.dyn_state.cac_leakage_table.entries[i].vddc2 = + le16_to_cpu(entry->usVddc2); + adev->pm.dpm.dyn_state.cac_leakage_table.entries[i].vddc3 = + le16_to_cpu(entry->usVddc3); + } else { + adev->pm.dpm.dyn_state.cac_leakage_table.entries[i].vddc = + le16_to_cpu(entry->usVddc); + adev->pm.dpm.dyn_state.cac_leakage_table.entries[i].leakage = + le32_to_cpu(entry->ulLeakageValue); + } + entry = (ATOM_PPLIB_CAC_Leakage_Record *) + ((u8 *)entry + sizeof(ATOM_PPLIB_CAC_Leakage_Record)); + } + adev->pm.dpm.dyn_state.cac_leakage_table.count = cac_table->ucNumEntries; + } + } + + /* ext tables */ + if (le16_to_cpu(power_info->pplib.usTableSize) >= + sizeof(struct _ATOM_PPLIB_POWERPLAYTABLE3)) { + ATOM_PPLIB_EXTENDEDHEADER *ext_hdr = (ATOM_PPLIB_EXTENDEDHEADER *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(power_info->pplib3.usExtendendedHeaderOffset)); + if ((le16_to_cpu(ext_hdr->usSize) >= SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V2) && + ext_hdr->usVCETableOffset) { + VCEClockInfoArray *array = (VCEClockInfoArray *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usVCETableOffset) + 1); + ATOM_PPLIB_VCE_Clock_Voltage_Limit_Table *limits = + (ATOM_PPLIB_VCE_Clock_Voltage_Limit_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usVCETableOffset) + 1 + + 1 + array->ucNumEntries * sizeof(VCEClockInfo)); + ATOM_PPLIB_VCE_State_Table *states = + (ATOM_PPLIB_VCE_State_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usVCETableOffset) + 1 + + 1 + (array->ucNumEntries * sizeof (VCEClockInfo)) + + 1 + (limits->numEntries * sizeof(ATOM_PPLIB_VCE_Clock_Voltage_Limit_Record))); + ATOM_PPLIB_VCE_Clock_Voltage_Limit_Record *entry; + ATOM_PPLIB_VCE_State_Record *state_entry; + VCEClockInfo *vce_clk; + u32 size = limits->numEntries * + sizeof(struct amdgpu_vce_clock_voltage_dependency_entry); + adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table.entries = + kzalloc(size, GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table.entries) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table.count = + limits->numEntries; + entry = &limits->entries[0]; + state_entry = &states->entries[0]; + for (i = 0; i < limits->numEntries; i++) { + vce_clk = (VCEClockInfo *) + ((u8 *)&array->entries[0] + + (entry->ucVCEClockInfoIndex * sizeof(VCEClockInfo))); + adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table.entries[i].evclk = + le16_to_cpu(vce_clk->usEVClkLow) | (vce_clk->ucEVClkHigh << 16); + adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table.entries[i].ecclk = + le16_to_cpu(vce_clk->usECClkLow) | (vce_clk->ucECClkHigh << 16); + adev->pm.dpm.dyn_state.vce_clock_voltage_dependency_table.entries[i].v = + le16_to_cpu(entry->usVoltage); + entry = (ATOM_PPLIB_VCE_Clock_Voltage_Limit_Record *) + ((u8 *)entry + sizeof(ATOM_PPLIB_VCE_Clock_Voltage_Limit_Record)); + } + for (i = 0; i < states->numEntries; i++) { + if (i >= AMDGPU_MAX_VCE_LEVELS) + break; + vce_clk = (VCEClockInfo *) + ((u8 *)&array->entries[0] + + (state_entry->ucVCEClockInfoIndex * sizeof(VCEClockInfo))); + adev->pm.dpm.vce_states[i].evclk = + le16_to_cpu(vce_clk->usEVClkLow) | (vce_clk->ucEVClkHigh << 16); + adev->pm.dpm.vce_states[i].ecclk = + le16_to_cpu(vce_clk->usECClkLow) | (vce_clk->ucECClkHigh << 16); + adev->pm.dpm.vce_states[i].clk_idx = + state_entry->ucClockInfoIndex & 0x3f; + adev->pm.dpm.vce_states[i].pstate = + (state_entry->ucClockInfoIndex & 0xc0) >> 6; + state_entry = (ATOM_PPLIB_VCE_State_Record *) + ((u8 *)state_entry + sizeof(ATOM_PPLIB_VCE_State_Record)); + } + } + if ((le16_to_cpu(ext_hdr->usSize) >= SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V3) && + ext_hdr->usUVDTableOffset) { + UVDClockInfoArray *array = (UVDClockInfoArray *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usUVDTableOffset) + 1); + ATOM_PPLIB_UVD_Clock_Voltage_Limit_Table *limits = + (ATOM_PPLIB_UVD_Clock_Voltage_Limit_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usUVDTableOffset) + 1 + + 1 + (array->ucNumEntries * sizeof (UVDClockInfo))); + ATOM_PPLIB_UVD_Clock_Voltage_Limit_Record *entry; + u32 size = limits->numEntries * + sizeof(struct amdgpu_uvd_clock_voltage_dependency_entry); + adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table.entries = + kzalloc(size, GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table.entries) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table.count = + limits->numEntries; + entry = &limits->entries[0]; + for (i = 0; i < limits->numEntries; i++) { + UVDClockInfo *uvd_clk = (UVDClockInfo *) + ((u8 *)&array->entries[0] + + (entry->ucUVDClockInfoIndex * sizeof(UVDClockInfo))); + adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table.entries[i].vclk = + le16_to_cpu(uvd_clk->usVClkLow) | (uvd_clk->ucVClkHigh << 16); + adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table.entries[i].dclk = + le16_to_cpu(uvd_clk->usDClkLow) | (uvd_clk->ucDClkHigh << 16); + adev->pm.dpm.dyn_state.uvd_clock_voltage_dependency_table.entries[i].v = + le16_to_cpu(entry->usVoltage); + entry = (ATOM_PPLIB_UVD_Clock_Voltage_Limit_Record *) + ((u8 *)entry + sizeof(ATOM_PPLIB_UVD_Clock_Voltage_Limit_Record)); + } + } + if ((le16_to_cpu(ext_hdr->usSize) >= SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V4) && + ext_hdr->usSAMUTableOffset) { + ATOM_PPLIB_SAMClk_Voltage_Limit_Table *limits = + (ATOM_PPLIB_SAMClk_Voltage_Limit_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usSAMUTableOffset) + 1); + ATOM_PPLIB_SAMClk_Voltage_Limit_Record *entry; + u32 size = limits->numEntries * + sizeof(struct amdgpu_clock_voltage_dependency_entry); + adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table.entries = + kzalloc(size, GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table.entries) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table.count = + limits->numEntries; + entry = &limits->entries[0]; + for (i = 0; i < limits->numEntries; i++) { + adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table.entries[i].clk = + le16_to_cpu(entry->usSAMClockLow) | (entry->ucSAMClockHigh << 16); + adev->pm.dpm.dyn_state.samu_clock_voltage_dependency_table.entries[i].v = + le16_to_cpu(entry->usVoltage); + entry = (ATOM_PPLIB_SAMClk_Voltage_Limit_Record *) + ((u8 *)entry + sizeof(ATOM_PPLIB_SAMClk_Voltage_Limit_Record)); + } + } + if ((le16_to_cpu(ext_hdr->usSize) >= SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V5) && + ext_hdr->usPPMTableOffset) { + ATOM_PPLIB_PPM_Table *ppm = (ATOM_PPLIB_PPM_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usPPMTableOffset)); + adev->pm.dpm.dyn_state.ppm_table = + kzalloc(sizeof(struct amdgpu_ppm_table), GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.ppm_table) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + adev->pm.dpm.dyn_state.ppm_table->ppm_design = ppm->ucPpmDesign; + adev->pm.dpm.dyn_state.ppm_table->cpu_core_number = + le16_to_cpu(ppm->usCpuCoreNumber); + adev->pm.dpm.dyn_state.ppm_table->platform_tdp = + le32_to_cpu(ppm->ulPlatformTDP); + adev->pm.dpm.dyn_state.ppm_table->small_ac_platform_tdp = + le32_to_cpu(ppm->ulSmallACPlatformTDP); + adev->pm.dpm.dyn_state.ppm_table->platform_tdc = + le32_to_cpu(ppm->ulPlatformTDC); + adev->pm.dpm.dyn_state.ppm_table->small_ac_platform_tdc = + le32_to_cpu(ppm->ulSmallACPlatformTDC); + adev->pm.dpm.dyn_state.ppm_table->apu_tdp = + le32_to_cpu(ppm->ulApuTDP); + adev->pm.dpm.dyn_state.ppm_table->dgpu_tdp = + le32_to_cpu(ppm->ulDGpuTDP); + adev->pm.dpm.dyn_state.ppm_table->dgpu_ulv_power = + le32_to_cpu(ppm->ulDGpuUlvPower); + adev->pm.dpm.dyn_state.ppm_table->tj_max = + le32_to_cpu(ppm->ulTjmax); + } + if ((le16_to_cpu(ext_hdr->usSize) >= SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V6) && + ext_hdr->usACPTableOffset) { + ATOM_PPLIB_ACPClk_Voltage_Limit_Table *limits = + (ATOM_PPLIB_ACPClk_Voltage_Limit_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usACPTableOffset) + 1); + ATOM_PPLIB_ACPClk_Voltage_Limit_Record *entry; + u32 size = limits->numEntries * + sizeof(struct amdgpu_clock_voltage_dependency_entry); + adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table.entries = + kzalloc(size, GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table.entries) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table.count = + limits->numEntries; + entry = &limits->entries[0]; + for (i = 0; i < limits->numEntries; i++) { + adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table.entries[i].clk = + le16_to_cpu(entry->usACPClockLow) | (entry->ucACPClockHigh << 16); + adev->pm.dpm.dyn_state.acp_clock_voltage_dependency_table.entries[i].v = + le16_to_cpu(entry->usVoltage); + entry = (ATOM_PPLIB_ACPClk_Voltage_Limit_Record *) + ((u8 *)entry + sizeof(ATOM_PPLIB_ACPClk_Voltage_Limit_Record)); + } + } + if ((le16_to_cpu(ext_hdr->usSize) >= SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V7) && + ext_hdr->usPowerTuneTableOffset) { + u8 rev = *(u8 *)(mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usPowerTuneTableOffset)); + ATOM_PowerTune_Table *pt; + adev->pm.dpm.dyn_state.cac_tdp_table = + kzalloc(sizeof(struct amdgpu_cac_tdp_table), GFP_KERNEL); + if (!adev->pm.dpm.dyn_state.cac_tdp_table) { + amdgpu_free_extended_power_table(adev); + return -ENOMEM; + } + if (rev > 0) { + ATOM_PPLIB_POWERTUNE_Table_V1 *ppt = (ATOM_PPLIB_POWERTUNE_Table_V1 *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usPowerTuneTableOffset)); + adev->pm.dpm.dyn_state.cac_tdp_table->maximum_power_delivery_limit = + ppt->usMaximumPowerDeliveryLimit; + pt = &ppt->power_tune_table; + } else { + ATOM_PPLIB_POWERTUNE_Table *ppt = (ATOM_PPLIB_POWERTUNE_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usPowerTuneTableOffset)); + adev->pm.dpm.dyn_state.cac_tdp_table->maximum_power_delivery_limit = 255; + pt = &ppt->power_tune_table; + } + adev->pm.dpm.dyn_state.cac_tdp_table->tdp = le16_to_cpu(pt->usTDP); + adev->pm.dpm.dyn_state.cac_tdp_table->configurable_tdp = + le16_to_cpu(pt->usConfigurableTDP); + adev->pm.dpm.dyn_state.cac_tdp_table->tdc = le16_to_cpu(pt->usTDC); + adev->pm.dpm.dyn_state.cac_tdp_table->battery_power_limit = + le16_to_cpu(pt->usBatteryPowerLimit); + adev->pm.dpm.dyn_state.cac_tdp_table->small_power_limit = + le16_to_cpu(pt->usSmallPowerLimit); + adev->pm.dpm.dyn_state.cac_tdp_table->low_cac_leakage = + le16_to_cpu(pt->usLowCACLeakage); + adev->pm.dpm.dyn_state.cac_tdp_table->high_cac_leakage = + le16_to_cpu(pt->usHighCACLeakage); + } + if ((le16_to_cpu(ext_hdr->usSize) >= SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V8) && + ext_hdr->usSclkVddgfxTableOffset) { + dep_table = (ATOM_PPLIB_Clock_Voltage_Dependency_Table *) + (mode_info->atom_context->bios + data_offset + + le16_to_cpu(ext_hdr->usSclkVddgfxTableOffset)); + ret = amdgpu_parse_clk_voltage_dep_table( + &adev->pm.dpm.dyn_state.vddgfx_dependency_on_sclk, + dep_table); + if (ret) { + kfree(adev->pm.dpm.dyn_state.vddgfx_dependency_on_sclk.entries); + return ret; + } + } + } + + return 0; +} + +void amdgpu_free_extended_power_table(struct amdgpu_device *adev) +{ + struct amdgpu_dpm_dynamic_state *dyn_state = &adev->pm.dpm.dyn_state; + + kfree(dyn_state->vddc_dependency_on_sclk.entries); + kfree(dyn_state->vddci_dependency_on_mclk.entries); + kfree(dyn_state->vddc_dependency_on_mclk.entries); + kfree(dyn_state->mvdd_dependency_on_mclk.entries); + kfree(dyn_state->cac_leakage_table.entries); + kfree(dyn_state->phase_shedding_limits_table.entries); + kfree(dyn_state->ppm_table); + kfree(dyn_state->cac_tdp_table); + kfree(dyn_state->vce_clock_voltage_dependency_table.entries); + kfree(dyn_state->uvd_clock_voltage_dependency_table.entries); + kfree(dyn_state->samu_clock_voltage_dependency_table.entries); + kfree(dyn_state->acp_clock_voltage_dependency_table.entries); + kfree(dyn_state->vddgfx_dependency_on_sclk.entries); +} + +static const char *pp_lib_thermal_controller_names[] = { + "NONE", + "lm63", + "adm1032", + "adm1030", + "max6649", + "lm64", + "f75375", + "RV6xx", + "RV770", + "adt7473", + "NONE", + "External GPIO", + "Evergreen", + "emc2103", + "Sumo", + "Northern Islands", + "Southern Islands", + "lm96163", + "Sea Islands", + "Kaveri/Kabini", +}; + +void amdgpu_add_thermal_controller(struct amdgpu_device *adev) +{ + struct amdgpu_mode_info *mode_info = &adev->mode_info; + ATOM_PPLIB_POWERPLAYTABLE *power_table; + int index = GetIndexIntoMasterTable(DATA, PowerPlayInfo); + ATOM_PPLIB_THERMALCONTROLLER *controller; + struct amdgpu_i2c_bus_rec i2c_bus; + u16 data_offset; + u8 frev, crev; + + if (!amdgpu_atom_parse_data_header(mode_info->atom_context, index, NULL, + &frev, &crev, &data_offset)) + return; + power_table = (ATOM_PPLIB_POWERPLAYTABLE *) + (mode_info->atom_context->bios + data_offset); + controller = &power_table->sThermalController; + + /* add the i2c bus for thermal/fan chip */ + if (controller->ucType > 0) { + if (controller->ucFanParameters & ATOM_PP_FANPARAMETERS_NOFAN) + adev->pm.no_fan = true; + adev->pm.fan_pulses_per_revolution = + controller->ucFanParameters & ATOM_PP_FANPARAMETERS_TACHOMETER_PULSES_PER_REVOLUTION_MASK; + if (adev->pm.fan_pulses_per_revolution) { + adev->pm.fan_min_rpm = controller->ucFanMinRPM; + adev->pm.fan_max_rpm = controller->ucFanMaxRPM; + } + if (controller->ucType == ATOM_PP_THERMALCONTROLLER_RV6xx) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_RV6XX; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_RV770) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_RV770; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_EVERGREEN) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_EVERGREEN; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_SUMO) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_SUMO; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_NISLANDS) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_NI; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_SISLANDS) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_SI; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_CISLANDS) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_CI; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_KAVERI) { + DRM_INFO("Internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_KV; + } else if (controller->ucType == ATOM_PP_THERMALCONTROLLER_EXTERNAL_GPIO) { + DRM_INFO("External GPIO thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_EXTERNAL_GPIO; + } else if (controller->ucType == + ATOM_PP_THERMALCONTROLLER_ADT7473_WITH_INTERNAL) { + DRM_INFO("ADT7473 with internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_ADT7473_WITH_INTERNAL; + } else if (controller->ucType == + ATOM_PP_THERMALCONTROLLER_EMC2103_WITH_INTERNAL) { + DRM_INFO("EMC2103 with internal thermal controller %s fan control\n", + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_EMC2103_WITH_INTERNAL; + } else if (controller->ucType < ARRAY_SIZE(pp_lib_thermal_controller_names)) { + DRM_INFO("Possible %s thermal controller at 0x%02x %s fan control\n", + pp_lib_thermal_controller_names[controller->ucType], + controller->ucI2cAddress >> 1, + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + adev->pm.int_thermal_type = THERMAL_TYPE_EXTERNAL; + i2c_bus = amdgpu_atombios_lookup_i2c_gpio(adev, controller->ucI2cLine); + adev->pm.i2c_bus = amdgpu_i2c_lookup(adev, &i2c_bus); + if (adev->pm.i2c_bus) { + struct i2c_board_info info = { }; + const char *name = pp_lib_thermal_controller_names[controller->ucType]; + info.addr = controller->ucI2cAddress >> 1; + strlcpy(info.type, name, sizeof(info.type)); + i2c_new_device(&adev->pm.i2c_bus->adapter, &info); + } + } else { + DRM_INFO("Unknown thermal controller type %d at 0x%02x %s fan control\n", + controller->ucType, + controller->ucI2cAddress >> 1, + (controller->ucFanParameters & + ATOM_PP_FANPARAMETERS_NOFAN) ? "without" : "with"); + } + } +} + +enum amdgpu_pcie_gen amdgpu_get_pcie_gen_support(struct amdgpu_device *adev, + u32 sys_mask, + enum amdgpu_pcie_gen asic_gen, + enum amdgpu_pcie_gen default_gen) +{ + switch (asic_gen) { + case AMDGPU_PCIE_GEN1: + return AMDGPU_PCIE_GEN1; + case AMDGPU_PCIE_GEN2: + return AMDGPU_PCIE_GEN2; + case AMDGPU_PCIE_GEN3: + return AMDGPU_PCIE_GEN3; + default: + if ((sys_mask & DRM_PCIE_SPEED_80) && (default_gen == AMDGPU_PCIE_GEN3)) + return AMDGPU_PCIE_GEN3; + else if ((sys_mask & DRM_PCIE_SPEED_50) && (default_gen == AMDGPU_PCIE_GEN2)) + return AMDGPU_PCIE_GEN2; + else + return AMDGPU_PCIE_GEN1; + } + return AMDGPU_PCIE_GEN1; +} + +u16 amdgpu_get_pcie_lane_support(struct amdgpu_device *adev, + u16 asic_lanes, + u16 default_lanes) +{ + switch (asic_lanes) { + case 0: + default: + return default_lanes; + case 1: + return 1; + case 2: + return 2; + case 4: + return 4; + case 8: + return 8; + case 12: + return 12; + case 16: + return 16; + } +} + +u8 amdgpu_encode_pci_lane_width(u32 lanes) +{ + u8 encoded_lanes[] = { 0, 1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6 }; + + if (lanes > 16) + return 0; + + return encoded_lanes[lanes]; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h new file mode 100644 index 000000000000..3738a96c2619 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dpm.h @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_DPM_H__ +#define __AMDGPU_DPM_H__ + +#define R600_SSTU_DFLT 0 +#define R600_SST_DFLT 0x00C8 + +/* XXX are these ok? */ +#define R600_TEMP_RANGE_MIN (90 * 1000) +#define R600_TEMP_RANGE_MAX (120 * 1000) + +#define FDO_PWM_MODE_STATIC 1 +#define FDO_PWM_MODE_STATIC_RPM 5 + +enum amdgpu_td { + AMDGPU_TD_AUTO, + AMDGPU_TD_UP, + AMDGPU_TD_DOWN, +}; + +enum amdgpu_display_watermark { + AMDGPU_DISPLAY_WATERMARK_LOW = 0, + AMDGPU_DISPLAY_WATERMARK_HIGH = 1, +}; + +enum amdgpu_display_gap +{ + AMDGPU_PM_DISPLAY_GAP_VBLANK_OR_WM = 0, + AMDGPU_PM_DISPLAY_GAP_VBLANK = 1, + AMDGPU_PM_DISPLAY_GAP_WATERMARK = 2, + AMDGPU_PM_DISPLAY_GAP_IGNORE = 3, +}; + +void amdgpu_dpm_print_class_info(u32 class, u32 class2); +void amdgpu_dpm_print_cap_info(u32 caps); +void amdgpu_dpm_print_ps_status(struct amdgpu_device *adev, + struct amdgpu_ps *rps); +u32 amdgpu_dpm_get_vblank_time(struct amdgpu_device *adev); +u32 amdgpu_dpm_get_vrefresh(struct amdgpu_device *adev); +bool amdgpu_is_uvd_state(u32 class, u32 class2); +void amdgpu_calculate_u_and_p(u32 i, u32 r_c, u32 p_b, + u32 *p, u32 *u); +int amdgpu_calculate_at(u32 t, u32 h, u32 fh, u32 fl, u32 *tl, u32 *th); + +bool amdgpu_is_internal_thermal_sensor(enum amdgpu_int_thermal_type sensor); + +int amdgpu_get_platform_caps(struct amdgpu_device *adev); + +int amdgpu_parse_extended_power_table(struct amdgpu_device *adev); +void amdgpu_free_extended_power_table(struct amdgpu_device *adev); + +void amdgpu_add_thermal_controller(struct amdgpu_device *adev); + +enum amdgpu_pcie_gen amdgpu_get_pcie_gen_support(struct amdgpu_device *adev, + u32 sys_mask, + enum amdgpu_pcie_gen asic_gen, + enum amdgpu_pcie_gen default_gen); + +u16 amdgpu_get_pcie_lane_support(struct amdgpu_device *adev, + u16 asic_lanes, + u16 default_lanes); +u8 amdgpu_encode_pci_lane_width(u32 lanes); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c new file mode 100644 index 000000000000..d1af448795f3 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -0,0 +1,439 @@ +/** + * \file amdgpu_drv.c + * AMD Amdgpu driver + * + * \author Gareth Hughes <gareth@valinux.com> + */ + +/* + * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. + * 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 <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include <drm/drm_gem.h> +#include "amdgpu_drv.h" + +#include <drm/drm_pciids.h> +#include <linux/console.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/vga_switcheroo.h> +#include "drm_crtc_helper.h" + +#include "amdgpu.h" +#include "amdgpu_irq.h" + +/* + * KMS wrapper. + * - 3.0.0 - initial driver + */ +#define KMS_DRIVER_MAJOR 3 +#define KMS_DRIVER_MINOR 0 +#define KMS_DRIVER_PATCHLEVEL 0 + +int amdgpu_vram_limit = 0; +int amdgpu_gart_size = -1; /* auto */ +int amdgpu_benchmarking = 0; +int amdgpu_testing = 0; +int amdgpu_audio = -1; +int amdgpu_disp_priority = 0; +int amdgpu_hw_i2c = 0; +int amdgpu_pcie_gen2 = -1; +int amdgpu_msi = -1; +int amdgpu_lockup_timeout = 10000; +int amdgpu_dpm = -1; +int amdgpu_smc_load_fw = 1; +int amdgpu_aspm = -1; +int amdgpu_runtime_pm = -1; +int amdgpu_hard_reset = 0; +unsigned amdgpu_ip_block_mask = 0xffffffff; +int amdgpu_bapm = -1; +int amdgpu_deep_color = 0; +int amdgpu_vm_size = 8; +int amdgpu_vm_block_size = -1; +int amdgpu_exp_hw_support = 0; + +MODULE_PARM_DESC(vramlimit, "Restrict VRAM for testing, in megabytes"); +module_param_named(vramlimit, amdgpu_vram_limit, int, 0600); + +MODULE_PARM_DESC(gartsize, "Size of PCIE/IGP gart to setup in megabytes (32, 64, etc., -1 = auto)"); +module_param_named(gartsize, amdgpu_gart_size, int, 0600); + +MODULE_PARM_DESC(benchmark, "Run benchmark"); +module_param_named(benchmark, amdgpu_benchmarking, int, 0444); + +MODULE_PARM_DESC(test, "Run tests"); +module_param_named(test, amdgpu_testing, int, 0444); + +MODULE_PARM_DESC(audio, "Audio enable (-1 = auto, 0 = disable, 1 = enable)"); +module_param_named(audio, amdgpu_audio, int, 0444); + +MODULE_PARM_DESC(disp_priority, "Display Priority (0 = auto, 1 = normal, 2 = high)"); +module_param_named(disp_priority, amdgpu_disp_priority, int, 0444); + +MODULE_PARM_DESC(hw_i2c, "hw i2c engine enable (0 = disable)"); +module_param_named(hw_i2c, amdgpu_hw_i2c, int, 0444); + +MODULE_PARM_DESC(pcie_gen2, "PCIE Gen2 mode (-1 = auto, 0 = disable, 1 = enable)"); +module_param_named(pcie_gen2, amdgpu_pcie_gen2, int, 0444); + +MODULE_PARM_DESC(msi, "MSI support (1 = enable, 0 = disable, -1 = auto)"); +module_param_named(msi, amdgpu_msi, int, 0444); + +MODULE_PARM_DESC(lockup_timeout, "GPU lockup timeout in ms (defaul 10000 = 10 seconds, 0 = disable)"); +module_param_named(lockup_timeout, amdgpu_lockup_timeout, int, 0444); + +MODULE_PARM_DESC(dpm, "DPM support (1 = enable, 0 = disable, -1 = auto)"); +module_param_named(dpm, amdgpu_dpm, int, 0444); + +MODULE_PARM_DESC(smc_load_fw, "SMC firmware loading(1 = enable, 0 = disable)"); +module_param_named(smc_load_fw, amdgpu_smc_load_fw, int, 0444); + +MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)"); +module_param_named(aspm, amdgpu_aspm, int, 0444); + +MODULE_PARM_DESC(runpm, "PX runtime pm (1 = force enable, 0 = disable, -1 = PX only default)"); +module_param_named(runpm, amdgpu_runtime_pm, int, 0444); + +MODULE_PARM_DESC(hard_reset, "PCI config reset (1 = force enable, 0 = disable (default))"); +module_param_named(hard_reset, amdgpu_hard_reset, int, 0444); + +MODULE_PARM_DESC(ip_block_mask, "IP Block Mask (all blocks enabled (default))"); +module_param_named(ip_block_mask, amdgpu_ip_block_mask, uint, 0444); + +MODULE_PARM_DESC(bapm, "BAPM support (1 = enable, 0 = disable, -1 = auto)"); +module_param_named(bapm, amdgpu_bapm, int, 0444); + +MODULE_PARM_DESC(deep_color, "Deep Color support (1 = enable, 0 = disable (default))"); +module_param_named(deep_color, amdgpu_deep_color, int, 0444); + +MODULE_PARM_DESC(vm_size, "VM address space size in gigabytes (default 4GB)"); +module_param_named(vm_size, amdgpu_vm_size, int, 0444); + +MODULE_PARM_DESC(vm_block_size, "VM page table size in bits (default depending on vm_size)"); +module_param_named(vm_block_size, amdgpu_vm_block_size, int, 0444); + +MODULE_PARM_DESC(exp_hw_support, "experimental hw support (1 = enable, 0 = disable (default))"); +module_param_named(exp_hw_support, amdgpu_exp_hw_support, int, 0444); + +static struct pci_device_id pciidlist[] = { + + {0, 0, 0} +}; + +MODULE_DEVICE_TABLE(pci, pciidlist); + +static struct drm_driver kms_driver; + +static int amdgpu_kick_out_firmware_fb(struct pci_dev *pdev) +{ + struct apertures_struct *ap; + bool primary = false; + + ap = alloc_apertures(1); + if (!ap) + return -ENOMEM; + + ap->ranges[0].base = pci_resource_start(pdev, 0); + ap->ranges[0].size = pci_resource_len(pdev, 0); + +#ifdef CONFIG_X86 + primary = pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; +#endif + remove_conflicting_framebuffers(ap, "amdgpudrmfb", primary); + kfree(ap); + + return 0; +} + +static int amdgpu_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + unsigned long flags = ent->driver_data; + int ret; + + if ((flags & AMDGPU_EXP_HW_SUPPORT) && !amdgpu_exp_hw_support) { + DRM_INFO("This hardware requires experimental hardware support.\n" + "See modparam exp_hw_support\n"); + return -ENODEV; + } + + /* Get rid of things like offb */ + ret = amdgpu_kick_out_firmware_fb(pdev); + if (ret) + return ret; + + return drm_get_pci_dev(pdev, ent, &kms_driver); +} + +static void +amdgpu_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + drm_put_dev(dev); +} + +static int amdgpu_pmops_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + return amdgpu_suspend_kms(drm_dev, true, true); +} + +static int amdgpu_pmops_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + return amdgpu_resume_kms(drm_dev, true, true); +} + +static int amdgpu_pmops_freeze(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + return amdgpu_suspend_kms(drm_dev, false, true); +} + +static int amdgpu_pmops_thaw(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + return amdgpu_resume_kms(drm_dev, false, true); +} + +static int amdgpu_pmops_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + int ret; + + if (!amdgpu_device_is_px(drm_dev)) { + pm_runtime_forbid(dev); + return -EBUSY; + } + + drm_dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; + drm_kms_helper_poll_disable(drm_dev); + vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_OFF); + + ret = amdgpu_suspend_kms(drm_dev, false, false); + pci_save_state(pdev); + pci_disable_device(pdev); + pci_ignore_hotplug(pdev); + pci_set_power_state(pdev, PCI_D3cold); + drm_dev->switch_power_state = DRM_SWITCH_POWER_DYNAMIC_OFF; + + return 0; +} + +static int amdgpu_pmops_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + int ret; + + if (!amdgpu_device_is_px(drm_dev)) + return -EINVAL; + + drm_dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + ret = pci_enable_device(pdev); + if (ret) + return ret; + pci_set_master(pdev); + + ret = amdgpu_resume_kms(drm_dev, false, false); + drm_kms_helper_poll_enable(drm_dev); + vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_ON); + drm_dev->switch_power_state = DRM_SWITCH_POWER_ON; + return 0; +} + +static int amdgpu_pmops_runtime_idle(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + struct drm_crtc *crtc; + + if (!amdgpu_device_is_px(drm_dev)) { + pm_runtime_forbid(dev); + return -EBUSY; + } + + list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) { + if (crtc->enabled) { + DRM_DEBUG_DRIVER("failing to power off - crtc active\n"); + return -EBUSY; + } + } + + pm_runtime_mark_last_busy(dev); + pm_runtime_autosuspend(dev); + /* we don't want the main rpm_idle to call suspend - we want to autosuspend */ + return 1; +} + +long amdgpu_drm_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev; + long ret; + dev = file_priv->minor->dev; + ret = pm_runtime_get_sync(dev->dev); + if (ret < 0) + return ret; + + ret = drm_ioctl(filp, cmd, arg); + + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + return ret; +} + +static const struct dev_pm_ops amdgpu_pm_ops = { + .suspend = amdgpu_pmops_suspend, + .resume = amdgpu_pmops_resume, + .freeze = amdgpu_pmops_freeze, + .thaw = amdgpu_pmops_thaw, + .poweroff = amdgpu_pmops_freeze, + .restore = amdgpu_pmops_resume, + .runtime_suspend = amdgpu_pmops_runtime_suspend, + .runtime_resume = amdgpu_pmops_runtime_resume, + .runtime_idle = amdgpu_pmops_runtime_idle, +}; + +static const struct file_operations amdgpu_driver_kms_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = amdgpu_drm_ioctl, + .mmap = amdgpu_mmap, + .poll = drm_poll, + .read = drm_read, +#ifdef CONFIG_COMPAT + .compat_ioctl = amdgpu_kms_compat_ioctl, +#endif +}; + +static struct drm_driver kms_driver = { + .driver_features = + DRIVER_USE_AGP | + DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | DRIVER_GEM | + DRIVER_PRIME | DRIVER_RENDER, + .dev_priv_size = 0, + .load = amdgpu_driver_load_kms, + .open = amdgpu_driver_open_kms, + .preclose = amdgpu_driver_preclose_kms, + .postclose = amdgpu_driver_postclose_kms, + .lastclose = amdgpu_driver_lastclose_kms, + .set_busid = drm_pci_set_busid, + .unload = amdgpu_driver_unload_kms, + .get_vblank_counter = amdgpu_get_vblank_counter_kms, + .enable_vblank = amdgpu_enable_vblank_kms, + .disable_vblank = amdgpu_disable_vblank_kms, + .get_vblank_timestamp = amdgpu_get_vblank_timestamp_kms, + .get_scanout_position = amdgpu_get_crtc_scanoutpos, +#if defined(CONFIG_DEBUG_FS) + .debugfs_init = amdgpu_debugfs_init, + .debugfs_cleanup = amdgpu_debugfs_cleanup, +#endif + .irq_preinstall = amdgpu_irq_preinstall, + .irq_postinstall = amdgpu_irq_postinstall, + .irq_uninstall = amdgpu_irq_uninstall, + .irq_handler = amdgpu_irq_handler, + .ioctls = amdgpu_ioctls_kms, + .gem_free_object = amdgpu_gem_object_free, + .gem_open_object = amdgpu_gem_object_open, + .gem_close_object = amdgpu_gem_object_close, + .dumb_create = amdgpu_mode_dumb_create, + .dumb_map_offset = amdgpu_mode_dumb_mmap, + .dumb_destroy = drm_gem_dumb_destroy, + .fops = &amdgpu_driver_kms_fops, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = amdgpu_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_pin = amdgpu_gem_prime_pin, + .gem_prime_unpin = amdgpu_gem_prime_unpin, + .gem_prime_res_obj = amdgpu_gem_prime_res_obj, + .gem_prime_get_sg_table = amdgpu_gem_prime_get_sg_table, + .gem_prime_import_sg_table = amdgpu_gem_prime_import_sg_table, + .gem_prime_vmap = amdgpu_gem_prime_vmap, + .gem_prime_vunmap = amdgpu_gem_prime_vunmap, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = KMS_DRIVER_MAJOR, + .minor = KMS_DRIVER_MINOR, + .patchlevel = KMS_DRIVER_PATCHLEVEL, +}; + +static struct drm_driver *driver; +static struct pci_driver *pdriver; + +static struct pci_driver amdgpu_kms_pci_driver = { + .name = DRIVER_NAME, + .id_table = pciidlist, + .probe = amdgpu_pci_probe, + .remove = amdgpu_pci_remove, + .driver.pm = &amdgpu_pm_ops, +}; + +static int __init amdgpu_init(void) +{ +#ifdef CONFIG_VGA_CONSOLE + if (vgacon_text_force()) { + DRM_ERROR("VGACON disables amdgpu kernel modesetting.\n"); + return -EINVAL; + } +#endif + DRM_INFO("amdgpu kernel modesetting enabled.\n"); + driver = &kms_driver; + pdriver = &amdgpu_kms_pci_driver; + driver->driver_features |= DRIVER_MODESET; + driver->num_ioctls = amdgpu_max_kms_ioctl; + amdgpu_register_atpx_handler(); + + /* let modprobe override vga console setting */ + return drm_pci_init(driver, pdriver); +} + +static void __exit amdgpu_exit(void) +{ + drm_pci_exit(driver, pdriver); + amdgpu_unregister_atpx_handler(); +} + +module_init(amdgpu_init); +module_exit(amdgpu_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.h new file mode 100644 index 000000000000..cceeb33c447a --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.h @@ -0,0 +1,48 @@ +/* amdgpu_drv.h -- Private header for amdgpu driver -*- linux-c -*- + * + * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. + * Copyright 2000 VA Linux Systems, Inc., Fremont, California. + * 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. + * + */ + +#ifndef __AMDGPU_DRV_H__ +#define __AMDGPU_DRV_H__ + +#include <linux/firmware.h> +#include <linux/platform_device.h> + +#include "amdgpu_family.h" + +/* General customization: + */ + +#define DRIVER_AUTHOR "AMD linux driver team" + +#define DRIVER_NAME "amdgpu" +#define DRIVER_DESC "AMD GPU" +#define DRIVER_DATE "20150101" + +long amdgpu_drm_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_encoders.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_encoders.c new file mode 100644 index 000000000000..94138abe093b --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_encoders.c @@ -0,0 +1,245 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_connectors.h" +#include "atom.h" +#include "atombios_encoders.h" + +void +amdgpu_link_encoder_connector(struct drm_device *dev) +{ + struct amdgpu_device *adev = dev->dev_private; + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + + /* walk the list and link encoders to connectors */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + amdgpu_connector = to_amdgpu_connector(connector); + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + amdgpu_encoder = to_amdgpu_encoder(encoder); + if (amdgpu_encoder->devices & amdgpu_connector->devices) { + drm_mode_connector_attach_encoder(connector, encoder); + if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) { + amdgpu_atombios_encoder_init_backlight(amdgpu_encoder, connector); + adev->mode_info.bl_encoder = amdgpu_encoder; + } + } + } + } +} + +void amdgpu_encoder_set_active_device(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_connector *connector; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + amdgpu_encoder->active_device = amdgpu_encoder->devices & amdgpu_connector->devices; + DRM_DEBUG_KMS("setting active device to %08x from %08x %08x for encoder %d\n", + amdgpu_encoder->active_device, amdgpu_encoder->devices, + amdgpu_connector->devices, encoder->encoder_type); + } + } +} + +struct drm_connector * +amdgpu_get_connector_for_encoder(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + amdgpu_connector = to_amdgpu_connector(connector); + if (amdgpu_encoder->active_device & amdgpu_connector->devices) + return connector; + } + return NULL; +} + +struct drm_connector * +amdgpu_get_connector_for_encoder_init(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + amdgpu_connector = to_amdgpu_connector(connector); + if (amdgpu_encoder->devices & amdgpu_connector->devices) + return connector; + } + return NULL; +} + +struct drm_encoder *amdgpu_get_external_encoder(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_encoder *other_encoder; + struct amdgpu_encoder *other_amdgpu_encoder; + + if (amdgpu_encoder->is_ext_encoder) + return NULL; + + list_for_each_entry(other_encoder, &dev->mode_config.encoder_list, head) { + if (other_encoder == encoder) + continue; + other_amdgpu_encoder = to_amdgpu_encoder(other_encoder); + if (other_amdgpu_encoder->is_ext_encoder && + (amdgpu_encoder->devices & other_amdgpu_encoder->devices)) + return other_encoder; + } + return NULL; +} + +u16 amdgpu_encoder_get_dp_bridge_encoder_id(struct drm_encoder *encoder) +{ + struct drm_encoder *other_encoder = amdgpu_get_external_encoder(encoder); + + if (other_encoder) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(other_encoder); + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_TRAVIS: + case ENCODER_OBJECT_ID_NUTMEG: + return amdgpu_encoder->encoder_id; + default: + return ENCODER_OBJECT_ID_NONE; + } + } + return ENCODER_OBJECT_ID_NONE; +} + +void amdgpu_panel_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *adjusted_mode) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + unsigned hblank = native_mode->htotal - native_mode->hdisplay; + unsigned vblank = native_mode->vtotal - native_mode->vdisplay; + unsigned hover = native_mode->hsync_start - native_mode->hdisplay; + unsigned vover = native_mode->vsync_start - native_mode->vdisplay; + unsigned hsync_width = native_mode->hsync_end - native_mode->hsync_start; + unsigned vsync_width = native_mode->vsync_end - native_mode->vsync_start; + + adjusted_mode->clock = native_mode->clock; + adjusted_mode->flags = native_mode->flags; + + adjusted_mode->hdisplay = native_mode->hdisplay; + adjusted_mode->vdisplay = native_mode->vdisplay; + + adjusted_mode->htotal = native_mode->hdisplay + hblank; + adjusted_mode->hsync_start = native_mode->hdisplay + hover; + adjusted_mode->hsync_end = adjusted_mode->hsync_start + hsync_width; + + adjusted_mode->vtotal = native_mode->vdisplay + vblank; + adjusted_mode->vsync_start = native_mode->vdisplay + vover; + adjusted_mode->vsync_end = adjusted_mode->vsync_start + vsync_width; + + drm_mode_set_crtcinfo(adjusted_mode, CRTC_INTERLACE_HALVE_V); + + adjusted_mode->crtc_hdisplay = native_mode->hdisplay; + adjusted_mode->crtc_vdisplay = native_mode->vdisplay; + + adjusted_mode->crtc_htotal = adjusted_mode->crtc_hdisplay + hblank; + adjusted_mode->crtc_hsync_start = adjusted_mode->crtc_hdisplay + hover; + adjusted_mode->crtc_hsync_end = adjusted_mode->crtc_hsync_start + hsync_width; + + adjusted_mode->crtc_vtotal = adjusted_mode->crtc_vdisplay + vblank; + adjusted_mode->crtc_vsync_start = adjusted_mode->crtc_vdisplay + vover; + adjusted_mode->crtc_vsync_end = adjusted_mode->crtc_vsync_start + vsync_width; + +} + +bool amdgpu_dig_monitor_is_duallink(struct drm_encoder *encoder, + u32 pixel_clock) +{ + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + struct amdgpu_connector_atom_dig *dig_connector; + + connector = amdgpu_get_connector_for_encoder(encoder); + /* if we don't have an active device yet, just use one of + * the connectors tied to the encoder. + */ + if (!connector) + connector = amdgpu_get_connector_for_encoder_init(encoder); + amdgpu_connector = to_amdgpu_connector(connector); + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_HDMIB: + if (amdgpu_connector->use_digital) { + /* HDMI 1.3 supports up to 340 Mhz over single link */ + if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + if (pixel_clock > 340000) + return true; + else + return false; + } else { + if (pixel_clock > 165000) + return true; + else + return false; + } + } else + return false; + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_DisplayPort: + dig_connector = amdgpu_connector->con_priv; + if ((dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) || + (dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP)) + return false; + else { + /* HDMI 1.3 supports up to 340 Mhz over single link */ + if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector))) { + if (pixel_clock > 340000) + return true; + else + return false; + } else { + if (pixel_clock > 165000) + return true; + else + return false; + } + } + default: + return false; + } +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fb.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fb.c new file mode 100644 index 000000000000..2b1735d2efd6 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fb.c @@ -0,0 +1,432 @@ +/* + * 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/slab.h> +#include <linux/fb.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +#include <drm/drm_fb_helper.h> + +#include <linux/vga_switcheroo.h> + +/* object hierarchy - + this contains a helper + a amdgpu fb + the helper contains a pointer to amdgpu framebuffer baseclass. +*/ +struct amdgpu_fbdev { + struct drm_fb_helper helper; + struct amdgpu_framebuffer rfb; + struct list_head fbdev_list; + struct amdgpu_device *adev; +}; + +static struct fb_ops amdgpufb_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, +}; + + +int amdgpu_align_pitch(struct amdgpu_device *adev, int width, int bpp, bool tiled) +{ + int aligned = width; + int pitch_mask = 0; + + switch (bpp / 8) { + case 1: + pitch_mask = 255; + break; + case 2: + pitch_mask = 127; + break; + case 3: + case 4: + pitch_mask = 63; + break; + } + + aligned += pitch_mask; + aligned &= ~pitch_mask; + return aligned; +} + +static void amdgpufb_destroy_pinned_object(struct drm_gem_object *gobj) +{ + struct amdgpu_bo *rbo = gem_to_amdgpu_bo(gobj); + int ret; + + ret = amdgpu_bo_reserve(rbo, false); + if (likely(ret == 0)) { + amdgpu_bo_kunmap(rbo); + amdgpu_bo_unpin(rbo); + amdgpu_bo_unreserve(rbo); + } + drm_gem_object_unreference_unlocked(gobj); +} + +static int amdgpufb_create_pinned_object(struct amdgpu_fbdev *rfbdev, + struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **gobj_p) +{ + struct amdgpu_device *adev = rfbdev->adev; + struct drm_gem_object *gobj = NULL; + struct amdgpu_bo *rbo = NULL; + bool fb_tiled = false; /* useful for testing */ + u32 tiling_flags = 0; + int ret; + int aligned_size, size; + int height = mode_cmd->height; + u32 bpp, depth; + + drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp); + + /* need to align pitch with crtc limits */ + mode_cmd->pitches[0] = amdgpu_align_pitch(adev, mode_cmd->width, bpp, + fb_tiled) * ((bpp + 1) / 8); + + height = ALIGN(mode_cmd->height, 8); + size = mode_cmd->pitches[0] * height; + aligned_size = ALIGN(size, PAGE_SIZE); + ret = amdgpu_gem_object_create(adev, aligned_size, 0, + AMDGPU_GEM_DOMAIN_VRAM, + 0, true, + &gobj); + if (ret) { + printk(KERN_ERR "failed to allocate framebuffer (%d)\n", + aligned_size); + return -ENOMEM; + } + rbo = gem_to_amdgpu_bo(gobj); + + if (fb_tiled) + tiling_flags = AMDGPU_TILING_MACRO; + +#ifdef __BIG_ENDIAN + switch (bpp) { + case 32: + tiling_flags |= AMDGPU_TILING_SWAP_32BIT; + break; + case 16: + tiling_flags |= AMDGPU_TILING_SWAP_16BIT; + default: + break; + } +#endif + + ret = amdgpu_bo_reserve(rbo, false); + if (unlikely(ret != 0)) + goto out_unref; + + if (tiling_flags) { + ret = amdgpu_bo_set_tiling_flags(rbo, + tiling_flags | AMDGPU_TILING_SURFACE); + if (ret) + dev_err(adev->dev, "FB failed to set tiling flags\n"); + } + + + ret = amdgpu_bo_pin_restricted(rbo, AMDGPU_GEM_DOMAIN_VRAM, 0, NULL); + if (ret) { + amdgpu_bo_unreserve(rbo); + goto out_unref; + } + ret = amdgpu_bo_kmap(rbo, NULL); + amdgpu_bo_unreserve(rbo); + if (ret) { + goto out_unref; + } + + *gobj_p = gobj; + return 0; +out_unref: + amdgpufb_destroy_pinned_object(gobj); + *gobj_p = NULL; + return ret; +} + +static int amdgpufb_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct amdgpu_fbdev *rfbdev = (struct amdgpu_fbdev *)helper; + struct amdgpu_device *adev = rfbdev->adev; + struct fb_info *info; + struct drm_framebuffer *fb = NULL; + struct drm_mode_fb_cmd2 mode_cmd; + struct drm_gem_object *gobj = NULL; + struct amdgpu_bo *rbo = NULL; + struct device *device = &adev->pdev->dev; + int ret; + unsigned long tmp; + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + + if (sizes->surface_bpp == 24) + sizes->surface_bpp = 32; + + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + ret = amdgpufb_create_pinned_object(rfbdev, &mode_cmd, &gobj); + if (ret) { + DRM_ERROR("failed to create fbcon object %d\n", ret); + return ret; + } + + rbo = gem_to_amdgpu_bo(gobj); + + /* okay we have an object now allocate the framebuffer */ + info = framebuffer_alloc(0, device); + if (info == NULL) { + ret = -ENOMEM; + goto out_unref; + } + + info->par = rfbdev; + + ret = amdgpu_framebuffer_init(adev->ddev, &rfbdev->rfb, &mode_cmd, gobj); + if (ret) { + DRM_ERROR("failed to initialize framebuffer %d\n", ret); + goto out_unref; + } + + fb = &rfbdev->rfb.base; + + /* setup helper */ + rfbdev->helper.fb = fb; + rfbdev->helper.fbdev = info; + + memset_io(rbo->kptr, 0x0, amdgpu_bo_size(rbo)); + + strcpy(info->fix.id, "amdgpudrmfb"); + + drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); + + info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT; + info->fbops = &amdgpufb_ops; + + tmp = amdgpu_bo_gpu_offset(rbo) - adev->mc.vram_start; + info->fix.smem_start = adev->mc.aper_base + tmp; + info->fix.smem_len = amdgpu_bo_size(rbo); + info->screen_base = rbo->kptr; + info->screen_size = amdgpu_bo_size(rbo); + + drm_fb_helper_fill_var(info, &rfbdev->helper, sizes->fb_width, sizes->fb_height); + + /* setup aperture base/size for vesafb takeover */ + info->apertures = alloc_apertures(1); + if (!info->apertures) { + ret = -ENOMEM; + goto out_unref; + } + info->apertures->ranges[0].base = adev->ddev->mode_config.fb_base; + info->apertures->ranges[0].size = adev->mc.aper_size; + + /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ + + if (info->screen_base == NULL) { + ret = -ENOSPC; + goto out_unref; + } + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) { + ret = -ENOMEM; + goto out_unref; + } + + DRM_INFO("fb mappable at 0x%lX\n", info->fix.smem_start); + DRM_INFO("vram apper at 0x%lX\n", (unsigned long)adev->mc.aper_base); + DRM_INFO("size %lu\n", (unsigned long)amdgpu_bo_size(rbo)); + DRM_INFO("fb depth is %d\n", fb->depth); + DRM_INFO(" pitch is %d\n", fb->pitches[0]); + + vga_switcheroo_client_fb_set(adev->ddev->pdev, info); + return 0; + +out_unref: + if (rbo) { + + } + if (fb && ret) { + drm_gem_object_unreference(gobj); + drm_framebuffer_unregister_private(fb); + drm_framebuffer_cleanup(fb); + kfree(fb); + } + return ret; +} + +void amdgpu_fb_output_poll_changed(struct amdgpu_device *adev) +{ + if (adev->mode_info.rfbdev) + drm_fb_helper_hotplug_event(&adev->mode_info.rfbdev->helper); +} + +static int amdgpu_fbdev_destroy(struct drm_device *dev, struct amdgpu_fbdev *rfbdev) +{ + struct fb_info *info; + struct amdgpu_framebuffer *rfb = &rfbdev->rfb; + + if (rfbdev->helper.fbdev) { + info = rfbdev->helper.fbdev; + + unregister_framebuffer(info); + if (info->cmap.len) + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + + if (rfb->obj) { + amdgpufb_destroy_pinned_object(rfb->obj); + rfb->obj = NULL; + } + drm_fb_helper_fini(&rfbdev->helper); + drm_framebuffer_unregister_private(&rfb->base); + drm_framebuffer_cleanup(&rfb->base); + + return 0; +} + +/** Sets the color ramps on behalf of fbcon */ +static void amdgpu_crtc_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green, + u16 blue, int regno) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + + amdgpu_crtc->lut_r[regno] = red >> 6; + amdgpu_crtc->lut_g[regno] = green >> 6; + amdgpu_crtc->lut_b[regno] = blue >> 6; +} + +/** Gets the color ramps on behalf of fbcon */ +static void amdgpu_crtc_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green, + u16 *blue, int regno) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + + *red = amdgpu_crtc->lut_r[regno] << 6; + *green = amdgpu_crtc->lut_g[regno] << 6; + *blue = amdgpu_crtc->lut_b[regno] << 6; +} + +static const struct drm_fb_helper_funcs amdgpu_fb_helper_funcs = { + .gamma_set = amdgpu_crtc_fb_gamma_set, + .gamma_get = amdgpu_crtc_fb_gamma_get, + .fb_probe = amdgpufb_create, +}; + +int amdgpu_fbdev_init(struct amdgpu_device *adev) +{ + struct amdgpu_fbdev *rfbdev; + int bpp_sel = 32; + int ret; + + /* don't init fbdev on hw without DCE */ + if (!adev->mode_info.mode_config_initialized) + return 0; + + /* select 8 bpp console on low vram cards */ + if (adev->mc.real_vram_size <= (32*1024*1024)) + bpp_sel = 8; + + rfbdev = kzalloc(sizeof(struct amdgpu_fbdev), GFP_KERNEL); + if (!rfbdev) + return -ENOMEM; + + rfbdev->adev = adev; + adev->mode_info.rfbdev = rfbdev; + + drm_fb_helper_prepare(adev->ddev, &rfbdev->helper, + &amdgpu_fb_helper_funcs); + + ret = drm_fb_helper_init(adev->ddev, &rfbdev->helper, + adev->mode_info.num_crtc, + AMDGPUFB_CONN_LIMIT); + if (ret) { + kfree(rfbdev); + return ret; + } + + drm_fb_helper_single_add_all_connectors(&rfbdev->helper); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(adev->ddev); + + drm_fb_helper_initial_config(&rfbdev->helper, bpp_sel); + return 0; +} + +void amdgpu_fbdev_fini(struct amdgpu_device *adev) +{ + if (!adev->mode_info.rfbdev) + return; + + amdgpu_fbdev_destroy(adev->ddev, adev->mode_info.rfbdev); + kfree(adev->mode_info.rfbdev); + adev->mode_info.rfbdev = NULL; +} + +void amdgpu_fbdev_set_suspend(struct amdgpu_device *adev, int state) +{ + if (adev->mode_info.rfbdev) + fb_set_suspend(adev->mode_info.rfbdev->helper.fbdev, state); +} + +int amdgpu_fbdev_total_size(struct amdgpu_device *adev) +{ + struct amdgpu_bo *robj; + int size = 0; + + if (!adev->mode_info.rfbdev) + return 0; + + robj = gem_to_amdgpu_bo(adev->mode_info.rfbdev->rfb.obj); + size += amdgpu_bo_size(robj); + return size; +} + +bool amdgpu_fbdev_robj_is_fb(struct amdgpu_device *adev, struct amdgpu_bo *robj) +{ + if (!adev->mode_info.rfbdev) + return false; + if (robj == gem_to_amdgpu_bo(adev->mode_info.rfbdev->rfb.obj)) + return true; + return false; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c new file mode 100644 index 000000000000..fc63855ed517 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -0,0 +1,1139 @@ +/* + * Copyright 2009 Jerome Glisse. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Jerome Glisse <glisse@freedesktop.org> + * Dave Airlie + */ +#include <linux/seq_file.h> +#include <linux/atomic.h> +#include <linux/wait.h> +#include <linux/kref.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <drm/drmP.h> +#include "amdgpu.h" +#include "amdgpu_trace.h" + +/* + * Fences + * Fences mark an event in the GPUs pipeline and are used + * for GPU/CPU synchronization. When the fence is written, + * it is expected that all buffers associated with that fence + * are no longer in use by the associated ring on the GPU and + * that the the relevant GPU caches have been flushed. + */ + +/** + * amdgpu_fence_write - write a fence value + * + * @ring: ring the fence is associated with + * @seq: sequence number to write + * + * Writes a fence value to memory (all asics). + */ +static void amdgpu_fence_write(struct amdgpu_ring *ring, u32 seq) +{ + struct amdgpu_fence_driver *drv = &ring->fence_drv; + + if (drv->cpu_addr) + *drv->cpu_addr = cpu_to_le32(seq); +} + +/** + * amdgpu_fence_read - read a fence value + * + * @ring: ring the fence is associated with + * + * Reads a fence value from memory (all asics). + * Returns the value of the fence read from memory. + */ +static u32 amdgpu_fence_read(struct amdgpu_ring *ring) +{ + struct amdgpu_fence_driver *drv = &ring->fence_drv; + u32 seq = 0; + + if (drv->cpu_addr) + seq = le32_to_cpu(*drv->cpu_addr); + else + seq = lower_32_bits(atomic64_read(&drv->last_seq)); + + return seq; +} + +/** + * amdgpu_fence_schedule_check - schedule lockup check + * + * @ring: pointer to struct amdgpu_ring + * + * Queues a delayed work item to check for lockups. + */ +static void amdgpu_fence_schedule_check(struct amdgpu_ring *ring) +{ + /* + * Do not reset the timer here with mod_delayed_work, + * this can livelock in an interaction with TTM delayed destroy. + */ + queue_delayed_work(system_power_efficient_wq, + &ring->fence_drv.lockup_work, + AMDGPU_FENCE_JIFFIES_TIMEOUT); +} + +/** + * amdgpu_fence_emit - emit a fence on the requested ring + * + * @ring: ring the fence is associated with + * @owner: creator of the fence + * @fence: amdgpu fence object + * + * Emits a fence command on the requested ring (all asics). + * Returns 0 on success, -ENOMEM on failure. + */ +int amdgpu_fence_emit(struct amdgpu_ring *ring, void *owner, + struct amdgpu_fence **fence) +{ + struct amdgpu_device *adev = ring->adev; + + /* we are protected by the ring emission mutex */ + *fence = kmalloc(sizeof(struct amdgpu_fence), GFP_KERNEL); + if ((*fence) == NULL) { + return -ENOMEM; + } + (*fence)->seq = ++ring->fence_drv.sync_seq[ring->idx]; + (*fence)->ring = ring; + (*fence)->owner = owner; + fence_init(&(*fence)->base, &amdgpu_fence_ops, + &adev->fence_queue.lock, adev->fence_context + ring->idx, + (*fence)->seq); + amdgpu_ring_emit_fence(ring, ring->fence_drv.gpu_addr, (*fence)->seq, false); + trace_amdgpu_fence_emit(ring->adev->ddev, ring->idx, (*fence)->seq); + return 0; +} + +/** + * amdgpu_fence_check_signaled - callback from fence_queue + * + * this function is called with fence_queue lock held, which is also used + * for the fence locking itself, so unlocked variants are used for + * fence_signal, and remove_wait_queue. + */ +static int amdgpu_fence_check_signaled(wait_queue_t *wait, unsigned mode, int flags, void *key) +{ + struct amdgpu_fence *fence; + struct amdgpu_device *adev; + u64 seq; + int ret; + + fence = container_of(wait, struct amdgpu_fence, fence_wake); + adev = fence->ring->adev; + + /* + * We cannot use amdgpu_fence_process here because we're already + * in the waitqueue, in a call from wake_up_all. + */ + seq = atomic64_read(&fence->ring->fence_drv.last_seq); + if (seq >= fence->seq) { + ret = fence_signal_locked(&fence->base); + if (!ret) + FENCE_TRACE(&fence->base, "signaled from irq context\n"); + else + FENCE_TRACE(&fence->base, "was already signaled\n"); + + amdgpu_irq_put(adev, fence->ring->fence_drv.irq_src, + fence->ring->fence_drv.irq_type); + __remove_wait_queue(&adev->fence_queue, &fence->fence_wake); + fence_put(&fence->base); + } else + FENCE_TRACE(&fence->base, "pending\n"); + return 0; +} + +/** + * amdgpu_fence_activity - check for fence activity + * + * @ring: pointer to struct amdgpu_ring + * + * Checks the current fence value and calculates the last + * signalled fence value. Returns true if activity occured + * on the ring, and the fence_queue should be waken up. + */ +static bool amdgpu_fence_activity(struct amdgpu_ring *ring) +{ + uint64_t seq, last_seq, last_emitted; + unsigned count_loop = 0; + bool wake = false; + + /* Note there is a scenario here for an infinite loop but it's + * very unlikely to happen. For it to happen, the current polling + * process need to be interrupted by another process and another + * process needs to update the last_seq btw the atomic read and + * xchg of the current process. + * + * More over for this to go in infinite loop there need to be + * continuously new fence signaled ie radeon_fence_read needs + * to return a different value each time for both the currently + * polling process and the other process that xchg the last_seq + * btw atomic read and xchg of the current process. And the + * value the other process set as last seq must be higher than + * the seq value we just read. Which means that current process + * need to be interrupted after radeon_fence_read and before + * atomic xchg. + * + * To be even more safe we count the number of time we loop and + * we bail after 10 loop just accepting the fact that we might + * have temporarly set the last_seq not to the true real last + * seq but to an older one. + */ + last_seq = atomic64_read(&ring->fence_drv.last_seq); + do { + last_emitted = ring->fence_drv.sync_seq[ring->idx]; + seq = amdgpu_fence_read(ring); + seq |= last_seq & 0xffffffff00000000LL; + if (seq < last_seq) { + seq &= 0xffffffff; + seq |= last_emitted & 0xffffffff00000000LL; + } + + if (seq <= last_seq || seq > last_emitted) { + break; + } + /* If we loop over we don't want to return without + * checking if a fence is signaled as it means that the + * seq we just read is different from the previous on. + */ + wake = true; + last_seq = seq; + if ((count_loop++) > 10) { + /* We looped over too many time leave with the + * fact that we might have set an older fence + * seq then the current real last seq as signaled + * by the hw. + */ + break; + } + } while (atomic64_xchg(&ring->fence_drv.last_seq, seq) > seq); + + if (seq < last_emitted) + amdgpu_fence_schedule_check(ring); + + return wake; +} + +/** + * amdgpu_fence_check_lockup - check for hardware lockup + * + * @work: delayed work item + * + * Checks for fence activity and if there is none probe + * the hardware if a lockup occured. + */ +static void amdgpu_fence_check_lockup(struct work_struct *work) +{ + struct amdgpu_fence_driver *fence_drv; + struct amdgpu_ring *ring; + + fence_drv = container_of(work, struct amdgpu_fence_driver, + lockup_work.work); + ring = fence_drv->ring; + + if (!down_read_trylock(&ring->adev->exclusive_lock)) { + /* just reschedule the check if a reset is going on */ + amdgpu_fence_schedule_check(ring); + return; + } + + if (fence_drv->delayed_irq && ring->adev->ddev->irq_enabled) { + fence_drv->delayed_irq = false; + amdgpu_irq_update(ring->adev, fence_drv->irq_src, + fence_drv->irq_type); + } + + if (amdgpu_fence_activity(ring)) + wake_up_all(&ring->adev->fence_queue); + else if (amdgpu_ring_is_lockup(ring)) { + /* good news we believe it's a lockup */ + dev_warn(ring->adev->dev, "GPU lockup (current fence id " + "0x%016llx last fence id 0x%016llx on ring %d)\n", + (uint64_t)atomic64_read(&fence_drv->last_seq), + fence_drv->sync_seq[ring->idx], ring->idx); + + /* remember that we need an reset */ + ring->adev->needs_reset = true; + wake_up_all(&ring->adev->fence_queue); + } + up_read(&ring->adev->exclusive_lock); +} + +/** + * amdgpu_fence_process - process a fence + * + * @adev: amdgpu_device pointer + * @ring: ring index the fence is associated with + * + * Checks the current fence value and wakes the fence queue + * if the sequence number has increased (all asics). + */ +void amdgpu_fence_process(struct amdgpu_ring *ring) +{ + uint64_t seq, last_seq, last_emitted; + unsigned count_loop = 0; + bool wake = false; + + /* Note there is a scenario here for an infinite loop but it's + * very unlikely to happen. For it to happen, the current polling + * process need to be interrupted by another process and another + * process needs to update the last_seq btw the atomic read and + * xchg of the current process. + * + * More over for this to go in infinite loop there need to be + * continuously new fence signaled ie amdgpu_fence_read needs + * to return a different value each time for both the currently + * polling process and the other process that xchg the last_seq + * btw atomic read and xchg of the current process. And the + * value the other process set as last seq must be higher than + * the seq value we just read. Which means that current process + * need to be interrupted after amdgpu_fence_read and before + * atomic xchg. + * + * To be even more safe we count the number of time we loop and + * we bail after 10 loop just accepting the fact that we might + * have temporarly set the last_seq not to the true real last + * seq but to an older one. + */ + last_seq = atomic64_read(&ring->fence_drv.last_seq); + do { + last_emitted = ring->fence_drv.sync_seq[ring->idx]; + seq = amdgpu_fence_read(ring); + seq |= last_seq & 0xffffffff00000000LL; + if (seq < last_seq) { + seq &= 0xffffffff; + seq |= last_emitted & 0xffffffff00000000LL; + } + + if (seq <= last_seq || seq > last_emitted) { + break; + } + /* If we loop over we don't want to return without + * checking if a fence is signaled as it means that the + * seq we just read is different from the previous on. + */ + wake = true; + last_seq = seq; + if ((count_loop++) > 10) { + /* We looped over too many time leave with the + * fact that we might have set an older fence + * seq then the current real last seq as signaled + * by the hw. + */ + break; + } + } while (atomic64_xchg(&ring->fence_drv.last_seq, seq) > seq); + + if (wake) + wake_up_all(&ring->adev->fence_queue); +} + +/** + * amdgpu_fence_seq_signaled - check if a fence sequence number has signaled + * + * @ring: ring the fence is associated with + * @seq: sequence number + * + * Check if the last signaled fence sequnce number is >= the requested + * sequence number (all asics). + * Returns true if the fence has signaled (current fence value + * is >= requested value) or false if it has not (current fence + * value is < the requested value. Helper function for + * amdgpu_fence_signaled(). + */ +static bool amdgpu_fence_seq_signaled(struct amdgpu_ring *ring, u64 seq) +{ + if (atomic64_read(&ring->fence_drv.last_seq) >= seq) + return true; + + /* poll new last sequence at least once */ + amdgpu_fence_process(ring); + if (atomic64_read(&ring->fence_drv.last_seq) >= seq) + return true; + + return false; +} + +static bool amdgpu_fence_is_signaled(struct fence *f) +{ + struct amdgpu_fence *fence = to_amdgpu_fence(f); + struct amdgpu_ring *ring = fence->ring; + struct amdgpu_device *adev = ring->adev; + + if (atomic64_read(&ring->fence_drv.last_seq) >= fence->seq) + return true; + + if (down_read_trylock(&adev->exclusive_lock)) { + amdgpu_fence_process(ring); + up_read(&adev->exclusive_lock); + + if (atomic64_read(&ring->fence_drv.last_seq) >= fence->seq) + return true; + } + return false; +} + +/** + * amdgpu_fence_enable_signaling - enable signalling on fence + * @fence: fence + * + * This function is called with fence_queue lock held, and adds a callback + * to fence_queue that checks if this fence is signaled, and if so it + * signals the fence and removes itself. + */ +static bool amdgpu_fence_enable_signaling(struct fence *f) +{ + struct amdgpu_fence *fence = to_amdgpu_fence(f); + struct amdgpu_ring *ring = fence->ring; + struct amdgpu_device *adev = ring->adev; + + if (atomic64_read(&ring->fence_drv.last_seq) >= fence->seq) + return false; + + if (down_read_trylock(&adev->exclusive_lock)) { + amdgpu_irq_get(adev, ring->fence_drv.irq_src, + ring->fence_drv.irq_type); + if (amdgpu_fence_activity(ring)) + wake_up_all_locked(&adev->fence_queue); + + /* did fence get signaled after we enabled the sw irq? */ + if (atomic64_read(&ring->fence_drv.last_seq) >= fence->seq) { + amdgpu_irq_put(adev, ring->fence_drv.irq_src, + ring->fence_drv.irq_type); + up_read(&adev->exclusive_lock); + return false; + } + + up_read(&adev->exclusive_lock); + } else { + /* we're probably in a lockup, lets not fiddle too much */ + if (amdgpu_irq_get_delayed(adev, ring->fence_drv.irq_src, + ring->fence_drv.irq_type)) + ring->fence_drv.delayed_irq = true; + amdgpu_fence_schedule_check(ring); + } + + fence->fence_wake.flags = 0; + fence->fence_wake.private = NULL; + fence->fence_wake.func = amdgpu_fence_check_signaled; + __add_wait_queue(&adev->fence_queue, &fence->fence_wake); + fence_get(f); + FENCE_TRACE(&fence->base, "armed on ring %i!\n", ring->idx); + return true; +} + +/** + * amdgpu_fence_signaled - check if a fence has signaled + * + * @fence: amdgpu fence object + * + * Check if the requested fence has signaled (all asics). + * Returns true if the fence has signaled or false if it has not. + */ +bool amdgpu_fence_signaled(struct amdgpu_fence *fence) +{ + if (!fence) + return true; + + if (fence->seq == AMDGPU_FENCE_SIGNALED_SEQ) + return true; + + if (amdgpu_fence_seq_signaled(fence->ring, fence->seq)) { + fence->seq = AMDGPU_FENCE_SIGNALED_SEQ; + if (!fence_signal(&fence->base)) + FENCE_TRACE(&fence->base, "signaled from amdgpu_fence_signaled\n"); + return true; + } + + return false; +} + +/** + * amdgpu_fence_any_seq_signaled - check if any sequence number is signaled + * + * @adev: amdgpu device pointer + * @seq: sequence numbers + * + * Check if the last signaled fence sequnce number is >= the requested + * sequence number (all asics). + * Returns true if any has signaled (current value is >= requested value) + * or false if it has not. Helper function for amdgpu_fence_wait_seq. + */ +static bool amdgpu_fence_any_seq_signaled(struct amdgpu_device *adev, u64 *seq) +{ + unsigned i; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + if (!adev->rings[i] || !seq[i]) + continue; + + if (amdgpu_fence_seq_signaled(adev->rings[i], seq[i])) + return true; + } + + return false; +} + +/** + * amdgpu_fence_wait_seq_timeout - wait for a specific sequence numbers + * + * @adev: amdgpu device pointer + * @target_seq: sequence number(s) we want to wait for + * @intr: use interruptable sleep + * @timeout: maximum time to wait, or MAX_SCHEDULE_TIMEOUT for infinite wait + * + * Wait for the requested sequence number(s) to be written by any ring + * (all asics). Sequnce number array is indexed by ring id. + * @intr selects whether to use interruptable (true) or non-interruptable + * (false) sleep when waiting for the sequence number. Helper function + * for amdgpu_fence_wait_*(). + * Returns remaining time if the sequence number has passed, 0 when + * the wait timeout, or an error for all other cases. + * -EDEADLK is returned when a GPU lockup has been detected. + */ +long amdgpu_fence_wait_seq_timeout(struct amdgpu_device *adev, u64 *target_seq, + bool intr, long timeout) +{ + uint64_t last_seq[AMDGPU_MAX_RINGS]; + bool signaled; + int i, r; + + while (!amdgpu_fence_any_seq_signaled(adev, target_seq)) { + + /* Save current sequence values, used to check for GPU lockups */ + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + + if (!ring || !target_seq[i]) + continue; + + last_seq[i] = atomic64_read(&ring->fence_drv.last_seq); + trace_amdgpu_fence_wait_begin(adev->ddev, i, target_seq[i]); + amdgpu_irq_get(adev, ring->fence_drv.irq_src, + ring->fence_drv.irq_type); + } + + if (intr) { + r = wait_event_interruptible_timeout(adev->fence_queue, ( + (signaled = amdgpu_fence_any_seq_signaled(adev, target_seq)) + || adev->needs_reset), AMDGPU_FENCE_JIFFIES_TIMEOUT); + } else { + r = wait_event_timeout(adev->fence_queue, ( + (signaled = amdgpu_fence_any_seq_signaled(adev, target_seq)) + || adev->needs_reset), AMDGPU_FENCE_JIFFIES_TIMEOUT); + } + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + + if (!ring || !target_seq[i]) + continue; + + amdgpu_irq_put(adev, ring->fence_drv.irq_src, + ring->fence_drv.irq_type); + trace_amdgpu_fence_wait_end(adev->ddev, i, target_seq[i]); + } + + if (unlikely(r < 0)) + return r; + + if (unlikely(!signaled)) { + + if (adev->needs_reset) + return -EDEADLK; + + /* we were interrupted for some reason and fence + * isn't signaled yet, resume waiting */ + if (r) + continue; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + + if (!ring || !target_seq[i]) + continue; + + if (last_seq[i] != atomic64_read(&ring->fence_drv.last_seq)) + break; + } + + if (i != AMDGPU_MAX_RINGS) + continue; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + if (!adev->rings[i] || !target_seq[i]) + continue; + + if (amdgpu_ring_is_lockup(adev->rings[i])) + break; + } + + if (i < AMDGPU_MAX_RINGS) { + /* good news we believe it's a lockup */ + dev_warn(adev->dev, "GPU lockup (waiting for " + "0x%016llx last fence id 0x%016llx on" + " ring %d)\n", + target_seq[i], last_seq[i], i); + + /* remember that we need an reset */ + adev->needs_reset = true; + wake_up_all(&adev->fence_queue); + return -EDEADLK; + } + + if (timeout < MAX_SCHEDULE_TIMEOUT) { + timeout -= AMDGPU_FENCE_JIFFIES_TIMEOUT; + if (timeout <= 0) { + return 0; + } + } + } + } + return timeout; +} + +/** + * amdgpu_fence_wait - wait for a fence to signal + * + * @fence: amdgpu fence object + * @intr: use interruptable sleep + * + * Wait for the requested fence to signal (all asics). + * @intr selects whether to use interruptable (true) or non-interruptable + * (false) sleep when waiting for the fence. + * Returns 0 if the fence has passed, error for all other cases. + */ +int amdgpu_fence_wait(struct amdgpu_fence *fence, bool intr) +{ + uint64_t seq[AMDGPU_MAX_RINGS] = {}; + long r; + + seq[fence->ring->idx] = fence->seq; + if (seq[fence->ring->idx] == AMDGPU_FENCE_SIGNALED_SEQ) + return 0; + + r = amdgpu_fence_wait_seq_timeout(fence->ring->adev, seq, intr, MAX_SCHEDULE_TIMEOUT); + if (r < 0) { + return r; + } + + fence->seq = AMDGPU_FENCE_SIGNALED_SEQ; + r = fence_signal(&fence->base); + if (!r) + FENCE_TRACE(&fence->base, "signaled from fence_wait\n"); + return 0; +} + +/** + * amdgpu_fence_wait_any - wait for a fence to signal on any ring + * + * @adev: amdgpu device pointer + * @fences: amdgpu fence object(s) + * @intr: use interruptable sleep + * + * Wait for any requested fence to signal (all asics). Fence + * array is indexed by ring id. @intr selects whether to use + * interruptable (true) or non-interruptable (false) sleep when + * waiting for the fences. Used by the suballocator. + * Returns 0 if any fence has passed, error for all other cases. + */ +int amdgpu_fence_wait_any(struct amdgpu_device *adev, + struct amdgpu_fence **fences, + bool intr) +{ + uint64_t seq[AMDGPU_MAX_RINGS]; + unsigned i, num_rings = 0; + long r; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + seq[i] = 0; + + if (!fences[i]) { + continue; + } + + seq[i] = fences[i]->seq; + ++num_rings; + + /* test if something was allready signaled */ + if (seq[i] == AMDGPU_FENCE_SIGNALED_SEQ) + return 0; + } + + /* nothing to wait for ? */ + if (num_rings == 0) + return -ENOENT; + + r = amdgpu_fence_wait_seq_timeout(adev, seq, intr, MAX_SCHEDULE_TIMEOUT); + if (r < 0) { + return r; + } + return 0; +} + +/** + * amdgpu_fence_wait_next - wait for the next fence to signal + * + * @adev: amdgpu device pointer + * @ring: ring index the fence is associated with + * + * Wait for the next fence on the requested ring to signal (all asics). + * Returns 0 if the next fence has passed, error for all other cases. + * Caller must hold ring lock. + */ +int amdgpu_fence_wait_next(struct amdgpu_ring *ring) +{ + uint64_t seq[AMDGPU_MAX_RINGS] = {}; + long r; + + seq[ring->idx] = atomic64_read(&ring->fence_drv.last_seq) + 1ULL; + if (seq[ring->idx] >= ring->fence_drv.sync_seq[ring->idx]) { + /* nothing to wait for, last_seq is + already the last emited fence */ + return -ENOENT; + } + r = amdgpu_fence_wait_seq_timeout(ring->adev, seq, false, MAX_SCHEDULE_TIMEOUT); + if (r < 0) + return r; + return 0; +} + +/** + * amdgpu_fence_wait_empty - wait for all fences to signal + * + * @adev: amdgpu device pointer + * @ring: ring index the fence is associated with + * + * Wait for all fences on the requested ring to signal (all asics). + * Returns 0 if the fences have passed, error for all other cases. + * Caller must hold ring lock. + */ +int amdgpu_fence_wait_empty(struct amdgpu_ring *ring) +{ + struct amdgpu_device *adev = ring->adev; + uint64_t seq[AMDGPU_MAX_RINGS] = {}; + long r; + + seq[ring->idx] = ring->fence_drv.sync_seq[ring->idx]; + if (!seq[ring->idx]) + return 0; + + r = amdgpu_fence_wait_seq_timeout(adev, seq, false, MAX_SCHEDULE_TIMEOUT); + if (r < 0) { + if (r == -EDEADLK) + return -EDEADLK; + + dev_err(adev->dev, "error waiting for ring[%d] to become idle (%ld)\n", + ring->idx, r); + } + return 0; +} + +/** + * amdgpu_fence_ref - take a ref on a fence + * + * @fence: amdgpu fence object + * + * Take a reference on a fence (all asics). + * Returns the fence. + */ +struct amdgpu_fence *amdgpu_fence_ref(struct amdgpu_fence *fence) +{ + fence_get(&fence->base); + return fence; +} + +/** + * amdgpu_fence_unref - remove a ref on a fence + * + * @fence: amdgpu fence object + * + * Remove a reference on a fence (all asics). + */ +void amdgpu_fence_unref(struct amdgpu_fence **fence) +{ + struct amdgpu_fence *tmp = *fence; + + *fence = NULL; + if (tmp) + fence_put(&tmp->base); +} + +/** + * amdgpu_fence_count_emitted - get the count of emitted fences + * + * @ring: ring the fence is associated with + * + * Get the number of fences emitted on the requested ring (all asics). + * Returns the number of emitted fences on the ring. Used by the + * dynpm code to ring track activity. + */ +unsigned amdgpu_fence_count_emitted(struct amdgpu_ring *ring) +{ + uint64_t emitted; + + /* We are not protected by ring lock when reading the last sequence + * but it's ok to report slightly wrong fence count here. + */ + amdgpu_fence_process(ring); + emitted = ring->fence_drv.sync_seq[ring->idx] + - atomic64_read(&ring->fence_drv.last_seq); + /* to avoid 32bits warp around */ + if (emitted > 0x10000000) + emitted = 0x10000000; + + return (unsigned)emitted; +} + +/** + * amdgpu_fence_need_sync - do we need a semaphore + * + * @fence: amdgpu fence object + * @dst_ring: which ring to check against + * + * Check if the fence needs to be synced against another ring + * (all asics). If so, we need to emit a semaphore. + * Returns true if we need to sync with another ring, false if + * not. + */ +bool amdgpu_fence_need_sync(struct amdgpu_fence *fence, + struct amdgpu_ring *dst_ring) +{ + struct amdgpu_fence_driver *fdrv; + + if (!fence) + return false; + + if (fence->ring == dst_ring) + return false; + + /* we are protected by the ring mutex */ + fdrv = &dst_ring->fence_drv; + if (fence->seq <= fdrv->sync_seq[fence->ring->idx]) + return false; + + return true; +} + +/** + * amdgpu_fence_note_sync - record the sync point + * + * @fence: amdgpu fence object + * @dst_ring: which ring to check against + * + * Note the sequence number at which point the fence will + * be synced with the requested ring (all asics). + */ +void amdgpu_fence_note_sync(struct amdgpu_fence *fence, + struct amdgpu_ring *dst_ring) +{ + struct amdgpu_fence_driver *dst, *src; + unsigned i; + + if (!fence) + return; + + if (fence->ring == dst_ring) + return; + + /* we are protected by the ring mutex */ + src = &fence->ring->fence_drv; + dst = &dst_ring->fence_drv; + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + if (i == dst_ring->idx) + continue; + + dst->sync_seq[i] = max(dst->sync_seq[i], src->sync_seq[i]); + } +} + +/** + * amdgpu_fence_driver_start_ring - make the fence driver + * ready for use on the requested ring. + * + * @ring: ring to start the fence driver on + * @irq_src: interrupt source to use for this ring + * @irq_type: interrupt type to use for this ring + * + * Make the fence driver ready for processing (all asics). + * Not all asics have all rings, so each asic will only + * start the fence driver on the rings it has. + * Returns 0 for success, errors for failure. + */ +int amdgpu_fence_driver_start_ring(struct amdgpu_ring *ring, + struct amdgpu_irq_src *irq_src, + unsigned irq_type) +{ + struct amdgpu_device *adev = ring->adev; + uint64_t index; + + if (ring != &adev->uvd.ring) { + ring->fence_drv.cpu_addr = &adev->wb.wb[ring->fence_offs]; + ring->fence_drv.gpu_addr = adev->wb.gpu_addr + (ring->fence_offs * 4); + } else { + /* put fence directly behind firmware */ + index = ALIGN(adev->uvd.fw->size, 8); + ring->fence_drv.cpu_addr = adev->uvd.cpu_addr + index; + ring->fence_drv.gpu_addr = adev->uvd.gpu_addr + index; + } + amdgpu_fence_write(ring, atomic64_read(&ring->fence_drv.last_seq)); + ring->fence_drv.initialized = true; + ring->fence_drv.irq_src = irq_src; + ring->fence_drv.irq_type = irq_type; + dev_info(adev->dev, "fence driver on ring %d use gpu addr 0x%016llx, " + "cpu addr 0x%p\n", ring->idx, + ring->fence_drv.gpu_addr, ring->fence_drv.cpu_addr); + return 0; +} + +/** + * amdgpu_fence_driver_init_ring - init the fence driver + * for the requested ring. + * + * @ring: ring to init the fence driver on + * + * Init the fence driver for the requested ring (all asics). + * Helper function for amdgpu_fence_driver_init(). + */ +void amdgpu_fence_driver_init_ring(struct amdgpu_ring *ring) +{ + int i; + + ring->fence_drv.cpu_addr = NULL; + ring->fence_drv.gpu_addr = 0; + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) + ring->fence_drv.sync_seq[i] = 0; + + atomic64_set(&ring->fence_drv.last_seq, 0); + ring->fence_drv.initialized = false; + + INIT_DELAYED_WORK(&ring->fence_drv.lockup_work, + amdgpu_fence_check_lockup); + ring->fence_drv.ring = ring; +} + +/** + * amdgpu_fence_driver_init - init the fence driver + * for all possible rings. + * + * @adev: amdgpu device pointer + * + * Init the fence driver for all possible rings (all asics). + * Not all asics have all rings, so each asic will only + * start the fence driver on the rings it has using + * amdgpu_fence_driver_start_ring(). + * Returns 0 for success. + */ +int amdgpu_fence_driver_init(struct amdgpu_device *adev) +{ + init_waitqueue_head(&adev->fence_queue); + if (amdgpu_debugfs_fence_init(adev)) + dev_err(adev->dev, "fence debugfs file creation failed\n"); + + return 0; +} + +/** + * amdgpu_fence_driver_fini - tear down the fence driver + * for all possible rings. + * + * @adev: amdgpu device pointer + * + * Tear down the fence driver for all possible rings (all asics). + */ +void amdgpu_fence_driver_fini(struct amdgpu_device *adev) +{ + int i, r; + + mutex_lock(&adev->ring_lock); + for (i = 0; i < AMDGPU_MAX_RINGS; i++) { + struct amdgpu_ring *ring = adev->rings[i]; + if (!ring || !ring->fence_drv.initialized) + continue; + r = amdgpu_fence_wait_empty(ring); + if (r) { + /* no need to trigger GPU reset as we are unloading */ + amdgpu_fence_driver_force_completion(adev); + } + wake_up_all(&adev->fence_queue); + ring->fence_drv.initialized = false; + } + mutex_unlock(&adev->ring_lock); +} + +/** + * amdgpu_fence_driver_force_completion - force all fence waiter to complete + * + * @adev: amdgpu device pointer + * + * In case of GPU reset failure make sure no process keep waiting on fence + * that will never complete. + */ +void amdgpu_fence_driver_force_completion(struct amdgpu_device *adev) +{ + int i; + + for (i = 0; i < AMDGPU_MAX_RINGS; i++) { + struct amdgpu_ring *ring = adev->rings[i]; + if (!ring || !ring->fence_drv.initialized) + continue; + + amdgpu_fence_write(ring, ring->fence_drv.sync_seq[i]); + } +} + + +/* + * Fence debugfs + */ +#if defined(CONFIG_DEBUG_FS) +static int amdgpu_debugfs_fence_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct amdgpu_device *adev = dev->dev_private; + int i, j; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + if (!ring || !ring->fence_drv.initialized) + continue; + + amdgpu_fence_process(ring); + + seq_printf(m, "--- ring %d ---\n", i); + seq_printf(m, "Last signaled fence 0x%016llx\n", + (unsigned long long)atomic64_read(&ring->fence_drv.last_seq)); + seq_printf(m, "Last emitted 0x%016llx\n", + ring->fence_drv.sync_seq[i]); + + for (j = 0; j < AMDGPU_MAX_RINGS; ++j) { + struct amdgpu_ring *other = adev->rings[j]; + if (i != j && other && other->fence_drv.initialized) + seq_printf(m, "Last sync to ring %d 0x%016llx\n", + j, ring->fence_drv.sync_seq[j]); + } + } + return 0; +} + +static struct drm_info_list amdgpu_debugfs_fence_list[] = { + {"amdgpu_fence_info", &amdgpu_debugfs_fence_info, 0, NULL}, +}; +#endif + +int amdgpu_debugfs_fence_init(struct amdgpu_device *adev) +{ +#if defined(CONFIG_DEBUG_FS) + return amdgpu_debugfs_add_files(adev, amdgpu_debugfs_fence_list, 1); +#else + return 0; +#endif +} + +static const char *amdgpu_fence_get_driver_name(struct fence *fence) +{ + return "amdgpu"; +} + +static const char *amdgpu_fence_get_timeline_name(struct fence *f) +{ + struct amdgpu_fence *fence = to_amdgpu_fence(f); + return (const char *)fence->ring->name; +} + +static inline bool amdgpu_test_signaled(struct amdgpu_fence *fence) +{ + return test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->base.flags); +} + +struct amdgpu_wait_cb { + struct fence_cb base; + struct task_struct *task; +}; + +static void amdgpu_fence_wait_cb(struct fence *fence, struct fence_cb *cb) +{ + struct amdgpu_wait_cb *wait = + container_of(cb, struct amdgpu_wait_cb, base); + wake_up_process(wait->task); +} + +static signed long amdgpu_fence_default_wait(struct fence *f, bool intr, + signed long t) +{ + struct amdgpu_fence *fence = to_amdgpu_fence(f); + struct amdgpu_device *adev = fence->ring->adev; + struct amdgpu_wait_cb cb; + + cb.task = current; + + if (fence_add_callback(f, &cb.base, amdgpu_fence_wait_cb)) + return t; + + while (t > 0) { + if (intr) + set_current_state(TASK_INTERRUPTIBLE); + else + set_current_state(TASK_UNINTERRUPTIBLE); + + /* + * amdgpu_test_signaled must be called after + * set_current_state to prevent a race with wake_up_process + */ + if (amdgpu_test_signaled(fence)) + break; + + if (adev->needs_reset) { + t = -EDEADLK; + break; + } + + t = schedule_timeout(t); + + if (t > 0 && intr && signal_pending(current)) + t = -ERESTARTSYS; + } + + __set_current_state(TASK_RUNNING); + fence_remove_callback(f, &cb.base); + + return t; +} + +const struct fence_ops amdgpu_fence_ops = { + .get_driver_name = amdgpu_fence_get_driver_name, + .get_timeline_name = amdgpu_fence_get_timeline_name, + .enable_signaling = amdgpu_fence_enable_signaling, + .signaled = amdgpu_fence_is_signaled, + .wait = amdgpu_fence_default_wait, + .release = NULL, +}; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gart.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gart.c new file mode 100644 index 000000000000..e02db0b2e839 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gart.c @@ -0,0 +1,371 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +/* + * GART + * The GART (Graphics Aperture Remapping Table) is an aperture + * in the GPU's address space. System pages can be mapped into + * the aperture and look like contiguous pages from the GPU's + * perspective. A page table maps the pages in the aperture + * to the actual backing pages in system memory. + * + * Radeon GPUs support both an internal GART, as described above, + * and AGP. AGP works similarly, but the GART table is configured + * and maintained by the northbridge rather than the driver. + * Radeon hw has a separate AGP aperture that is programmed to + * point to the AGP aperture provided by the northbridge and the + * requests are passed through to the northbridge aperture. + * Both AGP and internal GART can be used at the same time, however + * that is not currently supported by the driver. + * + * This file handles the common internal GART management. + */ + +/* + * Common GART table functions. + */ +/** + * amdgpu_gart_table_ram_alloc - allocate system ram for gart page table + * + * @adev: amdgpu_device pointer + * + * Allocate system memory for GART page table + * (r1xx-r3xx, non-pcie r4xx, rs400). These asics require the + * gart table to be in system memory. + * Returns 0 for success, -ENOMEM for failure. + */ +int amdgpu_gart_table_ram_alloc(struct amdgpu_device *adev) +{ + void *ptr; + + ptr = pci_alloc_consistent(adev->pdev, adev->gart.table_size, + &adev->gart.table_addr); + if (ptr == NULL) { + return -ENOMEM; + } +#ifdef CONFIG_X86 + if (0) { + set_memory_uc((unsigned long)ptr, + adev->gart.table_size >> PAGE_SHIFT); + } +#endif + adev->gart.ptr = ptr; + memset((void *)adev->gart.ptr, 0, adev->gart.table_size); + return 0; +} + +/** + * amdgpu_gart_table_ram_free - free system ram for gart page table + * + * @adev: amdgpu_device pointer + * + * Free system memory for GART page table + * (r1xx-r3xx, non-pcie r4xx, rs400). These asics require the + * gart table to be in system memory. + */ +void amdgpu_gart_table_ram_free(struct amdgpu_device *adev) +{ + if (adev->gart.ptr == NULL) { + return; + } +#ifdef CONFIG_X86 + if (0) { + set_memory_wb((unsigned long)adev->gart.ptr, + adev->gart.table_size >> PAGE_SHIFT); + } +#endif + pci_free_consistent(adev->pdev, adev->gart.table_size, + (void *)adev->gart.ptr, + adev->gart.table_addr); + adev->gart.ptr = NULL; + adev->gart.table_addr = 0; +} + +/** + * amdgpu_gart_table_vram_alloc - allocate vram for gart page table + * + * @adev: amdgpu_device pointer + * + * Allocate video memory for GART page table + * (pcie r4xx, r5xx+). These asics require the + * gart table to be in video memory. + * Returns 0 for success, error for failure. + */ +int amdgpu_gart_table_vram_alloc(struct amdgpu_device *adev) +{ + int r; + + if (adev->gart.robj == NULL) { + r = amdgpu_bo_create(adev, adev->gart.table_size, + PAGE_SIZE, true, AMDGPU_GEM_DOMAIN_VRAM, 0, + NULL, &adev->gart.robj); + if (r) { + return r; + } + } + return 0; +} + +/** + * amdgpu_gart_table_vram_pin - pin gart page table in vram + * + * @adev: amdgpu_device pointer + * + * Pin the GART page table in vram so it will not be moved + * by the memory manager (pcie r4xx, r5xx+). These asics require the + * gart table to be in video memory. + * Returns 0 for success, error for failure. + */ +int amdgpu_gart_table_vram_pin(struct amdgpu_device *adev) +{ + uint64_t gpu_addr; + int r; + + r = amdgpu_bo_reserve(adev->gart.robj, false); + if (unlikely(r != 0)) + return r; + r = amdgpu_bo_pin(adev->gart.robj, + AMDGPU_GEM_DOMAIN_VRAM, &gpu_addr); + if (r) { + amdgpu_bo_unreserve(adev->gart.robj); + return r; + } + r = amdgpu_bo_kmap(adev->gart.robj, &adev->gart.ptr); + if (r) + amdgpu_bo_unpin(adev->gart.robj); + amdgpu_bo_unreserve(adev->gart.robj); + adev->gart.table_addr = gpu_addr; + return r; +} + +/** + * amdgpu_gart_table_vram_unpin - unpin gart page table in vram + * + * @adev: amdgpu_device pointer + * + * Unpin the GART page table in vram (pcie r4xx, r5xx+). + * These asics require the gart table to be in video memory. + */ +void amdgpu_gart_table_vram_unpin(struct amdgpu_device *adev) +{ + int r; + + if (adev->gart.robj == NULL) { + return; + } + r = amdgpu_bo_reserve(adev->gart.robj, false); + if (likely(r == 0)) { + amdgpu_bo_kunmap(adev->gart.robj); + amdgpu_bo_unpin(adev->gart.robj); + amdgpu_bo_unreserve(adev->gart.robj); + adev->gart.ptr = NULL; + } +} + +/** + * amdgpu_gart_table_vram_free - free gart page table vram + * + * @adev: amdgpu_device pointer + * + * Free the video memory used for the GART page table + * (pcie r4xx, r5xx+). These asics require the gart table to + * be in video memory. + */ +void amdgpu_gart_table_vram_free(struct amdgpu_device *adev) +{ + if (adev->gart.robj == NULL) { + return; + } + amdgpu_bo_unref(&adev->gart.robj); +} + +/* + * Common gart functions. + */ +/** + * amdgpu_gart_unbind - unbind pages from the gart page table + * + * @adev: amdgpu_device pointer + * @offset: offset into the GPU's gart aperture + * @pages: number of pages to unbind + * + * Unbinds the requested pages from the gart page table and + * replaces them with the dummy page (all asics). + */ +void amdgpu_gart_unbind(struct amdgpu_device *adev, unsigned offset, + int pages) +{ + unsigned t; + unsigned p; + int i, j; + u64 page_base; + uint32_t flags = AMDGPU_PTE_SYSTEM; + + if (!adev->gart.ready) { + WARN(1, "trying to unbind memory from uninitialized GART !\n"); + return; + } + + t = offset / AMDGPU_GPU_PAGE_SIZE; + p = t / (PAGE_SIZE / AMDGPU_GPU_PAGE_SIZE); + for (i = 0; i < pages; i++, p++) { + if (adev->gart.pages[p]) { + adev->gart.pages[p] = NULL; + adev->gart.pages_addr[p] = adev->dummy_page.addr; + page_base = adev->gart.pages_addr[p]; + if (!adev->gart.ptr) + continue; + + for (j = 0; j < (PAGE_SIZE / AMDGPU_GPU_PAGE_SIZE); j++, t++) { + amdgpu_gart_set_pte_pde(adev, adev->gart.ptr, + t, page_base, flags); + page_base += AMDGPU_GPU_PAGE_SIZE; + } + } + } + mb(); + amdgpu_gart_flush_gpu_tlb(adev, 0); +} + +/** + * amdgpu_gart_bind - bind pages into the gart page table + * + * @adev: amdgpu_device pointer + * @offset: offset into the GPU's gart aperture + * @pages: number of pages to bind + * @pagelist: pages to bind + * @dma_addr: DMA addresses of pages + * + * Binds the requested pages to the gart page table + * (all asics). + * Returns 0 for success, -EINVAL for failure. + */ +int amdgpu_gart_bind(struct amdgpu_device *adev, unsigned offset, + int pages, struct page **pagelist, dma_addr_t *dma_addr, + uint32_t flags) +{ + unsigned t; + unsigned p; + uint64_t page_base; + int i, j; + + if (!adev->gart.ready) { + WARN(1, "trying to bind memory to uninitialized GART !\n"); + return -EINVAL; + } + + t = offset / AMDGPU_GPU_PAGE_SIZE; + p = t / (PAGE_SIZE / AMDGPU_GPU_PAGE_SIZE); + + for (i = 0; i < pages; i++, p++) { + adev->gart.pages_addr[p] = dma_addr[i]; + adev->gart.pages[p] = pagelist[i]; + if (adev->gart.ptr) { + page_base = adev->gart.pages_addr[p]; + for (j = 0; j < (PAGE_SIZE / AMDGPU_GPU_PAGE_SIZE); j++, t++) { + amdgpu_gart_set_pte_pde(adev, adev->gart.ptr, t, page_base, flags); + page_base += AMDGPU_GPU_PAGE_SIZE; + } + } + } + mb(); + amdgpu_gart_flush_gpu_tlb(adev, 0); + return 0; +} + +/** + * amdgpu_gart_init - init the driver info for managing the gart + * + * @adev: amdgpu_device pointer + * + * Allocate the dummy page and init the gart driver info (all asics). + * Returns 0 for success, error for failure. + */ +int amdgpu_gart_init(struct amdgpu_device *adev) +{ + int r, i; + + if (adev->gart.pages) { + return 0; + } + /* We need PAGE_SIZE >= AMDGPU_GPU_PAGE_SIZE */ + if (PAGE_SIZE < AMDGPU_GPU_PAGE_SIZE) { + DRM_ERROR("Page size is smaller than GPU page size!\n"); + return -EINVAL; + } + r = amdgpu_dummy_page_init(adev); + if (r) + return r; + /* Compute table size */ + adev->gart.num_cpu_pages = adev->mc.gtt_size / PAGE_SIZE; + adev->gart.num_gpu_pages = adev->mc.gtt_size / AMDGPU_GPU_PAGE_SIZE; + DRM_INFO("GART: num cpu pages %u, num gpu pages %u\n", + adev->gart.num_cpu_pages, adev->gart.num_gpu_pages); + /* Allocate pages table */ + adev->gart.pages = vzalloc(sizeof(void *) * adev->gart.num_cpu_pages); + if (adev->gart.pages == NULL) { + amdgpu_gart_fini(adev); + return -ENOMEM; + } + adev->gart.pages_addr = vzalloc(sizeof(dma_addr_t) * + adev->gart.num_cpu_pages); + if (adev->gart.pages_addr == NULL) { + amdgpu_gart_fini(adev); + return -ENOMEM; + } + /* set GART entry to point to the dummy page by default */ + for (i = 0; i < adev->gart.num_cpu_pages; i++) { + adev->gart.pages_addr[i] = adev->dummy_page.addr; + } + return 0; +} + +/** + * amdgpu_gart_fini - tear down the driver info for managing the gart + * + * @adev: amdgpu_device pointer + * + * Tear down the gart driver info and free the dummy page (all asics). + */ +void amdgpu_gart_fini(struct amdgpu_device *adev) +{ + if (adev->gart.pages && adev->gart.pages_addr && adev->gart.ready) { + /* unbind pages */ + amdgpu_gart_unbind(adev, 0, adev->gart.num_cpu_pages); + } + adev->gart.ready = false; + vfree(adev->gart.pages); + vfree(adev->gart.pages_addr); + adev->gart.pages = NULL; + adev->gart.pages_addr = NULL; + + amdgpu_dummy_page_fini(adev); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gds.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gds.h new file mode 100644 index 000000000000..c3f4e85594ff --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gds.h @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_GDS_H__ +#define __AMDGPU_GDS_H__ + +/* Because TTM request that alloacted buffer should be PAGE_SIZE aligned, + * we should report GDS/GWS/OA size as PAGE_SIZE aligned + * */ +#define AMDGPU_GDS_SHIFT 2 +#define AMDGPU_GWS_SHIFT PAGE_SHIFT +#define AMDGPU_OA_SHIFT PAGE_SHIFT + +#define AMDGPU_PL_GDS TTM_PL_PRIV0 +#define AMDGPU_PL_GWS TTM_PL_PRIV1 +#define AMDGPU_PL_OA TTM_PL_PRIV2 + +#define AMDGPU_PL_FLAG_GDS TTM_PL_FLAG_PRIV0 +#define AMDGPU_PL_FLAG_GWS TTM_PL_FLAG_PRIV1 +#define AMDGPU_PL_FLAG_OA TTM_PL_FLAG_PRIV2 + +struct amdgpu_ring; +struct amdgpu_bo; + +struct amdgpu_gds_asic_info { + uint32_t total_size; + uint32_t gfx_partition_size; + uint32_t cs_partition_size; +}; + +struct amdgpu_gds { + struct amdgpu_gds_asic_info mem; + struct amdgpu_gds_asic_info gws; + struct amdgpu_gds_asic_info oa; + /* At present, GDS, GWS and OA resources for gfx (graphics) + * is always pre-allocated and available for graphics operation. + * Such resource is shared between all gfx clients. + * TODO: move this operation to user space + * */ + struct amdgpu_bo* gds_gfx_bo; + struct amdgpu_bo* gws_gfx_bo; + struct amdgpu_bo* oa_gfx_bo; +}; + +struct amdgpu_gds_reg_offset { + uint32_t mem_base; + uint32_t mem_size; + uint32_t gws; + uint32_t oa; +}; + +#endif /* __AMDGPU_GDS_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c new file mode 100644 index 000000000000..5fd0bc73b302 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -0,0 +1,735 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <linux/ktime.h> +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +void amdgpu_gem_object_free(struct drm_gem_object *gobj) +{ + struct amdgpu_bo *robj = gem_to_amdgpu_bo(gobj); + + if (robj) { + if (robj->gem_base.import_attach) + drm_prime_gem_destroy(&robj->gem_base, robj->tbo.sg); + amdgpu_bo_unref(&robj); + } +} + +int amdgpu_gem_object_create(struct amdgpu_device *adev, unsigned long size, + int alignment, u32 initial_domain, + u64 flags, bool kernel, + struct drm_gem_object **obj) +{ + struct amdgpu_bo *robj; + unsigned long max_size; + int r; + + *obj = NULL; + /* At least align on page size */ + if (alignment < PAGE_SIZE) { + alignment = PAGE_SIZE; + } + + if (!(initial_domain & (AMDGPU_GEM_DOMAIN_GDS | AMDGPU_GEM_DOMAIN_GWS | AMDGPU_GEM_DOMAIN_OA))) { + /* Maximum bo size is the unpinned gtt size since we use the gtt to + * handle vram to system pool migrations. + */ + max_size = adev->mc.gtt_size - adev->gart_pin_size; + if (size > max_size) { + DRM_DEBUG("Allocation size %ldMb bigger than %ldMb limit\n", + size >> 20, max_size >> 20); + return -ENOMEM; + } + } +retry: + r = amdgpu_bo_create(adev, size, alignment, kernel, initial_domain, flags, NULL, &robj); + if (r) { + if (r != -ERESTARTSYS) { + if (initial_domain == AMDGPU_GEM_DOMAIN_VRAM) { + initial_domain |= AMDGPU_GEM_DOMAIN_GTT; + goto retry; + } + DRM_ERROR("Failed to allocate GEM object (%ld, %d, %u, %d)\n", + size, initial_domain, alignment, r); + } + return r; + } + *obj = &robj->gem_base; + robj->pid = task_pid_nr(current); + + mutex_lock(&adev->gem.mutex); + list_add_tail(&robj->list, &adev->gem.objects); + mutex_unlock(&adev->gem.mutex); + + return 0; +} + +int amdgpu_gem_init(struct amdgpu_device *adev) +{ + INIT_LIST_HEAD(&adev->gem.objects); + return 0; +} + +void amdgpu_gem_fini(struct amdgpu_device *adev) +{ + amdgpu_bo_force_delete(adev); +} + +/* + * Call from drm_gem_handle_create which appear in both new and open ioctl + * case. + */ +int amdgpu_gem_object_open(struct drm_gem_object *obj, struct drm_file *file_priv) +{ + struct amdgpu_bo *rbo = gem_to_amdgpu_bo(obj); + struct amdgpu_device *adev = rbo->adev; + struct amdgpu_fpriv *fpriv = file_priv->driver_priv; + struct amdgpu_vm *vm = &fpriv->vm; + struct amdgpu_bo_va *bo_va; + int r; + + r = amdgpu_bo_reserve(rbo, false); + if (r) { + return r; + } + + bo_va = amdgpu_vm_bo_find(vm, rbo); + if (!bo_va) { + bo_va = amdgpu_vm_bo_add(adev, vm, rbo); + } else { + ++bo_va->ref_count; + } + amdgpu_bo_unreserve(rbo); + + return 0; +} + +void amdgpu_gem_object_close(struct drm_gem_object *obj, + struct drm_file *file_priv) +{ + struct amdgpu_bo *rbo = gem_to_amdgpu_bo(obj); + struct amdgpu_device *adev = rbo->adev; + struct amdgpu_fpriv *fpriv = file_priv->driver_priv; + struct amdgpu_vm *vm = &fpriv->vm; + struct amdgpu_bo_va *bo_va; + int r; + + r = amdgpu_bo_reserve(rbo, true); + if (r) { + dev_err(adev->dev, "leaking bo va because " + "we fail to reserve bo (%d)\n", r); + return; + } + bo_va = amdgpu_vm_bo_find(vm, rbo); + if (bo_va) { + if (--bo_va->ref_count == 0) { + amdgpu_vm_bo_rmv(adev, bo_va); + } + } + amdgpu_bo_unreserve(rbo); +} + +static int amdgpu_gem_handle_lockup(struct amdgpu_device *adev, int r) +{ + if (r == -EDEADLK) { + r = amdgpu_gpu_reset(adev); + if (!r) + r = -EAGAIN; + } + return r; +} + +/* + * GEM ioctls. + */ +int amdgpu_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct amdgpu_device *adev = dev->dev_private; + union drm_amdgpu_gem_create *args = data; + uint64_t size = args->in.bo_size; + struct drm_gem_object *gobj; + uint32_t handle; + bool kernel = false; + int r; + + down_read(&adev->exclusive_lock); + /* create a gem object to contain this object in */ + if (args->in.domains & (AMDGPU_GEM_DOMAIN_GDS | + AMDGPU_GEM_DOMAIN_GWS | AMDGPU_GEM_DOMAIN_OA)) { + kernel = true; + if (args->in.domains == AMDGPU_GEM_DOMAIN_GDS) + size = size << AMDGPU_GDS_SHIFT; + else if (args->in.domains == AMDGPU_GEM_DOMAIN_GWS) + size = size << AMDGPU_GWS_SHIFT; + else if (args->in.domains == AMDGPU_GEM_DOMAIN_OA) + size = size << AMDGPU_OA_SHIFT; + else { + r = -EINVAL; + goto error_unlock; + } + } + size = roundup(size, PAGE_SIZE); + + r = amdgpu_gem_object_create(adev, size, args->in.alignment, + (u32)(0xffffffff & args->in.domains), + args->in.domain_flags, + kernel, &gobj); + if (r) + goto error_unlock; + + r = drm_gem_handle_create(filp, gobj, &handle); + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(gobj); + if (r) + goto error_unlock; + + memset(args, 0, sizeof(*args)); + args->out.handle = handle; + up_read(&adev->exclusive_lock); + return 0; + +error_unlock: + up_read(&adev->exclusive_lock); + r = amdgpu_gem_handle_lockup(adev, r); + return r; +} + +int amdgpu_gem_userptr_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct amdgpu_device *adev = dev->dev_private; + struct drm_amdgpu_gem_userptr *args = data; + struct drm_gem_object *gobj; + struct amdgpu_bo *bo; + uint32_t handle; + int r; + + if (offset_in_page(args->addr | args->size)) + return -EINVAL; + + /* reject unknown flag values */ + if (args->flags & ~(AMDGPU_GEM_USERPTR_READONLY | + AMDGPU_GEM_USERPTR_ANONONLY | AMDGPU_GEM_USERPTR_VALIDATE | + AMDGPU_GEM_USERPTR_REGISTER)) + return -EINVAL; + + if (!(args->flags & AMDGPU_GEM_USERPTR_ANONONLY) || + !(args->flags & AMDGPU_GEM_USERPTR_REGISTER)) { + + /* if we want to write to it we must require anonymous + memory and install a MMU notifier */ + return -EACCES; + } + + down_read(&adev->exclusive_lock); + + /* create a gem object to contain this object in */ + r = amdgpu_gem_object_create(adev, args->size, 0, + AMDGPU_GEM_DOMAIN_CPU, 0, + 0, &gobj); + if (r) + goto handle_lockup; + + bo = gem_to_amdgpu_bo(gobj); + r = amdgpu_ttm_tt_set_userptr(bo->tbo.ttm, args->addr, args->flags); + if (r) + goto release_object; + + if (args->flags & AMDGPU_GEM_USERPTR_REGISTER) { + r = amdgpu_mn_register(bo, args->addr); + if (r) + goto release_object; + } + + if (args->flags & AMDGPU_GEM_USERPTR_VALIDATE) { + down_read(¤t->mm->mmap_sem); + r = amdgpu_bo_reserve(bo, true); + if (r) { + up_read(¤t->mm->mmap_sem); + goto release_object; + } + + amdgpu_ttm_placement_from_domain(bo, AMDGPU_GEM_DOMAIN_GTT); + r = ttm_bo_validate(&bo->tbo, &bo->placement, true, false); + amdgpu_bo_unreserve(bo); + up_read(¤t->mm->mmap_sem); + if (r) + goto release_object; + } + + r = drm_gem_handle_create(filp, gobj, &handle); + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(gobj); + if (r) + goto handle_lockup; + + args->handle = handle; + up_read(&adev->exclusive_lock); + return 0; + +release_object: + drm_gem_object_unreference_unlocked(gobj); + +handle_lockup: + up_read(&adev->exclusive_lock); + r = amdgpu_gem_handle_lockup(adev, r); + + return r; +} + +int amdgpu_mode_dumb_mmap(struct drm_file *filp, + struct drm_device *dev, + uint32_t handle, uint64_t *offset_p) +{ + struct drm_gem_object *gobj; + struct amdgpu_bo *robj; + + gobj = drm_gem_object_lookup(dev, filp, handle); + if (gobj == NULL) { + return -ENOENT; + } + robj = gem_to_amdgpu_bo(gobj); + if (amdgpu_ttm_tt_has_userptr(robj->tbo.ttm)) { + drm_gem_object_unreference_unlocked(gobj); + return -EPERM; + } + *offset_p = amdgpu_bo_mmap_offset(robj); + drm_gem_object_unreference_unlocked(gobj); + return 0; +} + +int amdgpu_gem_mmap_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + union drm_amdgpu_gem_mmap *args = data; + uint32_t handle = args->in.handle; + memset(args, 0, sizeof(*args)); + return amdgpu_mode_dumb_mmap(filp, dev, handle, &args->out.addr_ptr); +} + +/** + * amdgpu_gem_timeout - calculate jiffies timeout from absolute value + * + * @timeout_ns: timeout in ns + * + * Calculate the timeout in jiffies from an absolute timeout in ns. + */ +unsigned long amdgpu_gem_timeout(uint64_t timeout_ns) +{ + unsigned long timeout_jiffies; + ktime_t timeout; + + /* clamp timeout if it's to large */ + if (((int64_t)timeout_ns) < 0) + return MAX_SCHEDULE_TIMEOUT; + + timeout = ktime_sub_ns(ktime_get(), timeout_ns); + if (ktime_to_ns(timeout) < 0) + return 0; + + timeout_jiffies = nsecs_to_jiffies(ktime_to_ns(timeout)); + /* clamp timeout to avoid unsigned-> signed overflow */ + if (timeout_jiffies > MAX_SCHEDULE_TIMEOUT ) + return MAX_SCHEDULE_TIMEOUT - 1; + + return timeout_jiffies; +} + +int amdgpu_gem_wait_idle_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct amdgpu_device *adev = dev->dev_private; + union drm_amdgpu_gem_wait_idle *args = data; + struct drm_gem_object *gobj; + struct amdgpu_bo *robj; + uint32_t handle = args->in.handle; + unsigned long timeout = amdgpu_gem_timeout(args->in.timeout); + int r = 0; + long ret; + + gobj = drm_gem_object_lookup(dev, filp, handle); + if (gobj == NULL) { + return -ENOENT; + } + robj = gem_to_amdgpu_bo(gobj); + if (timeout == 0) + ret = reservation_object_test_signaled_rcu(robj->tbo.resv, true); + else + ret = reservation_object_wait_timeout_rcu(robj->tbo.resv, true, true, timeout); + + /* ret == 0 means not signaled, + * ret > 0 means signaled + * ret < 0 means interrupted before timeout + */ + if (ret >= 0) { + memset(args, 0, sizeof(*args)); + args->out.status = (ret == 0); + } else + r = ret; + + drm_gem_object_unreference_unlocked(gobj); + r = amdgpu_gem_handle_lockup(adev, r); + return r; +} + +int amdgpu_gem_metadata_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct drm_amdgpu_gem_metadata *args = data; + struct drm_gem_object *gobj; + struct amdgpu_bo *robj; + int r = -1; + + DRM_DEBUG("%d \n", args->handle); + gobj = drm_gem_object_lookup(dev, filp, args->handle); + if (gobj == NULL) + return -ENOENT; + robj = gem_to_amdgpu_bo(gobj); + + r = amdgpu_bo_reserve(robj, false); + if (unlikely(r != 0)) + goto out; + + if (args->op == AMDGPU_GEM_METADATA_OP_GET_METADATA) { + amdgpu_bo_get_tiling_flags(robj, &args->data.tiling_info); + r = amdgpu_bo_get_metadata(robj, args->data.data, + sizeof(args->data.data), + &args->data.data_size_bytes, + &args->data.flags); + } else if (args->op == AMDGPU_GEM_METADATA_OP_SET_METADATA) { + r = amdgpu_bo_set_tiling_flags(robj, args->data.tiling_info); + if (!r) + r = amdgpu_bo_set_metadata(robj, args->data.data, + args->data.data_size_bytes, + args->data.flags); + } + + amdgpu_bo_unreserve(robj); +out: + drm_gem_object_unreference_unlocked(gobj); + return r; +} + +/** + * amdgpu_gem_va_update_vm -update the bo_va in its VM + * + * @adev: amdgpu_device pointer + * @bo_va: bo_va to update + * + * Update the bo_va directly after setting it's address. Errors are not + * vital here, so they are not reported back to userspace. + */ +static void amdgpu_gem_va_update_vm(struct amdgpu_device *adev, + struct amdgpu_bo_va *bo_va) +{ + struct ttm_validate_buffer tv, *entry; + struct amdgpu_bo_list_entry *vm_bos; + struct ww_acquire_ctx ticket; + struct list_head list; + unsigned domain; + int r; + + INIT_LIST_HEAD(&list); + + tv.bo = &bo_va->bo->tbo; + tv.shared = true; + list_add(&tv.head, &list); + + vm_bos = amdgpu_vm_get_bos(adev, bo_va->vm, &list); + if (!vm_bos) + return; + + r = ttm_eu_reserve_buffers(&ticket, &list, true, NULL); + if (r) + goto error_free; + + list_for_each_entry(entry, &list, head) { + domain = amdgpu_mem_type_to_domain(entry->bo->mem.mem_type); + /* if anything is swapped out don't swap it in here, + just abort and wait for the next CS */ + if (domain == AMDGPU_GEM_DOMAIN_CPU) + goto error_unreserve; + } + + mutex_lock(&bo_va->vm->mutex); + r = amdgpu_vm_clear_freed(adev, bo_va->vm); + if (r) + goto error_unlock; + + r = amdgpu_vm_bo_update(adev, bo_va, &bo_va->bo->tbo.mem); + +error_unlock: + mutex_unlock(&bo_va->vm->mutex); + +error_unreserve: + ttm_eu_backoff_reservation(&ticket, &list); + +error_free: + drm_free_large(vm_bos); + + if (r) + DRM_ERROR("Couldn't update BO_VA (%d)\n", r); +} + + + +int amdgpu_gem_va_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + union drm_amdgpu_gem_va *args = data; + struct drm_gem_object *gobj; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_fpriv *fpriv = filp->driver_priv; + struct amdgpu_bo *rbo; + struct amdgpu_bo_va *bo_va; + uint32_t invalid_flags, va_flags = 0; + int r = 0; + + if (!adev->vm_manager.enabled) { + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + return -ENOTTY; + } + + if (args->in.va_address < AMDGPU_VA_RESERVED_SIZE) { + dev_err(&dev->pdev->dev, + "va_address 0x%lX is in reserved area 0x%X\n", + (unsigned long)args->in.va_address, + AMDGPU_VA_RESERVED_SIZE); + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + return -EINVAL; + } + + invalid_flags = ~(AMDGPU_VM_PAGE_READABLE | AMDGPU_VM_PAGE_WRITEABLE | + AMDGPU_VM_PAGE_EXECUTABLE); + if ((args->in.flags & invalid_flags)) { + dev_err(&dev->pdev->dev, "invalid flags 0x%08X vs 0x%08X\n", + args->in.flags, invalid_flags); + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + return -EINVAL; + } + + switch (args->in.operation) { + case AMDGPU_VA_OP_MAP: + case AMDGPU_VA_OP_UNMAP: + break; + default: + dev_err(&dev->pdev->dev, "unsupported operation %d\n", + args->in.operation); + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + return -EINVAL; + } + + gobj = drm_gem_object_lookup(dev, filp, args->in.handle); + if (gobj == NULL) { + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + return -ENOENT; + } + rbo = gem_to_amdgpu_bo(gobj); + r = amdgpu_bo_reserve(rbo, false); + if (r) { + if (r != -ERESTARTSYS) { + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + } + drm_gem_object_unreference_unlocked(gobj); + return r; + } + bo_va = amdgpu_vm_bo_find(&fpriv->vm, rbo); + if (!bo_va) { + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + drm_gem_object_unreference_unlocked(gobj); + return -ENOENT; + } + + switch (args->in.operation) { + case AMDGPU_VA_OP_MAP: + if (args->in.flags & AMDGPU_VM_PAGE_READABLE) + va_flags |= AMDGPU_PTE_READABLE; + if (args->in.flags & AMDGPU_VM_PAGE_WRITEABLE) + va_flags |= AMDGPU_PTE_WRITEABLE; + if (args->in.flags & AMDGPU_VM_PAGE_EXECUTABLE) + va_flags |= AMDGPU_PTE_EXECUTABLE; + r = amdgpu_vm_bo_map(adev, bo_va, args->in.va_address, 0, + amdgpu_bo_size(bo_va->bo), va_flags); + break; + case AMDGPU_VA_OP_UNMAP: + r = amdgpu_vm_bo_unmap(adev, bo_va, args->in.va_address); + break; + default: + break; + } + + if (!r) { + amdgpu_gem_va_update_vm(adev, bo_va); + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_OK; + } else { + memset(args, 0, sizeof(*args)); + args->out.result = AMDGPU_VA_RESULT_ERROR; + } + + drm_gem_object_unreference_unlocked(gobj); + return r; +} + +int amdgpu_gem_op_ioctl(struct drm_device *dev, void *data, + struct drm_file *filp) +{ + struct drm_amdgpu_gem_op *args = data; + struct drm_gem_object *gobj; + struct amdgpu_bo *robj; + int r; + + gobj = drm_gem_object_lookup(dev, filp, args->handle); + if (gobj == NULL) { + return -ENOENT; + } + robj = gem_to_amdgpu_bo(gobj); + + r = amdgpu_bo_reserve(robj, false); + if (unlikely(r)) + goto out; + + switch (args->op) { + case AMDGPU_GEM_OP_GET_GEM_CREATE_INFO: { + struct drm_amdgpu_gem_create_in info; + void __user *out = (void __user *)(long)args->value; + + info.bo_size = robj->gem_base.size; + info.alignment = robj->tbo.mem.page_alignment << PAGE_SHIFT; + info.domains = robj->initial_domain; + info.domain_flags = robj->flags; + if (copy_to_user(out, &info, sizeof(info))) + r = -EFAULT; + break; + } + case AMDGPU_GEM_OP_SET_INITIAL_DOMAIN: + if (amdgpu_ttm_tt_has_userptr(robj->tbo.ttm)) { + r = -EPERM; + break; + } + robj->initial_domain = args->value & (AMDGPU_GEM_DOMAIN_VRAM | + AMDGPU_GEM_DOMAIN_GTT | + AMDGPU_GEM_DOMAIN_CPU); + break; + default: + r = -EINVAL; + } + + amdgpu_bo_unreserve(robj); +out: + drm_gem_object_unreference_unlocked(gobj); + return r; +} + +int amdgpu_mode_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct amdgpu_device *adev = dev->dev_private; + struct drm_gem_object *gobj; + uint32_t handle; + int r; + + args->pitch = amdgpu_align_pitch(adev, args->width, args->bpp, 0) * ((args->bpp + 1) / 8); + args->size = args->pitch * args->height; + args->size = ALIGN(args->size, PAGE_SIZE); + + r = amdgpu_gem_object_create(adev, args->size, 0, + AMDGPU_GEM_DOMAIN_VRAM, + 0, ttm_bo_type_device, + &gobj); + if (r) + return -ENOMEM; + + r = drm_gem_handle_create(file_priv, gobj, &handle); + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(gobj); + if (r) { + return r; + } + args->handle = handle; + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +static int amdgpu_debugfs_gem_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_bo *rbo; + unsigned i = 0; + + mutex_lock(&adev->gem.mutex); + list_for_each_entry(rbo, &adev->gem.objects, list) { + unsigned domain; + const char *placement; + + domain = amdgpu_mem_type_to_domain(rbo->tbo.mem.mem_type); + switch (domain) { + case AMDGPU_GEM_DOMAIN_VRAM: + placement = "VRAM"; + break; + case AMDGPU_GEM_DOMAIN_GTT: + placement = " GTT"; + break; + case AMDGPU_GEM_DOMAIN_CPU: + default: + placement = " CPU"; + break; + } + seq_printf(m, "bo[0x%08x] %8ldkB %8ldMB %s pid %8ld\n", + i, amdgpu_bo_size(rbo) >> 10, amdgpu_bo_size(rbo) >> 20, + placement, (unsigned long)rbo->pid); + i++; + } + mutex_unlock(&adev->gem.mutex); + return 0; +} + +static struct drm_info_list amdgpu_debugfs_gem_list[] = { + {"amdgpu_gem_info", &amdgpu_debugfs_gem_info, 0, NULL}, +}; +#endif + +int amdgpu_gem_debugfs_init(struct amdgpu_device *adev) +{ +#if defined(CONFIG_DEBUG_FS) + return amdgpu_debugfs_add_files(adev, amdgpu_debugfs_gem_list, 1); +#endif + return 0; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c new file mode 100644 index 000000000000..9f95da4f0536 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.c @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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. + * + */ +#include <drm/drmP.h> +#include "amdgpu.h" + +/* + * GPU scratch registers helpers function. + */ +/** + * amdgpu_gfx_scratch_get - Allocate a scratch register + * + * @adev: amdgpu_device pointer + * @reg: scratch register mmio offset + * + * Allocate a CP scratch register for use by the driver (all asics). + * Returns 0 on success or -EINVAL on failure. + */ +int amdgpu_gfx_scratch_get(struct amdgpu_device *adev, uint32_t *reg) +{ + int i; + + for (i = 0; i < adev->gfx.scratch.num_reg; i++) { + if (adev->gfx.scratch.free[i]) { + adev->gfx.scratch.free[i] = false; + *reg = adev->gfx.scratch.reg[i]; + return 0; + } + } + return -EINVAL; +} + +/** + * amdgpu_gfx_scratch_free - Free a scratch register + * + * @adev: amdgpu_device pointer + * @reg: scratch register mmio offset + * + * Free a CP scratch register allocated for use by the driver (all asics) + */ +void amdgpu_gfx_scratch_free(struct amdgpu_device *adev, uint32_t reg) +{ + int i; + + for (i = 0; i < adev->gfx.scratch.num_reg; i++) { + if (adev->gfx.scratch.reg[i] == reg) { + adev->gfx.scratch.free[i] = true; + return; + } + } +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h new file mode 100644 index 000000000000..dc06cbda7be6 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gfx.h @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_GFX_H__ +#define __AMDGPU_GFX_H__ + +int amdgpu_gfx_scratch_get(struct amdgpu_device *adev, uint32_t *reg); +void amdgpu_gfx_scratch_free(struct amdgpu_device *adev, uint32_t reg); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_i2c.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_i2c.c new file mode 100644 index 000000000000..31a676376d73 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_i2c.c @@ -0,0 +1,395 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include <linux/export.h> + +#include <drm/drmP.h> +#include <drm/drm_edid.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_i2c.h" +#include "amdgpu_atombios.h" +#include "atom.h" +#include "atombios_dp.h" +#include "atombios_i2c.h" + +/* bit banging i2c */ +static int amdgpu_i2c_pre_xfer(struct i2c_adapter *i2c_adap) +{ + struct amdgpu_i2c_chan *i2c = i2c_get_adapdata(i2c_adap); + struct amdgpu_device *adev = i2c->dev->dev_private; + struct amdgpu_i2c_bus_rec *rec = &i2c->rec; + uint32_t temp; + + mutex_lock(&i2c->mutex); + + /* switch the pads to ddc mode */ + if (rec->hw_capable) { + temp = RREG32(rec->mask_clk_reg); + temp &= ~(1 << 16); + WREG32(rec->mask_clk_reg, temp); + } + + /* clear the output pin values */ + temp = RREG32(rec->a_clk_reg) & ~rec->a_clk_mask; + WREG32(rec->a_clk_reg, temp); + + temp = RREG32(rec->a_data_reg) & ~rec->a_data_mask; + WREG32(rec->a_data_reg, temp); + + /* set the pins to input */ + temp = RREG32(rec->en_clk_reg) & ~rec->en_clk_mask; + WREG32(rec->en_clk_reg, temp); + + temp = RREG32(rec->en_data_reg) & ~rec->en_data_mask; + WREG32(rec->en_data_reg, temp); + + /* mask the gpio pins for software use */ + temp = RREG32(rec->mask_clk_reg) | rec->mask_clk_mask; + WREG32(rec->mask_clk_reg, temp); + temp = RREG32(rec->mask_clk_reg); + + temp = RREG32(rec->mask_data_reg) | rec->mask_data_mask; + WREG32(rec->mask_data_reg, temp); + temp = RREG32(rec->mask_data_reg); + + return 0; +} + +static void amdgpu_i2c_post_xfer(struct i2c_adapter *i2c_adap) +{ + struct amdgpu_i2c_chan *i2c = i2c_get_adapdata(i2c_adap); + struct amdgpu_device *adev = i2c->dev->dev_private; + struct amdgpu_i2c_bus_rec *rec = &i2c->rec; + uint32_t temp; + + /* unmask the gpio pins for software use */ + temp = RREG32(rec->mask_clk_reg) & ~rec->mask_clk_mask; + WREG32(rec->mask_clk_reg, temp); + temp = RREG32(rec->mask_clk_reg); + + temp = RREG32(rec->mask_data_reg) & ~rec->mask_data_mask; + WREG32(rec->mask_data_reg, temp); + temp = RREG32(rec->mask_data_reg); + + mutex_unlock(&i2c->mutex); +} + +static int amdgpu_i2c_get_clock(void *i2c_priv) +{ + struct amdgpu_i2c_chan *i2c = i2c_priv; + struct amdgpu_device *adev = i2c->dev->dev_private; + struct amdgpu_i2c_bus_rec *rec = &i2c->rec; + uint32_t val; + + /* read the value off the pin */ + val = RREG32(rec->y_clk_reg); + val &= rec->y_clk_mask; + + return (val != 0); +} + + +static int amdgpu_i2c_get_data(void *i2c_priv) +{ + struct amdgpu_i2c_chan *i2c = i2c_priv; + struct amdgpu_device *adev = i2c->dev->dev_private; + struct amdgpu_i2c_bus_rec *rec = &i2c->rec; + uint32_t val; + + /* read the value off the pin */ + val = RREG32(rec->y_data_reg); + val &= rec->y_data_mask; + + return (val != 0); +} + +static void amdgpu_i2c_set_clock(void *i2c_priv, int clock) +{ + struct amdgpu_i2c_chan *i2c = i2c_priv; + struct amdgpu_device *adev = i2c->dev->dev_private; + struct amdgpu_i2c_bus_rec *rec = &i2c->rec; + uint32_t val; + + /* set pin direction */ + val = RREG32(rec->en_clk_reg) & ~rec->en_clk_mask; + val |= clock ? 0 : rec->en_clk_mask; + WREG32(rec->en_clk_reg, val); +} + +static void amdgpu_i2c_set_data(void *i2c_priv, int data) +{ + struct amdgpu_i2c_chan *i2c = i2c_priv; + struct amdgpu_device *adev = i2c->dev->dev_private; + struct amdgpu_i2c_bus_rec *rec = &i2c->rec; + uint32_t val; + + /* set pin direction */ + val = RREG32(rec->en_data_reg) & ~rec->en_data_mask; + val |= data ? 0 : rec->en_data_mask; + WREG32(rec->en_data_reg, val); +} + +static const struct i2c_algorithm amdgpu_atombios_i2c_algo = { + .master_xfer = amdgpu_atombios_i2c_xfer, + .functionality = amdgpu_atombios_i2c_func, +}; + +struct amdgpu_i2c_chan *amdgpu_i2c_create(struct drm_device *dev, + struct amdgpu_i2c_bus_rec *rec, + const char *name) +{ + struct amdgpu_i2c_chan *i2c; + int ret; + + /* don't add the mm_i2c bus unless hw_i2c is enabled */ + if (rec->mm_i2c && (amdgpu_hw_i2c == 0)) + return NULL; + + i2c = kzalloc(sizeof(struct amdgpu_i2c_chan), GFP_KERNEL); + if (i2c == NULL) + return NULL; + + i2c->rec = *rec; + i2c->adapter.owner = THIS_MODULE; + i2c->adapter.class = I2C_CLASS_DDC; + i2c->adapter.dev.parent = &dev->pdev->dev; + i2c->dev = dev; + i2c_set_adapdata(&i2c->adapter, i2c); + mutex_init(&i2c->mutex); + if (rec->hw_capable && + amdgpu_hw_i2c) { + /* hw i2c using atom */ + snprintf(i2c->adapter.name, sizeof(i2c->adapter.name), + "AMDGPU i2c hw bus %s", name); + i2c->adapter.algo = &amdgpu_atombios_i2c_algo; + ret = i2c_add_adapter(&i2c->adapter); + if (ret) { + DRM_ERROR("Failed to register hw i2c %s\n", name); + goto out_free; + } + } else { + /* set the amdgpu bit adapter */ + snprintf(i2c->adapter.name, sizeof(i2c->adapter.name), + "AMDGPU i2c bit bus %s", name); + i2c->adapter.algo_data = &i2c->bit; + i2c->bit.pre_xfer = amdgpu_i2c_pre_xfer; + i2c->bit.post_xfer = amdgpu_i2c_post_xfer; + i2c->bit.setsda = amdgpu_i2c_set_data; + i2c->bit.setscl = amdgpu_i2c_set_clock; + i2c->bit.getsda = amdgpu_i2c_get_data; + i2c->bit.getscl = amdgpu_i2c_get_clock; + i2c->bit.udelay = 10; + i2c->bit.timeout = usecs_to_jiffies(2200); /* from VESA */ + i2c->bit.data = i2c; + ret = i2c_bit_add_bus(&i2c->adapter); + if (ret) { + DRM_ERROR("Failed to register bit i2c %s\n", name); + goto out_free; + } + } + + return i2c; +out_free: + kfree(i2c); + return NULL; + +} + +void amdgpu_i2c_destroy(struct amdgpu_i2c_chan *i2c) +{ + if (!i2c) + return; + i2c_del_adapter(&i2c->adapter); + kfree(i2c); +} + +/* Add the default buses */ +void amdgpu_i2c_init(struct amdgpu_device *adev) +{ + if (amdgpu_hw_i2c) + DRM_INFO("hw_i2c forced on, you may experience display detection problems!\n"); + + if (adev->is_atom_bios) + amdgpu_atombios_i2c_init(adev); +} + +/* remove all the buses */ +void amdgpu_i2c_fini(struct amdgpu_device *adev) +{ + int i; + + for (i = 0; i < AMDGPU_MAX_I2C_BUS; i++) { + if (adev->i2c_bus[i]) { + amdgpu_i2c_destroy(adev->i2c_bus[i]); + adev->i2c_bus[i] = NULL; + } + } +} + +/* Add additional buses */ +void amdgpu_i2c_add(struct amdgpu_device *adev, + struct amdgpu_i2c_bus_rec *rec, + const char *name) +{ + struct drm_device *dev = adev->ddev; + int i; + + for (i = 0; i < AMDGPU_MAX_I2C_BUS; i++) { + if (!adev->i2c_bus[i]) { + adev->i2c_bus[i] = amdgpu_i2c_create(dev, rec, name); + return; + } + } +} + +/* looks up bus based on id */ +struct amdgpu_i2c_chan * +amdgpu_i2c_lookup(struct amdgpu_device *adev, + struct amdgpu_i2c_bus_rec *i2c_bus) +{ + int i; + + for (i = 0; i < AMDGPU_MAX_I2C_BUS; i++) { + if (adev->i2c_bus[i] && + (adev->i2c_bus[i]->rec.i2c_id == i2c_bus->i2c_id)) { + return adev->i2c_bus[i]; + } + } + return NULL; +} + +static void amdgpu_i2c_get_byte(struct amdgpu_i2c_chan *i2c_bus, + u8 slave_addr, + u8 addr, + u8 *val) +{ + u8 out_buf[2]; + u8 in_buf[2]; + struct i2c_msg msgs[] = { + { + .addr = slave_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = slave_addr, + .flags = I2C_M_RD, + .len = 1, + .buf = in_buf, + } + }; + + out_buf[0] = addr; + out_buf[1] = 0; + + if (i2c_transfer(&i2c_bus->adapter, msgs, 2) == 2) { + *val = in_buf[0]; + DRM_DEBUG("val = 0x%02x\n", *val); + } else { + DRM_DEBUG("i2c 0x%02x 0x%02x read failed\n", + addr, *val); + } +} + +static void amdgpu_i2c_put_byte(struct amdgpu_i2c_chan *i2c_bus, + u8 slave_addr, + u8 addr, + u8 val) +{ + uint8_t out_buf[2]; + struct i2c_msg msg = { + .addr = slave_addr, + .flags = 0, + .len = 2, + .buf = out_buf, + }; + + out_buf[0] = addr; + out_buf[1] = val; + + if (i2c_transfer(&i2c_bus->adapter, &msg, 1) != 1) + DRM_DEBUG("i2c 0x%02x 0x%02x write failed\n", + addr, val); +} + +/* ddc router switching */ +void +amdgpu_i2c_router_select_ddc_port(struct amdgpu_connector *amdgpu_connector) +{ + u8 val; + + if (!amdgpu_connector->router.ddc_valid) + return; + + if (!amdgpu_connector->router_bus) + return; + + amdgpu_i2c_get_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x3, &val); + val &= ~amdgpu_connector->router.ddc_mux_control_pin; + amdgpu_i2c_put_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x3, val); + amdgpu_i2c_get_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x1, &val); + val &= ~amdgpu_connector->router.ddc_mux_control_pin; + val |= amdgpu_connector->router.ddc_mux_state; + amdgpu_i2c_put_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x1, val); +} + +/* clock/data router switching */ +void +amdgpu_i2c_router_select_cd_port(struct amdgpu_connector *amdgpu_connector) +{ + u8 val; + + if (!amdgpu_connector->router.cd_valid) + return; + + if (!amdgpu_connector->router_bus) + return; + + amdgpu_i2c_get_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x3, &val); + val &= ~amdgpu_connector->router.cd_mux_control_pin; + amdgpu_i2c_put_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x3, val); + amdgpu_i2c_get_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x1, &val); + val &= ~amdgpu_connector->router.cd_mux_control_pin; + val |= amdgpu_connector->router.cd_mux_state; + amdgpu_i2c_put_byte(amdgpu_connector->router_bus, + amdgpu_connector->router.i2c_addr, + 0x1, val); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_i2c.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_i2c.h new file mode 100644 index 000000000000..d81e19b53973 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_i2c.h @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_I2C_H__ +#define __AMDGPU_I2C_H__ + +struct amdgpu_i2c_chan *amdgpu_i2c_create(struct drm_device *dev, + struct amdgpu_i2c_bus_rec *rec, + const char *name); +void amdgpu_i2c_destroy(struct amdgpu_i2c_chan *i2c); +void amdgpu_i2c_init(struct amdgpu_device *adev); +void amdgpu_i2c_fini(struct amdgpu_device *adev); +void amdgpu_i2c_add(struct amdgpu_device *adev, + struct amdgpu_i2c_bus_rec *rec, + const char *name); +struct amdgpu_i2c_chan * +amdgpu_i2c_lookup(struct amdgpu_device *adev, + struct amdgpu_i2c_bus_rec *i2c_bus); +void +amdgpu_i2c_router_select_ddc_port(struct amdgpu_connector *amdgpu_connector); +void +amdgpu_i2c_router_select_cd_port(struct amdgpu_connector *amdgpu_connector); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c new file mode 100644 index 000000000000..847cab2b3fff --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ib.c @@ -0,0 +1,345 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + * Christian König + */ +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "atom.h" + +/* + * IB + * IBs (Indirect Buffers) and areas of GPU accessible memory where + * commands are stored. You can put a pointer to the IB in the + * command ring and the hw will fetch the commands from the IB + * and execute them. Generally userspace acceleration drivers + * produce command buffers which are send to the kernel and + * put in IBs for execution by the requested ring. + */ +static int amdgpu_debugfs_sa_init(struct amdgpu_device *adev); + +/** + * amdgpu_ib_get - request an IB (Indirect Buffer) + * + * @ring: ring index the IB is associated with + * @size: requested IB size + * @ib: IB object returned + * + * Request an IB (all asics). IBs are allocated using the + * suballocator. + * Returns 0 on success, error on failure. + */ +int amdgpu_ib_get(struct amdgpu_ring *ring, struct amdgpu_vm *vm, + unsigned size, struct amdgpu_ib *ib) +{ + struct amdgpu_device *adev = ring->adev; + int r; + + if (size) { + r = amdgpu_sa_bo_new(adev, &adev->ring_tmp_bo, + &ib->sa_bo, size, 256); + if (r) { + dev_err(adev->dev, "failed to get a new IB (%d)\n", r); + return r; + } + + ib->ptr = amdgpu_sa_bo_cpu_addr(ib->sa_bo); + + if (!vm) + ib->gpu_addr = amdgpu_sa_bo_gpu_addr(ib->sa_bo); + else + ib->gpu_addr = 0; + + } else { + ib->sa_bo = NULL; + ib->ptr = NULL; + ib->gpu_addr = 0; + } + + amdgpu_sync_create(&ib->sync); + + ib->ring = ring; + ib->fence = NULL; + ib->user = NULL; + ib->vm = vm; + ib->is_const_ib = false; + ib->gds_base = 0; + ib->gds_size = 0; + ib->gws_base = 0; + ib->gws_size = 0; + ib->oa_base = 0; + ib->oa_size = 0; + + return 0; +} + +/** + * amdgpu_ib_free - free an IB (Indirect Buffer) + * + * @adev: amdgpu_device pointer + * @ib: IB object to free + * + * Free an IB (all asics). + */ +void amdgpu_ib_free(struct amdgpu_device *adev, struct amdgpu_ib *ib) +{ + amdgpu_sync_free(adev, &ib->sync, ib->fence); + amdgpu_sa_bo_free(adev, &ib->sa_bo, ib->fence); + amdgpu_fence_unref(&ib->fence); +} + +/** + * amdgpu_ib_schedule - schedule an IB (Indirect Buffer) on the ring + * + * @adev: amdgpu_device pointer + * @num_ibs: number of IBs to schedule + * @ibs: IB objects to schedule + * @owner: owner for creating the fences + * + * Schedule an IB on the associated ring (all asics). + * Returns 0 on success, error on failure. + * + * On SI, there are two parallel engines fed from the primary ring, + * the CE (Constant Engine) and the DE (Drawing Engine). Since + * resource descriptors have moved to memory, the CE allows you to + * prime the caches while the DE is updating register state so that + * the resource descriptors will be already in cache when the draw is + * processed. To accomplish this, the userspace driver submits two + * IBs, one for the CE and one for the DE. If there is a CE IB (called + * a CONST_IB), it will be put on the ring prior to the DE IB. Prior + * to SI there was just a DE IB. + */ +int amdgpu_ib_schedule(struct amdgpu_device *adev, unsigned num_ibs, + struct amdgpu_ib *ibs, void *owner) +{ + struct amdgpu_ring *ring; + struct amdgpu_vm *vm = ibs->vm; + struct amdgpu_ib *ib = &ibs[0]; + unsigned i; + int r = 0; + bool flush_hdp = true; + + if (num_ibs == 0) + return -EINVAL; + + ring = ibs->ring; + if (!ring->ready) { + dev_err(adev->dev, "couldn't schedule ib\n"); + return -EINVAL; + } + + r = amdgpu_ring_lock(ring, (256 + AMDGPU_NUM_SYNCS * 8) * num_ibs); + if (r) { + dev_err(adev->dev, "scheduling IB failed (%d).\n", r); + return r; + } + + if (vm) { + /* grab a vm id if necessary */ + struct amdgpu_fence *vm_id_fence = NULL; + vm_id_fence = amdgpu_vm_grab_id(ibs->ring, ibs->vm); + amdgpu_sync_fence(&ibs->sync, vm_id_fence); + } + + r = amdgpu_sync_rings(&ibs->sync, ring); + if (r) { + amdgpu_ring_unlock_undo(ring); + dev_err(adev->dev, "failed to sync rings (%d)\n", r); + return r; + } + + if (vm) { + /* do context switch */ + amdgpu_vm_flush(ring, vm, ib->sync.last_vm_update); + } + + if (ring->funcs->emit_gds_switch && ib->vm && ib->gds_needed) + amdgpu_ring_emit_gds_switch(ring, ib->vm->ids[ring->idx].id, + ib->gds_base, ib->gds_size, + ib->gws_base, ib->gws_size, + ib->oa_base, ib->oa_size); + + for (i = 0; i < num_ibs; ++i) { + ib = &ibs[i]; + + if (ib->ring != ring) { + amdgpu_ring_unlock_undo(ring); + return -EINVAL; + } + ib->flush_hdp_writefifo = flush_hdp; + flush_hdp = false; + amdgpu_ring_emit_ib(ring, ib); + } + + r = amdgpu_fence_emit(ring, owner, &ib->fence); + if (r) { + dev_err(adev->dev, "failed to emit fence (%d)\n", r); + amdgpu_ring_unlock_undo(ring); + return r; + } + + /* wrap the last IB with fence */ + if (ib->user) { + uint64_t addr = amdgpu_bo_gpu_offset(ib->user->bo); + addr += ib->user->offset; + amdgpu_ring_emit_fence(ring, addr, ib->fence->seq, true); + } + + if (ib->vm) + amdgpu_vm_fence(adev, ib->vm, ib->fence); + + amdgpu_ring_unlock_commit(ring); + return 0; +} + +/** + * amdgpu_ib_pool_init - Init the IB (Indirect Buffer) pool + * + * @adev: amdgpu_device pointer + * + * Initialize the suballocator to manage a pool of memory + * for use as IBs (all asics). + * Returns 0 on success, error on failure. + */ +int amdgpu_ib_pool_init(struct amdgpu_device *adev) +{ + int r; + + if (adev->ib_pool_ready) { + return 0; + } + r = amdgpu_sa_bo_manager_init(adev, &adev->ring_tmp_bo, + AMDGPU_IB_POOL_SIZE*64*1024, + AMDGPU_GPU_PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT); + if (r) { + return r; + } + + r = amdgpu_sa_bo_manager_start(adev, &adev->ring_tmp_bo); + if (r) { + return r; + } + + adev->ib_pool_ready = true; + if (amdgpu_debugfs_sa_init(adev)) { + dev_err(adev->dev, "failed to register debugfs file for SA\n"); + } + return 0; +} + +/** + * amdgpu_ib_pool_fini - Free the IB (Indirect Buffer) pool + * + * @adev: amdgpu_device pointer + * + * Tear down the suballocator managing the pool of memory + * for use as IBs (all asics). + */ +void amdgpu_ib_pool_fini(struct amdgpu_device *adev) +{ + if (adev->ib_pool_ready) { + amdgpu_sa_bo_manager_suspend(adev, &adev->ring_tmp_bo); + amdgpu_sa_bo_manager_fini(adev, &adev->ring_tmp_bo); + adev->ib_pool_ready = false; + } +} + +/** + * amdgpu_ib_ring_tests - test IBs on the rings + * + * @adev: amdgpu_device pointer + * + * Test an IB (Indirect Buffer) on each ring. + * If the test fails, disable the ring. + * Returns 0 on success, error if the primary GFX ring + * IB test fails. + */ +int amdgpu_ib_ring_tests(struct amdgpu_device *adev) +{ + unsigned i; + int r; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + + if (!ring || !ring->ready) + continue; + + r = amdgpu_ring_test_ib(ring); + if (r) { + ring->ready = false; + adev->needs_reset = false; + + if (ring == &adev->gfx.gfx_ring[0]) { + /* oh, oh, that's really bad */ + DRM_ERROR("amdgpu: failed testing IB on GFX ring (%d).\n", r); + adev->accel_working = false; + return r; + + } else { + /* still not good, but we can live with it */ + DRM_ERROR("amdgpu: failed testing IB on ring %d (%d).\n", i, r); + } + } + } + return 0; +} + +/* + * Debugfs info + */ +#if defined(CONFIG_DEBUG_FS) + +static int amdgpu_debugfs_sa_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct amdgpu_device *adev = dev->dev_private; + + amdgpu_sa_bo_dump_debug_info(&adev->ring_tmp_bo, m); + + return 0; + +} + +static struct drm_info_list amdgpu_debugfs_sa_list[] = { + {"amdgpu_sa_info", &amdgpu_debugfs_sa_info, 0, NULL}, +}; + +#endif + +static int amdgpu_debugfs_sa_init(struct amdgpu_device *adev) +{ +#if defined(CONFIG_DEBUG_FS) + return amdgpu_debugfs_add_files(adev, amdgpu_debugfs_sa_list, 1); +#else + return 0; +#endif +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c new file mode 100644 index 000000000000..db5422e65ec5 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c @@ -0,0 +1,216 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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. + * + */ + +#include <drm/drmP.h> +#include "amdgpu.h" +#include "amdgpu_ih.h" + +/** + * amdgpu_ih_ring_alloc - allocate memory for the IH ring + * + * @adev: amdgpu_device pointer + * + * Allocate a ring buffer for the interrupt controller. + * Returns 0 for success, errors for failure. + */ +static int amdgpu_ih_ring_alloc(struct amdgpu_device *adev) +{ + int r; + + /* Allocate ring buffer */ + if (adev->irq.ih.ring_obj == NULL) { + r = amdgpu_bo_create(adev, adev->irq.ih.ring_size, + PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_GTT, 0, + NULL, &adev->irq.ih.ring_obj); + if (r) { + DRM_ERROR("amdgpu: failed to create ih ring buffer (%d).\n", r); + return r; + } + r = amdgpu_bo_reserve(adev->irq.ih.ring_obj, false); + if (unlikely(r != 0)) + return r; + r = amdgpu_bo_pin(adev->irq.ih.ring_obj, + AMDGPU_GEM_DOMAIN_GTT, + &adev->irq.ih.gpu_addr); + if (r) { + amdgpu_bo_unreserve(adev->irq.ih.ring_obj); + DRM_ERROR("amdgpu: failed to pin ih ring buffer (%d).\n", r); + return r; + } + r = amdgpu_bo_kmap(adev->irq.ih.ring_obj, + (void **)&adev->irq.ih.ring); + amdgpu_bo_unreserve(adev->irq.ih.ring_obj); + if (r) { + DRM_ERROR("amdgpu: failed to map ih ring buffer (%d).\n", r); + return r; + } + } + return 0; +} + +/** + * amdgpu_ih_ring_init - initialize the IH state + * + * @adev: amdgpu_device pointer + * + * Initializes the IH state and allocates a buffer + * for the IH ring buffer. + * Returns 0 for success, errors for failure. + */ +int amdgpu_ih_ring_init(struct amdgpu_device *adev, unsigned ring_size, + bool use_bus_addr) +{ + u32 rb_bufsz; + int r; + + /* Align ring size */ + rb_bufsz = order_base_2(ring_size / 4); + ring_size = (1 << rb_bufsz) * 4; + adev->irq.ih.ring_size = ring_size; + adev->irq.ih.ptr_mask = adev->irq.ih.ring_size - 1; + adev->irq.ih.rptr = 0; + adev->irq.ih.use_bus_addr = use_bus_addr; + + if (adev->irq.ih.use_bus_addr) { + if (!adev->irq.ih.ring) { + /* add 8 bytes for the rptr/wptr shadows and + * add them to the end of the ring allocation. + */ + adev->irq.ih.ring = kzalloc(adev->irq.ih.ring_size + 8, GFP_KERNEL); + if (adev->irq.ih.ring == NULL) + return -ENOMEM; + adev->irq.ih.rb_dma_addr = pci_map_single(adev->pdev, + (void *)adev->irq.ih.ring, + adev->irq.ih.ring_size, + PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(adev->pdev, adev->irq.ih.rb_dma_addr)) { + dev_err(&adev->pdev->dev, "Failed to DMA MAP the IH RB page\n"); + kfree((void *)adev->irq.ih.ring); + return -ENOMEM; + } + adev->irq.ih.wptr_offs = (adev->irq.ih.ring_size / 4) + 0; + adev->irq.ih.rptr_offs = (adev->irq.ih.ring_size / 4) + 1; + } + return 0; + } else { + r = amdgpu_wb_get(adev, &adev->irq.ih.wptr_offs); + if (r) { + dev_err(adev->dev, "(%d) ih wptr_offs wb alloc failed\n", r); + return r; + } + + r = amdgpu_wb_get(adev, &adev->irq.ih.rptr_offs); + if (r) { + amdgpu_wb_free(adev, adev->irq.ih.wptr_offs); + dev_err(adev->dev, "(%d) ih rptr_offs wb alloc failed\n", r); + return r; + } + + return amdgpu_ih_ring_alloc(adev); + } +} + +/** + * amdgpu_ih_ring_fini - tear down the IH state + * + * @adev: amdgpu_device pointer + * + * Tears down the IH state and frees buffer + * used for the IH ring buffer. + */ +void amdgpu_ih_ring_fini(struct amdgpu_device *adev) +{ + int r; + + if (adev->irq.ih.use_bus_addr) { + if (adev->irq.ih.ring) { + /* add 8 bytes for the rptr/wptr shadows and + * add them to the end of the ring allocation. + */ + pci_unmap_single(adev->pdev, adev->irq.ih.rb_dma_addr, + adev->irq.ih.ring_size + 8, PCI_DMA_BIDIRECTIONAL); + kfree((void *)adev->irq.ih.ring); + adev->irq.ih.ring = NULL; + } + } else { + if (adev->irq.ih.ring_obj) { + r = amdgpu_bo_reserve(adev->irq.ih.ring_obj, false); + if (likely(r == 0)) { + amdgpu_bo_kunmap(adev->irq.ih.ring_obj); + amdgpu_bo_unpin(adev->irq.ih.ring_obj); + amdgpu_bo_unreserve(adev->irq.ih.ring_obj); + } + amdgpu_bo_unref(&adev->irq.ih.ring_obj); + adev->irq.ih.ring = NULL; + adev->irq.ih.ring_obj = NULL; + } + amdgpu_wb_free(adev, adev->irq.ih.wptr_offs); + amdgpu_wb_free(adev, adev->irq.ih.rptr_offs); + } +} + +/** + * amdgpu_ih_process - interrupt handler + * + * @adev: amdgpu_device pointer + * + * Interrupt hander (VI), walk the IH ring. + * Returns irq process return code. + */ +int amdgpu_ih_process(struct amdgpu_device *adev) +{ + struct amdgpu_iv_entry entry; + u32 wptr; + + if (!adev->irq.ih.enabled || adev->shutdown) + return IRQ_NONE; + + wptr = amdgpu_ih_get_wptr(adev); + +restart_ih: + /* is somebody else already processing irqs? */ + if (atomic_xchg(&adev->irq.ih.lock, 1)) + return IRQ_NONE; + + DRM_DEBUG("%s: rptr %d, wptr %d\n", __func__, adev->irq.ih.rptr, wptr); + + /* Order reading of wptr vs. reading of IH ring data */ + rmb(); + + while (adev->irq.ih.rptr != wptr) { + amdgpu_ih_decode_iv(adev, &entry); + adev->irq.ih.rptr &= adev->irq.ih.ptr_mask; + + amdgpu_irq_dispatch(adev, &entry); + } + amdgpu_ih_set_rptr(adev); + atomic_set(&adev->irq.ih.lock, 0); + + /* make sure wptr hasn't changed while processing */ + wptr = amdgpu_ih_get_wptr(adev); + if (wptr != adev->irq.ih.rptr) + goto restart_ih; + + return IRQ_HANDLED; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.h new file mode 100644 index 000000000000..c62b09e555d6 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.h @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_IH_H__ +#define __AMDGPU_IH_H__ + +struct amdgpu_device; + +/* + * R6xx+ IH ring + */ +struct amdgpu_ih_ring { + struct amdgpu_bo *ring_obj; + volatile uint32_t *ring; + unsigned rptr; + unsigned ring_size; + uint64_t gpu_addr; + uint32_t ptr_mask; + atomic_t lock; + bool enabled; + unsigned wptr_offs; + unsigned rptr_offs; + u32 doorbell_index; + bool use_doorbell; + bool use_bus_addr; + dma_addr_t rb_dma_addr; /* only used when use_bus_addr = true */ +}; + +struct amdgpu_iv_entry { + unsigned src_id; + unsigned src_data; + unsigned ring_id; + unsigned vm_id; + unsigned pas_id; +}; + +int amdgpu_ih_ring_init(struct amdgpu_device *adev, unsigned ring_size, + bool use_bus_addr); +void amdgpu_ih_ring_fini(struct amdgpu_device *adev); +int amdgpu_ih_process(struct amdgpu_device *adev); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ioc32.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ioc32.c new file mode 100644 index 000000000000..26482914dc4b --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ioc32.c @@ -0,0 +1,47 @@ +/** + * \file amdgpu_ioc32.c + * + * 32-bit ioctl compatibility routines for the AMDGPU DRM. + * + * \author Paul Mackerras <paulus@samba.org> + * + * Copyright (C) Paul Mackerras 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 <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu_drv.h" + +long amdgpu_kms_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + unsigned int nr = DRM_IOCTL_NR(cmd); + int ret; + + if (nr < DRM_COMMAND_BASE) + return drm_compat_ioctl(filp, cmd, arg); + + ret = amdgpu_drm_ioctl(filp, cmd, arg); + + return ret; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c new file mode 100644 index 000000000000..2187960baf7c --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c @@ -0,0 +1,456 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_ih.h" +#include "atom.h" +#include "amdgpu_connectors.h" + +#include <linux/pm_runtime.h> + +#define AMDGPU_WAIT_IDLE_TIMEOUT 200 + +/* + * Handle hotplug events outside the interrupt handler proper. + */ +/** + * amdgpu_hotplug_work_func - display hotplug work handler + * + * @work: work struct + * + * This is the hot plug event work handler (all asics). + * The work gets scheduled from the irq handler if there + * was a hot plug interrupt. It walks the connector table + * and calls the hotplug handler for each one, then sends + * a drm hotplug event to alert userspace. + */ +static void amdgpu_hotplug_work_func(struct work_struct *work) +{ + struct amdgpu_device *adev = container_of(work, struct amdgpu_device, + hotplug_work); + struct drm_device *dev = adev->ddev; + struct drm_mode_config *mode_config = &dev->mode_config; + struct drm_connector *connector; + + if (mode_config->num_connector) { + list_for_each_entry(connector, &mode_config->connector_list, head) + amdgpu_connector_hotplug(connector); + } + /* Just fire off a uevent and let userspace tell us what to do */ + drm_helper_hpd_irq_event(dev); +} + +/** + * amdgpu_irq_reset_work_func - execute gpu reset + * + * @work: work struct + * + * Execute scheduled gpu reset (cayman+). + * This function is called when the irq handler + * thinks we need a gpu reset. + */ +static void amdgpu_irq_reset_work_func(struct work_struct *work) +{ + struct amdgpu_device *adev = container_of(work, struct amdgpu_device, + reset_work); + + amdgpu_gpu_reset(adev); +} + +/* Disable *all* interrupts */ +static void amdgpu_irq_disable_all(struct amdgpu_device *adev) +{ + unsigned long irqflags; + unsigned i, j; + int r; + + spin_lock_irqsave(&adev->irq.lock, irqflags); + for (i = 0; i < AMDGPU_MAX_IRQ_SRC_ID; ++i) { + struct amdgpu_irq_src *src = adev->irq.sources[i]; + + if (!src || !src->funcs->set || !src->num_types) + continue; + + for (j = 0; j < src->num_types; ++j) { + atomic_set(&src->enabled_types[j], 0); + r = src->funcs->set(adev, src, j, + AMDGPU_IRQ_STATE_DISABLE); + if (r) + DRM_ERROR("error disabling interrupt (%d)\n", + r); + } + } + spin_unlock_irqrestore(&adev->irq.lock, irqflags); +} + +/** + * amdgpu_irq_preinstall - drm irq preinstall callback + * + * @dev: drm dev pointer + * + * Gets the hw ready to enable irqs (all asics). + * This function disables all interrupt sources on the GPU. + */ +void amdgpu_irq_preinstall(struct drm_device *dev) +{ + struct amdgpu_device *adev = dev->dev_private; + + /* Disable *all* interrupts */ + amdgpu_irq_disable_all(adev); + /* Clear bits */ + amdgpu_ih_process(adev); +} + +/** + * amdgpu_irq_postinstall - drm irq preinstall callback + * + * @dev: drm dev pointer + * + * Handles stuff to be done after enabling irqs (all asics). + * Returns 0 on success. + */ +int amdgpu_irq_postinstall(struct drm_device *dev) +{ + dev->max_vblank_count = 0x001fffff; + return 0; +} + +/** + * amdgpu_irq_uninstall - drm irq uninstall callback + * + * @dev: drm dev pointer + * + * This function disables all interrupt sources on the GPU (all asics). + */ +void amdgpu_irq_uninstall(struct drm_device *dev) +{ + struct amdgpu_device *adev = dev->dev_private; + + if (adev == NULL) { + return; + } + amdgpu_irq_disable_all(adev); +} + +/** + * amdgpu_irq_handler - irq handler + * + * @int irq, void *arg: args + * + * This is the irq handler for the amdgpu driver (all asics). + */ +irqreturn_t amdgpu_irq_handler(int irq, void *arg) +{ + struct drm_device *dev = (struct drm_device *) arg; + struct amdgpu_device *adev = dev->dev_private; + irqreturn_t ret; + + ret = amdgpu_ih_process(adev); + if (ret == IRQ_HANDLED) + pm_runtime_mark_last_busy(dev->dev); + return ret; +} + +/** + * amdgpu_msi_ok - asic specific msi checks + * + * @adev: amdgpu device pointer + * + * Handles asic specific MSI checks to determine if + * MSIs should be enabled on a particular chip (all asics). + * Returns true if MSIs should be enabled, false if MSIs + * should not be enabled. + */ +static bool amdgpu_msi_ok(struct amdgpu_device *adev) +{ + /* force MSI on */ + if (amdgpu_msi == 1) + return true; + else if (amdgpu_msi == 0) + return false; + + return true; +} + +/** + * amdgpu_irq_init - init driver interrupt info + * + * @adev: amdgpu device pointer + * + * Sets up the work irq handlers, vblank init, MSIs, etc. (all asics). + * Returns 0 for success, error for failure. + */ +int amdgpu_irq_init(struct amdgpu_device *adev) +{ + int r = 0; + + spin_lock_init(&adev->irq.lock); + r = drm_vblank_init(adev->ddev, adev->mode_info.num_crtc); + if (r) { + return r; + } + /* enable msi */ + adev->irq.msi_enabled = false; + + if (amdgpu_msi_ok(adev)) { + int ret = pci_enable_msi(adev->pdev); + if (!ret) { + adev->irq.msi_enabled = true; + dev_info(adev->dev, "amdgpu: using MSI.\n"); + } + } + + INIT_WORK(&adev->hotplug_work, amdgpu_hotplug_work_func); + INIT_WORK(&adev->reset_work, amdgpu_irq_reset_work_func); + + adev->irq.installed = true; + r = drm_irq_install(adev->ddev, adev->ddev->pdev->irq); + if (r) { + adev->irq.installed = false; + flush_work(&adev->hotplug_work); + return r; + } + + DRM_INFO("amdgpu: irq initialized.\n"); + return 0; +} + +/** + * amdgpu_irq_fini - tear down driver interrupt info + * + * @adev: amdgpu device pointer + * + * Tears down the work irq handlers, vblank handlers, MSIs, etc. (all asics). + */ +void amdgpu_irq_fini(struct amdgpu_device *adev) +{ + unsigned i; + + drm_vblank_cleanup(adev->ddev); + if (adev->irq.installed) { + drm_irq_uninstall(adev->ddev); + adev->irq.installed = false; + if (adev->irq.msi_enabled) + pci_disable_msi(adev->pdev); + flush_work(&adev->hotplug_work); + } + + for (i = 0; i < AMDGPU_MAX_IRQ_SRC_ID; ++i) { + struct amdgpu_irq_src *src = adev->irq.sources[i]; + + if (!src) + continue; + + kfree(src->enabled_types); + src->enabled_types = NULL; + } +} + +/** + * amdgpu_irq_add_id - register irq source + * + * @adev: amdgpu device pointer + * @src_id: source id for this source + * @source: irq source + * + */ +int amdgpu_irq_add_id(struct amdgpu_device *adev, unsigned src_id, + struct amdgpu_irq_src *source) +{ + if (src_id >= AMDGPU_MAX_IRQ_SRC_ID) + return -EINVAL; + + if (adev->irq.sources[src_id] != NULL) + return -EINVAL; + + if (!source->funcs) + return -EINVAL; + + if (source->num_types && !source->enabled_types) { + atomic_t *types; + + types = kcalloc(source->num_types, sizeof(atomic_t), + GFP_KERNEL); + if (!types) + return -ENOMEM; + + source->enabled_types = types; + } + + adev->irq.sources[src_id] = source; + return 0; +} + +/** + * amdgpu_irq_dispatch - dispatch irq to IP blocks + * + * @adev: amdgpu device pointer + * @entry: interrupt vector + * + * Dispatches the irq to the different IP blocks + */ +void amdgpu_irq_dispatch(struct amdgpu_device *adev, + struct amdgpu_iv_entry *entry) +{ + unsigned src_id = entry->src_id; + struct amdgpu_irq_src *src; + int r; + + if (src_id >= AMDGPU_MAX_IRQ_SRC_ID) { + DRM_DEBUG("Invalid src_id in IV: %d\n", src_id); + return; + } + + src = adev->irq.sources[src_id]; + if (!src) { + DRM_DEBUG("Unhandled interrupt src_id: %d\n", src_id); + return; + } + + r = src->funcs->process(adev, src, entry); + if (r) + DRM_ERROR("error processing interrupt (%d)\n", r); +} + +/** + * amdgpu_irq_update - update hw interrupt state + * + * @adev: amdgpu device pointer + * @src: interrupt src you want to enable + * @type: type of interrupt you want to update + * + * Updates the interrupt state for a specific src (all asics). + */ +int amdgpu_irq_update(struct amdgpu_device *adev, + struct amdgpu_irq_src *src, unsigned type) +{ + unsigned long irqflags; + enum amdgpu_interrupt_state state; + int r; + + spin_lock_irqsave(&adev->irq.lock, irqflags); + + /* we need to determine after taking the lock, otherwise + we might disable just enabled interrupts again */ + if (amdgpu_irq_enabled(adev, src, type)) + state = AMDGPU_IRQ_STATE_ENABLE; + else + state = AMDGPU_IRQ_STATE_DISABLE; + + r = src->funcs->set(adev, src, type, state); + spin_unlock_irqrestore(&adev->irq.lock, irqflags); + return r; +} + +/** + * amdgpu_irq_get - enable interrupt + * + * @adev: amdgpu device pointer + * @src: interrupt src you want to enable + * @type: type of interrupt you want to enable + * + * Enables the interrupt type for a specific src (all asics). + */ +int amdgpu_irq_get(struct amdgpu_device *adev, struct amdgpu_irq_src *src, + unsigned type) +{ + if (!adev->ddev->irq_enabled) + return -ENOENT; + + if (type >= src->num_types) + return -EINVAL; + + if (!src->enabled_types || !src->funcs->set) + return -EINVAL; + + if (atomic_inc_return(&src->enabled_types[type]) == 1) + return amdgpu_irq_update(adev, src, type); + + return 0; +} + +bool amdgpu_irq_get_delayed(struct amdgpu_device *adev, + struct amdgpu_irq_src *src, + unsigned type) +{ + if ((type >= src->num_types) || !src->enabled_types) + return false; + return atomic_inc_return(&src->enabled_types[type]) == 1; +} + +/** + * amdgpu_irq_put - disable interrupt + * + * @adev: amdgpu device pointer + * @src: interrupt src you want to disable + * @type: type of interrupt you want to disable + * + * Disables the interrupt type for a specific src (all asics). + */ +int amdgpu_irq_put(struct amdgpu_device *adev, struct amdgpu_irq_src *src, + unsigned type) +{ + if (!adev->ddev->irq_enabled) + return -ENOENT; + + if (type >= src->num_types) + return -EINVAL; + + if (!src->enabled_types || !src->funcs->set) + return -EINVAL; + + if (atomic_dec_and_test(&src->enabled_types[type])) + return amdgpu_irq_update(adev, src, type); + + return 0; +} + +/** + * amdgpu_irq_enabled - test if irq is enabled or not + * + * @adev: amdgpu device pointer + * @idx: interrupt src you want to test + * + * Tests if the given interrupt source is enabled or not + */ +bool amdgpu_irq_enabled(struct amdgpu_device *adev, struct amdgpu_irq_src *src, + unsigned type) +{ + if (!adev->ddev->irq_enabled) + return false; + + if (type >= src->num_types) + return false; + + if (!src->enabled_types || !src->funcs->set) + return false; + + return !!atomic_read(&src->enabled_types[type]); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.h new file mode 100644 index 000000000000..8299795f2b2d --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.h @@ -0,0 +1,92 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_IRQ_H__ +#define __AMDGPU_IRQ_H__ + +#include "amdgpu_ih.h" + +#define AMDGPU_MAX_IRQ_SRC_ID 0x100 + +struct amdgpu_device; +struct amdgpu_iv_entry; + +enum amdgpu_interrupt_state { + AMDGPU_IRQ_STATE_DISABLE, + AMDGPU_IRQ_STATE_ENABLE, +}; + +struct amdgpu_irq_src { + unsigned num_types; + atomic_t *enabled_types; + const struct amdgpu_irq_src_funcs *funcs; +}; + +/* provided by interrupt generating IP blocks */ +struct amdgpu_irq_src_funcs { + int (*set)(struct amdgpu_device *adev, struct amdgpu_irq_src *source, + unsigned type, enum amdgpu_interrupt_state state); + + int (*process)(struct amdgpu_device *adev, + struct amdgpu_irq_src *source, + struct amdgpu_iv_entry *entry); +}; + +struct amdgpu_irq { + bool installed; + spinlock_t lock; + /* interrupt sources */ + struct amdgpu_irq_src *sources[AMDGPU_MAX_IRQ_SRC_ID]; + + /* status, etc. */ + bool msi_enabled; /* msi enabled */ + + /* interrupt ring */ + struct amdgpu_ih_ring ih; + const struct amdgpu_ih_funcs *ih_funcs; +}; + +void amdgpu_irq_preinstall(struct drm_device *dev); +int amdgpu_irq_postinstall(struct drm_device *dev); +void amdgpu_irq_uninstall(struct drm_device *dev); +irqreturn_t amdgpu_irq_handler(int irq, void *arg); + +int amdgpu_irq_init(struct amdgpu_device *adev); +void amdgpu_irq_fini(struct amdgpu_device *adev); +int amdgpu_irq_add_id(struct amdgpu_device *adev, unsigned src_id, + struct amdgpu_irq_src *source); +void amdgpu_irq_dispatch(struct amdgpu_device *adev, + struct amdgpu_iv_entry *entry); +int amdgpu_irq_update(struct amdgpu_device *adev, struct amdgpu_irq_src *src, + unsigned type); +int amdgpu_irq_get(struct amdgpu_device *adev, struct amdgpu_irq_src *src, + unsigned type); +bool amdgpu_irq_get_delayed(struct amdgpu_device *adev, + struct amdgpu_irq_src *src, + unsigned type); +int amdgpu_irq_put(struct amdgpu_device *adev, struct amdgpu_irq_src *src, + unsigned type); +bool amdgpu_irq_enabled(struct amdgpu_device *adev, struct amdgpu_irq_src *src, + unsigned type); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c new file mode 100644 index 000000000000..c271da34998d --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -0,0 +1,674 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <drm/drmP.h> +#include "amdgpu.h" +#include <drm/amdgpu_drm.h> +#include "amdgpu_uvd.h" +#include "amdgpu_vce.h" + +#include <linux/vga_switcheroo.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> + +#if defined(CONFIG_VGA_SWITCHEROO) +bool amdgpu_has_atpx(void); +#else +static inline bool amdgpu_has_atpx(void) { return false; } +#endif + +/** + * amdgpu_driver_unload_kms - Main unload function for KMS. + * + * @dev: drm dev pointer + * + * This is the main unload function for KMS (all asics). + * Returns 0 on success. + */ +int amdgpu_driver_unload_kms(struct drm_device *dev) +{ + struct amdgpu_device *adev = dev->dev_private; + + if (adev == NULL) + return 0; + + if (adev->rmmio == NULL) + goto done_free; + + pm_runtime_get_sync(dev->dev); + + amdgpu_acpi_fini(adev); + + amdgpu_device_fini(adev); + +done_free: + kfree(adev); + dev->dev_private = NULL; + return 0; +} + +/** + * amdgpu_driver_load_kms - Main load function for KMS. + * + * @dev: drm dev pointer + * @flags: device flags + * + * This is the main load function for KMS (all asics). + * Returns 0 on success, error on failure. + */ +int amdgpu_driver_load_kms(struct drm_device *dev, unsigned long flags) +{ + struct amdgpu_device *adev; + int r, acpi_status; + + adev = kzalloc(sizeof(struct amdgpu_device), GFP_KERNEL); + if (adev == NULL) { + return -ENOMEM; + } + dev->dev_private = (void *)adev; + + if ((amdgpu_runtime_pm != 0) && + amdgpu_has_atpx() && + ((flags & AMDGPU_IS_APU) == 0)) + flags |= AMDGPU_IS_PX; + + /* amdgpu_device_init should report only fatal error + * like memory allocation failure or iomapping failure, + * or memory manager initialization failure, it must + * properly initialize the GPU MC controller and permit + * VRAM allocation + */ + r = amdgpu_device_init(adev, dev, dev->pdev, flags); + if (r) { + dev_err(&dev->pdev->dev, "Fatal error during GPU init\n"); + goto out; + } + + /* Call ACPI methods: require modeset init + * but failure is not fatal + */ + if (!r) { + acpi_status = amdgpu_acpi_init(adev); + if (acpi_status) + dev_dbg(&dev->pdev->dev, + "Error during ACPI methods call\n"); + } + + if (amdgpu_device_is_px(dev)) { + pm_runtime_use_autosuspend(dev->dev); + pm_runtime_set_autosuspend_delay(dev->dev, 5000); + pm_runtime_set_active(dev->dev); + pm_runtime_allow(dev->dev); + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + } + +out: + if (r) + amdgpu_driver_unload_kms(dev); + + + return r; +} + +/* + * Userspace get information ioctl + */ +/** + * amdgpu_info_ioctl - answer a device specific request. + * + * @adev: amdgpu device pointer + * @data: request object + * @filp: drm filp + * + * This function is used to pass device specific parameters to the userspace + * drivers. Examples include: pci device id, pipeline parms, tiling params, + * etc. (all asics). + * Returns 0 on success, -EINVAL on failure. + */ +static int amdgpu_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp) +{ + struct amdgpu_device *adev = dev->dev_private; + struct drm_amdgpu_info *info = data; + struct amdgpu_mode_info *minfo = &adev->mode_info; + void __user *out = (void __user *)(long)info->return_pointer; + uint32_t size = info->return_size; + struct drm_crtc *crtc; + uint32_t ui32 = 0; + uint64_t ui64 = 0; + int i, found; + + if (!info->return_size || !info->return_pointer) + return -EINVAL; + + switch (info->query) { + case AMDGPU_INFO_ACCEL_WORKING: + ui32 = adev->accel_working; + return copy_to_user(out, &ui32, min(size, 4u)) ? -EFAULT : 0; + case AMDGPU_INFO_CRTC_FROM_ID: + for (i = 0, found = 0; i < adev->mode_info.num_crtc; i++) { + crtc = (struct drm_crtc *)minfo->crtcs[i]; + if (crtc && crtc->base.id == info->mode_crtc.id) { + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + ui32 = amdgpu_crtc->crtc_id; + found = 1; + break; + } + } + if (!found) { + DRM_DEBUG_KMS("unknown crtc id %d\n", info->mode_crtc.id); + return -EINVAL; + } + return copy_to_user(out, &ui32, min(size, 4u)) ? -EFAULT : 0; + case AMDGPU_INFO_HW_IP_INFO: { + struct drm_amdgpu_info_hw_ip ip = {}; + enum amdgpu_ip_block_type type; + uint32_t ring_mask = 0; + + if (info->query_hw_ip.ip_instance >= AMDGPU_HW_IP_INSTANCE_MAX_COUNT) + return -EINVAL; + + switch (info->query_hw_ip.type) { + case AMDGPU_HW_IP_GFX: + type = AMDGPU_IP_BLOCK_TYPE_GFX; + for (i = 0; i < adev->gfx.num_gfx_rings; i++) + ring_mask |= ((adev->gfx.gfx_ring[i].ready ? 1 : 0) << i); + break; + case AMDGPU_HW_IP_COMPUTE: + type = AMDGPU_IP_BLOCK_TYPE_GFX; + for (i = 0; i < adev->gfx.num_compute_rings; i++) + ring_mask |= ((adev->gfx.compute_ring[i].ready ? 1 : 0) << i); + break; + case AMDGPU_HW_IP_DMA: + type = AMDGPU_IP_BLOCK_TYPE_SDMA; + ring_mask = adev->sdma[0].ring.ready ? 1 : 0; + ring_mask |= ((adev->sdma[1].ring.ready ? 1 : 0) << 1); + break; + case AMDGPU_HW_IP_UVD: + type = AMDGPU_IP_BLOCK_TYPE_UVD; + ring_mask = adev->uvd.ring.ready ? 1 : 0; + break; + case AMDGPU_HW_IP_VCE: + type = AMDGPU_IP_BLOCK_TYPE_VCE; + for (i = 0; i < AMDGPU_MAX_VCE_RINGS; i++) + ring_mask |= ((adev->vce.ring[i].ready ? 1 : 0) << i); + break; + default: + return -EINVAL; + } + + for (i = 0; i < adev->num_ip_blocks; i++) { + if (adev->ip_blocks[i].type == type && + adev->ip_block_enabled[i]) { + ip.hw_ip_version_major = adev->ip_blocks[i].major; + ip.hw_ip_version_minor = adev->ip_blocks[i].minor; + ip.capabilities_flags = 0; + ip.available_rings = ring_mask; + break; + } + } + return copy_to_user(out, &ip, + min((size_t)size, sizeof(ip))) ? -EFAULT : 0; + } + case AMDGPU_INFO_HW_IP_COUNT: { + enum amdgpu_ip_block_type type; + uint32_t count = 0; + + switch (info->query_hw_ip.type) { + case AMDGPU_HW_IP_GFX: + type = AMDGPU_IP_BLOCK_TYPE_GFX; + break; + case AMDGPU_HW_IP_COMPUTE: + type = AMDGPU_IP_BLOCK_TYPE_GFX; + break; + case AMDGPU_HW_IP_DMA: + type = AMDGPU_IP_BLOCK_TYPE_SDMA; + break; + case AMDGPU_HW_IP_UVD: + type = AMDGPU_IP_BLOCK_TYPE_UVD; + break; + case AMDGPU_HW_IP_VCE: + type = AMDGPU_IP_BLOCK_TYPE_VCE; + break; + default: + return -EINVAL; + } + + for (i = 0; i < adev->num_ip_blocks; i++) + if (adev->ip_blocks[i].type == type && + adev->ip_block_enabled[i] && + count < AMDGPU_HW_IP_INSTANCE_MAX_COUNT) + count++; + + return copy_to_user(out, &count, min(size, 4u)) ? -EFAULT : 0; + } + case AMDGPU_INFO_TIMESTAMP: + ui64 = amdgpu_asic_get_gpu_clock_counter(adev); + return copy_to_user(out, &ui64, min(size, 8u)) ? -EFAULT : 0; + case AMDGPU_INFO_FW_VERSION: { + struct drm_amdgpu_info_firmware fw_info; + + /* We only support one instance of each IP block right now. */ + if (info->query_fw.ip_instance != 0) + return -EINVAL; + + switch (info->query_fw.fw_type) { + case AMDGPU_INFO_FW_VCE: + fw_info.ver = adev->vce.fw_version; + fw_info.feature = adev->vce.fb_version; + break; + case AMDGPU_INFO_FW_UVD: + fw_info.ver = 0; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_GMC: + fw_info.ver = adev->mc.fw_version; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_GFX_ME: + fw_info.ver = adev->gfx.me_fw_version; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_GFX_PFP: + fw_info.ver = adev->gfx.pfp_fw_version; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_GFX_CE: + fw_info.ver = adev->gfx.ce_fw_version; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_GFX_RLC: + fw_info.ver = adev->gfx.rlc_fw_version; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_GFX_MEC: + if (info->query_fw.index == 0) + fw_info.ver = adev->gfx.mec_fw_version; + else if (info->query_fw.index == 1) + fw_info.ver = adev->gfx.mec2_fw_version; + else + return -EINVAL; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_SMC: + fw_info.ver = adev->pm.fw_version; + fw_info.feature = 0; + break; + case AMDGPU_INFO_FW_SDMA: + if (info->query_fw.index >= 2) + return -EINVAL; + fw_info.ver = adev->sdma[info->query_fw.index].fw_version; + fw_info.feature = 0; + break; + default: + return -EINVAL; + } + return copy_to_user(out, &fw_info, + min((size_t)size, sizeof(fw_info))) ? -EFAULT : 0; + } + case AMDGPU_INFO_NUM_BYTES_MOVED: + ui64 = atomic64_read(&adev->num_bytes_moved); + return copy_to_user(out, &ui64, min(size, 8u)) ? -EFAULT : 0; + case AMDGPU_INFO_VRAM_USAGE: + ui64 = atomic64_read(&adev->vram_usage); + return copy_to_user(out, &ui64, min(size, 8u)) ? -EFAULT : 0; + case AMDGPU_INFO_VIS_VRAM_USAGE: + ui64 = atomic64_read(&adev->vram_vis_usage); + return copy_to_user(out, &ui64, min(size, 8u)) ? -EFAULT : 0; + case AMDGPU_INFO_GTT_USAGE: + ui64 = atomic64_read(&adev->gtt_usage); + return copy_to_user(out, &ui64, min(size, 8u)) ? -EFAULT : 0; + case AMDGPU_INFO_GDS_CONFIG: { + struct drm_amdgpu_info_gds gds_info; + + gds_info.gds_gfx_partition_size = adev->gds.mem.gfx_partition_size >> AMDGPU_GDS_SHIFT; + gds_info.compute_partition_size = adev->gds.mem.cs_partition_size >> AMDGPU_GDS_SHIFT; + gds_info.gds_total_size = adev->gds.mem.total_size >> AMDGPU_GDS_SHIFT; + gds_info.gws_per_gfx_partition = adev->gds.gws.gfx_partition_size >> AMDGPU_GWS_SHIFT; + gds_info.gws_per_compute_partition = adev->gds.gws.cs_partition_size >> AMDGPU_GWS_SHIFT; + gds_info.oa_per_gfx_partition = adev->gds.oa.gfx_partition_size >> AMDGPU_OA_SHIFT; + gds_info.oa_per_compute_partition = adev->gds.oa.cs_partition_size >> AMDGPU_OA_SHIFT; + return copy_to_user(out, &gds_info, + min((size_t)size, sizeof(gds_info))) ? -EFAULT : 0; + } + case AMDGPU_INFO_VRAM_GTT: { + struct drm_amdgpu_info_vram_gtt vram_gtt; + + vram_gtt.vram_size = adev->mc.real_vram_size; + vram_gtt.vram_cpu_accessible_size = adev->mc.visible_vram_size; + vram_gtt.vram_cpu_accessible_size -= adev->vram_pin_size; + vram_gtt.gtt_size = adev->mc.gtt_size; + vram_gtt.gtt_size -= adev->gart_pin_size; + return copy_to_user(out, &vram_gtt, + min((size_t)size, sizeof(vram_gtt))) ? -EFAULT : 0; + } + case AMDGPU_INFO_READ_MMR_REG: { + unsigned n, alloc_size = info->read_mmr_reg.count * 4; + uint32_t *regs; + unsigned se_num = (info->read_mmr_reg.instance >> + AMDGPU_INFO_MMR_SE_INDEX_SHIFT) & + AMDGPU_INFO_MMR_SE_INDEX_MASK; + unsigned sh_num = (info->read_mmr_reg.instance >> + AMDGPU_INFO_MMR_SH_INDEX_SHIFT) & + AMDGPU_INFO_MMR_SH_INDEX_MASK; + + /* set full masks if the userspace set all bits + * in the bitfields */ + if (se_num == AMDGPU_INFO_MMR_SE_INDEX_MASK) + se_num = 0xffffffff; + if (sh_num == AMDGPU_INFO_MMR_SH_INDEX_MASK) + sh_num = 0xffffffff; + + regs = kmalloc(alloc_size, GFP_KERNEL); + if (!regs) + return -ENOMEM; + + for (i = 0; i < info->read_mmr_reg.count; i++) + if (amdgpu_asic_read_register(adev, se_num, sh_num, + info->read_mmr_reg.dword_offset + i, + ®s[i])) { + DRM_DEBUG_KMS("unallowed offset %#x\n", + info->read_mmr_reg.dword_offset + i); + kfree(regs); + return -EFAULT; + } + n = copy_to_user(out, regs, min(size, alloc_size)); + kfree(regs); + return n ? -EFAULT : 0; + } + case AMDGPU_INFO_DEV_INFO: { + struct drm_amdgpu_info_device dev_info; + struct amdgpu_cu_info cu_info; + + dev_info.device_id = dev->pdev->device; + dev_info.chip_rev = adev->rev_id; + dev_info.external_rev = adev->external_rev_id; + dev_info.pci_rev = dev->pdev->revision; + dev_info.family = adev->family; + dev_info.num_shader_engines = adev->gfx.config.max_shader_engines; + dev_info.num_shader_arrays_per_engine = adev->gfx.config.max_sh_per_se; + /* return all clocks in KHz */ + dev_info.gpu_counter_freq = amdgpu_asic_get_xclk(adev) * 10; + if (adev->pm.dpm_enabled) + dev_info.max_engine_clock = + adev->pm.dpm.dyn_state.max_clock_voltage_on_ac.sclk * 10; + else + dev_info.max_engine_clock = adev->pm.default_sclk * 10; + dev_info.enabled_rb_pipes_mask = adev->gfx.config.backend_enable_mask; + dev_info.num_rb_pipes = adev->gfx.config.max_backends_per_se * + adev->gfx.config.max_shader_engines; + dev_info.num_hw_gfx_contexts = adev->gfx.config.max_hw_contexts; + dev_info._pad = 0; + dev_info.ids_flags = 0; + if (adev->flags & AMDGPU_IS_APU) + dev_info.ids_flags |= AMDGPU_IDS_FLAGS_FUSION; + dev_info.virtual_address_offset = AMDGPU_VA_RESERVED_SIZE; + dev_info.virtual_address_alignment = max(PAGE_SIZE, 0x10000UL); + dev_info.pte_fragment_size = (1 << AMDGPU_LOG2_PAGES_PER_FRAG) * + AMDGPU_GPU_PAGE_SIZE; + dev_info.gart_page_size = AMDGPU_GPU_PAGE_SIZE; + + amdgpu_asic_get_cu_info(adev, &cu_info); + dev_info.cu_active_number = cu_info.number; + dev_info.cu_ao_mask = cu_info.ao_cu_mask; + memcpy(&dev_info.cu_bitmap[0], &cu_info.bitmap[0], sizeof(cu_info.bitmap)); + + return copy_to_user(out, &dev_info, + min((size_t)size, sizeof(dev_info))) ? -EFAULT : 0; + } + default: + DRM_DEBUG_KMS("Invalid request %d\n", info->query); + return -EINVAL; + } + return 0; +} + + +/* + * Outdated mess for old drm with Xorg being in charge (void function now). + */ +/** + * amdgpu_driver_firstopen_kms - drm callback for last close + * + * @dev: drm dev pointer + * + * Switch vga switcheroo state after last close (all asics). + */ +void amdgpu_driver_lastclose_kms(struct drm_device *dev) +{ + vga_switcheroo_process_delayed_switch(); +} + +/** + * amdgpu_driver_open_kms - drm callback for open + * + * @dev: drm dev pointer + * @file_priv: drm file + * + * On device open, init vm on cayman+ (all asics). + * Returns 0 on success, error on failure. + */ +int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) +{ + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_fpriv *fpriv; + int r; + + file_priv->driver_priv = NULL; + + r = pm_runtime_get_sync(dev->dev); + if (r < 0) + return r; + + fpriv = kzalloc(sizeof(*fpriv), GFP_KERNEL); + if (unlikely(!fpriv)) + return -ENOMEM; + + r = amdgpu_vm_init(adev, &fpriv->vm); + if (r) + goto error_free; + + mutex_init(&fpriv->bo_list_lock); + idr_init(&fpriv->bo_list_handles); + + /* init context manager */ + mutex_init(&fpriv->ctx_mgr.hlock); + idr_init(&fpriv->ctx_mgr.ctx_handles); + fpriv->ctx_mgr.adev = adev; + + file_priv->driver_priv = fpriv; + + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + return 0; + +error_free: + kfree(fpriv); + + return r; +} + +/** + * amdgpu_driver_postclose_kms - drm callback for post close + * + * @dev: drm dev pointer + * @file_priv: drm file + * + * On device post close, tear down vm on cayman+ (all asics). + */ +void amdgpu_driver_postclose_kms(struct drm_device *dev, + struct drm_file *file_priv) +{ + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_fpriv *fpriv = file_priv->driver_priv; + struct amdgpu_bo_list *list; + int handle; + + if (!fpriv) + return; + + amdgpu_vm_fini(adev, &fpriv->vm); + + idr_for_each_entry(&fpriv->bo_list_handles, list, handle) + amdgpu_bo_list_free(list); + + idr_destroy(&fpriv->bo_list_handles); + mutex_destroy(&fpriv->bo_list_lock); + + /* release context */ + amdgpu_ctx_fini(fpriv); + + kfree(fpriv); + file_priv->driver_priv = NULL; +} + +/** + * amdgpu_driver_preclose_kms - drm callback for pre close + * + * @dev: drm dev pointer + * @file_priv: drm file + * + * On device pre close, tear down hyperz and cmask filps on r1xx-r5xx + * (all asics). + */ +void amdgpu_driver_preclose_kms(struct drm_device *dev, + struct drm_file *file_priv) +{ + struct amdgpu_device *adev = dev->dev_private; + + amdgpu_uvd_free_handles(adev, file_priv); + amdgpu_vce_free_handles(adev, file_priv); +} + +/* + * VBlank related functions. + */ +/** + * amdgpu_get_vblank_counter_kms - get frame count + * + * @dev: drm dev pointer + * @crtc: crtc to get the frame count from + * + * Gets the frame count on the requested crtc (all asics). + * Returns frame count on success, -EINVAL on failure. + */ +u32 amdgpu_get_vblank_counter_kms(struct drm_device *dev, int crtc) +{ + struct amdgpu_device *adev = dev->dev_private; + + if (crtc < 0 || crtc >= adev->mode_info.num_crtc) { + DRM_ERROR("Invalid crtc %d\n", crtc); + return -EINVAL; + } + + return amdgpu_display_vblank_get_counter(adev, crtc); +} + +/** + * amdgpu_enable_vblank_kms - enable vblank interrupt + * + * @dev: drm dev pointer + * @crtc: crtc to enable vblank interrupt for + * + * Enable the interrupt on the requested crtc (all asics). + * Returns 0 on success, -EINVAL on failure. + */ +int amdgpu_enable_vblank_kms(struct drm_device *dev, int crtc) +{ + struct amdgpu_device *adev = dev->dev_private; + int idx = amdgpu_crtc_idx_to_irq_type(adev, crtc); + + return amdgpu_irq_get(adev, &adev->crtc_irq, idx); +} + +/** + * amdgpu_disable_vblank_kms - disable vblank interrupt + * + * @dev: drm dev pointer + * @crtc: crtc to disable vblank interrupt for + * + * Disable the interrupt on the requested crtc (all asics). + */ +void amdgpu_disable_vblank_kms(struct drm_device *dev, int crtc) +{ + struct amdgpu_device *adev = dev->dev_private; + int idx = amdgpu_crtc_idx_to_irq_type(adev, crtc); + + amdgpu_irq_put(adev, &adev->crtc_irq, idx); +} + +/** + * amdgpu_get_vblank_timestamp_kms - get vblank timestamp + * + * @dev: drm dev pointer + * @crtc: crtc to get the timestamp for + * @max_error: max error + * @vblank_time: time value + * @flags: flags passed to the driver + * + * Gets the timestamp on the requested crtc based on the + * scanout position. (all asics). + * Returns postive status flags on success, negative error on failure. + */ +int amdgpu_get_vblank_timestamp_kms(struct drm_device *dev, int crtc, + int *max_error, + struct timeval *vblank_time, + unsigned flags) +{ + struct drm_crtc *drmcrtc; + struct amdgpu_device *adev = dev->dev_private; + + if (crtc < 0 || crtc >= dev->num_crtcs) { + DRM_ERROR("Invalid crtc %d\n", crtc); + return -EINVAL; + } + + /* Get associated drm_crtc: */ + drmcrtc = &adev->mode_info.crtcs[crtc]->base; + + /* Helper routine in DRM core does all the work: */ + return drm_calc_vbltimestamp_from_scanoutpos(dev, crtc, max_error, + vblank_time, flags, + drmcrtc, &drmcrtc->hwmode); +} + +const struct drm_ioctl_desc amdgpu_ioctls_kms[] = { + DRM_IOCTL_DEF_DRV(AMDGPU_GEM_CREATE, amdgpu_gem_create_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_CTX, amdgpu_ctx_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_BO_LIST, amdgpu_bo_list_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + /* KMS */ + DRM_IOCTL_DEF_DRV(AMDGPU_GEM_MMAP, amdgpu_gem_mmap_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_GEM_WAIT_IDLE, amdgpu_gem_wait_idle_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_CS, amdgpu_cs_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_INFO, amdgpu_info_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_WAIT_CS, amdgpu_cs_wait_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_GEM_METADATA, amdgpu_gem_metadata_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_GEM_VA, amdgpu_gem_va_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_GEM_OP, amdgpu_gem_op_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(AMDGPU_GEM_USERPTR, amdgpu_gem_userptr_ioctl, DRM_AUTH|DRM_UNLOCKED|DRM_RENDER_ALLOW), +}; +int amdgpu_max_kms_ioctl = ARRAY_SIZE(amdgpu_ioctls_kms); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mn.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mn.c new file mode 100644 index 000000000000..e94429185660 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mn.c @@ -0,0 +1,319 @@ +/* + * Copyright 2014 Advanced Micro Devices, Inc. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Christian König <christian.koenig@amd.com> + */ + +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/mmu_notifier.h> +#include <drm/drmP.h> +#include <drm/drm.h> + +#include "amdgpu.h" + +struct amdgpu_mn { + /* constant after initialisation */ + struct amdgpu_device *adev; + struct mm_struct *mm; + struct mmu_notifier mn; + + /* only used on destruction */ + struct work_struct work; + + /* protected by adev->mn_lock */ + struct hlist_node node; + + /* objects protected by lock */ + struct mutex lock; + struct rb_root objects; +}; + +struct amdgpu_mn_node { + struct interval_tree_node it; + struct list_head bos; +}; + +/** + * amdgpu_mn_destroy - destroy the rmn + * + * @work: previously sheduled work item + * + * Lazy destroys the notifier from a work item + */ +static void amdgpu_mn_destroy(struct work_struct *work) +{ + struct amdgpu_mn *rmn = container_of(work, struct amdgpu_mn, work); + struct amdgpu_device *adev = rmn->adev; + struct amdgpu_mn_node *node, *next_node; + struct amdgpu_bo *bo, *next_bo; + + mutex_lock(&adev->mn_lock); + mutex_lock(&rmn->lock); + hash_del(&rmn->node); + rbtree_postorder_for_each_entry_safe(node, next_node, &rmn->objects, + it.rb) { + + interval_tree_remove(&node->it, &rmn->objects); + list_for_each_entry_safe(bo, next_bo, &node->bos, mn_list) { + bo->mn = NULL; + list_del_init(&bo->mn_list); + } + kfree(node); + } + mutex_unlock(&rmn->lock); + mutex_unlock(&adev->mn_lock); + mmu_notifier_unregister(&rmn->mn, rmn->mm); + kfree(rmn); +} + +/** + * amdgpu_mn_release - callback to notify about mm destruction + * + * @mn: our notifier + * @mn: the mm this callback is about + * + * Shedule a work item to lazy destroy our notifier. + */ +static void amdgpu_mn_release(struct mmu_notifier *mn, + struct mm_struct *mm) +{ + struct amdgpu_mn *rmn = container_of(mn, struct amdgpu_mn, mn); + INIT_WORK(&rmn->work, amdgpu_mn_destroy); + schedule_work(&rmn->work); +} + +/** + * amdgpu_mn_invalidate_range_start - callback to notify about mm change + * + * @mn: our notifier + * @mn: the mm this callback is about + * @start: start of updated range + * @end: end of updated range + * + * We block for all BOs between start and end to be idle and + * unmap them by move them into system domain again. + */ +static void amdgpu_mn_invalidate_range_start(struct mmu_notifier *mn, + struct mm_struct *mm, + unsigned long start, + unsigned long end) +{ + struct amdgpu_mn *rmn = container_of(mn, struct amdgpu_mn, mn); + struct interval_tree_node *it; + + /* notification is exclusive, but interval is inclusive */ + end -= 1; + + mutex_lock(&rmn->lock); + + it = interval_tree_iter_first(&rmn->objects, start, end); + while (it) { + struct amdgpu_mn_node *node; + struct amdgpu_bo *bo; + int r; + + node = container_of(it, struct amdgpu_mn_node, it); + it = interval_tree_iter_next(it, start, end); + + list_for_each_entry(bo, &node->bos, mn_list) { + + r = amdgpu_bo_reserve(bo, true); + if (r) { + DRM_ERROR("(%d) failed to reserve user bo\n", r); + continue; + } + + r = reservation_object_wait_timeout_rcu(bo->tbo.resv, + true, false, MAX_SCHEDULE_TIMEOUT); + if (r) + DRM_ERROR("(%d) failed to wait for user bo\n", r); + + amdgpu_ttm_placement_from_domain(bo, AMDGPU_GEM_DOMAIN_CPU); + r = ttm_bo_validate(&bo->tbo, &bo->placement, false, false); + if (r) + DRM_ERROR("(%d) failed to validate user bo\n", r); + + amdgpu_bo_unreserve(bo); + } + } + + mutex_unlock(&rmn->lock); +} + +static const struct mmu_notifier_ops amdgpu_mn_ops = { + .release = amdgpu_mn_release, + .invalidate_range_start = amdgpu_mn_invalidate_range_start, +}; + +/** + * amdgpu_mn_get - create notifier context + * + * @adev: amdgpu device pointer + * + * Creates a notifier context for current->mm. + */ +static struct amdgpu_mn *amdgpu_mn_get(struct amdgpu_device *adev) +{ + struct mm_struct *mm = current->mm; + struct amdgpu_mn *rmn; + int r; + + down_write(&mm->mmap_sem); + mutex_lock(&adev->mn_lock); + + hash_for_each_possible(adev->mn_hash, rmn, node, (unsigned long)mm) + if (rmn->mm == mm) + goto release_locks; + + rmn = kzalloc(sizeof(*rmn), GFP_KERNEL); + if (!rmn) { + rmn = ERR_PTR(-ENOMEM); + goto release_locks; + } + + rmn->adev = adev; + rmn->mm = mm; + rmn->mn.ops = &amdgpu_mn_ops; + mutex_init(&rmn->lock); + rmn->objects = RB_ROOT; + + r = __mmu_notifier_register(&rmn->mn, mm); + if (r) + goto free_rmn; + + hash_add(adev->mn_hash, &rmn->node, (unsigned long)mm); + +release_locks: + mutex_unlock(&adev->mn_lock); + up_write(&mm->mmap_sem); + + return rmn; + +free_rmn: + mutex_unlock(&adev->mn_lock); + up_write(&mm->mmap_sem); + kfree(rmn); + + return ERR_PTR(r); +} + +/** + * amdgpu_mn_register - register a BO for notifier updates + * + * @bo: amdgpu buffer object + * @addr: userptr addr we should monitor + * + * Registers an MMU notifier for the given BO at the specified address. + * Returns 0 on success, -ERRNO if anything goes wrong. + */ +int amdgpu_mn_register(struct amdgpu_bo *bo, unsigned long addr) +{ + unsigned long end = addr + amdgpu_bo_size(bo) - 1; + struct amdgpu_device *adev = bo->adev; + struct amdgpu_mn *rmn; + struct amdgpu_mn_node *node = NULL; + struct list_head bos; + struct interval_tree_node *it; + + rmn = amdgpu_mn_get(adev); + if (IS_ERR(rmn)) + return PTR_ERR(rmn); + + INIT_LIST_HEAD(&bos); + + mutex_lock(&rmn->lock); + + while ((it = interval_tree_iter_first(&rmn->objects, addr, end))) { + kfree(node); + node = container_of(it, struct amdgpu_mn_node, it); + interval_tree_remove(&node->it, &rmn->objects); + addr = min(it->start, addr); + end = max(it->last, end); + list_splice(&node->bos, &bos); + } + + if (!node) { + node = kmalloc(sizeof(struct amdgpu_mn_node), GFP_KERNEL); + if (!node) { + mutex_unlock(&rmn->lock); + return -ENOMEM; + } + } + + bo->mn = rmn; + + node->it.start = addr; + node->it.last = end; + INIT_LIST_HEAD(&node->bos); + list_splice(&bos, &node->bos); + list_add(&bo->mn_list, &node->bos); + + interval_tree_insert(&node->it, &rmn->objects); + + mutex_unlock(&rmn->lock); + + return 0; +} + +/** + * amdgpu_mn_unregister - unregister a BO for notifier updates + * + * @bo: amdgpu buffer object + * + * Remove any registration of MMU notifier updates from the buffer object. + */ +void amdgpu_mn_unregister(struct amdgpu_bo *bo) +{ + struct amdgpu_device *adev = bo->adev; + struct amdgpu_mn *rmn; + struct list_head *head; + + mutex_lock(&adev->mn_lock); + rmn = bo->mn; + if (rmn == NULL) { + mutex_unlock(&adev->mn_lock); + return; + } + + mutex_lock(&rmn->lock); + /* save the next list entry for later */ + head = bo->mn_list.next; + + bo->mn = NULL; + list_del(&bo->mn_list); + + if (list_empty(head)) { + struct amdgpu_mn_node *node; + node = container_of(head, struct amdgpu_mn_node, bos); + interval_tree_remove(&node->it, &rmn->objects); + kfree(node); + } + + mutex_unlock(&rmn->lock); + mutex_unlock(&adev->mn_lock); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h new file mode 100644 index 000000000000..64efe5b52e65 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -0,0 +1,586 @@ +/* + * Copyright 2000 ATI Technologies Inc., Markham, Ontario, and + * VA Linux Systems Inc., Fremont, California. + * Copyright 2008 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. + * + * Original Authors: + * Kevin E. Martin, Rickard E. Faith, Alan Hourihane + * + * Kernel port Author: Dave Airlie + */ + +#ifndef AMDGPU_MODE_H +#define AMDGPU_MODE_H + +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_fixed.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_plane_helper.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +struct amdgpu_bo; +struct amdgpu_device; +struct amdgpu_encoder; +struct amdgpu_router; +struct amdgpu_hpd; + +#define to_amdgpu_crtc(x) container_of(x, struct amdgpu_crtc, base) +#define to_amdgpu_connector(x) container_of(x, struct amdgpu_connector, base) +#define to_amdgpu_encoder(x) container_of(x, struct amdgpu_encoder, base) +#define to_amdgpu_framebuffer(x) container_of(x, struct amdgpu_framebuffer, base) + +#define AMDGPU_MAX_HPD_PINS 6 +#define AMDGPU_MAX_CRTCS 6 +#define AMDGPU_MAX_AFMT_BLOCKS 7 + +enum amdgpu_rmx_type { + RMX_OFF, + RMX_FULL, + RMX_CENTER, + RMX_ASPECT +}; + +enum amdgpu_underscan_type { + UNDERSCAN_OFF, + UNDERSCAN_ON, + UNDERSCAN_AUTO, +}; + +#define AMDGPU_HPD_CONNECT_INT_DELAY_IN_MS 50 +#define AMDGPU_HPD_DISCONNECT_INT_DELAY_IN_MS 10 + +enum amdgpu_hpd_id { + AMDGPU_HPD_1 = 0, + AMDGPU_HPD_2, + AMDGPU_HPD_3, + AMDGPU_HPD_4, + AMDGPU_HPD_5, + AMDGPU_HPD_6, + AMDGPU_HPD_LAST, + AMDGPU_HPD_NONE = 0xff, +}; + +enum amdgpu_crtc_irq { + AMDGPU_CRTC_IRQ_VBLANK1 = 0, + AMDGPU_CRTC_IRQ_VBLANK2, + AMDGPU_CRTC_IRQ_VBLANK3, + AMDGPU_CRTC_IRQ_VBLANK4, + AMDGPU_CRTC_IRQ_VBLANK5, + AMDGPU_CRTC_IRQ_VBLANK6, + AMDGPU_CRTC_IRQ_VLINE1, + AMDGPU_CRTC_IRQ_VLINE2, + AMDGPU_CRTC_IRQ_VLINE3, + AMDGPU_CRTC_IRQ_VLINE4, + AMDGPU_CRTC_IRQ_VLINE5, + AMDGPU_CRTC_IRQ_VLINE6, + AMDGPU_CRTC_IRQ_LAST, + AMDGPU_CRTC_IRQ_NONE = 0xff +}; + +enum amdgpu_pageflip_irq { + AMDGPU_PAGEFLIP_IRQ_D1 = 0, + AMDGPU_PAGEFLIP_IRQ_D2, + AMDGPU_PAGEFLIP_IRQ_D3, + AMDGPU_PAGEFLIP_IRQ_D4, + AMDGPU_PAGEFLIP_IRQ_D5, + AMDGPU_PAGEFLIP_IRQ_D6, + AMDGPU_PAGEFLIP_IRQ_LAST, + AMDGPU_PAGEFLIP_IRQ_NONE = 0xff +}; + +enum amdgpu_flip_status { + AMDGPU_FLIP_NONE, + AMDGPU_FLIP_PENDING, + AMDGPU_FLIP_SUBMITTED +}; + +#define AMDGPU_MAX_I2C_BUS 16 + +/* amdgpu gpio-based i2c + * 1. "mask" reg and bits + * grabs the gpio pins for software use + * 0=not held 1=held + * 2. "a" reg and bits + * output pin value + * 0=low 1=high + * 3. "en" reg and bits + * sets the pin direction + * 0=input 1=output + * 4. "y" reg and bits + * input pin value + * 0=low 1=high + */ +struct amdgpu_i2c_bus_rec { + bool valid; + /* id used by atom */ + uint8_t i2c_id; + /* id used by atom */ + enum amdgpu_hpd_id hpd; + /* can be used with hw i2c engine */ + bool hw_capable; + /* uses multi-media i2c engine */ + bool mm_i2c; + /* regs and bits */ + uint32_t mask_clk_reg; + uint32_t mask_data_reg; + uint32_t a_clk_reg; + uint32_t a_data_reg; + uint32_t en_clk_reg; + uint32_t en_data_reg; + uint32_t y_clk_reg; + uint32_t y_data_reg; + uint32_t mask_clk_mask; + uint32_t mask_data_mask; + uint32_t a_clk_mask; + uint32_t a_data_mask; + uint32_t en_clk_mask; + uint32_t en_data_mask; + uint32_t y_clk_mask; + uint32_t y_data_mask; +}; + +#define AMDGPU_MAX_BIOS_CONNECTOR 16 + +/* pll flags */ +#define AMDGPU_PLL_USE_BIOS_DIVS (1 << 0) +#define AMDGPU_PLL_NO_ODD_POST_DIV (1 << 1) +#define AMDGPU_PLL_USE_REF_DIV (1 << 2) +#define AMDGPU_PLL_LEGACY (1 << 3) +#define AMDGPU_PLL_PREFER_LOW_REF_DIV (1 << 4) +#define AMDGPU_PLL_PREFER_HIGH_REF_DIV (1 << 5) +#define AMDGPU_PLL_PREFER_LOW_FB_DIV (1 << 6) +#define AMDGPU_PLL_PREFER_HIGH_FB_DIV (1 << 7) +#define AMDGPU_PLL_PREFER_LOW_POST_DIV (1 << 8) +#define AMDGPU_PLL_PREFER_HIGH_POST_DIV (1 << 9) +#define AMDGPU_PLL_USE_FRAC_FB_DIV (1 << 10) +#define AMDGPU_PLL_PREFER_CLOSEST_LOWER (1 << 11) +#define AMDGPU_PLL_USE_POST_DIV (1 << 12) +#define AMDGPU_PLL_IS_LCD (1 << 13) +#define AMDGPU_PLL_PREFER_MINM_OVER_MAXP (1 << 14) + +struct amdgpu_pll { + /* reference frequency */ + uint32_t reference_freq; + + /* fixed dividers */ + uint32_t reference_div; + uint32_t post_div; + + /* pll in/out limits */ + uint32_t pll_in_min; + uint32_t pll_in_max; + uint32_t pll_out_min; + uint32_t pll_out_max; + uint32_t lcd_pll_out_min; + uint32_t lcd_pll_out_max; + uint32_t best_vco; + + /* divider limits */ + uint32_t min_ref_div; + uint32_t max_ref_div; + uint32_t min_post_div; + uint32_t max_post_div; + uint32_t min_feedback_div; + uint32_t max_feedback_div; + uint32_t min_frac_feedback_div; + uint32_t max_frac_feedback_div; + + /* flags for the current clock */ + uint32_t flags; + + /* pll id */ + uint32_t id; +}; + +struct amdgpu_i2c_chan { + struct i2c_adapter adapter; + struct drm_device *dev; + struct i2c_algo_bit_data bit; + struct amdgpu_i2c_bus_rec rec; + struct drm_dp_aux aux; + bool has_aux; + struct mutex mutex; +}; + +struct amdgpu_fbdev; + +struct amdgpu_afmt { + bool enabled; + int offset; + bool last_buffer_filled_status; + int id; + struct amdgpu_audio_pin *pin; +}; + +/* + * Audio + */ +struct amdgpu_audio_pin { + int channels; + int rate; + int bits_per_sample; + u8 status_bits; + u8 category_code; + u32 offset; + bool connected; + u32 id; +}; + +struct amdgpu_audio { + bool enabled; + struct amdgpu_audio_pin pin[AMDGPU_MAX_AFMT_BLOCKS]; + int num_pins; +}; + +struct amdgpu_mode_mc_save { + u32 vga_render_control; + u32 vga_hdp_control; + bool crtc_enabled[AMDGPU_MAX_CRTCS]; +}; + +struct amdgpu_display_funcs { + /* vga render */ + void (*set_vga_render_state)(struct amdgpu_device *adev, bool render); + /* display watermarks */ + void (*bandwidth_update)(struct amdgpu_device *adev); + /* get frame count */ + u32 (*vblank_get_counter)(struct amdgpu_device *adev, int crtc); + /* wait for vblank */ + void (*vblank_wait)(struct amdgpu_device *adev, int crtc); + /* is dce hung */ + bool (*is_display_hung)(struct amdgpu_device *adev); + /* set backlight level */ + void (*backlight_set_level)(struct amdgpu_encoder *amdgpu_encoder, + u8 level); + /* get backlight level */ + u8 (*backlight_get_level)(struct amdgpu_encoder *amdgpu_encoder); + /* hotplug detect */ + bool (*hpd_sense)(struct amdgpu_device *adev, enum amdgpu_hpd_id hpd); + void (*hpd_set_polarity)(struct amdgpu_device *adev, + enum amdgpu_hpd_id hpd); + u32 (*hpd_get_gpio_reg)(struct amdgpu_device *adev); + /* pageflipping */ + void (*page_flip)(struct amdgpu_device *adev, + int crtc_id, u64 crtc_base); + int (*page_flip_get_scanoutpos)(struct amdgpu_device *adev, int crtc, + u32 *vbl, u32 *position); + /* display topology setup */ + void (*add_encoder)(struct amdgpu_device *adev, + uint32_t encoder_enum, + uint32_t supported_device, + u16 caps); + void (*add_connector)(struct amdgpu_device *adev, + uint32_t connector_id, + uint32_t supported_device, + int connector_type, + struct amdgpu_i2c_bus_rec *i2c_bus, + uint16_t connector_object_id, + struct amdgpu_hpd *hpd, + struct amdgpu_router *router); + void (*stop_mc_access)(struct amdgpu_device *adev, + struct amdgpu_mode_mc_save *save); + void (*resume_mc_access)(struct amdgpu_device *adev, + struct amdgpu_mode_mc_save *save); +}; + +struct amdgpu_mode_info { + struct atom_context *atom_context; + struct card_info *atom_card_info; + bool mode_config_initialized; + struct amdgpu_crtc *crtcs[6]; + struct amdgpu_afmt *afmt[7]; + /* DVI-I properties */ + struct drm_property *coherent_mode_property; + /* DAC enable load detect */ + struct drm_property *load_detect_property; + /* underscan */ + struct drm_property *underscan_property; + struct drm_property *underscan_hborder_property; + struct drm_property *underscan_vborder_property; + /* audio */ + struct drm_property *audio_property; + /* FMT dithering */ + struct drm_property *dither_property; + /* hardcoded DFP edid from BIOS */ + struct edid *bios_hardcoded_edid; + int bios_hardcoded_edid_size; + + /* pointer to fbdev info structure */ + struct amdgpu_fbdev *rfbdev; + /* firmware flags */ + u16 firmware_flags; + /* pointer to backlight encoder */ + struct amdgpu_encoder *bl_encoder; + struct amdgpu_audio audio; /* audio stuff */ + int num_crtc; /* number of crtcs */ + int num_hpd; /* number of hpd pins */ + int num_dig; /* number of dig blocks */ + int disp_priority; + const struct amdgpu_display_funcs *funcs; +}; + +#define AMDGPU_MAX_BL_LEVEL 0xFF + +#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) + +struct amdgpu_backlight_privdata { + struct amdgpu_encoder *encoder; + uint8_t negative; +}; + +#endif + +struct amdgpu_atom_ss { + uint16_t percentage; + uint16_t percentage_divider; + uint8_t type; + uint16_t step; + uint8_t delay; + uint8_t range; + uint8_t refdiv; + /* asic_ss */ + uint16_t rate; + uint16_t amount; +}; + +struct amdgpu_crtc { + struct drm_crtc base; + int crtc_id; + u16 lut_r[256], lut_g[256], lut_b[256]; + bool enabled; + bool can_tile; + uint32_t crtc_offset; + struct drm_gem_object *cursor_bo; + uint64_t cursor_addr; + int cursor_width; + int cursor_height; + int max_cursor_width; + int max_cursor_height; + enum amdgpu_rmx_type rmx_type; + u8 h_border; + u8 v_border; + fixed20_12 vsc; + fixed20_12 hsc; + struct drm_display_mode native_mode; + u32 pll_id; + /* page flipping */ + struct workqueue_struct *pflip_queue; + struct amdgpu_flip_work *pflip_works; + enum amdgpu_flip_status pflip_status; + int deferred_flip_completion; + /* pll sharing */ + struct amdgpu_atom_ss ss; + bool ss_enabled; + u32 adjusted_clock; + int bpc; + u32 pll_reference_div; + u32 pll_post_div; + u32 pll_flags; + struct drm_encoder *encoder; + struct drm_connector *connector; + /* for dpm */ + u32 line_time; + u32 wm_low; + u32 wm_high; + struct drm_display_mode hw_mode; +}; + +struct amdgpu_encoder_atom_dig { + bool linkb; + /* atom dig */ + bool coherent_mode; + int dig_encoder; /* -1 disabled, 0 DIGA, 1 DIGB, etc. */ + /* atom lvds/edp */ + uint32_t lcd_misc; + uint16_t panel_pwr_delay; + uint32_t lcd_ss_id; + /* panel mode */ + struct drm_display_mode native_mode; + struct backlight_device *bl_dev; + int dpms_mode; + uint8_t backlight_level; + int panel_mode; + struct amdgpu_afmt *afmt; +}; + +struct amdgpu_encoder { + struct drm_encoder base; + uint32_t encoder_enum; + uint32_t encoder_id; + uint32_t devices; + uint32_t active_device; + uint32_t flags; + uint32_t pixel_clock; + enum amdgpu_rmx_type rmx_type; + enum amdgpu_underscan_type underscan_type; + uint32_t underscan_hborder; + uint32_t underscan_vborder; + struct drm_display_mode native_mode; + void *enc_priv; + int audio_polling_active; + bool is_ext_encoder; + u16 caps; +}; + +struct amdgpu_connector_atom_dig { + /* displayport */ + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + u8 dp_sink_type; + int dp_clock; + int dp_lane_count; + bool edp_on; +}; + +struct amdgpu_gpio_rec { + bool valid; + u8 id; + u32 reg; + u32 mask; + u32 shift; +}; + +struct amdgpu_hpd { + enum amdgpu_hpd_id hpd; + u8 plugged_state; + struct amdgpu_gpio_rec gpio; +}; + +struct amdgpu_router { + u32 router_id; + struct amdgpu_i2c_bus_rec i2c_info; + u8 i2c_addr; + /* i2c mux */ + bool ddc_valid; + u8 ddc_mux_type; + u8 ddc_mux_control_pin; + u8 ddc_mux_state; + /* clock/data mux */ + bool cd_valid; + u8 cd_mux_type; + u8 cd_mux_control_pin; + u8 cd_mux_state; +}; + +enum amdgpu_connector_audio { + AMDGPU_AUDIO_DISABLE = 0, + AMDGPU_AUDIO_ENABLE = 1, + AMDGPU_AUDIO_AUTO = 2 +}; + +enum amdgpu_connector_dither { + AMDGPU_FMT_DITHER_DISABLE = 0, + AMDGPU_FMT_DITHER_ENABLE = 1, +}; + +struct amdgpu_connector { + struct drm_connector base; + uint32_t connector_id; + uint32_t devices; + struct amdgpu_i2c_chan *ddc_bus; + /* some systems have an hdmi and vga port with a shared ddc line */ + bool shared_ddc; + bool use_digital; + /* we need to mind the EDID between detect + and get modes due to analog/digital/tvencoder */ + struct edid *edid; + void *con_priv; + bool dac_load_detect; + bool detected_by_load; /* if the connection status was determined by load */ + uint16_t connector_object_id; + struct amdgpu_hpd hpd; + struct amdgpu_router router; + struct amdgpu_i2c_chan *router_bus; + enum amdgpu_connector_audio audio; + enum amdgpu_connector_dither dither; + unsigned pixelclock_for_modeset; +}; + +struct amdgpu_framebuffer { + struct drm_framebuffer base; + struct drm_gem_object *obj; +}; + +#define ENCODER_MODE_IS_DP(em) (((em) == ATOM_ENCODER_MODE_DP) || \ + ((em) == ATOM_ENCODER_MODE_DP_MST)) + +void amdgpu_link_encoder_connector(struct drm_device *dev); + +struct drm_connector * +amdgpu_get_connector_for_encoder(struct drm_encoder *encoder); +struct drm_connector * +amdgpu_get_connector_for_encoder_init(struct drm_encoder *encoder); +bool amdgpu_dig_monitor_is_duallink(struct drm_encoder *encoder, + u32 pixel_clock); + +u16 amdgpu_encoder_get_dp_bridge_encoder_id(struct drm_encoder *encoder); +struct drm_encoder *amdgpu_get_external_encoder(struct drm_encoder *encoder); + +bool amdgpu_ddc_probe(struct amdgpu_connector *amdgpu_connector, bool use_aux); + +void amdgpu_encoder_set_active_device(struct drm_encoder *encoder); + +int amdgpu_get_crtc_scanoutpos(struct drm_device *dev, int crtc, + unsigned int flags, + int *vpos, int *hpos, ktime_t *stime, + ktime_t *etime); + +int amdgpu_framebuffer_init(struct drm_device *dev, + struct amdgpu_framebuffer *rfb, + struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object *obj); + +int amdgpufb_remove(struct drm_device *dev, struct drm_framebuffer *fb); + +void amdgpu_enc_destroy(struct drm_encoder *encoder); +void amdgpu_copy_fb(struct drm_device *dev, struct drm_gem_object *dst_obj); +bool amdgpu_crtc_scaling_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +void amdgpu_panel_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *adjusted_mode); +int amdgpu_crtc_idx_to_irq_type(struct amdgpu_device *adev, int crtc); + +/* fbdev layer */ +int amdgpu_fbdev_init(struct amdgpu_device *adev); +void amdgpu_fbdev_fini(struct amdgpu_device *adev); +void amdgpu_fbdev_set_suspend(struct amdgpu_device *adev, int state); +int amdgpu_fbdev_total_size(struct amdgpu_device *adev); +bool amdgpu_fbdev_robj_is_fb(struct amdgpu_device *adev, struct amdgpu_bo *robj); + +void amdgpu_fb_output_poll_changed(struct amdgpu_device *adev); + + +int amdgpu_align_pitch(struct amdgpu_device *adev, int width, int bpp, bool tiled); + +/* amdgpu_display.c */ +void amdgpu_print_display_setup(struct drm_device *dev); +int amdgpu_modeset_create_props(struct amdgpu_device *adev); +int amdgpu_crtc_set_config(struct drm_mode_set *set); +int amdgpu_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t page_flip_flags); +extern const struct drm_mode_config_funcs amdgpu_mode_funcs; + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c new file mode 100644 index 000000000000..b51582714c21 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -0,0 +1,646 @@ +/* + * Copyright 2009 Jerome Glisse. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Jerome Glisse <glisse@freedesktop.org> + * Thomas Hellstrom <thomas-at-tungstengraphics-dot-com> + * Dave Airlie + */ +#include <linux/list.h> +#include <linux/slab.h> +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_trace.h" + + +int amdgpu_ttm_init(struct amdgpu_device *adev); +void amdgpu_ttm_fini(struct amdgpu_device *adev); + +static u64 amdgpu_get_vis_part_size(struct amdgpu_device *adev, + struct ttm_mem_reg * mem) +{ + u64 ret = 0; + if (mem->start << PAGE_SHIFT < adev->mc.visible_vram_size) { + ret = (u64)((mem->start << PAGE_SHIFT) + mem->size) > + adev->mc.visible_vram_size ? + adev->mc.visible_vram_size - (mem->start << PAGE_SHIFT): + mem->size; + } + return ret; +} + +static void amdgpu_update_memory_usage(struct amdgpu_device *adev, + struct ttm_mem_reg *old_mem, + struct ttm_mem_reg *new_mem) +{ + u64 vis_size; + if (!adev) + return; + + if (new_mem) { + switch (new_mem->mem_type) { + case TTM_PL_TT: + atomic64_add(new_mem->size, &adev->gtt_usage); + break; + case TTM_PL_VRAM: + atomic64_add(new_mem->size, &adev->vram_usage); + vis_size = amdgpu_get_vis_part_size(adev, new_mem); + atomic64_add(vis_size, &adev->vram_vis_usage); + break; + } + } + + if (old_mem) { + switch (old_mem->mem_type) { + case TTM_PL_TT: + atomic64_sub(old_mem->size, &adev->gtt_usage); + break; + case TTM_PL_VRAM: + atomic64_sub(old_mem->size, &adev->vram_usage); + vis_size = amdgpu_get_vis_part_size(adev, old_mem); + atomic64_sub(vis_size, &adev->vram_vis_usage); + break; + } + } +} + +static void amdgpu_ttm_bo_destroy(struct ttm_buffer_object *tbo) +{ + struct amdgpu_bo *bo; + + bo = container_of(tbo, struct amdgpu_bo, tbo); + + amdgpu_update_memory_usage(bo->adev, &bo->tbo.mem, NULL); + amdgpu_mn_unregister(bo); + + mutex_lock(&bo->adev->gem.mutex); + list_del_init(&bo->list); + mutex_unlock(&bo->adev->gem.mutex); + drm_gem_object_release(&bo->gem_base); + kfree(bo->metadata); + kfree(bo); +} + +bool amdgpu_ttm_bo_is_amdgpu_bo(struct ttm_buffer_object *bo) +{ + if (bo->destroy == &amdgpu_ttm_bo_destroy) + return true; + return false; +} + +void amdgpu_ttm_placement_from_domain(struct amdgpu_bo *rbo, u32 domain) +{ + u32 c = 0, i; + rbo->placement.placement = rbo->placements; + rbo->placement.busy_placement = rbo->placements; + + if (domain & AMDGPU_GEM_DOMAIN_VRAM) { + if (rbo->flags & AMDGPU_GEM_CREATE_NO_CPU_ACCESS && + rbo->adev->mc.visible_vram_size < rbo->adev->mc.real_vram_size) { + rbo->placements[c].fpfn = + rbo->adev->mc.visible_vram_size >> PAGE_SHIFT; + rbo->placements[c++].flags = TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED | + TTM_PL_FLAG_VRAM; + } + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED | + TTM_PL_FLAG_VRAM; + } + + if (domain & AMDGPU_GEM_DOMAIN_GTT) { + if (rbo->flags & AMDGPU_GEM_CREATE_CPU_GTT_UC) { + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_TT; + } else if (rbo->flags & AMDGPU_GEM_CREATE_CPU_GTT_WC) { + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_FLAG_WC | TTM_PL_FLAG_TT | + TTM_PL_FLAG_UNCACHED; + } else { + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_FLAG_CACHED | TTM_PL_FLAG_TT; + } + } + + if (domain & AMDGPU_GEM_DOMAIN_CPU) { + if (rbo->flags & AMDGPU_GEM_CREATE_CPU_GTT_UC) { + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_SYSTEM; + } else if (rbo->flags & AMDGPU_GEM_CREATE_CPU_GTT_WC) { + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_FLAG_WC | TTM_PL_FLAG_SYSTEM | + TTM_PL_FLAG_UNCACHED; + } else { + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_FLAG_CACHED | TTM_PL_FLAG_SYSTEM; + } + } + + if (domain & AMDGPU_GEM_DOMAIN_GDS) { + rbo->placements[c++].flags = TTM_PL_FLAG_UNCACHED | + AMDGPU_PL_FLAG_GDS; + } + if (domain & AMDGPU_GEM_DOMAIN_GWS) { + rbo->placements[c++].flags = TTM_PL_FLAG_UNCACHED | + AMDGPU_PL_FLAG_GWS; + } + if (domain & AMDGPU_GEM_DOMAIN_OA) { + rbo->placements[c++].flags = TTM_PL_FLAG_UNCACHED | + AMDGPU_PL_FLAG_OA; + } + + if (!c) { + rbo->placements[c].fpfn = 0; + rbo->placements[c++].flags = TTM_PL_MASK_CACHING | + TTM_PL_FLAG_SYSTEM; + } + rbo->placement.num_placement = c; + rbo->placement.num_busy_placement = c; + + for (i = 0; i < c; i++) { + if ((rbo->flags & AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED) && + (rbo->placements[i].flags & TTM_PL_FLAG_VRAM) && + !rbo->placements[i].fpfn) + rbo->placements[i].lpfn = + rbo->adev->mc.visible_vram_size >> PAGE_SHIFT; + else + rbo->placements[i].lpfn = 0; + } + + if (rbo->tbo.mem.size > 512 * 1024) { + for (i = 0; i < c; i++) { + rbo->placements[i].flags |= TTM_PL_FLAG_TOPDOWN; + } + } +} + +int amdgpu_bo_create(struct amdgpu_device *adev, + unsigned long size, int byte_align, bool kernel, u32 domain, u64 flags, + struct sg_table *sg, struct amdgpu_bo **bo_ptr) +{ + struct amdgpu_bo *bo; + enum ttm_bo_type type; + unsigned long page_align; + size_t acc_size; + int r; + + /* VI has a hw bug where VM PTEs have to be allocated in groups of 8. + * do this as a temporary workaround + */ + if (!(domain & (AMDGPU_GEM_DOMAIN_GDS | AMDGPU_GEM_DOMAIN_GWS | AMDGPU_GEM_DOMAIN_OA))) { + if (adev->asic_type >= CHIP_TOPAZ) { + if (byte_align & 0x7fff) + byte_align = ALIGN(byte_align, 0x8000); + if (size & 0x7fff) + size = ALIGN(size, 0x8000); + } + } + + page_align = roundup(byte_align, PAGE_SIZE) >> PAGE_SHIFT; + size = ALIGN(size, PAGE_SIZE); + + if (kernel) { + type = ttm_bo_type_kernel; + } else if (sg) { + type = ttm_bo_type_sg; + } else { + type = ttm_bo_type_device; + } + *bo_ptr = NULL; + + acc_size = ttm_bo_dma_acc_size(&adev->mman.bdev, size, + sizeof(struct amdgpu_bo)); + + bo = kzalloc(sizeof(struct amdgpu_bo), GFP_KERNEL); + if (bo == NULL) + return -ENOMEM; + r = drm_gem_object_init(adev->ddev, &bo->gem_base, size); + if (unlikely(r)) { + kfree(bo); + return r; + } + bo->adev = adev; + INIT_LIST_HEAD(&bo->list); + INIT_LIST_HEAD(&bo->va); + bo->initial_domain = domain & (AMDGPU_GEM_DOMAIN_VRAM | + AMDGPU_GEM_DOMAIN_GTT | + AMDGPU_GEM_DOMAIN_CPU | + AMDGPU_GEM_DOMAIN_GDS | + AMDGPU_GEM_DOMAIN_GWS | + AMDGPU_GEM_DOMAIN_OA); + + bo->flags = flags; + amdgpu_ttm_placement_from_domain(bo, domain); + /* Kernel allocation are uninterruptible */ + down_read(&adev->pm.mclk_lock); + r = ttm_bo_init(&adev->mman.bdev, &bo->tbo, size, type, + &bo->placement, page_align, !kernel, NULL, + acc_size, sg, NULL, &amdgpu_ttm_bo_destroy); + up_read(&adev->pm.mclk_lock); + if (unlikely(r != 0)) { + return r; + } + *bo_ptr = bo; + + trace_amdgpu_bo_create(bo); + + return 0; +} + +int amdgpu_bo_kmap(struct amdgpu_bo *bo, void **ptr) +{ + bool is_iomem; + int r; + + if (bo->kptr) { + if (ptr) { + *ptr = bo->kptr; + } + return 0; + } + r = ttm_bo_kmap(&bo->tbo, 0, bo->tbo.num_pages, &bo->kmap); + if (r) { + return r; + } + bo->kptr = ttm_kmap_obj_virtual(&bo->kmap, &is_iomem); + if (ptr) { + *ptr = bo->kptr; + } + return 0; +} + +void amdgpu_bo_kunmap(struct amdgpu_bo *bo) +{ + if (bo->kptr == NULL) + return; + bo->kptr = NULL; + ttm_bo_kunmap(&bo->kmap); +} + +struct amdgpu_bo *amdgpu_bo_ref(struct amdgpu_bo *bo) +{ + if (bo == NULL) + return NULL; + + ttm_bo_reference(&bo->tbo); + return bo; +} + +void amdgpu_bo_unref(struct amdgpu_bo **bo) +{ + struct ttm_buffer_object *tbo; + + if ((*bo) == NULL) + return; + + tbo = &((*bo)->tbo); + ttm_bo_unref(&tbo); + if (tbo == NULL) + *bo = NULL; +} + +int amdgpu_bo_pin_restricted(struct amdgpu_bo *bo, u32 domain, u64 max_offset, + u64 *gpu_addr) +{ + int r, i; + + if (amdgpu_ttm_tt_has_userptr(bo->tbo.ttm)) + return -EPERM; + + if (bo->pin_count) { + bo->pin_count++; + if (gpu_addr) + *gpu_addr = amdgpu_bo_gpu_offset(bo); + + if (max_offset != 0) { + u64 domain_start; + + if (domain == AMDGPU_GEM_DOMAIN_VRAM) + domain_start = bo->adev->mc.vram_start; + else + domain_start = bo->adev->mc.gtt_start; + WARN_ON_ONCE(max_offset < + (amdgpu_bo_gpu_offset(bo) - domain_start)); + } + + return 0; + } + amdgpu_ttm_placement_from_domain(bo, domain); + for (i = 0; i < bo->placement.num_placement; i++) { + /* force to pin into visible video ram */ + if ((bo->placements[i].flags & TTM_PL_FLAG_VRAM) && + !(bo->flags & AMDGPU_GEM_CREATE_NO_CPU_ACCESS) && + (!max_offset || max_offset > bo->adev->mc.visible_vram_size)) + bo->placements[i].lpfn = + bo->adev->mc.visible_vram_size >> PAGE_SHIFT; + else + bo->placements[i].lpfn = max_offset >> PAGE_SHIFT; + + bo->placements[i].flags |= TTM_PL_FLAG_NO_EVICT; + } + + r = ttm_bo_validate(&bo->tbo, &bo->placement, false, false); + if (likely(r == 0)) { + bo->pin_count = 1; + if (gpu_addr != NULL) + *gpu_addr = amdgpu_bo_gpu_offset(bo); + if (domain == AMDGPU_GEM_DOMAIN_VRAM) + bo->adev->vram_pin_size += amdgpu_bo_size(bo); + else + bo->adev->gart_pin_size += amdgpu_bo_size(bo); + } else { + dev_err(bo->adev->dev, "%p pin failed\n", bo); + } + return r; +} + +int amdgpu_bo_pin(struct amdgpu_bo *bo, u32 domain, u64 *gpu_addr) +{ + return amdgpu_bo_pin_restricted(bo, domain, 0, gpu_addr); +} + +int amdgpu_bo_unpin(struct amdgpu_bo *bo) +{ + int r, i; + + if (!bo->pin_count) { + dev_warn(bo->adev->dev, "%p unpin not necessary\n", bo); + return 0; + } + bo->pin_count--; + if (bo->pin_count) + return 0; + for (i = 0; i < bo->placement.num_placement; i++) { + bo->placements[i].lpfn = 0; + bo->placements[i].flags &= ~TTM_PL_FLAG_NO_EVICT; + } + r = ttm_bo_validate(&bo->tbo, &bo->placement, false, false); + if (likely(r == 0)) { + if (bo->tbo.mem.mem_type == TTM_PL_VRAM) + bo->adev->vram_pin_size -= amdgpu_bo_size(bo); + else + bo->adev->gart_pin_size -= amdgpu_bo_size(bo); + } else { + dev_err(bo->adev->dev, "%p validate failed for unpin\n", bo); + } + return r; +} + +int amdgpu_bo_evict_vram(struct amdgpu_device *adev) +{ + /* late 2.6.33 fix IGP hibernate - we need pm ops to do this correct */ + if (0 && (adev->flags & AMDGPU_IS_APU)) { + /* Useless to evict on IGP chips */ + return 0; + } + return ttm_bo_evict_mm(&adev->mman.bdev, TTM_PL_VRAM); +} + +void amdgpu_bo_force_delete(struct amdgpu_device *adev) +{ + struct amdgpu_bo *bo, *n; + + if (list_empty(&adev->gem.objects)) { + return; + } + dev_err(adev->dev, "Userspace still has active objects !\n"); + list_for_each_entry_safe(bo, n, &adev->gem.objects, list) { + mutex_lock(&adev->ddev->struct_mutex); + dev_err(adev->dev, "%p %p %lu %lu force free\n", + &bo->gem_base, bo, (unsigned long)bo->gem_base.size, + *((unsigned long *)&bo->gem_base.refcount)); + mutex_lock(&bo->adev->gem.mutex); + list_del_init(&bo->list); + mutex_unlock(&bo->adev->gem.mutex); + /* this should unref the ttm bo */ + drm_gem_object_unreference(&bo->gem_base); + mutex_unlock(&adev->ddev->struct_mutex); + } +} + +int amdgpu_bo_init(struct amdgpu_device *adev) +{ + /* Add an MTRR for the VRAM */ + adev->mc.vram_mtrr = arch_phys_wc_add(adev->mc.aper_base, + adev->mc.aper_size); + DRM_INFO("Detected VRAM RAM=%lluM, BAR=%lluM\n", + adev->mc.mc_vram_size >> 20, + (unsigned long long)adev->mc.aper_size >> 20); + DRM_INFO("RAM width %dbits DDR\n", + adev->mc.vram_width); + return amdgpu_ttm_init(adev); +} + +void amdgpu_bo_fini(struct amdgpu_device *adev) +{ + amdgpu_ttm_fini(adev); + arch_phys_wc_del(adev->mc.vram_mtrr); +} + +int amdgpu_bo_fbdev_mmap(struct amdgpu_bo *bo, + struct vm_area_struct *vma) +{ + return ttm_fbdev_mmap(vma, &bo->tbo); +} + +int amdgpu_bo_set_tiling_flags(struct amdgpu_bo *bo, u64 tiling_flags) +{ + unsigned bankw, bankh, mtaspect, tilesplit, stilesplit; + + bankw = (tiling_flags >> AMDGPU_TILING_EG_BANKW_SHIFT) & AMDGPU_TILING_EG_BANKW_MASK; + bankh = (tiling_flags >> AMDGPU_TILING_EG_BANKH_SHIFT) & AMDGPU_TILING_EG_BANKH_MASK; + mtaspect = (tiling_flags >> AMDGPU_TILING_EG_MACRO_TILE_ASPECT_SHIFT) & AMDGPU_TILING_EG_MACRO_TILE_ASPECT_MASK; + tilesplit = (tiling_flags >> AMDGPU_TILING_EG_TILE_SPLIT_SHIFT) & AMDGPU_TILING_EG_TILE_SPLIT_MASK; + stilesplit = (tiling_flags >> AMDGPU_TILING_EG_STENCIL_TILE_SPLIT_SHIFT) & AMDGPU_TILING_EG_STENCIL_TILE_SPLIT_MASK; + switch (bankw) { + case 0: + case 1: + case 2: + case 4: + case 8: + break; + default: + return -EINVAL; + } + switch (bankh) { + case 0: + case 1: + case 2: + case 4: + case 8: + break; + default: + return -EINVAL; + } + switch (mtaspect) { + case 0: + case 1: + case 2: + case 4: + case 8: + break; + default: + return -EINVAL; + } + if (tilesplit > 6) { + return -EINVAL; + } + if (stilesplit > 6) { + return -EINVAL; + } + + bo->tiling_flags = tiling_flags; + return 0; +} + +void amdgpu_bo_get_tiling_flags(struct amdgpu_bo *bo, u64 *tiling_flags) +{ + lockdep_assert_held(&bo->tbo.resv->lock.base); + + if (tiling_flags) + *tiling_flags = bo->tiling_flags; +} + +int amdgpu_bo_set_metadata (struct amdgpu_bo *bo, void *metadata, + uint32_t metadata_size, uint64_t flags) +{ + void *buffer; + + if (!metadata_size) { + if (bo->metadata_size) { + kfree(bo->metadata); + bo->metadata_size = 0; + } + return 0; + } + + if (metadata == NULL) + return -EINVAL; + + buffer = kzalloc(metadata_size, GFP_KERNEL); + if (buffer == NULL) + return -ENOMEM; + + memcpy(buffer, metadata, metadata_size); + + kfree(bo->metadata); + bo->metadata_flags = flags; + bo->metadata = buffer; + bo->metadata_size = metadata_size; + + return 0; +} + +int amdgpu_bo_get_metadata(struct amdgpu_bo *bo, void *buffer, + size_t buffer_size, uint32_t *metadata_size, + uint64_t *flags) +{ + if (!buffer && !metadata_size) + return -EINVAL; + + if (buffer) { + if (buffer_size < bo->metadata_size) + return -EINVAL; + + if (bo->metadata_size) + memcpy(buffer, bo->metadata, bo->metadata_size); + } + + if (metadata_size) + *metadata_size = bo->metadata_size; + if (flags) + *flags = bo->metadata_flags; + + return 0; +} + +void amdgpu_bo_move_notify(struct ttm_buffer_object *bo, + struct ttm_mem_reg *new_mem) +{ + struct amdgpu_bo *rbo; + + if (!amdgpu_ttm_bo_is_amdgpu_bo(bo)) + return; + + rbo = container_of(bo, struct amdgpu_bo, tbo); + amdgpu_vm_bo_invalidate(rbo->adev, rbo); + + /* update statistics */ + if (!new_mem) + return; + + /* move_notify is called before move happens */ + amdgpu_update_memory_usage(rbo->adev, &bo->mem, new_mem); +} + +int amdgpu_bo_fault_reserve_notify(struct ttm_buffer_object *bo) +{ + struct amdgpu_device *adev; + struct amdgpu_bo *rbo; + unsigned long offset, size; + int r; + + if (!amdgpu_ttm_bo_is_amdgpu_bo(bo)) + return 0; + rbo = container_of(bo, struct amdgpu_bo, tbo); + adev = rbo->adev; + if (bo->mem.mem_type == TTM_PL_VRAM) { + size = bo->mem.num_pages << PAGE_SHIFT; + offset = bo->mem.start << PAGE_SHIFT; + if ((offset + size) > adev->mc.visible_vram_size) { + /* hurrah the memory is not visible ! */ + amdgpu_ttm_placement_from_domain(rbo, AMDGPU_GEM_DOMAIN_VRAM); + rbo->placements[0].lpfn = adev->mc.visible_vram_size >> PAGE_SHIFT; + r = ttm_bo_validate(bo, &rbo->placement, false, false); + if (unlikely(r != 0)) + return r; + offset = bo->mem.start << PAGE_SHIFT; + /* this should not happen */ + if ((offset + size) > adev->mc.visible_vram_size) + return -EINVAL; + } + } + return 0; +} + +/** + * amdgpu_bo_fence - add fence to buffer object + * + * @bo: buffer object in question + * @fence: fence to add + * @shared: true if fence should be added shared + * + */ +void amdgpu_bo_fence(struct amdgpu_bo *bo, struct amdgpu_fence *fence, + bool shared) +{ + struct reservation_object *resv = bo->tbo.resv; + + if (shared) + reservation_object_add_shared_fence(resv, &fence->base); + else + reservation_object_add_excl_fence(resv, &fence->base); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h new file mode 100644 index 000000000000..b1e0a03c1d78 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h @@ -0,0 +1,196 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#ifndef __AMDGPU_OBJECT_H__ +#define __AMDGPU_OBJECT_H__ + +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +/** + * amdgpu_mem_type_to_domain - return domain corresponding to mem_type + * @mem_type: ttm memory type + * + * Returns corresponding domain of the ttm mem_type + */ +static inline unsigned amdgpu_mem_type_to_domain(u32 mem_type) +{ + switch (mem_type) { + case TTM_PL_VRAM: + return AMDGPU_GEM_DOMAIN_VRAM; + case TTM_PL_TT: + return AMDGPU_GEM_DOMAIN_GTT; + case TTM_PL_SYSTEM: + return AMDGPU_GEM_DOMAIN_CPU; + case AMDGPU_PL_GDS: + return AMDGPU_GEM_DOMAIN_GDS; + case AMDGPU_PL_GWS: + return AMDGPU_GEM_DOMAIN_GWS; + case AMDGPU_PL_OA: + return AMDGPU_GEM_DOMAIN_OA; + default: + break; + } + return 0; +} + +/** + * amdgpu_bo_reserve - reserve bo + * @bo: bo structure + * @no_intr: don't return -ERESTARTSYS on pending signal + * + * Returns: + * -ERESTARTSYS: A wait for the buffer to become unreserved was interrupted by + * a signal. Release all buffer reservations and return to user-space. + */ +static inline int amdgpu_bo_reserve(struct amdgpu_bo *bo, bool no_intr) +{ + int r; + + r = ttm_bo_reserve(&bo->tbo, !no_intr, false, false, 0); + if (unlikely(r != 0)) { + if (r != -ERESTARTSYS) + dev_err(bo->adev->dev, "%p reserve failed\n", bo); + return r; + } + return 0; +} + +static inline void amdgpu_bo_unreserve(struct amdgpu_bo *bo) +{ + ttm_bo_unreserve(&bo->tbo); +} + +/** + * amdgpu_bo_gpu_offset - return GPU offset of bo + * @bo: amdgpu object for which we query the offset + * + * Returns current GPU offset of the object. + * + * Note: object should either be pinned or reserved when calling this + * function, it might be useful to add check for this for debugging. + */ +static inline u64 amdgpu_bo_gpu_offset(struct amdgpu_bo *bo) +{ + return bo->tbo.offset; +} + +static inline unsigned long amdgpu_bo_size(struct amdgpu_bo *bo) +{ + return bo->tbo.num_pages << PAGE_SHIFT; +} + +static inline unsigned amdgpu_bo_ngpu_pages(struct amdgpu_bo *bo) +{ + return (bo->tbo.num_pages << PAGE_SHIFT) / AMDGPU_GPU_PAGE_SIZE; +} + +static inline unsigned amdgpu_bo_gpu_page_alignment(struct amdgpu_bo *bo) +{ + return (bo->tbo.mem.page_alignment << PAGE_SHIFT) / AMDGPU_GPU_PAGE_SIZE; +} + +/** + * amdgpu_bo_mmap_offset - return mmap offset of bo + * @bo: amdgpu object for which we query the offset + * + * Returns mmap offset of the object. + */ +static inline u64 amdgpu_bo_mmap_offset(struct amdgpu_bo *bo) +{ + return drm_vma_node_offset_addr(&bo->tbo.vma_node); +} + +int amdgpu_bo_create(struct amdgpu_device *adev, + unsigned long size, int byte_align, + bool kernel, u32 domain, u64 flags, + struct sg_table *sg, + struct amdgpu_bo **bo_ptr); +int amdgpu_bo_kmap(struct amdgpu_bo *bo, void **ptr); +void amdgpu_bo_kunmap(struct amdgpu_bo *bo); +struct amdgpu_bo *amdgpu_bo_ref(struct amdgpu_bo *bo); +void amdgpu_bo_unref(struct amdgpu_bo **bo); +int amdgpu_bo_pin(struct amdgpu_bo *bo, u32 domain, u64 *gpu_addr); +int amdgpu_bo_pin_restricted(struct amdgpu_bo *bo, u32 domain, + u64 max_offset, u64 *gpu_addr); +int amdgpu_bo_unpin(struct amdgpu_bo *bo); +int amdgpu_bo_evict_vram(struct amdgpu_device *adev); +void amdgpu_bo_force_delete(struct amdgpu_device *adev); +int amdgpu_bo_init(struct amdgpu_device *adev); +void amdgpu_bo_fini(struct amdgpu_device *adev); +int amdgpu_bo_fbdev_mmap(struct amdgpu_bo *bo, + struct vm_area_struct *vma); +int amdgpu_bo_set_tiling_flags(struct amdgpu_bo *bo, u64 tiling_flags); +void amdgpu_bo_get_tiling_flags(struct amdgpu_bo *bo, u64 *tiling_flags); +int amdgpu_bo_set_metadata (struct amdgpu_bo *bo, void *metadata, + uint32_t metadata_size, uint64_t flags); +int amdgpu_bo_get_metadata(struct amdgpu_bo *bo, void *buffer, + size_t buffer_size, uint32_t *metadata_size, + uint64_t *flags); +void amdgpu_bo_move_notify(struct ttm_buffer_object *bo, + struct ttm_mem_reg *new_mem); +int amdgpu_bo_fault_reserve_notify(struct ttm_buffer_object *bo); +void amdgpu_bo_fence(struct amdgpu_bo *bo, struct amdgpu_fence *fence, + bool shared); + +/* + * sub allocation + */ + +static inline uint64_t amdgpu_sa_bo_gpu_addr(struct amdgpu_sa_bo *sa_bo) +{ + return sa_bo->manager->gpu_addr + sa_bo->soffset; +} + +static inline void * amdgpu_sa_bo_cpu_addr(struct amdgpu_sa_bo *sa_bo) +{ + return sa_bo->manager->cpu_ptr + sa_bo->soffset; +} + +int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager, + unsigned size, u32 align, u32 domain); +void amdgpu_sa_bo_manager_fini(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager); +int amdgpu_sa_bo_manager_start(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager); +int amdgpu_sa_bo_manager_suspend(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager); +int amdgpu_sa_bo_new(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager, + struct amdgpu_sa_bo **sa_bo, + unsigned size, unsigned align); +void amdgpu_sa_bo_free(struct amdgpu_device *adev, + struct amdgpu_sa_bo **sa_bo, + struct amdgpu_fence *fence); +#if defined(CONFIG_DEBUG_FS) +void amdgpu_sa_bo_dump_debug_info(struct amdgpu_sa_manager *sa_manager, + struct seq_file *m); +#endif + + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pll.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_pll.c new file mode 100644 index 000000000000..d15314957732 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pll.c @@ -0,0 +1,350 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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. + * + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "atom.h" +#include "atombios_encoders.h" +#include <asm/div64.h> +#include <linux/gcd.h> + +/** + * amdgpu_pll_reduce_ratio - fractional number reduction + * + * @nom: nominator + * @den: denominator + * @nom_min: minimum value for nominator + * @den_min: minimum value for denominator + * + * Find the greatest common divisor and apply it on both nominator and + * denominator, but make nominator and denominator are at least as large + * as their minimum values. + */ +static void amdgpu_pll_reduce_ratio(unsigned *nom, unsigned *den, + unsigned nom_min, unsigned den_min) +{ + unsigned tmp; + + /* reduce the numbers to a simpler ratio */ + tmp = gcd(*nom, *den); + *nom /= tmp; + *den /= tmp; + + /* make sure nominator is large enough */ + if (*nom < nom_min) { + tmp = DIV_ROUND_UP(nom_min, *nom); + *nom *= tmp; + *den *= tmp; + } + + /* make sure the denominator is large enough */ + if (*den < den_min) { + tmp = DIV_ROUND_UP(den_min, *den); + *nom *= tmp; + *den *= tmp; + } +} + +/** + * amdgpu_pll_get_fb_ref_div - feedback and ref divider calculation + * + * @nom: nominator + * @den: denominator + * @post_div: post divider + * @fb_div_max: feedback divider maximum + * @ref_div_max: reference divider maximum + * @fb_div: resulting feedback divider + * @ref_div: resulting reference divider + * + * Calculate feedback and reference divider for a given post divider. Makes + * sure we stay within the limits. + */ +static void amdgpu_pll_get_fb_ref_div(unsigned nom, unsigned den, unsigned post_div, + unsigned fb_div_max, unsigned ref_div_max, + unsigned *fb_div, unsigned *ref_div) +{ + /* limit reference * post divider to a maximum */ + ref_div_max = min(128 / post_div, ref_div_max); + + /* get matching reference and feedback divider */ + *ref_div = min(max(DIV_ROUND_CLOSEST(den, post_div), 1u), ref_div_max); + *fb_div = DIV_ROUND_CLOSEST(nom * *ref_div * post_div, den); + + /* limit fb divider to its maximum */ + if (*fb_div > fb_div_max) { + *ref_div = DIV_ROUND_CLOSEST(*ref_div * fb_div_max, *fb_div); + *fb_div = fb_div_max; + } +} + +/** + * amdgpu_pll_compute - compute PLL paramaters + * + * @pll: information about the PLL + * @dot_clock_p: resulting pixel clock + * fb_div_p: resulting feedback divider + * frac_fb_div_p: fractional part of the feedback divider + * ref_div_p: resulting reference divider + * post_div_p: resulting reference divider + * + * Try to calculate the PLL parameters to generate the given frequency: + * dot_clock = (ref_freq * feedback_div) / (ref_div * post_div) + */ +void amdgpu_pll_compute(struct amdgpu_pll *pll, + u32 freq, + u32 *dot_clock_p, + u32 *fb_div_p, + u32 *frac_fb_div_p, + u32 *ref_div_p, + u32 *post_div_p) +{ + unsigned target_clock = pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV ? + freq : freq / 10; + + unsigned fb_div_min, fb_div_max, fb_div; + unsigned post_div_min, post_div_max, post_div; + unsigned ref_div_min, ref_div_max, ref_div; + unsigned post_div_best, diff_best; + unsigned nom, den; + + /* determine allowed feedback divider range */ + fb_div_min = pll->min_feedback_div; + fb_div_max = pll->max_feedback_div; + + if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV) { + fb_div_min *= 10; + fb_div_max *= 10; + } + + /* determine allowed ref divider range */ + if (pll->flags & AMDGPU_PLL_USE_REF_DIV) + ref_div_min = pll->reference_div; + else + ref_div_min = pll->min_ref_div; + + if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV && + pll->flags & AMDGPU_PLL_USE_REF_DIV) + ref_div_max = pll->reference_div; + else + ref_div_max = pll->max_ref_div; + + /* determine allowed post divider range */ + if (pll->flags & AMDGPU_PLL_USE_POST_DIV) { + post_div_min = pll->post_div; + post_div_max = pll->post_div; + } else { + unsigned vco_min, vco_max; + + if (pll->flags & AMDGPU_PLL_IS_LCD) { + vco_min = pll->lcd_pll_out_min; + vco_max = pll->lcd_pll_out_max; + } else { + vco_min = pll->pll_out_min; + vco_max = pll->pll_out_max; + } + + if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV) { + vco_min *= 10; + vco_max *= 10; + } + + post_div_min = vco_min / target_clock; + if ((target_clock * post_div_min) < vco_min) + ++post_div_min; + if (post_div_min < pll->min_post_div) + post_div_min = pll->min_post_div; + + post_div_max = vco_max / target_clock; + if ((target_clock * post_div_max) > vco_max) + --post_div_max; + if (post_div_max > pll->max_post_div) + post_div_max = pll->max_post_div; + } + + /* represent the searched ratio as fractional number */ + nom = target_clock; + den = pll->reference_freq; + + /* reduce the numbers to a simpler ratio */ + amdgpu_pll_reduce_ratio(&nom, &den, fb_div_min, post_div_min); + + /* now search for a post divider */ + if (pll->flags & AMDGPU_PLL_PREFER_MINM_OVER_MAXP) + post_div_best = post_div_min; + else + post_div_best = post_div_max; + diff_best = ~0; + + for (post_div = post_div_min; post_div <= post_div_max; ++post_div) { + unsigned diff; + amdgpu_pll_get_fb_ref_div(nom, den, post_div, fb_div_max, + ref_div_max, &fb_div, &ref_div); + diff = abs(target_clock - (pll->reference_freq * fb_div) / + (ref_div * post_div)); + + if (diff < diff_best || (diff == diff_best && + !(pll->flags & AMDGPU_PLL_PREFER_MINM_OVER_MAXP))) { + + post_div_best = post_div; + diff_best = diff; + } + } + post_div = post_div_best; + + /* get the feedback and reference divider for the optimal value */ + amdgpu_pll_get_fb_ref_div(nom, den, post_div, fb_div_max, ref_div_max, + &fb_div, &ref_div); + + /* reduce the numbers to a simpler ratio once more */ + /* this also makes sure that the reference divider is large enough */ + amdgpu_pll_reduce_ratio(&fb_div, &ref_div, fb_div_min, ref_div_min); + + /* avoid high jitter with small fractional dividers */ + if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV && (fb_div % 10)) { + fb_div_min = max(fb_div_min, (9 - (fb_div % 10)) * 20 + 60); + if (fb_div < fb_div_min) { + unsigned tmp = DIV_ROUND_UP(fb_div_min, fb_div); + fb_div *= tmp; + ref_div *= tmp; + } + } + + /* and finally save the result */ + if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV) { + *fb_div_p = fb_div / 10; + *frac_fb_div_p = fb_div % 10; + } else { + *fb_div_p = fb_div; + *frac_fb_div_p = 0; + } + + *dot_clock_p = ((pll->reference_freq * *fb_div_p * 10) + + (pll->reference_freq * *frac_fb_div_p)) / + (ref_div * post_div * 10); + *ref_div_p = ref_div; + *post_div_p = post_div; + + DRM_DEBUG_KMS("%d - %d, pll dividers - fb: %d.%d ref: %d, post %d\n", + freq, *dot_clock_p * 10, *fb_div_p, *frac_fb_div_p, + ref_div, post_div); +} + +/** + * amdgpu_pll_get_use_mask - look up a mask of which pplls are in use + * + * @crtc: drm crtc + * + * Returns the mask of which PPLLs (Pixel PLLs) are in use. + */ +u32 amdgpu_pll_get_use_mask(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_crtc *test_crtc; + struct amdgpu_crtc *test_amdgpu_crtc; + u32 pll_in_use = 0; + + list_for_each_entry(test_crtc, &dev->mode_config.crtc_list, head) { + if (crtc == test_crtc) + continue; + + test_amdgpu_crtc = to_amdgpu_crtc(test_crtc); + if (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID) + pll_in_use |= (1 << test_amdgpu_crtc->pll_id); + } + return pll_in_use; +} + +/** + * amdgpu_pll_get_shared_dp_ppll - return the PPLL used by another crtc for DP + * + * @crtc: drm crtc + * + * Returns the PPLL (Pixel PLL) used by another crtc/encoder which is + * also in DP mode. For DP, a single PPLL can be used for all DP + * crtcs/encoders. + */ +int amdgpu_pll_get_shared_dp_ppll(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_crtc *test_crtc; + struct amdgpu_crtc *test_amdgpu_crtc; + + list_for_each_entry(test_crtc, &dev->mode_config.crtc_list, head) { + if (crtc == test_crtc) + continue; + test_amdgpu_crtc = to_amdgpu_crtc(test_crtc); + if (test_amdgpu_crtc->encoder && + ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(test_amdgpu_crtc->encoder))) { + /* for DP use the same PLL for all */ + if (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID) + return test_amdgpu_crtc->pll_id; + } + } + return ATOM_PPLL_INVALID; +} + +/** + * amdgpu_pll_get_shared_nondp_ppll - return the PPLL used by another non-DP crtc + * + * @crtc: drm crtc + * @encoder: drm encoder + * + * Returns the PPLL (Pixel PLL) used by another non-DP crtc/encoder which can + * be shared (i.e., same clock). + */ +int amdgpu_pll_get_shared_nondp_ppll(struct drm_crtc *crtc) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct drm_crtc *test_crtc; + struct amdgpu_crtc *test_amdgpu_crtc; + u32 adjusted_clock, test_adjusted_clock; + + adjusted_clock = amdgpu_crtc->adjusted_clock; + + if (adjusted_clock == 0) + return ATOM_PPLL_INVALID; + + list_for_each_entry(test_crtc, &dev->mode_config.crtc_list, head) { + if (crtc == test_crtc) + continue; + test_amdgpu_crtc = to_amdgpu_crtc(test_crtc); + if (test_amdgpu_crtc->encoder && + !ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(test_amdgpu_crtc->encoder))) { + /* check if we are already driving this connector with another crtc */ + if (test_amdgpu_crtc->connector == amdgpu_crtc->connector) { + /* if we are, return that pll */ + if (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID) + return test_amdgpu_crtc->pll_id; + } + /* for non-DP check the clock */ + test_adjusted_clock = test_amdgpu_crtc->adjusted_clock; + if ((crtc->mode.clock == test_crtc->mode.clock) && + (adjusted_clock == test_adjusted_clock) && + (amdgpu_crtc->ss_enabled == test_amdgpu_crtc->ss_enabled) && + (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID)) + return test_amdgpu_crtc->pll_id; + } + } + return ATOM_PPLL_INVALID; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pll.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_pll.h new file mode 100644 index 000000000000..db6136f68b82 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pll.h @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_PLL_H__ +#define __AMDGPU_PLL_H__ + +void amdgpu_pll_compute(struct amdgpu_pll *pll, + u32 freq, + u32 *dot_clock_p, + u32 *fb_div_p, + u32 *frac_fb_div_p, + u32 *ref_div_p, + u32 *post_div_p); +u32 amdgpu_pll_get_use_mask(struct drm_crtc *crtc); +int amdgpu_pll_get_shared_dp_ppll(struct drm_crtc *crtc); +int amdgpu_pll_get_shared_nondp_ppll(struct drm_crtc *crtc); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c new file mode 100644 index 000000000000..89782543f854 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c @@ -0,0 +1,801 @@ +/* + * 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: Rafał Miłecki <zajec5@gmail.com> + * Alex Deucher <alexdeucher@gmail.com> + */ +#include <drm/drmP.h> +#include "amdgpu.h" +#include "amdgpu_drv.h" +#include "amdgpu_pm.h" +#include "amdgpu_dpm.h" +#include "atom.h" +#include <linux/power_supply.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +static int amdgpu_debugfs_pm_init(struct amdgpu_device *adev); + +void amdgpu_pm_acpi_event_handler(struct amdgpu_device *adev) +{ + if (adev->pm.dpm_enabled) { + mutex_lock(&adev->pm.mutex); + if (power_supply_is_system_supplied() > 0) + adev->pm.dpm.ac_power = true; + else + adev->pm.dpm.ac_power = false; + if (adev->pm.funcs->enable_bapm) + amdgpu_dpm_enable_bapm(adev, adev->pm.dpm.ac_power); + mutex_unlock(&adev->pm.mutex); + } +} + +static ssize_t amdgpu_get_dpm_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = ddev->dev_private; + enum amdgpu_pm_state_type pm = adev->pm.dpm.user_state; + + return snprintf(buf, PAGE_SIZE, "%s\n", + (pm == POWER_STATE_TYPE_BATTERY) ? "battery" : + (pm == POWER_STATE_TYPE_BALANCED) ? "balanced" : "performance"); +} + +static ssize_t amdgpu_set_dpm_state(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = ddev->dev_private; + + mutex_lock(&adev->pm.mutex); + if (strncmp("battery", buf, strlen("battery")) == 0) + adev->pm.dpm.user_state = POWER_STATE_TYPE_BATTERY; + else if (strncmp("balanced", buf, strlen("balanced")) == 0) + adev->pm.dpm.user_state = POWER_STATE_TYPE_BALANCED; + else if (strncmp("performance", buf, strlen("performance")) == 0) + adev->pm.dpm.user_state = POWER_STATE_TYPE_PERFORMANCE; + else { + mutex_unlock(&adev->pm.mutex); + count = -EINVAL; + goto fail; + } + mutex_unlock(&adev->pm.mutex); + + /* Can't set dpm state when the card is off */ + if (!(adev->flags & AMDGPU_IS_PX) || + (ddev->switch_power_state == DRM_SWITCH_POWER_ON)) + amdgpu_pm_compute_clocks(adev); +fail: + return count; +} + +static ssize_t amdgpu_get_dpm_forced_performance_level(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = ddev->dev_private; + enum amdgpu_dpm_forced_level level = adev->pm.dpm.forced_level; + + return snprintf(buf, PAGE_SIZE, "%s\n", + (level == AMDGPU_DPM_FORCED_LEVEL_AUTO) ? "auto" : + (level == AMDGPU_DPM_FORCED_LEVEL_LOW) ? "low" : "high"); +} + +static ssize_t amdgpu_set_dpm_forced_performance_level(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct amdgpu_device *adev = ddev->dev_private; + enum amdgpu_dpm_forced_level level; + int ret = 0; + + mutex_lock(&adev->pm.mutex); + if (strncmp("low", buf, strlen("low")) == 0) { + level = AMDGPU_DPM_FORCED_LEVEL_LOW; + } else if (strncmp("high", buf, strlen("high")) == 0) { + level = AMDGPU_DPM_FORCED_LEVEL_HIGH; + } else if (strncmp("auto", buf, strlen("auto")) == 0) { + level = AMDGPU_DPM_FORCED_LEVEL_AUTO; + } else { + count = -EINVAL; + goto fail; + } + if (adev->pm.funcs->force_performance_level) { + if (adev->pm.dpm.thermal_active) { + count = -EINVAL; + goto fail; + } + ret = amdgpu_dpm_force_performance_level(adev, level); + if (ret) + count = -EINVAL; + } +fail: + mutex_unlock(&adev->pm.mutex); + + return count; +} + +static DEVICE_ATTR(power_dpm_state, S_IRUGO | S_IWUSR, amdgpu_get_dpm_state, amdgpu_set_dpm_state); +static DEVICE_ATTR(power_dpm_force_performance_level, S_IRUGO | S_IWUSR, + amdgpu_get_dpm_forced_performance_level, + amdgpu_set_dpm_forced_performance_level); + +static ssize_t amdgpu_hwmon_show_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct amdgpu_device *adev = dev_get_drvdata(dev); + int temp; + + if (adev->pm.funcs->get_temperature) + temp = amdgpu_dpm_get_temperature(adev); + else + temp = 0; + + return snprintf(buf, PAGE_SIZE, "%d\n", temp); +} + +static ssize_t amdgpu_hwmon_show_temp_thresh(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct amdgpu_device *adev = dev_get_drvdata(dev); + int hyst = to_sensor_dev_attr(attr)->index; + int temp; + + if (hyst) + temp = adev->pm.dpm.thermal.min_temp; + else + temp = adev->pm.dpm.thermal.max_temp; + + return snprintf(buf, PAGE_SIZE, "%d\n", temp); +} + +static ssize_t amdgpu_hwmon_get_pwm1_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct amdgpu_device *adev = dev_get_drvdata(dev); + u32 pwm_mode = 0; + + if (adev->pm.funcs->get_fan_control_mode) + pwm_mode = amdgpu_dpm_get_fan_control_mode(adev); + + /* never 0 (full-speed), fuse or smc-controlled always */ + return sprintf(buf, "%i\n", pwm_mode == FDO_PWM_MODE_STATIC ? 1 : 2); +} + +static ssize_t amdgpu_hwmon_set_pwm1_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct amdgpu_device *adev = dev_get_drvdata(dev); + int err; + int value; + + if(!adev->pm.funcs->set_fan_control_mode) + return -EINVAL; + + err = kstrtoint(buf, 10, &value); + if (err) + return err; + + switch (value) { + case 1: /* manual, percent-based */ + amdgpu_dpm_set_fan_control_mode(adev, FDO_PWM_MODE_STATIC); + break; + default: /* disable */ + amdgpu_dpm_set_fan_control_mode(adev, 0); + break; + } + + return count; +} + +static ssize_t amdgpu_hwmon_get_pwm1_min(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%i\n", 0); +} + +static ssize_t amdgpu_hwmon_get_pwm1_max(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%i\n", 255); +} + +static ssize_t amdgpu_hwmon_set_pwm1(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct amdgpu_device *adev = dev_get_drvdata(dev); + int err; + u32 value; + + err = kstrtou32(buf, 10, &value); + if (err) + return err; + + value = (value * 100) / 255; + + err = amdgpu_dpm_set_fan_speed_percent(adev, value); + if (err) + return err; + + return count; +} + +static ssize_t amdgpu_hwmon_get_pwm1(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct amdgpu_device *adev = dev_get_drvdata(dev); + int err; + u32 speed; + + err = amdgpu_dpm_get_fan_speed_percent(adev, &speed); + if (err) + return err; + + speed = (speed * 255) / 100; + + return sprintf(buf, "%i\n", speed); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, amdgpu_hwmon_show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, amdgpu_hwmon_show_temp_thresh, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, amdgpu_hwmon_show_temp_thresh, NULL, 1); +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, amdgpu_hwmon_get_pwm1, amdgpu_hwmon_set_pwm1, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, amdgpu_hwmon_get_pwm1_enable, amdgpu_hwmon_set_pwm1_enable, 0); +static SENSOR_DEVICE_ATTR(pwm1_min, S_IRUGO, amdgpu_hwmon_get_pwm1_min, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm1_max, S_IRUGO, amdgpu_hwmon_get_pwm1_max, NULL, 0); + +static struct attribute *hwmon_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_min.dev_attr.attr, + &sensor_dev_attr_pwm1_max.dev_attr.attr, + NULL +}; + +static umode_t hwmon_attributes_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct amdgpu_device *adev = dev_get_drvdata(dev); + umode_t effective_mode = attr->mode; + + /* Skip limit attributes if DPM is not enabled */ + if (!adev->pm.dpm_enabled && + (attr == &sensor_dev_attr_temp1_crit.dev_attr.attr || + attr == &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr)) + return 0; + + /* Skip fan attributes if fan is not present */ + if (adev->pm.no_fan && + (attr == &sensor_dev_attr_pwm1.dev_attr.attr || + attr == &sensor_dev_attr_pwm1_enable.dev_attr.attr || + attr == &sensor_dev_attr_pwm1_max.dev_attr.attr || + attr == &sensor_dev_attr_pwm1_min.dev_attr.attr)) + return 0; + + /* mask fan attributes if we have no bindings for this asic to expose */ + if ((!adev->pm.funcs->get_fan_speed_percent && + attr == &sensor_dev_attr_pwm1.dev_attr.attr) || /* can't query fan */ + (!adev->pm.funcs->get_fan_control_mode && + attr == &sensor_dev_attr_pwm1_enable.dev_attr.attr)) /* can't query state */ + effective_mode &= ~S_IRUGO; + + if ((!adev->pm.funcs->set_fan_speed_percent && + attr == &sensor_dev_attr_pwm1.dev_attr.attr) || /* can't manage fan */ + (!adev->pm.funcs->set_fan_control_mode && + attr == &sensor_dev_attr_pwm1_enable.dev_attr.attr)) /* can't manage state */ + effective_mode &= ~S_IWUSR; + + /* hide max/min values if we can't both query and manage the fan */ + if ((!adev->pm.funcs->set_fan_speed_percent && + !adev->pm.funcs->get_fan_speed_percent) && + (attr == &sensor_dev_attr_pwm1_max.dev_attr.attr || + attr == &sensor_dev_attr_pwm1_min.dev_attr.attr)) + return 0; + + return effective_mode; +} + +static const struct attribute_group hwmon_attrgroup = { + .attrs = hwmon_attributes, + .is_visible = hwmon_attributes_visible, +}; + +static const struct attribute_group *hwmon_groups[] = { + &hwmon_attrgroup, + NULL +}; + +void amdgpu_dpm_thermal_work_handler(struct work_struct *work) +{ + struct amdgpu_device *adev = + container_of(work, struct amdgpu_device, + pm.dpm.thermal.work); + /* switch to the thermal state */ + enum amdgpu_pm_state_type dpm_state = POWER_STATE_TYPE_INTERNAL_THERMAL; + + if (!adev->pm.dpm_enabled) + return; + + if (adev->pm.funcs->get_temperature) { + int temp = amdgpu_dpm_get_temperature(adev); + + if (temp < adev->pm.dpm.thermal.min_temp) + /* switch back the user state */ + dpm_state = adev->pm.dpm.user_state; + } else { + if (adev->pm.dpm.thermal.high_to_low) + /* switch back the user state */ + dpm_state = adev->pm.dpm.user_state; + } + mutex_lock(&adev->pm.mutex); + if (dpm_state == POWER_STATE_TYPE_INTERNAL_THERMAL) + adev->pm.dpm.thermal_active = true; + else + adev->pm.dpm.thermal_active = false; + adev->pm.dpm.state = dpm_state; + mutex_unlock(&adev->pm.mutex); + + amdgpu_pm_compute_clocks(adev); +} + +static struct amdgpu_ps *amdgpu_dpm_pick_power_state(struct amdgpu_device *adev, + enum amdgpu_pm_state_type dpm_state) +{ + int i; + struct amdgpu_ps *ps; + u32 ui_class; + bool single_display = (adev->pm.dpm.new_active_crtc_count < 2) ? + true : false; + + /* check if the vblank period is too short to adjust the mclk */ + if (single_display && adev->pm.funcs->vblank_too_short) { + if (amdgpu_dpm_vblank_too_short(adev)) + single_display = false; + } + + /* certain older asics have a separare 3D performance state, + * so try that first if the user selected performance + */ + if (dpm_state == POWER_STATE_TYPE_PERFORMANCE) + dpm_state = POWER_STATE_TYPE_INTERNAL_3DPERF; + /* balanced states don't exist at the moment */ + if (dpm_state == POWER_STATE_TYPE_BALANCED) + dpm_state = POWER_STATE_TYPE_PERFORMANCE; + +restart_search: + /* Pick the best power state based on current conditions */ + for (i = 0; i < adev->pm.dpm.num_ps; i++) { + ps = &adev->pm.dpm.ps[i]; + ui_class = ps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK; + switch (dpm_state) { + /* user states */ + case POWER_STATE_TYPE_BATTERY: + if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_BATTERY) { + if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) { + if (single_display) + return ps; + } else + return ps; + } + break; + case POWER_STATE_TYPE_BALANCED: + if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_BALANCED) { + if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) { + if (single_display) + return ps; + } else + return ps; + } + break; + case POWER_STATE_TYPE_PERFORMANCE: + if (ui_class == ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) { + if (ps->caps & ATOM_PPLIB_SINGLE_DISPLAY_ONLY) { + if (single_display) + return ps; + } else + return ps; + } + break; + /* internal states */ + case POWER_STATE_TYPE_INTERNAL_UVD: + if (adev->pm.dpm.uvd_ps) + return adev->pm.dpm.uvd_ps; + else + break; + case POWER_STATE_TYPE_INTERNAL_UVD_SD: + if (ps->class & ATOM_PPLIB_CLASSIFICATION_SDSTATE) + return ps; + break; + case POWER_STATE_TYPE_INTERNAL_UVD_HD: + if (ps->class & ATOM_PPLIB_CLASSIFICATION_HDSTATE) + return ps; + break; + case POWER_STATE_TYPE_INTERNAL_UVD_HD2: + if (ps->class & ATOM_PPLIB_CLASSIFICATION_HD2STATE) + return ps; + break; + case POWER_STATE_TYPE_INTERNAL_UVD_MVC: + if (ps->class2 & ATOM_PPLIB_CLASSIFICATION2_MVC) + return ps; + break; + case POWER_STATE_TYPE_INTERNAL_BOOT: + return adev->pm.dpm.boot_ps; + case POWER_STATE_TYPE_INTERNAL_THERMAL: + if (ps->class & ATOM_PPLIB_CLASSIFICATION_THERMAL) + return ps; + break; + case POWER_STATE_TYPE_INTERNAL_ACPI: + if (ps->class & ATOM_PPLIB_CLASSIFICATION_ACPI) + return ps; + break; + case POWER_STATE_TYPE_INTERNAL_ULV: + if (ps->class2 & ATOM_PPLIB_CLASSIFICATION2_ULV) + return ps; + break; + case POWER_STATE_TYPE_INTERNAL_3DPERF: + if (ps->class & ATOM_PPLIB_CLASSIFICATION_3DPERFORMANCE) + return ps; + break; + default: + break; + } + } + /* use a fallback state if we didn't match */ + switch (dpm_state) { + case POWER_STATE_TYPE_INTERNAL_UVD_SD: + dpm_state = POWER_STATE_TYPE_INTERNAL_UVD_HD; + goto restart_search; + case POWER_STATE_TYPE_INTERNAL_UVD_HD: + case POWER_STATE_TYPE_INTERNAL_UVD_HD2: + case POWER_STATE_TYPE_INTERNAL_UVD_MVC: + if (adev->pm.dpm.uvd_ps) { + return adev->pm.dpm.uvd_ps; + } else { + dpm_state = POWER_STATE_TYPE_PERFORMANCE; + goto restart_search; + } + case POWER_STATE_TYPE_INTERNAL_THERMAL: + dpm_state = POWER_STATE_TYPE_INTERNAL_ACPI; + goto restart_search; + case POWER_STATE_TYPE_INTERNAL_ACPI: + dpm_state = POWER_STATE_TYPE_BATTERY; + goto restart_search; + case POWER_STATE_TYPE_BATTERY: + case POWER_STATE_TYPE_BALANCED: + case POWER_STATE_TYPE_INTERNAL_3DPERF: + dpm_state = POWER_STATE_TYPE_PERFORMANCE; + goto restart_search; + default: + break; + } + + return NULL; +} + +static void amdgpu_dpm_change_power_state_locked(struct amdgpu_device *adev) +{ + int i; + struct amdgpu_ps *ps; + enum amdgpu_pm_state_type dpm_state; + int ret; + + /* if dpm init failed */ + if (!adev->pm.dpm_enabled) + return; + + if (adev->pm.dpm.user_state != adev->pm.dpm.state) { + /* add other state override checks here */ + if ((!adev->pm.dpm.thermal_active) && + (!adev->pm.dpm.uvd_active)) + adev->pm.dpm.state = adev->pm.dpm.user_state; + } + dpm_state = adev->pm.dpm.state; + + ps = amdgpu_dpm_pick_power_state(adev, dpm_state); + if (ps) + adev->pm.dpm.requested_ps = ps; + else + return; + + /* no need to reprogram if nothing changed unless we are on BTC+ */ + if (adev->pm.dpm.current_ps == adev->pm.dpm.requested_ps) { + /* vce just modifies an existing state so force a change */ + if (ps->vce_active != adev->pm.dpm.vce_active) + goto force; + if (adev->flags & AMDGPU_IS_APU) { + /* for APUs if the num crtcs changed but state is the same, + * all we need to do is update the display configuration. + */ + if (adev->pm.dpm.new_active_crtcs != adev->pm.dpm.current_active_crtcs) { + /* update display watermarks based on new power state */ + amdgpu_display_bandwidth_update(adev); + /* update displays */ + amdgpu_dpm_display_configuration_changed(adev); + adev->pm.dpm.current_active_crtcs = adev->pm.dpm.new_active_crtcs; + adev->pm.dpm.current_active_crtc_count = adev->pm.dpm.new_active_crtc_count; + } + return; + } else { + /* for BTC+ if the num crtcs hasn't changed and state is the same, + * nothing to do, if the num crtcs is > 1 and state is the same, + * update display configuration. + */ + if (adev->pm.dpm.new_active_crtcs == + adev->pm.dpm.current_active_crtcs) { + return; + } else if ((adev->pm.dpm.current_active_crtc_count > 1) && + (adev->pm.dpm.new_active_crtc_count > 1)) { + /* update display watermarks based on new power state */ + amdgpu_display_bandwidth_update(adev); + /* update displays */ + amdgpu_dpm_display_configuration_changed(adev); + adev->pm.dpm.current_active_crtcs = adev->pm.dpm.new_active_crtcs; + adev->pm.dpm.current_active_crtc_count = adev->pm.dpm.new_active_crtc_count; + return; + } + } + } + +force: + if (amdgpu_dpm == 1) { + printk("switching from power state:\n"); + amdgpu_dpm_print_power_state(adev, adev->pm.dpm.current_ps); + printk("switching to power state:\n"); + amdgpu_dpm_print_power_state(adev, adev->pm.dpm.requested_ps); + } + + mutex_lock(&adev->ddev->struct_mutex); + down_write(&adev->pm.mclk_lock); + mutex_lock(&adev->ring_lock); + + /* update whether vce is active */ + ps->vce_active = adev->pm.dpm.vce_active; + + ret = amdgpu_dpm_pre_set_power_state(adev); + if (ret) + goto done; + + /* update display watermarks based on new power state */ + amdgpu_display_bandwidth_update(adev); + /* update displays */ + amdgpu_dpm_display_configuration_changed(adev); + + adev->pm.dpm.current_active_crtcs = adev->pm.dpm.new_active_crtcs; + adev->pm.dpm.current_active_crtc_count = adev->pm.dpm.new_active_crtc_count; + + /* wait for the rings to drain */ + for (i = 0; i < AMDGPU_MAX_RINGS; i++) { + struct amdgpu_ring *ring = adev->rings[i]; + if (ring && ring->ready) + amdgpu_fence_wait_empty(ring); + } + + /* program the new power state */ + amdgpu_dpm_set_power_state(adev); + + /* update current power state */ + adev->pm.dpm.current_ps = adev->pm.dpm.requested_ps; + + amdgpu_dpm_post_set_power_state(adev); + + if (adev->pm.funcs->force_performance_level) { + if (adev->pm.dpm.thermal_active) { + enum amdgpu_dpm_forced_level level = adev->pm.dpm.forced_level; + /* force low perf level for thermal */ + amdgpu_dpm_force_performance_level(adev, AMDGPU_DPM_FORCED_LEVEL_LOW); + /* save the user's level */ + adev->pm.dpm.forced_level = level; + } else { + /* otherwise, user selected level */ + amdgpu_dpm_force_performance_level(adev, adev->pm.dpm.forced_level); + } + } + +done: + mutex_unlock(&adev->ring_lock); + up_write(&adev->pm.mclk_lock); + mutex_unlock(&adev->ddev->struct_mutex); +} + +void amdgpu_dpm_enable_uvd(struct amdgpu_device *adev, bool enable) +{ + if (adev->pm.funcs->powergate_uvd) { + mutex_lock(&adev->pm.mutex); + /* enable/disable UVD */ + amdgpu_dpm_powergate_uvd(adev, !enable); + mutex_unlock(&adev->pm.mutex); + } else { + if (enable) { + mutex_lock(&adev->pm.mutex); + adev->pm.dpm.uvd_active = true; + adev->pm.dpm.state = POWER_STATE_TYPE_INTERNAL_UVD; + mutex_unlock(&adev->pm.mutex); + } else { + mutex_lock(&adev->pm.mutex); + adev->pm.dpm.uvd_active = false; + mutex_unlock(&adev->pm.mutex); + } + + amdgpu_pm_compute_clocks(adev); + } +} + +void amdgpu_dpm_enable_vce(struct amdgpu_device *adev, bool enable) +{ + if (enable) { + mutex_lock(&adev->pm.mutex); + adev->pm.dpm.vce_active = true; + /* XXX select vce level based on ring/task */ + adev->pm.dpm.vce_level = AMDGPU_VCE_LEVEL_AC_ALL; + mutex_unlock(&adev->pm.mutex); + } else { + mutex_lock(&adev->pm.mutex); + adev->pm.dpm.vce_active = false; + mutex_unlock(&adev->pm.mutex); + } + + amdgpu_pm_compute_clocks(adev); +} + +void amdgpu_pm_print_power_states(struct amdgpu_device *adev) +{ + int i; + + for (i = 0; i < adev->pm.dpm.num_ps; i++) { + printk("== power state %d ==\n", i); + amdgpu_dpm_print_power_state(adev, &adev->pm.dpm.ps[i]); + } +} + +int amdgpu_pm_sysfs_init(struct amdgpu_device *adev) +{ + int ret; + + if (adev->pm.funcs->get_temperature == NULL) + return 0; + adev->pm.int_hwmon_dev = hwmon_device_register_with_groups(adev->dev, + DRIVER_NAME, adev, + hwmon_groups); + if (IS_ERR(adev->pm.int_hwmon_dev)) { + ret = PTR_ERR(adev->pm.int_hwmon_dev); + dev_err(adev->dev, + "Unable to register hwmon device: %d\n", ret); + return ret; + } + + ret = device_create_file(adev->dev, &dev_attr_power_dpm_state); + if (ret) { + DRM_ERROR("failed to create device file for dpm state\n"); + return ret; + } + ret = device_create_file(adev->dev, &dev_attr_power_dpm_force_performance_level); + if (ret) { + DRM_ERROR("failed to create device file for dpm state\n"); + return ret; + } + ret = amdgpu_debugfs_pm_init(adev); + if (ret) { + DRM_ERROR("Failed to register debugfs file for dpm!\n"); + return ret; + } + + return 0; +} + +void amdgpu_pm_sysfs_fini(struct amdgpu_device *adev) +{ + if (adev->pm.int_hwmon_dev) + hwmon_device_unregister(adev->pm.int_hwmon_dev); + device_remove_file(adev->dev, &dev_attr_power_dpm_state); + device_remove_file(adev->dev, &dev_attr_power_dpm_force_performance_level); +} + +void amdgpu_pm_compute_clocks(struct amdgpu_device *adev) +{ + struct drm_device *ddev = adev->ddev; + struct drm_crtc *crtc; + struct amdgpu_crtc *amdgpu_crtc; + + if (!adev->pm.dpm_enabled) + return; + + mutex_lock(&adev->pm.mutex); + + /* update active crtc counts */ + adev->pm.dpm.new_active_crtcs = 0; + adev->pm.dpm.new_active_crtc_count = 0; + if (adev->mode_info.num_crtc && adev->mode_info.mode_config_initialized) { + list_for_each_entry(crtc, + &ddev->mode_config.crtc_list, head) { + amdgpu_crtc = to_amdgpu_crtc(crtc); + if (crtc->enabled) { + adev->pm.dpm.new_active_crtcs |= (1 << amdgpu_crtc->crtc_id); + adev->pm.dpm.new_active_crtc_count++; + } + } + } + + /* update battery/ac status */ + if (power_supply_is_system_supplied() > 0) + adev->pm.dpm.ac_power = true; + else + adev->pm.dpm.ac_power = false; + + amdgpu_dpm_change_power_state_locked(adev); + + mutex_unlock(&adev->pm.mutex); + +} + +/* + * Debugfs info + */ +#if defined(CONFIG_DEBUG_FS) + +static int amdgpu_debugfs_pm_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct amdgpu_device *adev = dev->dev_private; + + if (adev->pm.dpm_enabled) { + mutex_lock(&adev->pm.mutex); + if (adev->pm.funcs->debugfs_print_current_performance_level) + amdgpu_dpm_debugfs_print_current_performance_level(adev, m); + else + seq_printf(m, "Debugfs support not implemented for this asic\n"); + mutex_unlock(&adev->pm.mutex); + } + + return 0; +} + +static struct drm_info_list amdgpu_pm_info_list[] = { + {"amdgpu_pm_info", amdgpu_debugfs_pm_info, 0, NULL}, +}; +#endif + +static int amdgpu_debugfs_pm_init(struct amdgpu_device *adev) +{ +#if defined(CONFIG_DEBUG_FS) + return amdgpu_debugfs_add_files(adev, amdgpu_pm_info_list, ARRAY_SIZE(amdgpu_pm_info_list)); +#else + return 0; +#endif +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.h new file mode 100644 index 000000000000..5fd7734f15ca --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.h @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_PM_H__ +#define __AMDGPU_PM_H__ + +int amdgpu_pm_sysfs_init(struct amdgpu_device *adev); +void amdgpu_pm_sysfs_fini(struct amdgpu_device *adev); +void amdgpu_pm_print_power_states(struct amdgpu_device *adev); +void amdgpu_pm_compute_clocks(struct amdgpu_device *adev); +void amdgpu_dpm_thermal_work_handler(struct work_struct *work); +void amdgpu_dpm_enable_uvd(struct amdgpu_device *adev, bool enable); +void amdgpu_dpm_enable_vce(struct amdgpu_device *adev, bool enable); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_prime.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_prime.c new file mode 100644 index 000000000000..d9652fe32d6a --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_prime.c @@ -0,0 +1,125 @@ +/* + * Copyright 2012 Advanced Micro Devices, 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. + * + * based on nouveau_prime.c + * + * Authors: Alex Deucher + */ +#include <drm/drmP.h> + +#include "amdgpu.h" +#include <drm/amdgpu_drm.h> +#include <linux/dma-buf.h> + +struct sg_table *amdgpu_gem_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + int npages = bo->tbo.num_pages; + + return drm_prime_pages_to_sg(bo->tbo.ttm->pages, npages); +} + +void *amdgpu_gem_prime_vmap(struct drm_gem_object *obj) +{ + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + int ret; + + ret = ttm_bo_kmap(&bo->tbo, 0, bo->tbo.num_pages, + &bo->dma_buf_vmap); + if (ret) + return ERR_PTR(ret); + + return bo->dma_buf_vmap.virtual; +} + +void amdgpu_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr) +{ + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + + ttm_bo_kunmap(&bo->dma_buf_vmap); +} + +struct drm_gem_object *amdgpu_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sg) +{ + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_bo *bo; + int ret; + + ret = amdgpu_bo_create(adev, attach->dmabuf->size, PAGE_SIZE, false, + AMDGPU_GEM_DOMAIN_GTT, 0, sg, &bo); + if (ret) + return ERR_PTR(ret); + + mutex_lock(&adev->gem.mutex); + list_add_tail(&bo->list, &adev->gem.objects); + mutex_unlock(&adev->gem.mutex); + + return &bo->gem_base; +} + +int amdgpu_gem_prime_pin(struct drm_gem_object *obj) +{ + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + int ret = 0; + + ret = amdgpu_bo_reserve(bo, false); + if (unlikely(ret != 0)) + return ret; + + /* pin buffer into GTT */ + ret = amdgpu_bo_pin(bo, AMDGPU_GEM_DOMAIN_GTT, NULL); + amdgpu_bo_unreserve(bo); + return ret; +} + +void amdgpu_gem_prime_unpin(struct drm_gem_object *obj) +{ + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + int ret = 0; + + ret = amdgpu_bo_reserve(bo, false); + if (unlikely(ret != 0)) + return; + + amdgpu_bo_unpin(bo); + amdgpu_bo_unreserve(bo); +} + +struct reservation_object *amdgpu_gem_prime_res_obj(struct drm_gem_object *obj) +{ + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + + return bo->tbo.resv; +} + +struct dma_buf *amdgpu_gem_prime_export(struct drm_device *dev, + struct drm_gem_object *gobj, + int flags) +{ + struct amdgpu_bo *bo = gem_to_amdgpu_bo(gobj); + + if (amdgpu_ttm_tt_has_userptr(bo->tbo.ttm)) + return ERR_PTR(-EPERM); + + return drm_gem_prime_export(dev, gobj, flags); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c new file mode 100644 index 000000000000..855e2196657a --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -0,0 +1,561 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + * Christian König + */ +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "atom.h" + +/* + * Rings + * Most engines on the GPU are fed via ring buffers. Ring + * buffers are areas of GPU accessible memory that the host + * writes commands into and the GPU reads commands out of. + * There is a rptr (read pointer) that determines where the + * GPU is currently reading, and a wptr (write pointer) + * which determines where the host has written. When the + * pointers are equal, the ring is idle. When the host + * writes commands to the ring buffer, it increments the + * wptr. The GPU then starts fetching commands and executes + * them until the pointers are equal again. + */ +static int amdgpu_debugfs_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring); + +/** + * amdgpu_ring_free_size - update the free size + * + * @adev: amdgpu_device pointer + * @ring: amdgpu_ring structure holding ring information + * + * Update the free dw slots in the ring buffer (all asics). + */ +void amdgpu_ring_free_size(struct amdgpu_ring *ring) +{ + uint32_t rptr = amdgpu_ring_get_rptr(ring); + + /* This works because ring_size is a power of 2 */ + ring->ring_free_dw = rptr + (ring->ring_size / 4); + ring->ring_free_dw -= ring->wptr; + ring->ring_free_dw &= ring->ptr_mask; + if (!ring->ring_free_dw) { + /* this is an empty ring */ + ring->ring_free_dw = ring->ring_size / 4; + /* update lockup info to avoid false positive */ + amdgpu_ring_lockup_update(ring); + } +} + +/** + * amdgpu_ring_alloc - allocate space on the ring buffer + * + * @adev: amdgpu_device pointer + * @ring: amdgpu_ring structure holding ring information + * @ndw: number of dwords to allocate in the ring buffer + * + * Allocate @ndw dwords in the ring buffer (all asics). + * Returns 0 on success, error on failure. + */ +int amdgpu_ring_alloc(struct amdgpu_ring *ring, unsigned ndw) +{ + int r; + + /* make sure we aren't trying to allocate more space than there is on the ring */ + if (ndw > (ring->ring_size / 4)) + return -ENOMEM; + /* Align requested size with padding so unlock_commit can + * pad safely */ + amdgpu_ring_free_size(ring); + ndw = (ndw + ring->align_mask) & ~ring->align_mask; + while (ndw > (ring->ring_free_dw - 1)) { + amdgpu_ring_free_size(ring); + if (ndw < ring->ring_free_dw) { + break; + } + r = amdgpu_fence_wait_next(ring); + if (r) + return r; + } + ring->count_dw = ndw; + ring->wptr_old = ring->wptr; + return 0; +} + +/** + * amdgpu_ring_lock - lock the ring and allocate space on it + * + * @adev: amdgpu_device pointer + * @ring: amdgpu_ring structure holding ring information + * @ndw: number of dwords to allocate in the ring buffer + * + * Lock the ring and allocate @ndw dwords in the ring buffer + * (all asics). + * Returns 0 on success, error on failure. + */ +int amdgpu_ring_lock(struct amdgpu_ring *ring, unsigned ndw) +{ + int r; + + mutex_lock(ring->ring_lock); + r = amdgpu_ring_alloc(ring, ndw); + if (r) { + mutex_unlock(ring->ring_lock); + return r; + } + return 0; +} + +/** + * amdgpu_ring_commit - tell the GPU to execute the new + * commands on the ring buffer + * + * @adev: amdgpu_device pointer + * @ring: amdgpu_ring structure holding ring information + * + * Update the wptr (write pointer) to tell the GPU to + * execute new commands on the ring buffer (all asics). + */ +void amdgpu_ring_commit(struct amdgpu_ring *ring) +{ + /* We pad to match fetch size */ + while (ring->wptr & ring->align_mask) { + amdgpu_ring_write(ring, ring->nop); + } + mb(); + amdgpu_ring_set_wptr(ring); +} + +/** + * amdgpu_ring_unlock_commit - tell the GPU to execute the new + * commands on the ring buffer and unlock it + * + * @ring: amdgpu_ring structure holding ring information + * + * Call amdgpu_ring_commit() then unlock the ring (all asics). + */ +void amdgpu_ring_unlock_commit(struct amdgpu_ring *ring) +{ + amdgpu_ring_commit(ring); + mutex_unlock(ring->ring_lock); +} + +/** + * amdgpu_ring_undo - reset the wptr + * + * @ring: amdgpu_ring structure holding ring information + * + * Reset the driver's copy of the wptr (all asics). + */ +void amdgpu_ring_undo(struct amdgpu_ring *ring) +{ + ring->wptr = ring->wptr_old; +} + +/** + * amdgpu_ring_unlock_undo - reset the wptr and unlock the ring + * + * @ring: amdgpu_ring structure holding ring information + * + * Call amdgpu_ring_undo() then unlock the ring (all asics). + */ +void amdgpu_ring_unlock_undo(struct amdgpu_ring *ring) +{ + amdgpu_ring_undo(ring); + mutex_unlock(ring->ring_lock); +} + +/** + * amdgpu_ring_lockup_update - update lockup variables + * + * @ring: amdgpu_ring structure holding ring information + * + * Update the last rptr value and timestamp (all asics). + */ +void amdgpu_ring_lockup_update(struct amdgpu_ring *ring) +{ + atomic_set(&ring->last_rptr, amdgpu_ring_get_rptr(ring)); + atomic64_set(&ring->last_activity, jiffies_64); +} + +/** + * amdgpu_ring_test_lockup() - check if ring is lockedup by recording information + * @ring: amdgpu_ring structure holding ring information + * + */ +bool amdgpu_ring_test_lockup(struct amdgpu_ring *ring) +{ + uint32_t rptr = amdgpu_ring_get_rptr(ring); + uint64_t last = atomic64_read(&ring->last_activity); + uint64_t elapsed; + + if (rptr != atomic_read(&ring->last_rptr)) { + /* ring is still working, no lockup */ + amdgpu_ring_lockup_update(ring); + return false; + } + + elapsed = jiffies_to_msecs(jiffies_64 - last); + if (amdgpu_lockup_timeout && elapsed >= amdgpu_lockup_timeout) { + dev_err(ring->adev->dev, "ring %d stalled for more than %llumsec\n", + ring->idx, elapsed); + return true; + } + /* give a chance to the GPU ... */ + return false; +} + +/** + * amdgpu_ring_backup - Back up the content of a ring + * + * @ring: the ring we want to back up + * + * Saves all unprocessed commits from a ring, returns the number of dwords saved. + */ +unsigned amdgpu_ring_backup(struct amdgpu_ring *ring, + uint32_t **data) +{ + unsigned size, ptr, i; + + /* just in case lock the ring */ + mutex_lock(ring->ring_lock); + *data = NULL; + + if (ring->ring_obj == NULL) { + mutex_unlock(ring->ring_lock); + return 0; + } + + /* it doesn't make sense to save anything if all fences are signaled */ + if (!amdgpu_fence_count_emitted(ring)) { + mutex_unlock(ring->ring_lock); + return 0; + } + + ptr = le32_to_cpu(*ring->next_rptr_cpu_addr); + + size = ring->wptr + (ring->ring_size / 4); + size -= ptr; + size &= ring->ptr_mask; + if (size == 0) { + mutex_unlock(ring->ring_lock); + return 0; + } + + /* and then save the content of the ring */ + *data = kmalloc_array(size, sizeof(uint32_t), GFP_KERNEL); + if (!*data) { + mutex_unlock(ring->ring_lock); + return 0; + } + for (i = 0; i < size; ++i) { + (*data)[i] = ring->ring[ptr++]; + ptr &= ring->ptr_mask; + } + + mutex_unlock(ring->ring_lock); + return size; +} + +/** + * amdgpu_ring_restore - append saved commands to the ring again + * + * @ring: ring to append commands to + * @size: number of dwords we want to write + * @data: saved commands + * + * Allocates space on the ring and restore the previously saved commands. + */ +int amdgpu_ring_restore(struct amdgpu_ring *ring, + unsigned size, uint32_t *data) +{ + int i, r; + + if (!size || !data) + return 0; + + /* restore the saved ring content */ + r = amdgpu_ring_lock(ring, size); + if (r) + return r; + + for (i = 0; i < size; ++i) { + amdgpu_ring_write(ring, data[i]); + } + + amdgpu_ring_unlock_commit(ring); + kfree(data); + return 0; +} + +/** + * amdgpu_ring_init - init driver ring struct. + * + * @adev: amdgpu_device pointer + * @ring: amdgpu_ring structure holding ring information + * @ring_size: size of the ring + * @nop: nop packet for this ring + * + * Initialize the driver information for the selected ring (all asics). + * Returns 0 on success, error on failure. + */ +int amdgpu_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring, + unsigned ring_size, u32 nop, u32 align_mask, + struct amdgpu_irq_src *irq_src, unsigned irq_type, + enum amdgpu_ring_type ring_type) +{ + u32 rb_bufsz; + int r; + + if (ring->adev == NULL) { + if (adev->num_rings >= AMDGPU_MAX_RINGS) + return -EINVAL; + + ring->adev = adev; + ring->idx = adev->num_rings++; + adev->rings[ring->idx] = ring; + amdgpu_fence_driver_init_ring(ring); + } + + r = amdgpu_wb_get(adev, &ring->rptr_offs); + if (r) { + dev_err(adev->dev, "(%d) ring rptr_offs wb alloc failed\n", r); + return r; + } + + r = amdgpu_wb_get(adev, &ring->wptr_offs); + if (r) { + dev_err(adev->dev, "(%d) ring wptr_offs wb alloc failed\n", r); + return r; + } + + r = amdgpu_wb_get(adev, &ring->fence_offs); + if (r) { + dev_err(adev->dev, "(%d) ring fence_offs wb alloc failed\n", r); + return r; + } + + r = amdgpu_wb_get(adev, &ring->next_rptr_offs); + if (r) { + dev_err(adev->dev, "(%d) ring next_rptr wb alloc failed\n", r); + return r; + } + ring->next_rptr_gpu_addr = adev->wb.gpu_addr + (ring->next_rptr_offs * 4); + ring->next_rptr_cpu_addr = &adev->wb.wb[ring->next_rptr_offs]; + + r = amdgpu_fence_driver_start_ring(ring, irq_src, irq_type); + if (r) { + dev_err(adev->dev, "failed initializing fences (%d).\n", r); + return r; + } + + ring->ring_lock = &adev->ring_lock; + /* Align ring size */ + rb_bufsz = order_base_2(ring_size / 8); + ring_size = (1 << (rb_bufsz + 1)) * 4; + ring->ring_size = ring_size; + ring->align_mask = align_mask; + ring->nop = nop; + ring->type = ring_type; + + /* Allocate ring buffer */ + if (ring->ring_obj == NULL) { + r = amdgpu_bo_create(adev, ring->ring_size, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_GTT, 0, + NULL, &ring->ring_obj); + if (r) { + dev_err(adev->dev, "(%d) ring create failed\n", r); + return r; + } + r = amdgpu_bo_reserve(ring->ring_obj, false); + if (unlikely(r != 0)) + return r; + r = amdgpu_bo_pin(ring->ring_obj, AMDGPU_GEM_DOMAIN_GTT, + &ring->gpu_addr); + if (r) { + amdgpu_bo_unreserve(ring->ring_obj); + dev_err(adev->dev, "(%d) ring pin failed\n", r); + return r; + } + r = amdgpu_bo_kmap(ring->ring_obj, + (void **)&ring->ring); + amdgpu_bo_unreserve(ring->ring_obj); + if (r) { + dev_err(adev->dev, "(%d) ring map failed\n", r); + return r; + } + } + ring->ptr_mask = (ring->ring_size / 4) - 1; + ring->ring_free_dw = ring->ring_size / 4; + + if (amdgpu_debugfs_ring_init(adev, ring)) { + DRM_ERROR("Failed to register debugfs file for rings !\n"); + } + amdgpu_ring_lockup_update(ring); + return 0; +} + +/** + * amdgpu_ring_fini - tear down the driver ring struct. + * + * @adev: amdgpu_device pointer + * @ring: amdgpu_ring structure holding ring information + * + * Tear down the driver information for the selected ring (all asics). + */ +void amdgpu_ring_fini(struct amdgpu_ring *ring) +{ + int r; + struct amdgpu_bo *ring_obj; + + if (ring->ring_lock == NULL) + return; + + mutex_lock(ring->ring_lock); + ring_obj = ring->ring_obj; + ring->ready = false; + ring->ring = NULL; + ring->ring_obj = NULL; + mutex_unlock(ring->ring_lock); + + amdgpu_wb_free(ring->adev, ring->fence_offs); + amdgpu_wb_free(ring->adev, ring->rptr_offs); + amdgpu_wb_free(ring->adev, ring->wptr_offs); + amdgpu_wb_free(ring->adev, ring->next_rptr_offs); + + if (ring_obj) { + r = amdgpu_bo_reserve(ring_obj, false); + if (likely(r == 0)) { + amdgpu_bo_kunmap(ring_obj); + amdgpu_bo_unpin(ring_obj); + amdgpu_bo_unreserve(ring_obj); + } + amdgpu_bo_unref(&ring_obj); + } +} + +/* + * Debugfs info + */ +#if defined(CONFIG_DEBUG_FS) + +static int amdgpu_debugfs_ring_info(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct amdgpu_device *adev = dev->dev_private; + int roffset = *(int*)node->info_ent->data; + struct amdgpu_ring *ring = (void *)(((uint8_t*)adev) + roffset); + + uint32_t rptr, wptr, rptr_next; + unsigned count, i, j; + + amdgpu_ring_free_size(ring); + count = (ring->ring_size / 4) - ring->ring_free_dw; + + wptr = amdgpu_ring_get_wptr(ring); + seq_printf(m, "wptr: 0x%08x [%5d]\n", + wptr, wptr); + + rptr = amdgpu_ring_get_rptr(ring); + seq_printf(m, "rptr: 0x%08x [%5d]\n", + rptr, rptr); + + rptr_next = ~0; + + seq_printf(m, "driver's copy of the wptr: 0x%08x [%5d]\n", + ring->wptr, ring->wptr); + seq_printf(m, "last semaphore signal addr : 0x%016llx\n", + ring->last_semaphore_signal_addr); + seq_printf(m, "last semaphore wait addr : 0x%016llx\n", + ring->last_semaphore_wait_addr); + seq_printf(m, "%u free dwords in ring\n", ring->ring_free_dw); + seq_printf(m, "%u dwords in ring\n", count); + + if (!ring->ready) + return 0; + + /* print 8 dw before current rptr as often it's the last executed + * packet that is the root issue + */ + i = (rptr + ring->ptr_mask + 1 - 32) & ring->ptr_mask; + for (j = 0; j <= (count + 32); j++) { + seq_printf(m, "r[%5d]=0x%08x", i, ring->ring[i]); + if (rptr == i) + seq_puts(m, " *"); + if (rptr_next == i) + seq_puts(m, " #"); + seq_puts(m, "\n"); + i = (i + 1) & ring->ptr_mask; + } + return 0; +} + +/* TODO: clean this up !*/ +static int amdgpu_gfx_index = offsetof(struct amdgpu_device, gfx.gfx_ring[0]); +static int cayman_cp1_index = offsetof(struct amdgpu_device, gfx.compute_ring[0]); +static int cayman_cp2_index = offsetof(struct amdgpu_device, gfx.compute_ring[1]); +static int amdgpu_dma1_index = offsetof(struct amdgpu_device, sdma[0].ring); +static int amdgpu_dma2_index = offsetof(struct amdgpu_device, sdma[1].ring); +static int r600_uvd_index = offsetof(struct amdgpu_device, uvd.ring); +static int si_vce1_index = offsetof(struct amdgpu_device, vce.ring[0]); +static int si_vce2_index = offsetof(struct amdgpu_device, vce.ring[1]); + +static struct drm_info_list amdgpu_debugfs_ring_info_list[] = { + {"amdgpu_ring_gfx", amdgpu_debugfs_ring_info, 0, &amdgpu_gfx_index}, + {"amdgpu_ring_cp1", amdgpu_debugfs_ring_info, 0, &cayman_cp1_index}, + {"amdgpu_ring_cp2", amdgpu_debugfs_ring_info, 0, &cayman_cp2_index}, + {"amdgpu_ring_dma1", amdgpu_debugfs_ring_info, 0, &amdgpu_dma1_index}, + {"amdgpu_ring_dma2", amdgpu_debugfs_ring_info, 0, &amdgpu_dma2_index}, + {"amdgpu_ring_uvd", amdgpu_debugfs_ring_info, 0, &r600_uvd_index}, + {"amdgpu_ring_vce1", amdgpu_debugfs_ring_info, 0, &si_vce1_index}, + {"amdgpu_ring_vce2", amdgpu_debugfs_ring_info, 0, &si_vce2_index}, +}; + +#endif + +static int amdgpu_debugfs_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring) +{ +#if defined(CONFIG_DEBUG_FS) + unsigned i; + for (i = 0; i < ARRAY_SIZE(amdgpu_debugfs_ring_info_list); ++i) { + struct drm_info_list *info = &amdgpu_debugfs_ring_info_list[i]; + int roffset = *(int*)amdgpu_debugfs_ring_info_list[i].data; + struct amdgpu_ring *other = (void *)(((uint8_t*)adev) + roffset); + unsigned r; + + if (other != ring) + continue; + + r = amdgpu_debugfs_add_files(adev, info, 1); + if (r) + return r; + } +#endif + return 0; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c new file mode 100644 index 000000000000..eb20987ce18d --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sa.c @@ -0,0 +1,419 @@ +/* + * Copyright 2011 Red Hat Inc. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Jerome Glisse <glisse@freedesktop.org> + */ +/* Algorithm: + * + * We store the last allocated bo in "hole", we always try to allocate + * after the last allocated bo. Principle is that in a linear GPU ring + * progression was is after last is the oldest bo we allocated and thus + * the first one that should no longer be in use by the GPU. + * + * If it's not the case we skip over the bo after last to the closest + * done bo if such one exist. If none exist and we are not asked to + * block we report failure to allocate. + * + * If we are asked to block we wait on all the oldest fence of all + * rings. We just wait for any of those fence to complete. + */ +#include <drm/drmP.h> +#include "amdgpu.h" + +static void amdgpu_sa_bo_remove_locked(struct amdgpu_sa_bo *sa_bo); +static void amdgpu_sa_bo_try_free(struct amdgpu_sa_manager *sa_manager); + +int amdgpu_sa_bo_manager_init(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager, + unsigned size, u32 align, u32 domain) +{ + int i, r; + + init_waitqueue_head(&sa_manager->wq); + sa_manager->bo = NULL; + sa_manager->size = size; + sa_manager->domain = domain; + sa_manager->align = align; + sa_manager->hole = &sa_manager->olist; + INIT_LIST_HEAD(&sa_manager->olist); + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + INIT_LIST_HEAD(&sa_manager->flist[i]); + } + + r = amdgpu_bo_create(adev, size, align, true, + domain, 0, NULL, &sa_manager->bo); + if (r) { + dev_err(adev->dev, "(%d) failed to allocate bo for manager\n", r); + return r; + } + + return r; +} + +void amdgpu_sa_bo_manager_fini(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager) +{ + struct amdgpu_sa_bo *sa_bo, *tmp; + + if (!list_empty(&sa_manager->olist)) { + sa_manager->hole = &sa_manager->olist, + amdgpu_sa_bo_try_free(sa_manager); + if (!list_empty(&sa_manager->olist)) { + dev_err(adev->dev, "sa_manager is not empty, clearing anyway\n"); + } + } + list_for_each_entry_safe(sa_bo, tmp, &sa_manager->olist, olist) { + amdgpu_sa_bo_remove_locked(sa_bo); + } + amdgpu_bo_unref(&sa_manager->bo); + sa_manager->size = 0; +} + +int amdgpu_sa_bo_manager_start(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager) +{ + int r; + + if (sa_manager->bo == NULL) { + dev_err(adev->dev, "no bo for sa manager\n"); + return -EINVAL; + } + + /* map the buffer */ + r = amdgpu_bo_reserve(sa_manager->bo, false); + if (r) { + dev_err(adev->dev, "(%d) failed to reserve manager bo\n", r); + return r; + } + r = amdgpu_bo_pin(sa_manager->bo, sa_manager->domain, &sa_manager->gpu_addr); + if (r) { + amdgpu_bo_unreserve(sa_manager->bo); + dev_err(adev->dev, "(%d) failed to pin manager bo\n", r); + return r; + } + r = amdgpu_bo_kmap(sa_manager->bo, &sa_manager->cpu_ptr); + amdgpu_bo_unreserve(sa_manager->bo); + return r; +} + +int amdgpu_sa_bo_manager_suspend(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager) +{ + int r; + + if (sa_manager->bo == NULL) { + dev_err(adev->dev, "no bo for sa manager\n"); + return -EINVAL; + } + + r = amdgpu_bo_reserve(sa_manager->bo, false); + if (!r) { + amdgpu_bo_kunmap(sa_manager->bo); + amdgpu_bo_unpin(sa_manager->bo); + amdgpu_bo_unreserve(sa_manager->bo); + } + return r; +} + +static void amdgpu_sa_bo_remove_locked(struct amdgpu_sa_bo *sa_bo) +{ + struct amdgpu_sa_manager *sa_manager = sa_bo->manager; + if (sa_manager->hole == &sa_bo->olist) { + sa_manager->hole = sa_bo->olist.prev; + } + list_del_init(&sa_bo->olist); + list_del_init(&sa_bo->flist); + amdgpu_fence_unref(&sa_bo->fence); + kfree(sa_bo); +} + +static void amdgpu_sa_bo_try_free(struct amdgpu_sa_manager *sa_manager) +{ + struct amdgpu_sa_bo *sa_bo, *tmp; + + if (sa_manager->hole->next == &sa_manager->olist) + return; + + sa_bo = list_entry(sa_manager->hole->next, struct amdgpu_sa_bo, olist); + list_for_each_entry_safe_from(sa_bo, tmp, &sa_manager->olist, olist) { + if (sa_bo->fence == NULL || !amdgpu_fence_signaled(sa_bo->fence)) { + return; + } + amdgpu_sa_bo_remove_locked(sa_bo); + } +} + +static inline unsigned amdgpu_sa_bo_hole_soffset(struct amdgpu_sa_manager *sa_manager) +{ + struct list_head *hole = sa_manager->hole; + + if (hole != &sa_manager->olist) { + return list_entry(hole, struct amdgpu_sa_bo, olist)->eoffset; + } + return 0; +} + +static inline unsigned amdgpu_sa_bo_hole_eoffset(struct amdgpu_sa_manager *sa_manager) +{ + struct list_head *hole = sa_manager->hole; + + if (hole->next != &sa_manager->olist) { + return list_entry(hole->next, struct amdgpu_sa_bo, olist)->soffset; + } + return sa_manager->size; +} + +static bool amdgpu_sa_bo_try_alloc(struct amdgpu_sa_manager *sa_manager, + struct amdgpu_sa_bo *sa_bo, + unsigned size, unsigned align) +{ + unsigned soffset, eoffset, wasted; + + soffset = amdgpu_sa_bo_hole_soffset(sa_manager); + eoffset = amdgpu_sa_bo_hole_eoffset(sa_manager); + wasted = (align - (soffset % align)) % align; + + if ((eoffset - soffset) >= (size + wasted)) { + soffset += wasted; + + sa_bo->manager = sa_manager; + sa_bo->soffset = soffset; + sa_bo->eoffset = soffset + size; + list_add(&sa_bo->olist, sa_manager->hole); + INIT_LIST_HEAD(&sa_bo->flist); + sa_manager->hole = &sa_bo->olist; + return true; + } + return false; +} + +/** + * amdgpu_sa_event - Check if we can stop waiting + * + * @sa_manager: pointer to the sa_manager + * @size: number of bytes we want to allocate + * @align: alignment we need to match + * + * Check if either there is a fence we can wait for or + * enough free memory to satisfy the allocation directly + */ +static bool amdgpu_sa_event(struct amdgpu_sa_manager *sa_manager, + unsigned size, unsigned align) +{ + unsigned soffset, eoffset, wasted; + int i; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + if (!list_empty(&sa_manager->flist[i])) { + return true; + } + } + + soffset = amdgpu_sa_bo_hole_soffset(sa_manager); + eoffset = amdgpu_sa_bo_hole_eoffset(sa_manager); + wasted = (align - (soffset % align)) % align; + + if ((eoffset - soffset) >= (size + wasted)) { + return true; + } + + return false; +} + +static bool amdgpu_sa_bo_next_hole(struct amdgpu_sa_manager *sa_manager, + struct amdgpu_fence **fences, + unsigned *tries) +{ + struct amdgpu_sa_bo *best_bo = NULL; + unsigned i, soffset, best, tmp; + + /* if hole points to the end of the buffer */ + if (sa_manager->hole->next == &sa_manager->olist) { + /* try again with its beginning */ + sa_manager->hole = &sa_manager->olist; + return true; + } + + soffset = amdgpu_sa_bo_hole_soffset(sa_manager); + /* to handle wrap around we add sa_manager->size */ + best = sa_manager->size * 2; + /* go over all fence list and try to find the closest sa_bo + * of the current last + */ + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_sa_bo *sa_bo; + + if (list_empty(&sa_manager->flist[i])) { + continue; + } + + sa_bo = list_first_entry(&sa_manager->flist[i], + struct amdgpu_sa_bo, flist); + + if (!amdgpu_fence_signaled(sa_bo->fence)) { + fences[i] = sa_bo->fence; + continue; + } + + /* limit the number of tries each ring gets */ + if (tries[i] > 2) { + continue; + } + + tmp = sa_bo->soffset; + if (tmp < soffset) { + /* wrap around, pretend it's after */ + tmp += sa_manager->size; + } + tmp -= soffset; + if (tmp < best) { + /* this sa bo is the closest one */ + best = tmp; + best_bo = sa_bo; + } + } + + if (best_bo) { + ++tries[best_bo->fence->ring->idx]; + sa_manager->hole = best_bo->olist.prev; + + /* we knew that this one is signaled, + so it's save to remote it */ + amdgpu_sa_bo_remove_locked(best_bo); + return true; + } + return false; +} + +int amdgpu_sa_bo_new(struct amdgpu_device *adev, + struct amdgpu_sa_manager *sa_manager, + struct amdgpu_sa_bo **sa_bo, + unsigned size, unsigned align) +{ + struct amdgpu_fence *fences[AMDGPU_MAX_RINGS]; + unsigned tries[AMDGPU_MAX_RINGS]; + int i, r; + + BUG_ON(align > sa_manager->align); + BUG_ON(size > sa_manager->size); + + *sa_bo = kmalloc(sizeof(struct amdgpu_sa_bo), GFP_KERNEL); + if ((*sa_bo) == NULL) { + return -ENOMEM; + } + (*sa_bo)->manager = sa_manager; + (*sa_bo)->fence = NULL; + INIT_LIST_HEAD(&(*sa_bo)->olist); + INIT_LIST_HEAD(&(*sa_bo)->flist); + + spin_lock(&sa_manager->wq.lock); + do { + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + fences[i] = NULL; + tries[i] = 0; + } + + do { + amdgpu_sa_bo_try_free(sa_manager); + + if (amdgpu_sa_bo_try_alloc(sa_manager, *sa_bo, + size, align)) { + spin_unlock(&sa_manager->wq.lock); + return 0; + } + + /* see if we can skip over some allocations */ + } while (amdgpu_sa_bo_next_hole(sa_manager, fences, tries)); + + spin_unlock(&sa_manager->wq.lock); + r = amdgpu_fence_wait_any(adev, fences, false); + spin_lock(&sa_manager->wq.lock); + /* if we have nothing to wait for block */ + if (r == -ENOENT) { + r = wait_event_interruptible_locked( + sa_manager->wq, + amdgpu_sa_event(sa_manager, size, align) + ); + } + + } while (!r); + + spin_unlock(&sa_manager->wq.lock); + kfree(*sa_bo); + *sa_bo = NULL; + return r; +} + +void amdgpu_sa_bo_free(struct amdgpu_device *adev, struct amdgpu_sa_bo **sa_bo, + struct amdgpu_fence *fence) +{ + struct amdgpu_sa_manager *sa_manager; + + if (sa_bo == NULL || *sa_bo == NULL) { + return; + } + + sa_manager = (*sa_bo)->manager; + spin_lock(&sa_manager->wq.lock); + if (fence && !amdgpu_fence_signaled(fence)) { + (*sa_bo)->fence = amdgpu_fence_ref(fence); + list_add_tail(&(*sa_bo)->flist, + &sa_manager->flist[fence->ring->idx]); + } else { + amdgpu_sa_bo_remove_locked(*sa_bo); + } + wake_up_all_locked(&sa_manager->wq); + spin_unlock(&sa_manager->wq.lock); + *sa_bo = NULL; +} + +#if defined(CONFIG_DEBUG_FS) +void amdgpu_sa_bo_dump_debug_info(struct amdgpu_sa_manager *sa_manager, + struct seq_file *m) +{ + struct amdgpu_sa_bo *i; + + spin_lock(&sa_manager->wq.lock); + list_for_each_entry(i, &sa_manager->olist, olist) { + uint64_t soffset = i->soffset + sa_manager->gpu_addr; + uint64_t eoffset = i->eoffset + sa_manager->gpu_addr; + if (&i->olist == sa_manager->hole) { + seq_printf(m, ">"); + } else { + seq_printf(m, " "); + } + seq_printf(m, "[0x%010llx 0x%010llx] size %8lld", + soffset, eoffset, eoffset - soffset); + if (i->fence) { + seq_printf(m, " protected by 0x%016llx on ring %d", + i->fence->seq, i->fence->ring->idx); + } + seq_printf(m, "\n"); + } + spin_unlock(&sa_manager->wq.lock); +} +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_semaphore.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_semaphore.c new file mode 100644 index 000000000000..d6d41a42ab65 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_semaphore.c @@ -0,0 +1,102 @@ +/* + * Copyright 2011 Christian König. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Christian König <deathsimple@vodafone.de> + */ +#include <drm/drmP.h> +#include "amdgpu.h" +#include "amdgpu_trace.h" + +int amdgpu_semaphore_create(struct amdgpu_device *adev, + struct amdgpu_semaphore **semaphore) +{ + int r; + + *semaphore = kmalloc(sizeof(struct amdgpu_semaphore), GFP_KERNEL); + if (*semaphore == NULL) { + return -ENOMEM; + } + r = amdgpu_sa_bo_new(adev, &adev->ring_tmp_bo, + &(*semaphore)->sa_bo, 8, 8); + if (r) { + kfree(*semaphore); + *semaphore = NULL; + return r; + } + (*semaphore)->waiters = 0; + (*semaphore)->gpu_addr = amdgpu_sa_bo_gpu_addr((*semaphore)->sa_bo); + + *((uint64_t *)amdgpu_sa_bo_cpu_addr((*semaphore)->sa_bo)) = 0; + + return 0; +} + +bool amdgpu_semaphore_emit_signal(struct amdgpu_ring *ring, + struct amdgpu_semaphore *semaphore) +{ + trace_amdgpu_semaphore_signale(ring->idx, semaphore); + + if (amdgpu_ring_emit_semaphore(ring, semaphore, false)) { + --semaphore->waiters; + + /* for debugging lockup only, used by sysfs debug files */ + ring->last_semaphore_signal_addr = semaphore->gpu_addr; + return true; + } + return false; +} + +bool amdgpu_semaphore_emit_wait(struct amdgpu_ring *ring, + struct amdgpu_semaphore *semaphore) +{ + trace_amdgpu_semaphore_wait(ring->idx, semaphore); + + if (amdgpu_ring_emit_semaphore(ring, semaphore, true)) { + ++semaphore->waiters; + + /* for debugging lockup only, used by sysfs debug files */ + ring->last_semaphore_wait_addr = semaphore->gpu_addr; + return true; + } + return false; +} + +void amdgpu_semaphore_free(struct amdgpu_device *adev, + struct amdgpu_semaphore **semaphore, + struct amdgpu_fence *fence) +{ + if (semaphore == NULL || *semaphore == NULL) { + return; + } + if ((*semaphore)->waiters > 0) { + dev_err(adev->dev, "semaphore %p has more waiters than signalers," + " hardware lockup imminent!\n", *semaphore); + } + amdgpu_sa_bo_free(adev, &(*semaphore)->sa_bo, fence); + kfree(*semaphore); + *semaphore = NULL; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_sync.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_sync.c new file mode 100644 index 000000000000..855d56ac7115 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_sync.c @@ -0,0 +1,231 @@ +/* + * Copyright 2014 Advanced Micro Devices, Inc. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Christian König <christian.koenig@amd.com> + */ + +#include <drm/drmP.h> +#include "amdgpu.h" +#include "amdgpu_trace.h" + +/** + * amdgpu_sync_create - zero init sync object + * + * @sync: sync object to initialize + * + * Just clear the sync object for now. + */ +void amdgpu_sync_create(struct amdgpu_sync *sync) +{ + unsigned i; + + for (i = 0; i < AMDGPU_NUM_SYNCS; ++i) + sync->semaphores[i] = NULL; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) + sync->sync_to[i] = NULL; + + sync->last_vm_update = NULL; +} + +/** + * amdgpu_sync_fence - use the semaphore to sync to a fence + * + * @sync: sync object to add fence to + * @fence: fence to sync to + * + * Sync to the fence using the semaphore objects + */ +void amdgpu_sync_fence(struct amdgpu_sync *sync, + struct amdgpu_fence *fence) +{ + struct amdgpu_fence *other; + + if (!fence) + return; + + other = sync->sync_to[fence->ring->idx]; + sync->sync_to[fence->ring->idx] = amdgpu_fence_ref( + amdgpu_fence_later(fence, other)); + amdgpu_fence_unref(&other); + + if (fence->owner == AMDGPU_FENCE_OWNER_VM) { + other = sync->last_vm_update; + sync->last_vm_update = amdgpu_fence_ref( + amdgpu_fence_later(fence, other)); + amdgpu_fence_unref(&other); + } +} + +/** + * amdgpu_sync_resv - use the semaphores to sync to a reservation object + * + * @sync: sync object to add fences from reservation object to + * @resv: reservation object with embedded fence + * @shared: true if we should only sync to the exclusive fence + * + * Sync to the fence using the semaphore objects + */ +int amdgpu_sync_resv(struct amdgpu_device *adev, + struct amdgpu_sync *sync, + struct reservation_object *resv, + void *owner) +{ + struct reservation_object_list *flist; + struct fence *f; + struct amdgpu_fence *fence; + unsigned i; + int r = 0; + + /* always sync to the exclusive fence */ + f = reservation_object_get_excl(resv); + fence = f ? to_amdgpu_fence(f) : NULL; + if (fence && fence->ring->adev == adev) + amdgpu_sync_fence(sync, fence); + else if (f) + r = fence_wait(f, true); + + flist = reservation_object_get_list(resv); + if (!flist || r) + return r; + + for (i = 0; i < flist->shared_count; ++i) { + f = rcu_dereference_protected(flist->shared[i], + reservation_object_held(resv)); + fence = to_amdgpu_fence(f); + if (fence && fence->ring->adev == adev) { + if (fence->owner != owner || + fence->owner == AMDGPU_FENCE_OWNER_UNDEFINED) + amdgpu_sync_fence(sync, fence); + } else { + r = fence_wait(f, true); + if (r) + break; + } + } + return r; +} + +/** + * amdgpu_sync_rings - sync ring to all registered fences + * + * @sync: sync object to use + * @ring: ring that needs sync + * + * Ensure that all registered fences are signaled before letting + * the ring continue. The caller must hold the ring lock. + */ +int amdgpu_sync_rings(struct amdgpu_sync *sync, + struct amdgpu_ring *ring) +{ + struct amdgpu_device *adev = ring->adev; + unsigned count = 0; + int i, r; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_fence *fence = sync->sync_to[i]; + struct amdgpu_semaphore *semaphore; + struct amdgpu_ring *other = adev->rings[i]; + + /* check if we really need to sync */ + if (!amdgpu_fence_need_sync(fence, ring)) + continue; + + /* prevent GPU deadlocks */ + if (!other->ready) { + dev_err(adev->dev, "Syncing to a disabled ring!"); + return -EINVAL; + } + + if (count >= AMDGPU_NUM_SYNCS) { + /* not enough room, wait manually */ + r = amdgpu_fence_wait(fence, false); + if (r) + return r; + continue; + } + r = amdgpu_semaphore_create(adev, &semaphore); + if (r) + return r; + + sync->semaphores[count++] = semaphore; + + /* allocate enough space for sync command */ + r = amdgpu_ring_alloc(other, 16); + if (r) + return r; + + /* emit the signal semaphore */ + if (!amdgpu_semaphore_emit_signal(other, semaphore)) { + /* signaling wasn't successful wait manually */ + amdgpu_ring_undo(other); + r = amdgpu_fence_wait(fence, false); + if (r) + return r; + continue; + } + + /* we assume caller has already allocated space on waiters ring */ + if (!amdgpu_semaphore_emit_wait(ring, semaphore)) { + /* waiting wasn't successful wait manually */ + amdgpu_ring_undo(other); + r = amdgpu_fence_wait(fence, false); + if (r) + return r; + continue; + } + + amdgpu_ring_commit(other); + amdgpu_fence_note_sync(fence, ring); + } + + return 0; +} + +/** + * amdgpu_sync_free - free the sync object + * + * @adev: amdgpu_device pointer + * @sync: sync object to use + * @fence: fence to use for the free + * + * Free the sync object by freeing all semaphores in it. + */ +void amdgpu_sync_free(struct amdgpu_device *adev, + struct amdgpu_sync *sync, + struct amdgpu_fence *fence) +{ + unsigned i; + + for (i = 0; i < AMDGPU_NUM_SYNCS; ++i) + amdgpu_semaphore_free(adev, &sync->semaphores[i], fence); + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) + amdgpu_fence_unref(&sync->sync_to[i]); + + amdgpu_fence_unref(&sync->last_vm_update); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_test.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_test.c new file mode 100644 index 000000000000..df202999fbfe --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_test.c @@ -0,0 +1,552 @@ +/* + * Copyright 2009 VMware, 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: Michel Dänzer + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_uvd.h" +#include "amdgpu_vce.h" + +/* Test BO GTT->VRAM and VRAM->GTT GPU copies across the whole GTT aperture */ +static void amdgpu_do_test_moves(struct amdgpu_device *adev) +{ + struct amdgpu_ring *ring = adev->mman.buffer_funcs_ring; + struct amdgpu_bo *vram_obj = NULL; + struct amdgpu_bo **gtt_obj = NULL; + uint64_t gtt_addr, vram_addr; + unsigned n, size; + int i, r; + + size = 1024 * 1024; + + /* Number of tests = + * (Total GTT - IB pool - writeback page - ring buffers) / test size + */ + n = adev->mc.gtt_size - AMDGPU_IB_POOL_SIZE*64*1024; + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) + if (adev->rings[i]) + n -= adev->rings[i]->ring_size; + if (adev->wb.wb_obj) + n -= AMDGPU_GPU_PAGE_SIZE; + if (adev->irq.ih.ring_obj) + n -= adev->irq.ih.ring_size; + n /= size; + + gtt_obj = kzalloc(n * sizeof(*gtt_obj), GFP_KERNEL); + if (!gtt_obj) { + DRM_ERROR("Failed to allocate %d pointers\n", n); + r = 1; + goto out_cleanup; + } + + r = amdgpu_bo_create(adev, size, PAGE_SIZE, true, AMDGPU_GEM_DOMAIN_VRAM, 0, + NULL, &vram_obj); + if (r) { + DRM_ERROR("Failed to create VRAM object\n"); + goto out_cleanup; + } + r = amdgpu_bo_reserve(vram_obj, false); + if (unlikely(r != 0)) + goto out_unref; + r = amdgpu_bo_pin(vram_obj, AMDGPU_GEM_DOMAIN_VRAM, &vram_addr); + if (r) { + DRM_ERROR("Failed to pin VRAM object\n"); + goto out_unres; + } + for (i = 0; i < n; i++) { + void *gtt_map, *vram_map; + void **gtt_start, **gtt_end; + void **vram_start, **vram_end; + struct amdgpu_fence *fence = NULL; + + r = amdgpu_bo_create(adev, size, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_GTT, 0, NULL, gtt_obj + i); + if (r) { + DRM_ERROR("Failed to create GTT object %d\n", i); + goto out_lclean; + } + + r = amdgpu_bo_reserve(gtt_obj[i], false); + if (unlikely(r != 0)) + goto out_lclean_unref; + r = amdgpu_bo_pin(gtt_obj[i], AMDGPU_GEM_DOMAIN_GTT, >t_addr); + if (r) { + DRM_ERROR("Failed to pin GTT object %d\n", i); + goto out_lclean_unres; + } + + r = amdgpu_bo_kmap(gtt_obj[i], >t_map); + if (r) { + DRM_ERROR("Failed to map GTT object %d\n", i); + goto out_lclean_unpin; + } + + for (gtt_start = gtt_map, gtt_end = gtt_map + size; + gtt_start < gtt_end; + gtt_start++) + *gtt_start = gtt_start; + + amdgpu_bo_kunmap(gtt_obj[i]); + + r = amdgpu_copy_buffer(ring, gtt_addr, vram_addr, + size, NULL, &fence); + + if (r) { + DRM_ERROR("Failed GTT->VRAM copy %d\n", i); + goto out_lclean_unpin; + } + + r = amdgpu_fence_wait(fence, false); + if (r) { + DRM_ERROR("Failed to wait for GTT->VRAM fence %d\n", i); + goto out_lclean_unpin; + } + + amdgpu_fence_unref(&fence); + + r = amdgpu_bo_kmap(vram_obj, &vram_map); + if (r) { + DRM_ERROR("Failed to map VRAM object after copy %d\n", i); + goto out_lclean_unpin; + } + + for (gtt_start = gtt_map, gtt_end = gtt_map + size, + vram_start = vram_map, vram_end = vram_map + size; + vram_start < vram_end; + gtt_start++, vram_start++) { + if (*vram_start != gtt_start) { + DRM_ERROR("Incorrect GTT->VRAM copy %d: Got 0x%p, " + "expected 0x%p (GTT/VRAM offset " + "0x%16llx/0x%16llx)\n", + i, *vram_start, gtt_start, + (unsigned long long) + (gtt_addr - adev->mc.gtt_start + + (void*)gtt_start - gtt_map), + (unsigned long long) + (vram_addr - adev->mc.vram_start + + (void*)gtt_start - gtt_map)); + amdgpu_bo_kunmap(vram_obj); + goto out_lclean_unpin; + } + *vram_start = vram_start; + } + + amdgpu_bo_kunmap(vram_obj); + + r = amdgpu_copy_buffer(ring, vram_addr, gtt_addr, + size, NULL, &fence); + + if (r) { + DRM_ERROR("Failed VRAM->GTT copy %d\n", i); + goto out_lclean_unpin; + } + + r = amdgpu_fence_wait(fence, false); + if (r) { + DRM_ERROR("Failed to wait for VRAM->GTT fence %d\n", i); + goto out_lclean_unpin; + } + + amdgpu_fence_unref(&fence); + + r = amdgpu_bo_kmap(gtt_obj[i], >t_map); + if (r) { + DRM_ERROR("Failed to map GTT object after copy %d\n", i); + goto out_lclean_unpin; + } + + for (gtt_start = gtt_map, gtt_end = gtt_map + size, + vram_start = vram_map, vram_end = vram_map + size; + gtt_start < gtt_end; + gtt_start++, vram_start++) { + if (*gtt_start != vram_start) { + DRM_ERROR("Incorrect VRAM->GTT copy %d: Got 0x%p, " + "expected 0x%p (VRAM/GTT offset " + "0x%16llx/0x%16llx)\n", + i, *gtt_start, vram_start, + (unsigned long long) + (vram_addr - adev->mc.vram_start + + (void*)vram_start - vram_map), + (unsigned long long) + (gtt_addr - adev->mc.gtt_start + + (void*)vram_start - vram_map)); + amdgpu_bo_kunmap(gtt_obj[i]); + goto out_lclean_unpin; + } + } + + amdgpu_bo_kunmap(gtt_obj[i]); + + DRM_INFO("Tested GTT->VRAM and VRAM->GTT copy for GTT offset 0x%llx\n", + gtt_addr - adev->mc.gtt_start); + continue; + +out_lclean_unpin: + amdgpu_bo_unpin(gtt_obj[i]); +out_lclean_unres: + amdgpu_bo_unreserve(gtt_obj[i]); +out_lclean_unref: + amdgpu_bo_unref(>t_obj[i]); +out_lclean: + for (--i; i >= 0; --i) { + amdgpu_bo_unpin(gtt_obj[i]); + amdgpu_bo_unreserve(gtt_obj[i]); + amdgpu_bo_unref(>t_obj[i]); + } + if (fence) + amdgpu_fence_unref(&fence); + break; + } + + amdgpu_bo_unpin(vram_obj); +out_unres: + amdgpu_bo_unreserve(vram_obj); +out_unref: + amdgpu_bo_unref(&vram_obj); +out_cleanup: + kfree(gtt_obj); + if (r) { + printk(KERN_WARNING "Error while testing BO move.\n"); + } +} + +void amdgpu_test_moves(struct amdgpu_device *adev) +{ + if (adev->mman.buffer_funcs) + amdgpu_do_test_moves(adev); +} + +static int amdgpu_test_create_and_emit_fence(struct amdgpu_device *adev, + struct amdgpu_ring *ring, + struct amdgpu_fence **fence) +{ + uint32_t handle = ring->idx ^ 0xdeafbeef; + int r; + + if (ring == &adev->uvd.ring) { + r = amdgpu_uvd_get_create_msg(ring, handle, NULL); + if (r) { + DRM_ERROR("Failed to get dummy create msg\n"); + return r; + } + + r = amdgpu_uvd_get_destroy_msg(ring, handle, fence); + if (r) { + DRM_ERROR("Failed to get dummy destroy msg\n"); + return r; + } + + } else if (ring == &adev->vce.ring[0] || + ring == &adev->vce.ring[1]) { + r = amdgpu_vce_get_create_msg(ring, handle, NULL); + if (r) { + DRM_ERROR("Failed to get dummy create msg\n"); + return r; + } + + r = amdgpu_vce_get_destroy_msg(ring, handle, fence); + if (r) { + DRM_ERROR("Failed to get dummy destroy msg\n"); + return r; + } + + } else { + r = amdgpu_ring_lock(ring, 64); + if (r) { + DRM_ERROR("Failed to lock ring A %d\n", ring->idx); + return r; + } + amdgpu_fence_emit(ring, AMDGPU_FENCE_OWNER_UNDEFINED, fence); + amdgpu_ring_unlock_commit(ring); + } + return 0; +} + +void amdgpu_test_ring_sync(struct amdgpu_device *adev, + struct amdgpu_ring *ringA, + struct amdgpu_ring *ringB) +{ + struct amdgpu_fence *fence1 = NULL, *fence2 = NULL; + struct amdgpu_semaphore *semaphore = NULL; + int r; + + r = amdgpu_semaphore_create(adev, &semaphore); + if (r) { + DRM_ERROR("Failed to create semaphore\n"); + goto out_cleanup; + } + + r = amdgpu_ring_lock(ringA, 64); + if (r) { + DRM_ERROR("Failed to lock ring A %d\n", ringA->idx); + goto out_cleanup; + } + amdgpu_semaphore_emit_wait(ringA, semaphore); + amdgpu_ring_unlock_commit(ringA); + + r = amdgpu_test_create_and_emit_fence(adev, ringA, &fence1); + if (r) + goto out_cleanup; + + r = amdgpu_ring_lock(ringA, 64); + if (r) { + DRM_ERROR("Failed to lock ring A %d\n", ringA->idx); + goto out_cleanup; + } + amdgpu_semaphore_emit_wait(ringA, semaphore); + amdgpu_ring_unlock_commit(ringA); + + r = amdgpu_test_create_and_emit_fence(adev, ringA, &fence2); + if (r) + goto out_cleanup; + + mdelay(1000); + + if (amdgpu_fence_signaled(fence1)) { + DRM_ERROR("Fence 1 signaled without waiting for semaphore.\n"); + goto out_cleanup; + } + + r = amdgpu_ring_lock(ringB, 64); + if (r) { + DRM_ERROR("Failed to lock ring B %p\n", ringB); + goto out_cleanup; + } + amdgpu_semaphore_emit_signal(ringB, semaphore); + amdgpu_ring_unlock_commit(ringB); + + r = amdgpu_fence_wait(fence1, false); + if (r) { + DRM_ERROR("Failed to wait for sync fence 1\n"); + goto out_cleanup; + } + + mdelay(1000); + + if (amdgpu_fence_signaled(fence2)) { + DRM_ERROR("Fence 2 signaled without waiting for semaphore.\n"); + goto out_cleanup; + } + + r = amdgpu_ring_lock(ringB, 64); + if (r) { + DRM_ERROR("Failed to lock ring B %p\n", ringB); + goto out_cleanup; + } + amdgpu_semaphore_emit_signal(ringB, semaphore); + amdgpu_ring_unlock_commit(ringB); + + r = amdgpu_fence_wait(fence2, false); + if (r) { + DRM_ERROR("Failed to wait for sync fence 1\n"); + goto out_cleanup; + } + +out_cleanup: + amdgpu_semaphore_free(adev, &semaphore, NULL); + + if (fence1) + amdgpu_fence_unref(&fence1); + + if (fence2) + amdgpu_fence_unref(&fence2); + + if (r) + printk(KERN_WARNING "Error while testing ring sync (%d).\n", r); +} + +static void amdgpu_test_ring_sync2(struct amdgpu_device *adev, + struct amdgpu_ring *ringA, + struct amdgpu_ring *ringB, + struct amdgpu_ring *ringC) +{ + struct amdgpu_fence *fenceA = NULL, *fenceB = NULL; + struct amdgpu_semaphore *semaphore = NULL; + bool sigA, sigB; + int i, r; + + r = amdgpu_semaphore_create(adev, &semaphore); + if (r) { + DRM_ERROR("Failed to create semaphore\n"); + goto out_cleanup; + } + + r = amdgpu_ring_lock(ringA, 64); + if (r) { + DRM_ERROR("Failed to lock ring A %d\n", ringA->idx); + goto out_cleanup; + } + amdgpu_semaphore_emit_wait(ringA, semaphore); + amdgpu_ring_unlock_commit(ringA); + + r = amdgpu_test_create_and_emit_fence(adev, ringA, &fenceA); + if (r) + goto out_cleanup; + + r = amdgpu_ring_lock(ringB, 64); + if (r) { + DRM_ERROR("Failed to lock ring B %d\n", ringB->idx); + goto out_cleanup; + } + amdgpu_semaphore_emit_wait(ringB, semaphore); + amdgpu_ring_unlock_commit(ringB); + r = amdgpu_test_create_and_emit_fence(adev, ringB, &fenceB); + if (r) + goto out_cleanup; + + mdelay(1000); + + if (amdgpu_fence_signaled(fenceA)) { + DRM_ERROR("Fence A signaled without waiting for semaphore.\n"); + goto out_cleanup; + } + if (amdgpu_fence_signaled(fenceB)) { + DRM_ERROR("Fence B signaled without waiting for semaphore.\n"); + goto out_cleanup; + } + + r = amdgpu_ring_lock(ringC, 64); + if (r) { + DRM_ERROR("Failed to lock ring B %p\n", ringC); + goto out_cleanup; + } + amdgpu_semaphore_emit_signal(ringC, semaphore); + amdgpu_ring_unlock_commit(ringC); + + for (i = 0; i < 30; ++i) { + mdelay(100); + sigA = amdgpu_fence_signaled(fenceA); + sigB = amdgpu_fence_signaled(fenceB); + if (sigA || sigB) + break; + } + + if (!sigA && !sigB) { + DRM_ERROR("Neither fence A nor B has been signaled\n"); + goto out_cleanup; + } else if (sigA && sigB) { + DRM_ERROR("Both fence A and B has been signaled\n"); + goto out_cleanup; + } + + DRM_INFO("Fence %c was first signaled\n", sigA ? 'A' : 'B'); + + r = amdgpu_ring_lock(ringC, 64); + if (r) { + DRM_ERROR("Failed to lock ring B %p\n", ringC); + goto out_cleanup; + } + amdgpu_semaphore_emit_signal(ringC, semaphore); + amdgpu_ring_unlock_commit(ringC); + + mdelay(1000); + + r = amdgpu_fence_wait(fenceA, false); + if (r) { + DRM_ERROR("Failed to wait for sync fence A\n"); + goto out_cleanup; + } + r = amdgpu_fence_wait(fenceB, false); + if (r) { + DRM_ERROR("Failed to wait for sync fence B\n"); + goto out_cleanup; + } + +out_cleanup: + amdgpu_semaphore_free(adev, &semaphore, NULL); + + if (fenceA) + amdgpu_fence_unref(&fenceA); + + if (fenceB) + amdgpu_fence_unref(&fenceB); + + if (r) + printk(KERN_WARNING "Error while testing ring sync (%d).\n", r); +} + +static bool amdgpu_test_sync_possible(struct amdgpu_ring *ringA, + struct amdgpu_ring *ringB) +{ + if (ringA == &ringA->adev->vce.ring[0] && + ringB == &ringB->adev->vce.ring[1]) + return false; + + return true; +} + +void amdgpu_test_syncing(struct amdgpu_device *adev) +{ + int i, j, k; + + for (i = 1; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ringA = adev->rings[i]; + if (!ringA || !ringA->ready) + continue; + + for (j = 0; j < i; ++j) { + struct amdgpu_ring *ringB = adev->rings[j]; + if (!ringB || !ringB->ready) + continue; + + if (!amdgpu_test_sync_possible(ringA, ringB)) + continue; + + DRM_INFO("Testing syncing between rings %d and %d...\n", i, j); + amdgpu_test_ring_sync(adev, ringA, ringB); + + DRM_INFO("Testing syncing between rings %d and %d...\n", j, i); + amdgpu_test_ring_sync(adev, ringB, ringA); + + for (k = 0; k < j; ++k) { + struct amdgpu_ring *ringC = adev->rings[k]; + if (!ringC || !ringC->ready) + continue; + + if (!amdgpu_test_sync_possible(ringA, ringC)) + continue; + + if (!amdgpu_test_sync_possible(ringB, ringC)) + continue; + + DRM_INFO("Testing syncing between rings %d, %d and %d...\n", i, j, k); + amdgpu_test_ring_sync2(adev, ringA, ringB, ringC); + + DRM_INFO("Testing syncing between rings %d, %d and %d...\n", i, k, j); + amdgpu_test_ring_sync2(adev, ringA, ringC, ringB); + + DRM_INFO("Testing syncing between rings %d, %d and %d...\n", j, i, k); + amdgpu_test_ring_sync2(adev, ringB, ringA, ringC); + + DRM_INFO("Testing syncing between rings %d, %d and %d...\n", j, k, i); + amdgpu_test_ring_sync2(adev, ringB, ringC, ringA); + + DRM_INFO("Testing syncing between rings %d, %d and %d...\n", k, i, j); + amdgpu_test_ring_sync2(adev, ringC, ringA, ringB); + + DRM_INFO("Testing syncing between rings %d, %d and %d...\n", k, j, i); + amdgpu_test_ring_sync2(adev, ringC, ringB, ringA); + } + } + } +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h new file mode 100644 index 000000000000..b57647e582d4 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h @@ -0,0 +1,209 @@ +#if !defined(_AMDGPU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _AMDGPU_TRACE_H_ + +#include <linux/stringify.h> +#include <linux/types.h> +#include <linux/tracepoint.h> + +#include <drm/drmP.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM amdgpu +#define TRACE_SYSTEM_STRING __stringify(TRACE_SYSTEM) +#define TRACE_INCLUDE_FILE amdgpu_trace + +TRACE_EVENT(amdgpu_bo_create, + TP_PROTO(struct amdgpu_bo *bo), + TP_ARGS(bo), + TP_STRUCT__entry( + __field(struct amdgpu_bo *, bo) + __field(u32, pages) + ), + + TP_fast_assign( + __entry->bo = bo; + __entry->pages = bo->tbo.num_pages; + ), + TP_printk("bo=%p, pages=%u", __entry->bo, __entry->pages) +); + +TRACE_EVENT(amdgpu_cs, + TP_PROTO(struct amdgpu_cs_parser *p, int i), + TP_ARGS(p, i), + TP_STRUCT__entry( + __field(u32, ring) + __field(u32, dw) + __field(u32, fences) + ), + + TP_fast_assign( + __entry->ring = p->ibs[i].ring->idx; + __entry->dw = p->ibs[i].length_dw; + __entry->fences = amdgpu_fence_count_emitted( + p->ibs[i].ring); + ), + TP_printk("ring=%u, dw=%u, fences=%u", + __entry->ring, __entry->dw, + __entry->fences) +); + +TRACE_EVENT(amdgpu_vm_grab_id, + TP_PROTO(unsigned vmid, int ring), + TP_ARGS(vmid, ring), + TP_STRUCT__entry( + __field(u32, vmid) + __field(u32, ring) + ), + + TP_fast_assign( + __entry->vmid = vmid; + __entry->ring = ring; + ), + TP_printk("vmid=%u, ring=%u", __entry->vmid, __entry->ring) +); + +TRACE_EVENT(amdgpu_vm_bo_update, + TP_PROTO(struct amdgpu_bo_va_mapping *mapping), + TP_ARGS(mapping), + TP_STRUCT__entry( + __field(u64, soffset) + __field(u64, eoffset) + __field(u32, flags) + ), + + TP_fast_assign( + __entry->soffset = mapping->it.start; + __entry->eoffset = mapping->it.last + 1; + __entry->flags = mapping->flags; + ), + TP_printk("soffs=%010llx, eoffs=%010llx, flags=%08x", + __entry->soffset, __entry->eoffset, __entry->flags) +); + +TRACE_EVENT(amdgpu_vm_set_page, + TP_PROTO(uint64_t pe, uint64_t addr, unsigned count, + uint32_t incr, uint32_t flags), + TP_ARGS(pe, addr, count, incr, flags), + TP_STRUCT__entry( + __field(u64, pe) + __field(u64, addr) + __field(u32, count) + __field(u32, incr) + __field(u32, flags) + ), + + TP_fast_assign( + __entry->pe = pe; + __entry->addr = addr; + __entry->count = count; + __entry->incr = incr; + __entry->flags = flags; + ), + TP_printk("pe=%010Lx, addr=%010Lx, incr=%u, flags=%08x, count=%u", + __entry->pe, __entry->addr, __entry->incr, + __entry->flags, __entry->count) +); + +TRACE_EVENT(amdgpu_vm_flush, + TP_PROTO(uint64_t pd_addr, unsigned ring, unsigned id), + TP_ARGS(pd_addr, ring, id), + TP_STRUCT__entry( + __field(u64, pd_addr) + __field(u32, ring) + __field(u32, id) + ), + + TP_fast_assign( + __entry->pd_addr = pd_addr; + __entry->ring = ring; + __entry->id = id; + ), + TP_printk("pd_addr=%010Lx, ring=%u, id=%u", + __entry->pd_addr, __entry->ring, __entry->id) +); + +DECLARE_EVENT_CLASS(amdgpu_fence_request, + + TP_PROTO(struct drm_device *dev, int ring, u32 seqno), + + TP_ARGS(dev, ring, seqno), + + TP_STRUCT__entry( + __field(u32, dev) + __field(int, ring) + __field(u32, seqno) + ), + + TP_fast_assign( + __entry->dev = dev->primary->index; + __entry->ring = ring; + __entry->seqno = seqno; + ), + + TP_printk("dev=%u, ring=%d, seqno=%u", + __entry->dev, __entry->ring, __entry->seqno) +); + +DEFINE_EVENT(amdgpu_fence_request, amdgpu_fence_emit, + + TP_PROTO(struct drm_device *dev, int ring, u32 seqno), + + TP_ARGS(dev, ring, seqno) +); + +DEFINE_EVENT(amdgpu_fence_request, amdgpu_fence_wait_begin, + + TP_PROTO(struct drm_device *dev, int ring, u32 seqno), + + TP_ARGS(dev, ring, seqno) +); + +DEFINE_EVENT(amdgpu_fence_request, amdgpu_fence_wait_end, + + TP_PROTO(struct drm_device *dev, int ring, u32 seqno), + + TP_ARGS(dev, ring, seqno) +); + +DECLARE_EVENT_CLASS(amdgpu_semaphore_request, + + TP_PROTO(int ring, struct amdgpu_semaphore *sem), + + TP_ARGS(ring, sem), + + TP_STRUCT__entry( + __field(int, ring) + __field(signed, waiters) + __field(uint64_t, gpu_addr) + ), + + TP_fast_assign( + __entry->ring = ring; + __entry->waiters = sem->waiters; + __entry->gpu_addr = sem->gpu_addr; + ), + + TP_printk("ring=%u, waiters=%d, addr=%010Lx", __entry->ring, + __entry->waiters, __entry->gpu_addr) +); + +DEFINE_EVENT(amdgpu_semaphore_request, amdgpu_semaphore_signale, + + TP_PROTO(int ring, struct amdgpu_semaphore *sem), + + TP_ARGS(ring, sem) +); + +DEFINE_EVENT(amdgpu_semaphore_request, amdgpu_semaphore_wait, + + TP_PROTO(int ring, struct amdgpu_semaphore *sem), + + TP_ARGS(ring, sem) +); + +#endif + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include <trace/define_trace.h> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_trace_points.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace_points.c new file mode 100644 index 000000000000..385b7e1d72f9 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_trace_points.c @@ -0,0 +1,9 @@ +/* Copyright Red Hat Inc 2010. + * Author : Dave Airlie <airlied@redhat.com> + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +#define CREATE_TRACE_POINTS +#include "amdgpu_trace.h" diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c new file mode 100644 index 000000000000..120e6e7c4647 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -0,0 +1,1249 @@ +/* + * Copyright 2009 Jerome Glisse. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Jerome Glisse <glisse@freedesktop.org> + * Thomas Hellstrom <thomas-at-tungstengraphics-dot-com> + * Dave Airlie + */ +#include <ttm/ttm_bo_api.h> +#include <ttm/ttm_bo_driver.h> +#include <ttm/ttm_placement.h> +#include <ttm/ttm_module.h> +#include <ttm/ttm_page_alloc.h> +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/swiotlb.h> +#include <linux/swap.h> +#include <linux/pagemap.h> +#include <linux/debugfs.h> +#include "amdgpu.h" +#include "bif/bif_4_1_d.h" + +#define DRM_FILE_PAGE_OFFSET (0x100000000ULL >> PAGE_SHIFT) + +static int amdgpu_ttm_debugfs_init(struct amdgpu_device *adev); +static void amdgpu_ttm_debugfs_fini(struct amdgpu_device *adev); + +static struct amdgpu_device *amdgpu_get_adev(struct ttm_bo_device *bdev) +{ + struct amdgpu_mman *mman; + struct amdgpu_device *adev; + + mman = container_of(bdev, struct amdgpu_mman, bdev); + adev = container_of(mman, struct amdgpu_device, mman); + return adev; +} + + +/* + * Global memory. + */ +static int amdgpu_ttm_mem_global_init(struct drm_global_reference *ref) +{ + return ttm_mem_global_init(ref->object); +} + +static void amdgpu_ttm_mem_global_release(struct drm_global_reference *ref) +{ + ttm_mem_global_release(ref->object); +} + +static int amdgpu_ttm_global_init(struct amdgpu_device *adev) +{ + struct drm_global_reference *global_ref; + int r; + + adev->mman.mem_global_referenced = false; + global_ref = &adev->mman.mem_global_ref; + global_ref->global_type = DRM_GLOBAL_TTM_MEM; + global_ref->size = sizeof(struct ttm_mem_global); + global_ref->init = &amdgpu_ttm_mem_global_init; + global_ref->release = &amdgpu_ttm_mem_global_release; + r = drm_global_item_ref(global_ref); + if (r != 0) { + DRM_ERROR("Failed setting up TTM memory accounting " + "subsystem.\n"); + return r; + } + + adev->mman.bo_global_ref.mem_glob = + adev->mman.mem_global_ref.object; + global_ref = &adev->mman.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; + r = drm_global_item_ref(global_ref); + if (r != 0) { + DRM_ERROR("Failed setting up TTM BO subsystem.\n"); + drm_global_item_unref(&adev->mman.mem_global_ref); + return r; + } + + adev->mman.mem_global_referenced = true; + return 0; +} + +static void amdgpu_ttm_global_fini(struct amdgpu_device *adev) +{ + if (adev->mman.mem_global_referenced) { + drm_global_item_unref(&adev->mman.bo_global_ref.ref); + drm_global_item_unref(&adev->mman.mem_global_ref); + adev->mman.mem_global_referenced = false; + } +} + +static int amdgpu_invalidate_caches(struct ttm_bo_device *bdev, uint32_t flags) +{ + return 0; +} + +static int amdgpu_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, + struct ttm_mem_type_manager *man) +{ + struct amdgpu_device *adev; + + adev = amdgpu_get_adev(bdev); + + switch (type) { + case TTM_PL_SYSTEM: + /* System memory */ + man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; + man->available_caching = TTM_PL_MASK_CACHING; + man->default_caching = TTM_PL_FLAG_CACHED; + break; + case TTM_PL_TT: + man->func = &ttm_bo_manager_func; + man->gpu_offset = adev->mc.gtt_start; + man->available_caching = TTM_PL_MASK_CACHING; + man->default_caching = TTM_PL_FLAG_CACHED; + man->flags = TTM_MEMTYPE_FLAG_MAPPABLE | TTM_MEMTYPE_FLAG_CMA; + break; + case TTM_PL_VRAM: + /* "On-card" video ram */ + man->func = &ttm_bo_manager_func; + man->gpu_offset = adev->mc.vram_start; + 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 AMDGPU_PL_GDS: + case AMDGPU_PL_GWS: + case AMDGPU_PL_OA: + /* On-chip GDS memory*/ + man->func = &ttm_bo_manager_func; + man->gpu_offset = 0; + man->flags = TTM_MEMTYPE_FLAG_FIXED | TTM_MEMTYPE_FLAG_CMA; + man->available_caching = TTM_PL_FLAG_UNCACHED; + man->default_caching = TTM_PL_FLAG_UNCACHED; + break; + default: + DRM_ERROR("Unsupported memory type %u\n", (unsigned)type); + return -EINVAL; + } + return 0; +} + +static void amdgpu_evict_flags(struct ttm_buffer_object *bo, + struct ttm_placement *placement) +{ + struct amdgpu_bo *rbo; + static struct ttm_place placements = { + .fpfn = 0, + .lpfn = 0, + .flags = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM + }; + + if (!amdgpu_ttm_bo_is_amdgpu_bo(bo)) { + placement->placement = &placements; + placement->busy_placement = &placements; + placement->num_placement = 1; + placement->num_busy_placement = 1; + return; + } + rbo = container_of(bo, struct amdgpu_bo, tbo); + switch (bo->mem.mem_type) { + case TTM_PL_VRAM: + if (rbo->adev->mman.buffer_funcs_ring->ready == false) + amdgpu_ttm_placement_from_domain(rbo, AMDGPU_GEM_DOMAIN_CPU); + else + amdgpu_ttm_placement_from_domain(rbo, AMDGPU_GEM_DOMAIN_GTT); + break; + case TTM_PL_TT: + default: + amdgpu_ttm_placement_from_domain(rbo, AMDGPU_GEM_DOMAIN_CPU); + } + *placement = rbo->placement; +} + +static int amdgpu_verify_access(struct ttm_buffer_object *bo, struct file *filp) +{ + struct amdgpu_bo *rbo = container_of(bo, struct amdgpu_bo, tbo); + + return drm_vma_node_verify_access(&rbo->gem_base.vma_node, filp); +} + +static void amdgpu_move_null(struct ttm_buffer_object *bo, + struct ttm_mem_reg *new_mem) +{ + struct ttm_mem_reg *old_mem = &bo->mem; + + BUG_ON(old_mem->mm_node != NULL); + *old_mem = *new_mem; + new_mem->mm_node = NULL; +} + +static int amdgpu_move_blit(struct ttm_buffer_object *bo, + bool evict, bool no_wait_gpu, + struct ttm_mem_reg *new_mem, + struct ttm_mem_reg *old_mem) +{ + struct amdgpu_device *adev; + struct amdgpu_ring *ring; + uint64_t old_start, new_start; + struct amdgpu_fence *fence; + int r; + + adev = amdgpu_get_adev(bo->bdev); + ring = adev->mman.buffer_funcs_ring; + old_start = old_mem->start << PAGE_SHIFT; + new_start = new_mem->start << PAGE_SHIFT; + + switch (old_mem->mem_type) { + case TTM_PL_VRAM: + old_start += adev->mc.vram_start; + break; + case TTM_PL_TT: + old_start += adev->mc.gtt_start; + break; + default: + DRM_ERROR("Unknown placement %d\n", old_mem->mem_type); + return -EINVAL; + } + switch (new_mem->mem_type) { + case TTM_PL_VRAM: + new_start += adev->mc.vram_start; + break; + case TTM_PL_TT: + new_start += adev->mc.gtt_start; + break; + default: + DRM_ERROR("Unknown placement %d\n", old_mem->mem_type); + return -EINVAL; + } + if (!ring->ready) { + DRM_ERROR("Trying to move memory with ring turned off.\n"); + return -EINVAL; + } + + BUILD_BUG_ON((PAGE_SIZE % AMDGPU_GPU_PAGE_SIZE) != 0); + + r = amdgpu_copy_buffer(ring, old_start, new_start, + new_mem->num_pages * PAGE_SIZE, /* bytes */ + bo->resv, &fence); + /* FIXME: handle copy error */ + r = ttm_bo_move_accel_cleanup(bo, &fence->base, + evict, no_wait_gpu, new_mem); + amdgpu_fence_unref(&fence); + return r; +} + +static int amdgpu_move_vram_ram(struct ttm_buffer_object *bo, + bool evict, bool interruptible, + bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + struct amdgpu_device *adev; + struct ttm_mem_reg *old_mem = &bo->mem; + struct ttm_mem_reg tmp_mem; + struct ttm_place placements; + struct ttm_placement placement; + int r; + + adev = amdgpu_get_adev(bo->bdev); + tmp_mem = *new_mem; + tmp_mem.mm_node = NULL; + placement.num_placement = 1; + placement.placement = &placements; + placement.num_busy_placement = 1; + placement.busy_placement = &placements; + placements.fpfn = 0; + placements.lpfn = 0; + placements.flags = TTM_PL_MASK_CACHING | TTM_PL_FLAG_TT; + r = ttm_bo_mem_space(bo, &placement, &tmp_mem, + interruptible, no_wait_gpu); + if (unlikely(r)) { + return r; + } + + r = ttm_tt_set_placement_caching(bo->ttm, tmp_mem.placement); + if (unlikely(r)) { + goto out_cleanup; + } + + r = ttm_tt_bind(bo->ttm, &tmp_mem); + if (unlikely(r)) { + goto out_cleanup; + } + r = amdgpu_move_blit(bo, true, no_wait_gpu, &tmp_mem, old_mem); + if (unlikely(r)) { + goto out_cleanup; + } + r = ttm_bo_move_ttm(bo, true, no_wait_gpu, new_mem); +out_cleanup: + ttm_bo_mem_put(bo, &tmp_mem); + return r; +} + +static int amdgpu_move_ram_vram(struct ttm_buffer_object *bo, + bool evict, bool interruptible, + bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + struct amdgpu_device *adev; + struct ttm_mem_reg *old_mem = &bo->mem; + struct ttm_mem_reg tmp_mem; + struct ttm_placement placement; + struct ttm_place placements; + int r; + + adev = amdgpu_get_adev(bo->bdev); + tmp_mem = *new_mem; + tmp_mem.mm_node = NULL; + placement.num_placement = 1; + placement.placement = &placements; + placement.num_busy_placement = 1; + placement.busy_placement = &placements; + placements.fpfn = 0; + placements.lpfn = 0; + placements.flags = TTM_PL_MASK_CACHING | TTM_PL_FLAG_TT; + r = ttm_bo_mem_space(bo, &placement, &tmp_mem, + interruptible, no_wait_gpu); + if (unlikely(r)) { + return r; + } + r = ttm_bo_move_ttm(bo, true, no_wait_gpu, &tmp_mem); + if (unlikely(r)) { + goto out_cleanup; + } + r = amdgpu_move_blit(bo, true, no_wait_gpu, new_mem, old_mem); + if (unlikely(r)) { + goto out_cleanup; + } +out_cleanup: + ttm_bo_mem_put(bo, &tmp_mem); + return r; +} + +static int amdgpu_bo_move(struct ttm_buffer_object *bo, + bool evict, bool interruptible, + bool no_wait_gpu, + struct ttm_mem_reg *new_mem) +{ + struct amdgpu_device *adev; + struct ttm_mem_reg *old_mem = &bo->mem; + int r; + + adev = amdgpu_get_adev(bo->bdev); + if (old_mem->mem_type == TTM_PL_SYSTEM && bo->ttm == NULL) { + amdgpu_move_null(bo, new_mem); + return 0; + } + if ((old_mem->mem_type == TTM_PL_TT && + new_mem->mem_type == TTM_PL_SYSTEM) || + (old_mem->mem_type == TTM_PL_SYSTEM && + new_mem->mem_type == TTM_PL_TT)) { + /* bind is enough */ + amdgpu_move_null(bo, new_mem); + return 0; + } + if (adev->mman.buffer_funcs == NULL || + adev->mman.buffer_funcs_ring == NULL || + !adev->mman.buffer_funcs_ring->ready) { + /* use memcpy */ + goto memcpy; + } + + if (old_mem->mem_type == TTM_PL_VRAM && + new_mem->mem_type == TTM_PL_SYSTEM) { + r = amdgpu_move_vram_ram(bo, evict, interruptible, + no_wait_gpu, new_mem); + } else if (old_mem->mem_type == TTM_PL_SYSTEM && + new_mem->mem_type == TTM_PL_VRAM) { + r = amdgpu_move_ram_vram(bo, evict, interruptible, + no_wait_gpu, new_mem); + } else { + r = amdgpu_move_blit(bo, evict, no_wait_gpu, new_mem, old_mem); + } + + if (r) { +memcpy: + r = ttm_bo_move_memcpy(bo, evict, no_wait_gpu, new_mem); + if (r) { + return r; + } + } + + /* update statistics */ + atomic64_add((u64)bo->num_pages << PAGE_SHIFT, &adev->num_bytes_moved); + return 0; +} + +static int amdgpu_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 amdgpu_device *adev = amdgpu_get_adev(bdev); + + 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: + break; + case TTM_PL_VRAM: + mem->bus.offset = mem->start << PAGE_SHIFT; + /* check if it's visible */ + if ((mem->bus.offset + mem->bus.size) > adev->mc.visible_vram_size) + return -EINVAL; + mem->bus.base = adev->mc.aper_base; + mem->bus.is_iomem = true; +#ifdef __alpha__ + /* + * Alpha: use bus.addr to hold the ioremap() return, + * so we can modify bus.base below. + */ + if (mem->placement & TTM_PL_FLAG_WC) + mem->bus.addr = + ioremap_wc(mem->bus.base + mem->bus.offset, + mem->bus.size); + else + mem->bus.addr = + ioremap_nocache(mem->bus.base + mem->bus.offset, + mem->bus.size); + + /* + * Alpha: Use just the bus offset plus + * the hose/domain memory base for bus.base. + * It then can be used to build PTEs for VRAM + * access, as done in ttm_bo_vm_fault(). + */ + mem->bus.base = (mem->bus.base & 0x0ffffffffUL) + + adev->ddev->hose->dense_mem_base; +#endif + break; + default: + return -EINVAL; + } + return 0; +} + +static void amdgpu_ttm_io_mem_free(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem) +{ +} + +/* + * TTM backend functions. + */ +struct amdgpu_ttm_tt { + struct ttm_dma_tt ttm; + struct amdgpu_device *adev; + u64 offset; + uint64_t userptr; + struct mm_struct *usermm; + uint32_t userflags; +}; + +/* prepare the sg table with the user pages */ +static int amdgpu_ttm_tt_pin_userptr(struct ttm_tt *ttm) +{ + struct amdgpu_device *adev = amdgpu_get_adev(ttm->bdev); + struct amdgpu_ttm_tt *gtt = (void *)ttm; + unsigned pinned = 0, nents; + int r; + + int write = !(gtt->userflags & AMDGPU_GEM_USERPTR_READONLY); + enum dma_data_direction direction = write ? + DMA_BIDIRECTIONAL : DMA_TO_DEVICE; + + if (current->mm != gtt->usermm) + return -EPERM; + + if (gtt->userflags & AMDGPU_GEM_USERPTR_ANONONLY) { + /* check that we only pin down anonymous memory + to prevent problems with writeback */ + unsigned long end = gtt->userptr + ttm->num_pages * PAGE_SIZE; + struct vm_area_struct *vma; + + vma = find_vma(gtt->usermm, gtt->userptr); + if (!vma || vma->vm_file || vma->vm_end < end) + return -EPERM; + } + + do { + unsigned num_pages = ttm->num_pages - pinned; + uint64_t userptr = gtt->userptr + pinned * PAGE_SIZE; + struct page **pages = ttm->pages + pinned; + + r = get_user_pages(current, current->mm, userptr, num_pages, + write, 0, pages, NULL); + if (r < 0) + goto release_pages; + + pinned += r; + + } while (pinned < ttm->num_pages); + + r = sg_alloc_table_from_pages(ttm->sg, ttm->pages, ttm->num_pages, 0, + ttm->num_pages << PAGE_SHIFT, + GFP_KERNEL); + if (r) + goto release_sg; + + r = -ENOMEM; + nents = dma_map_sg(adev->dev, ttm->sg->sgl, ttm->sg->nents, direction); + if (nents != ttm->sg->nents) + goto release_sg; + + drm_prime_sg_to_page_addr_arrays(ttm->sg, ttm->pages, + gtt->ttm.dma_address, ttm->num_pages); + + return 0; + +release_sg: + kfree(ttm->sg); + +release_pages: + release_pages(ttm->pages, pinned, 0); + return r; +} + +static void amdgpu_ttm_tt_unpin_userptr(struct ttm_tt *ttm) +{ + struct amdgpu_device *adev = amdgpu_get_adev(ttm->bdev); + struct amdgpu_ttm_tt *gtt = (void *)ttm; + struct scatterlist *sg; + int i; + + int write = !(gtt->userflags & AMDGPU_GEM_USERPTR_READONLY); + enum dma_data_direction direction = write ? + DMA_BIDIRECTIONAL : DMA_TO_DEVICE; + + /* double check that we don't free the table twice */ + if (!ttm->sg->sgl) + return; + + /* free the sg table and pages again */ + dma_unmap_sg(adev->dev, ttm->sg->sgl, ttm->sg->nents, direction); + + for_each_sg(ttm->sg->sgl, sg, ttm->sg->nents, i) { + struct page *page = sg_page(sg); + + if (!(gtt->userflags & AMDGPU_GEM_USERPTR_READONLY)) + set_page_dirty(page); + + mark_page_accessed(page); + page_cache_release(page); + } + + sg_free_table(ttm->sg); +} + +static int amdgpu_ttm_backend_bind(struct ttm_tt *ttm, + struct ttm_mem_reg *bo_mem) +{ + struct amdgpu_ttm_tt *gtt = (void*)ttm; + uint32_t flags = amdgpu_ttm_tt_pte_flags(gtt->adev, ttm, bo_mem); + int r; + + if (gtt->userptr) + amdgpu_ttm_tt_pin_userptr(ttm); + + gtt->offset = (unsigned long)(bo_mem->start << PAGE_SHIFT); + if (!ttm->num_pages) { + WARN(1, "nothing to bind %lu pages for mreg %p back %p!\n", + ttm->num_pages, bo_mem, ttm); + } + + if (bo_mem->mem_type == AMDGPU_PL_GDS || + bo_mem->mem_type == AMDGPU_PL_GWS || + bo_mem->mem_type == AMDGPU_PL_OA) + return -EINVAL; + + r = amdgpu_gart_bind(gtt->adev, gtt->offset, ttm->num_pages, + ttm->pages, gtt->ttm.dma_address, flags); + + if (r) { + DRM_ERROR("failed to bind %lu pages at 0x%08X\n", + ttm->num_pages, (unsigned)gtt->offset); + return r; + } + return 0; +} + +static int amdgpu_ttm_backend_unbind(struct ttm_tt *ttm) +{ + struct amdgpu_ttm_tt *gtt = (void *)ttm; + + /* unbind shouldn't be done for GDS/GWS/OA in ttm_bo_clean_mm */ + if (gtt->adev->gart.ready) + amdgpu_gart_unbind(gtt->adev, gtt->offset, ttm->num_pages); + + if (gtt->userptr) + amdgpu_ttm_tt_unpin_userptr(ttm); + + return 0; +} + +static void amdgpu_ttm_backend_destroy(struct ttm_tt *ttm) +{ + struct amdgpu_ttm_tt *gtt = (void *)ttm; + + ttm_dma_tt_fini(>t->ttm); + kfree(gtt); +} + +static struct ttm_backend_func amdgpu_backend_func = { + .bind = &amdgpu_ttm_backend_bind, + .unbind = &amdgpu_ttm_backend_unbind, + .destroy = &amdgpu_ttm_backend_destroy, +}; + +static struct ttm_tt *amdgpu_ttm_tt_create(struct ttm_bo_device *bdev, + unsigned long size, uint32_t page_flags, + struct page *dummy_read_page) +{ + struct amdgpu_device *adev; + struct amdgpu_ttm_tt *gtt; + + adev = amdgpu_get_adev(bdev); + + gtt = kzalloc(sizeof(struct amdgpu_ttm_tt), GFP_KERNEL); + if (gtt == NULL) { + return NULL; + } + gtt->ttm.ttm.func = &amdgpu_backend_func; + gtt->adev = adev; + if (ttm_dma_tt_init(>t->ttm, bdev, size, page_flags, dummy_read_page)) { + kfree(gtt); + return NULL; + } + return >t->ttm.ttm; +} + +static int amdgpu_ttm_tt_populate(struct ttm_tt *ttm) +{ + struct amdgpu_device *adev; + struct amdgpu_ttm_tt *gtt = (void *)ttm; + unsigned i; + int r; + bool slave = !!(ttm->page_flags & TTM_PAGE_FLAG_SG); + + if (ttm->state != tt_unpopulated) + return 0; + + if (gtt && gtt->userptr) { + ttm->sg = kcalloc(1, sizeof(struct sg_table), GFP_KERNEL); + if (!ttm->sg) + return -ENOMEM; + + ttm->page_flags |= TTM_PAGE_FLAG_SG; + ttm->state = tt_unbound; + return 0; + } + + if (slave && ttm->sg) { + drm_prime_sg_to_page_addr_arrays(ttm->sg, ttm->pages, + gtt->ttm.dma_address, ttm->num_pages); + ttm->state = tt_unbound; + return 0; + } + + adev = amdgpu_get_adev(ttm->bdev); + +#ifdef CONFIG_SWIOTLB + if (swiotlb_nr_tbl()) { + return ttm_dma_populate(>t->ttm, adev->dev); + } +#endif + + r = ttm_pool_populate(ttm); + if (r) { + return r; + } + + for (i = 0; i < ttm->num_pages; i++) { + gtt->ttm.dma_address[i] = pci_map_page(adev->pdev, ttm->pages[i], + 0, PAGE_SIZE, + PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(adev->pdev, gtt->ttm.dma_address[i])) { + while (--i) { + pci_unmap_page(adev->pdev, gtt->ttm.dma_address[i], + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + gtt->ttm.dma_address[i] = 0; + } + ttm_pool_unpopulate(ttm); + return -EFAULT; + } + } + return 0; +} + +static void amdgpu_ttm_tt_unpopulate(struct ttm_tt *ttm) +{ + struct amdgpu_device *adev; + struct amdgpu_ttm_tt *gtt = (void *)ttm; + unsigned i; + bool slave = !!(ttm->page_flags & TTM_PAGE_FLAG_SG); + + if (gtt && gtt->userptr) { + kfree(ttm->sg); + ttm->page_flags &= ~TTM_PAGE_FLAG_SG; + return; + } + + if (slave) + return; + + adev = amdgpu_get_adev(ttm->bdev); + +#ifdef CONFIG_SWIOTLB + if (swiotlb_nr_tbl()) { + ttm_dma_unpopulate(>t->ttm, adev->dev); + return; + } +#endif + + for (i = 0; i < ttm->num_pages; i++) { + if (gtt->ttm.dma_address[i]) { + pci_unmap_page(adev->pdev, gtt->ttm.dma_address[i], + PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); + } + } + + ttm_pool_unpopulate(ttm); +} + +int amdgpu_ttm_tt_set_userptr(struct ttm_tt *ttm, uint64_t addr, + uint32_t flags) +{ + struct amdgpu_ttm_tt *gtt = (void *)ttm; + + if (gtt == NULL) + return -EINVAL; + + gtt->userptr = addr; + gtt->usermm = current->mm; + gtt->userflags = flags; + return 0; +} + +bool amdgpu_ttm_tt_has_userptr(struct ttm_tt *ttm) +{ + struct amdgpu_ttm_tt *gtt = (void *)ttm; + + if (gtt == NULL) + return false; + + return !!gtt->userptr; +} + +bool amdgpu_ttm_tt_is_readonly(struct ttm_tt *ttm) +{ + struct amdgpu_ttm_tt *gtt = (void *)ttm; + + if (gtt == NULL) + return false; + + return !!(gtt->userflags & AMDGPU_GEM_USERPTR_READONLY); +} + +uint32_t amdgpu_ttm_tt_pte_flags(struct amdgpu_device *adev, struct ttm_tt *ttm, + struct ttm_mem_reg *mem) +{ + uint32_t flags = 0; + + if (mem && mem->mem_type != TTM_PL_SYSTEM) + flags |= AMDGPU_PTE_VALID; + + if (mem && mem->mem_type == TTM_PL_TT) + flags |= AMDGPU_PTE_SYSTEM; + + if (!ttm || ttm->caching_state == tt_cached) + flags |= AMDGPU_PTE_SNOOPED; + + if (adev->asic_type >= CHIP_TOPAZ) + flags |= AMDGPU_PTE_EXECUTABLE; + + flags |= AMDGPU_PTE_READABLE; + + if (!amdgpu_ttm_tt_is_readonly(ttm)) + flags |= AMDGPU_PTE_WRITEABLE; + + return flags; +} + +static struct ttm_bo_driver amdgpu_bo_driver = { + .ttm_tt_create = &amdgpu_ttm_tt_create, + .ttm_tt_populate = &amdgpu_ttm_tt_populate, + .ttm_tt_unpopulate = &amdgpu_ttm_tt_unpopulate, + .invalidate_caches = &amdgpu_invalidate_caches, + .init_mem_type = &amdgpu_init_mem_type, + .evict_flags = &amdgpu_evict_flags, + .move = &amdgpu_bo_move, + .verify_access = &amdgpu_verify_access, + .move_notify = &amdgpu_bo_move_notify, + .fault_reserve_notify = &amdgpu_bo_fault_reserve_notify, + .io_mem_reserve = &amdgpu_ttm_io_mem_reserve, + .io_mem_free = &amdgpu_ttm_io_mem_free, +}; + +int amdgpu_ttm_init(struct amdgpu_device *adev) +{ + int r; + + r = amdgpu_ttm_global_init(adev); + if (r) { + return r; + } + /* No others user of address space so set it to 0 */ + r = ttm_bo_device_init(&adev->mman.bdev, + adev->mman.bo_global_ref.ref.object, + &amdgpu_bo_driver, + adev->ddev->anon_inode->i_mapping, + DRM_FILE_PAGE_OFFSET, + adev->need_dma32); + if (r) { + DRM_ERROR("failed initializing buffer object driver(%d).\n", r); + return r; + } + adev->mman.initialized = true; + r = ttm_bo_init_mm(&adev->mman.bdev, TTM_PL_VRAM, + adev->mc.real_vram_size >> PAGE_SHIFT); + if (r) { + DRM_ERROR("Failed initializing VRAM heap.\n"); + return r; + } + /* Change the size here instead of the init above so only lpfn is affected */ + amdgpu_ttm_set_active_vram_size(adev, adev->mc.visible_vram_size); + + r = amdgpu_bo_create(adev, 256 * 1024, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_VRAM, 0, + NULL, &adev->stollen_vga_memory); + if (r) { + return r; + } + r = amdgpu_bo_reserve(adev->stollen_vga_memory, false); + if (r) + return r; + r = amdgpu_bo_pin(adev->stollen_vga_memory, AMDGPU_GEM_DOMAIN_VRAM, NULL); + amdgpu_bo_unreserve(adev->stollen_vga_memory); + if (r) { + amdgpu_bo_unref(&adev->stollen_vga_memory); + return r; + } + DRM_INFO("amdgpu: %uM of VRAM memory ready\n", + (unsigned) (adev->mc.real_vram_size / (1024 * 1024))); + r = ttm_bo_init_mm(&adev->mman.bdev, TTM_PL_TT, + adev->mc.gtt_size >> PAGE_SHIFT); + if (r) { + DRM_ERROR("Failed initializing GTT heap.\n"); + return r; + } + DRM_INFO("amdgpu: %uM of GTT memory ready.\n", + (unsigned)(adev->mc.gtt_size / (1024 * 1024))); + + adev->gds.mem.total_size = adev->gds.mem.total_size << AMDGPU_GDS_SHIFT; + adev->gds.mem.gfx_partition_size = adev->gds.mem.gfx_partition_size << AMDGPU_GDS_SHIFT; + adev->gds.mem.cs_partition_size = adev->gds.mem.cs_partition_size << AMDGPU_GDS_SHIFT; + adev->gds.gws.total_size = adev->gds.gws.total_size << AMDGPU_GWS_SHIFT; + adev->gds.gws.gfx_partition_size = adev->gds.gws.gfx_partition_size << AMDGPU_GWS_SHIFT; + adev->gds.gws.cs_partition_size = adev->gds.gws.cs_partition_size << AMDGPU_GWS_SHIFT; + adev->gds.oa.total_size = adev->gds.oa.total_size << AMDGPU_OA_SHIFT; + adev->gds.oa.gfx_partition_size = adev->gds.oa.gfx_partition_size << AMDGPU_OA_SHIFT; + adev->gds.oa.cs_partition_size = adev->gds.oa.cs_partition_size << AMDGPU_OA_SHIFT; + /* GDS Memory */ + r = ttm_bo_init_mm(&adev->mman.bdev, AMDGPU_PL_GDS, + adev->gds.mem.total_size >> PAGE_SHIFT); + if (r) { + DRM_ERROR("Failed initializing GDS heap.\n"); + return r; + } + + /* GWS */ + r = ttm_bo_init_mm(&adev->mman.bdev, AMDGPU_PL_GWS, + adev->gds.gws.total_size >> PAGE_SHIFT); + if (r) { + DRM_ERROR("Failed initializing gws heap.\n"); + return r; + } + + /* OA */ + r = ttm_bo_init_mm(&adev->mman.bdev, AMDGPU_PL_OA, + adev->gds.oa.total_size >> PAGE_SHIFT); + if (r) { + DRM_ERROR("Failed initializing oa heap.\n"); + return r; + } + + r = amdgpu_ttm_debugfs_init(adev); + if (r) { + DRM_ERROR("Failed to init debugfs\n"); + return r; + } + return 0; +} + +void amdgpu_ttm_fini(struct amdgpu_device *adev) +{ + int r; + + if (!adev->mman.initialized) + return; + amdgpu_ttm_debugfs_fini(adev); + if (adev->stollen_vga_memory) { + r = amdgpu_bo_reserve(adev->stollen_vga_memory, false); + if (r == 0) { + amdgpu_bo_unpin(adev->stollen_vga_memory); + amdgpu_bo_unreserve(adev->stollen_vga_memory); + } + amdgpu_bo_unref(&adev->stollen_vga_memory); + } + ttm_bo_clean_mm(&adev->mman.bdev, TTM_PL_VRAM); + ttm_bo_clean_mm(&adev->mman.bdev, TTM_PL_TT); + ttm_bo_clean_mm(&adev->mman.bdev, AMDGPU_PL_GDS); + ttm_bo_clean_mm(&adev->mman.bdev, AMDGPU_PL_GWS); + ttm_bo_clean_mm(&adev->mman.bdev, AMDGPU_PL_OA); + ttm_bo_device_release(&adev->mman.bdev); + amdgpu_gart_fini(adev); + amdgpu_ttm_global_fini(adev); + adev->mman.initialized = false; + DRM_INFO("amdgpu: ttm finalized\n"); +} + +/* this should only be called at bootup or when userspace + * isn't running */ +void amdgpu_ttm_set_active_vram_size(struct amdgpu_device *adev, u64 size) +{ + struct ttm_mem_type_manager *man; + + if (!adev->mman.initialized) + return; + + man = &adev->mman.bdev.man[TTM_PL_VRAM]; + /* this just adjusts TTM size idea, which sets lpfn to the correct value */ + man->size = size >> PAGE_SHIFT; +} + +static struct vm_operations_struct amdgpu_ttm_vm_ops; +static const struct vm_operations_struct *ttm_vm_ops = NULL; + +static int amdgpu_ttm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct ttm_buffer_object *bo; + struct amdgpu_device *adev; + int r; + + bo = (struct ttm_buffer_object *)vma->vm_private_data; + if (bo == NULL) { + return VM_FAULT_NOPAGE; + } + adev = amdgpu_get_adev(bo->bdev); + down_read(&adev->pm.mclk_lock); + r = ttm_vm_ops->fault(vma, vmf); + up_read(&adev->pm.mclk_lock); + return r; +} + +int amdgpu_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_file *file_priv; + struct amdgpu_device *adev; + int r; + + if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET)) { + return -EINVAL; + } + + file_priv = filp->private_data; + adev = file_priv->minor->dev->dev_private; + if (adev == NULL) { + return -EINVAL; + } + r = ttm_bo_mmap(filp, vma, &adev->mman.bdev); + if (unlikely(r != 0)) { + return r; + } + if (unlikely(ttm_vm_ops == NULL)) { + ttm_vm_ops = vma->vm_ops; + amdgpu_ttm_vm_ops = *ttm_vm_ops; + amdgpu_ttm_vm_ops.fault = &amdgpu_ttm_fault; + } + vma->vm_ops = &amdgpu_ttm_vm_ops; + return 0; +} + +int amdgpu_copy_buffer(struct amdgpu_ring *ring, + uint64_t src_offset, + uint64_t dst_offset, + uint32_t byte_count, + struct reservation_object *resv, + struct amdgpu_fence **fence) +{ + struct amdgpu_device *adev = ring->adev; + struct amdgpu_sync sync; + uint32_t max_bytes; + unsigned num_loops, num_dw; + unsigned i; + int r; + + /* sync other rings */ + amdgpu_sync_create(&sync); + if (resv) { + r = amdgpu_sync_resv(adev, &sync, resv, false); + if (r) { + DRM_ERROR("sync failed (%d).\n", r); + amdgpu_sync_free(adev, &sync, NULL); + return r; + } + } + + max_bytes = adev->mman.buffer_funcs->copy_max_bytes; + num_loops = DIV_ROUND_UP(byte_count, max_bytes); + num_dw = num_loops * adev->mman.buffer_funcs->copy_num_dw; + + /* for fence and sync */ + num_dw += 64 + AMDGPU_NUM_SYNCS * 8; + + r = amdgpu_ring_lock(ring, num_dw); + if (r) { + DRM_ERROR("ring lock failed (%d).\n", r); + amdgpu_sync_free(adev, &sync, NULL); + return r; + } + + amdgpu_sync_rings(&sync, ring); + + for (i = 0; i < num_loops; i++) { + uint32_t cur_size_in_bytes = min(byte_count, max_bytes); + + amdgpu_emit_copy_buffer(adev, ring, src_offset, dst_offset, + cur_size_in_bytes); + + src_offset += cur_size_in_bytes; + dst_offset += cur_size_in_bytes; + byte_count -= cur_size_in_bytes; + } + + r = amdgpu_fence_emit(ring, AMDGPU_FENCE_OWNER_MOVE, fence); + if (r) { + amdgpu_ring_unlock_undo(ring); + amdgpu_sync_free(adev, &sync, NULL); + return r; + } + + amdgpu_ring_unlock_commit(ring); + amdgpu_sync_free(adev, &sync, *fence); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) + +static int amdgpu_mm_dump_table(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + unsigned ttm_pl = *(int *)node->info_ent->data; + struct drm_device *dev = node->minor->dev; + struct amdgpu_device *adev = dev->dev_private; + struct drm_mm *mm = (struct drm_mm *)adev->mman.bdev.man[ttm_pl].priv; + int ret; + struct ttm_bo_global *glob = adev->mman.bdev.glob; + + spin_lock(&glob->lru_lock); + ret = drm_mm_dump_table(m, mm); + spin_unlock(&glob->lru_lock); + return ret; +} + +static int ttm_pl_vram = TTM_PL_VRAM; +static int ttm_pl_tt = TTM_PL_TT; + +static struct drm_info_list amdgpu_ttm_debugfs_list[] = { + {"amdgpu_vram_mm", amdgpu_mm_dump_table, 0, &ttm_pl_vram}, + {"amdgpu_gtt_mm", amdgpu_mm_dump_table, 0, &ttm_pl_tt}, + {"ttm_page_pool", ttm_page_alloc_debugfs, 0, NULL}, +#ifdef CONFIG_SWIOTLB + {"ttm_dma_page_pool", ttm_dma_page_alloc_debugfs, 0, NULL} +#endif +}; + +static ssize_t amdgpu_ttm_vram_read(struct file *f, char __user *buf, + size_t size, loff_t *pos) +{ + struct amdgpu_device *adev = f->f_inode->i_private; + ssize_t result = 0; + int r; + + if (size & 0x3 || *pos & 0x3) + return -EINVAL; + + while (size) { + unsigned long flags; + uint32_t value; + + if (*pos >= adev->mc.mc_vram_size) + return result; + + spin_lock_irqsave(&adev->mmio_idx_lock, flags); + WREG32(mmMM_INDEX, ((uint32_t)*pos) | 0x80000000); + WREG32(mmMM_INDEX_HI, *pos >> 31); + value = RREG32(mmMM_DATA); + spin_unlock_irqrestore(&adev->mmio_idx_lock, flags); + + r = put_user(value, (uint32_t *)buf); + if (r) + return r; + + result += 4; + buf += 4; + *pos += 4; + size -= 4; + } + + return result; +} + +static const struct file_operations amdgpu_ttm_vram_fops = { + .owner = THIS_MODULE, + .read = amdgpu_ttm_vram_read, + .llseek = default_llseek +}; + +static ssize_t amdgpu_ttm_gtt_read(struct file *f, char __user *buf, + size_t size, loff_t *pos) +{ + struct amdgpu_device *adev = f->f_inode->i_private; + ssize_t result = 0; + int r; + + while (size) { + loff_t p = *pos / PAGE_SIZE; + unsigned off = *pos & ~PAGE_MASK; + size_t cur_size = min_t(size_t, size, PAGE_SIZE - off); + struct page *page; + void *ptr; + + if (p >= adev->gart.num_cpu_pages) + return result; + + page = adev->gart.pages[p]; + if (page) { + ptr = kmap(page); + ptr += off; + + r = copy_to_user(buf, ptr, cur_size); + kunmap(adev->gart.pages[p]); + } else + r = clear_user(buf, cur_size); + + if (r) + return -EFAULT; + + result += cur_size; + buf += cur_size; + *pos += cur_size; + size -= cur_size; + } + + return result; +} + +static const struct file_operations amdgpu_ttm_gtt_fops = { + .owner = THIS_MODULE, + .read = amdgpu_ttm_gtt_read, + .llseek = default_llseek +}; + +#endif + +static int amdgpu_ttm_debugfs_init(struct amdgpu_device *adev) +{ +#if defined(CONFIG_DEBUG_FS) + unsigned count; + + struct drm_minor *minor = adev->ddev->primary; + struct dentry *ent, *root = minor->debugfs_root; + + ent = debugfs_create_file("amdgpu_vram", S_IFREG | S_IRUGO, root, + adev, &amdgpu_ttm_vram_fops); + if (IS_ERR(ent)) + return PTR_ERR(ent); + i_size_write(ent->d_inode, adev->mc.mc_vram_size); + adev->mman.vram = ent; + + ent = debugfs_create_file("amdgpu_gtt", S_IFREG | S_IRUGO, root, + adev, &amdgpu_ttm_gtt_fops); + if (IS_ERR(ent)) + return PTR_ERR(ent); + i_size_write(ent->d_inode, adev->mc.gtt_size); + adev->mman.gtt = ent; + + count = ARRAY_SIZE(amdgpu_ttm_debugfs_list); + +#ifdef CONFIG_SWIOTLB + if (!swiotlb_nr_tbl()) + --count; +#endif + + return amdgpu_debugfs_add_files(adev, amdgpu_ttm_debugfs_list, count); +#else + + return 0; +#endif +} + +static void amdgpu_ttm_debugfs_fini(struct amdgpu_device *adev) +{ +#if defined(CONFIG_DEBUG_FS) + + debugfs_remove(adev->mman.vram); + adev->mman.vram = NULL; + + debugfs_remove(adev->mman.gtt); + adev->mman.gtt = NULL; +#endif +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ucode.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ucode.c new file mode 100644 index 000000000000..482e66797ae6 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ucode.c @@ -0,0 +1,317 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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. + * + */ + +#include <linux/firmware.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <drm/drmP.h> +#include "amdgpu.h" +#include "amdgpu_ucode.h" + +static void amdgpu_ucode_print_common_hdr(const struct common_firmware_header *hdr) +{ + DRM_DEBUG("size_bytes: %u\n", le32_to_cpu(hdr->size_bytes)); + DRM_DEBUG("header_size_bytes: %u\n", le32_to_cpu(hdr->header_size_bytes)); + DRM_DEBUG("header_version_major: %u\n", le16_to_cpu(hdr->header_version_major)); + DRM_DEBUG("header_version_minor: %u\n", le16_to_cpu(hdr->header_version_minor)); + DRM_DEBUG("ip_version_major: %u\n", le16_to_cpu(hdr->ip_version_major)); + DRM_DEBUG("ip_version_minor: %u\n", le16_to_cpu(hdr->ip_version_minor)); + DRM_DEBUG("ucode_version: 0x%08x\n", le32_to_cpu(hdr->ucode_version)); + DRM_DEBUG("ucode_size_bytes: %u\n", le32_to_cpu(hdr->ucode_size_bytes)); + DRM_DEBUG("ucode_array_offset_bytes: %u\n", + le32_to_cpu(hdr->ucode_array_offset_bytes)); + DRM_DEBUG("crc32: 0x%08x\n", le32_to_cpu(hdr->crc32)); +} + +void amdgpu_ucode_print_mc_hdr(const struct common_firmware_header *hdr) +{ + uint16_t version_major = le16_to_cpu(hdr->header_version_major); + uint16_t version_minor = le16_to_cpu(hdr->header_version_minor); + + DRM_DEBUG("MC\n"); + amdgpu_ucode_print_common_hdr(hdr); + + if (version_major == 1) { + const struct mc_firmware_header_v1_0 *mc_hdr = + container_of(hdr, struct mc_firmware_header_v1_0, header); + + DRM_DEBUG("io_debug_size_bytes: %u\n", + le32_to_cpu(mc_hdr->io_debug_size_bytes)); + DRM_DEBUG("io_debug_array_offset_bytes: %u\n", + le32_to_cpu(mc_hdr->io_debug_array_offset_bytes)); + } else { + DRM_ERROR("Unknown MC ucode version: %u.%u\n", version_major, version_minor); + } +} + +void amdgpu_ucode_print_smc_hdr(const struct common_firmware_header *hdr) +{ + uint16_t version_major = le16_to_cpu(hdr->header_version_major); + uint16_t version_minor = le16_to_cpu(hdr->header_version_minor); + + DRM_DEBUG("SMC\n"); + amdgpu_ucode_print_common_hdr(hdr); + + if (version_major == 1) { + const struct smc_firmware_header_v1_0 *smc_hdr = + container_of(hdr, struct smc_firmware_header_v1_0, header); + + DRM_DEBUG("ucode_start_addr: %u\n", le32_to_cpu(smc_hdr->ucode_start_addr)); + } else { + DRM_ERROR("Unknown SMC ucode version: %u.%u\n", version_major, version_minor); + } +} + +void amdgpu_ucode_print_gfx_hdr(const struct common_firmware_header *hdr) +{ + uint16_t version_major = le16_to_cpu(hdr->header_version_major); + uint16_t version_minor = le16_to_cpu(hdr->header_version_minor); + + DRM_DEBUG("GFX\n"); + amdgpu_ucode_print_common_hdr(hdr); + + if (version_major == 1) { + const struct gfx_firmware_header_v1_0 *gfx_hdr = + container_of(hdr, struct gfx_firmware_header_v1_0, header); + + DRM_DEBUG("ucode_feature_version: %u\n", + le32_to_cpu(gfx_hdr->ucode_feature_version)); + DRM_DEBUG("jt_offset: %u\n", le32_to_cpu(gfx_hdr->jt_offset)); + DRM_DEBUG("jt_size: %u\n", le32_to_cpu(gfx_hdr->jt_size)); + } else { + DRM_ERROR("Unknown GFX ucode version: %u.%u\n", version_major, version_minor); + } +} + +void amdgpu_ucode_print_rlc_hdr(const struct common_firmware_header *hdr) +{ + uint16_t version_major = le16_to_cpu(hdr->header_version_major); + uint16_t version_minor = le16_to_cpu(hdr->header_version_minor); + + DRM_DEBUG("RLC\n"); + amdgpu_ucode_print_common_hdr(hdr); + + if (version_major == 1) { + const struct rlc_firmware_header_v1_0 *rlc_hdr = + container_of(hdr, struct rlc_firmware_header_v1_0, header); + + DRM_DEBUG("ucode_feature_version: %u\n", + le32_to_cpu(rlc_hdr->ucode_feature_version)); + DRM_DEBUG("save_and_restore_offset: %u\n", + le32_to_cpu(rlc_hdr->save_and_restore_offset)); + DRM_DEBUG("clear_state_descriptor_offset: %u\n", + le32_to_cpu(rlc_hdr->clear_state_descriptor_offset)); + DRM_DEBUG("avail_scratch_ram_locations: %u\n", + le32_to_cpu(rlc_hdr->avail_scratch_ram_locations)); + DRM_DEBUG("master_pkt_description_offset: %u\n", + le32_to_cpu(rlc_hdr->master_pkt_description_offset)); + } else if (version_major == 2) { + const struct rlc_firmware_header_v2_0 *rlc_hdr = + container_of(hdr, struct rlc_firmware_header_v2_0, header); + + DRM_DEBUG("ucode_feature_version: %u\n", + le32_to_cpu(rlc_hdr->ucode_feature_version)); + DRM_DEBUG("jt_offset: %u\n", le32_to_cpu(rlc_hdr->jt_offset)); + DRM_DEBUG("jt_size: %u\n", le32_to_cpu(rlc_hdr->jt_size)); + DRM_DEBUG("save_and_restore_offset: %u\n", + le32_to_cpu(rlc_hdr->save_and_restore_offset)); + DRM_DEBUG("clear_state_descriptor_offset: %u\n", + le32_to_cpu(rlc_hdr->clear_state_descriptor_offset)); + DRM_DEBUG("avail_scratch_ram_locations: %u\n", + le32_to_cpu(rlc_hdr->avail_scratch_ram_locations)); + DRM_DEBUG("reg_restore_list_size: %u\n", + le32_to_cpu(rlc_hdr->reg_restore_list_size)); + DRM_DEBUG("reg_list_format_start: %u\n", + le32_to_cpu(rlc_hdr->reg_list_format_start)); + DRM_DEBUG("reg_list_format_separate_start: %u\n", + le32_to_cpu(rlc_hdr->reg_list_format_separate_start)); + DRM_DEBUG("starting_offsets_start: %u\n", + le32_to_cpu(rlc_hdr->starting_offsets_start)); + DRM_DEBUG("reg_list_format_size_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_format_size_bytes)); + DRM_DEBUG("reg_list_format_array_offset_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_format_array_offset_bytes)); + DRM_DEBUG("reg_list_size_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_size_bytes)); + DRM_DEBUG("reg_list_array_offset_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_array_offset_bytes)); + DRM_DEBUG("reg_list_format_separate_size_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_format_separate_size_bytes)); + DRM_DEBUG("reg_list_format_separate_array_offset_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_format_separate_array_offset_bytes)); + DRM_DEBUG("reg_list_separate_size_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_separate_size_bytes)); + DRM_DEBUG("reg_list_separate_size_bytes: %u\n", + le32_to_cpu(rlc_hdr->reg_list_separate_size_bytes)); + } else { + DRM_ERROR("Unknown RLC ucode version: %u.%u\n", version_major, version_minor); + } +} + +void amdgpu_ucode_print_sdma_hdr(const struct common_firmware_header *hdr) +{ + uint16_t version_major = le16_to_cpu(hdr->header_version_major); + uint16_t version_minor = le16_to_cpu(hdr->header_version_minor); + + DRM_DEBUG("SDMA\n"); + amdgpu_ucode_print_common_hdr(hdr); + + if (version_major == 1) { + const struct sdma_firmware_header_v1_0 *sdma_hdr = + container_of(hdr, struct sdma_firmware_header_v1_0, header); + + DRM_DEBUG("ucode_feature_version: %u\n", + le32_to_cpu(sdma_hdr->ucode_feature_version)); + DRM_DEBUG("ucode_change_version: %u\n", + le32_to_cpu(sdma_hdr->ucode_change_version)); + DRM_DEBUG("jt_offset: %u\n", le32_to_cpu(sdma_hdr->jt_offset)); + DRM_DEBUG("jt_size: %u\n", le32_to_cpu(sdma_hdr->jt_size)); + if (version_minor >= 1) { + const struct sdma_firmware_header_v1_1 *sdma_v1_1_hdr = + container_of(sdma_hdr, struct sdma_firmware_header_v1_1, v1_0); + DRM_DEBUG("digest_size: %u\n", le32_to_cpu(sdma_v1_1_hdr->digest_size)); + } + } else { + DRM_ERROR("Unknown SDMA ucode version: %u.%u\n", + version_major, version_minor); + } +} + +int amdgpu_ucode_validate(const struct firmware *fw) +{ + const struct common_firmware_header *hdr = + (const struct common_firmware_header *)fw->data; + + if (fw->size == le32_to_cpu(hdr->size_bytes)) + return 0; + + return -EINVAL; +} + +bool amdgpu_ucode_hdr_version(union amdgpu_firmware_header *hdr, + uint16_t hdr_major, uint16_t hdr_minor) +{ + if ((hdr->common.header_version_major == hdr_major) && + (hdr->common.header_version_minor == hdr_minor)) + return false; + return true; +} + +static int amdgpu_ucode_init_single_fw(struct amdgpu_firmware_info *ucode, + uint64_t mc_addr, void *kptr) +{ + const struct common_firmware_header *header = NULL; + + if (NULL == ucode->fw) + return 0; + + ucode->mc_addr = mc_addr; + ucode->kaddr = kptr; + + header = (const struct common_firmware_header *)ucode->fw->data; + memcpy(ucode->kaddr, (void *)((uint8_t *)ucode->fw->data + + le32_to_cpu(header->ucode_array_offset_bytes)), + le32_to_cpu(header->ucode_size_bytes)); + + return 0; +} + +int amdgpu_ucode_init_bo(struct amdgpu_device *adev) +{ + struct amdgpu_bo **bo = &adev->firmware.fw_buf; + uint64_t fw_mc_addr; + void *fw_buf_ptr = NULL; + uint64_t fw_offset = 0; + int i, err; + struct amdgpu_firmware_info *ucode = NULL; + const struct common_firmware_header *header = NULL; + + err = amdgpu_bo_create(adev, adev->firmware.fw_size, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_GTT, 0, NULL, bo); + if (err) { + dev_err(adev->dev, "(%d) Firmware buffer allocate failed\n", err); + err = -ENOMEM; + goto failed; + } + + err = amdgpu_bo_reserve(*bo, false); + if (err) { + amdgpu_bo_unref(bo); + dev_err(adev->dev, "(%d) Firmware buffer reserve failed\n", err); + goto failed; + } + + err = amdgpu_bo_pin(*bo, AMDGPU_GEM_DOMAIN_GTT, &fw_mc_addr); + if (err) { + amdgpu_bo_unreserve(*bo); + amdgpu_bo_unref(bo); + dev_err(adev->dev, "(%d) Firmware buffer pin failed\n", err); + goto failed; + } + + err = amdgpu_bo_kmap(*bo, &fw_buf_ptr); + if (err) { + dev_err(adev->dev, "(%d) Firmware buffer kmap failed\n", err); + amdgpu_bo_unpin(*bo); + amdgpu_bo_unreserve(*bo); + amdgpu_bo_unref(bo); + goto failed; + } + + amdgpu_bo_unreserve(*bo); + + fw_offset = 0; + for (i = 0; i < AMDGPU_UCODE_ID_MAXIMUM; i++) { + ucode = &adev->firmware.ucode[i]; + if (ucode->fw) { + header = (const struct common_firmware_header *)ucode->fw->data; + amdgpu_ucode_init_single_fw(ucode, fw_mc_addr + fw_offset, + fw_buf_ptr + fw_offset); + fw_offset += ALIGN(le32_to_cpu(header->ucode_size_bytes), PAGE_SIZE); + } + } + +failed: + if (err) + adev->firmware.smu_load = false; + + return err; +} + +int amdgpu_ucode_fini_bo(struct amdgpu_device *adev) +{ + int i; + struct amdgpu_firmware_info *ucode = NULL; + + for (i = 0; i < AMDGPU_UCODE_ID_MAXIMUM; i++) { + ucode = &adev->firmware.ucode[i]; + if (ucode->fw) { + ucode->mc_addr = 0; + ucode->kaddr = NULL; + } + } + amdgpu_bo_unref(&adev->firmware.fw_buf); + adev->firmware.fw_buf = NULL; + + return 0; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ucode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ucode.h new file mode 100644 index 000000000000..e468be4e28fa --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ucode.h @@ -0,0 +1,176 @@ +/* + * Copyright 2012 Advanced Micro Devices, 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 __AMDGPU_UCODE_H__ +#define __AMDGPU_UCODE_H__ + +struct common_firmware_header { + uint32_t size_bytes; /* size of the entire header+image(s) in bytes */ + uint32_t header_size_bytes; /* size of just the header in bytes */ + uint16_t header_version_major; /* header version */ + uint16_t header_version_minor; /* header version */ + uint16_t ip_version_major; /* IP version */ + uint16_t ip_version_minor; /* IP version */ + uint32_t ucode_version; + uint32_t ucode_size_bytes; /* size of ucode in bytes */ + uint32_t ucode_array_offset_bytes; /* payload offset from the start of the header */ + uint32_t crc32; /* crc32 checksum of the payload */ +}; + +/* version_major=1, version_minor=0 */ +struct mc_firmware_header_v1_0 { + struct common_firmware_header header; + uint32_t io_debug_size_bytes; /* size of debug array in dwords */ + uint32_t io_debug_array_offset_bytes; /* payload offset from the start of the header */ +}; + +/* version_major=1, version_minor=0 */ +struct smc_firmware_header_v1_0 { + struct common_firmware_header header; + uint32_t ucode_start_addr; +}; + +/* version_major=1, version_minor=0 */ +struct gfx_firmware_header_v1_0 { + struct common_firmware_header header; + uint32_t ucode_feature_version; + uint32_t jt_offset; /* jt location */ + uint32_t jt_size; /* size of jt */ +}; + +/* version_major=1, version_minor=0 */ +struct rlc_firmware_header_v1_0 { + struct common_firmware_header header; + uint32_t ucode_feature_version; + uint32_t save_and_restore_offset; + uint32_t clear_state_descriptor_offset; + uint32_t avail_scratch_ram_locations; + uint32_t master_pkt_description_offset; +}; + +/* version_major=2, version_minor=0 */ +struct rlc_firmware_header_v2_0 { + struct common_firmware_header header; + uint32_t ucode_feature_version; + uint32_t jt_offset; /* jt location */ + uint32_t jt_size; /* size of jt */ + uint32_t save_and_restore_offset; + uint32_t clear_state_descriptor_offset; + uint32_t avail_scratch_ram_locations; + uint32_t reg_restore_list_size; + uint32_t reg_list_format_start; + uint32_t reg_list_format_separate_start; + uint32_t starting_offsets_start; + uint32_t reg_list_format_size_bytes; /* size of reg list format array in bytes */ + uint32_t reg_list_format_array_offset_bytes; /* payload offset from the start of the header */ + uint32_t reg_list_size_bytes; /* size of reg list array in bytes */ + uint32_t reg_list_array_offset_bytes; /* payload offset from the start of the header */ + uint32_t reg_list_format_separate_size_bytes; /* size of reg list format array in bytes */ + uint32_t reg_list_format_separate_array_offset_bytes; /* payload offset from the start of the header */ + uint32_t reg_list_separate_size_bytes; /* size of reg list array in bytes */ + uint32_t reg_list_separate_array_offset_bytes; /* payload offset from the start of the header */ +}; + +/* version_major=1, version_minor=0 */ +struct sdma_firmware_header_v1_0 { + struct common_firmware_header header; + uint32_t ucode_feature_version; + uint32_t ucode_change_version; + uint32_t jt_offset; /* jt location */ + uint32_t jt_size; /* size of jt */ +}; + +/* version_major=1, version_minor=1 */ +struct sdma_firmware_header_v1_1 { + struct sdma_firmware_header_v1_0 v1_0; + uint32_t digest_size; +}; + +/* header is fixed size */ +union amdgpu_firmware_header { + struct common_firmware_header common; + struct mc_firmware_header_v1_0 mc; + struct smc_firmware_header_v1_0 smc; + struct gfx_firmware_header_v1_0 gfx; + struct rlc_firmware_header_v1_0 rlc; + struct rlc_firmware_header_v2_0 rlc_v2_0; + struct sdma_firmware_header_v1_0 sdma; + struct sdma_firmware_header_v1_1 sdma_v1_1; + uint8_t raw[0x100]; +}; + +/* + * fw loading support + */ +enum AMDGPU_UCODE_ID { + AMDGPU_UCODE_ID_SDMA0 = 0, + AMDGPU_UCODE_ID_SDMA1, + AMDGPU_UCODE_ID_CP_CE, + AMDGPU_UCODE_ID_CP_PFP, + AMDGPU_UCODE_ID_CP_ME, + AMDGPU_UCODE_ID_CP_MEC1, + AMDGPU_UCODE_ID_CP_MEC2, + AMDGPU_UCODE_ID_RLC_G, + AMDGPU_UCODE_ID_MAXIMUM, +}; + +/* engine firmware status */ +enum AMDGPU_UCODE_STATUS { + AMDGPU_UCODE_STATUS_INVALID, + AMDGPU_UCODE_STATUS_NOT_LOADED, + AMDGPU_UCODE_STATUS_LOADED, +}; + +/* conform to smu_ucode_xfer_cz.h */ +#define AMDGPU_SDMA0_UCODE_LOADED 0x00000001 +#define AMDGPU_SDMA1_UCODE_LOADED 0x00000002 +#define AMDGPU_CPCE_UCODE_LOADED 0x00000004 +#define AMDGPU_CPPFP_UCODE_LOADED 0x00000008 +#define AMDGPU_CPME_UCODE_LOADED 0x00000010 +#define AMDGPU_CPMEC1_UCODE_LOADED 0x00000020 +#define AMDGPU_CPMEC2_UCODE_LOADED 0x00000040 +#define AMDGPU_CPRLC_UCODE_LOADED 0x00000100 + +/* amdgpu firmware info */ +struct amdgpu_firmware_info { + /* ucode ID */ + enum AMDGPU_UCODE_ID ucode_id; + /* request_firmware */ + const struct firmware *fw; + /* starting mc address */ + uint64_t mc_addr; + /* kernel linear address */ + void *kaddr; +}; + +void amdgpu_ucode_print_mc_hdr(const struct common_firmware_header *hdr); +void amdgpu_ucode_print_smc_hdr(const struct common_firmware_header *hdr); +void amdgpu_ucode_print_gfx_hdr(const struct common_firmware_header *hdr); +void amdgpu_ucode_print_rlc_hdr(const struct common_firmware_header *hdr); +void amdgpu_ucode_print_sdma_hdr(const struct common_firmware_header *hdr); +int amdgpu_ucode_validate(const struct firmware *fw); +bool amdgpu_ucode_hdr_version(union amdgpu_firmware_header *hdr, + uint16_t hdr_major, uint16_t hdr_minor); +int amdgpu_ucode_init_bo(struct amdgpu_device *adev); +int amdgpu_ucode_fini_bo(struct amdgpu_device *adev); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c new file mode 100644 index 000000000000..c03bce6f32a9 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.c @@ -0,0 +1,976 @@ +/* + * Copyright 2011 Advanced Micro Devices, Inc. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + */ +/* + * Authors: + * Christian König <deathsimple@vodafone.de> + */ + +#include <linux/firmware.h> +#include <linux/module.h> +#include <drm/drmP.h> +#include <drm/drm.h> + +#include "amdgpu.h" +#include "amdgpu_pm.h" +#include "amdgpu_uvd.h" +#include "cikd.h" +#include "uvd/uvd_4_2_d.h" + +/* 1 second timeout */ +#define UVD_IDLE_TIMEOUT_MS 1000 + +/* Firmware Names */ +#ifdef CONFIG_DRM_AMDGPU_CIK +#define FIRMWARE_BONAIRE "radeon/bonaire_uvd.bin" +#define FIRMWARE_KABINI "radeon/kabini_uvd.bin" +#define FIRMWARE_KAVERI "radeon/kaveri_uvd.bin" +#define FIRMWARE_HAWAII "radeon/hawaii_uvd.bin" +#define FIRMWARE_MULLINS "radeon/mullins_uvd.bin" +#endif +#define FIRMWARE_TONGA "radeon/tonga_uvd.bin" +#define FIRMWARE_CARRIZO "radeon/carrizo_uvd.bin" + +/** + * amdgpu_uvd_cs_ctx - Command submission parser context + * + * Used for emulating virtual memory support on UVD 4.2. + */ +struct amdgpu_uvd_cs_ctx { + struct amdgpu_cs_parser *parser; + unsigned reg, count; + unsigned data0, data1; + unsigned idx; + unsigned ib_idx; + + /* does the IB has a msg command */ + bool has_msg_cmd; + + /* minimum buffer sizes */ + unsigned *buf_sizes; +}; + +#ifdef CONFIG_DRM_AMDGPU_CIK +MODULE_FIRMWARE(FIRMWARE_BONAIRE); +MODULE_FIRMWARE(FIRMWARE_KABINI); +MODULE_FIRMWARE(FIRMWARE_KAVERI); +MODULE_FIRMWARE(FIRMWARE_HAWAII); +MODULE_FIRMWARE(FIRMWARE_MULLINS); +#endif +MODULE_FIRMWARE(FIRMWARE_TONGA); +MODULE_FIRMWARE(FIRMWARE_CARRIZO); + +static void amdgpu_uvd_note_usage(struct amdgpu_device *adev); +static void amdgpu_uvd_idle_work_handler(struct work_struct *work); + +int amdgpu_uvd_sw_init(struct amdgpu_device *adev) +{ + unsigned long bo_size; + const char *fw_name; + const struct common_firmware_header *hdr; + unsigned version_major, version_minor, family_id; + int i, r; + + INIT_DELAYED_WORK(&adev->uvd.idle_work, amdgpu_uvd_idle_work_handler); + + switch (adev->asic_type) { +#ifdef CONFIG_DRM_AMDGPU_CIK + case CHIP_BONAIRE: + fw_name = FIRMWARE_BONAIRE; + break; + case CHIP_KABINI: + fw_name = FIRMWARE_KABINI; + break; + case CHIP_KAVERI: + fw_name = FIRMWARE_KAVERI; + break; + case CHIP_HAWAII: + fw_name = FIRMWARE_HAWAII; + break; + case CHIP_MULLINS: + fw_name = FIRMWARE_MULLINS; + break; +#endif + case CHIP_TONGA: + fw_name = FIRMWARE_TONGA; + break; + case CHIP_CARRIZO: + fw_name = FIRMWARE_CARRIZO; + break; + default: + return -EINVAL; + } + + r = request_firmware(&adev->uvd.fw, fw_name, adev->dev); + if (r) { + dev_err(adev->dev, "amdgpu_uvd: Can't load firmware \"%s\"\n", + fw_name); + return r; + } + + r = amdgpu_ucode_validate(adev->uvd.fw); + if (r) { + dev_err(adev->dev, "amdgpu_uvd: Can't validate firmware \"%s\"\n", + fw_name); + release_firmware(adev->uvd.fw); + adev->uvd.fw = NULL; + return r; + } + + hdr = (const struct common_firmware_header *)adev->uvd.fw->data; + family_id = le32_to_cpu(hdr->ucode_version) & 0xff; + version_major = (le32_to_cpu(hdr->ucode_version) >> 24) & 0xff; + version_minor = (le32_to_cpu(hdr->ucode_version) >> 8) & 0xff; + DRM_INFO("Found UVD firmware Version: %hu.%hu Family ID: %hu\n", + version_major, version_minor, family_id); + + bo_size = AMDGPU_GPU_PAGE_ALIGN(le32_to_cpu(hdr->ucode_size_bytes) + 8) + + AMDGPU_UVD_STACK_SIZE + AMDGPU_UVD_HEAP_SIZE; + r = amdgpu_bo_create(adev, bo_size, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_VRAM, 0, NULL, &adev->uvd.vcpu_bo); + if (r) { + dev_err(adev->dev, "(%d) failed to allocate UVD bo\n", r); + return r; + } + + r = amdgpu_bo_reserve(adev->uvd.vcpu_bo, false); + if (r) { + amdgpu_bo_unref(&adev->uvd.vcpu_bo); + dev_err(adev->dev, "(%d) failed to reserve UVD bo\n", r); + return r; + } + + r = amdgpu_bo_pin(adev->uvd.vcpu_bo, AMDGPU_GEM_DOMAIN_VRAM, + &adev->uvd.gpu_addr); + if (r) { + amdgpu_bo_unreserve(adev->uvd.vcpu_bo); + amdgpu_bo_unref(&adev->uvd.vcpu_bo); + dev_err(adev->dev, "(%d) UVD bo pin failed\n", r); + return r; + } + + r = amdgpu_bo_kmap(adev->uvd.vcpu_bo, &adev->uvd.cpu_addr); + if (r) { + dev_err(adev->dev, "(%d) UVD map failed\n", r); + return r; + } + + amdgpu_bo_unreserve(adev->uvd.vcpu_bo); + + for (i = 0; i < AMDGPU_MAX_UVD_HANDLES; ++i) { + atomic_set(&adev->uvd.handles[i], 0); + adev->uvd.filp[i] = NULL; + } + + /* from uvd v5.0 HW addressing capacity increased to 64 bits */ + if (!amdgpu_ip_block_version_cmp(adev, AMDGPU_IP_BLOCK_TYPE_UVD, 5, 0)) + adev->uvd.address_64_bit = true; + + return 0; +} + +int amdgpu_uvd_sw_fini(struct amdgpu_device *adev) +{ + int r; + + if (adev->uvd.vcpu_bo == NULL) + return 0; + + r = amdgpu_bo_reserve(adev->uvd.vcpu_bo, false); + if (!r) { + amdgpu_bo_kunmap(adev->uvd.vcpu_bo); + amdgpu_bo_unpin(adev->uvd.vcpu_bo); + amdgpu_bo_unreserve(adev->uvd.vcpu_bo); + } + + amdgpu_bo_unref(&adev->uvd.vcpu_bo); + + amdgpu_ring_fini(&adev->uvd.ring); + + release_firmware(adev->uvd.fw); + + return 0; +} + +int amdgpu_uvd_suspend(struct amdgpu_device *adev) +{ + unsigned size; + void *ptr; + const struct common_firmware_header *hdr; + int i; + + if (adev->uvd.vcpu_bo == NULL) + return 0; + + for (i = 0; i < AMDGPU_MAX_UVD_HANDLES; ++i) + if (atomic_read(&adev->uvd.handles[i])) + break; + + if (i == AMDGPU_MAX_UVD_HANDLES) + return 0; + + hdr = (const struct common_firmware_header *)adev->uvd.fw->data; + + size = amdgpu_bo_size(adev->uvd.vcpu_bo); + size -= le32_to_cpu(hdr->ucode_size_bytes); + + ptr = adev->uvd.cpu_addr; + ptr += le32_to_cpu(hdr->ucode_size_bytes); + + adev->uvd.saved_bo = kmalloc(size, GFP_KERNEL); + memcpy(adev->uvd.saved_bo, ptr, size); + + return 0; +} + +int amdgpu_uvd_resume(struct amdgpu_device *adev) +{ + unsigned size; + void *ptr; + const struct common_firmware_header *hdr; + unsigned offset; + + if (adev->uvd.vcpu_bo == NULL) + return -EINVAL; + + hdr = (const struct common_firmware_header *)adev->uvd.fw->data; + offset = le32_to_cpu(hdr->ucode_array_offset_bytes); + memcpy(adev->uvd.cpu_addr, (adev->uvd.fw->data) + offset, + (adev->uvd.fw->size) - offset); + + size = amdgpu_bo_size(adev->uvd.vcpu_bo); + size -= le32_to_cpu(hdr->ucode_size_bytes); + ptr = adev->uvd.cpu_addr; + ptr += le32_to_cpu(hdr->ucode_size_bytes); + + if (adev->uvd.saved_bo != NULL) { + memcpy(ptr, adev->uvd.saved_bo, size); + kfree(adev->uvd.saved_bo); + adev->uvd.saved_bo = NULL; + } else + memset(ptr, 0, size); + + return 0; +} + +void amdgpu_uvd_free_handles(struct amdgpu_device *adev, struct drm_file *filp) +{ + struct amdgpu_ring *ring = &adev->uvd.ring; + int i, r; + + for (i = 0; i < AMDGPU_MAX_UVD_HANDLES; ++i) { + uint32_t handle = atomic_read(&adev->uvd.handles[i]); + if (handle != 0 && adev->uvd.filp[i] == filp) { + struct amdgpu_fence *fence; + + amdgpu_uvd_note_usage(adev); + + r = amdgpu_uvd_get_destroy_msg(ring, handle, &fence); + if (r) { + DRM_ERROR("Error destroying UVD (%d)!\n", r); + continue; + } + + amdgpu_fence_wait(fence, false); + amdgpu_fence_unref(&fence); + + adev->uvd.filp[i] = NULL; + atomic_set(&adev->uvd.handles[i], 0); + } + } +} + +static void amdgpu_uvd_force_into_uvd_segment(struct amdgpu_bo *rbo) +{ + int i; + for (i = 0; i < rbo->placement.num_placement; ++i) { + rbo->placements[i].fpfn = 0 >> PAGE_SHIFT; + rbo->placements[i].lpfn = (256 * 1024 * 1024) >> PAGE_SHIFT; + } +} + +/** + * amdgpu_uvd_cs_pass1 - first parsing round + * + * @ctx: UVD parser context + * + * Make sure UVD message and feedback buffers are in VRAM and + * nobody is violating an 256MB boundary. + */ +static int amdgpu_uvd_cs_pass1(struct amdgpu_uvd_cs_ctx *ctx) +{ + struct amdgpu_bo_va_mapping *mapping; + struct amdgpu_bo *bo; + uint32_t cmd, lo, hi; + uint64_t addr; + int r = 0; + + lo = amdgpu_get_ib_value(ctx->parser, ctx->ib_idx, ctx->data0); + hi = amdgpu_get_ib_value(ctx->parser, ctx->ib_idx, ctx->data1); + addr = ((uint64_t)lo) | (((uint64_t)hi) << 32); + + mapping = amdgpu_cs_find_mapping(ctx->parser, addr, &bo); + if (mapping == NULL) { + DRM_ERROR("Can't find BO for addr 0x%08Lx\n", addr); + return -EINVAL; + } + + if (!ctx->parser->adev->uvd.address_64_bit) { + /* check if it's a message or feedback command */ + cmd = amdgpu_get_ib_value(ctx->parser, ctx->ib_idx, ctx->idx) >> 1; + if (cmd == 0x0 || cmd == 0x3) { + /* yes, force it into VRAM */ + uint32_t domain = AMDGPU_GEM_DOMAIN_VRAM; + amdgpu_ttm_placement_from_domain(bo, domain); + } + amdgpu_uvd_force_into_uvd_segment(bo); + + r = ttm_bo_validate(&bo->tbo, &bo->placement, false, false); + } + + return r; +} + +/** + * amdgpu_uvd_cs_msg_decode - handle UVD decode message + * + * @msg: pointer to message structure + * @buf_sizes: returned buffer sizes + * + * Peek into the decode message and calculate the necessary buffer sizes. + */ +static int amdgpu_uvd_cs_msg_decode(uint32_t *msg, unsigned buf_sizes[]) +{ + unsigned stream_type = msg[4]; + unsigned width = msg[6]; + unsigned height = msg[7]; + unsigned dpb_size = msg[9]; + unsigned pitch = msg[28]; + unsigned level = msg[57]; + + unsigned width_in_mb = width / 16; + unsigned height_in_mb = ALIGN(height / 16, 2); + unsigned fs_in_mb = width_in_mb * height_in_mb; + + unsigned image_size, tmp, min_dpb_size, num_dpb_buffer; + + image_size = width * height; + image_size += image_size / 2; + image_size = ALIGN(image_size, 1024); + + switch (stream_type) { + case 0: /* H264 */ + case 7: /* H264 Perf */ + switch(level) { + case 30: + num_dpb_buffer = 8100 / fs_in_mb; + break; + case 31: + num_dpb_buffer = 18000 / fs_in_mb; + break; + case 32: + num_dpb_buffer = 20480 / fs_in_mb; + break; + case 41: + num_dpb_buffer = 32768 / fs_in_mb; + break; + case 42: + num_dpb_buffer = 34816 / fs_in_mb; + break; + case 50: + num_dpb_buffer = 110400 / fs_in_mb; + break; + case 51: + num_dpb_buffer = 184320 / fs_in_mb; + break; + default: + num_dpb_buffer = 184320 / fs_in_mb; + break; + } + num_dpb_buffer++; + if (num_dpb_buffer > 17) + num_dpb_buffer = 17; + + /* reference picture buffer */ + min_dpb_size = image_size * num_dpb_buffer; + + /* macroblock context buffer */ + min_dpb_size += width_in_mb * height_in_mb * num_dpb_buffer * 192; + + /* IT surface buffer */ + min_dpb_size += width_in_mb * height_in_mb * 32; + break; + + case 1: /* VC1 */ + + /* reference picture buffer */ + min_dpb_size = image_size * 3; + + /* CONTEXT_BUFFER */ + min_dpb_size += width_in_mb * height_in_mb * 128; + + /* IT surface buffer */ + min_dpb_size += width_in_mb * 64; + + /* DB surface buffer */ + min_dpb_size += width_in_mb * 128; + + /* BP */ + tmp = max(width_in_mb, height_in_mb); + min_dpb_size += ALIGN(tmp * 7 * 16, 64); + break; + + case 3: /* MPEG2 */ + + /* reference picture buffer */ + min_dpb_size = image_size * 3; + break; + + case 4: /* MPEG4 */ + + /* reference picture buffer */ + min_dpb_size = image_size * 3; + + /* CM */ + min_dpb_size += width_in_mb * height_in_mb * 64; + + /* IT surface buffer */ + min_dpb_size += ALIGN(width_in_mb * height_in_mb * 32, 64); + break; + + default: + DRM_ERROR("UVD codec not handled %d!\n", stream_type); + return -EINVAL; + } + + if (width > pitch) { + DRM_ERROR("Invalid UVD decoding target pitch!\n"); + return -EINVAL; + } + + if (dpb_size < min_dpb_size) { + DRM_ERROR("Invalid dpb_size in UVD message (%d / %d)!\n", + dpb_size, min_dpb_size); + return -EINVAL; + } + + buf_sizes[0x1] = dpb_size; + buf_sizes[0x2] = image_size; + return 0; +} + +/** + * amdgpu_uvd_cs_msg - handle UVD message + * + * @ctx: UVD parser context + * @bo: buffer object containing the message + * @offset: offset into the buffer object + * + * Peek into the UVD message and extract the session id. + * Make sure that we don't open up to many sessions. + */ +static int amdgpu_uvd_cs_msg(struct amdgpu_uvd_cs_ctx *ctx, + struct amdgpu_bo *bo, unsigned offset) +{ + struct amdgpu_device *adev = ctx->parser->adev; + int32_t *msg, msg_type, handle; + struct fence *f; + void *ptr; + + int i, r; + + if (offset & 0x3F) { + DRM_ERROR("UVD messages must be 64 byte aligned!\n"); + return -EINVAL; + } + + f = reservation_object_get_excl(bo->tbo.resv); + if (f) { + r = amdgpu_fence_wait((struct amdgpu_fence *)f, false); + if (r) { + DRM_ERROR("Failed waiting for UVD message (%d)!\n", r); + return r; + } + } + + r = amdgpu_bo_kmap(bo, &ptr); + if (r) { + DRM_ERROR("Failed mapping the UVD message (%d)!\n", r); + return r; + } + + msg = ptr + offset; + + msg_type = msg[1]; + handle = msg[2]; + + if (handle == 0) { + DRM_ERROR("Invalid UVD handle!\n"); + return -EINVAL; + } + + if (msg_type == 1) { + /* it's a decode msg, calc buffer sizes */ + r = amdgpu_uvd_cs_msg_decode(msg, ctx->buf_sizes); + amdgpu_bo_kunmap(bo); + if (r) + return r; + + } else if (msg_type == 2) { + /* it's a destroy msg, free the handle */ + for (i = 0; i < AMDGPU_MAX_UVD_HANDLES; ++i) + atomic_cmpxchg(&adev->uvd.handles[i], handle, 0); + amdgpu_bo_kunmap(bo); + return 0; + } else { + /* it's a create msg */ + amdgpu_bo_kunmap(bo); + + if (msg_type != 0) { + DRM_ERROR("Illegal UVD message type (%d)!\n", msg_type); + return -EINVAL; + } + + /* it's a create msg, no special handling needed */ + } + + /* create or decode, validate the handle */ + for (i = 0; i < AMDGPU_MAX_UVD_HANDLES; ++i) { + if (atomic_read(&adev->uvd.handles[i]) == handle) + return 0; + } + + /* handle not found try to alloc a new one */ + for (i = 0; i < AMDGPU_MAX_UVD_HANDLES; ++i) { + if (!atomic_cmpxchg(&adev->uvd.handles[i], 0, handle)) { + adev->uvd.filp[i] = ctx->parser->filp; + return 0; + } + } + + DRM_ERROR("No more free UVD handles!\n"); + return -EINVAL; +} + +/** + * amdgpu_uvd_cs_pass2 - second parsing round + * + * @ctx: UVD parser context + * + * Patch buffer addresses, make sure buffer sizes are correct. + */ +static int amdgpu_uvd_cs_pass2(struct amdgpu_uvd_cs_ctx *ctx) +{ + struct amdgpu_bo_va_mapping *mapping; + struct amdgpu_bo *bo; + struct amdgpu_ib *ib; + uint32_t cmd, lo, hi; + uint64_t start, end; + uint64_t addr; + int r; + + lo = amdgpu_get_ib_value(ctx->parser, ctx->ib_idx, ctx->data0); + hi = amdgpu_get_ib_value(ctx->parser, ctx->ib_idx, ctx->data1); + addr = ((uint64_t)lo) | (((uint64_t)hi) << 32); + + mapping = amdgpu_cs_find_mapping(ctx->parser, addr, &bo); + if (mapping == NULL) + return -EINVAL; + + start = amdgpu_bo_gpu_offset(bo); + + end = (mapping->it.last + 1 - mapping->it.start); + end = end * AMDGPU_GPU_PAGE_SIZE + start; + + addr -= ((uint64_t)mapping->it.start) * AMDGPU_GPU_PAGE_SIZE; + start += addr; + + ib = &ctx->parser->ibs[ctx->ib_idx]; + ib->ptr[ctx->data0] = start & 0xFFFFFFFF; + ib->ptr[ctx->data1] = start >> 32; + + cmd = amdgpu_get_ib_value(ctx->parser, ctx->ib_idx, ctx->idx) >> 1; + if (cmd < 0x4) { + if ((end - start) < ctx->buf_sizes[cmd]) { + DRM_ERROR("buffer (%d) to small (%d / %d)!\n", cmd, + (unsigned)(end - start), + ctx->buf_sizes[cmd]); + return -EINVAL; + } + + } else if ((cmd != 0x100) && (cmd != 0x204)) { + DRM_ERROR("invalid UVD command %X!\n", cmd); + return -EINVAL; + } + + if (!ctx->parser->adev->uvd.address_64_bit) { + if ((start >> 28) != ((end - 1) >> 28)) { + DRM_ERROR("reloc %LX-%LX crossing 256MB boundary!\n", + start, end); + return -EINVAL; + } + + if ((cmd == 0 || cmd == 0x3) && + (start >> 28) != (ctx->parser->adev->uvd.gpu_addr >> 28)) { + DRM_ERROR("msg/fb buffer %LX-%LX out of 256MB segment!\n", + start, end); + return -EINVAL; + } + } + + if (cmd == 0) { + ctx->has_msg_cmd = true; + r = amdgpu_uvd_cs_msg(ctx, bo, addr); + if (r) + return r; + } else if (!ctx->has_msg_cmd) { + DRM_ERROR("Message needed before other commands are send!\n"); + return -EINVAL; + } + + return 0; +} + +/** + * amdgpu_uvd_cs_reg - parse register writes + * + * @ctx: UVD parser context + * @cb: callback function + * + * Parse the register writes, call cb on each complete command. + */ +static int amdgpu_uvd_cs_reg(struct amdgpu_uvd_cs_ctx *ctx, + int (*cb)(struct amdgpu_uvd_cs_ctx *ctx)) +{ + struct amdgpu_ib *ib = &ctx->parser->ibs[ctx->ib_idx]; + int i, r; + + ctx->idx++; + for (i = 0; i <= ctx->count; ++i) { + unsigned reg = ctx->reg + i; + + if (ctx->idx >= ib->length_dw) { + DRM_ERROR("Register command after end of CS!\n"); + return -EINVAL; + } + + switch (reg) { + case mmUVD_GPCOM_VCPU_DATA0: + ctx->data0 = ctx->idx; + break; + case mmUVD_GPCOM_VCPU_DATA1: + ctx->data1 = ctx->idx; + break; + case mmUVD_GPCOM_VCPU_CMD: + r = cb(ctx); + if (r) + return r; + break; + case mmUVD_ENGINE_CNTL: + break; + default: + DRM_ERROR("Invalid reg 0x%X!\n", reg); + return -EINVAL; + } + ctx->idx++; + } + return 0; +} + +/** + * amdgpu_uvd_cs_packets - parse UVD packets + * + * @ctx: UVD parser context + * @cb: callback function + * + * Parse the command stream packets. + */ +static int amdgpu_uvd_cs_packets(struct amdgpu_uvd_cs_ctx *ctx, + int (*cb)(struct amdgpu_uvd_cs_ctx *ctx)) +{ + struct amdgpu_ib *ib = &ctx->parser->ibs[ctx->ib_idx]; + int r; + + for (ctx->idx = 0 ; ctx->idx < ib->length_dw; ) { + uint32_t cmd = amdgpu_get_ib_value(ctx->parser, ctx->ib_idx, ctx->idx); + unsigned type = CP_PACKET_GET_TYPE(cmd); + switch (type) { + case PACKET_TYPE0: + ctx->reg = CP_PACKET0_GET_REG(cmd); + ctx->count = CP_PACKET_GET_COUNT(cmd); + r = amdgpu_uvd_cs_reg(ctx, cb); + if (r) + return r; + break; + case PACKET_TYPE2: + ++ctx->idx; + break; + default: + DRM_ERROR("Unknown packet type %d !\n", type); + return -EINVAL; + } + } + return 0; +} + +/** + * amdgpu_uvd_ring_parse_cs - UVD command submission parser + * + * @parser: Command submission parser context + * + * Parse the command stream, patch in addresses as necessary. + */ +int amdgpu_uvd_ring_parse_cs(struct amdgpu_cs_parser *parser, uint32_t ib_idx) +{ + struct amdgpu_uvd_cs_ctx ctx = {}; + unsigned buf_sizes[] = { + [0x00000000] = 2048, + [0x00000001] = 32 * 1024 * 1024, + [0x00000002] = 2048 * 1152 * 3, + [0x00000003] = 2048, + }; + struct amdgpu_ib *ib = &parser->ibs[ib_idx]; + int r; + + if (ib->length_dw % 16) { + DRM_ERROR("UVD IB length (%d) not 16 dwords aligned!\n", + ib->length_dw); + return -EINVAL; + } + + ctx.parser = parser; + ctx.buf_sizes = buf_sizes; + ctx.ib_idx = ib_idx; + + /* first round, make sure the buffers are actually in the UVD segment */ + r = amdgpu_uvd_cs_packets(&ctx, amdgpu_uvd_cs_pass1); + if (r) + return r; + + /* second round, patch buffer addresses into the command stream */ + r = amdgpu_uvd_cs_packets(&ctx, amdgpu_uvd_cs_pass2); + if (r) + return r; + + if (!ctx.has_msg_cmd) { + DRM_ERROR("UVD-IBs need a msg command!\n"); + return -EINVAL; + } + + amdgpu_uvd_note_usage(ctx.parser->adev); + + return 0; +} + +static int amdgpu_uvd_send_msg(struct amdgpu_ring *ring, + struct amdgpu_bo *bo, + struct amdgpu_fence **fence) +{ + struct ttm_validate_buffer tv; + struct ww_acquire_ctx ticket; + struct list_head head; + struct amdgpu_ib ib; + uint64_t addr; + int i, r; + + memset(&tv, 0, sizeof(tv)); + tv.bo = &bo->tbo; + + INIT_LIST_HEAD(&head); + list_add(&tv.head, &head); + + r = ttm_eu_reserve_buffers(&ticket, &head, true, NULL); + if (r) + return r; + + if (!bo->adev->uvd.address_64_bit) { + amdgpu_ttm_placement_from_domain(bo, AMDGPU_GEM_DOMAIN_VRAM); + amdgpu_uvd_force_into_uvd_segment(bo); + } + + r = ttm_bo_validate(&bo->tbo, &bo->placement, true, false); + if (r) + goto err; + + r = amdgpu_ib_get(ring, NULL, 64, &ib); + if (r) + goto err; + + addr = amdgpu_bo_gpu_offset(bo); + ib.ptr[0] = PACKET0(mmUVD_GPCOM_VCPU_DATA0, 0); + ib.ptr[1] = addr; + ib.ptr[2] = PACKET0(mmUVD_GPCOM_VCPU_DATA1, 0); + ib.ptr[3] = addr >> 32; + ib.ptr[4] = PACKET0(mmUVD_GPCOM_VCPU_CMD, 0); + ib.ptr[5] = 0; + for (i = 6; i < 16; ++i) + ib.ptr[i] = PACKET2(0); + ib.length_dw = 16; + + r = amdgpu_ib_schedule(ring->adev, 1, &ib, AMDGPU_FENCE_OWNER_UNDEFINED); + if (r) + goto err; + ttm_eu_fence_buffer_objects(&ticket, &head, &ib.fence->base); + + if (fence) + *fence = amdgpu_fence_ref(ib.fence); + + amdgpu_ib_free(ring->adev, &ib); + amdgpu_bo_unref(&bo); + return 0; + +err: + ttm_eu_backoff_reservation(&ticket, &head); + return r; +} + +/* multiple fence commands without any stream commands in between can + crash the vcpu so just try to emmit a dummy create/destroy msg to + avoid this */ +int amdgpu_uvd_get_create_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence) +{ + struct amdgpu_device *adev = ring->adev; + struct amdgpu_bo *bo; + uint32_t *msg; + int r, i; + + r = amdgpu_bo_create(adev, 1024, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_VRAM, 0, NULL, &bo); + if (r) + return r; + + r = amdgpu_bo_reserve(bo, false); + if (r) { + amdgpu_bo_unref(&bo); + return r; + } + + r = amdgpu_bo_kmap(bo, (void **)&msg); + if (r) { + amdgpu_bo_unreserve(bo); + amdgpu_bo_unref(&bo); + return r; + } + + /* stitch together an UVD create msg */ + msg[0] = cpu_to_le32(0x00000de4); + msg[1] = cpu_to_le32(0x00000000); + msg[2] = cpu_to_le32(handle); + msg[3] = cpu_to_le32(0x00000000); + msg[4] = cpu_to_le32(0x00000000); + msg[5] = cpu_to_le32(0x00000000); + msg[6] = cpu_to_le32(0x00000000); + msg[7] = cpu_to_le32(0x00000780); + msg[8] = cpu_to_le32(0x00000440); + msg[9] = cpu_to_le32(0x00000000); + msg[10] = cpu_to_le32(0x01b37000); + for (i = 11; i < 1024; ++i) + msg[i] = cpu_to_le32(0x0); + + amdgpu_bo_kunmap(bo); + amdgpu_bo_unreserve(bo); + + return amdgpu_uvd_send_msg(ring, bo, fence); +} + +int amdgpu_uvd_get_destroy_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence) +{ + struct amdgpu_device *adev = ring->adev; + struct amdgpu_bo *bo; + uint32_t *msg; + int r, i; + + r = amdgpu_bo_create(adev, 1024, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_VRAM, 0, NULL, &bo); + if (r) + return r; + + r = amdgpu_bo_reserve(bo, false); + if (r) { + amdgpu_bo_unref(&bo); + return r; + } + + r = amdgpu_bo_kmap(bo, (void **)&msg); + if (r) { + amdgpu_bo_unreserve(bo); + amdgpu_bo_unref(&bo); + return r; + } + + /* stitch together an UVD destroy msg */ + msg[0] = cpu_to_le32(0x00000de4); + msg[1] = cpu_to_le32(0x00000002); + msg[2] = cpu_to_le32(handle); + msg[3] = cpu_to_le32(0x00000000); + for (i = 4; i < 1024; ++i) + msg[i] = cpu_to_le32(0x0); + + amdgpu_bo_kunmap(bo); + amdgpu_bo_unreserve(bo); + + return amdgpu_uvd_send_msg(ring, bo, fence); +} + +static void amdgpu_uvd_idle_work_handler(struct work_struct *work) +{ + struct amdgpu_device *adev = + container_of(work, struct amdgpu_device, uvd.idle_work.work); + unsigned i, fences, handles = 0; + + fences = amdgpu_fence_count_emitted(&adev->uvd.ring); + + for (i = 0; i < AMDGPU_MAX_UVD_HANDLES; ++i) + if (atomic_read(&adev->uvd.handles[i])) + ++handles; + + if (fences == 0 && handles == 0) { + if (adev->pm.dpm_enabled) { + amdgpu_dpm_enable_uvd(adev, false); + } else { + amdgpu_asic_set_uvd_clocks(adev, 0, 0); + } + } else { + schedule_delayed_work(&adev->uvd.idle_work, + msecs_to_jiffies(UVD_IDLE_TIMEOUT_MS)); + } +} + +static void amdgpu_uvd_note_usage(struct amdgpu_device *adev) +{ + bool set_clocks = !cancel_delayed_work_sync(&adev->uvd.idle_work); + set_clocks &= schedule_delayed_work(&adev->uvd.idle_work, + msecs_to_jiffies(UVD_IDLE_TIMEOUT_MS)); + + if (set_clocks) { + if (adev->pm.dpm_enabled) { + amdgpu_dpm_enable_uvd(adev, true); + } else { + amdgpu_asic_set_uvd_clocks(adev, 53300, 40000); + } + } +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.h new file mode 100644 index 000000000000..2255aa710e33 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_uvd.h @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_UVD_H__ +#define __AMDGPU_UVD_H__ + +int amdgpu_uvd_sw_init(struct amdgpu_device *adev); +int amdgpu_uvd_sw_fini(struct amdgpu_device *adev); +int amdgpu_uvd_suspend(struct amdgpu_device *adev); +int amdgpu_uvd_resume(struct amdgpu_device *adev); +int amdgpu_uvd_get_create_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence); +int amdgpu_uvd_get_destroy_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence); +void amdgpu_uvd_free_handles(struct amdgpu_device *adev, + struct drm_file *filp); +int amdgpu_uvd_ring_parse_cs(struct amdgpu_cs_parser *parser, uint32_t ib_idx); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c new file mode 100644 index 000000000000..c65d93cb540d --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.c @@ -0,0 +1,727 @@ +/* + * Copyright 2013 Advanced Micro Devices, Inc. + * 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 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. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * Authors: Christian König <christian.koenig@amd.com> + */ + +#include <linux/firmware.h> +#include <linux/module.h> +#include <drm/drmP.h> +#include <drm/drm.h> + +#include "amdgpu.h" +#include "amdgpu_pm.h" +#include "amdgpu_vce.h" +#include "cikd.h" + +/* 1 second timeout */ +#define VCE_IDLE_TIMEOUT_MS 1000 + +/* Firmware Names */ +#ifdef CONFIG_DRM_AMDGPU_CIK +#define FIRMWARE_BONAIRE "radeon/bonaire_vce.bin" +#define FIRMWARE_KABINI "radeon/kabini_vce.bin" +#define FIRMWARE_KAVERI "radeon/kaveri_vce.bin" +#define FIRMWARE_HAWAII "radeon/hawaii_vce.bin" +#define FIRMWARE_MULLINS "radeon/mullins_vce.bin" +#endif +#define FIRMWARE_TONGA "radeon/tonga_vce.bin" +#define FIRMWARE_CARRIZO "radeon/carrizo_vce.bin" + +#ifdef CONFIG_DRM_AMDGPU_CIK +MODULE_FIRMWARE(FIRMWARE_BONAIRE); +MODULE_FIRMWARE(FIRMWARE_KABINI); +MODULE_FIRMWARE(FIRMWARE_KAVERI); +MODULE_FIRMWARE(FIRMWARE_HAWAII); +MODULE_FIRMWARE(FIRMWARE_MULLINS); +#endif +MODULE_FIRMWARE(FIRMWARE_TONGA); +MODULE_FIRMWARE(FIRMWARE_CARRIZO); + +static void amdgpu_vce_idle_work_handler(struct work_struct *work); + +/** + * amdgpu_vce_init - allocate memory, load vce firmware + * + * @adev: amdgpu_device pointer + * + * First step to get VCE online, allocate memory and load the firmware + */ +int amdgpu_vce_sw_init(struct amdgpu_device *adev) +{ + unsigned long size; + const char *fw_name; + const struct common_firmware_header *hdr; + unsigned ucode_version, version_major, version_minor, binary_id; + int i, r; + + INIT_DELAYED_WORK(&adev->vce.idle_work, amdgpu_vce_idle_work_handler); + + switch (adev->asic_type) { +#ifdef CONFIG_DRM_AMDGPU_CIK + case CHIP_BONAIRE: + fw_name = FIRMWARE_BONAIRE; + break; + case CHIP_KAVERI: + fw_name = FIRMWARE_KAVERI; + break; + case CHIP_KABINI: + fw_name = FIRMWARE_KABINI; + break; + case CHIP_HAWAII: + fw_name = FIRMWARE_HAWAII; + break; + case CHIP_MULLINS: + fw_name = FIRMWARE_MULLINS; + break; +#endif + case CHIP_TONGA: + fw_name = FIRMWARE_TONGA; + break; + case CHIP_CARRIZO: + fw_name = FIRMWARE_CARRIZO; + break; + + default: + return -EINVAL; + } + + r = request_firmware(&adev->vce.fw, fw_name, adev->dev); + if (r) { + dev_err(adev->dev, "amdgpu_vce: Can't load firmware \"%s\"\n", + fw_name); + return r; + } + + r = amdgpu_ucode_validate(adev->vce.fw); + if (r) { + dev_err(adev->dev, "amdgpu_vce: Can't validate firmware \"%s\"\n", + fw_name); + release_firmware(adev->vce.fw); + adev->vce.fw = NULL; + return r; + } + + hdr = (const struct common_firmware_header *)adev->vce.fw->data; + + ucode_version = le32_to_cpu(hdr->ucode_version); + version_major = (ucode_version >> 20) & 0xfff; + version_minor = (ucode_version >> 8) & 0xfff; + binary_id = ucode_version & 0xff; + DRM_INFO("Found VCE firmware Version: %hhd.%hhd Binary ID: %hhd\n", + version_major, version_minor, binary_id); + adev->vce.fw_version = ((version_major << 24) | (version_minor << 16) | + (binary_id << 8)); + + /* allocate firmware, stack and heap BO */ + + size = AMDGPU_GPU_PAGE_ALIGN(le32_to_cpu(hdr->ucode_size_bytes)) + + AMDGPU_VCE_STACK_SIZE + AMDGPU_VCE_HEAP_SIZE; + r = amdgpu_bo_create(adev, size, PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_VRAM, 0, NULL, &adev->vce.vcpu_bo); + if (r) { + dev_err(adev->dev, "(%d) failed to allocate VCE bo\n", r); + return r; + } + + r = amdgpu_bo_reserve(adev->vce.vcpu_bo, false); + if (r) { + amdgpu_bo_unref(&adev->vce.vcpu_bo); + dev_err(adev->dev, "(%d) failed to reserve VCE bo\n", r); + return r; + } + + r = amdgpu_bo_pin(adev->vce.vcpu_bo, AMDGPU_GEM_DOMAIN_VRAM, + &adev->vce.gpu_addr); + amdgpu_bo_unreserve(adev->vce.vcpu_bo); + if (r) { + amdgpu_bo_unref(&adev->vce.vcpu_bo); + dev_err(adev->dev, "(%d) VCE bo pin failed\n", r); + return r; + } + + for (i = 0; i < AMDGPU_MAX_VCE_HANDLES; ++i) { + atomic_set(&adev->vce.handles[i], 0); + adev->vce.filp[i] = NULL; + } + + return 0; +} + +/** + * amdgpu_vce_fini - free memory + * + * @adev: amdgpu_device pointer + * + * Last step on VCE teardown, free firmware memory + */ +int amdgpu_vce_sw_fini(struct amdgpu_device *adev) +{ + if (adev->vce.vcpu_bo == NULL) + return 0; + + amdgpu_bo_unref(&adev->vce.vcpu_bo); + + amdgpu_ring_fini(&adev->vce.ring[0]); + amdgpu_ring_fini(&adev->vce.ring[1]); + + release_firmware(adev->vce.fw); + + return 0; +} + +/** + * amdgpu_vce_suspend - unpin VCE fw memory + * + * @adev: amdgpu_device pointer + * + */ +int amdgpu_vce_suspend(struct amdgpu_device *adev) +{ + int i; + + if (adev->vce.vcpu_bo == NULL) + return 0; + + for (i = 0; i < AMDGPU_MAX_VCE_HANDLES; ++i) + if (atomic_read(&adev->vce.handles[i])) + break; + + if (i == AMDGPU_MAX_VCE_HANDLES) + return 0; + + /* TODO: suspending running encoding sessions isn't supported */ + return -EINVAL; +} + +/** + * amdgpu_vce_resume - pin VCE fw memory + * + * @adev: amdgpu_device pointer + * + */ +int amdgpu_vce_resume(struct amdgpu_device *adev) +{ + void *cpu_addr; + const struct common_firmware_header *hdr; + unsigned offset; + int r; + + if (adev->vce.vcpu_bo == NULL) + return -EINVAL; + + r = amdgpu_bo_reserve(adev->vce.vcpu_bo, false); + if (r) { + dev_err(adev->dev, "(%d) failed to reserve VCE bo\n", r); + return r; + } + + r = amdgpu_bo_kmap(adev->vce.vcpu_bo, &cpu_addr); + if (r) { + amdgpu_bo_unreserve(adev->vce.vcpu_bo); + dev_err(adev->dev, "(%d) VCE map failed\n", r); + return r; + } + + hdr = (const struct common_firmware_header *)adev->vce.fw->data; + offset = le32_to_cpu(hdr->ucode_array_offset_bytes); + memcpy(cpu_addr, (adev->vce.fw->data) + offset, + (adev->vce.fw->size) - offset); + + amdgpu_bo_kunmap(adev->vce.vcpu_bo); + + amdgpu_bo_unreserve(adev->vce.vcpu_bo); + + return 0; +} + +/** + * amdgpu_vce_idle_work_handler - power off VCE + * + * @work: pointer to work structure + * + * power of VCE when it's not used any more + */ +static void amdgpu_vce_idle_work_handler(struct work_struct *work) +{ + struct amdgpu_device *adev = + container_of(work, struct amdgpu_device, vce.idle_work.work); + + if ((amdgpu_fence_count_emitted(&adev->vce.ring[0]) == 0) && + (amdgpu_fence_count_emitted(&adev->vce.ring[1]) == 0)) { + if (adev->pm.dpm_enabled) { + amdgpu_dpm_enable_vce(adev, false); + } else { + amdgpu_asic_set_vce_clocks(adev, 0, 0); + } + } else { + schedule_delayed_work(&adev->vce.idle_work, + msecs_to_jiffies(VCE_IDLE_TIMEOUT_MS)); + } +} + +/** + * amdgpu_vce_note_usage - power up VCE + * + * @adev: amdgpu_device pointer + * + * Make sure VCE is powerd up when we want to use it + */ +static void amdgpu_vce_note_usage(struct amdgpu_device *adev) +{ + bool streams_changed = false; + bool set_clocks = !cancel_delayed_work_sync(&adev->vce.idle_work); + set_clocks &= schedule_delayed_work(&adev->vce.idle_work, + msecs_to_jiffies(VCE_IDLE_TIMEOUT_MS)); + + if (adev->pm.dpm_enabled) { + /* XXX figure out if the streams changed */ + streams_changed = false; + } + + if (set_clocks || streams_changed) { + if (adev->pm.dpm_enabled) { + amdgpu_dpm_enable_vce(adev, true); + } else { + amdgpu_asic_set_vce_clocks(adev, 53300, 40000); + } + } +} + +/** + * amdgpu_vce_free_handles - free still open VCE handles + * + * @adev: amdgpu_device pointer + * @filp: drm file pointer + * + * Close all VCE handles still open by this file pointer + */ +void amdgpu_vce_free_handles(struct amdgpu_device *adev, struct drm_file *filp) +{ + struct amdgpu_ring *ring = &adev->vce.ring[0]; + int i, r; + for (i = 0; i < AMDGPU_MAX_VCE_HANDLES; ++i) { + uint32_t handle = atomic_read(&adev->vce.handles[i]); + if (!handle || adev->vce.filp[i] != filp) + continue; + + amdgpu_vce_note_usage(adev); + + r = amdgpu_vce_get_destroy_msg(ring, handle, NULL); + if (r) + DRM_ERROR("Error destroying VCE handle (%d)!\n", r); + + adev->vce.filp[i] = NULL; + atomic_set(&adev->vce.handles[i], 0); + } +} + +/** + * amdgpu_vce_get_create_msg - generate a VCE create msg + * + * @adev: amdgpu_device pointer + * @ring: ring we should submit the msg to + * @handle: VCE session handle to use + * @fence: optional fence to return + * + * Open up a stream for HW test + */ +int amdgpu_vce_get_create_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence) +{ + const unsigned ib_size_dw = 1024; + struct amdgpu_ib ib; + uint64_t dummy; + int i, r; + + r = amdgpu_ib_get(ring, NULL, ib_size_dw * 4, &ib); + if (r) { + DRM_ERROR("amdgpu: failed to get ib (%d).\n", r); + return r; + } + + dummy = ib.gpu_addr + 1024; + + /* stitch together an VCE create msg */ + ib.length_dw = 0; + ib.ptr[ib.length_dw++] = 0x0000000c; /* len */ + ib.ptr[ib.length_dw++] = 0x00000001; /* session cmd */ + ib.ptr[ib.length_dw++] = handle; + + ib.ptr[ib.length_dw++] = 0x00000030; /* len */ + ib.ptr[ib.length_dw++] = 0x01000001; /* create cmd */ + ib.ptr[ib.length_dw++] = 0x00000000; + ib.ptr[ib.length_dw++] = 0x00000042; + ib.ptr[ib.length_dw++] = 0x0000000a; + ib.ptr[ib.length_dw++] = 0x00000001; + ib.ptr[ib.length_dw++] = 0x00000080; + ib.ptr[ib.length_dw++] = 0x00000060; + ib.ptr[ib.length_dw++] = 0x00000100; + ib.ptr[ib.length_dw++] = 0x00000100; + ib.ptr[ib.length_dw++] = 0x0000000c; + ib.ptr[ib.length_dw++] = 0x00000000; + + ib.ptr[ib.length_dw++] = 0x00000014; /* len */ + ib.ptr[ib.length_dw++] = 0x05000005; /* feedback buffer */ + ib.ptr[ib.length_dw++] = upper_32_bits(dummy); + ib.ptr[ib.length_dw++] = dummy; + ib.ptr[ib.length_dw++] = 0x00000001; + + for (i = ib.length_dw; i < ib_size_dw; ++i) + ib.ptr[i] = 0x0; + + r = amdgpu_ib_schedule(ring->adev, 1, &ib, AMDGPU_FENCE_OWNER_UNDEFINED); + if (r) { + DRM_ERROR("amdgpu: failed to schedule ib (%d).\n", r); + } + + if (fence) + *fence = amdgpu_fence_ref(ib.fence); + + amdgpu_ib_free(ring->adev, &ib); + + return r; +} + +/** + * amdgpu_vce_get_destroy_msg - generate a VCE destroy msg + * + * @adev: amdgpu_device pointer + * @ring: ring we should submit the msg to + * @handle: VCE session handle to use + * @fence: optional fence to return + * + * Close up a stream for HW test or if userspace failed to do so + */ +int amdgpu_vce_get_destroy_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence) +{ + const unsigned ib_size_dw = 1024; + struct amdgpu_ib ib; + uint64_t dummy; + int i, r; + + r = amdgpu_ib_get(ring, NULL, ib_size_dw * 4, &ib); + if (r) { + DRM_ERROR("amdgpu: failed to get ib (%d).\n", r); + return r; + } + + dummy = ib.gpu_addr + 1024; + + /* stitch together an VCE destroy msg */ + ib.length_dw = 0; + ib.ptr[ib.length_dw++] = 0x0000000c; /* len */ + ib.ptr[ib.length_dw++] = 0x00000001; /* session cmd */ + ib.ptr[ib.length_dw++] = handle; + + ib.ptr[ib.length_dw++] = 0x00000014; /* len */ + ib.ptr[ib.length_dw++] = 0x05000005; /* feedback buffer */ + ib.ptr[ib.length_dw++] = upper_32_bits(dummy); + ib.ptr[ib.length_dw++] = dummy; + ib.ptr[ib.length_dw++] = 0x00000001; + + ib.ptr[ib.length_dw++] = 0x00000008; /* len */ + ib.ptr[ib.length_dw++] = 0x02000001; /* destroy cmd */ + + for (i = ib.length_dw; i < ib_size_dw; ++i) + ib.ptr[i] = 0x0; + + r = amdgpu_ib_schedule(ring->adev, 1, &ib, AMDGPU_FENCE_OWNER_UNDEFINED); + if (r) { + DRM_ERROR("amdgpu: failed to schedule ib (%d).\n", r); + } + + if (fence) + *fence = amdgpu_fence_ref(ib.fence); + + amdgpu_ib_free(ring->adev, &ib); + + return r; +} + +/** + * amdgpu_vce_cs_reloc - command submission relocation + * + * @p: parser context + * @lo: address of lower dword + * @hi: address of higher dword + * + * Patch relocation inside command stream with real buffer address + */ +int amdgpu_vce_cs_reloc(struct amdgpu_cs_parser *p, uint32_t ib_idx, int lo, int hi) +{ + struct amdgpu_bo_va_mapping *mapping; + struct amdgpu_ib *ib = &p->ibs[ib_idx]; + struct amdgpu_bo *bo; + uint64_t addr; + + addr = ((uint64_t)amdgpu_get_ib_value(p, ib_idx, lo)) | + ((uint64_t)amdgpu_get_ib_value(p, ib_idx, hi)) << 32; + + mapping = amdgpu_cs_find_mapping(p, addr, &bo); + if (mapping == NULL) { + DRM_ERROR("Can't find BO for addr 0x%010Lx %d %d\n", + addr, lo, hi); + return -EINVAL; + } + + addr -= ((uint64_t)mapping->it.start) * AMDGPU_GPU_PAGE_SIZE; + addr += amdgpu_bo_gpu_offset(bo); + + ib->ptr[lo] = addr & 0xFFFFFFFF; + ib->ptr[hi] = addr >> 32; + + return 0; +} + +/** + * amdgpu_vce_cs_parse - parse and validate the command stream + * + * @p: parser context + * + */ +int amdgpu_vce_ring_parse_cs(struct amdgpu_cs_parser *p, uint32_t ib_idx) +{ + uint32_t handle = 0; + bool destroy = false; + int i, r, idx = 0; + struct amdgpu_ib *ib = &p->ibs[ib_idx]; + + amdgpu_vce_note_usage(p->adev); + + while (idx < ib->length_dw) { + uint32_t len = amdgpu_get_ib_value(p, ib_idx, idx); + uint32_t cmd = amdgpu_get_ib_value(p, ib_idx, idx + 1); + + if ((len < 8) || (len & 3)) { + DRM_ERROR("invalid VCE command length (%d)!\n", len); + return -EINVAL; + } + + switch (cmd) { + case 0x00000001: // session + handle = amdgpu_get_ib_value(p, ib_idx, idx + 2); + break; + + case 0x00000002: // task info + case 0x01000001: // create + case 0x04000001: // config extension + case 0x04000002: // pic control + case 0x04000005: // rate control + case 0x04000007: // motion estimation + case 0x04000008: // rdo + case 0x04000009: // vui + case 0x05000002: // auxiliary buffer + break; + + case 0x03000001: // encode + r = amdgpu_vce_cs_reloc(p, ib_idx, idx + 10, idx + 9); + if (r) + return r; + + r = amdgpu_vce_cs_reloc(p, ib_idx, idx + 12, idx + 11); + if (r) + return r; + break; + + case 0x02000001: // destroy + destroy = true; + break; + + case 0x05000001: // context buffer + case 0x05000004: // video bitstream buffer + case 0x05000005: // feedback buffer + r = amdgpu_vce_cs_reloc(p, ib_idx, idx + 3, idx + 2); + if (r) + return r; + break; + + default: + DRM_ERROR("invalid VCE command (0x%x)!\n", cmd); + return -EINVAL; + } + + idx += len / 4; + } + + if (destroy) { + /* IB contains a destroy msg, free the handle */ + for (i = 0; i < AMDGPU_MAX_VCE_HANDLES; ++i) + atomic_cmpxchg(&p->adev->vce.handles[i], handle, 0); + + return 0; + } + + /* create or encode, validate the handle */ + for (i = 0; i < AMDGPU_MAX_VCE_HANDLES; ++i) { + if (atomic_read(&p->adev->vce.handles[i]) == handle) + return 0; + } + + /* handle not found try to alloc a new one */ + for (i = 0; i < AMDGPU_MAX_VCE_HANDLES; ++i) { + if (!atomic_cmpxchg(&p->adev->vce.handles[i], 0, handle)) { + p->adev->vce.filp[i] = p->filp; + return 0; + } + } + + DRM_ERROR("No more free VCE handles!\n"); + + return -EINVAL; +} + +/** + * amdgpu_vce_ring_emit_semaphore - emit a semaphore command + * + * @ring: engine to use + * @semaphore: address of semaphore + * @emit_wait: true=emit wait, false=emit signal + * + */ +bool amdgpu_vce_ring_emit_semaphore(struct amdgpu_ring *ring, + struct amdgpu_semaphore *semaphore, + bool emit_wait) +{ + uint64_t addr = semaphore->gpu_addr; + + amdgpu_ring_write(ring, VCE_CMD_SEMAPHORE); + amdgpu_ring_write(ring, (addr >> 3) & 0x000FFFFF); + amdgpu_ring_write(ring, (addr >> 23) & 0x000FFFFF); + amdgpu_ring_write(ring, 0x01003000 | (emit_wait ? 1 : 0)); + if (!emit_wait) + amdgpu_ring_write(ring, VCE_CMD_END); + + return true; +} + +/** + * amdgpu_vce_ring_emit_ib - execute indirect buffer + * + * @ring: engine to use + * @ib: the IB to execute + * + */ +void amdgpu_vce_ring_emit_ib(struct amdgpu_ring *ring, struct amdgpu_ib *ib) +{ + amdgpu_ring_write(ring, VCE_CMD_IB); + amdgpu_ring_write(ring, lower_32_bits(ib->gpu_addr)); + amdgpu_ring_write(ring, upper_32_bits(ib->gpu_addr)); + amdgpu_ring_write(ring, ib->length_dw); +} + +/** + * amdgpu_vce_ring_emit_fence - add a fence command to the ring + * + * @ring: engine to use + * @fence: the fence + * + */ +void amdgpu_vce_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 seq, + bool write64bits) +{ + WARN_ON(write64bits); + + amdgpu_ring_write(ring, VCE_CMD_FENCE); + amdgpu_ring_write(ring, addr); + amdgpu_ring_write(ring, upper_32_bits(addr)); + amdgpu_ring_write(ring, seq); + amdgpu_ring_write(ring, VCE_CMD_TRAP); + amdgpu_ring_write(ring, VCE_CMD_END); +} + +/** + * amdgpu_vce_ring_test_ring - test if VCE ring is working + * + * @ring: the engine to test on + * + */ +int amdgpu_vce_ring_test_ring(struct amdgpu_ring *ring) +{ + struct amdgpu_device *adev = ring->adev; + uint32_t rptr = amdgpu_ring_get_rptr(ring); + unsigned i; + int r; + + r = amdgpu_ring_lock(ring, 16); + if (r) { + DRM_ERROR("amdgpu: vce failed to lock ring %d (%d).\n", + ring->idx, r); + return r; + } + amdgpu_ring_write(ring, VCE_CMD_END); + amdgpu_ring_unlock_commit(ring); + + for (i = 0; i < adev->usec_timeout; i++) { + if (amdgpu_ring_get_rptr(ring) != rptr) + break; + DRM_UDELAY(1); + } + + if (i < adev->usec_timeout) { + DRM_INFO("ring test on %d succeeded in %d usecs\n", + ring->idx, i); + } else { + DRM_ERROR("amdgpu: ring %d test failed\n", + ring->idx); + r = -ETIMEDOUT; + } + + return r; +} + +/** + * amdgpu_vce_ring_test_ib - test if VCE IBs are working + * + * @ring: the engine to test on + * + */ +int amdgpu_vce_ring_test_ib(struct amdgpu_ring *ring) +{ + struct amdgpu_fence *fence = NULL; + int r; + + r = amdgpu_vce_get_create_msg(ring, 1, NULL); + if (r) { + DRM_ERROR("amdgpu: failed to get create msg (%d).\n", r); + goto error; + } + + r = amdgpu_vce_get_destroy_msg(ring, 1, &fence); + if (r) { + DRM_ERROR("amdgpu: failed to get destroy ib (%d).\n", r); + goto error; + } + + r = amdgpu_fence_wait(fence, false); + if (r) { + DRM_ERROR("amdgpu: fence wait failed (%d).\n", r); + } else { + DRM_INFO("ib test on ring %d succeeded\n", ring->idx); + } +error: + amdgpu_fence_unref(&fence); + return r; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.h new file mode 100644 index 000000000000..b9411e43db25 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vce.h @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __AMDGPU_VCE_H__ +#define __AMDGPU_VCE_H__ + +int amdgpu_vce_sw_init(struct amdgpu_device *adev); +int amdgpu_vce_sw_fini(struct amdgpu_device *adev); +int amdgpu_vce_suspend(struct amdgpu_device *adev); +int amdgpu_vce_resume(struct amdgpu_device *adev); +int amdgpu_vce_get_create_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence); +int amdgpu_vce_get_destroy_msg(struct amdgpu_ring *ring, uint32_t handle, + struct amdgpu_fence **fence); +void amdgpu_vce_free_handles(struct amdgpu_device *adev, struct drm_file *filp); +int amdgpu_vce_cs_reloc(struct amdgpu_cs_parser *p, uint32_t ib_idx, int lo, int hi); +int amdgpu_vce_ring_parse_cs(struct amdgpu_cs_parser *p, uint32_t ib_idx); +bool amdgpu_vce_ring_emit_semaphore(struct amdgpu_ring *ring, + struct amdgpu_semaphore *semaphore, + bool emit_wait); +void amdgpu_vce_ring_emit_ib(struct amdgpu_ring *ring, struct amdgpu_ib *ib); +void amdgpu_vce_ring_emit_fence(struct amdgpu_ring *ring, u64 addr, u64 seq, + bool write64bit); +int amdgpu_vce_ring_test_ring(struct amdgpu_ring *ring); +int amdgpu_vce_ring_test_ib(struct amdgpu_ring *ring); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c new file mode 100644 index 000000000000..1cc01fb409dc --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -0,0 +1,1248 @@ +/* + * Copyright 2008 Advanced Micro Devices, Inc. + * Copyright 2008 Red Hat Inc. + * Copyright 2009 Jerome Glisse. + * + * 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_trace.h" + +/* + * GPUVM + * GPUVM is similar to the legacy gart on older asics, however + * rather than there being a single global gart table + * for the entire GPU, there are multiple VM page tables active + * at any given time. The VM page tables can contain a mix + * vram pages and system memory pages and system memory pages + * can be mapped as snooped (cached system pages) or unsnooped + * (uncached system pages). + * Each VM has an ID associated with it and there is a page table + * associated with each VMID. When execting a command buffer, + * the kernel tells the the ring what VMID to use for that command + * buffer. VMIDs are allocated dynamically as commands are submitted. + * The userspace drivers maintain their own address space and the kernel + * sets up their pages tables accordingly when they submit their + * command buffers and a VMID is assigned. + * Cayman/Trinity support up to 8 active VMs at any given time; + * SI supports 16. + */ + +/** + * amdgpu_vm_num_pde - return the number of page directory entries + * + * @adev: amdgpu_device pointer + * + * Calculate the number of page directory entries (cayman+). + */ +static unsigned amdgpu_vm_num_pdes(struct amdgpu_device *adev) +{ + return adev->vm_manager.max_pfn >> amdgpu_vm_block_size; +} + +/** + * amdgpu_vm_directory_size - returns the size of the page directory in bytes + * + * @adev: amdgpu_device pointer + * + * Calculate the size of the page directory in bytes (cayman+). + */ +static unsigned amdgpu_vm_directory_size(struct amdgpu_device *adev) +{ + return AMDGPU_GPU_PAGE_ALIGN(amdgpu_vm_num_pdes(adev) * 8); +} + +/** + * amdgpu_vm_get_bos - add the vm BOs to a validation list + * + * @vm: vm providing the BOs + * @head: head of validation list + * + * Add the page directory to the list of BOs to + * validate for command submission (cayman+). + */ +struct amdgpu_bo_list_entry *amdgpu_vm_get_bos(struct amdgpu_device *adev, + struct amdgpu_vm *vm, + struct list_head *head) +{ + struct amdgpu_bo_list_entry *list; + unsigned i, idx; + + list = drm_malloc_ab(vm->max_pde_used + 2, + sizeof(struct amdgpu_bo_list_entry)); + if (!list) + return NULL; + + /* add the vm page table to the list */ + list[0].robj = vm->page_directory; + list[0].prefered_domains = AMDGPU_GEM_DOMAIN_VRAM; + list[0].allowed_domains = AMDGPU_GEM_DOMAIN_VRAM; + list[0].priority = 0; + list[0].tv.bo = &vm->page_directory->tbo; + list[0].tv.shared = true; + list_add(&list[0].tv.head, head); + + for (i = 0, idx = 1; i <= vm->max_pde_used; i++) { + if (!vm->page_tables[i].bo) + continue; + + list[idx].robj = vm->page_tables[i].bo; + list[idx].prefered_domains = AMDGPU_GEM_DOMAIN_VRAM; + list[idx].allowed_domains = AMDGPU_GEM_DOMAIN_VRAM; + list[idx].priority = 0; + list[idx].tv.bo = &list[idx].robj->tbo; + list[idx].tv.shared = true; + list_add(&list[idx++].tv.head, head); + } + + return list; +} + +/** + * amdgpu_vm_grab_id - allocate the next free VMID + * + * @ring: ring we want to submit job to + * @vm: vm to allocate id for + * + * Allocate an id for the vm (cayman+). + * Returns the fence we need to sync to (if any). + * + * Global and local mutex must be locked! + */ +struct amdgpu_fence *amdgpu_vm_grab_id(struct amdgpu_ring *ring, + struct amdgpu_vm *vm) +{ + struct amdgpu_fence *best[AMDGPU_MAX_RINGS] = {}; + struct amdgpu_vm_id *vm_id = &vm->ids[ring->idx]; + struct amdgpu_device *adev = ring->adev; + + unsigned choices[2] = {}; + unsigned i; + + /* check if the id is still valid */ + if (vm_id->id && vm_id->last_id_use && + vm_id->last_id_use == adev->vm_manager.active[vm_id->id]) + return NULL; + + /* we definately need to flush */ + vm_id->pd_gpu_addr = ~0ll; + + /* skip over VMID 0, since it is the system VM */ + for (i = 1; i < adev->vm_manager.nvm; ++i) { + struct amdgpu_fence *fence = adev->vm_manager.active[i]; + + if (fence == NULL) { + /* found a free one */ + vm_id->id = i; + trace_amdgpu_vm_grab_id(i, ring->idx); + return NULL; + } + + if (amdgpu_fence_is_earlier(fence, best[fence->ring->idx])) { + best[fence->ring->idx] = fence; + choices[fence->ring == ring ? 0 : 1] = i; + } + } + + for (i = 0; i < 2; ++i) { + if (choices[i]) { + vm_id->id = choices[i]; + trace_amdgpu_vm_grab_id(choices[i], ring->idx); + return adev->vm_manager.active[choices[i]]; + } + } + + /* should never happen */ + BUG(); + return NULL; +} + +/** + * amdgpu_vm_flush - hardware flush the vm + * + * @ring: ring to use for flush + * @vm: vm we want to flush + * @updates: last vm update that we waited for + * + * Flush the vm (cayman+). + * + * Global and local mutex must be locked! + */ +void amdgpu_vm_flush(struct amdgpu_ring *ring, + struct amdgpu_vm *vm, + struct amdgpu_fence *updates) +{ + uint64_t pd_addr = amdgpu_bo_gpu_offset(vm->page_directory); + struct amdgpu_vm_id *vm_id = &vm->ids[ring->idx]; + + if (pd_addr != vm_id->pd_gpu_addr || !vm_id->flushed_updates || + amdgpu_fence_is_earlier(vm_id->flushed_updates, updates)) { + + trace_amdgpu_vm_flush(pd_addr, ring->idx, vm_id->id); + amdgpu_fence_unref(&vm_id->flushed_updates); + vm_id->flushed_updates = amdgpu_fence_ref(updates); + vm_id->pd_gpu_addr = pd_addr; + amdgpu_ring_emit_vm_flush(ring, vm_id->id, vm_id->pd_gpu_addr); + } +} + +/** + * amdgpu_vm_fence - remember fence for vm + * + * @adev: amdgpu_device pointer + * @vm: vm we want to fence + * @fence: fence to remember + * + * Fence the vm (cayman+). + * Set the fence used to protect page table and id. + * + * Global and local mutex must be locked! + */ +void amdgpu_vm_fence(struct amdgpu_device *adev, + struct amdgpu_vm *vm, + struct amdgpu_fence *fence) +{ + unsigned ridx = fence->ring->idx; + unsigned vm_id = vm->ids[ridx].id; + + amdgpu_fence_unref(&adev->vm_manager.active[vm_id]); + adev->vm_manager.active[vm_id] = amdgpu_fence_ref(fence); + + amdgpu_fence_unref(&vm->ids[ridx].last_id_use); + vm->ids[ridx].last_id_use = amdgpu_fence_ref(fence); +} + +/** + * amdgpu_vm_bo_find - find the bo_va for a specific vm & bo + * + * @vm: requested vm + * @bo: requested buffer object + * + * Find @bo inside the requested vm (cayman+). + * Search inside the @bos vm list for the requested vm + * Returns the found bo_va or NULL if none is found + * + * Object has to be reserved! + */ +struct amdgpu_bo_va *amdgpu_vm_bo_find(struct amdgpu_vm *vm, + struct amdgpu_bo *bo) +{ + struct amdgpu_bo_va *bo_va; + + list_for_each_entry(bo_va, &bo->va, bo_list) { + if (bo_va->vm == vm) { + return bo_va; + } + } + return NULL; +} + +/** + * amdgpu_vm_update_pages - helper to call the right asic function + * + * @adev: amdgpu_device pointer + * @ib: indirect buffer to fill with commands + * @pe: addr of the page entry + * @addr: dst addr to write into pe + * @count: number of page entries to update + * @incr: increase next addr by incr bytes + * @flags: hw access flags + * @gtt_flags: GTT hw access flags + * + * Traces the parameters and calls the right asic functions + * to setup the page table using the DMA. + */ +static void amdgpu_vm_update_pages(struct amdgpu_device *adev, + struct amdgpu_ib *ib, + uint64_t pe, uint64_t addr, + unsigned count, uint32_t incr, + uint32_t flags, uint32_t gtt_flags) +{ + trace_amdgpu_vm_set_page(pe, addr, count, incr, flags); + + if ((flags & AMDGPU_PTE_SYSTEM) && (flags == gtt_flags)) { + uint64_t src = adev->gart.table_addr + (addr >> 12) * 8; + amdgpu_vm_copy_pte(adev, ib, pe, src, count); + + } else if ((flags & AMDGPU_PTE_SYSTEM) || (count < 3)) { + amdgpu_vm_write_pte(adev, ib, pe, addr, + count, incr, flags); + + } else { + amdgpu_vm_set_pte_pde(adev, ib, pe, addr, + count, incr, flags); + } +} + +/** + * amdgpu_vm_clear_bo - initially clear the page dir/table + * + * @adev: amdgpu_device pointer + * @bo: bo to clear + */ +static int amdgpu_vm_clear_bo(struct amdgpu_device *adev, + struct amdgpu_bo *bo) +{ + struct amdgpu_ring *ring = adev->vm_manager.vm_pte_funcs_ring; + struct amdgpu_ib ib; + unsigned entries; + uint64_t addr; + int r; + + r = amdgpu_bo_reserve(bo, false); + if (r) + return r; + + r = ttm_bo_validate(&bo->tbo, &bo->placement, true, false); + if (r) + goto error_unreserve; + + addr = amdgpu_bo_gpu_offset(bo); + entries = amdgpu_bo_size(bo) / 8; + + r = amdgpu_ib_get(ring, NULL, entries * 2 + 64, &ib); + if (r) + goto error_unreserve; + + ib.length_dw = 0; + + amdgpu_vm_update_pages(adev, &ib, addr, 0, entries, 0, 0, 0); + amdgpu_vm_pad_ib(adev, &ib); + WARN_ON(ib.length_dw > 64); + + r = amdgpu_ib_schedule(adev, 1, &ib, AMDGPU_FENCE_OWNER_VM); + if (r) + goto error_free; + + amdgpu_bo_fence(bo, ib.fence, false); + +error_free: + amdgpu_ib_free(adev, &ib); + +error_unreserve: + amdgpu_bo_unreserve(bo); + return r; +} + +/** + * amdgpu_vm_map_gart - get the physical address of a gart page + * + * @adev: amdgpu_device pointer + * @addr: the unmapped addr + * + * Look up the physical address of the page that the pte resolves + * to (cayman+). + * Returns the physical address of the page. + */ +uint64_t amdgpu_vm_map_gart(struct amdgpu_device *adev, uint64_t addr) +{ + uint64_t result; + + /* page table offset */ + result = adev->gart.pages_addr[addr >> PAGE_SHIFT]; + + /* in case cpu page size != gpu page size*/ + result |= addr & (~PAGE_MASK); + + return result; +} + +/** + * amdgpu_vm_update_pdes - make sure that page directory is valid + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * @start: start of GPU address range + * @end: end of GPU address range + * + * Allocates new page tables if necessary + * and updates the page directory (cayman+). + * Returns 0 for success, error for failure. + * + * Global and local mutex must be locked! + */ +int amdgpu_vm_update_page_directory(struct amdgpu_device *adev, + struct amdgpu_vm *vm) +{ + struct amdgpu_ring *ring = adev->vm_manager.vm_pte_funcs_ring; + struct amdgpu_bo *pd = vm->page_directory; + uint64_t pd_addr = amdgpu_bo_gpu_offset(pd); + uint32_t incr = AMDGPU_VM_PTE_COUNT * 8; + uint64_t last_pde = ~0, last_pt = ~0; + unsigned count = 0, pt_idx, ndw; + struct amdgpu_ib ib; + int r; + + /* padding, etc. */ + ndw = 64; + + /* assume the worst case */ + ndw += vm->max_pde_used * 6; + + /* update too big for an IB */ + if (ndw > 0xfffff) + return -ENOMEM; + + r = amdgpu_ib_get(ring, NULL, ndw * 4, &ib); + if (r) + return r; + ib.length_dw = 0; + + /* walk over the address space and update the page directory */ + for (pt_idx = 0; pt_idx <= vm->max_pde_used; ++pt_idx) { + struct amdgpu_bo *bo = vm->page_tables[pt_idx].bo; + uint64_t pde, pt; + + if (bo == NULL) + continue; + + pt = amdgpu_bo_gpu_offset(bo); + if (vm->page_tables[pt_idx].addr == pt) + continue; + vm->page_tables[pt_idx].addr = pt; + + pde = pd_addr + pt_idx * 8; + if (((last_pde + 8 * count) != pde) || + ((last_pt + incr * count) != pt)) { + + if (count) { + amdgpu_vm_update_pages(adev, &ib, last_pde, + last_pt, count, incr, + AMDGPU_PTE_VALID, 0); + } + + count = 1; + last_pde = pde; + last_pt = pt; + } else { + ++count; + } + } + + if (count) + amdgpu_vm_update_pages(adev, &ib, last_pde, last_pt, count, + incr, AMDGPU_PTE_VALID, 0); + + if (ib.length_dw != 0) { + amdgpu_vm_pad_ib(adev, &ib); + amdgpu_sync_resv(adev, &ib.sync, pd->tbo.resv, AMDGPU_FENCE_OWNER_VM); + WARN_ON(ib.length_dw > ndw); + r = amdgpu_ib_schedule(adev, 1, &ib, AMDGPU_FENCE_OWNER_VM); + if (r) { + amdgpu_ib_free(adev, &ib); + return r; + } + amdgpu_bo_fence(pd, ib.fence, false); + } + amdgpu_ib_free(adev, &ib); + + return 0; +} + +/** + * amdgpu_vm_frag_ptes - add fragment information to PTEs + * + * @adev: amdgpu_device pointer + * @ib: IB for the update + * @pe_start: first PTE to handle + * @pe_end: last PTE to handle + * @addr: addr those PTEs should point to + * @flags: hw mapping flags + * @gtt_flags: GTT hw mapping flags + * + * Global and local mutex must be locked! + */ +static void amdgpu_vm_frag_ptes(struct amdgpu_device *adev, + struct amdgpu_ib *ib, + uint64_t pe_start, uint64_t pe_end, + uint64_t addr, uint32_t flags, + uint32_t gtt_flags) +{ + /** + * The MC L1 TLB supports variable sized pages, based on a fragment + * field in the PTE. When this field is set to a non-zero value, page + * granularity is increased from 4KB to (1 << (12 + frag)). The PTE + * flags are considered valid for all PTEs within the fragment range + * and corresponding mappings are assumed to be physically contiguous. + * + * The L1 TLB can store a single PTE for the whole fragment, + * significantly increasing the space available for translation + * caching. This leads to large improvements in throughput when the + * TLB is under pressure. + * + * The L2 TLB distributes small and large fragments into two + * asymmetric partitions. The large fragment cache is significantly + * larger. Thus, we try to use large fragments wherever possible. + * Userspace can support this by aligning virtual base address and + * allocation size to the fragment size. + */ + + /* SI and newer are optimized for 64KB */ + uint64_t frag_flags = AMDGPU_PTE_FRAG_64KB; + uint64_t frag_align = 0x80; + + uint64_t frag_start = ALIGN(pe_start, frag_align); + uint64_t frag_end = pe_end & ~(frag_align - 1); + + unsigned count; + + /* system pages are non continuously */ + if ((flags & AMDGPU_PTE_SYSTEM) || !(flags & AMDGPU_PTE_VALID) || + (frag_start >= frag_end)) { + + count = (pe_end - pe_start) / 8; + amdgpu_vm_update_pages(adev, ib, pe_start, addr, count, + AMDGPU_GPU_PAGE_SIZE, flags, gtt_flags); + return; + } + + /* handle the 4K area at the beginning */ + if (pe_start != frag_start) { + count = (frag_start - pe_start) / 8; + amdgpu_vm_update_pages(adev, ib, pe_start, addr, count, + AMDGPU_GPU_PAGE_SIZE, flags, gtt_flags); + addr += AMDGPU_GPU_PAGE_SIZE * count; + } + + /* handle the area in the middle */ + count = (frag_end - frag_start) / 8; + amdgpu_vm_update_pages(adev, ib, frag_start, addr, count, + AMDGPU_GPU_PAGE_SIZE, flags | frag_flags, + gtt_flags); + + /* handle the 4K area at the end */ + if (frag_end != pe_end) { + addr += AMDGPU_GPU_PAGE_SIZE * count; + count = (pe_end - frag_end) / 8; + amdgpu_vm_update_pages(adev, ib, frag_end, addr, count, + AMDGPU_GPU_PAGE_SIZE, flags, gtt_flags); + } +} + +/** + * amdgpu_vm_update_ptes - make sure that page tables are valid + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * @start: start of GPU address range + * @end: end of GPU address range + * @dst: destination address to map to + * @flags: mapping flags + * + * Update the page tables in the range @start - @end (cayman+). + * + * Global and local mutex must be locked! + */ +static int amdgpu_vm_update_ptes(struct amdgpu_device *adev, + struct amdgpu_vm *vm, + struct amdgpu_ib *ib, + uint64_t start, uint64_t end, + uint64_t dst, uint32_t flags, + uint32_t gtt_flags) +{ + uint64_t mask = AMDGPU_VM_PTE_COUNT - 1; + uint64_t last_pte = ~0, last_dst = ~0; + unsigned count = 0; + uint64_t addr; + + /* walk over the address space and update the page tables */ + for (addr = start; addr < end; ) { + uint64_t pt_idx = addr >> amdgpu_vm_block_size; + struct amdgpu_bo *pt = vm->page_tables[pt_idx].bo; + unsigned nptes; + uint64_t pte; + int r; + + amdgpu_sync_resv(adev, &ib->sync, pt->tbo.resv, + AMDGPU_FENCE_OWNER_VM); + r = reservation_object_reserve_shared(pt->tbo.resv); + if (r) + return r; + + if ((addr & ~mask) == (end & ~mask)) + nptes = end - addr; + else + nptes = AMDGPU_VM_PTE_COUNT - (addr & mask); + + pte = amdgpu_bo_gpu_offset(pt); + pte += (addr & mask) * 8; + + if ((last_pte + 8 * count) != pte) { + + if (count) { + amdgpu_vm_frag_ptes(adev, ib, last_pte, + last_pte + 8 * count, + last_dst, flags, + gtt_flags); + } + + count = nptes; + last_pte = pte; + last_dst = dst; + } else { + count += nptes; + } + + addr += nptes; + dst += nptes * AMDGPU_GPU_PAGE_SIZE; + } + + if (count) { + amdgpu_vm_frag_ptes(adev, ib, last_pte, + last_pte + 8 * count, + last_dst, flags, gtt_flags); + } + + return 0; +} + +/** + * amdgpu_vm_fence_pts - fence page tables after an update + * + * @vm: requested vm + * @start: start of GPU address range + * @end: end of GPU address range + * @fence: fence to use + * + * Fence the page tables in the range @start - @end (cayman+). + * + * Global and local mutex must be locked! + */ +static void amdgpu_vm_fence_pts(struct amdgpu_vm *vm, + uint64_t start, uint64_t end, + struct amdgpu_fence *fence) +{ + unsigned i; + + start >>= amdgpu_vm_block_size; + end >>= amdgpu_vm_block_size; + + for (i = start; i <= end; ++i) + amdgpu_bo_fence(vm->page_tables[i].bo, fence, true); +} + +/** + * amdgpu_vm_bo_update_mapping - update a mapping in the vm page table + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * @mapping: mapped range and flags to use for the update + * @addr: addr to set the area to + * @gtt_flags: flags as they are used for GTT + * @fence: optional resulting fence + * + * Fill in the page table entries for @mapping. + * Returns 0 for success, -EINVAL for failure. + * + * Object have to be reserved and mutex must be locked! + */ +static int amdgpu_vm_bo_update_mapping(struct amdgpu_device *adev, + struct amdgpu_vm *vm, + struct amdgpu_bo_va_mapping *mapping, + uint64_t addr, uint32_t gtt_flags, + struct amdgpu_fence **fence) +{ + struct amdgpu_ring *ring = adev->vm_manager.vm_pte_funcs_ring; + unsigned nptes, ncmds, ndw; + uint32_t flags = gtt_flags; + struct amdgpu_ib ib; + int r; + + /* normally,bo_va->flags only contians READABLE and WIRTEABLE bit go here + * but in case of something, we filter the flags in first place + */ + if (!(mapping->flags & AMDGPU_PTE_READABLE)) + flags &= ~AMDGPU_PTE_READABLE; + if (!(mapping->flags & AMDGPU_PTE_WRITEABLE)) + flags &= ~AMDGPU_PTE_WRITEABLE; + + trace_amdgpu_vm_bo_update(mapping); + + nptes = mapping->it.last - mapping->it.start + 1; + + /* + * reserve space for one command every (1 << BLOCK_SIZE) + * entries or 2k dwords (whatever is smaller) + */ + ncmds = (nptes >> min(amdgpu_vm_block_size, 11)) + 1; + + /* padding, etc. */ + ndw = 64; + + if ((flags & AMDGPU_PTE_SYSTEM) && (flags == gtt_flags)) { + /* only copy commands needed */ + ndw += ncmds * 7; + + } else if (flags & AMDGPU_PTE_SYSTEM) { + /* header for write data commands */ + ndw += ncmds * 4; + + /* body of write data command */ + ndw += nptes * 2; + + } else { + /* set page commands needed */ + ndw += ncmds * 10; + + /* two extra commands for begin/end of fragment */ + ndw += 2 * 10; + } + + /* update too big for an IB */ + if (ndw > 0xfffff) + return -ENOMEM; + + r = amdgpu_ib_get(ring, NULL, ndw * 4, &ib); + if (r) + return r; + ib.length_dw = 0; + + if (!(flags & AMDGPU_PTE_VALID)) { + unsigned i; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_fence *f = vm->ids[i].last_id_use; + amdgpu_sync_fence(&ib.sync, f); + } + } + + r = amdgpu_vm_update_ptes(adev, vm, &ib, mapping->it.start, + mapping->it.last + 1, addr + mapping->offset, + flags, gtt_flags); + + if (r) { + amdgpu_ib_free(adev, &ib); + return r; + } + + amdgpu_vm_pad_ib(adev, &ib); + WARN_ON(ib.length_dw > ndw); + + r = amdgpu_ib_schedule(adev, 1, &ib, AMDGPU_FENCE_OWNER_VM); + if (r) { + amdgpu_ib_free(adev, &ib); + return r; + } + amdgpu_vm_fence_pts(vm, mapping->it.start, + mapping->it.last + 1, ib.fence); + if (fence) { + amdgpu_fence_unref(fence); + *fence = amdgpu_fence_ref(ib.fence); + } + amdgpu_ib_free(adev, &ib); + + return 0; +} + +/** + * amdgpu_vm_bo_update - update all BO mappings in the vm page table + * + * @adev: amdgpu_device pointer + * @bo_va: requested BO and VM object + * @mem: ttm mem + * + * Fill in the page table entries for @bo_va. + * Returns 0 for success, -EINVAL for failure. + * + * Object have to be reserved and mutex must be locked! + */ +int amdgpu_vm_bo_update(struct amdgpu_device *adev, + struct amdgpu_bo_va *bo_va, + struct ttm_mem_reg *mem) +{ + struct amdgpu_vm *vm = bo_va->vm; + struct amdgpu_bo_va_mapping *mapping; + uint32_t flags; + uint64_t addr; + int r; + + if (mem) { + addr = mem->start << PAGE_SHIFT; + if (mem->mem_type != TTM_PL_TT) + addr += adev->vm_manager.vram_base_offset; + } else { + addr = 0; + } + + if (addr == bo_va->addr) + return 0; + + flags = amdgpu_ttm_tt_pte_flags(adev, bo_va->bo->tbo.ttm, mem); + + list_for_each_entry(mapping, &bo_va->mappings, list) { + r = amdgpu_vm_bo_update_mapping(adev, vm, mapping, addr, + flags, &bo_va->last_pt_update); + if (r) + return r; + } + + bo_va->addr = addr; + spin_lock(&vm->status_lock); + list_del_init(&bo_va->vm_status); + spin_unlock(&vm->status_lock); + + return 0; +} + +/** + * amdgpu_vm_clear_freed - clear freed BOs in the PT + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * + * Make sure all freed BOs are cleared in the PT. + * Returns 0 for success. + * + * PTs have to be reserved and mutex must be locked! + */ +int amdgpu_vm_clear_freed(struct amdgpu_device *adev, + struct amdgpu_vm *vm) +{ + struct amdgpu_bo_va_mapping *mapping; + int r; + + while (!list_empty(&vm->freed)) { + mapping = list_first_entry(&vm->freed, + struct amdgpu_bo_va_mapping, list); + list_del(&mapping->list); + + r = amdgpu_vm_bo_update_mapping(adev, vm, mapping, 0, 0, NULL); + kfree(mapping); + if (r) + return r; + + } + return 0; + +} + +/** + * amdgpu_vm_clear_invalids - clear invalidated BOs in the PT + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * + * Make sure all invalidated BOs are cleared in the PT. + * Returns 0 for success. + * + * PTs have to be reserved and mutex must be locked! + */ +int amdgpu_vm_clear_invalids(struct amdgpu_device *adev, + struct amdgpu_vm *vm) +{ + struct amdgpu_bo_va *bo_va; + int r; + + spin_lock(&vm->status_lock); + while (!list_empty(&vm->invalidated)) { + bo_va = list_first_entry(&vm->invalidated, + struct amdgpu_bo_va, vm_status); + spin_unlock(&vm->status_lock); + + r = amdgpu_vm_bo_update(adev, bo_va, NULL); + if (r) + return r; + + spin_lock(&vm->status_lock); + } + spin_unlock(&vm->status_lock); + + return 0; +} + +/** + * amdgpu_vm_bo_add - add a bo to a specific vm + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * @bo: amdgpu buffer object + * + * Add @bo into the requested vm (cayman+). + * Add @bo to the list of bos associated with the vm + * Returns newly added bo_va or NULL for failure + * + * Object has to be reserved! + */ +struct amdgpu_bo_va *amdgpu_vm_bo_add(struct amdgpu_device *adev, + struct amdgpu_vm *vm, + struct amdgpu_bo *bo) +{ + struct amdgpu_bo_va *bo_va; + + bo_va = kzalloc(sizeof(struct amdgpu_bo_va), GFP_KERNEL); + if (bo_va == NULL) { + return NULL; + } + bo_va->vm = vm; + bo_va->bo = bo; + bo_va->addr = 0; + bo_va->ref_count = 1; + INIT_LIST_HEAD(&bo_va->bo_list); + INIT_LIST_HEAD(&bo_va->mappings); + INIT_LIST_HEAD(&bo_va->vm_status); + + mutex_lock(&vm->mutex); + list_add_tail(&bo_va->bo_list, &bo->va); + mutex_unlock(&vm->mutex); + + return bo_va; +} + +/** + * amdgpu_vm_bo_map - map bo inside a vm + * + * @adev: amdgpu_device pointer + * @bo_va: bo_va to store the address + * @saddr: where to map the BO + * @offset: requested offset in the BO + * @flags: attributes of pages (read/write/valid/etc.) + * + * Add a mapping of the BO at the specefied addr into the VM. + * Returns 0 for success, error for failure. + * + * Object has to be reserved and gets unreserved by this function! + */ +int amdgpu_vm_bo_map(struct amdgpu_device *adev, + struct amdgpu_bo_va *bo_va, + uint64_t saddr, uint64_t offset, + uint64_t size, uint32_t flags) +{ + struct amdgpu_bo_va_mapping *mapping; + struct amdgpu_vm *vm = bo_va->vm; + struct interval_tree_node *it; + unsigned last_pfn, pt_idx; + uint64_t eaddr; + int r; + + /* make sure object fit at this offset */ + eaddr = saddr + size; + if ((saddr >= eaddr) || (offset + size > amdgpu_bo_size(bo_va->bo))) { + amdgpu_bo_unreserve(bo_va->bo); + return -EINVAL; + } + + last_pfn = eaddr / AMDGPU_GPU_PAGE_SIZE; + if (last_pfn > adev->vm_manager.max_pfn) { + dev_err(adev->dev, "va above limit (0x%08X > 0x%08X)\n", + last_pfn, adev->vm_manager.max_pfn); + amdgpu_bo_unreserve(bo_va->bo); + return -EINVAL; + } + + mutex_lock(&vm->mutex); + + saddr /= AMDGPU_GPU_PAGE_SIZE; + eaddr /= AMDGPU_GPU_PAGE_SIZE; + + it = interval_tree_iter_first(&vm->va, saddr, eaddr - 1); + if (it) { + struct amdgpu_bo_va_mapping *tmp; + tmp = container_of(it, struct amdgpu_bo_va_mapping, it); + /* bo and tmp overlap, invalid addr */ + dev_err(adev->dev, "bo %p va 0x%010Lx-0x%010Lx conflict with " + "0x%010lx-0x%010lx\n", bo_va->bo, saddr, eaddr, + tmp->it.start, tmp->it.last + 1); + amdgpu_bo_unreserve(bo_va->bo); + r = -EINVAL; + goto error_unlock; + } + + mapping = kmalloc(sizeof(*mapping), GFP_KERNEL); + if (!mapping) { + amdgpu_bo_unreserve(bo_va->bo); + r = -ENOMEM; + goto error_unlock; + } + + INIT_LIST_HEAD(&mapping->list); + mapping->it.start = saddr; + mapping->it.last = eaddr - 1; + mapping->offset = offset; + mapping->flags = flags; + + list_add(&mapping->list, &bo_va->mappings); + interval_tree_insert(&mapping->it, &vm->va); + + /* Make sure the page tables are allocated */ + saddr >>= amdgpu_vm_block_size; + eaddr >>= amdgpu_vm_block_size; + + BUG_ON(eaddr >= amdgpu_vm_num_pdes(adev)); + + if (eaddr > vm->max_pde_used) + vm->max_pde_used = eaddr; + + amdgpu_bo_unreserve(bo_va->bo); + + /* walk over the address space and allocate the page tables */ + for (pt_idx = saddr; pt_idx <= eaddr; ++pt_idx) { + struct amdgpu_bo *pt; + + if (vm->page_tables[pt_idx].bo) + continue; + + /* drop mutex to allocate and clear page table */ + mutex_unlock(&vm->mutex); + + r = amdgpu_bo_create(adev, AMDGPU_VM_PTE_COUNT * 8, + AMDGPU_GPU_PAGE_SIZE, true, + AMDGPU_GEM_DOMAIN_VRAM, 0, NULL, &pt); + if (r) + goto error_free; + + r = amdgpu_vm_clear_bo(adev, pt); + if (r) { + amdgpu_bo_unref(&pt); + goto error_free; + } + + /* aquire mutex again */ + mutex_lock(&vm->mutex); + if (vm->page_tables[pt_idx].bo) { + /* someone else allocated the pt in the meantime */ + mutex_unlock(&vm->mutex); + amdgpu_bo_unref(&pt); + mutex_lock(&vm->mutex); + continue; + } + + vm->page_tables[pt_idx].addr = 0; + vm->page_tables[pt_idx].bo = pt; + } + + mutex_unlock(&vm->mutex); + return 0; + +error_free: + mutex_lock(&vm->mutex); + list_del(&mapping->list); + interval_tree_remove(&mapping->it, &vm->va); + kfree(mapping); + +error_unlock: + mutex_unlock(&vm->mutex); + return r; +} + +/** + * amdgpu_vm_bo_unmap - remove bo mapping from vm + * + * @adev: amdgpu_device pointer + * @bo_va: bo_va to remove the address from + * @saddr: where to the BO is mapped + * + * Remove a mapping of the BO at the specefied addr from the VM. + * Returns 0 for success, error for failure. + * + * Object has to be reserved and gets unreserved by this function! + */ +int amdgpu_vm_bo_unmap(struct amdgpu_device *adev, + struct amdgpu_bo_va *bo_va, + uint64_t saddr) +{ + struct amdgpu_bo_va_mapping *mapping; + struct amdgpu_vm *vm = bo_va->vm; + + list_for_each_entry(mapping, &bo_va->mappings, list) { + if (mapping->it.start == saddr) + break; + } + + if (&mapping->list == &bo_va->mappings) { + amdgpu_bo_unreserve(bo_va->bo); + return -ENOENT; + } + + mutex_lock(&vm->mutex); + list_del(&mapping->list); + interval_tree_remove(&mapping->it, &vm->va); + + if (bo_va->addr) { + /* clear the old address */ + list_add(&mapping->list, &vm->freed); + } else { + kfree(mapping); + } + mutex_unlock(&vm->mutex); + amdgpu_bo_unreserve(bo_va->bo); + + return 0; +} + +/** + * amdgpu_vm_bo_rmv - remove a bo to a specific vm + * + * @adev: amdgpu_device pointer + * @bo_va: requested bo_va + * + * Remove @bo_va->bo from the requested vm (cayman+). + * + * Object have to be reserved! + */ +void amdgpu_vm_bo_rmv(struct amdgpu_device *adev, + struct amdgpu_bo_va *bo_va) +{ + struct amdgpu_bo_va_mapping *mapping, *next; + struct amdgpu_vm *vm = bo_va->vm; + + list_del(&bo_va->bo_list); + + mutex_lock(&vm->mutex); + + spin_lock(&vm->status_lock); + list_del(&bo_va->vm_status); + spin_unlock(&vm->status_lock); + + list_for_each_entry_safe(mapping, next, &bo_va->mappings, list) { + list_del(&mapping->list); + interval_tree_remove(&mapping->it, &vm->va); + if (bo_va->addr) + list_add(&mapping->list, &vm->freed); + else + kfree(mapping); + } + amdgpu_fence_unref(&bo_va->last_pt_update); + kfree(bo_va); + + mutex_unlock(&vm->mutex); +} + +/** + * amdgpu_vm_bo_invalidate - mark the bo as invalid + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * @bo: amdgpu buffer object + * + * Mark @bo as invalid (cayman+). + */ +void amdgpu_vm_bo_invalidate(struct amdgpu_device *adev, + struct amdgpu_bo *bo) +{ + struct amdgpu_bo_va *bo_va; + + list_for_each_entry(bo_va, &bo->va, bo_list) { + if (bo_va->addr) { + spin_lock(&bo_va->vm->status_lock); + list_del(&bo_va->vm_status); + list_add(&bo_va->vm_status, &bo_va->vm->invalidated); + spin_unlock(&bo_va->vm->status_lock); + } + } +} + +/** + * amdgpu_vm_init - initialize a vm instance + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * + * Init @vm fields (cayman+). + */ +int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm) +{ + const unsigned align = min(AMDGPU_VM_PTB_ALIGN_SIZE, + AMDGPU_VM_PTE_COUNT * 8); + unsigned pd_size, pd_entries, pts_size; + int i, r; + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + vm->ids[i].id = 0; + vm->ids[i].flushed_updates = NULL; + vm->ids[i].last_id_use = NULL; + } + mutex_init(&vm->mutex); + vm->va = RB_ROOT; + spin_lock_init(&vm->status_lock); + INIT_LIST_HEAD(&vm->invalidated); + INIT_LIST_HEAD(&vm->freed); + + pd_size = amdgpu_vm_directory_size(adev); + pd_entries = amdgpu_vm_num_pdes(adev); + + /* allocate page table array */ + pts_size = pd_entries * sizeof(struct amdgpu_vm_pt); + vm->page_tables = kzalloc(pts_size, GFP_KERNEL); + if (vm->page_tables == NULL) { + DRM_ERROR("Cannot allocate memory for page table array\n"); + return -ENOMEM; + } + + r = amdgpu_bo_create(adev, pd_size, align, true, + AMDGPU_GEM_DOMAIN_VRAM, 0, + NULL, &vm->page_directory); + if (r) + return r; + + r = amdgpu_vm_clear_bo(adev, vm->page_directory); + if (r) { + amdgpu_bo_unref(&vm->page_directory); + vm->page_directory = NULL; + return r; + } + + return 0; +} + +/** + * amdgpu_vm_fini - tear down a vm instance + * + * @adev: amdgpu_device pointer + * @vm: requested vm + * + * Tear down @vm (cayman+). + * Unbind the VM and remove all bos from the vm bo list + */ +void amdgpu_vm_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm) +{ + struct amdgpu_bo_va_mapping *mapping, *tmp; + int i; + + if (!RB_EMPTY_ROOT(&vm->va)) { + dev_err(adev->dev, "still active bo inside vm\n"); + } + rbtree_postorder_for_each_entry_safe(mapping, tmp, &vm->va, it.rb) { + list_del(&mapping->list); + interval_tree_remove(&mapping->it, &vm->va); + kfree(mapping); + } + list_for_each_entry_safe(mapping, tmp, &vm->freed, list) { + list_del(&mapping->list); + kfree(mapping); + } + + for (i = 0; i < amdgpu_vm_num_pdes(adev); i++) + amdgpu_bo_unref(&vm->page_tables[i].bo); + kfree(vm->page_tables); + + amdgpu_bo_unref(&vm->page_directory); + + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + amdgpu_fence_unref(&vm->ids[i].flushed_updates); + amdgpu_fence_unref(&vm->ids[i].last_id_use); + } + + mutex_destroy(&vm->mutex); +} diff --git a/drivers/gpu/drm/amd/amdgpu/atom-bits.h b/drivers/gpu/drm/amd/amdgpu/atom-bits.h new file mode 100644 index 000000000000..e8fae5c77514 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atom-bits.h @@ -0,0 +1,48 @@ +/* + * Copyright 2008 Advanced Micro Devices, 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: Stanislaw Skowronek + */ + +#ifndef ATOM_BITS_H +#define ATOM_BITS_H + +static inline uint8_t get_u8(void *bios, int ptr) +{ + return ((unsigned char *)bios)[ptr]; +} +#define U8(ptr) get_u8(ctx->ctx->bios, (ptr)) +#define CU8(ptr) get_u8(ctx->bios, (ptr)) +static inline uint16_t get_u16(void *bios, int ptr) +{ + return get_u8(bios ,ptr)|(((uint16_t)get_u8(bios, ptr+1))<<8); +} +#define U16(ptr) get_u16(ctx->ctx->bios, (ptr)) +#define CU16(ptr) get_u16(ctx->bios, (ptr)) +static inline uint32_t get_u32(void *bios, int ptr) +{ + return get_u16(bios, ptr)|(((uint32_t)get_u16(bios, ptr+2))<<16); +} +#define U32(ptr) get_u32(ctx->ctx->bios, (ptr)) +#define CU32(ptr) get_u32(ctx->bios, (ptr)) +#define CSTR(ptr) (((char *)(ctx->bios))+(ptr)) + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/atom-names.h b/drivers/gpu/drm/amd/amdgpu/atom-names.h new file mode 100644 index 000000000000..6f907a5ffa5f --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atom-names.h @@ -0,0 +1,100 @@ +/* + * Copyright 2008 Advanced Micro Devices, 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: Stanislaw Skowronek + */ + +#ifndef ATOM_NAMES_H +#define ATOM_NAMES_H + +#include "atom.h" + +#ifdef ATOM_DEBUG + +#define ATOM_OP_NAMES_CNT 123 +static char *atom_op_names[ATOM_OP_NAMES_CNT] = { +"RESERVED", "MOVE_REG", "MOVE_PS", "MOVE_WS", "MOVE_FB", "MOVE_PLL", +"MOVE_MC", "AND_REG", "AND_PS", "AND_WS", "AND_FB", "AND_PLL", "AND_MC", +"OR_REG", "OR_PS", "OR_WS", "OR_FB", "OR_PLL", "OR_MC", "SHIFT_LEFT_REG", +"SHIFT_LEFT_PS", "SHIFT_LEFT_WS", "SHIFT_LEFT_FB", "SHIFT_LEFT_PLL", +"SHIFT_LEFT_MC", "SHIFT_RIGHT_REG", "SHIFT_RIGHT_PS", "SHIFT_RIGHT_WS", +"SHIFT_RIGHT_FB", "SHIFT_RIGHT_PLL", "SHIFT_RIGHT_MC", "MUL_REG", +"MUL_PS", "MUL_WS", "MUL_FB", "MUL_PLL", "MUL_MC", "DIV_REG", "DIV_PS", +"DIV_WS", "DIV_FB", "DIV_PLL", "DIV_MC", "ADD_REG", "ADD_PS", "ADD_WS", +"ADD_FB", "ADD_PLL", "ADD_MC", "SUB_REG", "SUB_PS", "SUB_WS", "SUB_FB", +"SUB_PLL", "SUB_MC", "SET_ATI_PORT", "SET_PCI_PORT", "SET_SYS_IO_PORT", +"SET_REG_BLOCK", "SET_FB_BASE", "COMPARE_REG", "COMPARE_PS", +"COMPARE_WS", "COMPARE_FB", "COMPARE_PLL", "COMPARE_MC", "SWITCH", +"JUMP", "JUMP_EQUAL", "JUMP_BELOW", "JUMP_ABOVE", "JUMP_BELOW_OR_EQUAL", +"JUMP_ABOVE_OR_EQUAL", "JUMP_NOT_EQUAL", "TEST_REG", "TEST_PS", "TEST_WS", +"TEST_FB", "TEST_PLL", "TEST_MC", "DELAY_MILLISEC", "DELAY_MICROSEC", +"CALL_TABLE", "REPEAT", "CLEAR_REG", "CLEAR_PS", "CLEAR_WS", "CLEAR_FB", +"CLEAR_PLL", "CLEAR_MC", "NOP", "EOT", "MASK_REG", "MASK_PS", "MASK_WS", +"MASK_FB", "MASK_PLL", "MASK_MC", "POST_CARD", "BEEP", "SAVE_REG", +"RESTORE_REG", "SET_DATA_BLOCK", "XOR_REG", "XOR_PS", "XOR_WS", "XOR_FB", +"XOR_PLL", "XOR_MC", "SHL_REG", "SHL_PS", "SHL_WS", "SHL_FB", "SHL_PLL", +"SHL_MC", "SHR_REG", "SHR_PS", "SHR_WS", "SHR_FB", "SHR_PLL", "SHR_MC", +"DEBUG", "CTB_DS", +}; + +#define ATOM_TABLE_NAMES_CNT 74 +static char *atom_table_names[ATOM_TABLE_NAMES_CNT] = { +"ASIC_Init", "GetDisplaySurfaceSize", "ASIC_RegistersInit", +"VRAM_BlockVenderDetection", "SetClocksRatio", "MemoryControllerInit", +"GPIO_PinInit", "MemoryParamAdjust", "DVOEncoderControl", +"GPIOPinControl", "SetEngineClock", "SetMemoryClock", "SetPixelClock", +"DynamicClockGating", "ResetMemoryDLL", "ResetMemoryDevice", +"MemoryPLLInit", "EnableMemorySelfRefresh", "AdjustMemoryController", +"EnableASIC_StaticPwrMgt", "ASIC_StaticPwrMgtStatusChange", +"DAC_LoadDetection", "TMDS2EncoderControl", "LCD1OutputControl", +"DAC1EncoderControl", "DAC2EncoderControl", "DVOOutputControl", +"CV1OutputControl", "SetCRTC_DPM_State", "TVEncoderControl", +"TMDS1EncoderControl", "LVDSEncoderControl", "TV1OutputControl", +"EnableScaler", "BlankCRTC", "EnableCRTC", "GetPixelClock", +"EnableVGA_Render", "EnableVGA_Access", "SetCRTC_Timing", +"SetCRTC_OverScan", "SetCRTC_Replication", "SelectCRTC_Source", +"EnableGraphSurfaces", "UpdateCRTC_DoubleBufferRegisters", +"LUT_AutoFill", "EnableHW_IconCursor", "GetMemoryClock", +"GetEngineClock", "SetCRTC_UsingDTDTiming", "TVBootUpStdPinDetection", +"DFP2OutputControl", "VRAM_BlockDetectionByStrap", "MemoryCleanUp", +"ReadEDIDFromHWAssistedI2C", "WriteOneByteToHWAssistedI2C", +"ReadHWAssistedI2CStatus", "SpeedFanControl", "PowerConnectorDetection", +"MC_Synchronization", "ComputeMemoryEnginePLL", "MemoryRefreshConversion", +"VRAM_GetCurrentInfoBlock", "DynamicMemorySettings", "MemoryTraining", +"EnableLVDS_SS", "DFP1OutputControl", "SetVoltage", "CRT1OutputControl", +"CRT2OutputControl", "SetupHWAssistedI2CStatus", "ClockSource", +"MemoryDeviceInit", "EnableYUV", +}; + +#define ATOM_IO_NAMES_CNT 5 +static char *atom_io_names[ATOM_IO_NAMES_CNT] = { +"MM", "PLL", "MC", "PCIE", "PCIE PORT", +}; + +#else + +#define ATOM_OP_NAMES_CNT 0 +#define ATOM_TABLE_NAMES_CNT 0 +#define ATOM_IO_NAMES_CNT 0 + +#endif + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/atom-types.h b/drivers/gpu/drm/amd/amdgpu/atom-types.h new file mode 100644 index 000000000000..1125b866cdb0 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atom-types.h @@ -0,0 +1,42 @@ +/* + * Copyright 2008 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: Dave Airlie + */ + +#ifndef ATOM_TYPES_H +#define ATOM_TYPES_H + +/* sync atom types to kernel types */ + +typedef uint16_t USHORT; +typedef uint32_t ULONG; +typedef uint8_t UCHAR; + + +#ifndef ATOM_BIG_ENDIAN +#if defined(__BIG_ENDIAN) +#define ATOM_BIG_ENDIAN 1 +#else +#define ATOM_BIG_ENDIAN 0 +#endif +#endif +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/atom.c b/drivers/gpu/drm/amd/amdgpu/atom.c new file mode 100644 index 000000000000..a0346a90d805 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atom.c @@ -0,0 +1,1408 @@ +/* + * Copyright 2008 Advanced Micro Devices, 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: Stanislaw Skowronek + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#define ATOM_DEBUG + +#include "atom.h" +#include "atom-names.h" +#include "atom-bits.h" +#include "amdgpu.h" + +#define ATOM_COND_ABOVE 0 +#define ATOM_COND_ABOVEOREQUAL 1 +#define ATOM_COND_ALWAYS 2 +#define ATOM_COND_BELOW 3 +#define ATOM_COND_BELOWOREQUAL 4 +#define ATOM_COND_EQUAL 5 +#define ATOM_COND_NOTEQUAL 6 + +#define ATOM_PORT_ATI 0 +#define ATOM_PORT_PCI 1 +#define ATOM_PORT_SYSIO 2 + +#define ATOM_UNIT_MICROSEC 0 +#define ATOM_UNIT_MILLISEC 1 + +#define PLL_INDEX 2 +#define PLL_DATA 3 + +typedef struct { + struct atom_context *ctx; + uint32_t *ps, *ws; + int ps_shift; + uint16_t start; + unsigned last_jump; + unsigned long last_jump_jiffies; + bool abort; +} atom_exec_context; + +int amdgpu_atom_debug = 0; +static int amdgpu_atom_execute_table_locked(struct atom_context *ctx, int index, uint32_t * params); +int amdgpu_atom_execute_table(struct atom_context *ctx, int index, uint32_t * params); + +static uint32_t atom_arg_mask[8] = + { 0xFFFFFFFF, 0xFFFF, 0xFFFF00, 0xFFFF0000, 0xFF, 0xFF00, 0xFF0000, +0xFF000000 }; +static int atom_arg_shift[8] = { 0, 0, 8, 16, 0, 8, 16, 24 }; + +static int atom_dst_to_src[8][4] = { + /* translate destination alignment field to the source alignment encoding */ + {0, 0, 0, 0}, + {1, 2, 3, 0}, + {1, 2, 3, 0}, + {1, 2, 3, 0}, + {4, 5, 6, 7}, + {4, 5, 6, 7}, + {4, 5, 6, 7}, + {4, 5, 6, 7}, +}; +static int atom_def_dst[8] = { 0, 0, 1, 2, 0, 1, 2, 3 }; + +static int debug_depth = 0; +#ifdef ATOM_DEBUG +static void debug_print_spaces(int n) +{ + while (n--) + printk(" "); +} + +#define DEBUG(...) do if (amdgpu_atom_debug) { printk(KERN_DEBUG __VA_ARGS__); } while (0) +#define SDEBUG(...) do if (amdgpu_atom_debug) { printk(KERN_DEBUG); debug_print_spaces(debug_depth); printk(__VA_ARGS__); } while (0) +#else +#define DEBUG(...) do { } while (0) +#define SDEBUG(...) do { } while (0) +#endif + +static uint32_t atom_iio_execute(struct atom_context *ctx, int base, + uint32_t index, uint32_t data) +{ + uint32_t temp = 0xCDCDCDCD; + + while (1) + switch (CU8(base)) { + case ATOM_IIO_NOP: + base++; + break; + case ATOM_IIO_READ: + temp = ctx->card->ioreg_read(ctx->card, CU16(base + 1)); + base += 3; + break; + case ATOM_IIO_WRITE: + ctx->card->ioreg_write(ctx->card, CU16(base + 1), temp); + base += 3; + break; + case ATOM_IIO_CLEAR: + temp &= + ~((0xFFFFFFFF >> (32 - CU8(base + 1))) << + CU8(base + 2)); + base += 3; + break; + case ATOM_IIO_SET: + temp |= + (0xFFFFFFFF >> (32 - CU8(base + 1))) << CU8(base + + 2); + base += 3; + break; + case ATOM_IIO_MOVE_INDEX: + temp &= + ~((0xFFFFFFFF >> (32 - CU8(base + 1))) << + CU8(base + 3)); + temp |= + ((index >> CU8(base + 2)) & + (0xFFFFFFFF >> (32 - CU8(base + 1)))) << CU8(base + + 3); + base += 4; + break; + case ATOM_IIO_MOVE_DATA: + temp &= + ~((0xFFFFFFFF >> (32 - CU8(base + 1))) << + CU8(base + 3)); + temp |= + ((data >> CU8(base + 2)) & + (0xFFFFFFFF >> (32 - CU8(base + 1)))) << CU8(base + + 3); + base += 4; + break; + case ATOM_IIO_MOVE_ATTR: + temp &= + ~((0xFFFFFFFF >> (32 - CU8(base + 1))) << + CU8(base + 3)); + temp |= + ((ctx-> + io_attr >> CU8(base + 2)) & (0xFFFFFFFF >> (32 - + CU8 + (base + + + 1)))) + << CU8(base + 3); + base += 4; + break; + case ATOM_IIO_END: + return temp; + default: + printk(KERN_INFO "Unknown IIO opcode.\n"); + return 0; + } +} + +static uint32_t atom_get_src_int(atom_exec_context *ctx, uint8_t attr, + int *ptr, uint32_t *saved, int print) +{ + uint32_t idx, val = 0xCDCDCDCD, align, arg; + struct atom_context *gctx = ctx->ctx; + arg = attr & 7; + align = (attr >> 3) & 7; + switch (arg) { + case ATOM_ARG_REG: + idx = U16(*ptr); + (*ptr) += 2; + if (print) + DEBUG("REG[0x%04X]", idx); + idx += gctx->reg_block; + switch (gctx->io_mode) { + case ATOM_IO_MM: + val = gctx->card->reg_read(gctx->card, idx); + break; + case ATOM_IO_PCI: + printk(KERN_INFO + "PCI registers are not implemented.\n"); + return 0; + case ATOM_IO_SYSIO: + printk(KERN_INFO + "SYSIO registers are not implemented.\n"); + return 0; + default: + if (!(gctx->io_mode & 0x80)) { + printk(KERN_INFO "Bad IO mode.\n"); + return 0; + } + if (!gctx->iio[gctx->io_mode & 0x7F]) { + printk(KERN_INFO + "Undefined indirect IO read method %d.\n", + gctx->io_mode & 0x7F); + return 0; + } + val = + atom_iio_execute(gctx, + gctx->iio[gctx->io_mode & 0x7F], + idx, 0); + } + break; + case ATOM_ARG_PS: + idx = U8(*ptr); + (*ptr)++; + /* get_unaligned_le32 avoids unaligned accesses from atombios + * tables, noticed on a DEC Alpha. */ + val = get_unaligned_le32((u32 *)&ctx->ps[idx]); + if (print) + DEBUG("PS[0x%02X,0x%04X]", idx, val); + break; + case ATOM_ARG_WS: + idx = U8(*ptr); + (*ptr)++; + if (print) + DEBUG("WS[0x%02X]", idx); + switch (idx) { + case ATOM_WS_QUOTIENT: + val = gctx->divmul[0]; + break; + case ATOM_WS_REMAINDER: + val = gctx->divmul[1]; + break; + case ATOM_WS_DATAPTR: + val = gctx->data_block; + break; + case ATOM_WS_SHIFT: + val = gctx->shift; + break; + case ATOM_WS_OR_MASK: + val = 1 << gctx->shift; + break; + case ATOM_WS_AND_MASK: + val = ~(1 << gctx->shift); + break; + case ATOM_WS_FB_WINDOW: + val = gctx->fb_base; + break; + case ATOM_WS_ATTRIBUTES: + val = gctx->io_attr; + break; + case ATOM_WS_REGPTR: + val = gctx->reg_block; + break; + default: + val = ctx->ws[idx]; + } + break; + case ATOM_ARG_ID: + idx = U16(*ptr); + (*ptr) += 2; + if (print) { + if (gctx->data_block) + DEBUG("ID[0x%04X+%04X]", idx, gctx->data_block); + else + DEBUG("ID[0x%04X]", idx); + } + val = U32(idx + gctx->data_block); + break; + case ATOM_ARG_FB: + idx = U8(*ptr); + (*ptr)++; + if ((gctx->fb_base + (idx * 4)) > gctx->scratch_size_bytes) { + DRM_ERROR("ATOM: fb read beyond scratch region: %d vs. %d\n", + gctx->fb_base + (idx * 4), gctx->scratch_size_bytes); + val = 0; + } else + val = gctx->scratch[(gctx->fb_base / 4) + idx]; + if (print) + DEBUG("FB[0x%02X]", idx); + break; + case ATOM_ARG_IMM: + switch (align) { + case ATOM_SRC_DWORD: + val = U32(*ptr); + (*ptr) += 4; + if (print) + DEBUG("IMM 0x%08X\n", val); + return val; + case ATOM_SRC_WORD0: + case ATOM_SRC_WORD8: + case ATOM_SRC_WORD16: + val = U16(*ptr); + (*ptr) += 2; + if (print) + DEBUG("IMM 0x%04X\n", val); + return val; + case ATOM_SRC_BYTE0: + case ATOM_SRC_BYTE8: + case ATOM_SRC_BYTE16: + case ATOM_SRC_BYTE24: + val = U8(*ptr); + (*ptr)++; + if (print) + DEBUG("IMM 0x%02X\n", val); + return val; + } + return 0; + case ATOM_ARG_PLL: + idx = U8(*ptr); + (*ptr)++; + if (print) + DEBUG("PLL[0x%02X]", idx); + val = gctx->card->pll_read(gctx->card, idx); + break; + case ATOM_ARG_MC: + idx = U8(*ptr); + (*ptr)++; + if (print) + DEBUG("MC[0x%02X]", idx); + val = gctx->card->mc_read(gctx->card, idx); + break; + } + if (saved) + *saved = val; + val &= atom_arg_mask[align]; + val >>= atom_arg_shift[align]; + if (print) + switch (align) { + case ATOM_SRC_DWORD: + DEBUG(".[31:0] -> 0x%08X\n", val); + break; + case ATOM_SRC_WORD0: + DEBUG(".[15:0] -> 0x%04X\n", val); + break; + case ATOM_SRC_WORD8: + DEBUG(".[23:8] -> 0x%04X\n", val); + break; + case ATOM_SRC_WORD16: + DEBUG(".[31:16] -> 0x%04X\n", val); + break; + case ATOM_SRC_BYTE0: + DEBUG(".[7:0] -> 0x%02X\n", val); + break; + case ATOM_SRC_BYTE8: + DEBUG(".[15:8] -> 0x%02X\n", val); + break; + case ATOM_SRC_BYTE16: + DEBUG(".[23:16] -> 0x%02X\n", val); + break; + case ATOM_SRC_BYTE24: + DEBUG(".[31:24] -> 0x%02X\n", val); + break; + } + return val; +} + +static void atom_skip_src_int(atom_exec_context *ctx, uint8_t attr, int *ptr) +{ + uint32_t align = (attr >> 3) & 7, arg = attr & 7; + switch (arg) { + case ATOM_ARG_REG: + case ATOM_ARG_ID: + (*ptr) += 2; + break; + case ATOM_ARG_PLL: + case ATOM_ARG_MC: + case ATOM_ARG_PS: + case ATOM_ARG_WS: + case ATOM_ARG_FB: + (*ptr)++; + break; + case ATOM_ARG_IMM: + switch (align) { + case ATOM_SRC_DWORD: + (*ptr) += 4; + return; + case ATOM_SRC_WORD0: + case ATOM_SRC_WORD8: + case ATOM_SRC_WORD16: + (*ptr) += 2; + return; + case ATOM_SRC_BYTE0: + case ATOM_SRC_BYTE8: + case ATOM_SRC_BYTE16: + case ATOM_SRC_BYTE24: + (*ptr)++; + return; + } + return; + } +} + +static uint32_t atom_get_src(atom_exec_context *ctx, uint8_t attr, int *ptr) +{ + return atom_get_src_int(ctx, attr, ptr, NULL, 1); +} + +static uint32_t atom_get_src_direct(atom_exec_context *ctx, uint8_t align, int *ptr) +{ + uint32_t val = 0xCDCDCDCD; + + switch (align) { + case ATOM_SRC_DWORD: + val = U32(*ptr); + (*ptr) += 4; + break; + case ATOM_SRC_WORD0: + case ATOM_SRC_WORD8: + case ATOM_SRC_WORD16: + val = U16(*ptr); + (*ptr) += 2; + break; + case ATOM_SRC_BYTE0: + case ATOM_SRC_BYTE8: + case ATOM_SRC_BYTE16: + case ATOM_SRC_BYTE24: + val = U8(*ptr); + (*ptr)++; + break; + } + return val; +} + +static uint32_t atom_get_dst(atom_exec_context *ctx, int arg, uint8_t attr, + int *ptr, uint32_t *saved, int print) +{ + return atom_get_src_int(ctx, + arg | atom_dst_to_src[(attr >> 3) & + 7][(attr >> 6) & 3] << 3, + ptr, saved, print); +} + +static void atom_skip_dst(atom_exec_context *ctx, int arg, uint8_t attr, int *ptr) +{ + atom_skip_src_int(ctx, + arg | atom_dst_to_src[(attr >> 3) & 7][(attr >> 6) & + 3] << 3, ptr); +} + +static void atom_put_dst(atom_exec_context *ctx, int arg, uint8_t attr, + int *ptr, uint32_t val, uint32_t saved) +{ + uint32_t align = + atom_dst_to_src[(attr >> 3) & 7][(attr >> 6) & 3], old_val = + val, idx; + struct atom_context *gctx = ctx->ctx; + old_val &= atom_arg_mask[align] >> atom_arg_shift[align]; + val <<= atom_arg_shift[align]; + val &= atom_arg_mask[align]; + saved &= ~atom_arg_mask[align]; + val |= saved; + switch (arg) { + case ATOM_ARG_REG: + idx = U16(*ptr); + (*ptr) += 2; + DEBUG("REG[0x%04X]", idx); + idx += gctx->reg_block; + switch (gctx->io_mode) { + case ATOM_IO_MM: + if (idx == 0) + gctx->card->reg_write(gctx->card, idx, + val << 2); + else + gctx->card->reg_write(gctx->card, idx, val); + break; + case ATOM_IO_PCI: + printk(KERN_INFO + "PCI registers are not implemented.\n"); + return; + case ATOM_IO_SYSIO: + printk(KERN_INFO + "SYSIO registers are not implemented.\n"); + return; + default: + if (!(gctx->io_mode & 0x80)) { + printk(KERN_INFO "Bad IO mode.\n"); + return; + } + if (!gctx->iio[gctx->io_mode & 0xFF]) { + printk(KERN_INFO + "Undefined indirect IO write method %d.\n", + gctx->io_mode & 0x7F); + return; + } + atom_iio_execute(gctx, gctx->iio[gctx->io_mode & 0xFF], + idx, val); + } + break; + case ATOM_ARG_PS: + idx = U8(*ptr); + (*ptr)++; + DEBUG("PS[0x%02X]", idx); + ctx->ps[idx] = cpu_to_le32(val); + break; + case ATOM_ARG_WS: + idx = U8(*ptr); + (*ptr)++; + DEBUG("WS[0x%02X]", idx); + switch (idx) { + case ATOM_WS_QUOTIENT: + gctx->divmul[0] = val; + break; + case ATOM_WS_REMAINDER: + gctx->divmul[1] = val; + break; + case ATOM_WS_DATAPTR: + gctx->data_block = val; + break; + case ATOM_WS_SHIFT: + gctx->shift = val; + break; + case ATOM_WS_OR_MASK: + case ATOM_WS_AND_MASK: + break; + case ATOM_WS_FB_WINDOW: + gctx->fb_base = val; + break; + case ATOM_WS_ATTRIBUTES: + gctx->io_attr = val; + break; + case ATOM_WS_REGPTR: + gctx->reg_block = val; + break; + default: + ctx->ws[idx] = val; + } + break; + case ATOM_ARG_FB: + idx = U8(*ptr); + (*ptr)++; + if ((gctx->fb_base + (idx * 4)) > gctx->scratch_size_bytes) { + DRM_ERROR("ATOM: fb write beyond scratch region: %d vs. %d\n", + gctx->fb_base + (idx * 4), gctx->scratch_size_bytes); + } else + gctx->scratch[(gctx->fb_base / 4) + idx] = val; + DEBUG("FB[0x%02X]", idx); + break; + case ATOM_ARG_PLL: + idx = U8(*ptr); + (*ptr)++; + DEBUG("PLL[0x%02X]", idx); + gctx->card->pll_write(gctx->card, idx, val); + break; + case ATOM_ARG_MC: + idx = U8(*ptr); + (*ptr)++; + DEBUG("MC[0x%02X]", idx); + gctx->card->mc_write(gctx->card, idx, val); + return; + } + switch (align) { + case ATOM_SRC_DWORD: + DEBUG(".[31:0] <- 0x%08X\n", old_val); + break; + case ATOM_SRC_WORD0: + DEBUG(".[15:0] <- 0x%04X\n", old_val); + break; + case ATOM_SRC_WORD8: + DEBUG(".[23:8] <- 0x%04X\n", old_val); + break; + case ATOM_SRC_WORD16: + DEBUG(".[31:16] <- 0x%04X\n", old_val); + break; + case ATOM_SRC_BYTE0: + DEBUG(".[7:0] <- 0x%02X\n", old_val); + break; + case ATOM_SRC_BYTE8: + DEBUG(".[15:8] <- 0x%02X\n", old_val); + break; + case ATOM_SRC_BYTE16: + DEBUG(".[23:16] <- 0x%02X\n", old_val); + break; + case ATOM_SRC_BYTE24: + DEBUG(".[31:24] <- 0x%02X\n", old_val); + break; + } +} + +static void atom_op_add(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src, saved; + int dptr = *ptr; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + SDEBUG(" src: "); + src = atom_get_src(ctx, attr, ptr); + dst += src; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_and(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src, saved; + int dptr = *ptr; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + SDEBUG(" src: "); + src = atom_get_src(ctx, attr, ptr); + dst &= src; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_beep(atom_exec_context *ctx, int *ptr, int arg) +{ + printk("ATOM BIOS beeped!\n"); +} + +static void atom_op_calltable(atom_exec_context *ctx, int *ptr, int arg) +{ + int idx = U8((*ptr)++); + int r = 0; + + if (idx < ATOM_TABLE_NAMES_CNT) + SDEBUG(" table: %d (%s)\n", idx, atom_table_names[idx]); + else + SDEBUG(" table: %d\n", idx); + if (U16(ctx->ctx->cmd_table + 4 + 2 * idx)) + r = amdgpu_atom_execute_table_locked(ctx->ctx, idx, ctx->ps + ctx->ps_shift); + if (r) { + ctx->abort = true; + } +} + +static void atom_op_clear(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t saved; + int dptr = *ptr; + attr &= 0x38; + attr |= atom_def_dst[attr >> 3] << 6; + atom_get_dst(ctx, arg, attr, ptr, &saved, 0); + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, 0, saved); +} + +static void atom_op_compare(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src; + SDEBUG(" src1: "); + dst = atom_get_dst(ctx, arg, attr, ptr, NULL, 1); + SDEBUG(" src2: "); + src = atom_get_src(ctx, attr, ptr); + ctx->ctx->cs_equal = (dst == src); + ctx->ctx->cs_above = (dst > src); + SDEBUG(" result: %s %s\n", ctx->ctx->cs_equal ? "EQ" : "NE", + ctx->ctx->cs_above ? "GT" : "LE"); +} + +static void atom_op_delay(atom_exec_context *ctx, int *ptr, int arg) +{ + unsigned count = U8((*ptr)++); + SDEBUG(" count: %d\n", count); + if (arg == ATOM_UNIT_MICROSEC) + udelay(count); + else if (!drm_can_sleep()) + mdelay(count); + else + msleep(count); +} + +static void atom_op_div(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src; + SDEBUG(" src1: "); + dst = atom_get_dst(ctx, arg, attr, ptr, NULL, 1); + SDEBUG(" src2: "); + src = atom_get_src(ctx, attr, ptr); + if (src != 0) { + ctx->ctx->divmul[0] = dst / src; + ctx->ctx->divmul[1] = dst % src; + } else { + ctx->ctx->divmul[0] = 0; + ctx->ctx->divmul[1] = 0; + } +} + +static void atom_op_eot(atom_exec_context *ctx, int *ptr, int arg) +{ + /* functionally, a nop */ +} + +static void atom_op_jump(atom_exec_context *ctx, int *ptr, int arg) +{ + int execute = 0, target = U16(*ptr); + unsigned long cjiffies; + + (*ptr) += 2; + switch (arg) { + case ATOM_COND_ABOVE: + execute = ctx->ctx->cs_above; + break; + case ATOM_COND_ABOVEOREQUAL: + execute = ctx->ctx->cs_above || ctx->ctx->cs_equal; + break; + case ATOM_COND_ALWAYS: + execute = 1; + break; + case ATOM_COND_BELOW: + execute = !(ctx->ctx->cs_above || ctx->ctx->cs_equal); + break; + case ATOM_COND_BELOWOREQUAL: + execute = !ctx->ctx->cs_above; + break; + case ATOM_COND_EQUAL: + execute = ctx->ctx->cs_equal; + break; + case ATOM_COND_NOTEQUAL: + execute = !ctx->ctx->cs_equal; + break; + } + if (arg != ATOM_COND_ALWAYS) + SDEBUG(" taken: %s\n", execute ? "yes" : "no"); + SDEBUG(" target: 0x%04X\n", target); + if (execute) { + if (ctx->last_jump == (ctx->start + target)) { + cjiffies = jiffies; + if (time_after(cjiffies, ctx->last_jump_jiffies)) { + cjiffies -= ctx->last_jump_jiffies; + if ((jiffies_to_msecs(cjiffies) > 5000)) { + DRM_ERROR("atombios stuck in loop for more than 5secs aborting\n"); + ctx->abort = true; + } + } else { + /* jiffies wrap around we will just wait a little longer */ + ctx->last_jump_jiffies = jiffies; + } + } else { + ctx->last_jump = ctx->start + target; + ctx->last_jump_jiffies = jiffies; + } + *ptr = ctx->start + target; + } +} + +static void atom_op_mask(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, mask, src, saved; + int dptr = *ptr; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + mask = atom_get_src_direct(ctx, ((attr >> 3) & 7), ptr); + SDEBUG(" mask: 0x%08x", mask); + SDEBUG(" src: "); + src = atom_get_src(ctx, attr, ptr); + dst &= mask; + dst |= src; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_move(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t src, saved; + int dptr = *ptr; + if (((attr >> 3) & 7) != ATOM_SRC_DWORD) + atom_get_dst(ctx, arg, attr, ptr, &saved, 0); + else { + atom_skip_dst(ctx, arg, attr, ptr); + saved = 0xCDCDCDCD; + } + SDEBUG(" src: "); + src = atom_get_src(ctx, attr, ptr); + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, src, saved); +} + +static void atom_op_mul(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src; + SDEBUG(" src1: "); + dst = atom_get_dst(ctx, arg, attr, ptr, NULL, 1); + SDEBUG(" src2: "); + src = atom_get_src(ctx, attr, ptr); + ctx->ctx->divmul[0] = dst * src; +} + +static void atom_op_nop(atom_exec_context *ctx, int *ptr, int arg) +{ + /* nothing */ +} + +static void atom_op_or(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src, saved; + int dptr = *ptr; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + SDEBUG(" src: "); + src = atom_get_src(ctx, attr, ptr); + dst |= src; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_postcard(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t val = U8((*ptr)++); + SDEBUG("POST card output: 0x%02X\n", val); +} + +static void atom_op_repeat(atom_exec_context *ctx, int *ptr, int arg) +{ + printk(KERN_INFO "unimplemented!\n"); +} + +static void atom_op_restorereg(atom_exec_context *ctx, int *ptr, int arg) +{ + printk(KERN_INFO "unimplemented!\n"); +} + +static void atom_op_savereg(atom_exec_context *ctx, int *ptr, int arg) +{ + printk(KERN_INFO "unimplemented!\n"); +} + +static void atom_op_setdatablock(atom_exec_context *ctx, int *ptr, int arg) +{ + int idx = U8(*ptr); + (*ptr)++; + SDEBUG(" block: %d\n", idx); + if (!idx) + ctx->ctx->data_block = 0; + else if (idx == 255) + ctx->ctx->data_block = ctx->start; + else + ctx->ctx->data_block = U16(ctx->ctx->data_table + 4 + 2 * idx); + SDEBUG(" base: 0x%04X\n", ctx->ctx->data_block); +} + +static void atom_op_setfbbase(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + SDEBUG(" fb_base: "); + ctx->ctx->fb_base = atom_get_src(ctx, attr, ptr); +} + +static void atom_op_setport(atom_exec_context *ctx, int *ptr, int arg) +{ + int port; + switch (arg) { + case ATOM_PORT_ATI: + port = U16(*ptr); + if (port < ATOM_IO_NAMES_CNT) + SDEBUG(" port: %d (%s)\n", port, atom_io_names[port]); + else + SDEBUG(" port: %d\n", port); + if (!port) + ctx->ctx->io_mode = ATOM_IO_MM; + else + ctx->ctx->io_mode = ATOM_IO_IIO | port; + (*ptr) += 2; + break; + case ATOM_PORT_PCI: + ctx->ctx->io_mode = ATOM_IO_PCI; + (*ptr)++; + break; + case ATOM_PORT_SYSIO: + ctx->ctx->io_mode = ATOM_IO_SYSIO; + (*ptr)++; + break; + } +} + +static void atom_op_setregblock(atom_exec_context *ctx, int *ptr, int arg) +{ + ctx->ctx->reg_block = U16(*ptr); + (*ptr) += 2; + SDEBUG(" base: 0x%04X\n", ctx->ctx->reg_block); +} + +static void atom_op_shift_left(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++), shift; + uint32_t saved, dst; + int dptr = *ptr; + attr &= 0x38; + attr |= atom_def_dst[attr >> 3] << 6; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + shift = atom_get_src_direct(ctx, ATOM_SRC_BYTE0, ptr); + SDEBUG(" shift: %d\n", shift); + dst <<= shift; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_shift_right(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++), shift; + uint32_t saved, dst; + int dptr = *ptr; + attr &= 0x38; + attr |= atom_def_dst[attr >> 3] << 6; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + shift = atom_get_src_direct(ctx, ATOM_SRC_BYTE0, ptr); + SDEBUG(" shift: %d\n", shift); + dst >>= shift; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_shl(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++), shift; + uint32_t saved, dst; + int dptr = *ptr; + uint32_t dst_align = atom_dst_to_src[(attr >> 3) & 7][(attr >> 6) & 3]; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + /* op needs to full dst value */ + dst = saved; + shift = atom_get_src(ctx, attr, ptr); + SDEBUG(" shift: %d\n", shift); + dst <<= shift; + dst &= atom_arg_mask[dst_align]; + dst >>= atom_arg_shift[dst_align]; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_shr(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++), shift; + uint32_t saved, dst; + int dptr = *ptr; + uint32_t dst_align = atom_dst_to_src[(attr >> 3) & 7][(attr >> 6) & 3]; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + /* op needs to full dst value */ + dst = saved; + shift = atom_get_src(ctx, attr, ptr); + SDEBUG(" shift: %d\n", shift); + dst >>= shift; + dst &= atom_arg_mask[dst_align]; + dst >>= atom_arg_shift[dst_align]; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_sub(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src, saved; + int dptr = *ptr; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + SDEBUG(" src: "); + src = atom_get_src(ctx, attr, ptr); + dst -= src; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_switch(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t src, val, target; + SDEBUG(" switch: "); + src = atom_get_src(ctx, attr, ptr); + while (U16(*ptr) != ATOM_CASE_END) + if (U8(*ptr) == ATOM_CASE_MAGIC) { + (*ptr)++; + SDEBUG(" case: "); + val = + atom_get_src(ctx, (attr & 0x38) | ATOM_ARG_IMM, + ptr); + target = U16(*ptr); + if (val == src) { + SDEBUG(" target: %04X\n", target); + *ptr = ctx->start + target; + return; + } + (*ptr) += 2; + } else { + printk(KERN_INFO "Bad case.\n"); + return; + } + (*ptr) += 2; +} + +static void atom_op_test(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src; + SDEBUG(" src1: "); + dst = atom_get_dst(ctx, arg, attr, ptr, NULL, 1); + SDEBUG(" src2: "); + src = atom_get_src(ctx, attr, ptr); + ctx->ctx->cs_equal = ((dst & src) == 0); + SDEBUG(" result: %s\n", ctx->ctx->cs_equal ? "EQ" : "NE"); +} + +static void atom_op_xor(atom_exec_context *ctx, int *ptr, int arg) +{ + uint8_t attr = U8((*ptr)++); + uint32_t dst, src, saved; + int dptr = *ptr; + SDEBUG(" dst: "); + dst = atom_get_dst(ctx, arg, attr, ptr, &saved, 1); + SDEBUG(" src: "); + src = atom_get_src(ctx, attr, ptr); + dst ^= src; + SDEBUG(" dst: "); + atom_put_dst(ctx, arg, attr, &dptr, dst, saved); +} + +static void atom_op_debug(atom_exec_context *ctx, int *ptr, int arg) +{ + printk(KERN_INFO "unimplemented!\n"); +} + +static struct { + void (*func) (atom_exec_context *, int *, int); + int arg; +} opcode_table[ATOM_OP_CNT] = { + { + NULL, 0}, { + atom_op_move, ATOM_ARG_REG}, { + atom_op_move, ATOM_ARG_PS}, { + atom_op_move, ATOM_ARG_WS}, { + atom_op_move, ATOM_ARG_FB}, { + atom_op_move, ATOM_ARG_PLL}, { + atom_op_move, ATOM_ARG_MC}, { + atom_op_and, ATOM_ARG_REG}, { + atom_op_and, ATOM_ARG_PS}, { + atom_op_and, ATOM_ARG_WS}, { + atom_op_and, ATOM_ARG_FB}, { + atom_op_and, ATOM_ARG_PLL}, { + atom_op_and, ATOM_ARG_MC}, { + atom_op_or, ATOM_ARG_REG}, { + atom_op_or, ATOM_ARG_PS}, { + atom_op_or, ATOM_ARG_WS}, { + atom_op_or, ATOM_ARG_FB}, { + atom_op_or, ATOM_ARG_PLL}, { + atom_op_or, ATOM_ARG_MC}, { + atom_op_shift_left, ATOM_ARG_REG}, { + atom_op_shift_left, ATOM_ARG_PS}, { + atom_op_shift_left, ATOM_ARG_WS}, { + atom_op_shift_left, ATOM_ARG_FB}, { + atom_op_shift_left, ATOM_ARG_PLL}, { + atom_op_shift_left, ATOM_ARG_MC}, { + atom_op_shift_right, ATOM_ARG_REG}, { + atom_op_shift_right, ATOM_ARG_PS}, { + atom_op_shift_right, ATOM_ARG_WS}, { + atom_op_shift_right, ATOM_ARG_FB}, { + atom_op_shift_right, ATOM_ARG_PLL}, { + atom_op_shift_right, ATOM_ARG_MC}, { + atom_op_mul, ATOM_ARG_REG}, { + atom_op_mul, ATOM_ARG_PS}, { + atom_op_mul, ATOM_ARG_WS}, { + atom_op_mul, ATOM_ARG_FB}, { + atom_op_mul, ATOM_ARG_PLL}, { + atom_op_mul, ATOM_ARG_MC}, { + atom_op_div, ATOM_ARG_REG}, { + atom_op_div, ATOM_ARG_PS}, { + atom_op_div, ATOM_ARG_WS}, { + atom_op_div, ATOM_ARG_FB}, { + atom_op_div, ATOM_ARG_PLL}, { + atom_op_div, ATOM_ARG_MC}, { + atom_op_add, ATOM_ARG_REG}, { + atom_op_add, ATOM_ARG_PS}, { + atom_op_add, ATOM_ARG_WS}, { + atom_op_add, ATOM_ARG_FB}, { + atom_op_add, ATOM_ARG_PLL}, { + atom_op_add, ATOM_ARG_MC}, { + atom_op_sub, ATOM_ARG_REG}, { + atom_op_sub, ATOM_ARG_PS}, { + atom_op_sub, ATOM_ARG_WS}, { + atom_op_sub, ATOM_ARG_FB}, { + atom_op_sub, ATOM_ARG_PLL}, { + atom_op_sub, ATOM_ARG_MC}, { + atom_op_setport, ATOM_PORT_ATI}, { + atom_op_setport, ATOM_PORT_PCI}, { + atom_op_setport, ATOM_PORT_SYSIO}, { + atom_op_setregblock, 0}, { + atom_op_setfbbase, 0}, { + atom_op_compare, ATOM_ARG_REG}, { + atom_op_compare, ATOM_ARG_PS}, { + atom_op_compare, ATOM_ARG_WS}, { + atom_op_compare, ATOM_ARG_FB}, { + atom_op_compare, ATOM_ARG_PLL}, { + atom_op_compare, ATOM_ARG_MC}, { + atom_op_switch, 0}, { + atom_op_jump, ATOM_COND_ALWAYS}, { + atom_op_jump, ATOM_COND_EQUAL}, { + atom_op_jump, ATOM_COND_BELOW}, { + atom_op_jump, ATOM_COND_ABOVE}, { + atom_op_jump, ATOM_COND_BELOWOREQUAL}, { + atom_op_jump, ATOM_COND_ABOVEOREQUAL}, { + atom_op_jump, ATOM_COND_NOTEQUAL}, { + atom_op_test, ATOM_ARG_REG}, { + atom_op_test, ATOM_ARG_PS}, { + atom_op_test, ATOM_ARG_WS}, { + atom_op_test, ATOM_ARG_FB}, { + atom_op_test, ATOM_ARG_PLL}, { + atom_op_test, ATOM_ARG_MC}, { + atom_op_delay, ATOM_UNIT_MILLISEC}, { + atom_op_delay, ATOM_UNIT_MICROSEC}, { + atom_op_calltable, 0}, { + atom_op_repeat, 0}, { + atom_op_clear, ATOM_ARG_REG}, { + atom_op_clear, ATOM_ARG_PS}, { + atom_op_clear, ATOM_ARG_WS}, { + atom_op_clear, ATOM_ARG_FB}, { + atom_op_clear, ATOM_ARG_PLL}, { + atom_op_clear, ATOM_ARG_MC}, { + atom_op_nop, 0}, { + atom_op_eot, 0}, { + atom_op_mask, ATOM_ARG_REG}, { + atom_op_mask, ATOM_ARG_PS}, { + atom_op_mask, ATOM_ARG_WS}, { + atom_op_mask, ATOM_ARG_FB}, { + atom_op_mask, ATOM_ARG_PLL}, { + atom_op_mask, ATOM_ARG_MC}, { + atom_op_postcard, 0}, { + atom_op_beep, 0}, { + atom_op_savereg, 0}, { + atom_op_restorereg, 0}, { + atom_op_setdatablock, 0}, { + atom_op_xor, ATOM_ARG_REG}, { + atom_op_xor, ATOM_ARG_PS}, { + atom_op_xor, ATOM_ARG_WS}, { + atom_op_xor, ATOM_ARG_FB}, { + atom_op_xor, ATOM_ARG_PLL}, { + atom_op_xor, ATOM_ARG_MC}, { + atom_op_shl, ATOM_ARG_REG}, { + atom_op_shl, ATOM_ARG_PS}, { + atom_op_shl, ATOM_ARG_WS}, { + atom_op_shl, ATOM_ARG_FB}, { + atom_op_shl, ATOM_ARG_PLL}, { + atom_op_shl, ATOM_ARG_MC}, { + atom_op_shr, ATOM_ARG_REG}, { + atom_op_shr, ATOM_ARG_PS}, { + atom_op_shr, ATOM_ARG_WS}, { + atom_op_shr, ATOM_ARG_FB}, { + atom_op_shr, ATOM_ARG_PLL}, { + atom_op_shr, ATOM_ARG_MC}, { +atom_op_debug, 0},}; + +static int amdgpu_atom_execute_table_locked(struct atom_context *ctx, int index, uint32_t * params) +{ + int base = CU16(ctx->cmd_table + 4 + 2 * index); + int len, ws, ps, ptr; + unsigned char op; + atom_exec_context ectx; + int ret = 0; + + if (!base) + return -EINVAL; + + len = CU16(base + ATOM_CT_SIZE_PTR); + ws = CU8(base + ATOM_CT_WS_PTR); + ps = CU8(base + ATOM_CT_PS_PTR) & ATOM_CT_PS_MASK; + ptr = base + ATOM_CT_CODE_PTR; + + SDEBUG(">> execute %04X (len %d, WS %d, PS %d)\n", base, len, ws, ps); + + ectx.ctx = ctx; + ectx.ps_shift = ps / 4; + ectx.start = base; + ectx.ps = params; + ectx.abort = false; + ectx.last_jump = 0; + if (ws) + ectx.ws = kzalloc(4 * ws, GFP_KERNEL); + else + ectx.ws = NULL; + + debug_depth++; + while (1) { + op = CU8(ptr++); + if (op < ATOM_OP_NAMES_CNT) + SDEBUG("%s @ 0x%04X\n", atom_op_names[op], ptr - 1); + else + SDEBUG("[%d] @ 0x%04X\n", op, ptr - 1); + if (ectx.abort) { + DRM_ERROR("atombios stuck executing %04X (len %d, WS %d, PS %d) @ 0x%04X\n", + base, len, ws, ps, ptr - 1); + ret = -EINVAL; + goto free; + } + + if (op < ATOM_OP_CNT && op > 0) + opcode_table[op].func(&ectx, &ptr, + opcode_table[op].arg); + else + break; + + if (op == ATOM_OP_EOT) + break; + } + debug_depth--; + SDEBUG("<<\n"); + +free: + if (ws) + kfree(ectx.ws); + return ret; +} + +int amdgpu_atom_execute_table(struct atom_context *ctx, int index, uint32_t * params) +{ + int r; + + mutex_lock(&ctx->mutex); + /* reset data block */ + ctx->data_block = 0; + /* reset reg block */ + ctx->reg_block = 0; + /* reset fb window */ + ctx->fb_base = 0; + /* reset io mode */ + ctx->io_mode = ATOM_IO_MM; + /* reset divmul */ + ctx->divmul[0] = 0; + ctx->divmul[1] = 0; + r = amdgpu_atom_execute_table_locked(ctx, index, params); + mutex_unlock(&ctx->mutex); + return r; +} + +static int atom_iio_len[] = { 1, 2, 3, 3, 3, 3, 4, 4, 4, 3 }; + +static void atom_index_iio(struct atom_context *ctx, int base) +{ + ctx->iio = kzalloc(2 * 256, GFP_KERNEL); + if (!ctx->iio) + return; + while (CU8(base) == ATOM_IIO_START) { + ctx->iio[CU8(base + 1)] = base + 2; + base += 2; + while (CU8(base) != ATOM_IIO_END) + base += atom_iio_len[CU8(base)]; + base += 3; + } +} + +struct atom_context *amdgpu_atom_parse(struct card_info *card, void *bios) +{ + int base; + struct atom_context *ctx = + kzalloc(sizeof(struct atom_context), GFP_KERNEL); + char *str; + char name[512]; + int i; + + if (!ctx) + return NULL; + + ctx->card = card; + ctx->bios = bios; + + if (CU16(0) != ATOM_BIOS_MAGIC) { + printk(KERN_INFO "Invalid BIOS magic.\n"); + kfree(ctx); + return NULL; + } + if (strncmp + (CSTR(ATOM_ATI_MAGIC_PTR), ATOM_ATI_MAGIC, + strlen(ATOM_ATI_MAGIC))) { + printk(KERN_INFO "Invalid ATI magic.\n"); + kfree(ctx); + return NULL; + } + + base = CU16(ATOM_ROM_TABLE_PTR); + if (strncmp + (CSTR(base + ATOM_ROM_MAGIC_PTR), ATOM_ROM_MAGIC, + strlen(ATOM_ROM_MAGIC))) { + printk(KERN_INFO "Invalid ATOM magic.\n"); + kfree(ctx); + return NULL; + } + + ctx->cmd_table = CU16(base + ATOM_ROM_CMD_PTR); + ctx->data_table = CU16(base + ATOM_ROM_DATA_PTR); + atom_index_iio(ctx, CU16(ctx->data_table + ATOM_DATA_IIO_PTR) + 4); + if (!ctx->iio) { + amdgpu_atom_destroy(ctx); + return NULL; + } + + str = CSTR(CU16(base + ATOM_ROM_MSG_PTR)); + while (*str && ((*str == '\n') || (*str == '\r'))) + str++; + /* name string isn't always 0 terminated */ + for (i = 0; i < 511; i++) { + name[i] = str[i]; + if (name[i] < '.' || name[i] > 'z') { + name[i] = 0; + break; + } + } + printk(KERN_INFO "ATOM BIOS: %s\n", name); + + return ctx; +} + +int amdgpu_atom_asic_init(struct atom_context *ctx) +{ + int hwi = CU16(ctx->data_table + ATOM_DATA_FWI_PTR); + uint32_t ps[16]; + int ret; + + memset(ps, 0, 64); + + ps[0] = cpu_to_le32(CU32(hwi + ATOM_FWI_DEFSCLK_PTR)); + ps[1] = cpu_to_le32(CU32(hwi + ATOM_FWI_DEFMCLK_PTR)); + if (!ps[0] || !ps[1]) + return 1; + + if (!CU16(ctx->cmd_table + 4 + 2 * ATOM_CMD_INIT)) + return 1; + ret = amdgpu_atom_execute_table(ctx, ATOM_CMD_INIT, ps); + if (ret) + return ret; + + memset(ps, 0, 64); + + return ret; +} + +void amdgpu_atom_destroy(struct atom_context *ctx) +{ + kfree(ctx->iio); + kfree(ctx); +} + +bool amdgpu_atom_parse_data_header(struct atom_context *ctx, int index, + uint16_t * size, uint8_t * frev, uint8_t * crev, + uint16_t * data_start) +{ + int offset = index * 2 + 4; + int idx = CU16(ctx->data_table + offset); + u16 *mdt = (u16 *)(ctx->bios + ctx->data_table + 4); + + if (!mdt[index]) + return false; + + if (size) + *size = CU16(idx); + if (frev) + *frev = CU8(idx + 2); + if (crev) + *crev = CU8(idx + 3); + *data_start = idx; + return true; +} + +bool amdgpu_atom_parse_cmd_header(struct atom_context *ctx, int index, uint8_t * frev, + uint8_t * crev) +{ + int offset = index * 2 + 4; + int idx = CU16(ctx->cmd_table + offset); + u16 *mct = (u16 *)(ctx->bios + ctx->cmd_table + 4); + + if (!mct[index]) + return false; + + if (frev) + *frev = CU8(idx + 2); + if (crev) + *crev = CU8(idx + 3); + return true; +} + +int amdgpu_atom_allocate_fb_scratch(struct atom_context *ctx) +{ + int index = GetIndexIntoMasterTable(DATA, VRAM_UsageByFirmware); + uint16_t data_offset; + int usage_bytes = 0; + struct _ATOM_VRAM_USAGE_BY_FIRMWARE *firmware_usage; + + if (amdgpu_atom_parse_data_header(ctx, index, NULL, NULL, NULL, &data_offset)) { + firmware_usage = (struct _ATOM_VRAM_USAGE_BY_FIRMWARE *)(ctx->bios + data_offset); + + DRM_DEBUG("atom firmware requested %08x %dkb\n", + le32_to_cpu(firmware_usage->asFirmwareVramReserveInfo[0].ulStartAddrUsedByFirmware), + le16_to_cpu(firmware_usage->asFirmwareVramReserveInfo[0].usFirmwareUseInKb)); + + usage_bytes = le16_to_cpu(firmware_usage->asFirmwareVramReserveInfo[0].usFirmwareUseInKb) * 1024; + } + ctx->scratch_size_bytes = 0; + if (usage_bytes == 0) + usage_bytes = 20 * 1024; + /* allocate some scratch memory */ + ctx->scratch = kzalloc(usage_bytes, GFP_KERNEL); + if (!ctx->scratch) + return -ENOMEM; + ctx->scratch_size_bytes = usage_bytes; + return 0; +} diff --git a/drivers/gpu/drm/amd/amdgpu/atom.h b/drivers/gpu/drm/amd/amdgpu/atom.h new file mode 100644 index 000000000000..09d0f8230708 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atom.h @@ -0,0 +1,159 @@ +/* + * Copyright 2008 Advanced Micro Devices, 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: Stanislaw Skowronek + */ + +#ifndef ATOM_H +#define ATOM_H + +#include <linux/types.h> +#include <drm/drmP.h> + +#define ATOM_BIOS_MAGIC 0xAA55 +#define ATOM_ATI_MAGIC_PTR 0x30 +#define ATOM_ATI_MAGIC " 761295520" +#define ATOM_ROM_TABLE_PTR 0x48 + +#define ATOM_ROM_MAGIC "ATOM" +#define ATOM_ROM_MAGIC_PTR 4 + +#define ATOM_ROM_MSG_PTR 0x10 +#define ATOM_ROM_CMD_PTR 0x1E +#define ATOM_ROM_DATA_PTR 0x20 + +#define ATOM_CMD_INIT 0 +#define ATOM_CMD_SETSCLK 0x0A +#define ATOM_CMD_SETMCLK 0x0B +#define ATOM_CMD_SETPCLK 0x0C +#define ATOM_CMD_SPDFANCNTL 0x39 + +#define ATOM_DATA_FWI_PTR 0xC +#define ATOM_DATA_IIO_PTR 0x32 + +#define ATOM_FWI_DEFSCLK_PTR 8 +#define ATOM_FWI_DEFMCLK_PTR 0xC +#define ATOM_FWI_MAXSCLK_PTR 0x24 +#define ATOM_FWI_MAXMCLK_PTR 0x28 + +#define ATOM_CT_SIZE_PTR 0 +#define ATOM_CT_WS_PTR 4 +#define ATOM_CT_PS_PTR 5 +#define ATOM_CT_PS_MASK 0x7F +#define ATOM_CT_CODE_PTR 6 + +#define ATOM_OP_CNT 123 +#define ATOM_OP_EOT 91 + +#define ATOM_CASE_MAGIC 0x63 +#define ATOM_CASE_END 0x5A5A + +#define ATOM_ARG_REG 0 +#define ATOM_ARG_PS 1 +#define ATOM_ARG_WS 2 +#define ATOM_ARG_FB 3 +#define ATOM_ARG_ID 4 +#define ATOM_ARG_IMM 5 +#define ATOM_ARG_PLL 6 +#define ATOM_ARG_MC 7 + +#define ATOM_SRC_DWORD 0 +#define ATOM_SRC_WORD0 1 +#define ATOM_SRC_WORD8 2 +#define ATOM_SRC_WORD16 3 +#define ATOM_SRC_BYTE0 4 +#define ATOM_SRC_BYTE8 5 +#define ATOM_SRC_BYTE16 6 +#define ATOM_SRC_BYTE24 7 + +#define ATOM_WS_QUOTIENT 0x40 +#define ATOM_WS_REMAINDER 0x41 +#define ATOM_WS_DATAPTR 0x42 +#define ATOM_WS_SHIFT 0x43 +#define ATOM_WS_OR_MASK 0x44 +#define ATOM_WS_AND_MASK 0x45 +#define ATOM_WS_FB_WINDOW 0x46 +#define ATOM_WS_ATTRIBUTES 0x47 +#define ATOM_WS_REGPTR 0x48 + +#define ATOM_IIO_NOP 0 +#define ATOM_IIO_START 1 +#define ATOM_IIO_READ 2 +#define ATOM_IIO_WRITE 3 +#define ATOM_IIO_CLEAR 4 +#define ATOM_IIO_SET 5 +#define ATOM_IIO_MOVE_INDEX 6 +#define ATOM_IIO_MOVE_ATTR 7 +#define ATOM_IIO_MOVE_DATA 8 +#define ATOM_IIO_END 9 + +#define ATOM_IO_MM 0 +#define ATOM_IO_PCI 1 +#define ATOM_IO_SYSIO 2 +#define ATOM_IO_IIO 0x80 + +struct card_info { + struct drm_device *dev; + void (* reg_write)(struct card_info *, uint32_t, uint32_t); /* filled by driver */ + uint32_t (* reg_read)(struct card_info *, uint32_t); /* filled by driver */ + void (* ioreg_write)(struct card_info *, uint32_t, uint32_t); /* filled by driver */ + uint32_t (* ioreg_read)(struct card_info *, uint32_t); /* filled by driver */ + void (* mc_write)(struct card_info *, uint32_t, uint32_t); /* filled by driver */ + uint32_t (* mc_read)(struct card_info *, uint32_t); /* filled by driver */ + void (* pll_write)(struct card_info *, uint32_t, uint32_t); /* filled by driver */ + uint32_t (* pll_read)(struct card_info *, uint32_t); /* filled by driver */ +}; + +struct atom_context { + struct card_info *card; + struct mutex mutex; + void *bios; + uint32_t cmd_table, data_table; + uint16_t *iio; + + uint16_t data_block; + uint32_t fb_base; + uint32_t divmul[2]; + uint16_t io_attr; + uint16_t reg_block; + uint8_t shift; + int cs_equal, cs_above; + int io_mode; + uint32_t *scratch; + int scratch_size_bytes; +}; + +extern int amdgpu_atom_debug; + +struct atom_context *amdgpu_atom_parse(struct card_info *, void *); +int amdgpu_atom_execute_table(struct atom_context *, int, uint32_t *); +int amdgpu_atom_asic_init(struct atom_context *); +void amdgpu_atom_destroy(struct atom_context *); +bool amdgpu_atom_parse_data_header(struct atom_context *ctx, int index, uint16_t *size, + uint8_t *frev, uint8_t *crev, uint16_t *data_start); +bool amdgpu_atom_parse_cmd_header(struct atom_context *ctx, int index, + uint8_t *frev, uint8_t *crev); +int amdgpu_atom_allocate_fb_scratch(struct atom_context *ctx); +#include "atom-types.h" +#include "atombios.h" +#include "ObjectID.h" + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_crtc.c b/drivers/gpu/drm/amd/amdgpu/atombios_crtc.c new file mode 100644 index 000000000000..49aa35016653 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_crtc.c @@ -0,0 +1,807 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/amdgpu_drm.h> +#include <drm/drm_fixed.h> +#include "amdgpu.h" +#include "atom.h" +#include "atom-bits.h" +#include "atombios_encoders.h" +#include "amdgpu_atombios.h" +#include "amdgpu_pll.h" +#include "amdgpu_connectors.h" + +void amdgpu_atombios_crtc_overscan_setup(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + SET_CRTC_OVERSCAN_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, SetCRTC_OverScan); + int a1, a2; + + memset(&args, 0, sizeof(args)); + + args.ucCRTC = amdgpu_crtc->crtc_id; + + switch (amdgpu_crtc->rmx_type) { + case RMX_CENTER: + args.usOverscanTop = cpu_to_le16((adjusted_mode->crtc_vdisplay - mode->crtc_vdisplay) / 2); + args.usOverscanBottom = cpu_to_le16((adjusted_mode->crtc_vdisplay - mode->crtc_vdisplay) / 2); + args.usOverscanLeft = cpu_to_le16((adjusted_mode->crtc_hdisplay - mode->crtc_hdisplay) / 2); + args.usOverscanRight = cpu_to_le16((adjusted_mode->crtc_hdisplay - mode->crtc_hdisplay) / 2); + break; + case RMX_ASPECT: + a1 = mode->crtc_vdisplay * adjusted_mode->crtc_hdisplay; + a2 = adjusted_mode->crtc_vdisplay * mode->crtc_hdisplay; + + if (a1 > a2) { + args.usOverscanLeft = cpu_to_le16((adjusted_mode->crtc_hdisplay - (a2 / mode->crtc_vdisplay)) / 2); + args.usOverscanRight = cpu_to_le16((adjusted_mode->crtc_hdisplay - (a2 / mode->crtc_vdisplay)) / 2); + } else if (a2 > a1) { + args.usOverscanTop = cpu_to_le16((adjusted_mode->crtc_vdisplay - (a1 / mode->crtc_hdisplay)) / 2); + args.usOverscanBottom = cpu_to_le16((adjusted_mode->crtc_vdisplay - (a1 / mode->crtc_hdisplay)) / 2); + } + break; + case RMX_FULL: + default: + args.usOverscanRight = cpu_to_le16(amdgpu_crtc->h_border); + args.usOverscanLeft = cpu_to_le16(amdgpu_crtc->h_border); + args.usOverscanBottom = cpu_to_le16(amdgpu_crtc->v_border); + args.usOverscanTop = cpu_to_le16(amdgpu_crtc->v_border); + break; + } + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_crtc_scaler_setup(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + ENABLE_SCALER_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, EnableScaler); + + memset(&args, 0, sizeof(args)); + + args.ucScaler = amdgpu_crtc->crtc_id; + + switch (amdgpu_crtc->rmx_type) { + case RMX_FULL: + args.ucEnable = ATOM_SCALER_EXPANSION; + break; + case RMX_CENTER: + args.ucEnable = ATOM_SCALER_CENTER; + break; + case RMX_ASPECT: + args.ucEnable = ATOM_SCALER_EXPANSION; + break; + default: + args.ucEnable = ATOM_SCALER_DISABLE; + break; + } + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_crtc_lock(struct drm_crtc *crtc, int lock) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + int index = + GetIndexIntoMasterTable(COMMAND, UpdateCRTC_DoubleBufferRegisters); + ENABLE_CRTC_PS_ALLOCATION args; + + memset(&args, 0, sizeof(args)); + + args.ucCRTC = amdgpu_crtc->crtc_id; + args.ucEnable = lock; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_crtc_enable(struct drm_crtc *crtc, int state) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + int index = GetIndexIntoMasterTable(COMMAND, EnableCRTC); + ENABLE_CRTC_PS_ALLOCATION args; + + memset(&args, 0, sizeof(args)); + + args.ucCRTC = amdgpu_crtc->crtc_id; + args.ucEnable = state; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_crtc_blank(struct drm_crtc *crtc, int state) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + int index = GetIndexIntoMasterTable(COMMAND, BlankCRTC); + BLANK_CRTC_PS_ALLOCATION args; + + memset(&args, 0, sizeof(args)); + + args.ucCRTC = amdgpu_crtc->crtc_id; + args.ucBlanking = state; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_crtc_powergate(struct drm_crtc *crtc, int state) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + int index = GetIndexIntoMasterTable(COMMAND, EnableDispPowerGating); + ENABLE_DISP_POWER_GATING_PARAMETERS_V2_1 args; + + memset(&args, 0, sizeof(args)); + + args.ucDispPipeId = amdgpu_crtc->crtc_id; + args.ucEnable = state; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_crtc_powergate_init(struct amdgpu_device *adev) +{ + int index = GetIndexIntoMasterTable(COMMAND, EnableDispPowerGating); + ENABLE_DISP_POWER_GATING_PARAMETERS_V2_1 args; + + memset(&args, 0, sizeof(args)); + + args.ucEnable = ATOM_INIT; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +void amdgpu_atombios_crtc_set_dtd_timing(struct drm_crtc *crtc, + struct drm_display_mode *mode) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + SET_CRTC_USING_DTD_TIMING_PARAMETERS args; + int index = GetIndexIntoMasterTable(COMMAND, SetCRTC_UsingDTDTiming); + u16 misc = 0; + + memset(&args, 0, sizeof(args)); + args.usH_Size = cpu_to_le16(mode->crtc_hdisplay - (amdgpu_crtc->h_border * 2)); + args.usH_Blanking_Time = + cpu_to_le16(mode->crtc_hblank_end - mode->crtc_hdisplay + (amdgpu_crtc->h_border * 2)); + args.usV_Size = cpu_to_le16(mode->crtc_vdisplay - (amdgpu_crtc->v_border * 2)); + args.usV_Blanking_Time = + cpu_to_le16(mode->crtc_vblank_end - mode->crtc_vdisplay + (amdgpu_crtc->v_border * 2)); + args.usH_SyncOffset = + cpu_to_le16(mode->crtc_hsync_start - mode->crtc_hdisplay + amdgpu_crtc->h_border); + args.usH_SyncWidth = + cpu_to_le16(mode->crtc_hsync_end - mode->crtc_hsync_start); + args.usV_SyncOffset = + cpu_to_le16(mode->crtc_vsync_start - mode->crtc_vdisplay + amdgpu_crtc->v_border); + args.usV_SyncWidth = + cpu_to_le16(mode->crtc_vsync_end - mode->crtc_vsync_start); + args.ucH_Border = amdgpu_crtc->h_border; + args.ucV_Border = amdgpu_crtc->v_border; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + misc |= ATOM_VSYNC_POLARITY; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + misc |= ATOM_HSYNC_POLARITY; + if (mode->flags & DRM_MODE_FLAG_CSYNC) + misc |= ATOM_COMPOSITESYNC; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + misc |= ATOM_INTERLACE; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + misc |= ATOM_DOUBLE_CLOCK_MODE; + + args.susModeMiscInfo.usAccess = cpu_to_le16(misc); + args.ucCRTC = amdgpu_crtc->crtc_id; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +union atom_enable_ss { + ENABLE_SPREAD_SPECTRUM_ON_PPLL_PS_ALLOCATION v1; + ENABLE_SPREAD_SPECTRUM_ON_PPLL_V2 v2; + ENABLE_SPREAD_SPECTRUM_ON_PPLL_V3 v3; +}; + +static void amdgpu_atombios_crtc_program_ss(struct amdgpu_device *adev, + int enable, + int pll_id, + int crtc_id, + struct amdgpu_atom_ss *ss) +{ + unsigned i; + int index = GetIndexIntoMasterTable(COMMAND, EnableSpreadSpectrumOnPPLL); + union atom_enable_ss args; + + if (enable) { + /* Don't mess with SS if percentage is 0 or external ss. + * SS is already disabled previously, and disabling it + * again can cause display problems if the pll is already + * programmed. + */ + if (ss->percentage == 0) + return; + if (ss->type & ATOM_EXTERNAL_SS_MASK) + return; + } else { + for (i = 0; i < adev->mode_info.num_crtc; i++) { + if (adev->mode_info.crtcs[i] && + adev->mode_info.crtcs[i]->enabled && + i != crtc_id && + pll_id == adev->mode_info.crtcs[i]->pll_id) { + /* one other crtc is using this pll don't turn + * off spread spectrum as it might turn off + * display on active crtc + */ + return; + } + } + } + + memset(&args, 0, sizeof(args)); + + args.v3.usSpreadSpectrumAmountFrac = cpu_to_le16(0); + args.v3.ucSpreadSpectrumType = ss->type & ATOM_SS_CENTRE_SPREAD_MODE_MASK; + switch (pll_id) { + case ATOM_PPLL1: + args.v3.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_P1PLL; + break; + case ATOM_PPLL2: + args.v3.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_P2PLL; + break; + case ATOM_DCPLL: + args.v3.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_DCPLL; + break; + case ATOM_PPLL_INVALID: + return; + } + args.v3.usSpreadSpectrumAmount = cpu_to_le16(ss->amount); + args.v3.usSpreadSpectrumStep = cpu_to_le16(ss->step); + args.v3.ucEnable = enable; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +union adjust_pixel_clock { + ADJUST_DISPLAY_PLL_PS_ALLOCATION v1; + ADJUST_DISPLAY_PLL_PS_ALLOCATION_V3 v3; +}; + +static u32 amdgpu_atombios_crtc_adjust_pll(struct drm_crtc *crtc, + struct drm_display_mode *mode) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + struct drm_encoder *encoder = amdgpu_crtc->encoder; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder); + u32 adjusted_clock = mode->clock; + int encoder_mode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + u32 dp_clock = mode->clock; + u32 clock = mode->clock; + int bpc = amdgpu_crtc->bpc; + bool is_duallink = amdgpu_dig_monitor_is_duallink(encoder, mode->clock); + union adjust_pixel_clock args; + u8 frev, crev; + int index; + + amdgpu_crtc->pll_flags = AMDGPU_PLL_USE_FRAC_FB_DIV; + + if ((amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT | ATOM_DEVICE_DFP_SUPPORT)) || + (amdgpu_encoder_get_dp_bridge_encoder_id(encoder) != ENCODER_OBJECT_ID_NONE)) { + if (connector) { + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector = + amdgpu_connector->con_priv; + + dp_clock = dig_connector->dp_clock; + } + } + + /* use recommended ref_div for ss */ + if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) { + if (amdgpu_crtc->ss_enabled) { + if (amdgpu_crtc->ss.refdiv) { + amdgpu_crtc->pll_flags |= AMDGPU_PLL_USE_REF_DIV; + amdgpu_crtc->pll_reference_div = amdgpu_crtc->ss.refdiv; + amdgpu_crtc->pll_flags |= AMDGPU_PLL_USE_FRAC_FB_DIV; + } + } + } + + /* DVO wants 2x pixel clock if the DVO chip is in 12 bit mode */ + if (amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1) + adjusted_clock = mode->clock * 2; + if (amdgpu_encoder->active_device & (ATOM_DEVICE_TV_SUPPORT)) + amdgpu_crtc->pll_flags |= AMDGPU_PLL_PREFER_CLOSEST_LOWER; + if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) + amdgpu_crtc->pll_flags |= AMDGPU_PLL_IS_LCD; + + + /* adjust pll for deep color modes */ + if (encoder_mode == ATOM_ENCODER_MODE_HDMI) { + switch (bpc) { + case 8: + default: + break; + case 10: + clock = (clock * 5) / 4; + break; + case 12: + clock = (clock * 3) / 2; + break; + case 16: + clock = clock * 2; + break; + } + } + + /* DCE3+ has an AdjustDisplayPll that will adjust the pixel clock + * accordingly based on the encoder/transmitter to work around + * special hw requirements. + */ + index = GetIndexIntoMasterTable(COMMAND, AdjustDisplayPll); + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, + &crev)) + return adjusted_clock; + + memset(&args, 0, sizeof(args)); + + switch (frev) { + case 1: + switch (crev) { + case 1: + case 2: + args.v1.usPixelClock = cpu_to_le16(clock / 10); + args.v1.ucTransmitterID = amdgpu_encoder->encoder_id; + args.v1.ucEncodeMode = encoder_mode; + if (amdgpu_crtc->ss_enabled && amdgpu_crtc->ss.percentage) + args.v1.ucConfig |= + ADJUST_DISPLAY_CONFIG_SS_ENABLE; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, + index, (uint32_t *)&args); + adjusted_clock = le16_to_cpu(args.v1.usPixelClock) * 10; + break; + case 3: + args.v3.sInput.usPixelClock = cpu_to_le16(clock / 10); + args.v3.sInput.ucTransmitterID = amdgpu_encoder->encoder_id; + args.v3.sInput.ucEncodeMode = encoder_mode; + args.v3.sInput.ucDispPllConfig = 0; + if (amdgpu_crtc->ss_enabled && amdgpu_crtc->ss.percentage) + args.v3.sInput.ucDispPllConfig |= + DISPPLL_CONFIG_SS_ENABLE; + if (ENCODER_MODE_IS_DP(encoder_mode)) { + args.v3.sInput.ucDispPllConfig |= + DISPPLL_CONFIG_COHERENT_MODE; + /* 16200 or 27000 */ + args.v3.sInput.usPixelClock = cpu_to_le16(dp_clock / 10); + } else if (amdgpu_encoder->devices & (ATOM_DEVICE_DFP_SUPPORT)) { + struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; + if (dig->coherent_mode) + args.v3.sInput.ucDispPllConfig |= + DISPPLL_CONFIG_COHERENT_MODE; + if (is_duallink) + args.v3.sInput.ucDispPllConfig |= + DISPPLL_CONFIG_DUAL_LINK; + } + if (amdgpu_encoder_get_dp_bridge_encoder_id(encoder) != + ENCODER_OBJECT_ID_NONE) + args.v3.sInput.ucExtTransmitterID = + amdgpu_encoder_get_dp_bridge_encoder_id(encoder); + else + args.v3.sInput.ucExtTransmitterID = 0; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, + index, (uint32_t *)&args); + adjusted_clock = le32_to_cpu(args.v3.sOutput.ulDispPllFreq) * 10; + if (args.v3.sOutput.ucRefDiv) { + amdgpu_crtc->pll_flags |= AMDGPU_PLL_USE_FRAC_FB_DIV; + amdgpu_crtc->pll_flags |= AMDGPU_PLL_USE_REF_DIV; + amdgpu_crtc->pll_reference_div = args.v3.sOutput.ucRefDiv; + } + if (args.v3.sOutput.ucPostDiv) { + amdgpu_crtc->pll_flags |= AMDGPU_PLL_USE_FRAC_FB_DIV; + amdgpu_crtc->pll_flags |= AMDGPU_PLL_USE_POST_DIV; + amdgpu_crtc->pll_post_div = args.v3.sOutput.ucPostDiv; + } + break; + default: + DRM_ERROR("Unknown table version %d %d\n", frev, crev); + return adjusted_clock; + } + break; + default: + DRM_ERROR("Unknown table version %d %d\n", frev, crev); + return adjusted_clock; + } + + return adjusted_clock; +} + +union set_pixel_clock { + SET_PIXEL_CLOCK_PS_ALLOCATION base; + PIXEL_CLOCK_PARAMETERS v1; + PIXEL_CLOCK_PARAMETERS_V2 v2; + PIXEL_CLOCK_PARAMETERS_V3 v3; + PIXEL_CLOCK_PARAMETERS_V5 v5; + PIXEL_CLOCK_PARAMETERS_V6 v6; +}; + +/* on DCE5, make sure the voltage is high enough to support the + * required disp clk. + */ +void amdgpu_atombios_crtc_set_disp_eng_pll(struct amdgpu_device *adev, + u32 dispclk) +{ + u8 frev, crev; + int index; + union set_pixel_clock args; + + memset(&args, 0, sizeof(args)); + + index = GetIndexIntoMasterTable(COMMAND, SetPixelClock); + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, + &crev)) + return; + + switch (frev) { + case 1: + switch (crev) { + case 5: + /* if the default dcpll clock is specified, + * SetPixelClock provides the dividers + */ + args.v5.ucCRTC = ATOM_CRTC_INVALID; + args.v5.usPixelClock = cpu_to_le16(dispclk); + args.v5.ucPpll = ATOM_DCPLL; + break; + case 6: + /* if the default dcpll clock is specified, + * SetPixelClock provides the dividers + */ + args.v6.ulDispEngClkFreq = cpu_to_le32(dispclk); + args.v6.ucPpll = ATOM_EXT_PLL1; + break; + default: + DRM_ERROR("Unknown table version %d %d\n", frev, crev); + return; + } + break; + default: + DRM_ERROR("Unknown table version %d %d\n", frev, crev); + return; + } + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +static bool is_pixel_clock_source_from_pll(u32 encoder_mode, int pll_id) +{ + if (ENCODER_MODE_IS_DP(encoder_mode)) { + if (pll_id < ATOM_EXT_PLL1) + return true; + else + return false; + } else { + return true; + } +} + +void amdgpu_atombios_crtc_program_pll(struct drm_crtc *crtc, + u32 crtc_id, + int pll_id, + u32 encoder_mode, + u32 encoder_id, + u32 clock, + u32 ref_div, + u32 fb_div, + u32 frac_fb_div, + u32 post_div, + int bpc, + bool ss_enabled, + struct amdgpu_atom_ss *ss) +{ + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + u8 frev, crev; + int index = GetIndexIntoMasterTable(COMMAND, SetPixelClock); + union set_pixel_clock args; + + memset(&args, 0, sizeof(args)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, + &crev)) + return; + + switch (frev) { + case 1: + switch (crev) { + case 1: + if (clock == ATOM_DISABLE) + return; + args.v1.usPixelClock = cpu_to_le16(clock / 10); + args.v1.usRefDiv = cpu_to_le16(ref_div); + args.v1.usFbDiv = cpu_to_le16(fb_div); + args.v1.ucFracFbDiv = frac_fb_div; + args.v1.ucPostDiv = post_div; + args.v1.ucPpll = pll_id; + args.v1.ucCRTC = crtc_id; + args.v1.ucRefDivSrc = 1; + break; + case 2: + args.v2.usPixelClock = cpu_to_le16(clock / 10); + args.v2.usRefDiv = cpu_to_le16(ref_div); + args.v2.usFbDiv = cpu_to_le16(fb_div); + args.v2.ucFracFbDiv = frac_fb_div; + args.v2.ucPostDiv = post_div; + args.v2.ucPpll = pll_id; + args.v2.ucCRTC = crtc_id; + args.v2.ucRefDivSrc = 1; + break; + case 3: + args.v3.usPixelClock = cpu_to_le16(clock / 10); + args.v3.usRefDiv = cpu_to_le16(ref_div); + args.v3.usFbDiv = cpu_to_le16(fb_div); + args.v3.ucFracFbDiv = frac_fb_div; + args.v3.ucPostDiv = post_div; + args.v3.ucPpll = pll_id; + if (crtc_id == ATOM_CRTC2) + args.v3.ucMiscInfo = PIXEL_CLOCK_MISC_CRTC_SEL_CRTC2; + else + args.v3.ucMiscInfo = PIXEL_CLOCK_MISC_CRTC_SEL_CRTC1; + if (ss_enabled && (ss->type & ATOM_EXTERNAL_SS_MASK)) + args.v3.ucMiscInfo |= PIXEL_CLOCK_MISC_REF_DIV_SRC; + args.v3.ucTransmitterId = encoder_id; + args.v3.ucEncoderMode = encoder_mode; + break; + case 5: + args.v5.ucCRTC = crtc_id; + args.v5.usPixelClock = cpu_to_le16(clock / 10); + args.v5.ucRefDiv = ref_div; + args.v5.usFbDiv = cpu_to_le16(fb_div); + args.v5.ulFbDivDecFrac = cpu_to_le32(frac_fb_div * 100000); + args.v5.ucPostDiv = post_div; + args.v5.ucMiscInfo = 0; /* HDMI depth, etc. */ + if ((ss_enabled && (ss->type & ATOM_EXTERNAL_SS_MASK)) && + (pll_id < ATOM_EXT_PLL1)) + args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_REF_DIV_SRC; + if (encoder_mode == ATOM_ENCODER_MODE_HDMI) { + switch (bpc) { + case 8: + default: + args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_24BPP; + break; + case 10: + /* yes this is correct, the atom define is wrong */ + args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_32BPP; + break; + case 12: + /* yes this is correct, the atom define is wrong */ + args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_30BPP; + break; + } + } + args.v5.ucTransmitterID = encoder_id; + args.v5.ucEncoderMode = encoder_mode; + args.v5.ucPpll = pll_id; + break; + case 6: + args.v6.ulDispEngClkFreq = cpu_to_le32(crtc_id << 24 | clock / 10); + args.v6.ucRefDiv = ref_div; + args.v6.usFbDiv = cpu_to_le16(fb_div); + args.v6.ulFbDivDecFrac = cpu_to_le32(frac_fb_div * 100000); + args.v6.ucPostDiv = post_div; + args.v6.ucMiscInfo = 0; /* HDMI depth, etc. */ + if ((ss_enabled && (ss->type & ATOM_EXTERNAL_SS_MASK)) && + (pll_id < ATOM_EXT_PLL1) && + !is_pixel_clock_source_from_pll(encoder_mode, pll_id)) + args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_REF_DIV_SRC; + if (encoder_mode == ATOM_ENCODER_MODE_HDMI) { + switch (bpc) { + case 8: + default: + args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_24BPP; + break; + case 10: + args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_30BPP_V6; + break; + case 12: + args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_36BPP_V6; + break; + case 16: + args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_48BPP; + break; + } + } + args.v6.ucTransmitterID = encoder_id; + args.v6.ucEncoderMode = encoder_mode; + args.v6.ucPpll = pll_id; + break; + default: + DRM_ERROR("Unknown table version %d %d\n", frev, crev); + return; + } + break; + default: + DRM_ERROR("Unknown table version %d %d\n", frev, crev); + return; + } + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +int amdgpu_atombios_crtc_prepare_pll(struct drm_crtc *crtc, + struct drm_display_mode *mode) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = + to_amdgpu_encoder(amdgpu_crtc->encoder); + int encoder_mode = amdgpu_atombios_encoder_get_encoder_mode(amdgpu_crtc->encoder); + + amdgpu_crtc->bpc = 8; + amdgpu_crtc->ss_enabled = false; + + if ((amdgpu_encoder->active_device & (ATOM_DEVICE_LCD_SUPPORT | ATOM_DEVICE_DFP_SUPPORT)) || + (amdgpu_encoder_get_dp_bridge_encoder_id(amdgpu_crtc->encoder) != ENCODER_OBJECT_ID_NONE)) { + struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; + struct drm_connector *connector = + amdgpu_get_connector_for_encoder(amdgpu_crtc->encoder); + struct amdgpu_connector *amdgpu_connector = + to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector = + amdgpu_connector->con_priv; + int dp_clock; + + /* Assign mode clock for hdmi deep color max clock limit check */ + amdgpu_connector->pixelclock_for_modeset = mode->clock; + amdgpu_crtc->bpc = amdgpu_connector_get_monitor_bpc(connector); + + switch (encoder_mode) { + case ATOM_ENCODER_MODE_DP_MST: + case ATOM_ENCODER_MODE_DP: + /* DP/eDP */ + dp_clock = dig_connector->dp_clock / 10; + amdgpu_crtc->ss_enabled = + amdgpu_atombios_get_asic_ss_info(adev, &amdgpu_crtc->ss, + ASIC_INTERNAL_SS_ON_DP, + dp_clock); + break; + case ATOM_ENCODER_MODE_LVDS: + amdgpu_crtc->ss_enabled = + amdgpu_atombios_get_asic_ss_info(adev, + &amdgpu_crtc->ss, + dig->lcd_ss_id, + mode->clock / 10); + break; + case ATOM_ENCODER_MODE_DVI: + amdgpu_crtc->ss_enabled = + amdgpu_atombios_get_asic_ss_info(adev, + &amdgpu_crtc->ss, + ASIC_INTERNAL_SS_ON_TMDS, + mode->clock / 10); + break; + case ATOM_ENCODER_MODE_HDMI: + amdgpu_crtc->ss_enabled = + amdgpu_atombios_get_asic_ss_info(adev, + &amdgpu_crtc->ss, + ASIC_INTERNAL_SS_ON_HDMI, + mode->clock / 10); + break; + default: + break; + } + } + + /* adjust pixel clock as needed */ + amdgpu_crtc->adjusted_clock = amdgpu_atombios_crtc_adjust_pll(crtc, mode); + + return 0; +} + +void amdgpu_atombios_crtc_set_pll(struct drm_crtc *crtc, struct drm_display_mode *mode) +{ + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = + to_amdgpu_encoder(amdgpu_crtc->encoder); + u32 pll_clock = mode->clock; + u32 clock = mode->clock; + u32 ref_div = 0, fb_div = 0, frac_fb_div = 0, post_div = 0; + struct amdgpu_pll *pll; + int encoder_mode = amdgpu_atombios_encoder_get_encoder_mode(amdgpu_crtc->encoder); + + /* pass the actual clock to amdgpu_atombios_crtc_program_pll for HDMI */ + if ((encoder_mode == ATOM_ENCODER_MODE_HDMI) && + (amdgpu_crtc->bpc > 8)) + clock = amdgpu_crtc->adjusted_clock; + + switch (amdgpu_crtc->pll_id) { + case ATOM_PPLL1: + pll = &adev->clock.ppll[0]; + break; + case ATOM_PPLL2: + pll = &adev->clock.ppll[1]; + break; + case ATOM_PPLL0: + case ATOM_PPLL_INVALID: + default: + pll = &adev->clock.ppll[2]; + break; + } + + /* update pll params */ + pll->flags = amdgpu_crtc->pll_flags; + pll->reference_div = amdgpu_crtc->pll_reference_div; + pll->post_div = amdgpu_crtc->pll_post_div; + + amdgpu_pll_compute(pll, amdgpu_crtc->adjusted_clock, &pll_clock, + &fb_div, &frac_fb_div, &ref_div, &post_div); + + amdgpu_atombios_crtc_program_ss(adev, ATOM_DISABLE, amdgpu_crtc->pll_id, + amdgpu_crtc->crtc_id, &amdgpu_crtc->ss); + + amdgpu_atombios_crtc_program_pll(crtc, amdgpu_crtc->crtc_id, amdgpu_crtc->pll_id, + encoder_mode, amdgpu_encoder->encoder_id, clock, + ref_div, fb_div, frac_fb_div, post_div, + amdgpu_crtc->bpc, amdgpu_crtc->ss_enabled, &amdgpu_crtc->ss); + + if (amdgpu_crtc->ss_enabled) { + /* calculate ss amount and step size */ + u32 step_size; + u32 amount = (((fb_div * 10) + frac_fb_div) * + (u32)amdgpu_crtc->ss.percentage) / + (100 * (u32)amdgpu_crtc->ss.percentage_divider); + amdgpu_crtc->ss.amount = (amount / 10) & ATOM_PPLL_SS_AMOUNT_V2_FBDIV_MASK; + amdgpu_crtc->ss.amount |= ((amount - (amount / 10)) << ATOM_PPLL_SS_AMOUNT_V2_NFRAC_SHIFT) & + ATOM_PPLL_SS_AMOUNT_V2_NFRAC_MASK; + if (amdgpu_crtc->ss.type & ATOM_PPLL_SS_TYPE_V2_CENTRE_SPREAD) + step_size = (4 * amount * ref_div * ((u32)amdgpu_crtc->ss.rate * 2048)) / + (125 * 25 * pll->reference_freq / 100); + else + step_size = (2 * amount * ref_div * ((u32)amdgpu_crtc->ss.rate * 2048)) / + (125 * 25 * pll->reference_freq / 100); + amdgpu_crtc->ss.step = step_size; + + amdgpu_atombios_crtc_program_ss(adev, ATOM_ENABLE, amdgpu_crtc->pll_id, + amdgpu_crtc->crtc_id, &amdgpu_crtc->ss); + } +} + diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_crtc.h b/drivers/gpu/drm/amd/amdgpu/atombios_crtc.h new file mode 100644 index 000000000000..c67083335b13 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_crtc.h @@ -0,0 +1,58 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __ATOMBIOS_CRTC_H__ +#define __ATOMBIOS_CRTC_H__ + +void amdgpu_atombios_crtc_overscan_setup(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +void amdgpu_atombios_crtc_scaler_setup(struct drm_crtc *crtc); +void amdgpu_atombios_crtc_lock(struct drm_crtc *crtc, int lock); +void amdgpu_atombios_crtc_enable(struct drm_crtc *crtc, int state); +void amdgpu_atombios_crtc_blank(struct drm_crtc *crtc, int state); +void amdgpu_atombios_crtc_powergate(struct drm_crtc *crtc, int state); +void amdgpu_atombios_crtc_powergate_init(struct amdgpu_device *adev); +void amdgpu_atombios_crtc_set_dtd_timing(struct drm_crtc *crtc, + struct drm_display_mode *mode); +void amdgpu_atombios_crtc_set_disp_eng_pll(struct amdgpu_device *adev, + u32 dispclk); +void amdgpu_atombios_crtc_program_pll(struct drm_crtc *crtc, + u32 crtc_id, + int pll_id, + u32 encoder_mode, + u32 encoder_id, + u32 clock, + u32 ref_div, + u32 fb_div, + u32 frac_fb_div, + u32 post_div, + int bpc, + bool ss_enabled, + struct amdgpu_atom_ss *ss); +int amdgpu_atombios_crtc_prepare_pll(struct drm_crtc *crtc, + struct drm_display_mode *mode); +void amdgpu_atombios_crtc_set_pll(struct drm_crtc *crtc, + struct drm_display_mode *mode); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_dp.c b/drivers/gpu/drm/amd/amdgpu/atombios_dp.c new file mode 100644 index 000000000000..e00b8adde18d --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_dp.c @@ -0,0 +1,774 @@ +/* + * Copyright 2007-8 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + * Jerome Glisse + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" + +#include "atom.h" +#include "atom-bits.h" +#include "atombios_encoders.h" +#include "atombios_dp.h" +#include "amdgpu_connectors.h" +#include "amdgpu_atombios.h" +#include <drm/drm_dp_helper.h> + +/* move these to drm_dp_helper.c/h */ +#define DP_LINK_CONFIGURATION_SIZE 9 +#define DP_DPCD_SIZE DP_RECEIVER_CAP_SIZE + +static char *voltage_names[] = { + "0.4V", "0.6V", "0.8V", "1.2V" +}; +static char *pre_emph_names[] = { + "0dB", "3.5dB", "6dB", "9.5dB" +}; + +/***** amdgpu AUX functions *****/ + +union aux_channel_transaction { + PROCESS_AUX_CHANNEL_TRANSACTION_PS_ALLOCATION v1; + PROCESS_AUX_CHANNEL_TRANSACTION_PARAMETERS_V2 v2; +}; + +static int amdgpu_atombios_dp_process_aux_ch(struct amdgpu_i2c_chan *chan, + u8 *send, int send_bytes, + u8 *recv, int recv_size, + u8 delay, u8 *ack) +{ + struct drm_device *dev = chan->dev; + struct amdgpu_device *adev = dev->dev_private; + union aux_channel_transaction args; + int index = GetIndexIntoMasterTable(COMMAND, ProcessAuxChannelTransaction); + unsigned char *base; + int recv_bytes; + int r = 0; + + memset(&args, 0, sizeof(args)); + + mutex_lock(&chan->mutex); + + base = (unsigned char *)(adev->mode_info.atom_context->scratch + 1); + + amdgpu_atombios_copy_swap(base, send, send_bytes, true); + + args.v2.lpAuxRequest = cpu_to_le16((u16)(0 + 4)); + args.v2.lpDataOut = cpu_to_le16((u16)(16 + 4)); + args.v2.ucDataOutLen = 0; + args.v2.ucChannelID = chan->rec.i2c_id; + args.v2.ucDelay = delay / 10; + args.v2.ucHPD_ID = chan->rec.hpd; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + *ack = args.v2.ucReplyStatus; + + /* timeout */ + if (args.v2.ucReplyStatus == 1) { + DRM_DEBUG_KMS("dp_aux_ch timeout\n"); + r = -ETIMEDOUT; + goto done; + } + + /* flags not zero */ + if (args.v2.ucReplyStatus == 2) { + DRM_DEBUG_KMS("dp_aux_ch flags not zero\n"); + r = -EIO; + goto done; + } + + /* error */ + if (args.v2.ucReplyStatus == 3) { + DRM_DEBUG_KMS("dp_aux_ch error\n"); + r = -EIO; + goto done; + } + + recv_bytes = args.v1.ucDataOutLen; + if (recv_bytes > recv_size) + recv_bytes = recv_size; + + if (recv && recv_size) + amdgpu_atombios_copy_swap(recv, base + 16, recv_bytes, false); + + r = recv_bytes; +done: + mutex_unlock(&chan->mutex); + + return r; +} + +#define BARE_ADDRESS_SIZE 3 +#define HEADER_SIZE (BARE_ADDRESS_SIZE + 1) + +static ssize_t +amdgpu_atombios_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) +{ + struct amdgpu_i2c_chan *chan = + container_of(aux, struct amdgpu_i2c_chan, aux); + int ret; + u8 tx_buf[20]; + size_t tx_size; + u8 ack, delay = 0; + + if (WARN_ON(msg->size > 16)) + return -E2BIG; + + tx_buf[0] = msg->address & 0xff; + tx_buf[1] = msg->address >> 8; + tx_buf[2] = msg->request << 4; + tx_buf[3] = msg->size ? (msg->size - 1) : 0; + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_NATIVE_WRITE: + case DP_AUX_I2C_WRITE: + /* tx_size needs to be 4 even for bare address packets since the atom + * table needs the info in tx_buf[3]. + */ + tx_size = HEADER_SIZE + msg->size; + if (msg->size == 0) + tx_buf[3] |= BARE_ADDRESS_SIZE << 4; + else + tx_buf[3] |= tx_size << 4; + memcpy(tx_buf + HEADER_SIZE, msg->buffer, msg->size); + ret = amdgpu_atombios_dp_process_aux_ch(chan, + tx_buf, tx_size, NULL, 0, delay, &ack); + if (ret >= 0) + /* Return payload size. */ + ret = msg->size; + break; + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ: + /* tx_size needs to be 4 even for bare address packets since the atom + * table needs the info in tx_buf[3]. + */ + tx_size = HEADER_SIZE; + if (msg->size == 0) + tx_buf[3] |= BARE_ADDRESS_SIZE << 4; + else + tx_buf[3] |= tx_size << 4; + ret = amdgpu_atombios_dp_process_aux_ch(chan, + tx_buf, tx_size, msg->buffer, msg->size, delay, &ack); + break; + default: + ret = -EINVAL; + break; + } + + if (ret >= 0) + msg->reply = ack >> 4; + + return ret; +} + +void amdgpu_atombios_dp_aux_init(struct amdgpu_connector *amdgpu_connector) +{ + int ret; + + amdgpu_connector->ddc_bus->rec.hpd = amdgpu_connector->hpd.hpd; + amdgpu_connector->ddc_bus->aux.dev = amdgpu_connector->base.kdev; + amdgpu_connector->ddc_bus->aux.transfer = amdgpu_atombios_dp_aux_transfer; + ret = drm_dp_aux_register(&amdgpu_connector->ddc_bus->aux); + if (!ret) + amdgpu_connector->ddc_bus->has_aux = true; + + WARN(ret, "drm_dp_aux_register_i2c_bus() failed with error %d\n", ret); +} + +/***** general DP utility functions *****/ + +#define DP_VOLTAGE_MAX DP_TRAIN_VOLTAGE_SWING_LEVEL_3 +#define DP_PRE_EMPHASIS_MAX DP_TRAIN_PRE_EMPH_LEVEL_3 + +static void amdgpu_atombios_dp_get_adjust_train(u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count, + u8 train_set[4]) +{ + u8 v = 0; + u8 p = 0; + int lane; + + for (lane = 0; lane < lane_count; lane++) { + u8 this_v = drm_dp_get_adjust_request_voltage(link_status, lane); + u8 this_p = drm_dp_get_adjust_request_pre_emphasis(link_status, lane); + + DRM_DEBUG_KMS("requested signal parameters: lane %d voltage %s pre_emph %s\n", + lane, + voltage_names[this_v >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[this_p >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + if (this_v > v) + v = this_v; + if (this_p > p) + p = this_p; + } + + if (v >= DP_VOLTAGE_MAX) + v |= DP_TRAIN_MAX_SWING_REACHED; + + if (p >= DP_PRE_EMPHASIS_MAX) + p |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + DRM_DEBUG_KMS("using signal parameters: voltage %s pre_emph %s\n", + voltage_names[(v & DP_TRAIN_VOLTAGE_SWING_MASK) >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[(p & DP_TRAIN_PRE_EMPHASIS_MASK) >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + for (lane = 0; lane < 4; lane++) + train_set[lane] = v | p; +} + +/* convert bits per color to bits per pixel */ +/* get bpc from the EDID */ +static int amdgpu_atombios_dp_convert_bpc_to_bpp(int bpc) +{ + if (bpc == 0) + return 24; + else + return bpc * 3; +} + +/* get the max pix clock supported by the link rate and lane num */ +static int amdgpu_atombios_dp_get_max_dp_pix_clock(int link_rate, + int lane_num, + int bpp) +{ + return (link_rate * lane_num * 8) / bpp; +} + +/***** amdgpu specific DP functions *****/ + +/* First get the min lane# when low rate is used according to pixel clock + * (prefer low rate), second check max lane# supported by DP panel, + * if the max lane# < low rate lane# then use max lane# instead. + */ +static int amdgpu_atombios_dp_get_dp_lane_number(struct drm_connector *connector, + u8 dpcd[DP_DPCD_SIZE], + int pix_clock) +{ + int bpp = amdgpu_atombios_dp_convert_bpc_to_bpp(amdgpu_connector_get_monitor_bpc(connector)); + int max_link_rate = drm_dp_max_link_rate(dpcd); + int max_lane_num = drm_dp_max_lane_count(dpcd); + int lane_num; + int max_dp_pix_clock; + + for (lane_num = 1; lane_num < max_lane_num; lane_num <<= 1) { + max_dp_pix_clock = amdgpu_atombios_dp_get_max_dp_pix_clock(max_link_rate, lane_num, bpp); + if (pix_clock <= max_dp_pix_clock) + break; + } + + return lane_num; +} + +static int amdgpu_atombios_dp_get_dp_link_clock(struct drm_connector *connector, + u8 dpcd[DP_DPCD_SIZE], + int pix_clock) +{ + int bpp = amdgpu_atombios_dp_convert_bpc_to_bpp(amdgpu_connector_get_monitor_bpc(connector)); + int lane_num, max_pix_clock; + + if (amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector) == + ENCODER_OBJECT_ID_NUTMEG) + return 270000; + + lane_num = amdgpu_atombios_dp_get_dp_lane_number(connector, dpcd, pix_clock); + max_pix_clock = amdgpu_atombios_dp_get_max_dp_pix_clock(162000, lane_num, bpp); + if (pix_clock <= max_pix_clock) + return 162000; + max_pix_clock = amdgpu_atombios_dp_get_max_dp_pix_clock(270000, lane_num, bpp); + if (pix_clock <= max_pix_clock) + return 270000; + if (amdgpu_connector_is_dp12_capable(connector)) { + max_pix_clock = amdgpu_atombios_dp_get_max_dp_pix_clock(540000, lane_num, bpp); + if (pix_clock <= max_pix_clock) + return 540000; + } + + return drm_dp_max_link_rate(dpcd); +} + +static u8 amdgpu_atombios_dp_encoder_service(struct amdgpu_device *adev, + int action, int dp_clock, + u8 ucconfig, u8 lane_num) +{ + DP_ENCODER_SERVICE_PARAMETERS args; + int index = GetIndexIntoMasterTable(COMMAND, DPEncoderService); + + memset(&args, 0, sizeof(args)); + args.ucLinkClock = dp_clock / 10; + args.ucConfig = ucconfig; + args.ucAction = action; + args.ucLaneNum = lane_num; + args.ucStatus = 0; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + return args.ucStatus; +} + +u8 amdgpu_atombios_dp_get_sinktype(struct amdgpu_connector *amdgpu_connector) +{ + struct drm_device *dev = amdgpu_connector->base.dev; + struct amdgpu_device *adev = dev->dev_private; + + return amdgpu_atombios_dp_encoder_service(adev, ATOM_DP_ACTION_GET_SINK_TYPE, 0, + amdgpu_connector->ddc_bus->rec.i2c_id, 0); +} + +static void amdgpu_atombios_dp_probe_oui(struct amdgpu_connector *amdgpu_connector) +{ + struct amdgpu_connector_atom_dig *dig_connector = amdgpu_connector->con_priv; + u8 buf[3]; + + if (!(dig_connector->dpcd[DP_DOWN_STREAM_PORT_COUNT] & DP_OUI_SUPPORT)) + return; + + if (drm_dp_dpcd_read(&amdgpu_connector->ddc_bus->aux, DP_SINK_OUI, buf, 3) == 3) + DRM_DEBUG_KMS("Sink OUI: %02hx%02hx%02hx\n", + buf[0], buf[1], buf[2]); + + if (drm_dp_dpcd_read(&amdgpu_connector->ddc_bus->aux, DP_BRANCH_OUI, buf, 3) == 3) + DRM_DEBUG_KMS("Branch OUI: %02hx%02hx%02hx\n", + buf[0], buf[1], buf[2]); +} + +int amdgpu_atombios_dp_get_dpcd(struct amdgpu_connector *amdgpu_connector) +{ + struct amdgpu_connector_atom_dig *dig_connector = amdgpu_connector->con_priv; + u8 msg[DP_DPCD_SIZE]; + int ret, i; + + ret = drm_dp_dpcd_read(&amdgpu_connector->ddc_bus->aux, DP_DPCD_REV, msg, + DP_DPCD_SIZE); + if (ret > 0) { + memcpy(dig_connector->dpcd, msg, DP_DPCD_SIZE); + DRM_DEBUG_KMS("DPCD: "); + for (i = 0; i < DP_DPCD_SIZE; i++) + DRM_DEBUG_KMS("%02x ", msg[i]); + DRM_DEBUG_KMS("\n"); + + amdgpu_atombios_dp_probe_oui(amdgpu_connector); + + return 0; + } + dig_connector->dpcd[0] = 0; + return -EINVAL; +} + +int amdgpu_atombios_dp_get_panel_mode(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector; + int panel_mode = DP_PANEL_MODE_EXTERNAL_DP_MODE; + u16 dp_bridge = amdgpu_connector_encoder_get_dp_bridge_encoder_id(connector); + u8 tmp; + + if (!amdgpu_connector->con_priv) + return panel_mode; + + dig_connector = amdgpu_connector->con_priv; + + if (dp_bridge != ENCODER_OBJECT_ID_NONE) { + /* DP bridge chips */ + if (drm_dp_dpcd_readb(&amdgpu_connector->ddc_bus->aux, + DP_EDP_CONFIGURATION_CAP, &tmp) == 1) { + if (tmp & 1) + panel_mode = DP_PANEL_MODE_INTERNAL_DP2_MODE; + else if ((dp_bridge == ENCODER_OBJECT_ID_NUTMEG) || + (dp_bridge == ENCODER_OBJECT_ID_TRAVIS)) + panel_mode = DP_PANEL_MODE_INTERNAL_DP1_MODE; + else + panel_mode = DP_PANEL_MODE_EXTERNAL_DP_MODE; + } + } else if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + /* eDP */ + if (drm_dp_dpcd_readb(&amdgpu_connector->ddc_bus->aux, + DP_EDP_CONFIGURATION_CAP, &tmp) == 1) { + if (tmp & 1) + panel_mode = DP_PANEL_MODE_INTERNAL_DP2_MODE; + } + } + + return panel_mode; +} + +void amdgpu_atombios_dp_set_link_config(struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector; + + if (!amdgpu_connector->con_priv) + return; + dig_connector = amdgpu_connector->con_priv; + + if ((dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) || + (dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP)) { + dig_connector->dp_clock = + amdgpu_atombios_dp_get_dp_link_clock(connector, dig_connector->dpcd, mode->clock); + dig_connector->dp_lane_count = + amdgpu_atombios_dp_get_dp_lane_number(connector, dig_connector->dpcd, mode->clock); + } +} + +int amdgpu_atombios_dp_mode_valid_helper(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector; + int dp_clock; + + if (!amdgpu_connector->con_priv) + return MODE_CLOCK_HIGH; + dig_connector = amdgpu_connector->con_priv; + + dp_clock = + amdgpu_atombios_dp_get_dp_link_clock(connector, dig_connector->dpcd, mode->clock); + + if ((dp_clock == 540000) && + (!amdgpu_connector_is_dp12_capable(connector))) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +bool amdgpu_atombios_dp_needs_link_train(struct amdgpu_connector *amdgpu_connector) +{ + u8 link_status[DP_LINK_STATUS_SIZE]; + struct amdgpu_connector_atom_dig *dig = amdgpu_connector->con_priv; + + if (drm_dp_dpcd_read_link_status(&amdgpu_connector->ddc_bus->aux, link_status) + <= 0) + return false; + if (drm_dp_channel_eq_ok(link_status, dig->dp_lane_count)) + return false; + return true; +} + +void amdgpu_atombios_dp_set_rx_power_state(struct drm_connector *connector, + u8 power_state) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector; + + if (!amdgpu_connector->con_priv) + return; + + dig_connector = amdgpu_connector->con_priv; + + /* power up/down the sink */ + if (dig_connector->dpcd[0] >= 0x11) { + drm_dp_dpcd_writeb(&amdgpu_connector->ddc_bus->aux, + DP_SET_POWER, power_state); + usleep_range(1000, 2000); + } +} + +struct amdgpu_atombios_dp_link_train_info { + struct amdgpu_device *adev; + struct drm_encoder *encoder; + struct drm_connector *connector; + int dp_clock; + int dp_lane_count; + bool tp3_supported; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + u8 train_set[4]; + u8 link_status[DP_LINK_STATUS_SIZE]; + u8 tries; + struct drm_dp_aux *aux; +}; + +static void +amdgpu_atombios_dp_update_vs_emph(struct amdgpu_atombios_dp_link_train_info *dp_info) +{ + /* set the initial vs/emph on the source */ + amdgpu_atombios_encoder_setup_dig_transmitter(dp_info->encoder, + ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH, + 0, dp_info->train_set[0]); /* sets all lanes at once */ + + /* set the vs/emph on the sink */ + drm_dp_dpcd_write(dp_info->aux, DP_TRAINING_LANE0_SET, + dp_info->train_set, dp_info->dp_lane_count); +} + +static void +amdgpu_atombios_dp_set_tp(struct amdgpu_atombios_dp_link_train_info *dp_info, int tp) +{ + int rtp = 0; + + /* set training pattern on the source */ + switch (tp) { + case DP_TRAINING_PATTERN_1: + rtp = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN1; + break; + case DP_TRAINING_PATTERN_2: + rtp = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN2; + break; + case DP_TRAINING_PATTERN_3: + rtp = ATOM_ENCODER_CMD_DP_LINK_TRAINING_PATTERN3; + break; + } + amdgpu_atombios_encoder_setup_dig_encoder(dp_info->encoder, rtp, 0); + + /* enable training pattern on the sink */ + drm_dp_dpcd_writeb(dp_info->aux, DP_TRAINING_PATTERN_SET, tp); +} + +static int +amdgpu_atombios_dp_link_train_init(struct amdgpu_atombios_dp_link_train_info *dp_info) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(dp_info->encoder); + struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; + u8 tmp; + + /* power up the sink */ + amdgpu_atombios_dp_set_rx_power_state(dp_info->connector, DP_SET_POWER_D0); + + /* possibly enable downspread on the sink */ + if (dp_info->dpcd[3] & 0x1) + drm_dp_dpcd_writeb(dp_info->aux, + DP_DOWNSPREAD_CTRL, DP_SPREAD_AMP_0_5); + else + drm_dp_dpcd_writeb(dp_info->aux, + DP_DOWNSPREAD_CTRL, 0); + + if (dig->panel_mode == DP_PANEL_MODE_INTERNAL_DP2_MODE) + drm_dp_dpcd_writeb(dp_info->aux, DP_EDP_CONFIGURATION_SET, 1); + + /* set the lane count on the sink */ + tmp = dp_info->dp_lane_count; + if (drm_dp_enhanced_frame_cap(dp_info->dpcd)) + tmp |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + drm_dp_dpcd_writeb(dp_info->aux, DP_LANE_COUNT_SET, tmp); + + /* set the link rate on the sink */ + tmp = drm_dp_link_rate_to_bw_code(dp_info->dp_clock); + drm_dp_dpcd_writeb(dp_info->aux, DP_LINK_BW_SET, tmp); + + /* start training on the source */ + amdgpu_atombios_encoder_setup_dig_encoder(dp_info->encoder, + ATOM_ENCODER_CMD_DP_LINK_TRAINING_START, 0); + + /* disable the training pattern on the sink */ + drm_dp_dpcd_writeb(dp_info->aux, + DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + return 0; +} + +static int +amdgpu_atombios_dp_link_train_finish(struct amdgpu_atombios_dp_link_train_info *dp_info) +{ + udelay(400); + + /* disable the training pattern on the sink */ + drm_dp_dpcd_writeb(dp_info->aux, + DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + /* disable the training pattern on the source */ + amdgpu_atombios_encoder_setup_dig_encoder(dp_info->encoder, + ATOM_ENCODER_CMD_DP_LINK_TRAINING_COMPLETE, 0); + + return 0; +} + +static int +amdgpu_atombios_dp_link_train_cr(struct amdgpu_atombios_dp_link_train_info *dp_info) +{ + bool clock_recovery; + u8 voltage; + int i; + + amdgpu_atombios_dp_set_tp(dp_info, DP_TRAINING_PATTERN_1); + memset(dp_info->train_set, 0, 4); + amdgpu_atombios_dp_update_vs_emph(dp_info); + + udelay(400); + + /* clock recovery loop */ + clock_recovery = false; + dp_info->tries = 0; + voltage = 0xff; + while (1) { + drm_dp_link_train_clock_recovery_delay(dp_info->dpcd); + + if (drm_dp_dpcd_read_link_status(dp_info->aux, + dp_info->link_status) <= 0) { + DRM_ERROR("displayport link status failed\n"); + break; + } + + if (drm_dp_clock_recovery_ok(dp_info->link_status, dp_info->dp_lane_count)) { + clock_recovery = true; + break; + } + + for (i = 0; i < dp_info->dp_lane_count; i++) { + if ((dp_info->train_set[i] & DP_TRAIN_MAX_SWING_REACHED) == 0) + break; + } + if (i == dp_info->dp_lane_count) { + DRM_ERROR("clock recovery reached max voltage\n"); + break; + } + + if ((dp_info->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage) { + ++dp_info->tries; + if (dp_info->tries == 5) { + DRM_ERROR("clock recovery tried 5 times\n"); + break; + } + } else + dp_info->tries = 0; + + voltage = dp_info->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + + /* Compute new train_set as requested by sink */ + amdgpu_atombios_dp_get_adjust_train(dp_info->link_status, dp_info->dp_lane_count, + dp_info->train_set); + + amdgpu_atombios_dp_update_vs_emph(dp_info); + } + if (!clock_recovery) { + DRM_ERROR("clock recovery failed\n"); + return -1; + } else { + DRM_DEBUG_KMS("clock recovery at voltage %d pre-emphasis %d\n", + dp_info->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (dp_info->train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT); + return 0; + } +} + +static int +amdgpu_atombios_dp_link_train_ce(struct amdgpu_atombios_dp_link_train_info *dp_info) +{ + bool channel_eq; + + if (dp_info->tp3_supported) + amdgpu_atombios_dp_set_tp(dp_info, DP_TRAINING_PATTERN_3); + else + amdgpu_atombios_dp_set_tp(dp_info, DP_TRAINING_PATTERN_2); + + /* channel equalization loop */ + dp_info->tries = 0; + channel_eq = false; + while (1) { + drm_dp_link_train_channel_eq_delay(dp_info->dpcd); + + if (drm_dp_dpcd_read_link_status(dp_info->aux, + dp_info->link_status) <= 0) { + DRM_ERROR("displayport link status failed\n"); + break; + } + + if (drm_dp_channel_eq_ok(dp_info->link_status, dp_info->dp_lane_count)) { + channel_eq = true; + break; + } + + /* Try 5 times */ + if (dp_info->tries > 5) { + DRM_ERROR("channel eq failed: 5 tries\n"); + break; + } + + /* Compute new train_set as requested by sink */ + amdgpu_atombios_dp_get_adjust_train(dp_info->link_status, dp_info->dp_lane_count, + dp_info->train_set); + + amdgpu_atombios_dp_update_vs_emph(dp_info); + dp_info->tries++; + } + + if (!channel_eq) { + DRM_ERROR("channel eq failed\n"); + return -1; + } else { + DRM_DEBUG_KMS("channel eq at voltage %d pre-emphasis %d\n", + dp_info->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (dp_info->train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) + >> DP_TRAIN_PRE_EMPHASIS_SHIFT); + return 0; + } +} + +void amdgpu_atombios_dp_link_train(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_encoder_atom_dig *dig; + struct amdgpu_connector *amdgpu_connector; + struct amdgpu_connector_atom_dig *dig_connector; + struct amdgpu_atombios_dp_link_train_info dp_info; + u8 tmp; + + if (!amdgpu_encoder->enc_priv) + return; + dig = amdgpu_encoder->enc_priv; + + amdgpu_connector = to_amdgpu_connector(connector); + if (!amdgpu_connector->con_priv) + return; + dig_connector = amdgpu_connector->con_priv; + + if ((dig_connector->dp_sink_type != CONNECTOR_OBJECT_ID_DISPLAYPORT) && + (dig_connector->dp_sink_type != CONNECTOR_OBJECT_ID_eDP)) + return; + + if (drm_dp_dpcd_readb(&amdgpu_connector->ddc_bus->aux, DP_MAX_LANE_COUNT, &tmp) + == 1) { + if (tmp & DP_TPS3_SUPPORTED) + dp_info.tp3_supported = true; + else + dp_info.tp3_supported = false; + } else { + dp_info.tp3_supported = false; + } + + memcpy(dp_info.dpcd, dig_connector->dpcd, DP_RECEIVER_CAP_SIZE); + dp_info.adev = adev; + dp_info.encoder = encoder; + dp_info.connector = connector; + dp_info.dp_lane_count = dig_connector->dp_lane_count; + dp_info.dp_clock = dig_connector->dp_clock; + dp_info.aux = &amdgpu_connector->ddc_bus->aux; + + if (amdgpu_atombios_dp_link_train_init(&dp_info)) + goto done; + if (amdgpu_atombios_dp_link_train_cr(&dp_info)) + goto done; + if (amdgpu_atombios_dp_link_train_ce(&dp_info)) + goto done; +done: + if (amdgpu_atombios_dp_link_train_finish(&dp_info)) + return; +} diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_dp.h b/drivers/gpu/drm/amd/amdgpu/atombios_dp.h new file mode 100644 index 000000000000..f59d85eaddf0 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_dp.h @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __ATOMBIOS_DP_H__ +#define __ATOMBIOS_DP_H__ + +void amdgpu_atombios_dp_aux_init(struct amdgpu_connector *amdgpu_connector); +u8 amdgpu_atombios_dp_get_sinktype(struct amdgpu_connector *amdgpu_connector); +int amdgpu_atombios_dp_get_dpcd(struct amdgpu_connector *amdgpu_connector); +int amdgpu_atombios_dp_get_panel_mode(struct drm_encoder *encoder, + struct drm_connector *connector); +void amdgpu_atombios_dp_set_link_config(struct drm_connector *connector, + const struct drm_display_mode *mode); +int amdgpu_atombios_dp_mode_valid_helper(struct drm_connector *connector, + struct drm_display_mode *mode); +bool amdgpu_atombios_dp_needs_link_train(struct amdgpu_connector *amdgpu_connector); +void amdgpu_atombios_dp_set_rx_power_state(struct drm_connector *connector, + u8 power_state); +void amdgpu_atombios_dp_link_train(struct drm_encoder *encoder, + struct drm_connector *connector); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c new file mode 100644 index 000000000000..ae8caca61e04 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c @@ -0,0 +1,2066 @@ +/* + * Copyright 2007-11 Advanced Micro Devices, Inc. + * Copyright 2008 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: Dave Airlie + * Alex Deucher + */ +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "amdgpu_connectors.h" +#include "atom.h" +#include "atombios_encoders.h" +#include "atombios_dp.h" +#include <linux/backlight.h> +#include "bif/bif_4_1_d.h" + +static u8 +amdgpu_atombios_encoder_get_backlight_level_from_reg(struct amdgpu_device *adev) +{ + u8 backlight_level; + u32 bios_2_scratch; + + bios_2_scratch = RREG32(mmBIOS_SCRATCH_2); + + backlight_level = ((bios_2_scratch & ATOM_S2_CURRENT_BL_LEVEL_MASK) >> + ATOM_S2_CURRENT_BL_LEVEL_SHIFT); + + return backlight_level; +} + +static void +amdgpu_atombios_encoder_set_backlight_level_to_reg(struct amdgpu_device *adev, + u8 backlight_level) +{ + u32 bios_2_scratch; + + bios_2_scratch = RREG32(mmBIOS_SCRATCH_2); + + bios_2_scratch &= ~ATOM_S2_CURRENT_BL_LEVEL_MASK; + bios_2_scratch |= ((backlight_level << ATOM_S2_CURRENT_BL_LEVEL_SHIFT) & + ATOM_S2_CURRENT_BL_LEVEL_MASK); + + WREG32(mmBIOS_SCRATCH_2, bios_2_scratch); +} + +u8 +amdgpu_atombios_encoder_get_backlight_level(struct amdgpu_encoder *amdgpu_encoder) +{ + struct drm_device *dev = amdgpu_encoder->base.dev; + struct amdgpu_device *adev = dev->dev_private; + + if (!(adev->mode_info.firmware_flags & ATOM_BIOS_INFO_BL_CONTROLLED_BY_GPU)) + return 0; + + return amdgpu_atombios_encoder_get_backlight_level_from_reg(adev); +} + +void +amdgpu_atombios_encoder_set_backlight_level(struct amdgpu_encoder *amdgpu_encoder, + u8 level) +{ + struct drm_encoder *encoder = &amdgpu_encoder->base; + struct drm_device *dev = amdgpu_encoder->base.dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder_atom_dig *dig; + + if (!(adev->mode_info.firmware_flags & ATOM_BIOS_INFO_BL_CONTROLLED_BY_GPU)) + return; + + if ((amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) && + amdgpu_encoder->enc_priv) { + dig = amdgpu_encoder->enc_priv; + dig->backlight_level = level; + amdgpu_atombios_encoder_set_backlight_level_to_reg(adev, dig->backlight_level); + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + if (dig->backlight_level == 0) + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, + ATOM_TRANSMITTER_ACTION_LCD_BLOFF, 0, 0); + else { + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, + ATOM_TRANSMITTER_ACTION_BL_BRIGHTNESS_CONTROL, 0, 0); + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, + ATOM_TRANSMITTER_ACTION_LCD_BLON, 0, 0); + } + break; + default: + break; + } + } +} + +#if defined(CONFIG_BACKLIGHT_CLASS_DEVICE) || defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE) + +static u8 amdgpu_atombios_encoder_backlight_level(struct backlight_device *bd) +{ + u8 level; + + /* Convert brightness to hardware level */ + if (bd->props.brightness < 0) + level = 0; + else if (bd->props.brightness > AMDGPU_MAX_BL_LEVEL) + level = AMDGPU_MAX_BL_LEVEL; + else + level = bd->props.brightness; + + return level; +} + +static int amdgpu_atombios_encoder_update_backlight_status(struct backlight_device *bd) +{ + struct amdgpu_backlight_privdata *pdata = bl_get_data(bd); + struct amdgpu_encoder *amdgpu_encoder = pdata->encoder; + + amdgpu_atombios_encoder_set_backlight_level(amdgpu_encoder, + amdgpu_atombios_encoder_backlight_level(bd)); + + return 0; +} + +static int +amdgpu_atombios_encoder_get_backlight_brightness(struct backlight_device *bd) +{ + struct amdgpu_backlight_privdata *pdata = bl_get_data(bd); + struct amdgpu_encoder *amdgpu_encoder = pdata->encoder; + struct drm_device *dev = amdgpu_encoder->base.dev; + struct amdgpu_device *adev = dev->dev_private; + + return amdgpu_atombios_encoder_get_backlight_level_from_reg(adev); +} + +static const struct backlight_ops amdgpu_atombios_encoder_backlight_ops = { + .get_brightness = amdgpu_atombios_encoder_get_backlight_brightness, + .update_status = amdgpu_atombios_encoder_update_backlight_status, +}; + +void amdgpu_atombios_encoder_init_backlight(struct amdgpu_encoder *amdgpu_encoder, + struct drm_connector *drm_connector) +{ + struct drm_device *dev = amdgpu_encoder->base.dev; + struct amdgpu_device *adev = dev->dev_private; + struct backlight_device *bd; + struct backlight_properties props; + struct amdgpu_backlight_privdata *pdata; + struct amdgpu_encoder_atom_dig *dig; + u8 backlight_level; + char bl_name[16]; + + /* Mac laptops with multiple GPUs use the gmux driver for backlight + * so don't register a backlight device + */ + if ((adev->pdev->subsystem_vendor == PCI_VENDOR_ID_APPLE) && + (adev->pdev->device == 0x6741)) + return; + + if (!amdgpu_encoder->enc_priv) + return; + + if (!adev->is_atom_bios) + return; + + if (!(adev->mode_info.firmware_flags & ATOM_BIOS_INFO_BL_CONTROLLED_BY_GPU)) + return; + + pdata = kmalloc(sizeof(struct amdgpu_backlight_privdata), GFP_KERNEL); + if (!pdata) { + DRM_ERROR("Memory allocation failed\n"); + goto error; + } + + memset(&props, 0, sizeof(props)); + props.max_brightness = AMDGPU_MAX_BL_LEVEL; + props.type = BACKLIGHT_RAW; + snprintf(bl_name, sizeof(bl_name), + "amdgpu_bl%d", dev->primary->index); + bd = backlight_device_register(bl_name, drm_connector->kdev, + pdata, &amdgpu_atombios_encoder_backlight_ops, &props); + if (IS_ERR(bd)) { + DRM_ERROR("Backlight registration failed\n"); + goto error; + } + + pdata->encoder = amdgpu_encoder; + + backlight_level = amdgpu_atombios_encoder_get_backlight_level_from_reg(adev); + + dig = amdgpu_encoder->enc_priv; + dig->bl_dev = bd; + + bd->props.brightness = amdgpu_atombios_encoder_get_backlight_brightness(bd); + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + DRM_INFO("amdgpu atom DIG backlight initialized\n"); + + return; + +error: + kfree(pdata); + return; +} + +void +amdgpu_atombios_encoder_fini_backlight(struct amdgpu_encoder *amdgpu_encoder) +{ + struct drm_device *dev = amdgpu_encoder->base.dev; + struct amdgpu_device *adev = dev->dev_private; + struct backlight_device *bd = NULL; + struct amdgpu_encoder_atom_dig *dig; + + if (!amdgpu_encoder->enc_priv) + return; + + if (!adev->is_atom_bios) + return; + + if (!(adev->mode_info.firmware_flags & ATOM_BIOS_INFO_BL_CONTROLLED_BY_GPU)) + return; + + dig = amdgpu_encoder->enc_priv; + bd = dig->bl_dev; + dig->bl_dev = NULL; + + if (bd) { + struct amdgpu_legacy_backlight_privdata *pdata; + + pdata = bl_get_data(bd); + backlight_device_unregister(bd); + kfree(pdata); + + DRM_INFO("amdgpu atom LVDS backlight unloaded\n"); + } +} + +#else /* !CONFIG_BACKLIGHT_CLASS_DEVICE */ + +void amdgpu_atombios_encoder_init_backlight(struct amdgpu_encoder *encoder) +{ +} + +void amdgpu_atombios_encoder_fini_backlight(struct amdgpu_encoder *encoder) +{ +} + +#endif + +bool amdgpu_atombios_encoder_is_digital(struct drm_encoder *encoder) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3: + return true; + default: + return false; + } +} + +bool amdgpu_atombios_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + + /* set the active encoder to connector routing */ + amdgpu_encoder_set_active_device(encoder); + drm_mode_set_crtcinfo(adjusted_mode, 0); + + /* hw bug */ + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) + && (mode->crtc_vsync_start < (mode->crtc_vdisplay + 2))) + adjusted_mode->crtc_vsync_start = adjusted_mode->crtc_vdisplay + 2; + + /* get the native mode for scaling */ + if (amdgpu_encoder->active_device & (ATOM_DEVICE_LCD_SUPPORT)) + amdgpu_panel_mode_fixup(encoder, adjusted_mode); + else if (amdgpu_encoder->rmx_type != RMX_OFF) + amdgpu_panel_mode_fixup(encoder, adjusted_mode); + + if ((amdgpu_encoder->active_device & (ATOM_DEVICE_DFP_SUPPORT | ATOM_DEVICE_LCD_SUPPORT)) || + (amdgpu_encoder_get_dp_bridge_encoder_id(encoder) != ENCODER_OBJECT_ID_NONE)) { + struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder); + amdgpu_atombios_dp_set_link_config(connector, adjusted_mode); + } + + return true; +} + +static void +amdgpu_atombios_encoder_setup_dac(struct drm_encoder *encoder, int action) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + DAC_ENCODER_CONTROL_PS_ALLOCATION args; + int index = 0; + + memset(&args, 0, sizeof(args)); + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_DAC1: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1: + index = GetIndexIntoMasterTable(COMMAND, DAC1EncoderControl); + break; + case ENCODER_OBJECT_ID_INTERNAL_DAC2: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2: + index = GetIndexIntoMasterTable(COMMAND, DAC2EncoderControl); + break; + } + + args.ucAction = action; + args.ucDacStandard = ATOM_DAC1_PS2; + args.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + +} + +static u8 amdgpu_atombios_encoder_get_bpc(struct drm_encoder *encoder) +{ + int bpc = 8; + + if (encoder->crtc) { + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(encoder->crtc); + bpc = amdgpu_crtc->bpc; + } + + switch (bpc) { + case 0: + return PANEL_BPC_UNDEFINE; + case 6: + return PANEL_6BIT_PER_COLOR; + case 8: + default: + return PANEL_8BIT_PER_COLOR; + case 10: + return PANEL_10BIT_PER_COLOR; + case 12: + return PANEL_12BIT_PER_COLOR; + case 16: + return PANEL_16BIT_PER_COLOR; + } +} + +union dvo_encoder_control { + ENABLE_EXTERNAL_TMDS_ENCODER_PS_ALLOCATION ext_tmds; + DVO_ENCODER_CONTROL_PS_ALLOCATION dvo; + DVO_ENCODER_CONTROL_PS_ALLOCATION_V3 dvo_v3; + DVO_ENCODER_CONTROL_PS_ALLOCATION_V1_4 dvo_v4; +}; + +static void +amdgpu_atombios_encoder_setup_dvo(struct drm_encoder *encoder, int action) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + union dvo_encoder_control args; + int index = GetIndexIntoMasterTable(COMMAND, DVOEncoderControl); + uint8_t frev, crev; + + memset(&args, 0, sizeof(args)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return; + + switch (frev) { + case 1: + switch (crev) { + case 1: + /* R4xx, R5xx */ + args.ext_tmds.sXTmdsEncoder.ucEnable = action; + + if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.ext_tmds.sXTmdsEncoder.ucMisc |= PANEL_ENCODER_MISC_DUAL; + + args.ext_tmds.sXTmdsEncoder.ucMisc |= ATOM_PANEL_MISC_888RGB; + break; + case 2: + /* RS600/690/740 */ + args.dvo.sDVOEncoder.ucAction = action; + args.dvo.sDVOEncoder.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + /* DFP1, CRT1, TV1 depending on the type of port */ + args.dvo.sDVOEncoder.ucDeviceType = ATOM_DEVICE_DFP1_INDEX; + + if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.dvo.sDVOEncoder.usDevAttr.sDigAttrib.ucAttribute |= PANEL_ENCODER_MISC_DUAL; + break; + case 3: + /* R6xx */ + args.dvo_v3.ucAction = action; + args.dvo_v3.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + args.dvo_v3.ucDVOConfig = 0; /* XXX */ + break; + case 4: + /* DCE8 */ + args.dvo_v4.ucAction = action; + args.dvo_v4.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + args.dvo_v4.ucDVOConfig = 0; /* XXX */ + args.dvo_v4.ucBitPerColor = amdgpu_atombios_encoder_get_bpc(encoder); + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + break; + } + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + break; + } + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +int amdgpu_atombios_encoder_get_encoder_mode(struct drm_encoder *encoder) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_connector *connector; + struct amdgpu_connector *amdgpu_connector; + struct amdgpu_connector_atom_dig *dig_connector; + + /* dp bridges are always DP */ + if (amdgpu_encoder_get_dp_bridge_encoder_id(encoder) != ENCODER_OBJECT_ID_NONE) + return ATOM_ENCODER_MODE_DP; + + /* DVO is always DVO */ + if ((amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DVO1) || + (amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1)) + return ATOM_ENCODER_MODE_DVO; + + connector = amdgpu_get_connector_for_encoder(encoder); + /* if we don't have an active device yet, just use one of + * the connectors tied to the encoder. + */ + if (!connector) + connector = amdgpu_get_connector_for_encoder_init(encoder); + amdgpu_connector = to_amdgpu_connector(connector); + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_HDMIB: /* HDMI-B is basically DL-DVI; analog works fine */ + if (amdgpu_audio != 0) { + if (amdgpu_connector->use_digital && + (amdgpu_connector->audio == AMDGPU_AUDIO_ENABLE)) + return ATOM_ENCODER_MODE_HDMI; + else if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector)) && + (amdgpu_connector->audio == AMDGPU_AUDIO_AUTO)) + return ATOM_ENCODER_MODE_HDMI; + else if (amdgpu_connector->use_digital) + return ATOM_ENCODER_MODE_DVI; + else + return ATOM_ENCODER_MODE_CRT; + } else if (amdgpu_connector->use_digital) { + return ATOM_ENCODER_MODE_DVI; + } else { + return ATOM_ENCODER_MODE_CRT; + } + break; + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_HDMIA: + default: + if (amdgpu_audio != 0) { + if (amdgpu_connector->audio == AMDGPU_AUDIO_ENABLE) + return ATOM_ENCODER_MODE_HDMI; + else if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector)) && + (amdgpu_connector->audio == AMDGPU_AUDIO_AUTO)) + return ATOM_ENCODER_MODE_HDMI; + else + return ATOM_ENCODER_MODE_DVI; + } else { + return ATOM_ENCODER_MODE_DVI; + } + break; + case DRM_MODE_CONNECTOR_LVDS: + return ATOM_ENCODER_MODE_LVDS; + break; + case DRM_MODE_CONNECTOR_DisplayPort: + dig_connector = amdgpu_connector->con_priv; + if ((dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_DISPLAYPORT) || + (dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP)) { + return ATOM_ENCODER_MODE_DP; + } else if (amdgpu_audio != 0) { + if (amdgpu_connector->audio == AMDGPU_AUDIO_ENABLE) + return ATOM_ENCODER_MODE_HDMI; + else if (drm_detect_hdmi_monitor(amdgpu_connector_edid(connector)) && + (amdgpu_connector->audio == AMDGPU_AUDIO_AUTO)) + return ATOM_ENCODER_MODE_HDMI; + else + return ATOM_ENCODER_MODE_DVI; + } else { + return ATOM_ENCODER_MODE_DVI; + } + break; + case DRM_MODE_CONNECTOR_eDP: + return ATOM_ENCODER_MODE_DP; + case DRM_MODE_CONNECTOR_DVIA: + case DRM_MODE_CONNECTOR_VGA: + return ATOM_ENCODER_MODE_CRT; + break; + case DRM_MODE_CONNECTOR_Composite: + case DRM_MODE_CONNECTOR_SVIDEO: + case DRM_MODE_CONNECTOR_9PinDIN: + /* fix me */ + return ATOM_ENCODER_MODE_TV; + /*return ATOM_ENCODER_MODE_CV;*/ + break; + } +} + +/* + * DIG Encoder/Transmitter Setup + * + * DCE 6.0 + * - 3 DIG transmitter blocks UNIPHY0/1/2 (links A and B). + * Supports up to 6 digital outputs + * - 6 DIG encoder blocks. + * - DIG to PHY mapping is hardcoded + * DIG1 drives UNIPHY0 link A, A+B + * DIG2 drives UNIPHY0 link B + * DIG3 drives UNIPHY1 link A, A+B + * DIG4 drives UNIPHY1 link B + * DIG5 drives UNIPHY2 link A, A+B + * DIG6 drives UNIPHY2 link B + * + * Routing + * crtc -> dig encoder -> UNIPHY/LVTMA (1 or 2 links) + * Examples: + * crtc0 -> dig2 -> LVTMA links A+B -> TMDS/HDMI + * crtc1 -> dig1 -> UNIPHY0 link B -> DP + * crtc0 -> dig1 -> UNIPHY2 link A -> LVDS + * crtc1 -> dig2 -> UNIPHY1 link B+A -> TMDS/HDMI + */ + +union dig_encoder_control { + DIG_ENCODER_CONTROL_PS_ALLOCATION v1; + DIG_ENCODER_CONTROL_PARAMETERS_V2 v2; + DIG_ENCODER_CONTROL_PARAMETERS_V3 v3; + DIG_ENCODER_CONTROL_PARAMETERS_V4 v4; +}; + +void +amdgpu_atombios_encoder_setup_dig_encoder(struct drm_encoder *encoder, + int action, int panel_mode) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; + struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder); + union dig_encoder_control args; + int index = GetIndexIntoMasterTable(COMMAND, DIGxEncoderControl); + uint8_t frev, crev; + int dp_clock = 0; + int dp_lane_count = 0; + int hpd_id = AMDGPU_HPD_NONE; + + if (connector) { + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector = + amdgpu_connector->con_priv; + + dp_clock = dig_connector->dp_clock; + dp_lane_count = dig_connector->dp_lane_count; + hpd_id = amdgpu_connector->hpd.hpd; + } + + /* no dig encoder assigned */ + if (dig->dig_encoder == -1) + return; + + memset(&args, 0, sizeof(args)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return; + + switch (frev) { + case 1: + switch (crev) { + case 1: + args.v1.ucAction = action; + args.v1.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + if (action == ATOM_ENCODER_CMD_SETUP_PANEL_MODE) + args.v3.ucPanelMode = panel_mode; + else + args.v1.ucEncoderMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + + if (ENCODER_MODE_IS_DP(args.v1.ucEncoderMode)) + args.v1.ucLaneNum = dp_lane_count; + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v1.ucLaneNum = 8; + else + args.v1.ucLaneNum = 4; + + if (ENCODER_MODE_IS_DP(args.v1.ucEncoderMode) && (dp_clock == 270000)) + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_DPLINKRATE_2_70GHZ; + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + args.v1.ucConfig = ATOM_ENCODER_CONFIG_V2_TRANSMITTER1; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + args.v1.ucConfig = ATOM_ENCODER_CONFIG_V2_TRANSMITTER2; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + args.v1.ucConfig = ATOM_ENCODER_CONFIG_V2_TRANSMITTER3; + break; + } + if (dig->linkb) + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_LINKB; + else + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_LINKA; + break; + case 2: + case 3: + args.v3.ucAction = action; + args.v3.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + if (action == ATOM_ENCODER_CMD_SETUP_PANEL_MODE) + args.v3.ucPanelMode = panel_mode; + else + args.v3.ucEncoderMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + + if (ENCODER_MODE_IS_DP(args.v3.ucEncoderMode)) + args.v3.ucLaneNum = dp_lane_count; + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v3.ucLaneNum = 8; + else + args.v3.ucLaneNum = 4; + + if (ENCODER_MODE_IS_DP(args.v3.ucEncoderMode) && (dp_clock == 270000)) + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_V3_DPLINKRATE_2_70GHZ; + args.v3.acConfig.ucDigSel = dig->dig_encoder; + args.v3.ucBitPerColor = amdgpu_atombios_encoder_get_bpc(encoder); + break; + case 4: + args.v4.ucAction = action; + args.v4.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + if (action == ATOM_ENCODER_CMD_SETUP_PANEL_MODE) + args.v4.ucPanelMode = panel_mode; + else + args.v4.ucEncoderMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + + if (ENCODER_MODE_IS_DP(args.v4.ucEncoderMode)) + args.v4.ucLaneNum = dp_lane_count; + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v4.ucLaneNum = 8; + else + args.v4.ucLaneNum = 4; + + if (ENCODER_MODE_IS_DP(args.v4.ucEncoderMode)) { + if (dp_clock == 540000) + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_V4_DPLINKRATE_5_40GHZ; + else if (dp_clock == 324000) + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_V4_DPLINKRATE_3_24GHZ; + else if (dp_clock == 270000) + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_V4_DPLINKRATE_2_70GHZ; + else + args.v1.ucConfig |= ATOM_ENCODER_CONFIG_V4_DPLINKRATE_1_62GHZ; + } + args.v4.acConfig.ucDigSel = dig->dig_encoder; + args.v4.ucBitPerColor = amdgpu_atombios_encoder_get_bpc(encoder); + if (hpd_id == AMDGPU_HPD_NONE) + args.v4.ucHPD_ID = 0; + else + args.v4.ucHPD_ID = hpd_id + 1; + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + break; + } + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + break; + } + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + +} + +union dig_transmitter_control { + DIG_TRANSMITTER_CONTROL_PS_ALLOCATION v1; + DIG_TRANSMITTER_CONTROL_PARAMETERS_V2 v2; + DIG_TRANSMITTER_CONTROL_PARAMETERS_V3 v3; + DIG_TRANSMITTER_CONTROL_PARAMETERS_V4 v4; + DIG_TRANSMITTER_CONTROL_PARAMETERS_V1_5 v5; +}; + +void +amdgpu_atombios_encoder_setup_dig_transmitter(struct drm_encoder *encoder, int action, + uint8_t lane_num, uint8_t lane_set) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; + struct drm_connector *connector; + union dig_transmitter_control args; + int index = 0; + uint8_t frev, crev; + bool is_dp = false; + int pll_id = 0; + int dp_clock = 0; + int dp_lane_count = 0; + int connector_object_id = 0; + int igp_lane_info = 0; + int dig_encoder = dig->dig_encoder; + int hpd_id = AMDGPU_HPD_NONE; + + if (action == ATOM_TRANSMITTER_ACTION_INIT) { + connector = amdgpu_get_connector_for_encoder_init(encoder); + /* just needed to avoid bailing in the encoder check. the encoder + * isn't used for init + */ + dig_encoder = 0; + } else + connector = amdgpu_get_connector_for_encoder(encoder); + + if (connector) { + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector = + amdgpu_connector->con_priv; + + hpd_id = amdgpu_connector->hpd.hpd; + dp_clock = dig_connector->dp_clock; + dp_lane_count = dig_connector->dp_lane_count; + connector_object_id = + (amdgpu_connector->connector_object_id & OBJECT_ID_MASK) >> OBJECT_ID_SHIFT; + } + + if (encoder->crtc) { + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(encoder->crtc); + pll_id = amdgpu_crtc->pll_id; + } + + /* no dig encoder assigned */ + if (dig_encoder == -1) + return; + + if (ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(encoder))) + is_dp = true; + + memset(&args, 0, sizeof(args)); + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: + index = GetIndexIntoMasterTable(COMMAND, DVOOutputControl); + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3: + index = GetIndexIntoMasterTable(COMMAND, UNIPHYTransmitterControl); + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + index = GetIndexIntoMasterTable(COMMAND, LVTMATransmitterControl); + break; + } + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return; + + switch (frev) { + case 1: + switch (crev) { + case 1: + args.v1.ucAction = action; + if (action == ATOM_TRANSMITTER_ACTION_INIT) { + args.v1.usInitInfo = cpu_to_le16(connector_object_id); + } else if (action == ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH) { + args.v1.asMode.ucLaneSel = lane_num; + args.v1.asMode.ucLaneSet = lane_set; + } else { + if (is_dp) + args.v1.usPixelClock = cpu_to_le16(dp_clock / 10); + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v1.usPixelClock = cpu_to_le16((amdgpu_encoder->pixel_clock / 2) / 10); + else + args.v1.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + } + + args.v1.ucConfig = ATOM_TRANSMITTER_CONFIG_CLKSRC_PPLL; + + if (dig_encoder) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG2_ENCODER; + else + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_DIG1_ENCODER; + + if ((adev->flags & AMDGPU_IS_APU) && + (amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_UNIPHY)) { + if (is_dp || + !amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) { + if (igp_lane_info & 0x1) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_0_3; + else if (igp_lane_info & 0x2) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_4_7; + else if (igp_lane_info & 0x4) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_8_11; + else if (igp_lane_info & 0x8) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_12_15; + } else { + if (igp_lane_info & 0x3) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_0_7; + else if (igp_lane_info & 0xc) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LANE_8_15; + } + } + + if (dig->linkb) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKB; + else + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_LINKA; + + if (is_dp) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_COHERENT; + else if (amdgpu_encoder->devices & (ATOM_DEVICE_DFP_SUPPORT)) { + if (dig->coherent_mode) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_COHERENT; + if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v1.ucConfig |= ATOM_TRANSMITTER_CONFIG_8LANE_LINK; + } + break; + case 2: + args.v2.ucAction = action; + if (action == ATOM_TRANSMITTER_ACTION_INIT) { + args.v2.usInitInfo = cpu_to_le16(connector_object_id); + } else if (action == ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH) { + args.v2.asMode.ucLaneSel = lane_num; + args.v2.asMode.ucLaneSet = lane_set; + } else { + if (is_dp) + args.v2.usPixelClock = cpu_to_le16(dp_clock / 10); + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v2.usPixelClock = cpu_to_le16((amdgpu_encoder->pixel_clock / 2) / 10); + else + args.v2.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + } + + args.v2.acConfig.ucEncoderSel = dig_encoder; + if (dig->linkb) + args.v2.acConfig.ucLinkSel = 1; + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + args.v2.acConfig.ucTransmitterSel = 0; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + args.v2.acConfig.ucTransmitterSel = 1; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + args.v2.acConfig.ucTransmitterSel = 2; + break; + } + + if (is_dp) { + args.v2.acConfig.fCoherentMode = 1; + args.v2.acConfig.fDPConnector = 1; + } else if (amdgpu_encoder->devices & (ATOM_DEVICE_DFP_SUPPORT)) { + if (dig->coherent_mode) + args.v2.acConfig.fCoherentMode = 1; + if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v2.acConfig.fDualLinkConnector = 1; + } + break; + case 3: + args.v3.ucAction = action; + if (action == ATOM_TRANSMITTER_ACTION_INIT) { + args.v3.usInitInfo = cpu_to_le16(connector_object_id); + } else if (action == ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH) { + args.v3.asMode.ucLaneSel = lane_num; + args.v3.asMode.ucLaneSet = lane_set; + } else { + if (is_dp) + args.v3.usPixelClock = cpu_to_le16(dp_clock / 10); + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v3.usPixelClock = cpu_to_le16((amdgpu_encoder->pixel_clock / 2) / 10); + else + args.v3.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + } + + if (is_dp) + args.v3.ucLaneNum = dp_lane_count; + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v3.ucLaneNum = 8; + else + args.v3.ucLaneNum = 4; + + if (dig->linkb) + args.v3.acConfig.ucLinkSel = 1; + if (dig_encoder & 1) + args.v3.acConfig.ucEncoderSel = 1; + + /* Select the PLL for the PHY + * DP PHY should be clocked from external src if there is + * one. + */ + /* On DCE4, if there is an external clock, it generates the DP ref clock */ + if (is_dp && adev->clock.dp_extclk) + args.v3.acConfig.ucRefClkSource = 2; /* external src */ + else + args.v3.acConfig.ucRefClkSource = pll_id; + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + args.v3.acConfig.ucTransmitterSel = 0; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + args.v3.acConfig.ucTransmitterSel = 1; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + args.v3.acConfig.ucTransmitterSel = 2; + break; + } + + if (is_dp) + args.v3.acConfig.fCoherentMode = 1; /* DP requires coherent */ + else if (amdgpu_encoder->devices & (ATOM_DEVICE_DFP_SUPPORT)) { + if (dig->coherent_mode) + args.v3.acConfig.fCoherentMode = 1; + if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v3.acConfig.fDualLinkConnector = 1; + } + break; + case 4: + args.v4.ucAction = action; + if (action == ATOM_TRANSMITTER_ACTION_INIT) { + args.v4.usInitInfo = cpu_to_le16(connector_object_id); + } else if (action == ATOM_TRANSMITTER_ACTION_SETUP_VSEMPH) { + args.v4.asMode.ucLaneSel = lane_num; + args.v4.asMode.ucLaneSet = lane_set; + } else { + if (is_dp) + args.v4.usPixelClock = cpu_to_le16(dp_clock / 10); + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v4.usPixelClock = cpu_to_le16((amdgpu_encoder->pixel_clock / 2) / 10); + else + args.v4.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + } + + if (is_dp) + args.v4.ucLaneNum = dp_lane_count; + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v4.ucLaneNum = 8; + else + args.v4.ucLaneNum = 4; + + if (dig->linkb) + args.v4.acConfig.ucLinkSel = 1; + if (dig_encoder & 1) + args.v4.acConfig.ucEncoderSel = 1; + + /* Select the PLL for the PHY + * DP PHY should be clocked from external src if there is + * one. + */ + /* On DCE5 DCPLL usually generates the DP ref clock */ + if (is_dp) { + if (adev->clock.dp_extclk) + args.v4.acConfig.ucRefClkSource = ENCODER_REFCLK_SRC_EXTCLK; + else + args.v4.acConfig.ucRefClkSource = ENCODER_REFCLK_SRC_DCPLL; + } else + args.v4.acConfig.ucRefClkSource = pll_id; + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + args.v4.acConfig.ucTransmitterSel = 0; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + args.v4.acConfig.ucTransmitterSel = 1; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + args.v4.acConfig.ucTransmitterSel = 2; + break; + } + + if (is_dp) + args.v4.acConfig.fCoherentMode = 1; /* DP requires coherent */ + else if (amdgpu_encoder->devices & (ATOM_DEVICE_DFP_SUPPORT)) { + if (dig->coherent_mode) + args.v4.acConfig.fCoherentMode = 1; + if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v4.acConfig.fDualLinkConnector = 1; + } + break; + case 5: + args.v5.ucAction = action; + if (is_dp) + args.v5.usSymClock = cpu_to_le16(dp_clock / 10); + else + args.v5.usSymClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + if (dig->linkb) + args.v5.ucPhyId = ATOM_PHY_ID_UNIPHYB; + else + args.v5.ucPhyId = ATOM_PHY_ID_UNIPHYA; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + if (dig->linkb) + args.v5.ucPhyId = ATOM_PHY_ID_UNIPHYD; + else + args.v5.ucPhyId = ATOM_PHY_ID_UNIPHYC; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + if (dig->linkb) + args.v5.ucPhyId = ATOM_PHY_ID_UNIPHYF; + else + args.v5.ucPhyId = ATOM_PHY_ID_UNIPHYE; + break; + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3: + args.v5.ucPhyId = ATOM_PHY_ID_UNIPHYG; + break; + } + if (is_dp) + args.v5.ucLaneNum = dp_lane_count; + else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v5.ucLaneNum = 8; + else + args.v5.ucLaneNum = 4; + args.v5.ucConnObjId = connector_object_id; + args.v5.ucDigMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + + if (is_dp && adev->clock.dp_extclk) + args.v5.asConfig.ucPhyClkSrcId = ENCODER_REFCLK_SRC_EXTCLK; + else + args.v5.asConfig.ucPhyClkSrcId = pll_id; + + if (is_dp) + args.v5.asConfig.ucCoherentMode = 1; /* DP requires coherent */ + else if (amdgpu_encoder->devices & (ATOM_DEVICE_DFP_SUPPORT)) { + if (dig->coherent_mode) + args.v5.asConfig.ucCoherentMode = 1; + } + if (hpd_id == AMDGPU_HPD_NONE) + args.v5.asConfig.ucHPDSel = 0; + else + args.v5.asConfig.ucHPDSel = hpd_id + 1; + args.v5.ucDigEncoderSel = 1 << dig_encoder; + args.v5.ucDPLaneSet = lane_set; + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + break; + } + break; + default: + DRM_ERROR("Unknown table version %d, %d\n", frev, crev); + break; + } + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +bool +amdgpu_atombios_encoder_set_edp_panel_power(struct drm_connector *connector, + int action) +{ + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct drm_device *dev = amdgpu_connector->base.dev; + struct amdgpu_device *adev = dev->dev_private; + union dig_transmitter_control args; + int index = GetIndexIntoMasterTable(COMMAND, UNIPHYTransmitterControl); + uint8_t frev, crev; + + if (connector->connector_type != DRM_MODE_CONNECTOR_eDP) + goto done; + + if ((action != ATOM_TRANSMITTER_ACTION_POWER_ON) && + (action != ATOM_TRANSMITTER_ACTION_POWER_OFF)) + goto done; + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + goto done; + + memset(&args, 0, sizeof(args)); + + args.v1.ucAction = action; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + /* wait for the panel to power up */ + if (action == ATOM_TRANSMITTER_ACTION_POWER_ON) { + int i; + + for (i = 0; i < 300; i++) { + if (amdgpu_display_hpd_sense(adev, amdgpu_connector->hpd.hpd)) + return true; + mdelay(1); + } + return false; + } +done: + return true; +} + +union external_encoder_control { + EXTERNAL_ENCODER_CONTROL_PS_ALLOCATION v1; + EXTERNAL_ENCODER_CONTROL_PS_ALLOCATION_V3 v3; +}; + +static void +amdgpu_atombios_encoder_setup_external_encoder(struct drm_encoder *encoder, + struct drm_encoder *ext_encoder, + int action) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_encoder *ext_amdgpu_encoder = to_amdgpu_encoder(ext_encoder); + union external_encoder_control args; + struct drm_connector *connector; + int index = GetIndexIntoMasterTable(COMMAND, ExternalEncoderControl); + u8 frev, crev; + int dp_clock = 0; + int dp_lane_count = 0; + int connector_object_id = 0; + u32 ext_enum = (ext_amdgpu_encoder->encoder_enum & ENUM_ID_MASK) >> ENUM_ID_SHIFT; + + if (action == EXTERNAL_ENCODER_ACTION_V3_ENCODER_INIT) + connector = amdgpu_get_connector_for_encoder_init(encoder); + else + connector = amdgpu_get_connector_for_encoder(encoder); + + if (connector) { + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct amdgpu_connector_atom_dig *dig_connector = + amdgpu_connector->con_priv; + + dp_clock = dig_connector->dp_clock; + dp_lane_count = dig_connector->dp_lane_count; + connector_object_id = + (amdgpu_connector->connector_object_id & OBJECT_ID_MASK) >> OBJECT_ID_SHIFT; + } + + memset(&args, 0, sizeof(args)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return; + + switch (frev) { + case 1: + /* no params on frev 1 */ + break; + case 2: + switch (crev) { + case 1: + case 2: + args.v1.sDigEncoder.ucAction = action; + args.v1.sDigEncoder.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + args.v1.sDigEncoder.ucEncoderMode = + amdgpu_atombios_encoder_get_encoder_mode(encoder); + + if (ENCODER_MODE_IS_DP(args.v1.sDigEncoder.ucEncoderMode)) { + if (dp_clock == 270000) + args.v1.sDigEncoder.ucConfig |= ATOM_ENCODER_CONFIG_DPLINKRATE_2_70GHZ; + args.v1.sDigEncoder.ucLaneNum = dp_lane_count; + } else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v1.sDigEncoder.ucLaneNum = 8; + else + args.v1.sDigEncoder.ucLaneNum = 4; + break; + case 3: + args.v3.sExtEncoder.ucAction = action; + if (action == EXTERNAL_ENCODER_ACTION_V3_ENCODER_INIT) + args.v3.sExtEncoder.usConnectorId = cpu_to_le16(connector_object_id); + else + args.v3.sExtEncoder.usPixelClock = cpu_to_le16(amdgpu_encoder->pixel_clock / 10); + args.v3.sExtEncoder.ucEncoderMode = + amdgpu_atombios_encoder_get_encoder_mode(encoder); + + if (ENCODER_MODE_IS_DP(args.v3.sExtEncoder.ucEncoderMode)) { + if (dp_clock == 270000) + args.v3.sExtEncoder.ucConfig |= EXTERNAL_ENCODER_CONFIG_V3_DPLINKRATE_2_70GHZ; + else if (dp_clock == 540000) + args.v3.sExtEncoder.ucConfig |= EXTERNAL_ENCODER_CONFIG_V3_DPLINKRATE_5_40GHZ; + args.v3.sExtEncoder.ucLaneNum = dp_lane_count; + } else if (amdgpu_dig_monitor_is_duallink(encoder, amdgpu_encoder->pixel_clock)) + args.v3.sExtEncoder.ucLaneNum = 8; + else + args.v3.sExtEncoder.ucLaneNum = 4; + switch (ext_enum) { + case GRAPH_OBJECT_ENUM_ID1: + args.v3.sExtEncoder.ucConfig |= EXTERNAL_ENCODER_CONFIG_V3_ENCODER1; + break; + case GRAPH_OBJECT_ENUM_ID2: + args.v3.sExtEncoder.ucConfig |= EXTERNAL_ENCODER_CONFIG_V3_ENCODER2; + break; + case GRAPH_OBJECT_ENUM_ID3: + args.v3.sExtEncoder.ucConfig |= EXTERNAL_ENCODER_CONFIG_V3_ENCODER3; + break; + } + args.v3.sExtEncoder.ucBitPerColor = amdgpu_atombios_encoder_get_bpc(encoder); + break; + default: + DRM_ERROR("Unknown table version: %d, %d\n", frev, crev); + return; + } + break; + default: + DRM_ERROR("Unknown table version: %d, %d\n", frev, crev); + return; + } + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +static void +amdgpu_atombios_encoder_setup_dig(struct drm_encoder *encoder, int action) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_encoder *ext_encoder = amdgpu_get_external_encoder(encoder); + struct amdgpu_encoder_atom_dig *dig = amdgpu_encoder->enc_priv; + struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder); + struct amdgpu_connector *amdgpu_connector = NULL; + struct amdgpu_connector_atom_dig *amdgpu_dig_connector = NULL; + + if (connector) { + amdgpu_connector = to_amdgpu_connector(connector); + amdgpu_dig_connector = amdgpu_connector->con_priv; + } + + if (action == ATOM_ENABLE) { + if (!connector) + dig->panel_mode = DP_PANEL_MODE_EXTERNAL_DP_MODE; + else + dig->panel_mode = amdgpu_atombios_dp_get_panel_mode(encoder, connector); + + /* setup and enable the encoder */ + amdgpu_atombios_encoder_setup_dig_encoder(encoder, ATOM_ENCODER_CMD_SETUP, 0); + amdgpu_atombios_encoder_setup_dig_encoder(encoder, + ATOM_ENCODER_CMD_SETUP_PANEL_MODE, + dig->panel_mode); + if (ext_encoder) + amdgpu_atombios_encoder_setup_external_encoder(encoder, ext_encoder, + EXTERNAL_ENCODER_ACTION_V3_ENCODER_SETUP); + if (ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(encoder)) && + connector) { + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + amdgpu_atombios_encoder_set_edp_panel_power(connector, + ATOM_TRANSMITTER_ACTION_POWER_ON); + amdgpu_dig_connector->edp_on = true; + } + } + /* enable the transmitter */ + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, + ATOM_TRANSMITTER_ACTION_ENABLE, + 0, 0); + if (ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(encoder)) && + connector) { + /* DP_SET_POWER_D0 is set in amdgpu_atombios_dp_link_train */ + amdgpu_atombios_dp_link_train(encoder, connector); + amdgpu_atombios_encoder_setup_dig_encoder(encoder, ATOM_ENCODER_CMD_DP_VIDEO_ON, 0); + } + if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, + ATOM_TRANSMITTER_ACTION_LCD_BLON, 0, 0); + if (ext_encoder) + amdgpu_atombios_encoder_setup_external_encoder(encoder, ext_encoder, ATOM_ENABLE); + } else { + if (ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(encoder)) && + connector) + amdgpu_atombios_encoder_setup_dig_encoder(encoder, + ATOM_ENCODER_CMD_DP_VIDEO_OFF, 0); + if (ext_encoder) + amdgpu_atombios_encoder_setup_external_encoder(encoder, ext_encoder, ATOM_DISABLE); + if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, + ATOM_TRANSMITTER_ACTION_LCD_BLOFF, 0, 0); + + if (ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(encoder)) && + connector) + amdgpu_atombios_dp_set_rx_power_state(connector, DP_SET_POWER_D3); + /* disable the transmitter */ + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, + ATOM_TRANSMITTER_ACTION_DISABLE, 0, 0); + if (ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(encoder)) && + connector) { + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + amdgpu_atombios_encoder_set_edp_panel_power(connector, + ATOM_TRANSMITTER_ACTION_POWER_OFF); + amdgpu_dig_connector->edp_on = false; + } + } + } +} + +void +amdgpu_atombios_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + + DRM_DEBUG_KMS("encoder dpms %d to mode %d, devices %08x, active_devices %08x\n", + amdgpu_encoder->encoder_id, mode, amdgpu_encoder->devices, + amdgpu_encoder->active_device); + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3: + switch (mode) { + case DRM_MODE_DPMS_ON: + amdgpu_atombios_encoder_setup_dig(encoder, ATOM_ENABLE); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + amdgpu_atombios_encoder_setup_dig(encoder, ATOM_DISABLE); + break; + } + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: + switch (mode) { + case DRM_MODE_DPMS_ON: + amdgpu_atombios_encoder_setup_dvo(encoder, ATOM_ENABLE); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + amdgpu_atombios_encoder_setup_dvo(encoder, ATOM_DISABLE); + break; + } + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1: + switch (mode) { + case DRM_MODE_DPMS_ON: + amdgpu_atombios_encoder_setup_dac(encoder, ATOM_ENABLE); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + amdgpu_atombios_encoder_setup_dac(encoder, ATOM_DISABLE); + break; + } + break; + default: + return; + } +} + +union crtc_source_param { + SELECT_CRTC_SOURCE_PS_ALLOCATION v1; + SELECT_CRTC_SOURCE_PARAMETERS_V2 v2; + SELECT_CRTC_SOURCE_PARAMETERS_V3 v3; +}; + +void +amdgpu_atombios_encoder_set_crtc_source(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(encoder->crtc); + union crtc_source_param args; + int index = GetIndexIntoMasterTable(COMMAND, SelectCRTC_Source); + uint8_t frev, crev; + struct amdgpu_encoder_atom_dig *dig; + + memset(&args, 0, sizeof(args)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return; + + switch (frev) { + case 1: + switch (crev) { + case 1: + default: + args.v1.ucCRTC = amdgpu_crtc->crtc_id; + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_TMDS1: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_TMDS1: + args.v1.ucDevice = ATOM_DEVICE_DFP1_INDEX; + break; + case ENCODER_OBJECT_ID_INTERNAL_LVDS: + case ENCODER_OBJECT_ID_INTERNAL_LVTM1: + if (amdgpu_encoder->devices & ATOM_DEVICE_LCD1_SUPPORT) + args.v1.ucDevice = ATOM_DEVICE_LCD1_INDEX; + else + args.v1.ucDevice = ATOM_DEVICE_DFP3_INDEX; + break; + case ENCODER_OBJECT_ID_INTERNAL_DVO1: + case ENCODER_OBJECT_ID_INTERNAL_DDI: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: + args.v1.ucDevice = ATOM_DEVICE_DFP2_INDEX; + break; + case ENCODER_OBJECT_ID_INTERNAL_DAC1: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1: + if (amdgpu_encoder->active_device & (ATOM_DEVICE_TV_SUPPORT)) + args.v1.ucDevice = ATOM_DEVICE_TV1_INDEX; + else if (amdgpu_encoder->active_device & (ATOM_DEVICE_CV_SUPPORT)) + args.v1.ucDevice = ATOM_DEVICE_CV_INDEX; + else + args.v1.ucDevice = ATOM_DEVICE_CRT1_INDEX; + break; + case ENCODER_OBJECT_ID_INTERNAL_DAC2: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2: + if (amdgpu_encoder->active_device & (ATOM_DEVICE_TV_SUPPORT)) + args.v1.ucDevice = ATOM_DEVICE_TV1_INDEX; + else if (amdgpu_encoder->active_device & (ATOM_DEVICE_CV_SUPPORT)) + args.v1.ucDevice = ATOM_DEVICE_CV_INDEX; + else + args.v1.ucDevice = ATOM_DEVICE_CRT2_INDEX; + break; + } + break; + case 2: + args.v2.ucCRTC = amdgpu_crtc->crtc_id; + if (amdgpu_encoder_get_dp_bridge_encoder_id(encoder) != ENCODER_OBJECT_ID_NONE) { + struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder); + + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) + args.v2.ucEncodeMode = ATOM_ENCODER_MODE_LVDS; + else if (connector->connector_type == DRM_MODE_CONNECTOR_VGA) + args.v2.ucEncodeMode = ATOM_ENCODER_MODE_CRT; + else + args.v2.ucEncodeMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + } else if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) { + args.v2.ucEncodeMode = ATOM_ENCODER_MODE_LVDS; + } else { + args.v2.ucEncodeMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + } + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + dig = amdgpu_encoder->enc_priv; + switch (dig->dig_encoder) { + case 0: + args.v2.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID; + break; + case 1: + args.v2.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID; + break; + case 2: + args.v2.ucEncoderID = ASIC_INT_DIG3_ENCODER_ID; + break; + case 3: + args.v2.ucEncoderID = ASIC_INT_DIG4_ENCODER_ID; + break; + case 4: + args.v2.ucEncoderID = ASIC_INT_DIG5_ENCODER_ID; + break; + case 5: + args.v2.ucEncoderID = ASIC_INT_DIG6_ENCODER_ID; + break; + case 6: + args.v2.ucEncoderID = ASIC_INT_DIG7_ENCODER_ID; + break; + } + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: + args.v2.ucEncoderID = ASIC_INT_DVO_ENCODER_ID; + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1: + if (amdgpu_encoder->active_device & (ATOM_DEVICE_TV_SUPPORT)) + args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else if (amdgpu_encoder->active_device & (ATOM_DEVICE_CV_SUPPORT)) + args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else + args.v2.ucEncoderID = ASIC_INT_DAC1_ENCODER_ID; + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2: + if (amdgpu_encoder->active_device & (ATOM_DEVICE_TV_SUPPORT)) + args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else if (amdgpu_encoder->active_device & (ATOM_DEVICE_CV_SUPPORT)) + args.v2.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else + args.v2.ucEncoderID = ASIC_INT_DAC2_ENCODER_ID; + break; + } + break; + case 3: + args.v3.ucCRTC = amdgpu_crtc->crtc_id; + if (amdgpu_encoder_get_dp_bridge_encoder_id(encoder) != ENCODER_OBJECT_ID_NONE) { + struct drm_connector *connector = amdgpu_get_connector_for_encoder(encoder); + + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) + args.v2.ucEncodeMode = ATOM_ENCODER_MODE_LVDS; + else if (connector->connector_type == DRM_MODE_CONNECTOR_VGA) + args.v2.ucEncodeMode = ATOM_ENCODER_MODE_CRT; + else + args.v2.ucEncodeMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + } else if (amdgpu_encoder->devices & (ATOM_DEVICE_LCD_SUPPORT)) { + args.v2.ucEncodeMode = ATOM_ENCODER_MODE_LVDS; + } else { + args.v2.ucEncodeMode = amdgpu_atombios_encoder_get_encoder_mode(encoder); + } + args.v3.ucDstBpc = amdgpu_atombios_encoder_get_bpc(encoder); + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3: + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_LVTMA: + dig = amdgpu_encoder->enc_priv; + switch (dig->dig_encoder) { + case 0: + args.v3.ucEncoderID = ASIC_INT_DIG1_ENCODER_ID; + break; + case 1: + args.v3.ucEncoderID = ASIC_INT_DIG2_ENCODER_ID; + break; + case 2: + args.v3.ucEncoderID = ASIC_INT_DIG3_ENCODER_ID; + break; + case 3: + args.v3.ucEncoderID = ASIC_INT_DIG4_ENCODER_ID; + break; + case 4: + args.v3.ucEncoderID = ASIC_INT_DIG5_ENCODER_ID; + break; + case 5: + args.v3.ucEncoderID = ASIC_INT_DIG6_ENCODER_ID; + break; + case 6: + args.v3.ucEncoderID = ASIC_INT_DIG7_ENCODER_ID; + break; + } + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: + args.v3.ucEncoderID = ASIC_INT_DVO_ENCODER_ID; + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1: + if (amdgpu_encoder->active_device & (ATOM_DEVICE_TV_SUPPORT)) + args.v3.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else if (amdgpu_encoder->active_device & (ATOM_DEVICE_CV_SUPPORT)) + args.v3.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else + args.v3.ucEncoderID = ASIC_INT_DAC1_ENCODER_ID; + break; + case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC2: + if (amdgpu_encoder->active_device & (ATOM_DEVICE_TV_SUPPORT)) + args.v3.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else if (amdgpu_encoder->active_device & (ATOM_DEVICE_CV_SUPPORT)) + args.v3.ucEncoderID = ASIC_INT_TV_ENCODER_ID; + else + args.v3.ucEncoderID = ASIC_INT_DAC2_ENCODER_ID; + break; + } + break; + } + break; + default: + DRM_ERROR("Unknown table version: %d, %d\n", frev, crev); + return; + } + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); +} + +/* This only needs to be called once at startup */ +void +amdgpu_atombios_encoder_init_dig(struct amdgpu_device *adev) +{ + struct drm_device *dev = adev->ddev; + struct drm_encoder *encoder; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_encoder *ext_encoder = amdgpu_get_external_encoder(encoder); + + switch (amdgpu_encoder->encoder_id) { + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY1: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY2: + case ENCODER_OBJECT_ID_INTERNAL_UNIPHY3: + amdgpu_atombios_encoder_setup_dig_transmitter(encoder, ATOM_TRANSMITTER_ACTION_INIT, + 0, 0); + break; + } + + if (ext_encoder) + amdgpu_atombios_encoder_setup_external_encoder(encoder, ext_encoder, + EXTERNAL_ENCODER_ACTION_V3_ENCODER_INIT); + } +} + +static bool +amdgpu_atombios_encoder_dac_load_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + + if (amdgpu_encoder->devices & (ATOM_DEVICE_TV_SUPPORT | + ATOM_DEVICE_CV_SUPPORT | + ATOM_DEVICE_CRT_SUPPORT)) { + DAC_LOAD_DETECTION_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, DAC_LoadDetection); + uint8_t frev, crev; + + memset(&args, 0, sizeof(args)); + + if (!amdgpu_atom_parse_cmd_header(adev->mode_info.atom_context, index, &frev, &crev)) + return false; + + args.sDacload.ucMisc = 0; + + if ((amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_DAC1) || + (amdgpu_encoder->encoder_id == ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DAC1)) + args.sDacload.ucDacType = ATOM_DAC_A; + else + args.sDacload.ucDacType = ATOM_DAC_B; + + if (amdgpu_connector->devices & ATOM_DEVICE_CRT1_SUPPORT) + args.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_CRT1_SUPPORT); + else if (amdgpu_connector->devices & ATOM_DEVICE_CRT2_SUPPORT) + args.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_CRT2_SUPPORT); + else if (amdgpu_connector->devices & ATOM_DEVICE_CV_SUPPORT) { + args.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_CV_SUPPORT); + if (crev >= 3) + args.sDacload.ucMisc = DAC_LOAD_MISC_YPrPb; + } else if (amdgpu_connector->devices & ATOM_DEVICE_TV1_SUPPORT) { + args.sDacload.usDeviceID = cpu_to_le16(ATOM_DEVICE_TV1_SUPPORT); + if (crev >= 3) + args.sDacload.ucMisc = DAC_LOAD_MISC_YPrPb; + } + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + return true; + } else + return false; +} + +enum drm_connector_status +amdgpu_atombios_encoder_dac_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + uint32_t bios_0_scratch; + + if (!amdgpu_atombios_encoder_dac_load_detect(encoder, connector)) { + DRM_DEBUG_KMS("detect returned false \n"); + return connector_status_unknown; + } + + bios_0_scratch = RREG32(mmBIOS_SCRATCH_0); + + DRM_DEBUG_KMS("Bios 0 scratch %x %08x\n", bios_0_scratch, amdgpu_encoder->devices); + if (amdgpu_connector->devices & ATOM_DEVICE_CRT1_SUPPORT) { + if (bios_0_scratch & ATOM_S0_CRT1_MASK) + return connector_status_connected; + } + if (amdgpu_connector->devices & ATOM_DEVICE_CRT2_SUPPORT) { + if (bios_0_scratch & ATOM_S0_CRT2_MASK) + return connector_status_connected; + } + if (amdgpu_connector->devices & ATOM_DEVICE_CV_SUPPORT) { + if (bios_0_scratch & (ATOM_S0_CV_MASK|ATOM_S0_CV_MASK_A)) + return connector_status_connected; + } + if (amdgpu_connector->devices & ATOM_DEVICE_TV1_SUPPORT) { + if (bios_0_scratch & (ATOM_S0_TV1_COMPOSITE | ATOM_S0_TV1_COMPOSITE_A)) + return connector_status_connected; /* CTV */ + else if (bios_0_scratch & (ATOM_S0_TV1_SVIDEO | ATOM_S0_TV1_SVIDEO_A)) + return connector_status_connected; /* STV */ + } + return connector_status_disconnected; +} + +enum drm_connector_status +amdgpu_atombios_encoder_dig_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct amdgpu_connector *amdgpu_connector = to_amdgpu_connector(connector); + struct drm_encoder *ext_encoder = amdgpu_get_external_encoder(encoder); + u32 bios_0_scratch; + + if (!ext_encoder) + return connector_status_unknown; + + if ((amdgpu_connector->devices & ATOM_DEVICE_CRT_SUPPORT) == 0) + return connector_status_unknown; + + /* load detect on the dp bridge */ + amdgpu_atombios_encoder_setup_external_encoder(encoder, ext_encoder, + EXTERNAL_ENCODER_ACTION_V3_DACLOAD_DETECTION); + + bios_0_scratch = RREG32(mmBIOS_SCRATCH_0); + + DRM_DEBUG_KMS("Bios 0 scratch %x %08x\n", bios_0_scratch, amdgpu_encoder->devices); + if (amdgpu_connector->devices & ATOM_DEVICE_CRT1_SUPPORT) { + if (bios_0_scratch & ATOM_S0_CRT1_MASK) + return connector_status_connected; + } + if (amdgpu_connector->devices & ATOM_DEVICE_CRT2_SUPPORT) { + if (bios_0_scratch & ATOM_S0_CRT2_MASK) + return connector_status_connected; + } + if (amdgpu_connector->devices & ATOM_DEVICE_CV_SUPPORT) { + if (bios_0_scratch & (ATOM_S0_CV_MASK|ATOM_S0_CV_MASK_A)) + return connector_status_connected; + } + if (amdgpu_connector->devices & ATOM_DEVICE_TV1_SUPPORT) { + if (bios_0_scratch & (ATOM_S0_TV1_COMPOSITE | ATOM_S0_TV1_COMPOSITE_A)) + return connector_status_connected; /* CTV */ + else if (bios_0_scratch & (ATOM_S0_TV1_SVIDEO | ATOM_S0_TV1_SVIDEO_A)) + return connector_status_connected; /* STV */ + } + return connector_status_disconnected; +} + +void +amdgpu_atombios_encoder_setup_ext_encoder_ddc(struct drm_encoder *encoder) +{ + struct drm_encoder *ext_encoder = amdgpu_get_external_encoder(encoder); + + if (ext_encoder) + /* ddc_setup on the dp bridge */ + amdgpu_atombios_encoder_setup_external_encoder(encoder, ext_encoder, + EXTERNAL_ENCODER_ACTION_V3_DDC_SETUP); + +} + +void +amdgpu_atombios_encoder_set_bios_scratch_regs(struct drm_connector *connector, + struct drm_encoder *encoder, + bool connected) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_connector *amdgpu_connector = + to_amdgpu_connector(connector); + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + uint32_t bios_0_scratch, bios_3_scratch, bios_6_scratch; + + bios_0_scratch = RREG32(mmBIOS_SCRATCH_0); + bios_3_scratch = RREG32(mmBIOS_SCRATCH_3); + bios_6_scratch = RREG32(mmBIOS_SCRATCH_6); + + if ((amdgpu_encoder->devices & ATOM_DEVICE_LCD1_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_LCD1_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("LCD1 connected\n"); + bios_0_scratch |= ATOM_S0_LCD1; + bios_3_scratch |= ATOM_S3_LCD1_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_LCD1; + } else { + DRM_DEBUG_KMS("LCD1 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_LCD1; + bios_3_scratch &= ~ATOM_S3_LCD1_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_LCD1; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_CRT1_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_CRT1_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("CRT1 connected\n"); + bios_0_scratch |= ATOM_S0_CRT1_COLOR; + bios_3_scratch |= ATOM_S3_CRT1_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_CRT1; + } else { + DRM_DEBUG_KMS("CRT1 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_CRT1_MASK; + bios_3_scratch &= ~ATOM_S3_CRT1_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_CRT1; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_CRT2_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_CRT2_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("CRT2 connected\n"); + bios_0_scratch |= ATOM_S0_CRT2_COLOR; + bios_3_scratch |= ATOM_S3_CRT2_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_CRT2; + } else { + DRM_DEBUG_KMS("CRT2 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_CRT2_MASK; + bios_3_scratch &= ~ATOM_S3_CRT2_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_CRT2; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_DFP1_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_DFP1_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("DFP1 connected\n"); + bios_0_scratch |= ATOM_S0_DFP1; + bios_3_scratch |= ATOM_S3_DFP1_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_DFP1; + } else { + DRM_DEBUG_KMS("DFP1 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_DFP1; + bios_3_scratch &= ~ATOM_S3_DFP1_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_DFP1; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_DFP2_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_DFP2_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("DFP2 connected\n"); + bios_0_scratch |= ATOM_S0_DFP2; + bios_3_scratch |= ATOM_S3_DFP2_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_DFP2; + } else { + DRM_DEBUG_KMS("DFP2 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_DFP2; + bios_3_scratch &= ~ATOM_S3_DFP2_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_DFP2; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_DFP3_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_DFP3_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("DFP3 connected\n"); + bios_0_scratch |= ATOM_S0_DFP3; + bios_3_scratch |= ATOM_S3_DFP3_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_DFP3; + } else { + DRM_DEBUG_KMS("DFP3 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_DFP3; + bios_3_scratch &= ~ATOM_S3_DFP3_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_DFP3; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_DFP4_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_DFP4_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("DFP4 connected\n"); + bios_0_scratch |= ATOM_S0_DFP4; + bios_3_scratch |= ATOM_S3_DFP4_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_DFP4; + } else { + DRM_DEBUG_KMS("DFP4 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_DFP4; + bios_3_scratch &= ~ATOM_S3_DFP4_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_DFP4; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_DFP5_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_DFP5_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("DFP5 connected\n"); + bios_0_scratch |= ATOM_S0_DFP5; + bios_3_scratch |= ATOM_S3_DFP5_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_DFP5; + } else { + DRM_DEBUG_KMS("DFP5 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_DFP5; + bios_3_scratch &= ~ATOM_S3_DFP5_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_DFP5; + } + } + if ((amdgpu_encoder->devices & ATOM_DEVICE_DFP6_SUPPORT) && + (amdgpu_connector->devices & ATOM_DEVICE_DFP6_SUPPORT)) { + if (connected) { + DRM_DEBUG_KMS("DFP6 connected\n"); + bios_0_scratch |= ATOM_S0_DFP6; + bios_3_scratch |= ATOM_S3_DFP6_ACTIVE; + bios_6_scratch |= ATOM_S6_ACC_REQ_DFP6; + } else { + DRM_DEBUG_KMS("DFP6 disconnected\n"); + bios_0_scratch &= ~ATOM_S0_DFP6; + bios_3_scratch &= ~ATOM_S3_DFP6_ACTIVE; + bios_6_scratch &= ~ATOM_S6_ACC_REQ_DFP6; + } + } + + WREG32(mmBIOS_SCRATCH_0, bios_0_scratch); + WREG32(mmBIOS_SCRATCH_3, bios_3_scratch); + WREG32(mmBIOS_SCRATCH_6, bios_6_scratch); +} + +union lvds_info { + struct _ATOM_LVDS_INFO info; + struct _ATOM_LVDS_INFO_V12 info_12; +}; + +struct amdgpu_encoder_atom_dig * +amdgpu_atombios_encoder_get_lcd_info(struct amdgpu_encoder *encoder) +{ + struct drm_device *dev = encoder->base.dev; + struct amdgpu_device *adev = dev->dev_private; + struct amdgpu_mode_info *mode_info = &adev->mode_info; + int index = GetIndexIntoMasterTable(DATA, LVDS_Info); + uint16_t data_offset, misc; + union lvds_info *lvds_info; + uint8_t frev, crev; + struct amdgpu_encoder_atom_dig *lvds = NULL; + int encoder_enum = (encoder->encoder_enum & ENUM_ID_MASK) >> ENUM_ID_SHIFT; + + if (amdgpu_atom_parse_data_header(mode_info->atom_context, index, NULL, + &frev, &crev, &data_offset)) { + lvds_info = + (union lvds_info *)(mode_info->atom_context->bios + data_offset); + lvds = + kzalloc(sizeof(struct amdgpu_encoder_atom_dig), GFP_KERNEL); + + if (!lvds) + return NULL; + + lvds->native_mode.clock = + le16_to_cpu(lvds_info->info.sLCDTiming.usPixClk) * 10; + lvds->native_mode.hdisplay = + le16_to_cpu(lvds_info->info.sLCDTiming.usHActive); + lvds->native_mode.vdisplay = + le16_to_cpu(lvds_info->info.sLCDTiming.usVActive); + lvds->native_mode.htotal = lvds->native_mode.hdisplay + + le16_to_cpu(lvds_info->info.sLCDTiming.usHBlanking_Time); + lvds->native_mode.hsync_start = lvds->native_mode.hdisplay + + le16_to_cpu(lvds_info->info.sLCDTiming.usHSyncOffset); + lvds->native_mode.hsync_end = lvds->native_mode.hsync_start + + le16_to_cpu(lvds_info->info.sLCDTiming.usHSyncWidth); + lvds->native_mode.vtotal = lvds->native_mode.vdisplay + + le16_to_cpu(lvds_info->info.sLCDTiming.usVBlanking_Time); + lvds->native_mode.vsync_start = lvds->native_mode.vdisplay + + le16_to_cpu(lvds_info->info.sLCDTiming.usVSyncOffset); + lvds->native_mode.vsync_end = lvds->native_mode.vsync_start + + le16_to_cpu(lvds_info->info.sLCDTiming.usVSyncWidth); + lvds->panel_pwr_delay = + le16_to_cpu(lvds_info->info.usOffDelayInMs); + lvds->lcd_misc = lvds_info->info.ucLVDS_Misc; + + misc = le16_to_cpu(lvds_info->info.sLCDTiming.susModeMiscInfo.usAccess); + if (misc & ATOM_VSYNC_POLARITY) + lvds->native_mode.flags |= DRM_MODE_FLAG_NVSYNC; + if (misc & ATOM_HSYNC_POLARITY) + lvds->native_mode.flags |= DRM_MODE_FLAG_NHSYNC; + if (misc & ATOM_COMPOSITESYNC) + lvds->native_mode.flags |= DRM_MODE_FLAG_CSYNC; + if (misc & ATOM_INTERLACE) + lvds->native_mode.flags |= DRM_MODE_FLAG_INTERLACE; + if (misc & ATOM_DOUBLE_CLOCK_MODE) + lvds->native_mode.flags |= DRM_MODE_FLAG_DBLSCAN; + + lvds->native_mode.width_mm = le16_to_cpu(lvds_info->info.sLCDTiming.usImageHSize); + lvds->native_mode.height_mm = le16_to_cpu(lvds_info->info.sLCDTiming.usImageVSize); + + /* set crtc values */ + drm_mode_set_crtcinfo(&lvds->native_mode, CRTC_INTERLACE_HALVE_V); + + lvds->lcd_ss_id = lvds_info->info.ucSS_Id; + + encoder->native_mode = lvds->native_mode; + + if (encoder_enum == 2) + lvds->linkb = true; + else + lvds->linkb = false; + + /* parse the lcd record table */ + if (le16_to_cpu(lvds_info->info.usModePatchTableOffset)) { + ATOM_FAKE_EDID_PATCH_RECORD *fake_edid_record; + ATOM_PANEL_RESOLUTION_PATCH_RECORD *panel_res_record; + bool bad_record = false; + u8 *record; + + if ((frev == 1) && (crev < 2)) + /* absolute */ + record = (u8 *)(mode_info->atom_context->bios + + le16_to_cpu(lvds_info->info.usModePatchTableOffset)); + else + /* relative */ + record = (u8 *)(mode_info->atom_context->bios + + data_offset + + le16_to_cpu(lvds_info->info.usModePatchTableOffset)); + while (*record != ATOM_RECORD_END_TYPE) { + switch (*record) { + case LCD_MODE_PATCH_RECORD_MODE_TYPE: + record += sizeof(ATOM_PATCH_RECORD_MODE); + break; + case LCD_RTS_RECORD_TYPE: + record += sizeof(ATOM_LCD_RTS_RECORD); + break; + case LCD_CAP_RECORD_TYPE: + record += sizeof(ATOM_LCD_MODE_CONTROL_CAP); + break; + case LCD_FAKE_EDID_PATCH_RECORD_TYPE: + fake_edid_record = (ATOM_FAKE_EDID_PATCH_RECORD *)record; + if (fake_edid_record->ucFakeEDIDLength) { + struct edid *edid; + int edid_size = + max((int)EDID_LENGTH, (int)fake_edid_record->ucFakeEDIDLength); + edid = kmalloc(edid_size, GFP_KERNEL); + if (edid) { + memcpy((u8 *)edid, (u8 *)&fake_edid_record->ucFakeEDIDString[0], + fake_edid_record->ucFakeEDIDLength); + + if (drm_edid_is_valid(edid)) { + adev->mode_info.bios_hardcoded_edid = edid; + adev->mode_info.bios_hardcoded_edid_size = edid_size; + } else + kfree(edid); + } + } + record += fake_edid_record->ucFakeEDIDLength ? + fake_edid_record->ucFakeEDIDLength + 2 : + sizeof(ATOM_FAKE_EDID_PATCH_RECORD); + break; + case LCD_PANEL_RESOLUTION_RECORD_TYPE: + panel_res_record = (ATOM_PANEL_RESOLUTION_PATCH_RECORD *)record; + lvds->native_mode.width_mm = panel_res_record->usHSize; + lvds->native_mode.height_mm = panel_res_record->usVSize; + record += sizeof(ATOM_PANEL_RESOLUTION_PATCH_RECORD); + break; + default: + DRM_ERROR("Bad LCD record %d\n", *record); + bad_record = true; + break; + } + if (bad_record) + break; + } + } + } + return lvds; +} + +struct amdgpu_encoder_atom_dig * +amdgpu_atombios_encoder_get_dig_info(struct amdgpu_encoder *amdgpu_encoder) +{ + int encoder_enum = (amdgpu_encoder->encoder_enum & ENUM_ID_MASK) >> ENUM_ID_SHIFT; + struct amdgpu_encoder_atom_dig *dig = kzalloc(sizeof(struct amdgpu_encoder_atom_dig), GFP_KERNEL); + + if (!dig) + return NULL; + + /* coherent mode by default */ + dig->coherent_mode = true; + dig->dig_encoder = -1; + + if (encoder_enum == 2) + dig->linkb = true; + else + dig->linkb = false; + + return dig; +} + diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_encoders.h b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.h new file mode 100644 index 000000000000..2bdec40515ce --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.h @@ -0,0 +1,73 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __ATOMBIOS_ENCODER_H__ +#define __ATOMBIOS_ENCODER_H__ + +u8 +amdgpu_atombios_encoder_get_backlight_level(struct amdgpu_encoder *amdgpu_encoder); +void +amdgpu_atombios_encoder_set_backlight_level(struct amdgpu_encoder *amdgpu_encoder, + u8 level); +void amdgpu_atombios_encoder_init_backlight(struct amdgpu_encoder *amdgpu_encoder, + struct drm_connector *drm_connector); +void +amdgpu_atombios_encoder_fini_backlight(struct amdgpu_encoder *amdgpu_encoder); +bool amdgpu_atombios_encoder_is_digital(struct drm_encoder *encoder); +bool amdgpu_atombios_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +int amdgpu_atombios_encoder_get_encoder_mode(struct drm_encoder *encoder); +void +amdgpu_atombios_encoder_setup_dig_encoder(struct drm_encoder *encoder, + int action, int panel_mode); +void +amdgpu_atombios_encoder_setup_dig_transmitter(struct drm_encoder *encoder, int action, + uint8_t lane_num, uint8_t lane_set); +bool +amdgpu_atombios_encoder_set_edp_panel_power(struct drm_connector *connector, + int action); +void +amdgpu_atombios_encoder_dpms(struct drm_encoder *encoder, int mode); +void +amdgpu_atombios_encoder_set_crtc_source(struct drm_encoder *encoder); +void +amdgpu_atombios_encoder_init_dig(struct amdgpu_device *adev); +enum drm_connector_status +amdgpu_atombios_encoder_dac_detect(struct drm_encoder *encoder, + struct drm_connector *connector); +enum drm_connector_status +amdgpu_atombios_encoder_dig_detect(struct drm_encoder *encoder, + struct drm_connector *connector); +void +amdgpu_atombios_encoder_setup_ext_encoder_ddc(struct drm_encoder *encoder); +void +amdgpu_atombios_encoder_set_bios_scratch_regs(struct drm_connector *connector, + struct drm_encoder *encoder, + bool connected); +struct amdgpu_encoder_atom_dig * +amdgpu_atombios_encoder_get_lcd_info(struct amdgpu_encoder *encoder); +struct amdgpu_encoder_atom_dig * +amdgpu_atombios_encoder_get_dig_info(struct amdgpu_encoder *amdgpu_encoder); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_i2c.c b/drivers/gpu/drm/amd/amdgpu/atombios_i2c.c new file mode 100644 index 000000000000..13cdb01e9b45 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_i2c.c @@ -0,0 +1,158 @@ +/* + * Copyright 2011 Advanced Micro Devices, 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: Alex Deucher + * + */ +#include <drm/drmP.h> +#include <drm/amdgpu_drm.h> +#include "amdgpu.h" +#include "atom.h" +#include "amdgpu_atombios.h" + +#define TARGET_HW_I2C_CLOCK 50 + +/* these are a limitation of ProcessI2cChannelTransaction not the hw */ +#define ATOM_MAX_HW_I2C_WRITE 3 +#define ATOM_MAX_HW_I2C_READ 255 + +static int amdgpu_atombios_i2c_process_i2c_ch(struct amdgpu_i2c_chan *chan, + u8 slave_addr, u8 flags, + u8 *buf, u8 num) +{ + struct drm_device *dev = chan->dev; + struct amdgpu_device *adev = dev->dev_private; + PROCESS_I2C_CHANNEL_TRANSACTION_PS_ALLOCATION args; + int index = GetIndexIntoMasterTable(COMMAND, ProcessI2cChannelTransaction); + unsigned char *base; + u16 out = cpu_to_le16(0); + int r = 0; + + memset(&args, 0, sizeof(args)); + + mutex_lock(&chan->mutex); + + base = (unsigned char *)adev->mode_info.atom_context->scratch; + + if (flags & HW_I2C_WRITE) { + if (num > ATOM_MAX_HW_I2C_WRITE) { + DRM_ERROR("hw i2c: tried to write too many bytes (%d vs 3)\n", num); + r = -EINVAL; + goto done; + } + if (buf == NULL) + args.ucRegIndex = 0; + else + args.ucRegIndex = buf[0]; + if (num) + num--; + if (num) + memcpy(&out, &buf[1], num); + args.lpI2CDataOut = cpu_to_le16(out); + } else { + if (num > ATOM_MAX_HW_I2C_READ) { + DRM_ERROR("hw i2c: tried to read too many bytes (%d vs 255)\n", num); + r = -EINVAL; + goto done; + } + args.ucRegIndex = 0; + args.lpI2CDataOut = 0; + } + + args.ucFlag = flags; + args.ucI2CSpeed = TARGET_HW_I2C_CLOCK; + args.ucTransBytes = num; + args.ucSlaveAddr = slave_addr << 1; + args.ucLineNumber = chan->rec.i2c_id; + + amdgpu_atom_execute_table(adev->mode_info.atom_context, index, (uint32_t *)&args); + + /* error */ + if (args.ucStatus != HW_ASSISTED_I2C_STATUS_SUCCESS) { + DRM_DEBUG_KMS("hw_i2c error\n"); + r = -EIO; + goto done; + } + + if (!(flags & HW_I2C_WRITE)) + amdgpu_atombios_copy_swap(buf, base, num, false); + +done: + mutex_unlock(&chan->mutex); + + return r; +} + +int amdgpu_atombios_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct amdgpu_i2c_chan *i2c = i2c_get_adapdata(i2c_adap); + struct i2c_msg *p; + int i, remaining, current_count, buffer_offset, max_bytes, ret; + u8 flags; + + /* check for bus probe */ + p = &msgs[0]; + if ((num == 1) && (p->len == 0)) { + ret = amdgpu_atombios_i2c_process_i2c_ch(i2c, + p->addr, HW_I2C_WRITE, + NULL, 0); + if (ret) + return ret; + else + return num; + } + + for (i = 0; i < num; i++) { + p = &msgs[i]; + remaining = p->len; + buffer_offset = 0; + /* max_bytes are a limitation of ProcessI2cChannelTransaction not the hw */ + if (p->flags & I2C_M_RD) { + max_bytes = ATOM_MAX_HW_I2C_READ; + flags = HW_I2C_READ; + } else { + max_bytes = ATOM_MAX_HW_I2C_WRITE; + flags = HW_I2C_WRITE; + } + while (remaining) { + if (remaining > max_bytes) + current_count = max_bytes; + else + current_count = remaining; + ret = amdgpu_atombios_i2c_process_i2c_ch(i2c, + p->addr, flags, + &p->buf[buffer_offset], current_count); + if (ret) + return ret; + remaining -= current_count; + buffer_offset += current_count; + } + } + + return num; +} + +u32 amdgpu_atombios_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_i2c.h b/drivers/gpu/drm/amd/amdgpu/atombios_i2c.h new file mode 100644 index 000000000000..d6128d9de56e --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/atombios_i2c.h @@ -0,0 +1,31 @@ +/* + * Copyright 2014 Advanced Micro Devices, 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 __ATOMBIOS_I2C_H__ +#define __ATOMBIOS_I2C_H__ + +int amdgpu_atombios_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num); +u32 amdgpu_atombios_i2c_func(struct i2c_adapter *adap); + +#endif diff --git a/drivers/gpu/drm/amd/amdgpu/cikd.h b/drivers/gpu/drm/amd/amdgpu/cikd.h new file mode 100644 index 000000000000..11828e2cdf34 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/cikd.h @@ -0,0 +1,550 @@ +/* + * Copyright 2012 Advanced Micro Devices, 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: Alex Deucher + */ +#ifndef CIK_H +#define CIK_H + +#define MC_SEQ_MISC0__GDDR5__SHIFT 0x1c +#define MC_SEQ_MISC0__GDDR5_MASK 0xf0000000 +#define MC_SEQ_MISC0__GDDR5_VALUE 5 + +#define CP_ME_TABLE_SIZE 96 + +/* display controller offsets used for crtc/cur/lut/grph/viewport/etc. */ +#define CRTC0_REGISTER_OFFSET (0x1b7c - 0x1b7c) +#define CRTC1_REGISTER_OFFSET (0x1e7c - 0x1b7c) +#define CRTC2_REGISTER_OFFSET (0x417c - 0x1b7c) +#define CRTC3_REGISTER_OFFSET (0x447c - 0x1b7c) +#define CRTC4_REGISTER_OFFSET (0x477c - 0x1b7c) +#define CRTC5_REGISTER_OFFSET (0x4a7c - 0x1b7c) + +#define BONAIRE_GB_ADDR_CONFIG_GOLDEN 0x12010001 +#define HAWAII_GB_ADDR_CONFIG_GOLDEN 0x12011003 + +#define CIK_RB_BITMAP_WIDTH_PER_SH 2 +#define HAWAII_RB_BITMAP_WIDTH_PER_SH 4 + +#define AMDGPU_NUM_OF_VMIDS 8 + +#define PIPEID(x) ((x) << 0) +#define MEID(x) ((x) << 2) +#define VMID(x) ((x) << 4) +#define QUEUEID(x) ((x) << 8) + +#define mmCC_DRM_ID_STRAPS 0x1559 +#define CC_DRM_ID_STRAPS__ATI_REV_ID_MASK 0xf0000000 + +#define mmCHUB_CONTROL 0x619 +#define BYPASS_VM (1 << 0) + +#define SYSTEM_APERTURE_UNMAPPED_ACCESS_PASS_THRU (0 << 5) + +#define mmGRPH_LUT_10BIT_BYPASS_CONTROL 0x1a02 +#define LUT_10BIT_BYPASS_EN (1 << 8) + +# define CURSOR_MONO 0 +# define CURSOR_24_1 1 +# define CURSOR_24_8_PRE_MULT 2 +# define CURSOR_24_8_UNPRE_MULT 3 +# define CURSOR_URGENT_ALWAYS 0 +# define CURSOR_URGENT_1_8 1 +# define CURSOR_URGENT_1_4 2 +# define CURSOR_URGENT_3_8 3 +# define CURSOR_URGENT_1_2 4 + +# define GRPH_DEPTH_8BPP 0 +# define GRPH_DEPTH_16BPP 1 +# define GRPH_DEPTH_32BPP 2 +/* 8 BPP */ +# define GRPH_FORMAT_INDEXED 0 +/* 16 BPP */ +# define GRPH_FORMAT_ARGB1555 0 +# define GRPH_FORMAT_ARGB565 1 +# define GRPH_FORMAT_ARGB4444 2 +# define GRPH_FORMAT_AI88 3 +# define GRPH_FORMAT_MONO16 4 +# define GRPH_FORMAT_BGRA5551 5 +/* 32 BPP */ +# define GRPH_FORMAT_ARGB8888 0 +# define GRPH_FORMAT_ARGB2101010 1 +# define GRPH_FORMAT_32BPP_DIG 2 +# define GRPH_FORMAT_8B_ARGB2101010 3 +# define GRPH_FORMAT_BGRA1010102 4 +# define GRPH_FORMAT_8B_BGRA1010102 5 +# define GRPH_FORMAT_RGB111110 6 +# define GRPH_FORMAT_BGR101111 7 +# define ADDR_SURF_MACRO_TILE_ASPECT_1 0 +# define ADDR_SURF_MACRO_TILE_ASPECT_2 1 +# define ADDR_SURF_MACRO_TILE_ASPECT_4 2 +# define ADDR_SURF_MACRO_TILE_ASPECT_8 3 +# define GRPH_ARRAY_LINEAR_GENERAL 0 +# define GRPH_ARRAY_LINEAR_ALIGNED 1 +# define GRPH_ARRAY_1D_TILED_THIN1 2 +# define GRPH_ARRAY_2D_TILED_THIN1 4 +# define DISPLAY_MICRO_TILING 0 +# define THIN_MICRO_TILING 1 +# define DEPTH_MICRO_TILING 2 +# define ROTATED_MICRO_TILING 4 +# define GRPH_ENDIAN_NONE 0 +# define GRPH_ENDIAN_8IN16 1 +# define GRPH_ENDIAN_8IN32 2 +# define GRPH_ENDIAN_8IN64 3 +# define GRPH_RED_SEL_R 0 +# define GRPH_RED_SEL_G 1 +# define GRPH_RED_SEL_B 2 +# define GRPH_RED_SEL_A 3 +# define GRPH_GREEN_SEL_G 0 +# define GRPH_GREEN_SEL_B 1 +# define GRPH_GREEN_SEL_A 2 +# define GRPH_GREEN_SEL_R 3 +# define GRPH_BLUE_SEL_B 0 +# define GRPH_BLUE_SEL_A 1 +# define GRPH_BLUE_SEL_R 2 +# define GRPH_BLUE_SEL_G 3 +# define GRPH_ALPHA_SEL_A 0 +# define GRPH_ALPHA_SEL_R 1 +# define GRPH_ALPHA_SEL_G 2 +# define GRPH_ALPHA_SEL_B 3 +# define INPUT_GAMMA_USE_LUT 0 +# define INPUT_GAMMA_BYPASS 1 +# define INPUT_GAMMA_SRGB_24 2 +# define INPUT_GAMMA_XVYCC_222 3 + +# define INPUT_CSC_BYPASS 0 +# define INPUT_CSC_PROG_COEFF 1 +# define INPUT_CSC_PROG_SHARED_MATRIXA 2 + +# define OUTPUT_CSC_BYPASS 0 +# define OUTPUT_CSC_TV_RGB 1 +# define OUTPUT_CSC_YCBCR_601 2 +# define OUTPUT_CSC_YCBCR_709 3 +# define OUTPUT_CSC_PROG_COEFF 4 +# define OUTPUT_CSC_PROG_SHARED_MATRIXB 5 + +# define DEGAMMA_BYPASS 0 +# define DEGAMMA_SRGB_24 1 +# define DEGAMMA_XVYCC_222 2 +# define GAMUT_REMAP_BYPASS 0 +# define GAMUT_REMAP_PROG_COEFF 1 +# define GAMUT_REMAP_PROG_SHARED_MATRIXA 2 +# define GAMUT_REMAP_PROG_SHARED_MATRIXB 3 + +# define REGAMMA_BYPASS 0 +# define REGAMMA_SRGB_24 1 +# define REGAMMA_XVYCC_222 2 +# define REGAMMA_PROG_A 3 +# define REGAMMA_PROG_B 4 + +# define FMT_CLAMP_6BPC 0 +# define FMT_CLAMP_8BPC 1 +# define FMT_CLAMP_10BPC 2 + +# define HDMI_24BIT_DEEP_COLOR 0 +# define HDMI_30BIT_DEEP_COLOR 1 +# define HDMI_36BIT_DEEP_COLOR 2 +# define HDMI_ACR_HW 0 +# define HDMI_ACR_32 1 +# define HDMI_ACR_44 2 +# define HDMI_ACR_48 3 +# define HDMI_ACR_X1 1 +# define HDMI_ACR_X2 2 +# define HDMI_ACR_X4 4 +# define AFMT_AVI_INFO_Y_RGB 0 +# define AFMT_AVI_INFO_Y_YCBCR422 1 +# define AFMT_AVI_INFO_Y_YCBCR444 2 + +#define NO_AUTO 0 +#define ES_AUTO 1 +#define GS_AUTO 2 +#define ES_AND_GS_AUTO 3 + +# define ARRAY_MODE(x) ((x) << 2) +# define PIPE_CONFIG(x) ((x) << 6) +# define TILE_SPLIT(x) ((x) << 11) +# define MICRO_TILE_MODE_NEW(x) ((x) << 22) +# define SAMPLE_SPLIT(x) ((x) << 25) +# define BANK_WIDTH(x) ((x) << 0) +# define BANK_HEIGHT(x) ((x) << 2) +# define MACRO_TILE_ASPECT(x) ((x) << 4) +# define NUM_BANKS(x) ((x) << 6) + +#define MSG_ENTER_RLC_SAFE_MODE 1 +#define MSG_EXIT_RLC_SAFE_MODE 0 + +/* + * PM4 + */ +#define PACKET_TYPE0 0 +#define PACKET_TYPE1 1 +#define PACKET_TYPE2 2 +#define PACKET_TYPE3 3 + +#define CP_PACKET_GET_TYPE(h) (((h) >> 30) & 3) +#define CP_PACKET_GET_COUNT(h) (((h) >> 16) & 0x3FFF) +#define CP_PACKET0_GET_REG(h) ((h) & 0xFFFF) +#define CP_PACKET3_GET_OPCODE(h) (((h) >> 8) & 0xFF) +#define PACKET0(reg, n) ((PACKET_TYPE0 << 30) | \ + ((reg) & 0xFFFF) | \ + ((n) & 0x3FFF) << 16) +#define CP_PACKET2 0x80000000 +#define PACKET2_PAD_SHIFT 0 +#define PACKET2_PAD_MASK (0x3fffffff << 0) + +#define PACKET2(v) (CP_PACKET2 | REG_SET(PACKET2_PAD, (v))) + +#define PACKET3(op, n) ((PACKET_TYPE3 << 30) | \ + (((op) & 0xFF) << 8) | \ + ((n) & 0x3FFF) << 16) + +#define PACKET3_COMPUTE(op, n) (PACKET3(op, n) | 1 << 1) + +/* Packet 3 types */ +#define PACKET3_NOP 0x10 +#define PACKET3_SET_BASE 0x11 +#define PACKET3_BASE_INDEX(x) ((x) << 0) +#define CE_PARTITION_BASE 3 +#define PACKET3_CLEAR_STATE 0x12 +#define PACKET3_INDEX_BUFFER_SIZE 0x13 +#define PACKET3_DISPATCH_DIRECT 0x15 +#define PACKET3_DISPATCH_INDIRECT 0x16 +#define PACKET3_ATOMIC_GDS 0x1D +#define PACKET3_ATOMIC_MEM 0x1E +#define PACKET3_OCCLUSION_QUERY 0x1F +#define PACKET3_SET_PREDICATION 0x20 +#define PACKET3_REG_RMW 0x21 +#define PACKET3_COND_EXEC 0x22 +#define PACKET3_PRED_EXEC 0x23 +#define PACKET3_DRAW_INDIRECT 0x24 +#define PACKET3_DRAW_INDEX_INDIRECT 0x25 +#define PACKET3_INDEX_BASE 0x26 +#define PACKET3_DRAW_INDEX_2 0x27 +#define PACKET3_CONTEXT_CONTROL 0x28 +#define PACKET3_INDEX_TYPE 0x2A +#define PACKET3_DRAW_INDIRECT_MULTI 0x2C +#define PACKET3_DRAW_INDEX_AUTO 0x2D +#define PACKET3_NUM_INSTANCES 0x2F +#define PACKET3_DRAW_INDEX_MULTI_AUTO 0x30 +#define PACKET3_INDIRECT_BUFFER_CONST 0x33 +#define PACKET3_STRMOUT_BUFFER_UPDATE 0x34 +#define PACKET3_DRAW_INDEX_OFFSET_2 0x35 +#define PACKET3_DRAW_PREAMBLE 0x36 +#define PACKET3_WRITE_DATA 0x37 +#define WRITE_DATA_DST_SEL(x) ((x) << 8) + /* 0 - register + * 1 - memory (sync - via GRBM) + * 2 - gl2 + * 3 - gds + * 4 - reserved + * 5 - memory (async - direct) + */ +#define WR_ONE_ADDR (1 << 16) +#define WR_CONFIRM (1 << 20) +#define WRITE_DATA_CACHE_POLICY(x) ((x) << 25) + /* 0 - LRU + * 1 - Stream + */ +#define WRITE_DATA_ENGINE_SEL(x) ((x) << 30) + /* 0 - me + * 1 - pfp + * 2 - ce + */ +#define PACKET3_DRAW_INDEX_INDIRECT_MULTI 0x38 +#define PACKET3_MEM_SEMAPHORE 0x39 +# define PACKET3_SEM_USE_MAILBOX (0x1 << 16) +# define PACKET3_SEM_SEL_SIGNAL_TYPE (0x1 << 20) /* 0 = increment, 1 = write 1 */ +# define PACKET3_SEM_CLIENT_CODE ((x) << 24) /* 0 = CP, 1 = CB, 2 = DB */ +# define PACKET3_SEM_SEL_SIGNAL (0x6 << 29) +# define PACKET3_SEM_SEL_WAIT (0x7 << 29) +#define PACKET3_COPY_DW 0x3B +#define PACKET3_WAIT_REG_MEM 0x3C +#define WAIT_REG_MEM_FUNCTION(x) ((x) << 0) + /* 0 - always + * 1 - < + * 2 - <= + * 3 - == + * 4 - != + * 5 - >= + * 6 - > + */ +#define WAIT_REG_MEM_MEM_SPACE(x) ((x) << 4) + /* 0 - reg + * 1 - mem + */ +#define WAIT_REG_MEM_OPERATION(x) ((x) << 6) + /* 0 - wait_reg_mem + * 1 - wr_wait_wr_reg + */ +#define WAIT_REG_MEM_ENGINE(x) ((x) << 8) + /* 0 - me + * 1 - pfp + */ +#define PACKET3_INDIRECT_BUFFER 0x3F +#define INDIRECT_BUFFER_TCL2_VOLATILE (1 << 22) +#define INDIRECT_BUFFER_VALID (1 << 23) +#define INDIRECT_BUFFER_CACHE_POLICY(x) ((x) << 28) + /* 0 - LRU + * 1 - Stream + * 2 - Bypass + */ +#define PACKET3_COPY_DATA 0x40 +#define PACKET3_PFP_SYNC_ME 0x42 +#define PACKET3_SURFACE_SYNC 0x43 +# define PACKET3_DEST_BASE_0_ENA (1 << 0) +# define PACKET3_DEST_BASE_1_ENA (1 << 1) +# define PACKET3_CB0_DEST_BASE_ENA (1 << 6) +# define PACKET3_CB1_DEST_BASE_ENA (1 << 7) +# define PACKET3_CB2_DEST_BASE_ENA (1 << 8) +# define PACKET3_CB3_DEST_BASE_ENA (1 << 9) +# define PACKET3_CB4_DEST_BASE_ENA (1 << 10) +# define PACKET3_CB5_DEST_BASE_ENA (1 << 11) +# define PACKET3_CB6_DEST_BASE_ENA (1 << 12) +# define PACKET3_CB7_DEST_BASE_ENA (1 << 13) +# define PACKET3_DB_DEST_BASE_ENA (1 << 14) +# define PACKET3_TCL1_VOL_ACTION_ENA (1 << 15) +# define PACKET3_TC_VOL_ACTION_ENA (1 << 16) /* L2 */ +# define PACKET3_TC_WB_ACTION_ENA (1 << 18) /* L2 */ +# define PACKET3_DEST_BASE_2_ENA (1 << 19) +# define PACKET3_DEST_BASE_3_ENA (1 << 21) +# define PACKET3_TCL1_ACTION_ENA (1 << 22) +# define PACKET3_TC_ACTION_ENA (1 << 23) /* L2 */ +# define PACKET3_CB_ACTION_ENA (1 << 25) +# define PACKET3_DB_ACTION_ENA (1 << 26) +# define PACKET3_SH_KCACHE_ACTION_ENA (1 << 27) +# define PACKET3_SH_KCACHE_VOL_ACTION_ENA (1 << 28) +# define PACKET3_SH_ICACHE_ACTION_ENA (1 << 29) +#define PACKET3_COND_WRITE 0x45 +#define PACKET3_EVENT_WRITE 0x46 +#define EVENT_TYPE(x) ((x) << 0) +#define EVENT_INDEX(x) ((x) << 8) + /* 0 - any non-TS event + * 1 - ZPASS_DONE, PIXEL_PIPE_STAT_* + * 2 - SAMPLE_PIPELINESTAT + * 3 - SAMPLE_STREAMOUTSTAT* + * 4 - *S_PARTIAL_FLUSH + * 5 - EOP events + * 6 - EOS events + */ +#define PACKET3_EVENT_WRITE_EOP 0x47 +#define EOP_TCL1_VOL_ACTION_EN (1 << 12) +#define EOP_TC_VOL_ACTION_EN (1 << 13) /* L2 */ +#define EOP_TC_WB_ACTION_EN (1 << 15) /* L2 */ +#define EOP_TCL1_ACTION_EN (1 << 16) +#define EOP_TC_ACTION_EN (1 << 17) /* L2 */ +#define EOP_TCL2_VOLATILE (1 << 24) +#define EOP_CACHE_POLICY(x) ((x) << 25) + /* 0 - LRU + * 1 - Stream + * 2 - Bypass + */ +#define DATA_SEL(x) ((x) << 29) + /* 0 - discard + * 1 - send low 32bit data + * 2 - send 64bit data + * 3 - send 64bit GPU counter value + * 4 - send 64bit sys counter value + */ +#define INT_SEL(x) ((x) << 24) + /* 0 - none + * 1 - interrupt only (DATA_SEL = 0) + * 2 - interrupt when data write is confirmed + */ +#define DST_SEL(x) ((x) << 16) + /* 0 - MC + * 1 - TC/L2 + */ +#define PACKET3_EVENT_WRITE_EOS 0x48 +#define PACKET3_RELEASE_MEM 0x49 +#define PACKET3_PREAMBLE_CNTL 0x4A +# define PACKET3_PREAMBLE_BEGIN_CLEAR_STATE (2 << 28) +# define PACKET3_PREAMBLE_END_CLEAR_STATE (3 << 28) +#define PACKET3_DMA_DATA 0x50 +/* 1. header + * 2. CONTROL + * 3. SRC_ADDR_LO or DATA [31:0] + * 4. SRC_ADDR_HI [31:0] + * 5. DST_ADDR_LO [31:0] + * 6. DST_ADDR_HI [7:0] + * 7. COMMAND [30:21] | BYTE_COUNT [20:0] + */ +/* CONTROL */ +# define PACKET3_DMA_DATA_ENGINE(x) ((x) << 0) + /* 0 - ME + * 1 - PFP + */ +# define PACKET3_DMA_DATA_SRC_CACHE_POLICY(x) ((x) << 13) + /* 0 - LRU + * 1 - Stream + * 2 - Bypass + */ +# define PACKET3_DMA_DATA_SRC_VOLATILE (1 << 15) +# define PACKET3_DMA_DATA_DST_SEL(x) ((x) << 20) + /* 0 - DST_ADDR using DAS + * 1 - GDS + * 3 - DST_ADDR using L2 + */ +# define PACKET3_DMA_DATA_DST_CACHE_POLICY(x) ((x) << 25) + /* 0 - LRU + * 1 - Stream + * 2 - Bypass + */ +# define PACKET3_DMA_DATA_DST_VOLATILE (1 << 27) +# define PACKET3_DMA_DATA_SRC_SEL(x) ((x) << 29) + /* 0 - SRC_ADDR using SAS + * 1 - GDS + * 2 - DATA + * 3 - SRC_ADDR using L2 + */ +# define PACKET3_DMA_DATA_CP_SYNC (1 << 31) +/* COMMAND */ +# define PACKET3_DMA_DATA_DIS_WC (1 << 21) +# define PACKET3_DMA_DATA_CMD_SRC_SWAP(x) ((x) << 22) + /* 0 - none + * 1 - 8 in 16 + * 2 - 8 in 32 + * 3 - 8 in 64 + */ +# define PACKET3_DMA_DATA_CMD_DST_SWAP(x) ((x) << 24) + /* 0 - none + * 1 - 8 in 16 + * 2 - 8 in 32 + * 3 - 8 in 64 + */ +# define PACKET3_DMA_DATA_CMD_SAS (1 << 26) + /* 0 - memory + * 1 - register + */ +# define PACKET3_DMA_DATA_CMD_DAS (1 << 27) + /* 0 - memory + * 1 - register + */ +# define PACKET3_DMA_DATA_CMD_SAIC (1 << 28) +# define PACKET3_DMA_DATA_CMD_DAIC (1 << 29) +# define PACKET3_DMA_DATA_CMD_RAW_WAIT (1 << 30) +#define PACKET3_AQUIRE_MEM 0x58 +#define PACKET3_REWIND 0x59 +#define PACKET3_LOAD_UCONFIG_REG 0x5E +#define PACKET3_LOAD_SH_REG 0x5F +#define PACKET3_LOAD_CONFIG_REG 0x60 +#define PACKET3_LOAD_CONTEXT_REG 0x61 +#define PACKET3_SET_CONFIG_REG 0x68 +#define PACKET3_SET_CONFIG_REG_START 0x00002000 +#define PACKET3_SET_CONFIG_REG_END 0x00002c00 +#define PACKET3_SET_CONTEXT_REG 0x69 +#define PACKET3_SET_CONTEXT_REG_START 0x0000a000 +#define PACKET3_SET_CONTEXT_REG_END 0x0000a400 +#define PACKET3_SET_CONTEXT_REG_INDIRECT 0x73 +#define PACKET3_SET_SH_REG 0x76 +#define PACKET3_SET_SH_REG_START 0x00002c00 +#define PACKET3_SET_SH_REG_END 0x00003000 +#define PACKET3_SET_SH_REG_OFFSET 0x77 +#define PACKET3_SET_QUEUE_REG 0x78 +#define PACKET3_SET_UCONFIG_REG 0x79 +#define PACKET3_SET_UCONFIG_REG_START 0x0000c000 +#define PACKET3_SET_UCONFIG_REG_END 0x0000c400 +#define PACKET3_SCRATCH_RAM_WRITE 0x7D +#define PACKET3_SCRATCH_RAM_READ 0x7E +#define PACKET3_LOAD_CONST_RAM 0x80 +#define PACKET3_WRITE_CONST_RAM 0x81 +#define PACKET3_DUMP_CONST_RAM 0x83 +#define PACKET3_INCREMENT_CE_COUNTER 0x84 +#define PACKET3_INCREMENT_DE_COUNTER 0x85 +#define PACKET3_WAIT_ON_CE_COUNTER 0x86 +#define PACKET3_WAIT_ON_DE_COUNTER_DIFF 0x88 +#define PACKET3_SWITCH_BUFFER 0x8B + +/* SDMA - first instance at 0xd000, second at 0xd800 */ +#define SDMA0_REGISTER_OFFSET 0x0 /* not a register */ +#define SDMA1_REGISTER_OFFSET 0x200 /* not a register */ +#define SDMA_MAX_INSTANCE 2 + +#define SDMA_PACKET(op, sub_op, e) ((((e) & 0xFFFF) << 16) | \ + (((sub_op) & 0xFF) << 8) | \ + (((op) & 0xFF) << 0)) +/* sDMA opcodes */ +#define SDMA_OPCODE_NOP 0 +#define SDMA_OPCODE_COPY 1 +# define SDMA_COPY_SUB_OPCODE_LINEAR 0 +# define SDMA_COPY_SUB_OPCODE_TILED 1 +# define SDMA_COPY_SUB_OPCODE_SOA 3 +# define SDMA_COPY_SUB_OPCODE_LINEAR_SUB_WINDOW 4 +# define SDMA_COPY_SUB_OPCODE_TILED_SUB_WINDOW 5 +# define SDMA_COPY_SUB_OPCODE_T2T_SUB_WINDOW 6 +#define SDMA_OPCODE_WRITE 2 +# define SDMA_WRITE_SUB_OPCODE_LINEAR 0 +# define SDMA_WRTIE_SUB_OPCODE_TILED 1 +#define SDMA_OPCODE_INDIRECT_BUFFER 4 +#define SDMA_OPCODE_FENCE 5 +#define SDMA_OPCODE_TRAP 6 +#define SDMA_OPCODE_SEMAPHORE 7 +# define SDMA_SEMAPHORE_EXTRA_O (1 << 13) + /* 0 - increment + * 1 - write 1 + */ +# define SDMA_SEMAPHORE_EXTRA_S (1 << 14) + /* 0 - wait + * 1 - signal + */ +# define SDMA_SEMAPHORE_EXTRA_M (1 << 15) + /* mailbox */ +#define SDMA_OPCODE_POLL_REG_MEM 8 +# define SDMA_POLL_REG_MEM_EXTRA_OP(x) ((x) << 10) + /* 0 - wait_reg_mem + * 1 - wr_wait_wr_reg + */ +# define SDMA_POLL_REG_MEM_EXTRA_FUNC(x) ((x) << 12) + /* 0 - always + * 1 - < + * 2 - <= + * 3 - == + * 4 - != + * 5 - >= + * 6 - > + */ +# define SDMA_POLL_REG_MEM_EXTRA_M (1 << 15) + /* 0 = register + * 1 = memory + */ +#define SDMA_OPCODE_COND_EXEC 9 +#define SDMA_OPCODE_CONSTANT_FILL 11 +# define SDMA_CONSTANT_FILL_EXTRA_SIZE(x) ((x) << 14) + /* 0 = byte fill + * 2 = DW fill + */ +#define SDMA_OPCODE_GENERATE_PTE_PDE 12 +#define SDMA_OPCODE_TIMESTAMP 13 +# define SDMA_TIMESTAMP_SUB_OPCODE_SET_LOCAL 0 +# define SDMA_TIMESTAMP_SUB_OPCODE_GET_LOCAL 1 +# define SDMA_TIMESTAMP_SUB_OPCODE_GET_GLOBAL 2 +#define SDMA_OPCODE_SRBM_WRITE 14 +# define SDMA_SRBM_WRITE_EXTRA_BYTE_ENABLE(x) ((x) << 12) + /* byte mask */ + +#define VCE_CMD_NO_OP 0x00000000 +#define VCE_CMD_END 0x00000001 +#define VCE_CMD_IB 0x00000002 +#define VCE_CMD_FENCE 0x00000003 +#define VCE_CMD_TRAP 0x00000004 +#define VCE_CMD_IB_AUTO 0x00000005 +#define VCE_CMD_SEMAPHORE 0x00000006 + +#endif |