Files
android_kernel_samsung_sm8750/drivers/soc/qcom/qti-pmic-lpm.c
2025-08-12 22:16:57 +02:00

452 lines
11 KiB
C
Executable File

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include <linux/regmap.h>
#define QTI_PMIC_LPM_DEV_NAME "qti,pmic-lpm"
#define SDAM_PBS_ARG_REG 0x42
#define SDAM_INT_REASON_REG 0x47
#define APPS_LPM_ENTRY_BIT BIT(0)
#define APPS_LPM_EXIT_BIT BIT(1)
#define APPS_NOTIFY_BUTTON_OWNER BIT(2)
#define SDAM_INT_TEST1 0xE0
#define TEST_MODE_EN_BIT BIT(7)
#define SDAM_INT_TEST_VAL 0xE1
#define SDAM_INT_TEST_VAL_BIT BIT(1)
#define SDAM_HANDSHAKE_REG 0x48
#define SDAM_TWM_HANDSHAKE_BIT BIT(1)
static void qti_pmic_lpm_syscore_shutdown(void);
/**
* struct qti_pmic_lpm - Structure for QTI pmic lpm device
* @dev: Device pointer to the QTI pmic lpm device
* @regmap: Regmap structure for reads/writes
* @twm_class: Pointer to twm_class class
* @sdam_base: Base address of the pmic sdam
* @twm_enable: Flag to indicate TWM is enabled
*/
struct qti_pmic_lpm {
struct device *dev;
struct regmap *regmap;
struct class twm_class;
int sdam_base;
bool twm_enable;
bool twm_exit;
bool ds_exit;
};
static struct qti_pmic_lpm *gchip;
static int pmic_lpm_read(struct qti_pmic_lpm *chip, int addr, u8 *data, int len)
{
int rc;
rc = regmap_bulk_read(chip->regmap, chip->sdam_base + addr, data, len);
if (rc < 0)
pr_err("Failed to read from sdam addr:%#x,rc=%d\n",
chip->sdam_base + addr, rc);
return rc;
}
static int pmic_lpm_write(struct qti_pmic_lpm *chip, int addr, u8 *data, int len)
{
int rc;
rc = regmap_bulk_write(chip->regmap, chip->sdam_base + addr, data, len);
if (rc < 0)
pr_err("Failed to write to sdam addr:%#x,rc=%d\n",
chip->sdam_base + addr, rc);
return rc;
}
static int pmic_lpm_masked_write(struct qti_pmic_lpm *chip, int addr, u8 mask, u8 val)
{
int rc;
rc = regmap_update_bits(chip->regmap, chip->sdam_base + addr, mask, val);
if (rc < 0)
pr_err("Failed to write to sdam addr:%#x,rc=%d\n",
chip->sdam_base + addr, rc);
return rc;
}
static int qti_pmic_handle_lpm(struct qti_pmic_lpm *chip, bool entry)
{
int rc;
u8 val;
val = entry ? APPS_LPM_ENTRY_BIT : APPS_LPM_EXIT_BIT;
rc = pmic_lpm_write(chip, SDAM_INT_REASON_REG, &val, 1);
if (rc < 0) {
pr_err("Failed to write to pmic sdam offset %#x, rc=%d\n",
SDAM_INT_REASON_REG, rc);
return rc;
}
val = SDAM_INT_TEST_VAL_BIT;
rc = pmic_lpm_write(chip, SDAM_INT_TEST_VAL, &val, 1);
if (rc < 0)
pr_err("Failed to write to pmic sdam offset %#x, rc=%d\n",
SDAM_INT_TEST_VAL, rc);
val = 0;
rc = pmic_lpm_write(chip, SDAM_INT_TEST_VAL, &val, 1);
if (rc < 0)
pr_err("Failed to write to pmic sdam offset %#x, rc=%d\n",
SDAM_INT_TEST_VAL, rc);
pr_debug("PMIC LPM %s\n", entry ? "entry" : "exit");
return rc;
}
static ssize_t pmic_twm_enable_store(const struct class *c,
const struct class_attribute *attr,
const char *buf, size_t count)
{
struct qti_pmic_lpm *chip = container_of(c, struct qti_pmic_lpm,
twm_class);
u8 val = 0;
ssize_t rc;
rc = kstrtou8(buf, 10, &val);
if (rc < 0)
return rc;
chip->twm_enable = val ? true : false;
if (chip->twm_enable) {
/*
* Send APPS_NOTIFY_BUTTON_OWNER notification to Co-Proc
* as soon as the user-space intiates TWM entry to optimize
* Buttons response latency.
*/
val = APPS_NOTIFY_BUTTON_OWNER;
rc = pmic_lpm_write(gchip, SDAM_INT_REASON_REG, &val, 1);
if (rc < 0) {
pr_err("PMIC: Failed to write to pmic sdam offset %#x, rc=%ld\n",
SDAM_INT_REASON_REG, rc);
return -EINVAL;
}
val = SDAM_INT_TEST_VAL_BIT;
rc = pmic_lpm_write(gchip, SDAM_INT_TEST_VAL, &val, 1);
if (rc < 0) {
pr_err("PMIC: Failed to write to pmic sdam offset %#x, rc=%ld\n",
SDAM_INT_TEST_VAL, rc);
return -EINVAL;
}
val = 0;
rc = pmic_lpm_write(gchip, SDAM_INT_TEST_VAL, &val, 1);
if (rc < 0) {
pr_err("PMIC: Failed to write to pmic sdam offset %#x, rc=%ld\n",
SDAM_INT_TEST_VAL, rc);
return -EINVAL;
}
pr_info("PMIC : Successfully sent Button owner notification to Co-proc\n");
}
return count;
}
static ssize_t pmic_twm_enable_show(const struct class *c,
const struct class_attribute *attr, char *buf)
{
struct qti_pmic_lpm *chip = container_of(c, struct qti_pmic_lpm,
twm_class);
return scnprintf(buf, PAGE_SIZE, "%d\n", chip->twm_enable);
}
static ssize_t pmic_twm_exit_show(const struct class *c,
const struct class_attribute *attr, char *buf)
{
struct qti_pmic_lpm *chip = container_of(c, struct qti_pmic_lpm,
twm_class);
return scnprintf(buf, PAGE_SIZE, "%x\n", chip->twm_exit);
}
static ssize_t pmic_ds_exit_show(const struct class *c,
const struct class_attribute *attr, char *buf)
{
struct qti_pmic_lpm *chip = container_of(c, struct qti_pmic_lpm,
twm_class);
return scnprintf(buf, PAGE_SIZE, "%x\n", chip->ds_exit);
}
static CLASS_ATTR_RW(pmic_twm_enable);
static CLASS_ATTR_RO(pmic_twm_exit);
static CLASS_ATTR_RO(pmic_ds_exit);
static struct attribute *twm_attrs[] = {
&class_attr_pmic_twm_enable.attr,
&class_attr_pmic_twm_exit.attr,
&class_attr_pmic_ds_exit.attr,
NULL,
};
ATTRIBUTE_GROUPS(twm);
static struct syscore_ops qti_pmic_lpm_syscore_ops = {
.shutdown = qti_pmic_lpm_syscore_shutdown,
};
static int pmic_get_wakeup_status(struct qti_pmic_lpm *chip)
{
u8 val = 0;
int rc = 0;
chip->twm_exit = false;
chip->ds_exit = false;
rc = pmic_lpm_read(chip, SDAM_PBS_ARG_REG, &val, 1);
if (rc < 0) {
pr_err("Failed to read pmic sdam offset %#x, rc=%d\n",
SDAM_PBS_ARG_REG, rc);
return rc;
}
switch (val) {
case 0x06:
chip->twm_exit = true;
break;
case 0x04:
chip->ds_exit = true;
break;
default:
break;
}
return rc;
}
static int qti_pmic_lpm_probe(struct platform_device *pdev)
{
struct qti_pmic_lpm *chip;
int rc;
u8 val;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!chip->regmap) {
dev_err(&pdev->dev, "Failed to get regmap\n");
return -EINVAL;
}
rc = of_property_read_u32(pdev->dev.of_node, "reg", &chip->sdam_base);
if (rc < 0) {
pr_err("Failed to get base address of pmic sdam, rc=%d\n", rc);
return rc;
}
rc = pmic_lpm_read(chip, SDAM_INT_TEST1, &val, 1);
if (rc < 0) {
pr_err("Failed to read from pmic sdam offset %#x, rc=%d\n",
SDAM_INT_TEST1, rc);
return rc;
}
/* Enable interrupt test mode if not enabled already */
if (val ^ TEST_MODE_EN_BIT) {
val = TEST_MODE_EN_BIT;
rc = pmic_lpm_write(chip, SDAM_INT_TEST1, &val, 1);
if (rc < 0) {
pr_err("Failed to write to pmic sdam offset %#x, rc=%d\n",
SDAM_INT_TEST1, rc);
return rc;
}
}
rc = pmic_get_wakeup_status(chip);
if (rc < 0) {
pr_err("Failed to get the twm exit status rc=%d\n", rc);
return rc;
}
chip->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, chip);
chip->twm_class.name = "pmic-lpm";
chip->twm_class.class_groups = twm_groups;
rc = class_register(&chip->twm_class);
if (rc < 0) {
pr_err("Failed to register twm_class class rc=%d\n", rc);
return rc;
}
gchip = chip;
/**
* There is a possiblity where-in driver shutdown callback of
* LPM driver is called first before other PM drivers shutdown
* callback. This can lead to misconfiguration of INT registers.
* Hence notify co-proc in syscore_ops shutdown callback which is
* called after all the driver shutdown callbacks are called.
*/
register_syscore_ops(&qti_pmic_lpm_syscore_ops);
dev_info(chip->dev, "qti-pmic-lpm probe successful\n");
return 0;
}
static int qti_pmic_lpm_remove(struct platform_device *pdev)
{
struct qti_pmic_lpm *chip = platform_get_drvdata(pdev);
int rc;
u8 val = 0;
rc = pmic_lpm_write(chip, SDAM_INT_REASON_REG, &val, 1);
if (rc < 0) {
pr_err("Failed to write to pmic sdam offset %#x, rc=%d\n",
SDAM_INT_REASON_REG, rc);
return rc;
}
class_unregister(&chip->twm_class);
return rc;
}
#define TWM_HANDSHAKE_RETRY_COUNT 50
#define TWM_HANDSHAKE_COMPLETION_DATA 0x02
static void qti_pmic_lpm_syscore_shutdown(void)
{
int rc = 0;
u8 data = 0, count = TWM_HANDSHAKE_RETRY_COUNT;
if (gchip == NULL) {
pr_err("gchip is NULL\n");
return;
}
pr_debug("LPM Syscore shutdown twm_state : %d\n", gchip->twm_enable);
if (gchip->twm_enable) {
/*Clear the handshake bit before sending TWM notification */
pmic_lpm_masked_write(gchip, SDAM_HANDSHAKE_REG, SDAM_TWM_HANDSHAKE_BIT, 0);
rc = qti_pmic_handle_lpm(gchip, true);
if (rc < 0)
dev_err(gchip->dev, "Failed to handle twm entry, rc:%d\n",
rc);
/*
* Once Co-proc receives TWM entry notification -- It configures
* SPI block and sets SDAM48(BIT1) to notify APPS of completion
* of configuration. Block APPS shutdown until Co-proc notifies
* APPS of SPI configuration completion.
*/
do {
pmic_lpm_read(gchip, SDAM_HANDSHAKE_REG, &data, 1);
pr_debug("Value of SDAM_HANDSHAKE_REG(SDAM48) : %d\n",
data);
if (data & TWM_HANDSHAKE_COMPLETION_DATA) {
pr_info("PMIC TWM enabled\n");
return;
}
/*
* Shutdown callback is called from atomic context,
* mdelay is used to add a delay for APPS shutdown
* until Co-proc notifies APPS of TWM entry.
*/
mdelay(10);
} while (count--);
}
}
#ifdef CONFIG_DEEPSLEEP
static int qti_pmic_lpm_suspend_late(struct device *dev)
{
int rc = 0;
struct qti_pmic_lpm *chip = dev_get_drvdata(dev);
/* mem_sleep_current = PM_SUSPEND_MEM in DeepSleep */
if (pm_suspend_via_firmware()) {
rc = qti_pmic_handle_lpm(chip, true);
if (rc < 0)
dev_err(dev, "Failed to handle suspend_late(), rc:%d\n",
rc);
}
return rc;
}
static int qti_pmic_lpm_resume_early(struct device *dev)
{
int rc = 0;
struct qti_pmic_lpm *chip = dev_get_drvdata(dev);
/* mem_sleep_current = PM_SUSPEND_MEM in DeepSleep */
if (pm_suspend_via_firmware()) {
rc = qti_pmic_handle_lpm(chip, false);
if (rc < 0) {
dev_err(dev, "Failed to handle resume_early(), rc:%d\n",
rc);
return rc;
}
rc = pmic_get_wakeup_status(chip);
if (rc < 0)
dev_err(dev, "Failed to get deepsleep exit status, rc:%d\n", rc);
}
return rc;
}
#endif
static const struct dev_pm_ops qti_pmic_lpm_pm_ops = {
#ifdef CONFIG_DEEPSLEEP
.suspend_late = qti_pmic_lpm_suspend_late,
.resume_early = qti_pmic_lpm_resume_early,
#endif
};
static const struct of_device_id qti_pmic_lpm_match_table[] = {
{ .compatible = QTI_PMIC_LPM_DEV_NAME },
{}
};
static struct platform_driver qti_pmic_lpm_driver = {
.probe = qti_pmic_lpm_probe,
.remove = qti_pmic_lpm_remove,
.driver = {
.name = "qti,pmic-lpm",
.of_match_table = qti_pmic_lpm_match_table,
.pm = pm_ptr(&qti_pmic_lpm_pm_ops),
},
};
module_platform_driver(qti_pmic_lpm_driver);
MODULE_DESCRIPTION("QTI PMIC LPM driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" QTI_PMIC_LPM_DEV_NAME);