Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
d5a4dfc3
Commit
d5a4dfc3
authored
Aug 25, 2022
by
Hans de Goede
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'platform-drivers-x86-amd-pmf' into pdx86-base
parents
568035b0
ea522b80
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1488 additions
and
0 deletions
+1488
-0
MAINTAINERS
MAINTAINERS
+6
-0
drivers/platform/x86/amd/Kconfig
drivers/platform/x86/amd/Kconfig
+2
-0
drivers/platform/x86/amd/Makefile
drivers/platform/x86/amd/Makefile
+1
-0
drivers/platform/x86/amd/pmf/Kconfig
drivers/platform/x86/amd/pmf/Kconfig
+16
-0
drivers/platform/x86/amd/pmf/Makefile
drivers/platform/x86/amd/pmf/Makefile
+9
-0
drivers/platform/x86/amd/pmf/acpi.c
drivers/platform/x86/amd/pmf/acpi.c
+288
-0
drivers/platform/x86/amd/pmf/auto-mode.c
drivers/platform/x86/amd/pmf/auto-mode.c
+305
-0
drivers/platform/x86/amd/pmf/core.c
drivers/platform/x86/amd/pmf/core.c
+388
-0
drivers/platform/x86/amd/pmf/pmf.h
drivers/platform/x86/amd/pmf/pmf.h
+327
-0
drivers/platform/x86/amd/pmf/sps.c
drivers/platform/x86/amd/pmf/sps.c
+146
-0
No files found.
MAINTAINERS
View file @
d5a4dfc3
...
@@ -1022,6 +1022,12 @@ L: platform-driver-x86@vger.kernel.org
...
@@ -1022,6 +1022,12 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
S: Maintained
F: drivers/platform/x86/amd/pmc.c
F: drivers/platform/x86/amd/pmc.c
AMD PMF DRIVER
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/amd/pmf/
AMD HSMP DRIVER
AMD HSMP DRIVER
M: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
M: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
R: Carlos Bilbao <carlos.bilbao@amd.com>
R: Carlos Bilbao <carlos.bilbao@amd.com>
...
...
drivers/platform/x86/amd/Kconfig
View file @
d5a4dfc3
...
@@ -3,6 +3,8 @@
...
@@ -3,6 +3,8 @@
# AMD x86 Platform Specific Drivers
# AMD x86 Platform Specific Drivers
#
#
source "drivers/platform/x86/amd/pmf/Kconfig"
config AMD_PMC
config AMD_PMC
tristate "AMD SoC PMC driver"
tristate "AMD SoC PMC driver"
depends on ACPI && PCI && RTC_CLASS
depends on ACPI && PCI && RTC_CLASS
...
...
drivers/platform/x86/amd/Makefile
View file @
d5a4dfc3
...
@@ -8,3 +8,4 @@ amd-pmc-y := pmc.o
...
@@ -8,3 +8,4 @@ amd-pmc-y := pmc.o
obj-$(CONFIG_AMD_PMC)
+=
amd-pmc.o
obj-$(CONFIG_AMD_PMC)
+=
amd-pmc.o
amd_hsmp-y
:=
hsmp.o
amd_hsmp-y
:=
hsmp.o
obj-$(CONFIG_AMD_HSMP)
+=
amd_hsmp.o
obj-$(CONFIG_AMD_HSMP)
+=
amd_hsmp.o
obj-$(CONFIG_AMD_PMF)
+=
pmf/
drivers/platform/x86/amd/pmf/Kconfig
0 → 100644
View file @
d5a4dfc3
# SPDX-License-Identifier: GPL-2.0-only
#
# AMD PMF Driver
#
config AMD_PMF
tristate "AMD Platform Management Framework"
depends on ACPI && PCI
select ACPI_PLATFORM_PROFILE
help
This driver provides support for the AMD Platform Management Framework.
The goal is to enhance end user experience by making AMD PCs smarter,
quiter, power efficient by adapting to user behavior and environment.
To compile this driver as a module, choose M here: the module will
be called amd_pmf.
drivers/platform/x86/amd/pmf/Makefile
0 → 100644
View file @
d5a4dfc3
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for linux/drivers/platform/x86/amd/pmf
# AMD Platform Management Framework
#
obj-$(CONFIG_AMD_PMF)
+=
amd-pmf.o
amd-pmf-objs
:=
core.o acpi.o sps.o
\
auto-mode.o
drivers/platform/x86/amd/pmf/acpi.c
0 → 100644
View file @
d5a4dfc3
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/acpi.h>
#include "pmf.h"
#define APMF_CQL_NOTIFICATION 2
#define APMF_AMT_NOTIFICATION 3
static
union
acpi_object
*
apmf_if_call
(
struct
amd_pmf_dev
*
pdev
,
int
fn
,
struct
acpi_buffer
*
param
)
{
struct
acpi_buffer
buffer
=
{
ACPI_ALLOCATE_BUFFER
,
NULL
};
acpi_handle
ahandle
=
ACPI_HANDLE
(
pdev
->
dev
);
struct
acpi_object_list
apmf_if_arg_list
;
union
acpi_object
apmf_if_args
[
2
];
acpi_status
status
;
apmf_if_arg_list
.
count
=
2
;
apmf_if_arg_list
.
pointer
=
&
apmf_if_args
[
0
];
apmf_if_args
[
0
].
type
=
ACPI_TYPE_INTEGER
;
apmf_if_args
[
0
].
integer
.
value
=
fn
;
if
(
param
)
{
apmf_if_args
[
1
].
type
=
ACPI_TYPE_BUFFER
;
apmf_if_args
[
1
].
buffer
.
length
=
param
->
length
;
apmf_if_args
[
1
].
buffer
.
pointer
=
param
->
pointer
;
}
else
{
apmf_if_args
[
1
].
type
=
ACPI_TYPE_INTEGER
;
apmf_if_args
[
1
].
integer
.
value
=
0
;
}
status
=
acpi_evaluate_object
(
ahandle
,
"APMF"
,
&
apmf_if_arg_list
,
&
buffer
);
if
(
ACPI_FAILURE
(
status
))
{
dev_err
(
pdev
->
dev
,
"APMF method:%d call failed
\n
"
,
fn
);
kfree
(
buffer
.
pointer
);
return
NULL
;
}
return
buffer
.
pointer
;
}
static
int
apmf_if_call_store_buffer
(
struct
amd_pmf_dev
*
pdev
,
int
fn
,
void
*
dest
,
size_t
out_sz
)
{
union
acpi_object
*
info
;
size_t
size
;
int
err
=
0
;
info
=
apmf_if_call
(
pdev
,
fn
,
NULL
);
if
(
!
info
)
return
-
EIO
;
if
(
info
->
type
!=
ACPI_TYPE_BUFFER
)
{
dev_err
(
pdev
->
dev
,
"object is not a buffer
\n
"
);
err
=
-
EINVAL
;
goto
out
;
}
if
(
info
->
buffer
.
length
<
2
)
{
dev_err
(
pdev
->
dev
,
"buffer too small
\n
"
);
err
=
-
EINVAL
;
goto
out
;
}
size
=
*
(
u16
*
)
info
->
buffer
.
pointer
;
if
(
info
->
buffer
.
length
<
size
)
{
dev_err
(
pdev
->
dev
,
"buffer smaller then headersize %u < %zu
\n
"
,
info
->
buffer
.
length
,
size
);
err
=
-
EINVAL
;
goto
out
;
}
if
(
size
<
out_sz
)
{
dev_err
(
pdev
->
dev
,
"buffer too small %zu
\n
"
,
size
);
err
=
-
EINVAL
;
goto
out
;
}
memcpy
(
dest
,
info
->
buffer
.
pointer
,
out_sz
);
out:
kfree
(
info
);
return
err
;
}
int
is_apmf_func_supported
(
struct
amd_pmf_dev
*
pdev
,
unsigned
long
index
)
{
/* If bit-n is set, that indicates function n+1 is supported */
return
!!
(
pdev
->
supported_func
&
BIT
(
index
-
1
));
}
int
apmf_get_static_slider_granular
(
struct
amd_pmf_dev
*
pdev
,
struct
apmf_static_slider_granular_output
*
data
)
{
if
(
!
is_apmf_func_supported
(
pdev
,
APMF_FUNC_STATIC_SLIDER_GRANULAR
))
return
-
EINVAL
;
return
apmf_if_call_store_buffer
(
pdev
,
APMF_FUNC_STATIC_SLIDER_GRANULAR
,
data
,
sizeof
(
*
data
));
}
static
void
apmf_sbios_heartbeat_notify
(
struct
work_struct
*
work
)
{
struct
amd_pmf_dev
*
dev
=
container_of
(
work
,
struct
amd_pmf_dev
,
heart_beat
.
work
);
union
acpi_object
*
info
;
dev_dbg
(
dev
->
dev
,
"Sending heartbeat to SBIOS
\n
"
);
info
=
apmf_if_call
(
dev
,
APMF_FUNC_SBIOS_HEARTBEAT
,
NULL
);
if
(
!
info
)
goto
out
;
schedule_delayed_work
(
&
dev
->
heart_beat
,
msecs_to_jiffies
(
dev
->
hb_interval
*
1000
));
out:
kfree
(
info
);
}
int
apmf_update_fan_idx
(
struct
amd_pmf_dev
*
pdev
,
bool
manual
,
u32
idx
)
{
union
acpi_object
*
info
;
struct
apmf_fan_idx
args
;
struct
acpi_buffer
params
;
int
err
=
0
;
args
.
size
=
sizeof
(
args
);
args
.
fan_ctl_mode
=
manual
;
args
.
fan_ctl_idx
=
idx
;
params
.
length
=
sizeof
(
args
);
params
.
pointer
=
(
void
*
)
&
args
;
info
=
apmf_if_call
(
pdev
,
APMF_FUNC_SET_FAN_IDX
,
&
params
);
if
(
!
info
)
{
err
=
-
EIO
;
goto
out
;
}
out:
kfree
(
info
);
return
err
;
}
int
apmf_get_auto_mode_def
(
struct
amd_pmf_dev
*
pdev
,
struct
apmf_auto_mode
*
data
)
{
return
apmf_if_call_store_buffer
(
pdev
,
APMF_FUNC_AUTO_MODE
,
data
,
sizeof
(
*
data
));
}
int
apmf_get_sbios_requests
(
struct
amd_pmf_dev
*
pdev
,
struct
apmf_sbios_req
*
req
)
{
return
apmf_if_call_store_buffer
(
pdev
,
APMF_FUNC_SBIOS_REQUESTS
,
req
,
sizeof
(
*
req
));
}
static
void
apmf_event_handler
(
acpi_handle
handle
,
u32
event
,
void
*
data
)
{
struct
amd_pmf_dev
*
pmf_dev
=
data
;
struct
apmf_sbios_req
req
;
int
ret
;
mutex_lock
(
&
pmf_dev
->
update_mutex
);
ret
=
apmf_get_sbios_requests
(
pmf_dev
,
&
req
);
if
(
ret
)
{
dev_err
(
pmf_dev
->
dev
,
"Failed to get SBIOS requests:%d
\n
"
,
ret
);
goto
out
;
}
if
(
req
.
pending_req
&
BIT
(
APMF_AMT_NOTIFICATION
))
{
dev_dbg
(
pmf_dev
->
dev
,
"AMT is supported and notifications %s
\n
"
,
req
.
amt_event
?
"Enabled"
:
"Disabled"
);
pmf_dev
->
amt_enabled
=
!!
req
.
amt_event
;
if
(
pmf_dev
->
amt_enabled
)
amd_pmf_handle_amt
(
pmf_dev
);
else
amd_pmf_reset_amt
(
pmf_dev
);
}
if
(
req
.
pending_req
&
BIT
(
APMF_CQL_NOTIFICATION
))
{
dev_dbg
(
pmf_dev
->
dev
,
"CQL is supported and notifications %s
\n
"
,
req
.
cql_event
?
"Enabled"
:
"Disabled"
);
/* update the target mode information */
if
(
pmf_dev
->
amt_enabled
)
amd_pmf_update_2_cql
(
pmf_dev
,
req
.
cql_event
);
}
out:
mutex_unlock
(
&
pmf_dev
->
update_mutex
);
}
static
int
apmf_if_verify_interface
(
struct
amd_pmf_dev
*
pdev
)
{
struct
apmf_verify_interface
output
;
int
err
;
err
=
apmf_if_call_store_buffer
(
pdev
,
APMF_FUNC_VERIFY_INTERFACE
,
&
output
,
sizeof
(
output
));
if
(
err
)
return
err
;
pdev
->
supported_func
=
output
.
supported_functions
;
dev_dbg
(
pdev
->
dev
,
"supported functions:0x%x notifications:0x%x
\n
"
,
output
.
supported_functions
,
output
.
notification_mask
);
return
0
;
}
static
int
apmf_get_system_params
(
struct
amd_pmf_dev
*
dev
)
{
struct
apmf_system_params
params
;
int
err
;
if
(
!
is_apmf_func_supported
(
dev
,
APMF_FUNC_GET_SYS_PARAMS
))
return
-
EINVAL
;
err
=
apmf_if_call_store_buffer
(
dev
,
APMF_FUNC_GET_SYS_PARAMS
,
&
params
,
sizeof
(
params
));
if
(
err
)
return
err
;
dev_dbg
(
dev
->
dev
,
"system params mask:0x%x flags:0x%x cmd_code:0x%x heartbeat:%d
\n
"
,
params
.
valid_mask
,
params
.
flags
,
params
.
command_code
,
params
.
heartbeat_int
);
params
.
flags
=
params
.
flags
&
params
.
valid_mask
;
dev
->
hb_interval
=
params
.
heartbeat_int
;
return
0
;
}
void
apmf_acpi_deinit
(
struct
amd_pmf_dev
*
pmf_dev
)
{
acpi_handle
ahandle
=
ACPI_HANDLE
(
pmf_dev
->
dev
);
if
(
pmf_dev
->
hb_interval
)
cancel_delayed_work_sync
(
&
pmf_dev
->
heart_beat
);
if
(
is_apmf_func_supported
(
pmf_dev
,
APMF_FUNC_AUTO_MODE
)
&&
is_apmf_func_supported
(
pmf_dev
,
APMF_FUNC_SBIOS_REQUESTS
))
acpi_remove_notify_handler
(
ahandle
,
ACPI_ALL_NOTIFY
,
apmf_event_handler
);
}
int
apmf_acpi_init
(
struct
amd_pmf_dev
*
pmf_dev
)
{
acpi_handle
ahandle
=
ACPI_HANDLE
(
pmf_dev
->
dev
);
acpi_status
status
;
int
ret
;
ret
=
apmf_if_verify_interface
(
pmf_dev
);
if
(
ret
)
{
dev_err
(
pmf_dev
->
dev
,
"APMF verify interface failed :%d
\n
"
,
ret
);
goto
out
;
}
ret
=
apmf_get_system_params
(
pmf_dev
);
if
(
ret
)
{
dev_err
(
pmf_dev
->
dev
,
"APMF apmf_get_system_params failed :%d
\n
"
,
ret
);
goto
out
;
}
if
(
pmf_dev
->
hb_interval
)
{
/* send heartbeats only if the interval is not zero */
INIT_DELAYED_WORK
(
&
pmf_dev
->
heart_beat
,
apmf_sbios_heartbeat_notify
);
schedule_delayed_work
(
&
pmf_dev
->
heart_beat
,
0
);
}
/* Install the APMF Notify handler */
if
(
is_apmf_func_supported
(
pmf_dev
,
APMF_FUNC_AUTO_MODE
)
&&
is_apmf_func_supported
(
pmf_dev
,
APMF_FUNC_SBIOS_REQUESTS
))
{
status
=
acpi_install_notify_handler
(
ahandle
,
ACPI_ALL_NOTIFY
,
apmf_event_handler
,
pmf_dev
);
if
(
ACPI_FAILURE
(
status
))
{
dev_err
(
pmf_dev
->
dev
,
"failed to install notify handler
\n
"
);
return
-
ENODEV
;
}
/* Call the handler once manually to catch up with possibly missed notifies. */
apmf_event_handler
(
ahandle
,
0
,
pmf_dev
);
}
out:
return
ret
;
}
drivers/platform/x86/amd/pmf/auto-mode.c
0 → 100644
View file @
d5a4dfc3
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/acpi.h>
#include <linux/workqueue.h>
#include "pmf.h"
static
struct
auto_mode_mode_config
config_store
;
static
const
char
*
state_as_str
(
unsigned
int
state
);
static
void
amd_pmf_set_automode
(
struct
amd_pmf_dev
*
dev
,
int
idx
,
struct
auto_mode_mode_config
*
table
)
{
struct
power_table_control
*
pwr_ctrl
=
&
config_store
.
mode_set
[
idx
].
power_control
;
amd_pmf_send_cmd
(
dev
,
SET_SPL
,
false
,
pwr_ctrl
->
spl
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_FPPT
,
false
,
pwr_ctrl
->
fppt
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_SPPT
,
false
,
pwr_ctrl
->
sppt
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_SPPT_APU_ONLY
,
false
,
pwr_ctrl
->
sppt_apu_only
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_STT_MIN_LIMIT
,
false
,
pwr_ctrl
->
stt_min
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_STT_LIMIT_APU
,
false
,
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_APU
],
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_STT_LIMIT_HS2
,
false
,
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_HS2
],
NULL
);
if
(
is_apmf_func_supported
(
dev
,
APMF_FUNC_SET_FAN_IDX
))
apmf_update_fan_idx
(
dev
,
config_store
.
mode_set
[
idx
].
fan_control
.
manual
,
config_store
.
mode_set
[
idx
].
fan_control
.
fan_id
);
}
static
int
amd_pmf_get_moving_avg
(
struct
amd_pmf_dev
*
pdev
,
int
socket_power
)
{
int
i
,
total
=
0
;
if
(
pdev
->
socket_power_history_idx
==
-
1
)
{
for
(
i
=
0
;
i
<
AVG_SAMPLE_SIZE
;
i
++
)
pdev
->
socket_power_history
[
i
]
=
socket_power
;
}
pdev
->
socket_power_history_idx
=
(
pdev
->
socket_power_history_idx
+
1
)
%
AVG_SAMPLE_SIZE
;
pdev
->
socket_power_history
[
pdev
->
socket_power_history_idx
]
=
socket_power
;
for
(
i
=
0
;
i
<
AVG_SAMPLE_SIZE
;
i
++
)
total
+=
pdev
->
socket_power_history
[
i
];
return
total
/
AVG_SAMPLE_SIZE
;
}
void
amd_pmf_trans_automode
(
struct
amd_pmf_dev
*
dev
,
int
socket_power
,
ktime_t
time_elapsed_ms
)
{
int
avg_power
=
0
;
bool
update
=
false
;
int
i
,
j
;
/* Get the average moving average computed by auto mode algorithm */
avg_power
=
amd_pmf_get_moving_avg
(
dev
,
socket_power
);
for
(
i
=
0
;
i
<
AUTO_TRANSITION_MAX
;
i
++
)
{
if
((
config_store
.
transition
[
i
].
shifting_up
&&
avg_power
>=
config_store
.
transition
[
i
].
power_threshold
)
||
(
!
config_store
.
transition
[
i
].
shifting_up
&&
avg_power
<=
config_store
.
transition
[
i
].
power_threshold
))
{
if
(
config_store
.
transition
[
i
].
timer
<
config_store
.
transition
[
i
].
time_constant
)
config_store
.
transition
[
i
].
timer
+=
time_elapsed_ms
;
}
else
{
config_store
.
transition
[
i
].
timer
=
0
;
}
if
(
config_store
.
transition
[
i
].
timer
>=
config_store
.
transition
[
i
].
time_constant
&&
!
config_store
.
transition
[
i
].
applied
)
{
config_store
.
transition
[
i
].
applied
=
true
;
update
=
true
;
}
else
if
(
config_store
.
transition
[
i
].
timer
<=
config_store
.
transition
[
i
].
time_constant
&&
config_store
.
transition
[
i
].
applied
)
{
config_store
.
transition
[
i
].
applied
=
false
;
update
=
true
;
}
}
dev_dbg
(
dev
->
dev
,
"[AUTO_MODE] avg power: %u mW mode: %s
\n
"
,
avg_power
,
state_as_str
(
config_store
.
current_mode
));
if
(
update
)
{
for
(
j
=
0
;
j
<
AUTO_TRANSITION_MAX
;
j
++
)
{
/* Apply the mode with highest priority indentified */
if
(
config_store
.
transition
[
j
].
applied
)
{
if
(
config_store
.
current_mode
!=
config_store
.
transition
[
j
].
target_mode
)
{
config_store
.
current_mode
=
config_store
.
transition
[
j
].
target_mode
;
dev_dbg
(
dev
->
dev
,
"[AUTO_MODE] moving to mode:%s
\n
"
,
state_as_str
(
config_store
.
current_mode
));
amd_pmf_set_automode
(
dev
,
config_store
.
current_mode
,
NULL
);
}
break
;
}
}
}
}
void
amd_pmf_update_2_cql
(
struct
amd_pmf_dev
*
dev
,
bool
is_cql_event
)
{
int
mode
=
config_store
.
current_mode
;
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
target_mode
=
is_cql_event
?
AUTO_PERFORMANCE_ON_LAP
:
AUTO_PERFORMANCE
;
if
((
mode
==
AUTO_PERFORMANCE
||
mode
==
AUTO_PERFORMANCE_ON_LAP
)
&&
mode
!=
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
target_mode
)
{
mode
=
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
target_mode
;
amd_pmf_set_automode
(
dev
,
mode
,
NULL
);
}
dev_dbg
(
dev
->
dev
,
"updated CQL thermals
\n
"
);
}
static
void
amd_pmf_get_power_threshold
(
void
)
{
config_store
.
transition
[
AUTO_TRANSITION_TO_QUIET
].
power_threshold
=
config_store
.
mode_set
[
AUTO_BALANCE
].
power_floor
-
config_store
.
transition
[
AUTO_TRANSITION_TO_QUIET
].
power_delta
;
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
power_threshold
=
config_store
.
mode_set
[
AUTO_BALANCE
].
power_floor
-
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
power_delta
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE
].
power_threshold
=
config_store
.
mode_set
[
AUTO_QUIET
].
power_floor
-
config_store
.
transition
[
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE
].
power_delta
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE
].
power_threshold
=
config_store
.
mode_set
[
AUTO_PERFORMANCE
].
power_floor
-
config_store
.
transition
[
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE
].
power_delta
;
}
static
const
char
*
state_as_str
(
unsigned
int
state
)
{
switch
(
state
)
{
case
AUTO_QUIET
:
return
"QUIET"
;
case
AUTO_BALANCE
:
return
"BALANCED"
;
case
AUTO_PERFORMANCE_ON_LAP
:
return
"ON_LAP"
;
case
AUTO_PERFORMANCE
:
return
"PERFORMANCE"
;
default:
return
"Unknown Auto Mode State"
;
}
}
static
void
amd_pmf_load_defaults_auto_mode
(
struct
amd_pmf_dev
*
dev
)
{
struct
apmf_auto_mode
output
;
struct
power_table_control
*
pwr_ctrl
;
int
i
;
apmf_get_auto_mode_def
(
dev
,
&
output
);
/* time constant */
config_store
.
transition
[
AUTO_TRANSITION_TO_QUIET
].
time_constant
=
output
.
balanced_to_quiet
;
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
time_constant
=
output
.
balanced_to_perf
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE
].
time_constant
=
output
.
quiet_to_balanced
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE
].
time_constant
=
output
.
perf_to_balanced
;
/* power floor */
config_store
.
mode_set
[
AUTO_QUIET
].
power_floor
=
output
.
pfloor_quiet
;
config_store
.
mode_set
[
AUTO_BALANCE
].
power_floor
=
output
.
pfloor_balanced
;
config_store
.
mode_set
[
AUTO_PERFORMANCE
].
power_floor
=
output
.
pfloor_perf
;
config_store
.
mode_set
[
AUTO_PERFORMANCE_ON_LAP
].
power_floor
=
output
.
pfloor_perf
;
/* Power delta for mode change */
config_store
.
transition
[
AUTO_TRANSITION_TO_QUIET
].
power_delta
=
output
.
pd_balanced_to_quiet
;
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
power_delta
=
output
.
pd_balanced_to_perf
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE
].
power_delta
=
output
.
pd_quiet_to_balanced
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE
].
power_delta
=
output
.
pd_perf_to_balanced
;
/* Power threshold */
amd_pmf_get_power_threshold
();
/* skin temperature limits */
pwr_ctrl
=
&
config_store
.
mode_set
[
AUTO_QUIET
].
power_control
;
pwr_ctrl
->
spl
=
output
.
spl_quiet
;
pwr_ctrl
->
sppt
=
output
.
sppt_quiet
;
pwr_ctrl
->
fppt
=
output
.
fppt_quiet
;
pwr_ctrl
->
sppt_apu_only
=
output
.
sppt_apu_only_quiet
;
pwr_ctrl
->
stt_min
=
output
.
stt_min_limit_quiet
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_APU
]
=
output
.
stt_apu_quiet
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_HS2
]
=
output
.
stt_hs2_quiet
;
pwr_ctrl
=
&
config_store
.
mode_set
[
AUTO_BALANCE
].
power_control
;
pwr_ctrl
->
spl
=
output
.
spl_balanced
;
pwr_ctrl
->
sppt
=
output
.
sppt_balanced
;
pwr_ctrl
->
fppt
=
output
.
fppt_balanced
;
pwr_ctrl
->
sppt_apu_only
=
output
.
sppt_apu_only_balanced
;
pwr_ctrl
->
stt_min
=
output
.
stt_min_limit_balanced
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_APU
]
=
output
.
stt_apu_balanced
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_HS2
]
=
output
.
stt_hs2_balanced
;
pwr_ctrl
=
&
config_store
.
mode_set
[
AUTO_PERFORMANCE
].
power_control
;
pwr_ctrl
->
spl
=
output
.
spl_perf
;
pwr_ctrl
->
sppt
=
output
.
sppt_perf
;
pwr_ctrl
->
fppt
=
output
.
fppt_perf
;
pwr_ctrl
->
sppt_apu_only
=
output
.
sppt_apu_only_perf
;
pwr_ctrl
->
stt_min
=
output
.
stt_min_limit_perf
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_APU
]
=
output
.
stt_apu_perf
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_HS2
]
=
output
.
stt_hs2_perf
;
pwr_ctrl
=
&
config_store
.
mode_set
[
AUTO_PERFORMANCE_ON_LAP
].
power_control
;
pwr_ctrl
->
spl
=
output
.
spl_perf_on_lap
;
pwr_ctrl
->
sppt
=
output
.
sppt_perf_on_lap
;
pwr_ctrl
->
fppt
=
output
.
fppt_perf_on_lap
;
pwr_ctrl
->
sppt_apu_only
=
output
.
sppt_apu_only_perf_on_lap
;
pwr_ctrl
->
stt_min
=
output
.
stt_min_limit_perf_on_lap
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_APU
]
=
output
.
stt_apu_perf_on_lap
;
pwr_ctrl
->
stt_skin_temp
[
STT_TEMP_HS2
]
=
output
.
stt_hs2_perf_on_lap
;
/* Fan ID */
config_store
.
mode_set
[
AUTO_QUIET
].
fan_control
.
fan_id
=
output
.
fan_id_quiet
;
config_store
.
mode_set
[
AUTO_BALANCE
].
fan_control
.
fan_id
=
output
.
fan_id_balanced
;
config_store
.
mode_set
[
AUTO_PERFORMANCE
].
fan_control
.
fan_id
=
output
.
fan_id_perf
;
config_store
.
mode_set
[
AUTO_PERFORMANCE_ON_LAP
].
fan_control
.
fan_id
=
output
.
fan_id_perf
;
config_store
.
transition
[
AUTO_TRANSITION_TO_QUIET
].
target_mode
=
AUTO_QUIET
;
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
target_mode
=
AUTO_PERFORMANCE
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE
].
target_mode
=
AUTO_BALANCE
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE
].
target_mode
=
AUTO_BALANCE
;
config_store
.
transition
[
AUTO_TRANSITION_TO_QUIET
].
shifting_up
=
false
;
config_store
.
transition
[
AUTO_TRANSITION_TO_PERFORMANCE
].
shifting_up
=
true
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE
].
shifting_up
=
true
;
config_store
.
transition
[
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE
].
shifting_up
=
false
;
for
(
i
=
0
;
i
<
AUTO_MODE_MAX
;
i
++
)
{
if
(
config_store
.
mode_set
[
i
].
fan_control
.
fan_id
==
FAN_INDEX_AUTO
)
config_store
.
mode_set
[
i
].
fan_control
.
manual
=
false
;
else
config_store
.
mode_set
[
i
].
fan_control
.
manual
=
true
;
}
/* set to initial default values */
config_store
.
current_mode
=
AUTO_BALANCE
;
dev
->
socket_power_history_idx
=
-
1
;
}
int
amd_pmf_reset_amt
(
struct
amd_pmf_dev
*
dev
)
{
/*
* OEM BIOS implementation guide says that if the auto mode is enabled
* the platform_profile registration shall be done by the OEM driver.
* There could be cases where both static slider and auto mode BIOS
* functions are enabled, in that case enable static slider updates
* only if it advertised as supported.
*/
if
(
is_apmf_func_supported
(
dev
,
APMF_FUNC_STATIC_SLIDER_GRANULAR
))
{
int
mode
=
amd_pmf_get_pprof_modes
(
dev
);
if
(
mode
<
0
)
return
mode
;
dev_dbg
(
dev
->
dev
,
"resetting AMT thermals
\n
"
);
amd_pmf_update_slider
(
dev
,
SLIDER_OP_SET
,
mode
,
NULL
);
}
return
0
;
}
void
amd_pmf_handle_amt
(
struct
amd_pmf_dev
*
dev
)
{
amd_pmf_set_automode
(
dev
,
config_store
.
current_mode
,
NULL
);
}
void
amd_pmf_deinit_auto_mode
(
struct
amd_pmf_dev
*
dev
)
{
cancel_delayed_work_sync
(
&
dev
->
work_buffer
);
}
void
amd_pmf_init_auto_mode
(
struct
amd_pmf_dev
*
dev
)
{
amd_pmf_load_defaults_auto_mode
(
dev
);
/* update the thermal limits for Automode */
amd_pmf_set_automode
(
dev
,
config_store
.
current_mode
,
NULL
);
amd_pmf_init_metrics_table
(
dev
);
}
drivers/platform/x86/amd/pmf/core.c
0 → 100644
View file @
d5a4dfc3
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include "pmf.h"
/* PMF-SMU communication registers */
#define AMD_PMF_REGISTER_MESSAGE 0xA18
#define AMD_PMF_REGISTER_RESPONSE 0xA78
#define AMD_PMF_REGISTER_ARGUMENT 0xA58
/* Base address of SMU for mapping physical address to virtual address */
#define AMD_PMF_SMU_INDEX_ADDRESS 0xB8
#define AMD_PMF_SMU_INDEX_DATA 0xBC
#define AMD_PMF_MAPPING_SIZE 0x01000
#define AMD_PMF_BASE_ADDR_OFFSET 0x10000
#define AMD_PMF_BASE_ADDR_LO 0x13B102E8
#define AMD_PMF_BASE_ADDR_HI 0x13B102EC
#define AMD_PMF_BASE_ADDR_LO_MASK GENMASK(15, 0)
#define AMD_PMF_BASE_ADDR_HI_MASK GENMASK(31, 20)
/* SMU Response Codes */
#define AMD_PMF_RESULT_OK 0x01
#define AMD_PMF_RESULT_CMD_REJECT_BUSY 0xFC
#define AMD_PMF_RESULT_CMD_REJECT_PREREQ 0xFD
#define AMD_PMF_RESULT_CMD_UNKNOWN 0xFE
#define AMD_PMF_RESULT_FAILED 0xFF
/* List of supported CPU ids */
#define AMD_CPU_ID_RMB 0x14b5
#define AMD_CPU_ID_PS 0x14e8
#define PMF_MSG_DELAY_MIN_US 50
#define RESPONSE_REGISTER_LOOP_MAX 20000
#define DELAY_MIN_US 2000
#define DELAY_MAX_US 3000
/* override Metrics Table sample size time (in ms) */
static
int
metrics_table_loop_ms
=
1000
;
module_param
(
metrics_table_loop_ms
,
int
,
0644
);
MODULE_PARM_DESC
(
metrics_table_loop_ms
,
"Metrics Table sample size time (default = 1000ms)"
);
/* Force load on supported older platforms */
static
bool
force_load
;
module_param
(
force_load
,
bool
,
0444
);
MODULE_PARM_DESC
(
force_load
,
"Force load this driver on supported older platforms (experimental)"
);
static
int
current_power_limits_show
(
struct
seq_file
*
seq
,
void
*
unused
)
{
struct
amd_pmf_dev
*
dev
=
seq
->
private
;
struct
amd_pmf_static_slider_granular
table
;
int
mode
,
src
=
0
;
mode
=
amd_pmf_get_pprof_modes
(
dev
);
if
(
mode
<
0
)
return
mode
;
src
=
amd_pmf_get_power_source
();
amd_pmf_update_slider
(
dev
,
SLIDER_OP_GET
,
mode
,
&
table
);
seq_printf
(
seq
,
"spl:%u fppt:%u sppt:%u sppt_apu_only:%u stt_min:%u stt[APU]:%u stt[HS2]: %u
\n
"
,
table
.
prop
[
src
][
mode
].
spl
,
table
.
prop
[
src
][
mode
].
fppt
,
table
.
prop
[
src
][
mode
].
sppt
,
table
.
prop
[
src
][
mode
].
sppt_apu_only
,
table
.
prop
[
src
][
mode
].
stt_min
,
table
.
prop
[
src
][
mode
].
stt_skin_temp
[
STT_TEMP_APU
],
table
.
prop
[
src
][
mode
].
stt_skin_temp
[
STT_TEMP_HS2
]);
return
0
;
}
DEFINE_SHOW_ATTRIBUTE
(
current_power_limits
);
static
void
amd_pmf_dbgfs_unregister
(
struct
amd_pmf_dev
*
dev
)
{
debugfs_remove_recursive
(
dev
->
dbgfs_dir
);
}
static
void
amd_pmf_dbgfs_register
(
struct
amd_pmf_dev
*
dev
)
{
dev
->
dbgfs_dir
=
debugfs_create_dir
(
"amd_pmf"
,
NULL
);
debugfs_create_file
(
"current_power_limits"
,
0644
,
dev
->
dbgfs_dir
,
dev
,
&
current_power_limits_fops
);
}
int
amd_pmf_get_power_source
(
void
)
{
if
(
power_supply_is_system_supplied
()
>
0
)
return
POWER_SOURCE_AC
;
else
return
POWER_SOURCE_DC
;
}
static
void
amd_pmf_get_metrics
(
struct
work_struct
*
work
)
{
struct
amd_pmf_dev
*
dev
=
container_of
(
work
,
struct
amd_pmf_dev
,
work_buffer
.
work
);
ktime_t
time_elapsed_ms
;
int
socket_power
;
mutex_lock
(
&
dev
->
update_mutex
);
/* Transfer table contents */
memset
(
dev
->
buf
,
0
,
sizeof
(
dev
->
m_table
));
amd_pmf_send_cmd
(
dev
,
SET_TRANSFER_TABLE
,
0
,
7
,
NULL
);
memcpy
(
&
dev
->
m_table
,
dev
->
buf
,
sizeof
(
dev
->
m_table
));
time_elapsed_ms
=
ktime_to_ms
(
ktime_get
())
-
dev
->
start_time
;
/* Calculate the avg SoC power consumption */
socket_power
=
dev
->
m_table
.
apu_power
+
dev
->
m_table
.
dgpu_power
;
if
(
dev
->
amt_enabled
)
{
/* Apply the Auto Mode transition */
amd_pmf_trans_automode
(
dev
,
socket_power
,
time_elapsed_ms
);
}
dev
->
start_time
=
ktime_to_ms
(
ktime_get
());
schedule_delayed_work
(
&
dev
->
work_buffer
,
msecs_to_jiffies
(
metrics_table_loop_ms
));
mutex_unlock
(
&
dev
->
update_mutex
);
}
static
inline
u32
amd_pmf_reg_read
(
struct
amd_pmf_dev
*
dev
,
int
reg_offset
)
{
return
ioread32
(
dev
->
regbase
+
reg_offset
);
}
static
inline
void
amd_pmf_reg_write
(
struct
amd_pmf_dev
*
dev
,
int
reg_offset
,
u32
val
)
{
iowrite32
(
val
,
dev
->
regbase
+
reg_offset
);
}
static
void
__maybe_unused
amd_pmf_dump_registers
(
struct
amd_pmf_dev
*
dev
)
{
u32
value
;
value
=
amd_pmf_reg_read
(
dev
,
AMD_PMF_REGISTER_RESPONSE
);
dev_dbg
(
dev
->
dev
,
"AMD_PMF_REGISTER_RESPONSE:%x
\n
"
,
value
);
value
=
amd_pmf_reg_read
(
dev
,
AMD_PMF_REGISTER_ARGUMENT
);
dev_dbg
(
dev
->
dev
,
"AMD_PMF_REGISTER_ARGUMENT:%d
\n
"
,
value
);
value
=
amd_pmf_reg_read
(
dev
,
AMD_PMF_REGISTER_MESSAGE
);
dev_dbg
(
dev
->
dev
,
"AMD_PMF_REGISTER_MESSAGE:%x
\n
"
,
value
);
}
int
amd_pmf_send_cmd
(
struct
amd_pmf_dev
*
dev
,
u8
message
,
bool
get
,
u32
arg
,
u32
*
data
)
{
int
rc
;
u32
val
;
mutex_lock
(
&
dev
->
lock
);
/* Wait until we get a valid response */
rc
=
readx_poll_timeout
(
ioread32
,
dev
->
regbase
+
AMD_PMF_REGISTER_RESPONSE
,
val
,
val
!=
0
,
PMF_MSG_DELAY_MIN_US
,
PMF_MSG_DELAY_MIN_US
*
RESPONSE_REGISTER_LOOP_MAX
);
if
(
rc
)
{
dev_err
(
dev
->
dev
,
"failed to talk to SMU
\n
"
);
goto
out_unlock
;
}
/* Write zero to response register */
amd_pmf_reg_write
(
dev
,
AMD_PMF_REGISTER_RESPONSE
,
0
);
/* Write argument into argument register */
amd_pmf_reg_write
(
dev
,
AMD_PMF_REGISTER_ARGUMENT
,
arg
);
/* Write message ID to message ID register */
amd_pmf_reg_write
(
dev
,
AMD_PMF_REGISTER_MESSAGE
,
message
);
/* Wait until we get a valid response */
rc
=
readx_poll_timeout
(
ioread32
,
dev
->
regbase
+
AMD_PMF_REGISTER_RESPONSE
,
val
,
val
!=
0
,
PMF_MSG_DELAY_MIN_US
,
PMF_MSG_DELAY_MIN_US
*
RESPONSE_REGISTER_LOOP_MAX
);
if
(
rc
)
{
dev_err
(
dev
->
dev
,
"SMU response timed out
\n
"
);
goto
out_unlock
;
}
switch
(
val
)
{
case
AMD_PMF_RESULT_OK
:
if
(
get
)
{
/* PMFW may take longer time to return back the data */
usleep_range
(
DELAY_MIN_US
,
10
*
DELAY_MAX_US
);
*
data
=
amd_pmf_reg_read
(
dev
,
AMD_PMF_REGISTER_ARGUMENT
);
}
break
;
case
AMD_PMF_RESULT_CMD_REJECT_BUSY
:
dev_err
(
dev
->
dev
,
"SMU not ready. err: 0x%x
\n
"
,
val
);
rc
=
-
EBUSY
;
goto
out_unlock
;
case
AMD_PMF_RESULT_CMD_UNKNOWN
:
dev_err
(
dev
->
dev
,
"SMU cmd unknown. err: 0x%x
\n
"
,
val
);
rc
=
-
EINVAL
;
goto
out_unlock
;
case
AMD_PMF_RESULT_CMD_REJECT_PREREQ
:
case
AMD_PMF_RESULT_FAILED
:
default:
dev_err
(
dev
->
dev
,
"SMU cmd failed. err: 0x%x
\n
"
,
val
);
rc
=
-
EIO
;
goto
out_unlock
;
}
out_unlock:
mutex_unlock
(
&
dev
->
lock
);
amd_pmf_dump_registers
(
dev
);
return
rc
;
}
static
const
struct
pci_device_id
pmf_pci_ids
[]
=
{
{
PCI_DEVICE
(
PCI_VENDOR_ID_AMD
,
AMD_CPU_ID_RMB
)
},
{
PCI_DEVICE
(
PCI_VENDOR_ID_AMD
,
AMD_CPU_ID_PS
)
},
{
}
};
int
amd_pmf_init_metrics_table
(
struct
amd_pmf_dev
*
dev
)
{
u64
phys_addr
;
u32
hi
,
low
;
INIT_DELAYED_WORK
(
&
dev
->
work_buffer
,
amd_pmf_get_metrics
);
/* Get Metrics Table Address */
dev
->
buf
=
kzalloc
(
sizeof
(
dev
->
m_table
),
GFP_KERNEL
);
if
(
!
dev
->
buf
)
return
-
ENOMEM
;
phys_addr
=
virt_to_phys
(
dev
->
buf
);
hi
=
phys_addr
>>
32
;
low
=
phys_addr
&
GENMASK
(
31
,
0
);
amd_pmf_send_cmd
(
dev
,
SET_DRAM_ADDR_HIGH
,
0
,
hi
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_DRAM_ADDR_LOW
,
0
,
low
,
NULL
);
/*
* Start collecting the metrics data after a small delay
* or else, we might end up getting stale values from PMFW.
*/
schedule_delayed_work
(
&
dev
->
work_buffer
,
msecs_to_jiffies
(
metrics_table_loop_ms
*
3
));
return
0
;
}
static
void
amd_pmf_init_features
(
struct
amd_pmf_dev
*
dev
)
{
/* Enable Static Slider */
if
(
is_apmf_func_supported
(
dev
,
APMF_FUNC_STATIC_SLIDER_GRANULAR
))
{
amd_pmf_init_sps
(
dev
);
dev_dbg
(
dev
->
dev
,
"SPS enabled and Platform Profiles registered
\n
"
);
}
/* Enable Auto Mode */
if
(
is_apmf_func_supported
(
dev
,
APMF_FUNC_AUTO_MODE
))
{
amd_pmf_init_auto_mode
(
dev
);
dev_dbg
(
dev
->
dev
,
"Auto Mode Init done
\n
"
);
}
}
static
void
amd_pmf_deinit_features
(
struct
amd_pmf_dev
*
dev
)
{
if
(
is_apmf_func_supported
(
dev
,
APMF_FUNC_STATIC_SLIDER_GRANULAR
))
amd_pmf_deinit_sps
(
dev
);
if
(
is_apmf_func_supported
(
dev
,
APMF_FUNC_AUTO_MODE
))
amd_pmf_deinit_auto_mode
(
dev
);
}
static
const
struct
acpi_device_id
amd_pmf_acpi_ids
[]
=
{
{
"AMDI0100"
,
0x100
},
{
"AMDI0102"
,
0
},
{
}
};
MODULE_DEVICE_TABLE
(
acpi
,
amd_pmf_acpi_ids
);
static
int
amd_pmf_probe
(
struct
platform_device
*
pdev
)
{
const
struct
acpi_device_id
*
id
;
struct
amd_pmf_dev
*
dev
;
struct
pci_dev
*
rdev
;
u32
base_addr_lo
;
u32
base_addr_hi
;
u64
base_addr
;
u32
val
;
int
err
;
id
=
acpi_match_device
(
amd_pmf_acpi_ids
,
&
pdev
->
dev
);
if
(
!
id
)
return
-
ENODEV
;
if
(
id
->
driver_data
==
0x100
&&
!
force_load
)
return
-
ENODEV
;
dev
=
devm_kzalloc
(
&
pdev
->
dev
,
sizeof
(
*
dev
),
GFP_KERNEL
);
if
(
!
dev
)
return
-
ENOMEM
;
dev
->
dev
=
&
pdev
->
dev
;
rdev
=
pci_get_domain_bus_and_slot
(
0
,
0
,
PCI_DEVFN
(
0
,
0
));
if
(
!
rdev
||
!
pci_match_id
(
pmf_pci_ids
,
rdev
))
{
pci_dev_put
(
rdev
);
return
-
ENODEV
;
}
dev
->
cpu_id
=
rdev
->
device
;
err
=
pci_write_config_dword
(
rdev
,
AMD_PMF_SMU_INDEX_ADDRESS
,
AMD_PMF_BASE_ADDR_LO
);
if
(
err
)
{
dev_err
(
dev
->
dev
,
"error writing to 0x%x
\n
"
,
AMD_PMF_SMU_INDEX_ADDRESS
);
pci_dev_put
(
rdev
);
return
pcibios_err_to_errno
(
err
);
}
err
=
pci_read_config_dword
(
rdev
,
AMD_PMF_SMU_INDEX_DATA
,
&
val
);
if
(
err
)
{
pci_dev_put
(
rdev
);
return
pcibios_err_to_errno
(
err
);
}
base_addr_lo
=
val
&
AMD_PMF_BASE_ADDR_HI_MASK
;
err
=
pci_write_config_dword
(
rdev
,
AMD_PMF_SMU_INDEX_ADDRESS
,
AMD_PMF_BASE_ADDR_HI
);
if
(
err
)
{
dev_err
(
dev
->
dev
,
"error writing to 0x%x
\n
"
,
AMD_PMF_SMU_INDEX_ADDRESS
);
pci_dev_put
(
rdev
);
return
pcibios_err_to_errno
(
err
);
}
err
=
pci_read_config_dword
(
rdev
,
AMD_PMF_SMU_INDEX_DATA
,
&
val
);
if
(
err
)
{
pci_dev_put
(
rdev
);
return
pcibios_err_to_errno
(
err
);
}
base_addr_hi
=
val
&
AMD_PMF_BASE_ADDR_LO_MASK
;
pci_dev_put
(
rdev
);
base_addr
=
((
u64
)
base_addr_hi
<<
32
|
base_addr_lo
);
dev
->
regbase
=
devm_ioremap
(
dev
->
dev
,
base_addr
+
AMD_PMF_BASE_ADDR_OFFSET
,
AMD_PMF_MAPPING_SIZE
);
if
(
!
dev
->
regbase
)
return
-
ENOMEM
;
apmf_acpi_init
(
dev
);
platform_set_drvdata
(
pdev
,
dev
);
amd_pmf_init_features
(
dev
);
amd_pmf_dbgfs_register
(
dev
);
mutex_init
(
&
dev
->
lock
);
mutex_init
(
&
dev
->
update_mutex
);
dev_info
(
dev
->
dev
,
"registered PMF device successfully
\n
"
);
return
0
;
}
static
int
amd_pmf_remove
(
struct
platform_device
*
pdev
)
{
struct
amd_pmf_dev
*
dev
=
platform_get_drvdata
(
pdev
);
mutex_destroy
(
&
dev
->
lock
);
mutex_destroy
(
&
dev
->
update_mutex
);
amd_pmf_deinit_features
(
dev
);
apmf_acpi_deinit
(
dev
);
amd_pmf_dbgfs_unregister
(
dev
);
kfree
(
dev
->
buf
);
return
0
;
}
static
struct
platform_driver
amd_pmf_driver
=
{
.
driver
=
{
.
name
=
"amd-pmf"
,
.
acpi_match_table
=
amd_pmf_acpi_ids
,
},
.
probe
=
amd_pmf_probe
,
.
remove
=
amd_pmf_remove
,
};
module_platform_driver
(
amd_pmf_driver
);
MODULE_LICENSE
(
"GPL"
);
MODULE_DESCRIPTION
(
"AMD Platform Management Framework Driver"
);
drivers/platform/x86/amd/pmf/pmf.h
0 → 100644
View file @
d5a4dfc3
/* SPDX-License-Identifier: GPL-2.0 */
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#ifndef PMF_H
#define PMF_H
#include <linux/acpi.h>
#include <linux/platform_profile.h>
/* APMF Functions */
#define APMF_FUNC_VERIFY_INTERFACE 0
#define APMF_FUNC_GET_SYS_PARAMS 1
#define APMF_FUNC_SBIOS_REQUESTS 2
#define APMF_FUNC_SBIOS_HEARTBEAT 4
#define APMF_FUNC_AUTO_MODE 5
#define APMF_FUNC_SET_FAN_IDX 7
#define APMF_FUNC_STATIC_SLIDER_GRANULAR 9
/* Message Definitions */
#define SET_SPL 0x03
/* SPL: Sustained Power Limit */
#define SET_SPPT 0x05
/* SPPT: Slow Package Power Tracking */
#define SET_FPPT 0x07
/* FPPT: Fast Package Power Tracking */
#define GET_SPL 0x0B
#define GET_SPPT 0x0D
#define GET_FPPT 0x0F
#define SET_DRAM_ADDR_HIGH 0x14
#define SET_DRAM_ADDR_LOW 0x15
#define SET_TRANSFER_TABLE 0x16
#define SET_STT_MIN_LIMIT 0x18
/* STT: Skin Temperature Tracking */
#define SET_STT_LIMIT_APU 0x19
#define SET_STT_LIMIT_HS2 0x1A
#define SET_SPPT_APU_ONLY 0x1D
#define GET_SPPT_APU_ONLY 0x1E
#define GET_STT_MIN_LIMIT 0x1F
#define GET_STT_LIMIT_APU 0x20
#define GET_STT_LIMIT_HS2 0x21
/* Fan Index for Auto Mode */
#define FAN_INDEX_AUTO 0xFFFFFFFF
#define ARG_NONE 0
#define AVG_SAMPLE_SIZE 3
/* AMD PMF BIOS interfaces */
struct
apmf_verify_interface
{
u16
size
;
u16
version
;
u32
notification_mask
;
u32
supported_functions
;
}
__packed
;
struct
apmf_system_params
{
u16
size
;
u32
valid_mask
;
u32
flags
;
u8
command_code
;
u32
heartbeat_int
;
}
__packed
;
struct
apmf_sbios_req
{
u16
size
;
u32
pending_req
;
u8
rsd
;
u8
cql_event
;
u8
amt_event
;
u32
fppt
;
u32
sppt
;
u32
fppt_apu_only
;
u32
spl
;
u32
stt_min_limit
;
u8
skin_temp_apu
;
u8
skin_temp_hs2
;
}
__packed
;
struct
apmf_fan_idx
{
u16
size
;
u8
fan_ctl_mode
;
u32
fan_ctl_idx
;
}
__packed
;
struct
smu_pmf_metrics
{
u16
gfxclk_freq
;
/* in MHz */
u16
socclk_freq
;
/* in MHz */
u16
vclk_freq
;
/* in MHz */
u16
dclk_freq
;
/* in MHz */
u16
memclk_freq
;
/* in MHz */
u16
spare
;
u16
gfx_activity
;
/* in Centi */
u16
uvd_activity
;
/* in Centi */
u16
voltage
[
2
];
/* in mV */
u16
currents
[
2
];
/* in mA */
u16
power
[
2
];
/* in mW */
u16
core_freq
[
8
];
/* in MHz */
u16
core_power
[
8
];
/* in mW */
u16
core_temp
[
8
];
/* in centi-Celsius */
u16
l3_freq
;
/* in MHz */
u16
l3_temp
;
/* in centi-Celsius */
u16
gfx_temp
;
/* in centi-Celsius */
u16
soc_temp
;
/* in centi-Celsius */
u16
throttler_status
;
u16
current_socketpower
;
/* in mW */
u16
stapm_orig_limit
;
/* in W */
u16
stapm_cur_limit
;
/* in W */
u32
apu_power
;
/* in mW */
u32
dgpu_power
;
/* in mW */
u16
vdd_tdc_val
;
/* in mA */
u16
soc_tdc_val
;
/* in mA */
u16
vdd_edc_val
;
/* in mA */
u16
soc_edcv_al
;
/* in mA */
u16
infra_cpu_maxfreq
;
/* in MHz */
u16
infra_gfx_maxfreq
;
/* in MHz */
u16
skin_temp
;
/* in centi-Celsius */
u16
device_state
;
}
__packed
;
enum
amd_stt_skin_temp
{
STT_TEMP_APU
,
STT_TEMP_HS2
,
STT_TEMP_COUNT
,
};
enum
amd_slider_op
{
SLIDER_OP_GET
,
SLIDER_OP_SET
,
};
enum
power_source
{
POWER_SOURCE_AC
,
POWER_SOURCE_DC
,
POWER_SOURCE_MAX
,
};
enum
power_modes
{
POWER_MODE_PERFORMANCE
,
POWER_MODE_BALANCED_POWER
,
POWER_MODE_POWER_SAVER
,
POWER_MODE_MAX
,
};
struct
amd_pmf_dev
{
void
__iomem
*
regbase
;
void
__iomem
*
smu_virt_addr
;
void
*
buf
;
u32
base_addr
;
u32
cpu_id
;
struct
device
*
dev
;
struct
mutex
lock
;
/* protects the PMF interface */
u32
supported_func
;
enum
platform_profile_option
current_profile
;
struct
platform_profile_handler
pprof
;
struct
dentry
*
dbgfs_dir
;
int
hb_interval
;
/* SBIOS heartbeat interval */
struct
delayed_work
heart_beat
;
struct
smu_pmf_metrics
m_table
;
struct
delayed_work
work_buffer
;
ktime_t
start_time
;
int
socket_power_history
[
AVG_SAMPLE_SIZE
];
int
socket_power_history_idx
;
bool
amt_enabled
;
struct
mutex
update_mutex
;
/* protects race between ACPI handler and metrics thread */
};
struct
apmf_sps_prop_granular
{
u32
fppt
;
u32
sppt
;
u32
sppt_apu_only
;
u32
spl
;
u32
stt_min
;
u8
stt_skin_temp
[
STT_TEMP_COUNT
];
u32
fan_id
;
}
__packed
;
/* Static Slider */
struct
apmf_static_slider_granular_output
{
u16
size
;
struct
apmf_sps_prop_granular
prop
[
POWER_SOURCE_MAX
*
POWER_MODE_MAX
];
}
__packed
;
struct
amd_pmf_static_slider_granular
{
u16
size
;
struct
apmf_sps_prop_granular
prop
[
POWER_SOURCE_MAX
][
POWER_MODE_MAX
];
};
struct
fan_table_control
{
bool
manual
;
unsigned
long
fan_id
;
};
struct
power_table_control
{
u32
spl
;
u32
sppt
;
u32
fppt
;
u32
sppt_apu_only
;
u32
stt_min
;
u32
stt_skin_temp
[
STT_TEMP_COUNT
];
u32
reserved
[
16
];
};
/* Auto Mode Layer */
enum
auto_mode_transition_priority
{
AUTO_TRANSITION_TO_PERFORMANCE
,
/* Any other mode to Performance Mode */
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE
,
/* Quiet Mode to Balance Mode */
AUTO_TRANSITION_TO_QUIET
,
/* Any other mode to Quiet Mode */
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE
,
/* Performance Mode to Balance Mode */
AUTO_TRANSITION_MAX
,
};
enum
auto_mode_mode
{
AUTO_QUIET
,
AUTO_BALANCE
,
AUTO_PERFORMANCE_ON_LAP
,
AUTO_PERFORMANCE
,
AUTO_MODE_MAX
,
};
struct
auto_mode_trans_params
{
u32
time_constant
;
/* minimum time required to switch to next mode */
u32
power_delta
;
/* delta power to shift mode */
u32
power_threshold
;
u32
timer
;
/* elapsed time. if timer > TimeThreshold, it will move to next mode */
u32
applied
;
enum
auto_mode_mode
target_mode
;
u32
shifting_up
;
};
struct
auto_mode_mode_settings
{
struct
power_table_control
power_control
;
struct
fan_table_control
fan_control
;
u32
power_floor
;
};
struct
auto_mode_mode_config
{
struct
auto_mode_trans_params
transition
[
AUTO_TRANSITION_MAX
];
struct
auto_mode_mode_settings
mode_set
[
AUTO_MODE_MAX
];
enum
auto_mode_mode
current_mode
;
};
struct
apmf_auto_mode
{
u16
size
;
/* time constant */
u32
balanced_to_perf
;
u32
perf_to_balanced
;
u32
quiet_to_balanced
;
u32
balanced_to_quiet
;
/* power floor */
u32
pfloor_perf
;
u32
pfloor_balanced
;
u32
pfloor_quiet
;
/* Power delta for mode change */
u32
pd_balanced_to_perf
;
u32
pd_perf_to_balanced
;
u32
pd_quiet_to_balanced
;
u32
pd_balanced_to_quiet
;
/* skin temperature limits */
u8
stt_apu_perf_on_lap
;
/* CQL ON */
u8
stt_hs2_perf_on_lap
;
/* CQL ON */
u8
stt_apu_perf
;
u8
stt_hs2_perf
;
u8
stt_apu_balanced
;
u8
stt_hs2_balanced
;
u8
stt_apu_quiet
;
u8
stt_hs2_quiet
;
u32
stt_min_limit_perf_on_lap
;
/* CQL ON */
u32
stt_min_limit_perf
;
u32
stt_min_limit_balanced
;
u32
stt_min_limit_quiet
;
/* SPL based */
u32
fppt_perf_on_lap
;
/* CQL ON */
u32
sppt_perf_on_lap
;
/* CQL ON */
u32
spl_perf_on_lap
;
/* CQL ON */
u32
sppt_apu_only_perf_on_lap
;
/* CQL ON */
u32
fppt_perf
;
u32
sppt_perf
;
u32
spl_perf
;
u32
sppt_apu_only_perf
;
u32
fppt_balanced
;
u32
sppt_balanced
;
u32
spl_balanced
;
u32
sppt_apu_only_balanced
;
u32
fppt_quiet
;
u32
sppt_quiet
;
u32
spl_quiet
;
u32
sppt_apu_only_quiet
;
/* Fan ID */
u32
fan_id_perf
;
u32
fan_id_balanced
;
u32
fan_id_quiet
;
}
__packed
;
/* Core Layer */
int
apmf_acpi_init
(
struct
amd_pmf_dev
*
pmf_dev
);
void
apmf_acpi_deinit
(
struct
amd_pmf_dev
*
pmf_dev
);
int
is_apmf_func_supported
(
struct
amd_pmf_dev
*
pdev
,
unsigned
long
index
);
int
amd_pmf_send_cmd
(
struct
amd_pmf_dev
*
dev
,
u8
message
,
bool
get
,
u32
arg
,
u32
*
data
);
int
amd_pmf_init_metrics_table
(
struct
amd_pmf_dev
*
dev
);
int
amd_pmf_get_power_source
(
void
);
/* SPS Layer */
int
amd_pmf_get_pprof_modes
(
struct
amd_pmf_dev
*
pmf
);
void
amd_pmf_update_slider
(
struct
amd_pmf_dev
*
dev
,
bool
op
,
int
idx
,
struct
amd_pmf_static_slider_granular
*
table
);
int
amd_pmf_init_sps
(
struct
amd_pmf_dev
*
dev
);
void
amd_pmf_deinit_sps
(
struct
amd_pmf_dev
*
dev
);
int
apmf_get_static_slider_granular
(
struct
amd_pmf_dev
*
pdev
,
struct
apmf_static_slider_granular_output
*
output
);
int
apmf_update_fan_idx
(
struct
amd_pmf_dev
*
pdev
,
bool
manual
,
u32
idx
);
/* Auto Mode Layer */
int
apmf_get_auto_mode_def
(
struct
amd_pmf_dev
*
pdev
,
struct
apmf_auto_mode
*
data
);
void
amd_pmf_init_auto_mode
(
struct
amd_pmf_dev
*
dev
);
void
amd_pmf_deinit_auto_mode
(
struct
amd_pmf_dev
*
dev
);
void
amd_pmf_trans_automode
(
struct
amd_pmf_dev
*
dev
,
int
socket_power
,
ktime_t
time_elapsed_ms
);
int
apmf_get_sbios_requests
(
struct
amd_pmf_dev
*
pdev
,
struct
apmf_sbios_req
*
req
);
void
amd_pmf_update_2_cql
(
struct
amd_pmf_dev
*
dev
,
bool
is_cql_event
);
int
amd_pmf_reset_amt
(
struct
amd_pmf_dev
*
dev
);
void
amd_pmf_handle_amt
(
struct
amd_pmf_dev
*
dev
);
#endif
/* PMF_H */
drivers/platform/x86/amd/pmf/sps.c
0 → 100644
View file @
d5a4dfc3
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework (PMF) Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include "pmf.h"
static
struct
amd_pmf_static_slider_granular
config_store
;
static
void
amd_pmf_load_defaults_sps
(
struct
amd_pmf_dev
*
dev
)
{
struct
apmf_static_slider_granular_output
output
;
int
i
,
j
,
idx
=
0
;
memset
(
&
config_store
,
0
,
sizeof
(
config_store
));
apmf_get_static_slider_granular
(
dev
,
&
output
);
for
(
i
=
0
;
i
<
POWER_SOURCE_MAX
;
i
++
)
{
for
(
j
=
0
;
j
<
POWER_MODE_MAX
;
j
++
)
{
config_store
.
prop
[
i
][
j
].
spl
=
output
.
prop
[
idx
].
spl
;
config_store
.
prop
[
i
][
j
].
sppt
=
output
.
prop
[
idx
].
sppt
;
config_store
.
prop
[
i
][
j
].
sppt_apu_only
=
output
.
prop
[
idx
].
sppt_apu_only
;
config_store
.
prop
[
i
][
j
].
fppt
=
output
.
prop
[
idx
].
fppt
;
config_store
.
prop
[
i
][
j
].
stt_min
=
output
.
prop
[
idx
].
stt_min
;
config_store
.
prop
[
i
][
j
].
stt_skin_temp
[
STT_TEMP_APU
]
=
output
.
prop
[
idx
].
stt_skin_temp
[
STT_TEMP_APU
];
config_store
.
prop
[
i
][
j
].
stt_skin_temp
[
STT_TEMP_HS2
]
=
output
.
prop
[
idx
].
stt_skin_temp
[
STT_TEMP_HS2
];
config_store
.
prop
[
i
][
j
].
fan_id
=
output
.
prop
[
idx
].
fan_id
;
idx
++
;
}
}
}
void
amd_pmf_update_slider
(
struct
amd_pmf_dev
*
dev
,
bool
op
,
int
idx
,
struct
amd_pmf_static_slider_granular
*
table
)
{
int
src
=
amd_pmf_get_power_source
();
if
(
op
==
SLIDER_OP_SET
)
{
amd_pmf_send_cmd
(
dev
,
SET_SPL
,
false
,
config_store
.
prop
[
src
][
idx
].
spl
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_FPPT
,
false
,
config_store
.
prop
[
src
][
idx
].
fppt
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_SPPT
,
false
,
config_store
.
prop
[
src
][
idx
].
sppt
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_SPPT_APU_ONLY
,
false
,
config_store
.
prop
[
src
][
idx
].
sppt_apu_only
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_STT_MIN_LIMIT
,
false
,
config_store
.
prop
[
src
][
idx
].
stt_min
,
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_STT_LIMIT_APU
,
false
,
config_store
.
prop
[
src
][
idx
].
stt_skin_temp
[
STT_TEMP_APU
],
NULL
);
amd_pmf_send_cmd
(
dev
,
SET_STT_LIMIT_HS2
,
false
,
config_store
.
prop
[
src
][
idx
].
stt_skin_temp
[
STT_TEMP_HS2
],
NULL
);
}
else
if
(
op
==
SLIDER_OP_GET
)
{
amd_pmf_send_cmd
(
dev
,
GET_SPL
,
true
,
ARG_NONE
,
&
table
->
prop
[
src
][
idx
].
spl
);
amd_pmf_send_cmd
(
dev
,
GET_FPPT
,
true
,
ARG_NONE
,
&
table
->
prop
[
src
][
idx
].
fppt
);
amd_pmf_send_cmd
(
dev
,
GET_SPPT
,
true
,
ARG_NONE
,
&
table
->
prop
[
src
][
idx
].
sppt
);
amd_pmf_send_cmd
(
dev
,
GET_SPPT_APU_ONLY
,
true
,
ARG_NONE
,
&
table
->
prop
[
src
][
idx
].
sppt_apu_only
);
amd_pmf_send_cmd
(
dev
,
GET_STT_MIN_LIMIT
,
true
,
ARG_NONE
,
&
table
->
prop
[
src
][
idx
].
stt_min
);
amd_pmf_send_cmd
(
dev
,
GET_STT_LIMIT_APU
,
true
,
ARG_NONE
,
(
u32
*
)
&
table
->
prop
[
src
][
idx
].
stt_skin_temp
[
STT_TEMP_APU
]);
amd_pmf_send_cmd
(
dev
,
GET_STT_LIMIT_HS2
,
true
,
ARG_NONE
,
(
u32
*
)
&
table
->
prop
[
src
][
idx
].
stt_skin_temp
[
STT_TEMP_HS2
]);
}
}
static
int
amd_pmf_profile_get
(
struct
platform_profile_handler
*
pprof
,
enum
platform_profile_option
*
profile
)
{
struct
amd_pmf_dev
*
pmf
=
container_of
(
pprof
,
struct
amd_pmf_dev
,
pprof
);
*
profile
=
pmf
->
current_profile
;
return
0
;
}
int
amd_pmf_get_pprof_modes
(
struct
amd_pmf_dev
*
pmf
)
{
int
mode
;
switch
(
pmf
->
current_profile
)
{
case
PLATFORM_PROFILE_PERFORMANCE
:
mode
=
POWER_MODE_PERFORMANCE
;
break
;
case
PLATFORM_PROFILE_BALANCED
:
mode
=
POWER_MODE_BALANCED_POWER
;
break
;
case
PLATFORM_PROFILE_LOW_POWER
:
mode
=
POWER_MODE_POWER_SAVER
;
break
;
default:
dev_err
(
pmf
->
dev
,
"Unknown Platform Profile.
\n
"
);
return
-
EOPNOTSUPP
;
}
return
mode
;
}
static
int
amd_pmf_profile_set
(
struct
platform_profile_handler
*
pprof
,
enum
platform_profile_option
profile
)
{
struct
amd_pmf_dev
*
pmf
=
container_of
(
pprof
,
struct
amd_pmf_dev
,
pprof
);
int
mode
;
pmf
->
current_profile
=
profile
;
mode
=
amd_pmf_get_pprof_modes
(
pmf
);
if
(
mode
<
0
)
return
mode
;
amd_pmf_update_slider
(
pmf
,
SLIDER_OP_SET
,
mode
,
NULL
);
return
0
;
}
int
amd_pmf_init_sps
(
struct
amd_pmf_dev
*
dev
)
{
int
err
;
dev
->
current_profile
=
PLATFORM_PROFILE_BALANCED
;
amd_pmf_load_defaults_sps
(
dev
);
dev
->
pprof
.
profile_get
=
amd_pmf_profile_get
;
dev
->
pprof
.
profile_set
=
amd_pmf_profile_set
;
/* Setup supported modes */
set_bit
(
PLATFORM_PROFILE_LOW_POWER
,
dev
->
pprof
.
choices
);
set_bit
(
PLATFORM_PROFILE_BALANCED
,
dev
->
pprof
.
choices
);
set_bit
(
PLATFORM_PROFILE_PERFORMANCE
,
dev
->
pprof
.
choices
);
/* Create platform_profile structure and register */
err
=
platform_profile_register
(
&
dev
->
pprof
);
if
(
err
)
dev_err
(
dev
->
dev
,
"Failed to register SPS support, this is most likely an SBIOS bug: %d
\n
"
,
err
);
return
err
;
}
void
amd_pmf_deinit_sps
(
struct
amd_pmf_dev
*
dev
)
{
platform_profile_remove
();
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment