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>
This commit is contained in:
Alex Deucher 2015-04-20 16:55:21 -04:00
commit d38ceaf99e
78 changed files with 33596 additions and 0 deletions

View file

@ -120,6 +120,27 @@ config DRM_RADEON
source "drivers/gpu/drm/radeon/Kconfig"
config DRM_AMDGPU
tristate "AMD GPU"
depends on DRM && PCI
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
select FW_LOADER
select DRM_KMS_HELPER
select DRM_KMS_FB_HELPER
select DRM_TTM
select POWER_SUPPLY
select HWMON
select BACKLIGHT_CLASS_DEVICE
select INTERVAL_TREE
help
Choose this option if you have a recent AMD Radeon graphics card.
If M is selected, the module will be called amdgpu.
source "drivers/gpu/drm/amd/amdgpu/Kconfig"
source "drivers/gpu/drm/nouveau/Kconfig"
config DRM_I810

View file

@ -37,6 +37,7 @@ obj-$(CONFIG_DRM_TDFX) += tdfx/
obj-$(CONFIG_DRM_R128) += r128/
obj-$(CONFIG_HSA_AMD) += amd/amdkfd/
obj-$(CONFIG_DRM_RADEON)+= radeon/
obj-$(CONFIG_DRM_AMDGPU)+= amd/amdgpu/
obj-$(CONFIG_DRM_MGA) += mga/
obj-$(CONFIG_DRM_I810) += i810/
obj-$(CONFIG_DRM_I915) += i915/

View file

@ -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.

View file

@ -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)

View file

@ -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(&params, 0, sizeof(params));
size = min(sizeof(params), size);
memcpy(&params, 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, &params);
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);
}

View file

@ -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

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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,
&params);
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,
&params);
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,
&params);
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,
&params);
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,
&params);
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();
}

View file

@ -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");
}
}

View file

@ -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;
}

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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(&current->mm->mmap_sem);
r = amdgpu_cs_list_validate(p);
if (need_mmap_lock)
up_read(&current->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;
}

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -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;
}
}

View file

@ -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];
}

Some files were not shown because too many files have changed in this diff Show more