Commit 59348401 authored by Mario Limonciello's avatar Mario Limonciello Committed by Hans de Goede
Browse files

platform/x86: amd-pmc: Add special handling for timer based S0i3 wakeup



RTC based wakeup from s0i3 doesn't work properly on some Green Sardine
platforms. Because of this, a newer SMU for Green Sardine has the ability
to pass wakeup time as argument of the upper 16 bits of OS_HINT message.

With older firmware setting the timer value in OS_HINT will cause firmware
to reject the hint, so only run this path on:
1) Green Sardine
2) Minimum SMU FW
3) RTC alarm armed during s0i3 entry

Using this method has some limitations that the s0i3 wakeup will need to
be between 4 seconds and 18 hours, so check those boundary conditions as
well and abort the suspend if RTC is armed for too short or too long of a
duration.

Signed-off-by: default avatarMario Limonciello <mario.limonciello@amd.com>
Link: https://lore.kernel.org/r/20211020162946.10537-2-mario.limonciello@amd.com


Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
parent 4c9dbf86
Loading
Loading
Loading
Loading
+59 −1
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>
#include <linux/suspend.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
@@ -412,20 +414,76 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev)
	return -EINVAL;
}

static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg)
{
	struct rtc_device *rtc_device;
	time64_t then, now, duration;
	struct rtc_wkalrm alarm;
	struct rtc_time tm;
	int rc;

	if (pdev->major < 64 || (pdev->major == 64 && pdev->minor < 53))
		return 0;

	rtc_device = rtc_class_open(CONFIG_RTC_SYSTOHC_DEVICE);
	if (!rtc_device)
		return 0;
	rc = rtc_read_alarm(rtc_device, &alarm);
	if (rc)
		return rc;
	if (!alarm.enabled) {
		dev_dbg(pdev->dev, "alarm not enabled\n");
		return 0;
	}
	rc = rtc_valid_tm(&alarm.time);
	if (rc)
		return rc;
	rc = rtc_read_time(rtc_device, &tm);
	if (rc)
		return rc;
	then = rtc_tm_to_time64(&alarm.time);
	now = rtc_tm_to_time64(&tm);
	duration = then-now;

	/* in the past */
	if (then < now)
		return 0;

	/* will be stored in upper 16 bits of s0i3 hint argument,
	 * so timer wakeup from s0i3 is limited to ~18 hours or less
	 */
	if (duration <= 4 || duration > U16_MAX)
		return -EINVAL;

	*arg |= (duration << 16);
	rc = rtc_alarm_irq_enable(rtc_device, 0);
	dev_info(pdev->dev, "wakeup timer programmed for %lld seconds\n", duration);

	return rc;
}

static int __maybe_unused amd_pmc_suspend(struct device *dev)
{
	struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
	int rc;
	u8 msg;
	u32 arg = 1;

	/* Reset and Start SMU logging - to monitor the s0i3 stats */
	amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_RESET, 0);
	amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_START, 0);

	/* Activate CZN specific RTC functionality */
	if (pdev->cpu_id == AMD_CPU_ID_CZN) {
		rc = amd_pmc_verify_czn_rtc(pdev, &arg);
		if (rc < 0)
			return rc;
	}

	/* Dump the IdleMask before we send hint to SMU */
	amd_pmc_idlemask_read(pdev, dev, NULL);
	msg = amd_pmc_get_os_hint(pdev);
	rc = amd_pmc_send_cmd(pdev, 1, NULL, msg, 0);
	rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0);
	if (rc)
		dev_err(pdev->dev, "suspend failed\n");