Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
# SPDX-License-Identifier: GPL-2.0
#
# Samsung SoC drivers
#
menuconfig SOC_SAMSUNG
bool "Samsung SoC driver support" if COMPILE_TEST
if SOC_SAMSUNG
# There is no need to enable these drivers for ARMv8
config EXYNOS_ASV_ARM
bool "Exynos ASV ARMv7-specific driver extensions" if COMPILE_TEST
depends on EXYNOS_CHIPID
config EXYNOS_CHIPID
tristate "Exynos ChipID controller and ASV driver"
depends on ARCH_EXYNOS || COMPILE_TEST
default ARCH_EXYNOS
select EXYNOS_ASV_ARM if ARM && ARCH_EXYNOS
select MFD_SYSCON
select SOC_BUS
help
Support for Samsung Exynos SoC ChipID and Adaptive Supply Voltage.
This driver can also be built as module (exynos_chipid).
config EXYNOS_USI
tristate "Exynos USI (Universal Serial Interface) driver"
default ARCH_EXYNOS && ARM64
depends on ARCH_EXYNOS || COMPILE_TEST
select MFD_SYSCON
help
Enable support for USI block. USI (Universal Serial Interface) is an
IP-core found in modern Samsung Exynos SoCs, like Exynos850 and
ExynosAutoV9. USI block can be configured to provide one of the
following serial protocols: UART, SPI or High Speed I2C.
This driver allows one to configure USI for desired protocol, which
is usually done in USI node in Device Tree.
config EXYNOS_PMU
bool "Exynos PMU controller driver" if COMPILE_TEST
depends on ARCH_EXYNOS || ((ARM || ARM64) && COMPILE_TEST)
select EXYNOS_PMU_ARM_DRIVERS if ARM && ARCH_EXYNOS
select MFD_CORE
select REGMAP_MMIO
# There is no need to enable these drivers for ARMv8
config EXYNOS_PMU_ARM_DRIVERS
bool "Exynos PMU ARMv7-specific driver extensions" if COMPILE_TEST
depends on EXYNOS_PMU
config SAMSUNG_PM_CHECK
bool "S3C2410 PM Suspend Memory CRC"
depends on PM && (ARCH_S3C64XX || ARCH_S5PV210)
select CRC32
help
Enable the PM code's memory area checksum over sleep. This option
will generate CRCs of all blocks of memory, and store them before
going to sleep. The blocks are then checked on resume for any
errors.
Note, this can take several seconds depending on memory size
and CPU speed.
config SAMSUNG_PM_CHECK_CHUNKSIZE
int "S3C2410 PM Suspend CRC Chunksize (KiB)"
depends on PM && SAMSUNG_PM_CHECK
default 64
help
Set the chunksize in Kilobytes of the CRC for checking memory
corruption over suspend and resume. A smaller value will mean that
the CRC data block will take more memory, but will identify any
faults with better precision.
config EXYNOS_REGULATOR_COUPLER
bool "Exynos SoC Regulator Coupler" if COMPILE_TEST
depends on ARCH_EXYNOS || COMPILE_TEST
endif
+16
View File
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_EXYNOS_ASV_ARM) += exynos5422-asv.o
obj-$(CONFIG_EXYNOS_CHIPID) += exynos_chipid.o
exynos_chipid-y += exynos-chipid.o exynos-asv.o
obj-$(CONFIG_EXYNOS_USI) += exynos-usi.o
obj-$(CONFIG_EXYNOS_PMU) += exynos_pmu.o
exynos_pmu-y += exynos-pmu.o gs101-pmu.o
obj-$(CONFIG_EXYNOS_PMU_ARM_DRIVERS) += exynos3250-pmu.o exynos4-pmu.o \
exynos5250-pmu.o exynos5420-pmu.o
obj-$(CONFIG_EXYNOS_REGULATOR_COUPLER) += exynos-regulator-coupler.o
obj-$(CONFIG_SAMSUNG_PM_CHECK) += s3c-pm-check.o
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
* Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org>
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
* Author: Krzysztof Kozlowski <krzk@kernel.org>
*
* Samsung Exynos SoC Adaptive Supply Voltage support
*/
#include <linux/array_size.h>
#include <linux/cpu.h>
#include <linux/device.h>
#include <linux/energy_model.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/pm_opp.h>
#include <linux/regmap.h>
#include <linux/soc/samsung/exynos-chipid.h>
#include "exynos-asv.h"
#include "exynos5422-asv.h"
#define MHZ 1000000U
static int exynos_asv_update_cpu_opps(struct exynos_asv *asv,
struct device *cpu)
{
struct exynos_asv_subsys *subsys = NULL;
struct dev_pm_opp *opp;
unsigned int opp_freq;
int i;
for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) {
if (of_device_is_compatible(cpu->of_node,
asv->subsys[i].cpu_dt_compat)) {
subsys = &asv->subsys[i];
break;
}
}
if (!subsys)
return -EINVAL;
for (i = 0; i < subsys->table.num_rows; i++) {
unsigned int new_volt, volt;
int ret;
opp_freq = exynos_asv_opp_get_frequency(subsys, i);
opp = dev_pm_opp_find_freq_exact(cpu, opp_freq * MHZ, true);
if (IS_ERR(opp)) {
dev_info(asv->dev, "cpu%d opp%d, freq: %u missing\n",
cpu->id, i, opp_freq);
continue;
}
volt = dev_pm_opp_get_voltage(opp);
new_volt = asv->opp_get_voltage(subsys, i, volt);
dev_pm_opp_put(opp);
if (new_volt == volt)
continue;
ret = dev_pm_opp_adjust_voltage(cpu, opp_freq * MHZ,
new_volt, new_volt, new_volt);
if (ret < 0)
dev_err(asv->dev,
"Failed to adjust OPP %u Hz/%u uV for cpu%d\n",
opp_freq, new_volt, cpu->id);
else
dev_dbg(asv->dev,
"Adjusted OPP %u Hz/%u -> %u uV, cpu%d\n",
opp_freq, volt, new_volt, cpu->id);
}
return 0;
}
static int exynos_asv_update_opps(struct exynos_asv *asv)
{
struct opp_table *last_opp_table = NULL;
struct device *cpu;
int ret, cpuid;
for_each_possible_cpu(cpuid) {
struct opp_table *opp_table;
cpu = get_cpu_device(cpuid);
if (!cpu)
continue;
opp_table = dev_pm_opp_get_opp_table(cpu);
if (IS_ERR(opp_table))
continue;
if (!last_opp_table || opp_table != last_opp_table) {
last_opp_table = opp_table;
ret = exynos_asv_update_cpu_opps(asv, cpu);
if (!ret) {
/*
* Update EM power values since OPP
* voltage values may have changed.
*/
em_dev_update_chip_binning(cpu);
} else {
dev_err(asv->dev, "Couldn't udate OPPs for cpu%d\n",
cpuid);
}
}
dev_pm_opp_put_opp_table(opp_table);
}
return 0;
}
int exynos_asv_init(struct device *dev, struct regmap *regmap)
{
int (*probe_func)(struct exynos_asv *asv);
struct exynos_asv *asv;
struct device *cpu_dev;
u32 product_id = 0;
int ret, i;
asv = devm_kzalloc(dev, sizeof(*asv), GFP_KERNEL);
if (!asv)
return -ENOMEM;
asv->chipid_regmap = regmap;
asv->dev = dev;
ret = regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PRO_ID,
&product_id);
if (ret < 0) {
dev_err(dev, "Cannot read revision from ChipID: %d\n", ret);
return -ENODEV;
}
switch (product_id & EXYNOS_MASK) {
case 0xE5422000:
probe_func = exynos5422_asv_init;
break;
default:
dev_dbg(dev, "No ASV support for this SoC\n");
devm_kfree(dev, asv);
return 0;
}
cpu_dev = get_cpu_device(0);
ret = dev_pm_opp_get_opp_count(cpu_dev);
if (ret < 0)
return -EPROBE_DEFER;
ret = of_property_read_u32(dev->of_node, "samsung,asv-bin",
&asv->of_bin);
if (ret < 0)
asv->of_bin = -EINVAL;
for (i = 0; i < ARRAY_SIZE(asv->subsys); i++)
asv->subsys[i].asv = asv;
ret = probe_func(asv);
if (ret < 0)
return ret;
return exynos_asv_update_opps(asv);
}
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
* Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
*
* Samsung Exynos SoC Adaptive Supply Voltage support
*/
#ifndef __LINUX_SOC_EXYNOS_ASV_H
#define __LINUX_SOC_EXYNOS_ASV_H
struct regmap;
/* HPM, IDS values to select target group */
struct asv_limit_entry {
unsigned int hpm;
unsigned int ids;
};
struct exynos_asv_table {
unsigned int num_rows;
unsigned int num_cols;
u32 *buf;
};
struct exynos_asv_subsys {
struct exynos_asv *asv;
const char *cpu_dt_compat;
int id;
struct exynos_asv_table table;
unsigned int base_volt;
unsigned int offset_volt_h;
unsigned int offset_volt_l;
};
struct exynos_asv {
struct device *dev;
struct regmap *chipid_regmap;
struct exynos_asv_subsys subsys[2];
int (*opp_get_voltage)(const struct exynos_asv_subsys *subs,
int level, unsigned int voltage);
unsigned int group;
unsigned int table;
/* True if SG fields from PKG_ID register should be used */
bool use_sg;
/* ASV bin read from DT */
int of_bin;
};
static inline u32 __asv_get_table_entry(const struct exynos_asv_table *table,
unsigned int row, unsigned int col)
{
return table->buf[row * (table->num_cols) + col];
}
static inline u32 exynos_asv_opp_get_voltage(const struct exynos_asv_subsys *subsys,
unsigned int level, unsigned int group)
{
return __asv_get_table_entry(&subsys->table, level, group + 1);
}
static inline u32 exynos_asv_opp_get_frequency(const struct exynos_asv_subsys *subsys,
unsigned int level)
{
return __asv_get_table_entry(&subsys->table, level, 0);
}
int exynos_asv_init(struct device *dev, struct regmap *regmap);
#endif /* __LINUX_SOC_EXYNOS_ASV_H */
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
* Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org>
*
* Exynos - CHIP ID support
* Author: Pankaj Dubey <pankaj.dubey@samsung.com>
* Author: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
* Author: Krzysztof Kozlowski <krzk@kernel.org>
*
* Samsung Exynos SoC Adaptive Supply Voltage and Chip ID support
*/
#include <linux/array_size.h>
#include <linux/device.h>
#include <linux/device/devres.h>
#include <linux/err.h>
#include <linux/ioport.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/soc/samsung/exynos-chipid.h>
#include <linux/sys_soc.h>
#include "exynos-asv.h"
struct exynos_chipid_variant {
unsigned int main_rev_reg; /* main revision register offset */
unsigned int sub_rev_reg; /* sub revision register offset */
unsigned int main_rev_shift; /* main revision offset in rev_reg */
unsigned int sub_rev_shift; /* sub revision offset in rev_reg */
bool efuse;
};
struct exynos_chipid_info {
u32 product_id;
u32 revision;
};
static const struct exynos_soc_id {
const char *name;
unsigned int id;
} soc_ids[] = {
/* List ordered by SoC name */
/* Compatible with: samsung,exynos4210-chipid */
{ "EXYNOS3250", 0xE3472000 },
{ "EXYNOS4210", 0x43200000 }, /* EVT0 revision */
{ "EXYNOS4210", 0x43210000 },
{ "EXYNOS4212", 0x43220000 },
{ "EXYNOS4412", 0xE4412000 },
{ "EXYNOS5250", 0x43520000 },
{ "EXYNOS5260", 0xE5260000 },
{ "EXYNOS5410", 0xE5410000 },
{ "EXYNOS5420", 0xE5420000 },
{ "EXYNOS5433", 0xE5433000 },
{ "EXYNOS5440", 0xE5440000 },
{ "EXYNOS5800", 0xE5422000 },
{ "EXYNOS7420", 0xE7420000 },
{ "EXYNOS7870", 0xE7870000 },
{ "EXYNOS8890", 0xE8890000 },
/* Compatible with: samsung,exynos850-chipid */
{ "EXYNOS2200", 0xE9925000 },
{ "EXYNOS7885", 0xE7885000 },
{ "EXYNOS850", 0xE3830000 },
{ "EXYNOS8895", 0xE8895000 },
{ "EXYNOS9610", 0xE9610000 },
{ "EXYNOS9810", 0xE9810000 },
{ "EXYNOS990", 0xE9830000 },
{ "EXYNOSAUTOV9", 0xAAA80000 },
{ "EXYNOSAUTOV920", 0x0A920000 },
/* Compatible with: google,gs101-otp */
{ "GS101", 0x9845000 },
};
static const char *exynos_product_id_to_name(unsigned int product_id)
{
int i;
for (i = 0; i < ARRAY_SIZE(soc_ids); i++)
if (product_id == soc_ids[i].id)
return soc_ids[i].name;
return NULL;
}
static int exynos_chipid_get_chipid_info(struct device *dev,
struct regmap *regmap, const struct exynos_chipid_variant *data,
struct exynos_chipid_info *soc_info)
{
int ret;
unsigned int val, main_rev, sub_rev;
ret = regmap_read(regmap, EXYNOS_CHIPID_REG_PRO_ID, &val);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to read Product ID\n");
soc_info->product_id = val & EXYNOS_MASK;
if (data->sub_rev_reg == EXYNOS_CHIPID_REG_PRO_ID) {
/* exynos4210 case */
main_rev = (val >> data->main_rev_shift) & EXYNOS_REV_PART_MASK;
sub_rev = (val >> data->sub_rev_shift) & EXYNOS_REV_PART_MASK;
} else {
unsigned int val2;
ret = regmap_read(regmap, data->sub_rev_reg, &val2);
if (ret < 0)
return dev_err_probe(dev, ret,
"failed to read revision\n");
if (data->main_rev_reg == EXYNOS_CHIPID_REG_PRO_ID)
/* gs101 case */
main_rev = (val >> data->main_rev_shift) & EXYNOS_REV_PART_MASK;
else
/* exynos850 case */
main_rev = (val2 >> data->main_rev_shift) & EXYNOS_REV_PART_MASK;
sub_rev = (val2 >> data->sub_rev_shift) & EXYNOS_REV_PART_MASK;
}
soc_info->revision = (main_rev << EXYNOS_REV_PART_SHIFT) | sub_rev;
return 0;
}
static struct regmap *exynos_chipid_get_efuse_regmap(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(base))
return ERR_CAST(base);
const struct regmap_config reg_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.use_relaxed_mmio = true,
.max_register = (resource_size(res) - reg_config.reg_stride),
};
return devm_regmap_init_mmio_clk(&pdev->dev, "pclk", base, &reg_config);
}
static void exynos_chipid_unregister_soc(void *data)
{
soc_device_unregister(data);
}
static int exynos_chipid_probe(struct platform_device *pdev)
{
const struct exynos_chipid_variant *drv_data;
struct exynos_chipid_info soc_info;
struct soc_device_attribute *soc_dev_attr;
struct device *dev = &pdev->dev;
struct soc_device *soc_dev;
struct device_node *root;
struct regmap *regmap;
int ret;
drv_data = of_device_get_match_data(dev);
if (!drv_data)
return dev_err_probe(dev, -EINVAL,
"failed to get match data\n");
if (drv_data->efuse)
regmap = exynos_chipid_get_efuse_regmap(pdev);
else
regmap = device_node_to_regmap(dev->of_node);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap),
"failed to get regmap\n");
ret = exynos_chipid_get_chipid_info(dev, regmap, drv_data, &soc_info);
if (ret < 0)
return ret;
soc_dev_attr = devm_kzalloc(dev, sizeof(*soc_dev_attr), GFP_KERNEL);
if (!soc_dev_attr)
return -ENOMEM;
soc_dev_attr->family = "Samsung Exynos";
root = of_find_node_by_path("/");
of_property_read_string(root, "model", &soc_dev_attr->machine);
of_node_put(root);
soc_dev_attr->revision = devm_kasprintf(dev, GFP_KERNEL, "%x",
soc_info.revision);
if (!soc_dev_attr->revision)
return -ENOMEM;
soc_dev_attr->soc_id = exynos_product_id_to_name(soc_info.product_id);
if (!soc_dev_attr->soc_id)
return dev_err_probe(dev, -ENODEV, "Unknown SoC\n");
/* please note that the actual registration will be deferred */
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev))
return dev_err_probe(dev, PTR_ERR(soc_dev),
"failed to register to the soc interface\n");
ret = devm_add_action_or_reset(dev, exynos_chipid_unregister_soc,
soc_dev);
if (ret)
return dev_err_probe(dev, ret, "failed to add devm action\n");
ret = exynos_asv_init(dev, regmap);
if (ret)
return ret;
dev_dbg(dev, "Exynos: CPU[%s] PRO_ID[0x%x] REV[0x%x] Detected\n",
soc_dev_attr->soc_id, soc_info.product_id, soc_info.revision);
return 0;
}
static const struct exynos_chipid_variant exynos4210_chipid_drv_data = {
.main_rev_shift = 4,
.sub_rev_shift = 0,
};
static const struct exynos_chipid_variant exynos850_chipid_drv_data = {
.main_rev_reg = 0x10,
.sub_rev_reg = 0x10,
.main_rev_shift = 20,
.sub_rev_shift = 16,
};
static const struct exynos_chipid_variant gs101_chipid_drv_data = {
.sub_rev_reg = 0x10,
.sub_rev_shift = 16,
.efuse = true,
};
static const struct of_device_id exynos_chipid_of_device_ids[] = {
{
.compatible = "google,gs101-otp",
.data = &gs101_chipid_drv_data,
}, {
.compatible = "samsung,exynos4210-chipid",
.data = &exynos4210_chipid_drv_data,
}, {
.compatible = "samsung,exynos850-chipid",
.data = &exynos850_chipid_drv_data,
},
{ }
};
MODULE_DEVICE_TABLE(of, exynos_chipid_of_device_ids);
static struct platform_driver exynos_chipid_driver = {
.driver = {
.name = "exynos-chipid",
.of_match_table = exynos_chipid_of_device_ids,
},
.probe = exynos_chipid_probe,
};
module_platform_driver(exynos_chipid_driver);
MODULE_DESCRIPTION("Samsung Exynos ChipID controller and ASV driver");
MODULE_AUTHOR("Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>");
MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");
MODULE_AUTHOR("Pankaj Dubey <pankaj.dubey@samsung.com>");
MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,592 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2011-2014 Samsung Electronics Co., Ltd.
// http://www.samsung.com/
//
// Exynos - CPU PMU(Power Management Unit) support
#include <linux/array_size.h>
#include <linux/bitmap.h>
#include <linux/cpuhotplug.h>
#include <linux/cpu_pm.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/mfd/core.h>
#include <linux/mfd/syscon.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/soc/samsung/exynos-regs-pmu.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include "exynos-pmu.h"
struct exynos_pmu_context {
struct device *dev;
const struct exynos_pmu_data *pmu_data;
struct regmap *pmureg;
struct regmap *pmuintrgen;
/*
* Serialization lock for CPU hot plug and cpuidle ACPM hint
* programming. Also protects in_cpuhp, sys_insuspend & sys_inreboot
* flags.
*/
raw_spinlock_t cpupm_lock;
unsigned long *in_cpuhp;
bool sys_insuspend;
bool sys_inreboot;
};
void __iomem *pmu_base_addr;
static struct exynos_pmu_context *pmu_context;
/* forward declaration */
static struct platform_driver exynos_pmu_driver;
void pmu_raw_writel(u32 val, u32 offset)
{
writel_relaxed(val, pmu_base_addr + offset);
}
u32 pmu_raw_readl(u32 offset)
{
return readl_relaxed(pmu_base_addr + offset);
}
void exynos_sys_powerdown_conf(enum sys_powerdown mode)
{
unsigned int i;
const struct exynos_pmu_data *pmu_data;
if (!pmu_context || !pmu_context->pmu_data)
return;
pmu_data = pmu_context->pmu_data;
if (pmu_data->powerdown_conf)
pmu_data->powerdown_conf(mode);
if (pmu_data->pmu_config) {
for (i = 0; (pmu_data->pmu_config[i].offset != PMU_TABLE_END); i++)
pmu_raw_writel(pmu_data->pmu_config[i].val[mode],
pmu_data->pmu_config[i].offset);
}
if (pmu_data->powerdown_conf_extra)
pmu_data->powerdown_conf_extra(mode);
if (pmu_data->pmu_config_extra) {
for (i = 0; pmu_data->pmu_config_extra[i].offset != PMU_TABLE_END; i++)
pmu_raw_writel(pmu_data->pmu_config_extra[i].val[mode],
pmu_data->pmu_config_extra[i].offset);
}
}
/*
* Split the data between ARM architectures because it is relatively big
* and useless on other arch.
*/
#ifdef CONFIG_EXYNOS_PMU_ARM_DRIVERS
#define exynos_pmu_data_arm_ptr(data) (&data)
#else
#define exynos_pmu_data_arm_ptr(data) NULL
#endif
static const struct regmap_config regmap_smccfg = {
.name = "pmu_regs",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.use_single_read = true,
.use_single_write = true,
.reg_read = tensor_sec_reg_read,
.reg_write = tensor_sec_reg_write,
.reg_update_bits = tensor_sec_update_bits,
.use_raw_spinlock = true,
};
static const struct regmap_config regmap_pmu_intr = {
.name = "pmu_intr_gen",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.use_raw_spinlock = true,
};
/*
* PMU platform driver and devicetree bindings.
*/
static const struct of_device_id exynos_pmu_of_device_ids[] = {
{
.compatible = "google,gs101-pmu",
.data = &gs101_pmu_data,
}, {
.compatible = "samsung,exynos3250-pmu",
.data = exynos_pmu_data_arm_ptr(exynos3250_pmu_data),
}, {
.compatible = "samsung,exynos4210-pmu",
.data = exynos_pmu_data_arm_ptr(exynos4210_pmu_data),
}, {
.compatible = "samsung,exynos4212-pmu",
.data = exynos_pmu_data_arm_ptr(exynos4212_pmu_data),
}, {
.compatible = "samsung,exynos4412-pmu",
.data = exynos_pmu_data_arm_ptr(exynos4412_pmu_data),
}, {
.compatible = "samsung,exynos5250-pmu",
.data = exynos_pmu_data_arm_ptr(exynos5250_pmu_data),
}, {
.compatible = "samsung,exynos5410-pmu",
}, {
.compatible = "samsung,exynos5420-pmu",
.data = exynos_pmu_data_arm_ptr(exynos5420_pmu_data),
}, {
.compatible = "samsung,exynos5433-pmu",
}, {
.compatible = "samsung,exynos7-pmu",
}, {
.compatible = "samsung,exynos850-pmu",
},
{ /*sentinel*/ },
};
static const struct mfd_cell exynos_pmu_devs[] = {
{ .name = "exynos-clkout", },
};
/**
* exynos_get_pmu_regmap() - Obtain pmureg regmap
*
* Find the pmureg regmap previously configured in probe() and return regmap
* pointer.
*
* Return: A pointer to regmap if found or ERR_PTR error value.
*/
struct regmap *exynos_get_pmu_regmap(void)
{
struct device_node *np = of_find_matching_node(NULL,
exynos_pmu_of_device_ids);
if (np)
return exynos_get_pmu_regmap_by_phandle(np, NULL);
return ERR_PTR(-ENODEV);
}
EXPORT_SYMBOL_GPL(exynos_get_pmu_regmap);
/**
* exynos_get_pmu_regmap_by_phandle() - Obtain pmureg regmap via phandle
* @np: Device node holding PMU phandle property
* @propname: Name of property holding phandle value
*
* Find the pmureg regmap previously configured in probe() and return regmap
* pointer.
*
* Return: A pointer to regmap if found or ERR_PTR error value.
*/
struct regmap *exynos_get_pmu_regmap_by_phandle(struct device_node *np,
const char *propname)
{
struct device_node *pmu_np;
struct device *dev;
if (propname)
pmu_np = of_parse_phandle(np, propname, 0);
else
pmu_np = np;
if (!pmu_np)
return ERR_PTR(-ENODEV);
/*
* Determine if exynos-pmu device has probed and therefore regmap
* has been created and can be returned to the caller. Otherwise we
* return -EPROBE_DEFER.
*/
dev = driver_find_device_by_of_node(&exynos_pmu_driver.driver,
(void *)pmu_np);
if (propname)
of_node_put(pmu_np);
if (!dev)
return ERR_PTR(-EPROBE_DEFER);
put_device(dev);
return syscon_node_to_regmap(pmu_np);
}
EXPORT_SYMBOL_GPL(exynos_get_pmu_regmap_by_phandle);
/*
* CPU_INFORM register "hint" values are required to be programmed in addition to
* the standard PSCI calls to have functional CPU hotplug and CPU idle states.
* This is required to workaround limitations in the el3mon/ACPM firmware.
*/
#define CPU_INFORM_CLEAR 0
#define CPU_INFORM_C2 1
/*
* __gs101_cpu_pmu_ prefix functions are common code shared by CPU PM notifiers
* (CPUIdle) and CPU hotplug callbacks. Functions should be called with IRQs
* disabled and cpupm_lock held.
*/
static int __gs101_cpu_pmu_online(unsigned int cpu)
__must_hold(&pmu_context->cpupm_lock)
{
unsigned int cpuhint = smp_processor_id();
u32 reg, mask;
/* clear cpu inform hint */
regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint),
CPU_INFORM_CLEAR);
mask = BIT(cpu);
regmap_update_bits(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_ENABLE,
mask, (0 << cpu));
regmap_read(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_UPEND, &reg);
regmap_write(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_CLEAR,
reg & mask);
return 0;
}
/* Called from CPU PM notifier (CPUIdle code path) with IRQs disabled */
static int gs101_cpu_pmu_online(void)
{
int cpu;
raw_spin_lock(&pmu_context->cpupm_lock);
if (pmu_context->sys_inreboot) {
raw_spin_unlock(&pmu_context->cpupm_lock);
return NOTIFY_OK;
}
cpu = smp_processor_id();
__gs101_cpu_pmu_online(cpu);
raw_spin_unlock(&pmu_context->cpupm_lock);
return NOTIFY_OK;
}
/* Called from CPU hot plug callback with IRQs enabled */
static int gs101_cpuhp_pmu_online(unsigned int cpu)
{
unsigned long flags;
raw_spin_lock_irqsave(&pmu_context->cpupm_lock, flags);
__gs101_cpu_pmu_online(cpu);
/*
* Mark this CPU as having finished the hotplug.
* This means this CPU can now enter C2 idle state.
*/
clear_bit(cpu, pmu_context->in_cpuhp);
raw_spin_unlock_irqrestore(&pmu_context->cpupm_lock, flags);
return 0;
}
/* Common function shared by both CPU hot plug and CPUIdle */
static int __gs101_cpu_pmu_offline(unsigned int cpu)
__must_hold(&pmu_context->cpupm_lock)
{
unsigned int cpuhint = smp_processor_id();
u32 reg, mask;
/* set cpu inform hint */
regmap_write(pmu_context->pmureg, GS101_CPU_INFORM(cpuhint),
CPU_INFORM_C2);
mask = BIT(cpu);
regmap_update_bits(pmu_context->pmuintrgen, GS101_GRP2_INTR_BID_ENABLE,
mask, BIT(cpu));
regmap_read(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_UPEND, &reg);
regmap_write(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_CLEAR,
reg & mask);
mask = (BIT(cpu + 8));
regmap_read(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_UPEND, &reg);
regmap_write(pmu_context->pmuintrgen, GS101_GRP1_INTR_BID_CLEAR,
reg & mask);
return 0;
}
/* Called from CPU PM notifier (CPUIdle code path) with IRQs disabled */
static int gs101_cpu_pmu_offline(void)
{
int cpu;
raw_spin_lock(&pmu_context->cpupm_lock);
cpu = smp_processor_id();
if (test_bit(cpu, pmu_context->in_cpuhp)) {
raw_spin_unlock(&pmu_context->cpupm_lock);
return NOTIFY_BAD;
}
/* Ignore CPU_PM_ENTER event in reboot or suspend sequence. */
if (pmu_context->sys_insuspend || pmu_context->sys_inreboot) {
raw_spin_unlock(&pmu_context->cpupm_lock);
return NOTIFY_OK;
}
__gs101_cpu_pmu_offline(cpu);
raw_spin_unlock(&pmu_context->cpupm_lock);
return NOTIFY_OK;
}
/* Called from CPU hot plug callback with IRQs enabled */
static int gs101_cpuhp_pmu_offline(unsigned int cpu)
{
unsigned long flags;
raw_spin_lock_irqsave(&pmu_context->cpupm_lock, flags);
/*
* Mark this CPU as entering hotplug. So as not to confuse
* ACPM the CPU entering hotplug should not enter C2 idle state.
*/
set_bit(cpu, pmu_context->in_cpuhp);
__gs101_cpu_pmu_offline(cpu);
raw_spin_unlock_irqrestore(&pmu_context->cpupm_lock, flags);
return 0;
}
static int gs101_cpu_pm_notify_callback(struct notifier_block *self,
unsigned long action, void *v)
{
switch (action) {
case CPU_PM_ENTER:
return gs101_cpu_pmu_offline();
case CPU_PM_EXIT:
return gs101_cpu_pmu_online();
}
return NOTIFY_OK;
}
static struct notifier_block gs101_cpu_pm_notifier = {
.notifier_call = gs101_cpu_pm_notify_callback,
/*
* We want to be called first, as the ACPM hint and handshake is what
* puts the CPU into C2.
*/
.priority = INT_MAX
};
static int exynos_cpupm_reboot_notifier(struct notifier_block *nb,
unsigned long event, void *v)
{
unsigned long flags;
switch (event) {
case SYS_POWER_OFF:
case SYS_RESTART:
raw_spin_lock_irqsave(&pmu_context->cpupm_lock, flags);
pmu_context->sys_inreboot = true;
raw_spin_unlock_irqrestore(&pmu_context->cpupm_lock, flags);
break;
}
return NOTIFY_OK;
}
static struct notifier_block exynos_cpupm_reboot_nb = {
.priority = INT_MAX,
.notifier_call = exynos_cpupm_reboot_notifier,
};
static int setup_cpuhp_and_cpuidle(struct device *dev)
{
struct device_node *intr_gen_node;
struct resource intrgen_res;
void __iomem *virt_addr;
int ret, cpu;
intr_gen_node = of_parse_phandle(dev->of_node,
"google,pmu-intr-gen-syscon", 0);
if (!intr_gen_node) {
/*
* To maintain support for older DTs that didn't specify syscon
* phandle just issue a warning rather than fail to probe.
*/
dev_warn(dev, "pmu-intr-gen syscon unavailable\n");
return 0;
}
/*
* To avoid lockdep issues (CPU PM notifiers use raw spinlocks) create
* a mmio regmap for pmu-intr-gen that uses raw spinlocks instead of
* syscon provided regmap.
*/
ret = of_address_to_resource(intr_gen_node, 0, &intrgen_res);
of_node_put(intr_gen_node);
virt_addr = devm_ioremap(dev, intrgen_res.start,
resource_size(&intrgen_res));
if (!virt_addr)
return -ENOMEM;
pmu_context->pmuintrgen = devm_regmap_init_mmio(dev, virt_addr,
&regmap_pmu_intr);
if (IS_ERR(pmu_context->pmuintrgen)) {
dev_err(dev, "failed to initialize pmu-intr-gen regmap\n");
return PTR_ERR(pmu_context->pmuintrgen);
}
/* register custom mmio regmap with syscon */
ret = of_syscon_register_regmap(intr_gen_node,
pmu_context->pmuintrgen);
if (ret)
return ret;
pmu_context->in_cpuhp = devm_bitmap_zalloc(dev, num_possible_cpus(),
GFP_KERNEL);
if (!pmu_context->in_cpuhp)
return -ENOMEM;
/* set PMU to power on */
for_each_online_cpu(cpu)
gs101_cpuhp_pmu_online(cpu);
/* register CPU hotplug callbacks */
cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "soc/exynos-pmu:prepare",
gs101_cpuhp_pmu_online, NULL);
cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/exynos-pmu:online",
NULL, gs101_cpuhp_pmu_offline);
/* register CPU PM notifiers for cpuidle */
cpu_pm_register_notifier(&gs101_cpu_pm_notifier);
register_reboot_notifier(&exynos_cpupm_reboot_nb);
return 0;
}
static int exynos_pmu_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct regmap_config pmu_regmcfg;
struct regmap *regmap;
struct resource *res;
int ret;
pmu_base_addr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(pmu_base_addr))
return PTR_ERR(pmu_base_addr);
pmu_context = devm_kzalloc(&pdev->dev,
sizeof(struct exynos_pmu_context),
GFP_KERNEL);
if (!pmu_context)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
pmu_context->pmu_data = of_device_get_match_data(dev);
/* For SoCs that secure PMU register writes use custom regmap */
if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_secure) {
pmu_regmcfg = regmap_smccfg;
pmu_regmcfg.max_register = resource_size(res) -
pmu_regmcfg.reg_stride;
pmu_regmcfg.wr_table = pmu_context->pmu_data->wr_table;
pmu_regmcfg.rd_table = pmu_context->pmu_data->rd_table;
/* Need physical address for SMC call */
regmap = devm_regmap_init(dev, NULL,
(void *)(uintptr_t)res->start,
&pmu_regmcfg);
if (IS_ERR(regmap))
return dev_err_probe(&pdev->dev, PTR_ERR(regmap),
"regmap init failed\n");
ret = of_syscon_register_regmap(dev->of_node, regmap);
if (ret)
return ret;
} else {
/* let syscon create mmio regmap */
regmap = syscon_node_to_regmap(dev->of_node);
if (IS_ERR(regmap))
return dev_err_probe(&pdev->dev, PTR_ERR(regmap),
"syscon_node_to_regmap failed\n");
}
pmu_context->pmureg = regmap;
pmu_context->dev = dev;
raw_spin_lock_init(&pmu_context->cpupm_lock);
pmu_context->sys_inreboot = false;
pmu_context->sys_insuspend = false;
if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_cpuhp) {
ret = setup_cpuhp_and_cpuidle(dev);
if (ret)
return ret;
}
if (pmu_context->pmu_data && pmu_context->pmu_data->pmu_init)
pmu_context->pmu_data->pmu_init();
platform_set_drvdata(pdev, pmu_context);
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, exynos_pmu_devs,
ARRAY_SIZE(exynos_pmu_devs), NULL, 0, NULL);
if (ret)
return ret;
if (devm_of_platform_populate(dev))
dev_err(dev, "Error populating children, reboot and poweroff might not work properly\n");
dev_dbg(dev, "Exynos PMU Driver probe done\n");
return 0;
}
static int exynos_cpupm_suspend_noirq(struct device *dev)
{
raw_spin_lock(&pmu_context->cpupm_lock);
pmu_context->sys_insuspend = true;
raw_spin_unlock(&pmu_context->cpupm_lock);
return 0;
}
static int exynos_cpupm_resume_noirq(struct device *dev)
{
raw_spin_lock(&pmu_context->cpupm_lock);
pmu_context->sys_insuspend = false;
raw_spin_unlock(&pmu_context->cpupm_lock);
return 0;
}
static const struct dev_pm_ops cpupm_pm_ops = {
NOIRQ_SYSTEM_SLEEP_PM_OPS(exynos_cpupm_suspend_noirq,
exynos_cpupm_resume_noirq)
};
static struct platform_driver exynos_pmu_driver = {
.driver = {
.name = "exynos-pmu",
.of_match_table = exynos_pmu_of_device_ids,
.pm = pm_sleep_ptr(&cpupm_pm_ops),
},
.probe = exynos_pmu_probe,
};
static int __init exynos_pmu_init(void)
{
return platform_driver_register(&exynos_pmu_driver);
}
postcore_initcall(exynos_pmu_init);
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Header for Exynos PMU Driver support
*/
#ifndef __EXYNOS_PMU_H
#define __EXYNOS_PMU_H
#include <linux/io.h>
#define PMU_TABLE_END (-1U)
struct regmap_access_table;
struct exynos_pmu_conf {
unsigned int offset;
u8 val[NUM_SYS_POWERDOWN];
};
/**
* struct exynos_pmu_data - of_device_id (match) data
*
* @pmu_config: Optional table detailing register writes for target system
* states: SYS_AFTR, SYS_LPA, SYS_SLEEP.
* @pmu_config_extra: Optional secondary table detailing additional register
* writes for target system states: SYS_AFTR, SYS_LPA,
* SYS_SLEEP.
* @pmu_secure: Whether or not PMU register writes need to be done via SMC call.
* @pmu_cpuhp: Whether or not extra handling is required for CPU hotplug and
* CPUidle outside of standard PSCI calls, due to non-compliant
* firmware.
* @pmu_init: Optional init function.
* @powerdown_conf: Optional callback before entering target system states:
* SYS_AFTR, SYS_LPA, SYS_SLEEP. This will be invoked before
* the registers from @pmu_config are written.
* @powerdown_conf_extra: Optional secondary callback before entering
* target system states: SYS_AFTR, SYS_LPA, SYS_SLEEP.
* This will be invoked after @pmu_config registers have
* been written.
* @rd_table: A table of readable register ranges in case a custom regmap is
* used (i.e. when @pmu_secure is @true).
* @wr_table: A table of writable register ranges in case a custom regmap is
* used (i.e. when @pmu_secure is @true).
*/
struct exynos_pmu_data {
const struct exynos_pmu_conf *pmu_config;
const struct exynos_pmu_conf *pmu_config_extra;
bool pmu_secure;
bool pmu_cpuhp;
void (*pmu_init)(void);
void (*powerdown_conf)(enum sys_powerdown);
void (*powerdown_conf_extra)(enum sys_powerdown);
const struct regmap_access_table *rd_table;
const struct regmap_access_table *wr_table;
};
extern void __iomem *pmu_base_addr;
#ifdef CONFIG_EXYNOS_PMU_ARM_DRIVERS
/* list of all exported SoC specific data */
extern const struct exynos_pmu_data exynos3250_pmu_data;
extern const struct exynos_pmu_data exynos4210_pmu_data;
extern const struct exynos_pmu_data exynos4212_pmu_data;
extern const struct exynos_pmu_data exynos4412_pmu_data;
extern const struct exynos_pmu_data exynos5250_pmu_data;
extern const struct exynos_pmu_data exynos5420_pmu_data;
#endif
extern const struct exynos_pmu_data gs101_pmu_data;
extern void pmu_raw_writel(u32 val, u32 offset);
extern u32 pmu_raw_readl(u32 offset);
int tensor_sec_reg_write(void *context, unsigned int reg, unsigned int val);
int tensor_sec_reg_read(void *context, unsigned int reg, unsigned int *val);
int tensor_sec_update_bits(void *context, unsigned int reg, unsigned int mask,
unsigned int val);
#endif /* __EXYNOS_PMU_H */
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
* Author: Marek Szyprowski <m.szyprowski@samsung.com>
*
* Simplified generic voltage coupler from regulator core.c
* The main difference is that it keeps current regulator voltage
* if consumers didn't apply their constraints yet.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
static int regulator_get_optimal_voltage(struct regulator_dev *rdev,
int *current_uV,
int *min_uV, int *max_uV,
suspend_state_t state)
{
struct coupling_desc *c_desc = &rdev->coupling_desc;
struct regulator_dev **c_rdevs = c_desc->coupled_rdevs;
struct regulation_constraints *constraints = rdev->constraints;
int desired_min_uV = 0, desired_max_uV = INT_MAX;
int max_current_uV = 0, min_current_uV = INT_MAX;
int highest_min_uV = 0, target_uV, possible_uV;
int i, ret, max_spread, n_coupled = c_desc->n_coupled;
bool done;
*current_uV = -1;
/* Find highest min desired voltage */
for (i = 0; i < n_coupled; i++) {
int tmp_min = 0;
int tmp_max = INT_MAX;
lockdep_assert_held_once(&c_rdevs[i]->mutex.base);
ret = regulator_check_consumers(c_rdevs[i],
&tmp_min,
&tmp_max, state);
if (ret < 0)
return ret;
if (tmp_min == 0) {
ret = regulator_get_voltage_rdev(c_rdevs[i]);
if (ret < 0)
return ret;
tmp_min = ret;
}
/* apply constraints */
ret = regulator_check_voltage(c_rdevs[i], &tmp_min, &tmp_max);
if (ret < 0)
return ret;
highest_min_uV = max(highest_min_uV, tmp_min);
if (i == 0) {
desired_min_uV = tmp_min;
desired_max_uV = tmp_max;
}
}
max_spread = constraints->max_spread[0];
/*
* Let target_uV be equal to the desired one if possible.
* If not, set it to minimum voltage, allowed by other coupled
* regulators.
*/
target_uV = max(desired_min_uV, highest_min_uV - max_spread);
/*
* Find min and max voltages, which currently aren't violating
* max_spread.
*/
for (i = 1; i < n_coupled; i++) {
int tmp_act;
tmp_act = regulator_get_voltage_rdev(c_rdevs[i]);
if (tmp_act < 0)
return tmp_act;
min_current_uV = min(tmp_act, min_current_uV);
max_current_uV = max(tmp_act, max_current_uV);
}
/*
* Correct target voltage, so as it currently isn't
* violating max_spread
*/
possible_uV = max(target_uV, max_current_uV - max_spread);
possible_uV = min(possible_uV, min_current_uV + max_spread);
if (possible_uV > desired_max_uV)
return -EINVAL;
done = (possible_uV == target_uV);
desired_min_uV = possible_uV;
/* Set current_uV if wasn't done earlier in the code and if necessary */
if (*current_uV == -1) {
ret = regulator_get_voltage_rdev(rdev);
if (ret < 0)
return ret;
*current_uV = ret;
}
*min_uV = desired_min_uV;
*max_uV = desired_max_uV;
return done;
}
static int exynos_coupler_balance_voltage(struct regulator_coupler *coupler,
struct regulator_dev *rdev,
suspend_state_t state)
{
struct regulator_dev **c_rdevs;
struct regulator_dev *best_rdev;
struct coupling_desc *c_desc = &rdev->coupling_desc;
int i, ret, n_coupled, best_min_uV, best_max_uV, best_c_rdev;
unsigned int delta, best_delta;
unsigned long c_rdev_done = 0;
bool best_c_rdev_done;
c_rdevs = c_desc->coupled_rdevs;
n_coupled = c_desc->n_coupled;
/*
* Find the best possible voltage change on each loop. Leave the loop
* if there isn't any possible change.
*/
do {
best_c_rdev_done = false;
best_delta = 0;
best_min_uV = 0;
best_max_uV = 0;
best_c_rdev = 0;
best_rdev = NULL;
/*
* Find highest difference between optimal voltage
* and current voltage.
*/
for (i = 0; i < n_coupled; i++) {
/*
* optimal_uV is the best voltage that can be set for
* i-th regulator at the moment without violating
* max_spread constraint in order to balance
* the coupled voltages.
*/
int optimal_uV = 0, optimal_max_uV = 0, current_uV = 0;
if (test_bit(i, &c_rdev_done))
continue;
ret = regulator_get_optimal_voltage(c_rdevs[i],
&current_uV,
&optimal_uV,
&optimal_max_uV,
state);
if (ret < 0)
goto out;
delta = abs(optimal_uV - current_uV);
if (delta && best_delta <= delta) {
best_c_rdev_done = ret;
best_delta = delta;
best_rdev = c_rdevs[i];
best_min_uV = optimal_uV;
best_max_uV = optimal_max_uV;
best_c_rdev = i;
}
}
/* Nothing to change, return successfully */
if (!best_rdev) {
ret = 0;
goto out;
}
ret = regulator_set_voltage_rdev(best_rdev, best_min_uV,
best_max_uV, state);
if (ret < 0)
goto out;
if (best_c_rdev_done)
set_bit(best_c_rdev, &c_rdev_done);
} while (n_coupled > 1);
out:
return ret;
}
static int exynos_coupler_attach(struct regulator_coupler *coupler,
struct regulator_dev *rdev)
{
return 0;
}
static struct regulator_coupler exynos_coupler = {
.attach_regulator = exynos_coupler_attach,
.balance_voltage = exynos_coupler_balance_voltage,
};
static int __init exynos_coupler_init(void)
{
if (!of_machine_is_compatible("samsung,exynos5800"))
return 0;
return regulator_coupler_register(&exynos_coupler);
}
arch_initcall(exynos_coupler_init);
@@ -0,0 +1,355 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 Linaro Ltd.
* Author: Sam Protsenko <semen.protsenko@linaro.org>
*
* Samsung Exynos USI driver (Universal Serial Interface).
*/
#include <linux/array_size.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <dt-bindings/soc/samsung,exynos-usi.h>
/* USIv1: System Register: SW_CONF register bits */
#define USI_V1_SW_CONF_NONE 0x0
#define USI_V1_SW_CONF_I2C0 0x1
#define USI_V1_SW_CONF_I2C1 0x2
#define USI_V1_SW_CONF_I2C0_1 0x3
#define USI_V1_SW_CONF_SPI 0x4
#define USI_V1_SW_CONF_UART 0x8
#define USI_V1_SW_CONF_UART_I2C1 0xa
#define USI_V1_SW_CONF_MASK (USI_V1_SW_CONF_I2C0 | USI_V1_SW_CONF_I2C1 | \
USI_V1_SW_CONF_I2C0_1 | USI_V1_SW_CONF_SPI | \
USI_V1_SW_CONF_UART | USI_V1_SW_CONF_UART_I2C1)
/* USIv2: System Register: SW_CONF register bits */
#define USI_V2_SW_CONF_NONE 0x0
#define USI_V2_SW_CONF_UART BIT(0)
#define USI_V2_SW_CONF_SPI BIT(1)
#define USI_V2_SW_CONF_I2C BIT(2)
#define USI_V2_SW_CONF_MASK (USI_V2_SW_CONF_UART | USI_V2_SW_CONF_SPI | \
USI_V2_SW_CONF_I2C)
/* USIv2: USI register offsets */
#define USI_CON 0x04
#define USI_OPTION 0x08
/* USIv2: USI register bits */
#define USI_CON_RESET BIT(0)
#define USI_OPTION_CLKREQ_ON BIT(1)
#define USI_OPTION_CLKSTOP_ON BIT(2)
enum exynos_usi_ver {
USI_VER1 = 0,
USI_VER2,
};
struct exynos_usi_variant {
enum exynos_usi_ver ver; /* USI IP-core version */
unsigned int sw_conf_mask; /* SW_CONF mask for all protocols */
size_t min_mode; /* first index in exynos_usi_modes[] */
size_t max_mode; /* last index in exynos_usi_modes[] */
size_t num_clks; /* number of clocks to assert */
const char * const *clk_names; /* clock names to assert */
};
struct exynos_usi {
struct device *dev;
void __iomem *regs; /* USI register map */
struct clk_bulk_data *clks; /* USI clocks */
size_t mode; /* current USI SW_CONF mode index */
bool clkreq_on; /* always provide clock to IP */
/* System Register */
struct regmap *sysreg; /* System Register map */
unsigned int sw_conf; /* SW_CONF register offset in sysreg */
const struct exynos_usi_variant *data;
};
struct exynos_usi_mode {
const char *name; /* mode name */
unsigned int val; /* mode register value */
};
#define USI_MODES_MAX (USI_MODE_UART_I2C1 + 1)
static const struct exynos_usi_mode exynos_usi_modes[][USI_MODES_MAX] = {
[USI_VER1] = {
[USI_MODE_NONE] = { .name = "none", .val = USI_V1_SW_CONF_NONE },
[USI_MODE_UART] = { .name = "uart", .val = USI_V1_SW_CONF_UART },
[USI_MODE_SPI] = { .name = "spi", .val = USI_V1_SW_CONF_SPI },
[USI_MODE_I2C] = { .name = "i2c", .val = USI_V1_SW_CONF_I2C0 },
[USI_MODE_I2C1] = { .name = "i2c1", .val = USI_V1_SW_CONF_I2C1 },
[USI_MODE_I2C0_1] = { .name = "i2c0_1", .val = USI_V1_SW_CONF_I2C0_1 },
[USI_MODE_UART_I2C1] = { .name = "uart_i2c1", .val = USI_V1_SW_CONF_UART_I2C1 },
}, [USI_VER2] = {
[USI_MODE_NONE] = { .name = "none", .val = USI_V2_SW_CONF_NONE },
[USI_MODE_UART] = { .name = "uart", .val = USI_V2_SW_CONF_UART },
[USI_MODE_SPI] = { .name = "spi", .val = USI_V2_SW_CONF_SPI },
[USI_MODE_I2C] = { .name = "i2c", .val = USI_V2_SW_CONF_I2C },
},
};
static const char * const exynos850_usi_clk_names[] = { "pclk", "ipclk" };
static const struct exynos_usi_variant exynos850_usi_data = {
.ver = USI_VER2,
.sw_conf_mask = USI_V2_SW_CONF_MASK,
.min_mode = USI_MODE_NONE,
.max_mode = USI_MODE_I2C,
.num_clks = ARRAY_SIZE(exynos850_usi_clk_names),
.clk_names = exynos850_usi_clk_names,
};
static const struct exynos_usi_variant exynos8895_usi_data = {
.ver = USI_VER1,
.sw_conf_mask = USI_V1_SW_CONF_MASK,
.min_mode = USI_MODE_NONE,
.max_mode = USI_MODE_UART_I2C1,
.num_clks = ARRAY_SIZE(exynos850_usi_clk_names),
.clk_names = exynos850_usi_clk_names,
};
static const struct of_device_id exynos_usi_dt_match[] = {
{
.compatible = "samsung,exynos850-usi",
.data = &exynos850_usi_data,
}, {
.compatible = "samsung,exynos8895-usi",
.data = &exynos8895_usi_data,
},
{ } /* sentinel */
};
MODULE_DEVICE_TABLE(of, exynos_usi_dt_match);
/**
* exynos_usi_set_sw_conf - Set USI block configuration mode
* @usi: USI driver object
* @mode: Mode index
*
* Select underlying serial protocol (UART/SPI/I2C) in USI IP-core.
*
* Return: 0 on success, or negative error code on failure.
*/
static int exynos_usi_set_sw_conf(struct exynos_usi *usi, size_t mode)
{
unsigned int val;
int ret;
if (mode < usi->data->min_mode || mode > usi->data->max_mode)
return -EINVAL;
val = exynos_usi_modes[usi->data->ver][mode].val;
ret = regmap_update_bits(usi->sysreg, usi->sw_conf,
usi->data->sw_conf_mask, val);
if (ret)
return ret;
usi->mode = mode;
dev_dbg(usi->dev, "protocol: %s\n",
exynos_usi_modes[usi->data->ver][usi->mode].name);
return 0;
}
/**
* exynos_usi_enable - Initialize USI block
* @usi: USI driver object
*
* USI IP-core start state is "reset" (on startup and after CPU resume). This
* routine enables the USI block by clearing the reset flag. It also configures
* HWACG behavior (needed e.g. for UART Rx). It should be performed before
* underlying protocol becomes functional.
*
* Return: 0 on success, or negative error code on failure.
*/
static int exynos_usi_enable(const struct exynos_usi *usi)
{
u32 val;
int ret;
ret = clk_bulk_prepare_enable(usi->data->num_clks, usi->clks);
if (ret)
return ret;
/* Enable USI block */
val = readl(usi->regs + USI_CON);
val &= ~USI_CON_RESET;
writel(val, usi->regs + USI_CON);
udelay(1);
/* Continuously provide the clock to USI IP w/o gating */
if (usi->clkreq_on) {
val = readl(usi->regs + USI_OPTION);
val &= ~USI_OPTION_CLKSTOP_ON;
val |= USI_OPTION_CLKREQ_ON;
writel(val, usi->regs + USI_OPTION);
}
clk_bulk_disable_unprepare(usi->data->num_clks, usi->clks);
return ret;
}
static int exynos_usi_configure(struct exynos_usi *usi)
{
int ret;
ret = exynos_usi_set_sw_conf(usi, usi->mode);
if (ret)
return ret;
if (usi->data->ver == USI_VER1)
ret = clk_bulk_prepare_enable(usi->data->num_clks,
usi->clks);
else if (usi->data->ver == USI_VER2)
ret = exynos_usi_enable(usi);
return ret;
}
static void exynos_usi_unconfigure(void *data)
{
struct exynos_usi *usi = data;
u32 val;
int ret;
if (usi->data->ver == USI_VER1) {
clk_bulk_disable_unprepare(usi->data->num_clks, usi->clks);
return;
}
ret = clk_bulk_prepare_enable(usi->data->num_clks, usi->clks);
if (ret)
return;
/* Make sure that we've stopped providing the clock to USI IP */
val = readl(usi->regs + USI_OPTION);
val &= ~USI_OPTION_CLKREQ_ON;
val |= USI_OPTION_CLKSTOP_ON;
writel(val, usi->regs + USI_OPTION);
/* Set USI block state to reset */
val = readl(usi->regs + USI_CON);
val |= USI_CON_RESET;
writel(val, usi->regs + USI_CON);
clk_bulk_disable_unprepare(usi->data->num_clks, usi->clks);
}
static int exynos_usi_parse_dt(struct device_node *np, struct exynos_usi *usi)
{
int ret;
u32 mode;
ret = of_property_read_u32(np, "samsung,mode", &mode);
if (ret)
return ret;
if (mode < usi->data->min_mode || mode > usi->data->max_mode)
return -EINVAL;
usi->mode = mode;
usi->sysreg = syscon_regmap_lookup_by_phandle_args(np, "samsung,sysreg",
1, &usi->sw_conf);
if (IS_ERR(usi->sysreg))
return PTR_ERR(usi->sysreg);
usi->clkreq_on = of_property_read_bool(np, "samsung,clkreq-on");
return 0;
}
static int exynos_usi_get_clocks(struct exynos_usi *usi)
{
const size_t num = usi->data->num_clks;
struct device *dev = usi->dev;
size_t i;
if (num == 0)
return 0;
usi->clks = devm_kcalloc(dev, num, sizeof(*usi->clks), GFP_KERNEL);
if (!usi->clks)
return -ENOMEM;
for (i = 0; i < num; ++i)
usi->clks[i].id = usi->data->clk_names[i];
return devm_clk_bulk_get(dev, num, usi->clks);
}
static int exynos_usi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct exynos_usi *usi;
int ret;
usi = devm_kzalloc(dev, sizeof(*usi), GFP_KERNEL);
if (!usi)
return -ENOMEM;
usi->dev = dev;
platform_set_drvdata(pdev, usi);
usi->data = of_device_get_match_data(dev);
if (!usi->data)
return -EINVAL;
ret = exynos_usi_parse_dt(np, usi);
if (ret)
return ret;
ret = exynos_usi_get_clocks(usi);
if (ret)
return ret;
if (usi->data->ver == USI_VER2) {
usi->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(usi->regs))
return PTR_ERR(usi->regs);
}
ret = exynos_usi_configure(usi);
if (ret)
return ret;
ret = devm_add_action_or_reset(&pdev->dev, exynos_usi_unconfigure, usi);
if (ret)
return ret;
/* Make it possible to embed protocol nodes into USI np */
return of_platform_populate(np, NULL, NULL, dev);
}
static int __maybe_unused exynos_usi_resume_noirq(struct device *dev)
{
struct exynos_usi *usi = dev_get_drvdata(dev);
return exynos_usi_configure(usi);
}
static const struct dev_pm_ops exynos_usi_pm = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, exynos_usi_resume_noirq)
};
static struct platform_driver exynos_usi_driver = {
.driver = {
.name = "exynos-usi",
.pm = &exynos_usi_pm,
.of_match_table = exynos_usi_dt_match,
},
.probe = exynos_usi_probe,
};
module_platform_driver(exynos_usi_driver);
MODULE_DESCRIPTION("Samsung USI driver");
MODULE_AUTHOR("Sam Protsenko <semen.protsenko@linaro.org>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2011-2015 Samsung Electronics Co., Ltd.
// http://www.samsung.com/
//
// Exynos3250 - CPU PMU (Power Management Unit) support
#include <linux/array_size.h>
#include <linux/soc/samsung/exynos-regs-pmu.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include "exynos-pmu.h"
static const struct exynos_pmu_conf exynos3250_pmu_config[] = {
/* { .offset = offset, .val = { AFTR, W-AFTR, SLEEP } */
{ EXYNOS3_ARM_CORE0_SYS_PWR_REG, { 0x0, 0x0, 0x2} },
{ EXYNOS3_DIS_IRQ_ARM_CORE0_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS3_DIS_IRQ_ARM_CORE0_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS3_ARM_CORE1_SYS_PWR_REG, { 0x0, 0x0, 0x2} },
{ EXYNOS3_DIS_IRQ_ARM_CORE1_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS3_DIS_IRQ_ARM_CORE1_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS3_ISP_ARM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_DIS_IRQ_ISP_ARM_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS3_DIS_IRQ_ISP_ARM_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS3_ARM_COMMON_SYS_PWR_REG, { 0x0, 0x0, 0x2} },
{ EXYNOS3_ARM_L2_SYS_PWR_REG, { 0x0, 0x0, 0x3} },
{ EXYNOS3_CMU_ACLKSTOP_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_CMU_SCLKSTOP_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_CMU_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_DRAM_FREQ_DOWN_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS3_DDRPHY_DLLOFF_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS3_LPDDR_PHY_DLL_LOCK_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS3_CMU_ACLKSTOP_COREBLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_SCLKSTOP_COREBLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_RESET_COREBLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_APLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_MPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_BPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_VPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_EPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_UPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS3_EPLLUSER_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_MPLLUSER_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_BPLLUSER_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_CLKSTOP_CAM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_CLKSTOP_MFC_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_CLKSTOP_G3D_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_CLKSTOP_LCD0_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_CLKSTOP_ISP_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_CLKSTOP_MAUDIO_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_RESET_CAM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_RESET_MFC_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_RESET_G3D_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_RESET_LCD0_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_RESET_ISP_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_CMU_RESET_MAUDIO_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS3_TOP_BUS_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS3_TOP_RETENTION_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS3_TOP_PWR_SYS_PWR_REG, { 0x3, 0x3, 0x3} },
{ EXYNOS3_TOP_BUS_COREBLK_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS3_TOP_RETENTION_COREBLK_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS3_TOP_PWR_COREBLK_SYS_PWR_REG, { 0x3, 0x3, 0x3} },
{ EXYNOS3_LOGIC_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_OSCCLK_GATE_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS3_LOGIC_RESET_COREBLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_OSCCLK_GATE_COREBLK_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS3_PAD_RETENTION_DRAM_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_MAUDIO_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_GPIO_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_UART_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_MMC0_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_MMC1_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_MMC2_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_SPI_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_EBIA_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_EBIB_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_RETENTION_JTAG_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_ISOLATION_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_PAD_ALV_SEL_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_XUSBXTI_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_XXTI_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_EXT_REGULATOR_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_EXT_REGULATOR_COREBLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_GPIO_MODE_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_GPIO_MODE_MAUDIO_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_TOP_ASB_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_TOP_ASB_ISOLATION_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_TOP_ASB_RESET_COREBLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_TOP_ASB_ISOLATION_COREBLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS3_CAM_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS3_MFC_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS3_G3D_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS3_LCD0_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS3_ISP_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS3_MAUDIO_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS3_CMU_SYSCLK_ISP_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ PMU_TABLE_END,},
};
static unsigned int const exynos3250_list_feed[] = {
EXYNOS3_ARM_CORE_OPTION(0),
EXYNOS3_ARM_CORE_OPTION(1),
EXYNOS3_ARM_CORE_OPTION(2),
EXYNOS3_ARM_CORE_OPTION(3),
EXYNOS3_ARM_COMMON_OPTION,
EXYNOS3_TOP_PWR_OPTION,
EXYNOS3_CORE_TOP_PWR_OPTION,
S5P_CAM_OPTION,
S5P_MFC_OPTION,
S5P_G3D_OPTION,
S5P_LCD0_OPTION,
S5P_ISP_OPTION,
};
static void exynos3250_powerdown_conf_extra(enum sys_powerdown mode)
{
unsigned int i;
unsigned int tmp;
/* Enable only SC_FEEDBACK */
for (i = 0; i < ARRAY_SIZE(exynos3250_list_feed); i++) {
tmp = pmu_raw_readl(exynos3250_list_feed[i]);
tmp &= ~(EXYNOS3_OPTION_USE_SC_COUNTER);
tmp |= EXYNOS3_OPTION_USE_SC_FEEDBACK;
pmu_raw_writel(tmp, exynos3250_list_feed[i]);
}
if (mode != SYS_SLEEP)
return;
pmu_raw_writel(XUSBXTI_DURATION, EXYNOS3_XUSBXTI_DURATION);
pmu_raw_writel(XXTI_DURATION, EXYNOS3_XXTI_DURATION);
pmu_raw_writel(EXT_REGULATOR_DURATION, EXYNOS3_EXT_REGULATOR_DURATION);
pmu_raw_writel(EXT_REGULATOR_COREBLK_DURATION,
EXYNOS3_EXT_REGULATOR_COREBLK_DURATION);
}
static void exynos3250_pmu_init(void)
{
unsigned int value;
/*
* To prevent from issuing new bus request form L2 memory system
* If core status is power down, should be set '1' to L2 power down
*/
value = pmu_raw_readl(EXYNOS3_ARM_COMMON_OPTION);
value |= EXYNOS3_OPTION_SKIP_DEACTIVATE_ACEACP_IN_PWDN;
pmu_raw_writel(value, EXYNOS3_ARM_COMMON_OPTION);
/* Enable USE_STANDBY_WFI for all CORE */
pmu_raw_writel(S5P_USE_STANDBY_WFI_ALL, S5P_CENTRAL_SEQ_OPTION);
/*
* Set PSHOLD port for output high
*/
value = pmu_raw_readl(S5P_PS_HOLD_CONTROL);
value |= S5P_PS_HOLD_OUTPUT_HIGH;
pmu_raw_writel(value, S5P_PS_HOLD_CONTROL);
/*
* Enable signal for PSHOLD port
*/
value = pmu_raw_readl(S5P_PS_HOLD_CONTROL);
value |= S5P_PS_HOLD_EN;
pmu_raw_writel(value, S5P_PS_HOLD_CONTROL);
}
const struct exynos_pmu_data exynos3250_pmu_data = {
.pmu_config = exynos3250_pmu_config,
.pmu_init = exynos3250_pmu_init,
.powerdown_conf_extra = exynos3250_powerdown_conf_extra,
};
@@ -0,0 +1,218 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2011-2015 Samsung Electronics Co., Ltd.
// http://www.samsung.com/
//
// Exynos4 - CPU PMU(Power Management Unit) support
#include <linux/soc/samsung/exynos-regs-pmu.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include "exynos-pmu.h"
static const struct exynos_pmu_conf exynos4210_pmu_config[] = {
/* { .offset = offset, .val = { AFTR, LPA, SLEEP } */
{ S5P_ARM_CORE0_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_DIS_IRQ_CORE0, { 0x0, 0x0, 0x0 } },
{ S5P_DIS_IRQ_CENTRAL0, { 0x0, 0x0, 0x0 } },
{ S5P_ARM_CORE1_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_DIS_IRQ_CORE1, { 0x0, 0x0, 0x0 } },
{ S5P_DIS_IRQ_CENTRAL1, { 0x0, 0x0, 0x0 } },
{ S5P_ARM_COMMON_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_L2_0_LOWPWR, { 0x2, 0x2, 0x3 } },
{ S5P_L2_1_LOWPWR, { 0x2, 0x2, 0x3 } },
{ S5P_CMU_ACLKSTOP_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_SCLKSTOP_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_APLL_SYSCLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_MPLL_SYSCLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_VPLL_SYSCLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_EPLL_SYSCLK_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_GPS_ALIVE_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_GPSALIVE_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_CAM_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_TV_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_MFC_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_G3D_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_LCD0_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_LCD1_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_MAUDIO_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_CLKSTOP_GPS_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_CAM_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_TV_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_MFC_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_G3D_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_LCD0_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_LCD1_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_MAUDIO_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_GPS_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_TOP_BUS_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_TOP_RETENTION_LOWPWR, { 0x1, 0x0, 0x1 } },
{ S5P_TOP_PWR_LOWPWR, { 0x3, 0x0, 0x3 } },
{ S5P_LOGIC_RESET_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_ONENAND_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_MODIMIF_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_G2D_ACP_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_USBOTG_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_HSMMC_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_CSSYS_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_SECSS_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_PCIE_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_SATA_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_DRAM_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_MAUDIO_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_PAD_RETENTION_GPIO_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_UART_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_MMCA_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_MMCB_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_EBIA_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_EBIB_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_ISOLATION_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_ALV_SEL_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_XUSBXTI_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_XXTI_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_EXT_REGULATOR_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_GPIO_MODE_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_GPIO_MODE_MAUDIO_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CAM_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_TV_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_MFC_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_G3D_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_LCD0_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_LCD1_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_MAUDIO_LOWPWR, { 0x7, 0x7, 0x0 } },
{ S5P_GPS_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_GPS_ALIVE_LOWPWR, { 0x7, 0x0, 0x0 } },
{ PMU_TABLE_END,},
};
static const struct exynos_pmu_conf exynos4x12_pmu_config[] = {
{ S5P_ARM_CORE0_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_DIS_IRQ_CORE0, { 0x0, 0x0, 0x0 } },
{ S5P_DIS_IRQ_CENTRAL0, { 0x0, 0x0, 0x0 } },
{ S5P_ARM_CORE1_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_DIS_IRQ_CORE1, { 0x0, 0x0, 0x0 } },
{ S5P_DIS_IRQ_CENTRAL1, { 0x0, 0x0, 0x0 } },
{ S5P_ISP_ARM_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_DIS_IRQ_ISP_ARM_LOCAL_LOWPWR, { 0x0, 0x0, 0x0 } },
{ S5P_DIS_IRQ_ISP_ARM_CENTRAL_LOWPWR, { 0x0, 0x0, 0x0 } },
{ S5P_ARM_COMMON_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_L2_0_LOWPWR, { 0x0, 0x0, 0x3 } },
/* XXX_OPTION register should be set other field */
{ S5P_ARM_L2_0_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_L2_1_LOWPWR, { 0x0, 0x0, 0x3 } },
{ S5P_ARM_L2_1_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_CMU_ACLKSTOP_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_SCLKSTOP_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_DRAM_FREQ_DOWN_LOWPWR, { 0x1, 0x1, 0x1 } },
{ S5P_DDRPHY_DLLOFF_LOWPWR, { 0x1, 0x1, 0x1 } },
{ S5P_LPDDR_PHY_DLL_LOCK_LOWPWR, { 0x1, 0x1, 0x1 } },
{ S5P_CMU_ACLKSTOP_COREBLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_SCLKSTOP_COREBLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_COREBLK_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_APLL_SYSCLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_MPLL_SYSCLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_VPLL_SYSCLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_EPLL_SYSCLK_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_MPLLUSER_SYSCLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_GPS_ALIVE_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_GPSALIVE_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_CAM_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_TV_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_MFC_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_G3D_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_LCD0_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_ISP_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_MAUDIO_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_CLKSTOP_GPS_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_CAM_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_TV_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_MFC_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_G3D_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_LCD0_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_ISP_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_RESET_MAUDIO_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_CMU_RESET_GPS_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_TOP_BUS_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_TOP_RETENTION_LOWPWR, { 0x1, 0x0, 0x1 } },
{ S5P_TOP_PWR_LOWPWR, { 0x3, 0x0, 0x3 } },
{ S5P_TOP_BUS_COREBLK_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_TOP_RETENTION_COREBLK_LOWPWR, { 0x1, 0x0, 0x1 } },
{ S5P_TOP_PWR_COREBLK_LOWPWR, { 0x3, 0x0, 0x3 } },
{ S5P_LOGIC_RESET_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_OSCCLK_GATE_LOWPWR, { 0x1, 0x0, 0x1 } },
{ S5P_LOGIC_RESET_COREBLK_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_OSCCLK_GATE_COREBLK_LOWPWR, { 0x1, 0x0, 0x1 } },
{ S5P_ONENAND_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_ONENAND_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_HSI_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_HSI_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_G2D_ACP_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_G2D_ACP_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_USBOTG_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_USBOTG_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_HSMMC_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_HSMMC_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_CSSYS_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_CSSYS_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_SECSS_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_SECSS_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_ROTATOR_MEM_LOWPWR, { 0x3, 0x0, 0x0 } },
{ S5P_ROTATOR_MEM_OPTION, { 0x10, 0x10, 0x0 } },
{ S5P_PAD_RETENTION_DRAM_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_MAUDIO_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_PAD_RETENTION_GPIO_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_UART_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_MMCA_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_MMCB_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_EBIA_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_EBIB_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_GPIO_COREBLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_ISOLATION_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_ISOLATION_COREBLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_PAD_RETENTION_ALV_SEL_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_XUSBXTI_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_XXTI_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_EXT_REGULATOR_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_GPIO_MODE_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_GPIO_MODE_COREBLK_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_GPIO_MODE_MAUDIO_LOWPWR, { 0x1, 0x1, 0x0 } },
{ S5P_TOP_ASB_RESET_LOWPWR, { 0x1, 0x1, 0x1 } },
{ S5P_TOP_ASB_ISOLATION_LOWPWR, { 0x1, 0x0, 0x1 } },
{ S5P_CAM_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_TV_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_MFC_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_G3D_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_LCD0_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_ISP_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_MAUDIO_LOWPWR, { 0x7, 0x7, 0x0 } },
{ S5P_GPS_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_GPS_ALIVE_LOWPWR, { 0x7, 0x0, 0x0 } },
{ S5P_CMU_SYSCLK_ISP_LOWPWR, { 0x1, 0x0, 0x0 } },
{ S5P_CMU_SYSCLK_GPS_LOWPWR, { 0x1, 0x0, 0x0 } },
{ PMU_TABLE_END,},
};
static const struct exynos_pmu_conf exynos4412_pmu_config[] = {
{ S5P_ARM_CORE2_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_DIS_IRQ_CORE2, { 0x0, 0x0, 0x0 } },
{ S5P_DIS_IRQ_CENTRAL2, { 0x0, 0x0, 0x0 } },
{ S5P_ARM_CORE3_LOWPWR, { 0x0, 0x0, 0x2 } },
{ S5P_DIS_IRQ_CORE3, { 0x0, 0x0, 0x0 } },
{ S5P_DIS_IRQ_CENTRAL3, { 0x0, 0x0, 0x0 } },
{ PMU_TABLE_END,},
};
const struct exynos_pmu_data exynos4210_pmu_data = {
.pmu_config = exynos4210_pmu_config,
};
const struct exynos_pmu_data exynos4212_pmu_data = {
.pmu_config = exynos4x12_pmu_config,
};
const struct exynos_pmu_data exynos4412_pmu_data = {
.pmu_config = exynos4x12_pmu_config,
.pmu_config_extra = exynos4412_pmu_config,
};
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2011-2015 Samsung Electronics Co., Ltd.
// http://www.samsung.com/
//
// Exynos5250 - CPU PMU (Power Management Unit) support
#include <linux/array_size.h>
#include <linux/soc/samsung/exynos-regs-pmu.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include "exynos-pmu.h"
static const struct exynos_pmu_conf exynos5250_pmu_config[] = {
/* { .offset = offset, .val = { AFTR, LPA, SLEEP } */
{ EXYNOS5_ARM_CORE0_SYS_PWR_REG, { 0x0, 0x0, 0x2} },
{ EXYNOS5_DIS_IRQ_ARM_CORE0_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ARM_CORE0_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_ARM_CORE1_SYS_PWR_REG, { 0x0, 0x0, 0x2} },
{ EXYNOS5_DIS_IRQ_ARM_CORE1_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ARM_CORE1_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_FSYS_ARM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_FSYS_ARM_CENTRAL_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_ISP_ARM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ISP_ARM_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ISP_ARM_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_ARM_COMMON_SYS_PWR_REG, { 0x0, 0x0, 0x2} },
{ EXYNOS5_ARM_L2_SYS_PWR_REG, { 0x3, 0x3, 0x3} },
{ EXYNOS_L2_OPTION(0), { 0x10, 0x10, 0x0 } },
{ EXYNOS5_CMU_ACLKSTOP_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_CMU_SCLKSTOP_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_CMU_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_CMU_ACLKSTOP_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_CMU_SCLKSTOP_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_CMU_RESET_SYSMEM_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_DRAM_FREQ_DOWN_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_DDRPHY_DLLOFF_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_DDRPHY_DLLLOCK_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_APLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_MPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_VPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_EPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_BPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_MPLLUSER_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_BPLLUSER_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_TOP_BUS_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_TOP_RETENTION_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_TOP_PWR_SYS_PWR_REG, { 0x3, 0x0, 0x3} },
{ EXYNOS5_TOP_BUS_SYSMEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_TOP_RETENTION_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_TOP_PWR_SYSMEM_SYS_PWR_REG, { 0x3, 0x0, 0x3} },
{ EXYNOS5_LOGIC_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_OSCCLK_GATE_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_LOGIC_RESET_SYSMEM_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_OSCCLK_GATE_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_USBOTG_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_G2D_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_USBDRD_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_SDMMC_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_CSSYS_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_SECSS_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_ROTATOR_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_INTRAM_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_INTROM_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_JPEG_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_JPEG_MEM_OPTION, { 0x10, 0x10, 0x0} },
{ EXYNOS5_HSI_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_MCUIOP_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_SATA_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_DRAM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_MAU_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_PAD_RETENTION_GPIO_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_UART_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_MMCA_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_MMCB_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_EBIA_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_EBIB_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_SPI_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_GPIO_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_ISOLATION_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_ISOLATION_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_ALV_SEL_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_XUSBXTI_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_XXTI_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_EXT_REGULATOR_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_GPIO_MODE_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_GPIO_MODE_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_GPIO_MODE_MAU_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_TOP_ASB_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_TOP_ASB_ISOLATION_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_GSCL_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_ISP_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_MFC_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_G3D_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_DISP1_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_MAU_SYS_PWR_REG, { 0x7, 0x7, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_GSCL_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_ISP_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_MFC_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_G3D_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_DISP1_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_MAU_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_CMU_SYSCLK_GSCL_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_ISP_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_MFC_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_G3D_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_DISP1_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_MAU_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_CMU_RESET_GSCL_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_ISP_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_MFC_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_G3D_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_DISP1_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_MAU_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ PMU_TABLE_END,},
};
static unsigned int const exynos5_list_both_cnt_feed[] = {
EXYNOS5_ARM_CORE0_OPTION,
EXYNOS5_ARM_CORE1_OPTION,
EXYNOS5_ARM_COMMON_OPTION,
EXYNOS5_GSCL_OPTION,
EXYNOS5_ISP_OPTION,
EXYNOS5_MFC_OPTION,
EXYNOS5_G3D_OPTION,
EXYNOS5_DISP1_OPTION,
EXYNOS5_MAU_OPTION,
EXYNOS5_TOP_PWR_OPTION,
EXYNOS5_TOP_PWR_SYSMEM_OPTION,
};
static unsigned int const exynos5_list_disable_wfi_wfe[] = {
EXYNOS5_ARM_CORE1_OPTION,
EXYNOS5_FSYS_ARM_OPTION,
EXYNOS5_ISP_ARM_OPTION,
};
static void exynos5250_pmu_init(void)
{
unsigned int value;
/*
* When SYS_WDTRESET is set, watchdog timer reset request
* is ignored by power management unit.
*/
value = pmu_raw_readl(EXYNOS5_AUTO_WDTRESET_DISABLE);
value &= ~EXYNOS5_SYS_WDTRESET;
pmu_raw_writel(value, EXYNOS5_AUTO_WDTRESET_DISABLE);
value = pmu_raw_readl(EXYNOS5_MASK_WDTRESET_REQUEST);
value &= ~EXYNOS5_SYS_WDTRESET;
pmu_raw_writel(value, EXYNOS5_MASK_WDTRESET_REQUEST);
}
static void exynos5_powerdown_conf(enum sys_powerdown mode)
{
unsigned int i;
unsigned int tmp;
/*
* Enable both SC_FEEDBACK and SC_COUNTER
*/
for (i = 0; i < ARRAY_SIZE(exynos5_list_both_cnt_feed); i++) {
tmp = pmu_raw_readl(exynos5_list_both_cnt_feed[i]);
tmp |= (EXYNOS5_USE_SC_FEEDBACK |
EXYNOS5_USE_SC_COUNTER);
pmu_raw_writel(tmp, exynos5_list_both_cnt_feed[i]);
}
/*
* SKIP_DEACTIVATE_ACEACP_IN_PWDN_BITFIELD Enable
*/
tmp = pmu_raw_readl(EXYNOS5_ARM_COMMON_OPTION);
tmp |= EXYNOS5_SKIP_DEACTIVATE_ACEACP_IN_PWDN;
pmu_raw_writel(tmp, EXYNOS5_ARM_COMMON_OPTION);
/*
* Disable WFI/WFE on XXX_OPTION
*/
for (i = 0; i < ARRAY_SIZE(exynos5_list_disable_wfi_wfe); i++) {
tmp = pmu_raw_readl(exynos5_list_disable_wfi_wfe[i]);
tmp &= ~(EXYNOS5_OPTION_USE_STANDBYWFE |
EXYNOS5_OPTION_USE_STANDBYWFI);
pmu_raw_writel(tmp, exynos5_list_disable_wfi_wfe[i]);
}
}
const struct exynos_pmu_data exynos5250_pmu_data = {
.pmu_config = exynos5250_pmu_config,
.pmu_init = exynos5250_pmu_init,
.powerdown_conf = exynos5_powerdown_conf,
};
@@ -0,0 +1,277 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (c) 2011-2015 Samsung Electronics Co., Ltd.
// http://www.samsung.com/
//
// Exynos5420 - CPU PMU (Power Management Unit) support
#include <linux/array_size.h>
#include <linux/pm.h>
#include <linux/soc/samsung/exynos-regs-pmu.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include <asm/cputype.h>
#include "exynos-pmu.h"
static const struct exynos_pmu_conf exynos5420_pmu_config[] = {
/* { .offset = offset, .val = { AFTR, LPA, SLEEP } */
{ EXYNOS5_ARM_CORE0_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ARM_CORE0_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ARM_CORE0_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_ARM_CORE1_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ARM_CORE1_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ARM_CORE1_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_ARM_CORE2_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_ARM_CORE2_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_ARM_CORE2_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_ARM_CORE3_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_ARM_CORE3_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_ARM_CORE3_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_KFC_CORE0_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE0_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE0_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_KFC_CORE1_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE1_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE1_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_KFC_CORE2_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE2_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE2_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_KFC_CORE3_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE3_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_DIS_IRQ_KFC_CORE3_CENTRAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_ISP_ARM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ISP_ARM_LOCAL_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ISP_ARM_CENTRAL_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_ARM_COMMON_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_KFC_COMMON_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_ARM_L2_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_KFC_L2_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_ACLKSTOP_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_SCLKSTOP_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_CMU_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_CMU_ACLKSTOP_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CMU_SCLKSTOP_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_CMU_RESET_SYSMEM_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_DRAM_FREQ_DOWN_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_DDRPHY_DLLOFF_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_DDRPHY_DLLLOCK_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_APLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_MPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_VPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_EPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_BPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_CPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_DPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_IPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_KPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_MPLLUSER_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_BPLLUSER_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_RPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_SPLL_SYSCLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_TOP_BUS_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_TOP_RETENTION_SYS_PWR_REG, { 0x1, 0x1, 0x1} },
{ EXYNOS5_TOP_PWR_SYS_PWR_REG, { 0x3, 0x3, 0x0} },
{ EXYNOS5_TOP_BUS_SYSMEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_TOP_RETENTION_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_TOP_PWR_SYSMEM_SYS_PWR_REG, { 0x3, 0x0, 0x0} },
{ EXYNOS5_LOGIC_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_OSCCLK_GATE_SYS_PWR_REG, { 0x1, 0x0, 0x1} },
{ EXYNOS5_LOGIC_RESET_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_OSCCLK_GATE_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_INTRAM_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x3} },
{ EXYNOS5420_INTROM_MEM_SYS_PWR_REG, { 0x3, 0x0, 0x3} },
{ EXYNOS5_PAD_RETENTION_DRAM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_RETENTION_MAU_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5420_PAD_RETENTION_JTAG_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5420_PAD_RETENTION_DRAM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_UART_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_MMC0_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_MMC1_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_MMC2_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_HSI_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_EBIA_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_EBIB_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_SPI_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5420_PAD_RETENTION_DRAM_COREBLK_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_ISOLATION_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_PAD_ISOLATION_SYSMEM_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_PAD_ALV_SEL_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_XUSBXTI_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_XXTI_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_EXT_REGULATOR_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_GPIO_MODE_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_GPIO_MODE_SYSMEM_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_GPIO_MODE_MAU_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_TOP_ASB_RESET_SYS_PWR_REG, { 0x1, 0x1, 0x0} },
{ EXYNOS5_TOP_ASB_ISOLATION_SYS_PWR_REG, { 0x1, 0x0, 0x0} },
{ EXYNOS5_GSCL_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_ISP_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_MFC_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_G3D_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_DISP1_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_MAU_SYS_PWR_REG, { 0x7, 0x7, 0x0} },
{ EXYNOS5420_G2D_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_MSC_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_FSYS_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_FSYS2_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_PSGEN_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_PERIC_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5420_WCORE_SYS_PWR_REG, { 0x7, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_GSCL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_ISP_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_MFC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_CLKSTOP_G3D_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_DISP1_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_MAU_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_G2D_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_MSC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_FSYS_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_PSGEN_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_PERIC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_CLKSTOP_WCORE_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_GSCL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_ISP_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_MFC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_SYSCLK_G3D_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_DISP1_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_MAU_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_G2D_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_MSC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_FSYS_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_FSYS2_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_PSGEN_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_PERIC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_SYSCLK_WCORE_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_FSYS2_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_PSGEN_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_PERIC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_WCORE_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_GSCL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_ISP_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_MFC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_CMU_RESET_G3D_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_DISP1_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_MAU_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_G2D_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_MSC_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5420_CMU_RESET_FSYS_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ PMU_TABLE_END,},
};
static unsigned int const exynos5420_list_disable_pmu_reg[] = {
EXYNOS5_CMU_CLKSTOP_GSCL_SYS_PWR_REG,
EXYNOS5_CMU_CLKSTOP_ISP_SYS_PWR_REG,
EXYNOS5_CMU_CLKSTOP_G3D_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_DISP1_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_MAU_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_G2D_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_MSC_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_FSYS_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_PSGEN_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_PERIC_SYS_PWR_REG,
EXYNOS5420_CMU_CLKSTOP_WCORE_SYS_PWR_REG,
EXYNOS5_CMU_SYSCLK_GSCL_SYS_PWR_REG,
EXYNOS5_CMU_SYSCLK_ISP_SYS_PWR_REG,
EXYNOS5_CMU_SYSCLK_G3D_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_DISP1_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_MAU_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_G2D_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_MSC_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_FSYS_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_FSYS2_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_PSGEN_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_PERIC_SYS_PWR_REG,
EXYNOS5420_CMU_SYSCLK_WCORE_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_FSYS2_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_PSGEN_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_PERIC_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_WCORE_SYS_PWR_REG,
EXYNOS5_CMU_RESET_GSCL_SYS_PWR_REG,
EXYNOS5_CMU_RESET_ISP_SYS_PWR_REG,
EXYNOS5_CMU_RESET_G3D_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_DISP1_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_MAU_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_G2D_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_MSC_SYS_PWR_REG,
EXYNOS5420_CMU_RESET_FSYS_SYS_PWR_REG,
};
static void exynos5420_powerdown_conf(enum sys_powerdown mode)
{
u32 this_cluster;
this_cluster = MPIDR_AFFINITY_LEVEL(read_cpuid_mpidr(), 1);
/*
* set the cluster id to IROM register to ensure that we wake
* up with the current cluster.
*/
pmu_raw_writel(this_cluster, EXYNOS_IROM_DATA2);
}
static void exynos5420_pmu_init(void)
{
unsigned int value;
int i;
/*
* Set the CMU_RESET, CMU_SYSCLK and CMU_CLKSTOP registers
* for local power blocks to Low initially as per Table 8-4:
* "System-Level Power-Down Configuration Registers".
*/
for (i = 0; i < ARRAY_SIZE(exynos5420_list_disable_pmu_reg); i++)
pmu_raw_writel(0, exynos5420_list_disable_pmu_reg[i]);
/* Enable USE_STANDBY_WFI for all CORE */
pmu_raw_writel(EXYNOS5420_USE_STANDBY_WFI_ALL, S5P_CENTRAL_SEQ_OPTION);
value = pmu_raw_readl(EXYNOS_L2_OPTION(0));
value &= ~EXYNOS_L2_USE_RETENTION;
pmu_raw_writel(value, EXYNOS_L2_OPTION(0));
value = pmu_raw_readl(EXYNOS_L2_OPTION(1));
value &= ~EXYNOS_L2_USE_RETENTION;
pmu_raw_writel(value, EXYNOS_L2_OPTION(1));
/*
* If L2_COMMON is turned off, clocks related to ATB async
* bridge are gated. Thus, when ISP power is gated, LPI
* may get stuck.
*/
value = pmu_raw_readl(EXYNOS5420_LPI_MASK);
value |= EXYNOS5420_ATB_ISP_ARM;
pmu_raw_writel(value, EXYNOS5420_LPI_MASK);
value = pmu_raw_readl(EXYNOS5420_LPI_MASK1);
value |= EXYNOS5420_ATB_KFC;
pmu_raw_writel(value, EXYNOS5420_LPI_MASK1);
/* Prevent issue of new bus request from L2 memory */
value = pmu_raw_readl(EXYNOS5420_ARM_COMMON_OPTION);
value |= EXYNOS5_SKIP_DEACTIVATE_ACEACP_IN_PWDN;
pmu_raw_writel(value, EXYNOS5420_ARM_COMMON_OPTION);
value = pmu_raw_readl(EXYNOS5420_KFC_COMMON_OPTION);
value |= EXYNOS5_SKIP_DEACTIVATE_ACEACP_IN_PWDN;
pmu_raw_writel(value, EXYNOS5420_KFC_COMMON_OPTION);
/* This setting is to reduce suspend/resume time */
pmu_raw_writel(DUR_WAIT_RESET, EXYNOS5420_LOGIC_RESET_DURATION3);
/* Serialized CPU wakeup of Eagle */
pmu_raw_writel(SPREAD_ENABLE, EXYNOS5420_ARM_INTR_SPREAD_ENABLE);
pmu_raw_writel(SPREAD_USE_STANDWFI,
EXYNOS5420_ARM_INTR_SPREAD_USE_STANDBYWFI);
pmu_raw_writel(0x1, EXYNOS5420_UP_SCHEDULER);
pr_info("EXYNOS5420 PMU initialized\n");
}
const struct exynos_pmu_data exynos5420_pmu_data = {
.pmu_config = exynos5420_pmu_config,
.pmu_init = exynos5420_pmu_init,
.powerdown_conf = exynos5420_powerdown_conf,
};
@@ -0,0 +1,506 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* Samsung Exynos 5422 SoC Adaptive Supply Voltage support
*/
#include <linux/bitrev.h>
#include <linux/errno.h>
#include <linux/regmap.h>
#include <linux/soc/samsung/exynos-chipid.h>
#include <linux/slab.h>
#include "exynos-asv.h"
#include "exynos5422-asv.h"
#define ASV_GROUPS_NUM 14
#define ASV_ARM_DVFS_NUM 20
#define ASV_ARM_BIN2_DVFS_NUM 17
#define ASV_KFC_DVFS_NUM 14
#define ASV_KFC_BIN2_DVFS_NUM 12
/*
* This array is a set of 4 ASV data tables, first column of each ASV table
* contains frequency value in MHz and subsequent columns contain the CPU
* cluster's supply voltage values in uV.
* In order to create a set of OPPs for specific SoC revision one of the voltage
* columns (1...14) from one of the tables (0...3) is selected during
* initialization. There are separate ASV tables for the big (ARM) and little
* (KFC) CPU cluster. Only OPPs which are already defined in devicetree
* will be updated.
*/
static const u32 asv_arm_table[][ASV_ARM_DVFS_NUM][ASV_GROUPS_NUM + 1] = {
{
/* ARM 0, 1 */
{ 2100, 1362500, 1362500, 1350000, 1337500, 1325000, 1312500, 1300000,
1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000 },
{ 2000, 1312500, 1312500, 1300000, 1287500, 1275000, 1262500, 1250000,
1237500, 1225000, 1237500, 1225000, 1212500, 1200000, 1187500 },
{ 1900, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500, 1175000,
1162500, 1150000, 1162500, 1150000, 1137500, 1125000, 1112500 },
{ 1800, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500, 1125000,
1112500, 1100000, 1112500, 1100000, 1087500, 1075000, 1062500 },
{ 1700, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500,
1075000, 1062500, 1075000, 1062500, 1050000, 1037500, 1025000 },
{ 1600, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500, 1050000,
1037500, 1025000, 1037500, 1025000, 1012500, 1000000, 987500 },
{ 1500, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500,
1000000, 987500, 1000000, 987500, 975000, 962500, 950000 },
{ 1400, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
975000, 962500, 975000, 962500, 950000, 937500, 925000 },
{ 1300, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
962500, 950000, 962500, 950000, 937500, 925000, 912500 },
{ 1200, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
937500, 925000, 937500, 925000, 912500, 900000, 900000 },
{ 1100, 1000000, 987500, 975000, 962500, 950000, 937500, 925000,
912500, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 1000, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 900, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 800, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 700, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}, {
/* ARM 2 */
{ 2100, 1362500, 1362500, 1350000, 1337500, 1325000, 1312500, 1300000,
1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000 },
{ 2000, 1312500, 1312500, 1312500, 1300000, 1275000, 1262500, 1250000,
1237500, 1225000, 1237500, 1225000, 1212500, 1200000, 1187500 },
{ 1900, 1262500, 1250000, 1250000, 1237500, 1212500, 1200000, 1187500,
1175000, 1162500, 1175000, 1162500, 1150000, 1137500, 1125000 },
{ 1800, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500,
1125000, 1112500, 1125000, 1112500, 1100000, 1087500, 1075000 },
{ 1700, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
1087500, 1075000, 1087500, 1075000, 1062500, 1050000, 1037500 },
{ 1600, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
1050000, 1037500, 1050000, 1037500, 1025000, 1012500, 1000000 },
{ 1500, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
1012500, 1000000, 1012500, 1000000, 987500, 975000, 962500 },
{ 1400, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000,
987500, 975000, 987500, 975000, 962500, 950000, 937500 },
{ 1300, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
962500, 950000, 962500, 950000, 937500, 925000, 912500 },
{ 1200, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
937500, 925000, 937500, 925000, 912500, 900000, 900000 },
{ 1100, 1000000, 987500, 975000, 962500, 950000, 937500, 925000,
912500, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 1000, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 900, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 800, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 700, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}, {
/* ARM 3 */
{ 2100, 1362500, 1362500, 1350000, 1337500, 1325000, 1312500, 1300000,
1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000 },
{ 2000, 1312500, 1312500, 1300000, 1287500, 1275000, 1262500, 1250000,
1237500, 1225000, 1237500, 1225000, 1212500, 1200000, 1187500 },
{ 1900, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500,
1175000, 1162500, 1175000, 1162500, 1150000, 1137500, 1125000 },
{ 1800, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500,
1125000, 1112500, 1125000, 1112500, 1100000, 1087500, 1075000 },
{ 1700, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
1087500, 1075000, 1087500, 1075000, 1062500, 1050000, 1037500 },
{ 1600, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
1050000, 1037500, 1050000, 1037500, 1025000, 1012500, 1000000 },
{ 1500, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
1012500, 1000000, 1012500, 1000000, 987500, 975000, 962500 },
{ 1400, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000,
987500, 975000, 987500, 975000, 962500, 950000, 937500 },
{ 1300, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
962500, 950000, 962500, 950000, 937500, 925000, 912500 },
{ 1200, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
937500, 925000, 937500, 925000, 912500, 900000, 900000 },
{ 1100, 1000000, 987500, 975000, 962500, 950000, 937500, 925000,
912500, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 1000, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 900, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 800, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 700, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}, {
/* ARM bin 2 */
{ 1800, 1237500, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500,
1150000, 1137500, 1150000, 1137500, 1125000, 1112500, 1100000 },
{ 1700, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500, 1125000,
1112500, 1100000, 1112500, 1100000, 1087500, 1075000, 1062500 },
{ 1600, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500,
1075000, 1062500, 1075000, 1062500, 1050000, 1037500, 1025000 },
{ 1500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500, 1050000,
1037500, 1025000, 1037500, 1025000, 1012500, 1000000, 987500 },
{ 1400, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
1012500, 1000000, 1012500, 1000000, 987500, 975000, 962500 },
{ 1300, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500,
1000000, 987500, 1000000, 987500, 975000, 962500, 950000 },
{ 1200, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
975000, 962500, 975000, 962500, 950000, 937500, 925000 },
{ 1100, 1037500, 1025000, 1012500, 1000000, 987500, 975000, 962500,
950000, 937500, 950000, 937500, 925000, 912500, 900000 },
{ 1000, 1012500, 1000000, 987500, 975000, 962500, 950000, 937500,
925000, 912500, 925000, 912500, 900000, 900000, 900000 },
{ 900, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 800, 962500, 950000, 937500, 925000, 912500, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 700, 937500, 925000, 912500, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}
};
static const u32 asv_kfc_table[][ASV_KFC_DVFS_NUM][ASV_GROUPS_NUM + 1] = {
{
/* KFC 0, 1 */
{ 1500000, 1300000, 1300000, 1300000, 1287500, 1287500, 1287500, 1275000,
1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500 },
{ 1400000, 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000,
1187500, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500 },
{ 1300000, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000,
1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500 },
{ 1200000, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500 },
{ 1100000, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000 },
{ 1000000, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
1012500, 1000000, 987500, 975000, 962500, 950000, 937500 },
{ 900000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
975000, 962500, 950000, 937500, 925000, 912500, 900000 },
{ 800000, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
937500, 925000, 912500, 900000, 900000, 900000, 900000 },
{ 700000, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 600000, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500000, 912500, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400000, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300000, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200000, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}, {
/* KFC 2 */
{ 1500, 1300000, 1300000, 1300000, 1287500, 1287500, 1287500, 1275000,
1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500 },
{ 1400, 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000,
1187500, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500 },
{ 1300, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000,
1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500 },
{ 1200, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500 },
{ 1100, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000 },
{ 1000, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
1012500, 1000000, 987500, 975000, 962500, 950000, 937500 },
{ 900, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
975000, 962500, 950000, 937500, 925000, 912500, 900000 },
{ 800, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
937500, 925000, 912500, 900000, 900000, 900000, 900000 },
{ 700, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 600, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500, 912500, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}, {
/* KFC 3 */
{ 1500, 1300000, 1300000, 1300000, 1287500, 1287500, 1287500, 1275000,
1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500 },
{ 1400, 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000,
1187500, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500 },
{ 1300, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000,
1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500 },
{ 1200, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500 },
{ 1100, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000 },
{ 1000, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
1012500, 1000000, 987500, 975000, 962500, 950000, 937500 },
{ 900, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
975000, 962500, 950000, 937500, 925000, 912500, 900000 },
{ 800, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
937500, 925000, 912500, 900000, 900000, 900000, 900000 },
{ 700, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 600, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500, 912500, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}, {
/* KFC bin 2 */
{ 1300, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500, 1175000,
1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500 },
{ 1200, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500, 1125000,
1112500, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500 },
{ 1100, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500,
1075000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000 },
{ 1000, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500, 1050000,
1037500, 1025000, 1012500, 1000000, 987500, 975000, 962500 },
{ 900, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500,
1000000, 987500, 975000, 962500, 950000, 937500, 925000 },
{ 800, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
962500, 950000, 937500, 925000, 912500, 900000, 900000 },
{ 700, 1012500, 1000000, 987500, 975000, 962500, 950000, 937500,
925000, 912500, 900000, 900000, 900000, 900000, 900000 },
{ 600, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 500, 937500, 925000, 912500, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 400, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
{ 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
900000, 900000, 900000, 900000, 900000, 900000, 900000 },
}
};
static const struct asv_limit_entry __asv_limits[ASV_GROUPS_NUM] = {
{ 13, 55 },
{ 21, 65 },
{ 25, 69 },
{ 30, 72 },
{ 36, 74 },
{ 43, 76 },
{ 51, 78 },
{ 65, 80 },
{ 81, 82 },
{ 98, 84 },
{ 119, 87 },
{ 135, 89 },
{ 150, 92 },
{ 999, 999 },
};
static int exynos5422_asv_get_group(struct exynos_asv *asv)
{
unsigned int pkgid_reg, auxi_reg;
int hpm, ids, i;
regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PKG_ID, &pkgid_reg);
regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_AUX_INFO, &auxi_reg);
if (asv->use_sg) {
u32 sga = (pkgid_reg >> EXYNOS5422_SG_A_OFFSET) &
EXYNOS5422_SG_A_MASK;
u32 sgb = (pkgid_reg >> EXYNOS5422_SG_B_OFFSET) &
EXYNOS5422_SG_B_MASK;
if ((pkgid_reg >> EXYNOS5422_SG_BSIGN_OFFSET) &
EXYNOS5422_SG_BSIGN_MASK)
return sga + sgb;
else
return sga - sgb;
}
hpm = (auxi_reg >> EXYNOS5422_TMCB_OFFSET) & EXYNOS5422_TMCB_MASK;
ids = (pkgid_reg >> EXYNOS5422_IDS_OFFSET) & EXYNOS5422_IDS_MASK;
for (i = 0; i < ASV_GROUPS_NUM; i++) {
if (ids <= __asv_limits[i].ids)
break;
if (hpm <= __asv_limits[i].hpm)
break;
}
if (i < ASV_GROUPS_NUM)
return i;
return 0;
}
static int __asv_offset_voltage(unsigned int index)
{
switch (index) {
case 1:
return 12500;
case 2:
return 50000;
case 3:
return 25000;
default:
return 0;
}
}
static void exynos5422_asv_offset_voltage_setup(struct exynos_asv *asv)
{
struct exynos_asv_subsys *subsys;
unsigned int reg, value;
regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_AUX_INFO, &reg);
/* ARM offset voltage setup */
subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_ARM];
subsys->base_volt = 1000000;
value = (reg >> EXYNOS5422_ARM_UP_OFFSET) & EXYNOS5422_ARM_UP_MASK;
subsys->offset_volt_h = __asv_offset_voltage(value);
value = (reg >> EXYNOS5422_ARM_DN_OFFSET) & EXYNOS5422_ARM_DN_MASK;
subsys->offset_volt_l = __asv_offset_voltage(value);
/* KFC offset voltage setup */
subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_KFC];
subsys->base_volt = 1000000;
value = (reg >> EXYNOS5422_KFC_UP_OFFSET) & EXYNOS5422_KFC_UP_MASK;
subsys->offset_volt_h = __asv_offset_voltage(value);
value = (reg >> EXYNOS5422_KFC_DN_OFFSET) & EXYNOS5422_KFC_DN_MASK;
subsys->offset_volt_l = __asv_offset_voltage(value);
}
static int exynos5422_asv_opp_get_voltage(const struct exynos_asv_subsys *subsys,
int level, unsigned int volt)
{
unsigned int asv_volt;
if (level >= subsys->table.num_rows)
return volt;
asv_volt = exynos_asv_opp_get_voltage(subsys, level,
subsys->asv->group);
if (volt > subsys->base_volt)
asv_volt += subsys->offset_volt_h;
else
asv_volt += subsys->offset_volt_l;
return asv_volt;
}
static unsigned int exynos5422_asv_parse_table(unsigned int pkg_id)
{
return (pkg_id >> EXYNOS5422_TABLE_OFFSET) & EXYNOS5422_TABLE_MASK;
}
static bool exynos5422_asv_parse_bin2(unsigned int pkg_id)
{
return (pkg_id >> EXYNOS5422_BIN2_OFFSET) & EXYNOS5422_BIN2_MASK;
}
static bool exynos5422_asv_parse_sg(unsigned int pkg_id)
{
return (pkg_id >> EXYNOS5422_USESG_OFFSET) & EXYNOS5422_USESG_MASK;
}
int exynos5422_asv_init(struct exynos_asv *asv)
{
struct exynos_asv_subsys *subsys;
unsigned int table_index;
unsigned int pkg_id;
bool bin2;
regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PKG_ID, &pkg_id);
if (asv->of_bin == 2) {
bin2 = true;
asv->use_sg = false;
} else {
asv->use_sg = exynos5422_asv_parse_sg(pkg_id);
bin2 = exynos5422_asv_parse_bin2(pkg_id);
}
asv->group = exynos5422_asv_get_group(asv);
asv->table = exynos5422_asv_parse_table(pkg_id);
exynos5422_asv_offset_voltage_setup(asv);
if (bin2) {
table_index = 3;
} else {
if (asv->table == 2 || asv->table == 3)
table_index = asv->table - 1;
else
table_index = 0;
}
subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_ARM];
subsys->cpu_dt_compat = "arm,cortex-a15";
if (bin2)
subsys->table.num_rows = ASV_ARM_BIN2_DVFS_NUM;
else
subsys->table.num_rows = ASV_ARM_DVFS_NUM;
subsys->table.num_cols = ASV_GROUPS_NUM + 1;
subsys->table.buf = (u32 *)asv_arm_table[table_index];
subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_KFC];
subsys->cpu_dt_compat = "arm,cortex-a7";
if (bin2)
subsys->table.num_rows = ASV_KFC_BIN2_DVFS_NUM;
else
subsys->table.num_rows = ASV_KFC_DVFS_NUM;
subsys->table.num_cols = ASV_GROUPS_NUM + 1;
subsys->table.buf = (u32 *)asv_kfc_table[table_index];
asv->opp_get_voltage = exynos5422_asv_opp_get_voltage;
return 0;
}
EXPORT_SYMBOL_GPL(exynos5422_asv_init);
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* Samsung Exynos 5422 SoC Adaptive Supply Voltage support
*/
#ifndef __LINUX_SOC_EXYNOS5422_ASV_H
#define __LINUX_SOC_EXYNOS5422_ASV_H
#include <linux/errno.h>
enum {
EXYNOS_ASV_SUBSYS_ID_ARM,
EXYNOS_ASV_SUBSYS_ID_KFC,
EXYNOS_ASV_SUBSYS_ID_MAX
};
struct exynos_asv;
#ifdef CONFIG_EXYNOS_ASV_ARM
int exynos5422_asv_init(struct exynos_asv *asv);
#else
static inline int exynos5422_asv_init(struct exynos_asv *asv)
{
return -ENOTSUPP;
}
#endif
#endif /* __LINUX_SOC_EXYNOS5422_ASV_H */
+446
View File
@@ -0,0 +1,446 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2025 Linaro Ltd.
*
* GS101 PMU (Power Management Unit) support
*/
#include <linux/arm-smccc.h>
#include <linux/array_size.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include <linux/soc/samsung/exynos-regs-pmu.h>
#include <linux/regmap.h>
#include "exynos-pmu.h"
#define PMUALIVE_MASK GENMASK(13, 0)
#define TENSOR_SET_BITS (BIT(15) | BIT(14))
#define TENSOR_CLR_BITS BIT(15)
#define TENSOR_SMC_PMU_SEC_REG 0x82000504
#define TENSOR_PMUREG_READ 0
#define TENSOR_PMUREG_WRITE 1
#define TENSOR_PMUREG_RMW 2
static const struct regmap_range gs101_pmu_registers[] = {
regmap_reg_range(GS101_OM_STAT, GS101_SYSTEM_INFO),
regmap_reg_range(GS101_IDLE_IP(0), GS101_IDLE_IP_MASK(3)),
regmap_reg_range(GS101_DATARAM_STATE_SLC_CH(0),
GS101_PPMPURAM_INFORM_SCL_CH(3)),
regmap_reg_range(GS101_INFORM0, GS101_SYSIP_DAT(0)),
/* skip SYSIP_DAT1 SYSIP_DAT2 */
regmap_reg_range(GS101_SYSIP_DAT(3), GS101_PWR_HOLD_SW_TRIP),
regmap_reg_range(GS101_GSA_INFORM(0), GS101_GSA_INFORM(1)),
regmap_reg_range(GS101_INFORM4, GS101_IROM_INFORM),
regmap_reg_range(GS101_IROM_CPU_INFORM(0), GS101_IROM_CPU_INFORM(7)),
regmap_reg_range(GS101_PMU_SPARE(0), GS101_PMU_SPARE(3)),
/* skip most IROM_xxx registers */
regmap_reg_range(GS101_DREX_CALIBRATION(0), GS101_DREX_CALIBRATION(7)),
#define CLUSTER_CPU_RANGE(cl, cpu) \
regmap_reg_range(GS101_CLUSTER_CPU_CONFIGURATION(cl, cpu), \
GS101_CLUSTER_CPU_OPTION(cl, cpu)), \
regmap_reg_range(GS101_CLUSTER_CPU_OUT(cl, cpu), \
GS101_CLUSTER_CPU_IN(cl, cpu)), \
regmap_reg_range(GS101_CLUSTER_CPU_INT_IN(cl, cpu), \
GS101_CLUSTER_CPU_INT_DIR(cl, cpu))
/* cluster 0..2 and cpu 0..4 or 0..1 */
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 0),
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 1),
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 2),
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 3),
CLUSTER_CPU_RANGE(GS101_CLUSTER1_OFFSET, 0),
CLUSTER_CPU_RANGE(GS101_CLUSTER1_OFFSET, 1),
CLUSTER_CPU_RANGE(GS101_CLUSTER2_OFFSET, 0),
CLUSTER_CPU_RANGE(GS101_CLUSTER2_OFFSET, 1),
#undef CLUSTER_CPU_RANGE
#define CLUSTER_NONCPU_RANGE(cl) \
regmap_reg_range(GS101_CLUSTER_NONCPU_CONFIGURATION(cl), \
GS101_CLUSTER_NONCPU_OPTION(cl)), \
regmap_reg_range(GS101_CLUSTER_NONCPU_OUT(cl), \
GS101_CLUSTER_NONCPU_IN(cl)), \
regmap_reg_range(GS101_CLUSTER_NONCPU_INT_IN(cl), \
GS101_CLUSTER_NONCPU_INT_DIR(cl)), \
regmap_reg_range(GS101_CLUSTER_NONCPU_DUALRAIL_CTRL_OUT(cl), \
GS101_CLUSTER_NONCPU_DUALRAIL_POS_OUT(cl)), \
regmap_reg_range(GS101_CLUSTER_NONCPU_DUALRAIL_CTRL_IN(cl), \
GS101_CLUSTER_NONCPU_DUALRAIL_CTRL_IN(cl))
CLUSTER_NONCPU_RANGE(0),
regmap_reg_range(GS101_CLUSTER0_NONCPU_DSU_PCH,
GS101_CLUSTER0_NONCPU_DSU_PCH),
CLUSTER_NONCPU_RANGE(1),
CLUSTER_NONCPU_RANGE(2),
#undef CLUSTER_NONCPU_RANGE
#define SUBBLK_RANGE(blk) \
regmap_reg_range(GS101_SUBBLK_CONFIGURATION(blk), \
GS101_SUBBLK_CTRL(blk)), \
regmap_reg_range(GS101_SUBBLK_OUT(blk), GS101_SUBBLK_IN(blk)), \
regmap_reg_range(GS101_SUBBLK_INT_IN(blk), \
GS101_SUBBLK_INT_DIR(blk)), \
regmap_reg_range(GS101_SUBBLK_MEMORY_OUT(blk), \
GS101_SUBBLK_MEMORY_IN(blk))
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_ALIVE),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_AOC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_APM),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CMU),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BUS0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BUS1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BUS2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CORE),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_EH),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CPUCL0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CPUCL1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CPUCL2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_G3D),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_EMBEDDED_CPUCL0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_EMBEDDED_G3D),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_HSI0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_HSI1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_HSI2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_DPU),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_DISP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_G2D),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MFC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CSIS),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_PDP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_DNS),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_G3AA),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_IPP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_ITP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MCSC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_GDC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_TNR),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BO),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_TPU),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF3),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MISC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_PERIC0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_PERIC1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_S2D),
#undef SUBBLK_RANGE
#define SUBBLK_CPU_RANGE(blk) \
regmap_reg_range(GS101_SUBBLK_CPU_CONFIGURATION(blk), \
GS101_SUBBLK_CPU_OPTION(blk)), \
regmap_reg_range(GS101_SUBBLK_CPU_OUT(blk), \
GS101_SUBBLK_CPU_IN(blk)), \
regmap_reg_range(GS101_SUBBLK_CPU_INT_IN(blk), \
GS101_SUBBLK_CPU_INT_DIR(blk))
SUBBLK_CPU_RANGE(GS101_SUBBBLK_CPU_OFFSET_APM),
SUBBLK_CPU_RANGE(GS101_SUBBBLK_CPU_OFFSET_DBGCORE),
SUBBLK_CPU_RANGE(GS101_SUBBBLK_CPU_OFFSET_SSS),
#undef SUBBLK_CPU_RANGE
regmap_reg_range(GS101_MIF_CONFIGURATION, GS101_MIF_CTRL),
regmap_reg_range(GS101_MIF_OUT, GS101_MIF_IN),
regmap_reg_range(GS101_MIF_INT_IN, GS101_MIF_INT_DIR),
regmap_reg_range(GS101_TOP_CONFIGURATION, GS101_TOP_OPTION),
regmap_reg_range(GS101_TOP_OUT, GS101_TOP_IN),
regmap_reg_range(GS101_TOP_INT_IN, GS101_WAKEUP2_STAT),
regmap_reg_range(GS101_WAKEUP2_INT_IN, GS101_WAKEUP2_INT_DIR),
regmap_reg_range(GS101_SYSTEM_CONFIGURATION, GS101_USER_DEFINED_OUT),
regmap_reg_range(GS101_SYSTEM_OUT, GS101_SYSTEM_IN),
regmap_reg_range(GS101_SYSTEM_INT_IN, GS101_EINT_WAKEUP_MASK3),
regmap_reg_range(GS101_USER_DEFINED_INT_IN, GS101_SCAN2DRAM_INT_DIR),
/* skip HCU_START */
regmap_reg_range(GS101_CUSTOM_OUT, GS101_CUSTOM_IN),
regmap_reg_range(GS101_CUSTOM_INT_IN, GS101_CUSTOM_INT_DIR),
regmap_reg_range(GS101_ACK_LAST_CPU, GS101_HCU_R(3)),
regmap_reg_range(GS101_HCU_SP, GS101_HCU_PC),
/* skip PMU_RAM_CTRL */
regmap_reg_range(GS101_APM_HCU_CTRL, GS101_APM_HCU_CTRL),
regmap_reg_range(GS101_APM_NMI_ENABLE, GS101_RST_STAT_PMU),
regmap_reg_range(GS101_HPM_INT_IN, GS101_BOOT_STAT),
regmap_reg_range(GS101_PMLINK_OUT, GS101_PMLINK_AOC_CTRL),
regmap_reg_range(GS101_TCXO_BUF_CTRL, GS101_ADD_CTRL),
regmap_reg_range(GS101_HCU_TIMEOUT_RESET, GS101_HCU_TIMEOUT_SCAN2DRAM),
regmap_reg_range(GS101_TIMER(0), GS101_TIMER(3)),
regmap_reg_range(GS101_PPC_MIF(0), GS101_PPC_EH),
/* PPC_OFFSET, skip PPC_CPUCL1_0 PPC_CPUCL1_1 */
regmap_reg_range(GS101_EXT_REGULATOR_MIF_DURATION, GS101_TCXO_DURATION),
regmap_reg_range(GS101_BURNIN_CTRL, GS101_TMU_SUB_TRIP),
regmap_reg_range(GS101_MEMORY_CEN, GS101_MEMORY_SMX_FEEDBACK),
regmap_reg_range(GS101_SLC_PCH_CHANNEL, GS101_SLC_PCH_CB),
regmap_reg_range(GS101_FORCE_NOMC, GS101_FORCE_NOMC),
regmap_reg_range(GS101_FORCE_BOOST, GS101_PMLINK_SLC_BUSY),
regmap_reg_range(GS101_BOOTSYNC_OUT, GS101_CTRL_SECJTAG_ALIVE),
regmap_reg_range(GS101_CTRL_DIV_PLL_ALV_DIVLOW, GS101_CTRL_CLKDIV__CLKRTC),
regmap_reg_range(GS101_CTRL_SOC32K, GS101_CTRL_SBU_SW_EN),
regmap_reg_range(GS101_PAD_CTRL_CLKOUT0, GS101_PAD_CTRL_WRESETO_n),
regmap_reg_range(GS101_PHY_CTRL_USB20, GS101_PHY_CTRL_UFS),
};
static const struct regmap_range gs101_pmu_ro_registers[] = {
regmap_reg_range(GS101_OM_STAT, GS101_VERSION),
regmap_reg_range(GS101_OTP_STATUS, GS101_OTP_STATUS),
regmap_reg_range(GS101_DATARAM_STATE_SLC_CH(0),
GS101_PPMPURAM_STATE_SLC_CH(0)),
regmap_reg_range(GS101_DATARAM_STATE_SLC_CH(1),
GS101_PPMPURAM_STATE_SLC_CH(1)),
regmap_reg_range(GS101_DATARAM_STATE_SLC_CH(2),
GS101_PPMPURAM_STATE_SLC_CH(2)),
regmap_reg_range(GS101_DATARAM_STATE_SLC_CH(3),
GS101_PPMPURAM_STATE_SLC_CH(3)),
#define CLUSTER_CPU_RANGE(cl, cpu) \
regmap_reg_range(GS101_CLUSTER_CPU_IN(cl, cpu), \
GS101_CLUSTER_CPU_IN(cl, cpu)), \
regmap_reg_range(GS101_CLUSTER_CPU_INT_IN(cl, cpu), \
GS101_CLUSTER_CPU_INT_IN(cl, cpu))
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 0),
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 1),
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 2),
CLUSTER_CPU_RANGE(GS101_CLUSTER0_OFFSET, 3),
CLUSTER_CPU_RANGE(GS101_CLUSTER1_OFFSET, 0),
CLUSTER_CPU_RANGE(GS101_CLUSTER1_OFFSET, 1),
CLUSTER_CPU_RANGE(GS101_CLUSTER2_OFFSET, 0),
CLUSTER_CPU_RANGE(GS101_CLUSTER2_OFFSET, 1),
#undef CLUSTER_CPU_RANGE
#define CLUSTER_NONCPU_RANGE(cl) \
regmap_reg_range(GS101_CLUSTER_NONCPU_IN(cl), \
GS101_CLUSTER_NONCPU_IN(cl)), \
regmap_reg_range(GS101_CLUSTER_NONCPU_INT_IN(cl), \
GS101_CLUSTER_NONCPU_INT_IN(cl)), \
regmap_reg_range(GS101_CLUSTER_NONCPU_DUALRAIL_CTRL_IN(cl), \
GS101_CLUSTER_NONCPU_DUALRAIL_CTRL_IN(cl))
CLUSTER_NONCPU_RANGE(0),
CLUSTER_NONCPU_RANGE(1),
CLUSTER_NONCPU_RANGE(2),
regmap_reg_range(GS101_CLUSTER_NONCPU_INT_EN(2),
GS101_CLUSTER_NONCPU_INT_DIR(2)),
#undef CLUSTER_NONCPU_RANGE
#define SUBBLK_RANGE(blk) \
regmap_reg_range(GS101_SUBBLK_IN(blk), GS101_SUBBLK_IN(blk)), \
regmap_reg_range(GS101_SUBBLK_INT_IN(blk), \
GS101_SUBBLK_INT_IN(blk)), \
regmap_reg_range(GS101_SUBBLK_MEMORY_IN(blk), \
GS101_SUBBLK_MEMORY_IN(blk))
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_ALIVE),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_AOC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_APM),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CMU),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BUS0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BUS1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BUS2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CORE),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_EH),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CPUCL0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CPUCL1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CPUCL2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_G3D),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_EMBEDDED_CPUCL0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_EMBEDDED_G3D),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_HSI0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_HSI1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_HSI2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_DPU),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_DISP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_G2D),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MFC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_CSIS),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_PDP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_DNS),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_G3AA),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_IPP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_ITP),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MCSC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_GDC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_TNR),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_BO),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_TPU),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF2),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MIF3),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_MISC),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_PERIC0),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_PERIC1),
SUBBLK_RANGE(GS101_SUBBBLK_OFFSET_S2D),
#undef SUBBLK_RANGE
#define SUBBLK_CPU_RANGE(blk) \
regmap_reg_range(GS101_SUBBLK_CPU_IN(blk), \
GS101_SUBBLK_CPU_IN(blk)), \
regmap_reg_range(GS101_SUBBLK_CPU_INT_IN(blk), \
GS101_SUBBLK_CPU_INT_IN(blk))
SUBBLK_CPU_RANGE(GS101_SUBBBLK_CPU_OFFSET_APM),
SUBBLK_CPU_RANGE(GS101_SUBBBLK_CPU_OFFSET_DBGCORE),
SUBBLK_CPU_RANGE(GS101_SUBBBLK_CPU_OFFSET_SSS),
#undef SUBBLK_CPU_RANGE
regmap_reg_range(GS101_MIF_CONFIGURATION, GS101_MIF_CONFIGURATION),
regmap_reg_range(GS101_MIF_IN, GS101_MIF_IN),
regmap_reg_range(GS101_MIF_INT_IN, GS101_MIF_INT_IN),
regmap_reg_range(GS101_TOP_IN, GS101_TOP_IN),
regmap_reg_range(GS101_TOP_INT_IN, GS101_TOP_INT_IN),
regmap_reg_range(GS101_WAKEUP2_INT_IN, GS101_WAKEUP2_INT_IN),
regmap_reg_range(GS101_SYSTEM_IN, GS101_SYSTEM_IN),
regmap_reg_range(GS101_SYSTEM_INT_IN, GS101_SYSTEM_INT_IN),
regmap_reg_range(GS101_EINT_INT_IN, GS101_EINT_INT_IN),
regmap_reg_range(GS101_EINT2_INT_IN, GS101_EINT2_INT_IN),
regmap_reg_range(GS101_EINT3_INT_IN, GS101_EINT3_INT_IN),
regmap_reg_range(GS101_USER_DEFINED_INT_IN, GS101_USER_DEFINED_INT_IN),
regmap_reg_range(GS101_SCAN2DRAM_INT_IN, GS101_SCAN2DRAM_INT_IN),
regmap_reg_range(GS101_CUSTOM_IN, GS101_CUSTOM_IN),
regmap_reg_range(GS101_CUSTOM_INT_IN, GS101_CUSTOM_INT_IN),
regmap_reg_range(GS101_HCU_R(0), GS101_HCU_R(3)),
regmap_reg_range(GS101_HCU_SP, GS101_HCU_PC),
regmap_reg_range(GS101_NMI_SRC_IN, GS101_NMI_SRC_IN),
regmap_reg_range(GS101_HPM_INT_IN, GS101_HPM_INT_IN),
regmap_reg_range(GS101_MEMORY_PGEN_FEEDBACK, GS101_MEMORY_PGEN_FEEDBACK),
regmap_reg_range(GS101_MEMORY_SMX_FEEDBACK, GS101_MEMORY_SMX_FEEDBACK),
regmap_reg_range(GS101_PMLINK_SLC_ACK, GS101_PMLINK_SLC_BUSY),
regmap_reg_range(GS101_BOOTSYNC_IN, GS101_BOOTSYNC_IN),
regmap_reg_range(GS101_SCAN_READY_IN, GS101_SCAN_READY_IN),
regmap_reg_range(GS101_CTRL_PLL_ALV_LOCK, GS101_CTRL_PLL_ALV_LOCK),
};
static const struct regmap_access_table gs101_pmu_rd_table = {
.yes_ranges = gs101_pmu_registers,
.n_yes_ranges = ARRAY_SIZE(gs101_pmu_registers),
};
static const struct regmap_access_table gs101_pmu_wr_table = {
.yes_ranges = gs101_pmu_registers,
.n_yes_ranges = ARRAY_SIZE(gs101_pmu_registers),
.no_ranges = gs101_pmu_ro_registers,
.n_no_ranges = ARRAY_SIZE(gs101_pmu_ro_registers),
};
const struct exynos_pmu_data gs101_pmu_data = {
.pmu_secure = true,
.pmu_cpuhp = true,
.rd_table = &gs101_pmu_rd_table,
.wr_table = &gs101_pmu_wr_table,
};
/*
* Tensor SoCs are configured so that PMU_ALIVE registers can only be written
* from EL3, but are still read accessible. As Linux needs to write some of
* these registers, the following functions are provided and exposed via
* regmap.
*
* Note: This SMC interface is known to be implemented on gs101 and derivative
* SoCs.
*/
/* Write to a protected PMU register. */
int tensor_sec_reg_write(void *context, unsigned int reg, unsigned int val)
{
struct arm_smccc_res res;
unsigned long pmu_base = (unsigned long)context;
arm_smccc_smc(TENSOR_SMC_PMU_SEC_REG, pmu_base + reg,
TENSOR_PMUREG_WRITE, val, 0, 0, 0, 0, &res);
/* returns -EINVAL if access isn't allowed or 0 */
if (res.a0)
pr_warn("%s(): SMC failed: %d\n", __func__, (int)res.a0);
return (int)res.a0;
}
/* Read/Modify/Write a protected PMU register. */
static int tensor_sec_reg_rmw(void *context, unsigned int reg,
unsigned int mask, unsigned int val)
{
struct arm_smccc_res res;
unsigned long pmu_base = (unsigned long)context;
arm_smccc_smc(TENSOR_SMC_PMU_SEC_REG, pmu_base + reg,
TENSOR_PMUREG_RMW, mask, val, 0, 0, 0, &res);
/* returns -EINVAL if access isn't allowed or 0 */
if (res.a0)
pr_warn("%s(): SMC failed: %d\n", __func__, (int)res.a0);
return (int)res.a0;
}
/*
* Read a protected PMU register. All PMU registers can be read by Linux.
* Note: The SMC read register is not used, as only registers that can be
* written are readable via SMC.
*/
int tensor_sec_reg_read(void *context, unsigned int reg, unsigned int *val)
{
*val = pmu_raw_readl(reg);
return 0;
}
/*
* For SoCs that have set/clear bit hardware this function can be used when
* the PMU register will be accessed by multiple masters.
*
* For example, to set bits 13:8 in PMU reg offset 0x3e80
* tensor_set_bits_atomic(ctx, 0x3e80, 0x3f00, 0x3f00);
*
* Set bit 8, and clear bits 13:9 PMU reg offset 0x3e80
* tensor_set_bits_atomic(0x3e80, 0x100, 0x3f00);
*/
static int tensor_set_bits_atomic(void *context, unsigned int offset, u32 val,
u32 mask)
{
int ret;
unsigned int i;
for (i = 0; i < 32; i++) {
if (!(mask & BIT(i)))
continue;
offset &= ~TENSOR_SET_BITS;
if (val & BIT(i))
offset |= TENSOR_SET_BITS;
else
offset |= TENSOR_CLR_BITS;
ret = tensor_sec_reg_write(context, offset, i);
if (ret)
return ret;
}
return 0;
}
static bool tensor_is_atomic(unsigned int reg)
{
/*
* Use atomic operations for PMU_ALIVE registers (offset 0~0x3FFF)
* as the target registers can be accessed by multiple masters. SFRs
* that don't support atomic are added to the switch statement below.
*/
if (reg > PMUALIVE_MASK)
return false;
switch (reg) {
case GS101_SYSIP_DAT(0):
case GS101_SYSTEM_CONFIGURATION:
return false;
default:
return true;
}
}
int tensor_sec_update_bits(void *context, unsigned int reg, unsigned int mask,
unsigned int val)
{
if (!tensor_is_atomic(reg))
return tensor_sec_reg_rmw(context, reg, mask, val);
return tensor_set_bits_atomic(context, reg, val, mask);
}
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0
//
// originally in linux/arch/arm/plat-s3c24xx/pm.c
//
// Copyright (c) 2004-2008 Simtec Electronics
// http://armlinux.simtec.co.uk
// Ben Dooks <ben@simtec.co.uk>
//
// S3C Power Mangament - suspend/resume memory corruption check.
#include <linux/kernel.h>
#include <linux/suspend.h>
#include <linux/init.h>
#include <linux/crc32.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/soc/samsung/s3c-pm.h>
#if CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE < 1
#error CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE must be a positive non-zero value
#endif
/* suspend checking code...
*
* this next area does a set of crc checks over all the installed
* memory, so the system can verify if the resume was ok.
*
* CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE defines the block-size for the CRC,
* increasing it will mean that the area corrupted will be less easy to spot,
* and reducing the size will cause the CRC save area to grow
*/
#define CHECK_CHUNKSIZE (CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE * 1024)
static u32 crc_size; /* size needed for the crc block */
static u32 *crcs; /* allocated over suspend/resume */
typedef u32 *(run_fn_t)(struct resource *ptr, u32 *arg);
/* s3c_pm_run_res
*
* go through the given resource list, and look for system ram
*/
static void s3c_pm_run_res(struct resource *ptr, run_fn_t fn, u32 *arg)
{
while (ptr != NULL) {
if (ptr->child != NULL)
s3c_pm_run_res(ptr->child, fn, arg);
if ((ptr->flags & IORESOURCE_SYSTEM_RAM)
== IORESOURCE_SYSTEM_RAM) {
S3C_PMDBG("Found system RAM at %08lx..%08lx\n",
(unsigned long)ptr->start,
(unsigned long)ptr->end);
arg = (fn)(ptr, arg);
}
ptr = ptr->sibling;
}
}
static void s3c_pm_run_sysram(run_fn_t fn, u32 *arg)
{
s3c_pm_run_res(&iomem_resource, fn, arg);
}
static u32 *s3c_pm_countram(struct resource *res, u32 *val)
{
u32 size = (u32)resource_size(res);
size += CHECK_CHUNKSIZE-1;
size /= CHECK_CHUNKSIZE;
S3C_PMDBG("Area %08lx..%08lx, %d blocks\n",
(unsigned long)res->start, (unsigned long)res->end, size);
*val += size * sizeof(u32);
return val;
}
/* s3c_pm_prepare_check
*
* prepare the necessary information for creating the CRCs. This
* must be done before the final save, as it will require memory
* allocating, and thus touching bits of the kernel we do not
* know about.
*/
void s3c_pm_check_prepare(void)
{
crc_size = 0;
s3c_pm_run_sysram(s3c_pm_countram, &crc_size);
S3C_PMDBG("s3c_pm_prepare_check: %u checks needed\n", crc_size);
crcs = kmalloc(crc_size+4, GFP_KERNEL);
if (crcs == NULL)
printk(KERN_ERR "Cannot allocated CRC save area\n");
}
static u32 *s3c_pm_makecheck(struct resource *res, u32 *val)
{
unsigned long addr, left;
for (addr = res->start; addr < res->end;
addr += CHECK_CHUNKSIZE) {
left = res->end - addr;
if (left > CHECK_CHUNKSIZE)
left = CHECK_CHUNKSIZE;
*val = crc32_le(~0, phys_to_virt(addr), left);
val++;
}
return val;
}
/* s3c_pm_check_store
*
* compute the CRC values for the memory blocks before the final
* sleep.
*/
void s3c_pm_check_store(void)
{
if (crcs != NULL)
s3c_pm_run_sysram(s3c_pm_makecheck, crcs);
}
/* in_region
*
* return TRUE if the area defined by ptr..ptr+size contains the
* what..what+whatsz
*/
static inline int in_region(void *ptr, int size, void *what, size_t whatsz)
{
if ((what+whatsz) < ptr)
return 0;
if (what > (ptr+size))
return 0;
return 1;
}
/**
* s3c_pm_runcheck() - helper to check a resource on restore.
* @res: The resource to check
* @val: Pointer to list of CRC32 values to check.
*
* Called from the s3c_pm_check_restore() via s3c_pm_run_sysram(), this
* function runs the given memory resource checking it against the stored
* CRC to ensure that memory is restored. The function tries to skip as
* many of the areas used during the suspend process.
*/
static u32 *s3c_pm_runcheck(struct resource *res, u32 *val)
{
unsigned long addr;
unsigned long left;
void *stkpage;
void *ptr;
u32 calc;
stkpage = (void *)((u32)&calc & ~PAGE_MASK);
for (addr = res->start; addr < res->end;
addr += CHECK_CHUNKSIZE) {
left = res->end - addr;
if (left > CHECK_CHUNKSIZE)
left = CHECK_CHUNKSIZE;
ptr = phys_to_virt(addr);
if (in_region(ptr, left, stkpage, 4096)) {
S3C_PMDBG("skipping %08lx, has stack in\n", addr);
goto skip_check;
}
if (in_region(ptr, left, crcs, crc_size)) {
S3C_PMDBG("skipping %08lx, has crc block in\n", addr);
goto skip_check;
}
/* calculate and check the checksum */
calc = crc32_le(~0, ptr, left);
if (calc != *val) {
printk(KERN_ERR "Restore CRC error at "
"%08lx (%08x vs %08x)\n", addr, calc, *val);
S3C_PMDBG("Restore CRC error at %08lx (%08x vs %08x)\n",
addr, calc, *val);
}
skip_check:
val++;
}
return val;
}
/**
* s3c_pm_check_restore() - memory check called on resume
*
* check the CRCs after the restore event and free the memory used
* to hold them
*/
void s3c_pm_check_restore(void)
{
if (crcs != NULL)
s3c_pm_run_sysram(s3c_pm_runcheck, crcs);
}
/**
* s3c_pm_check_cleanup() - free memory resources
*
* Free the resources that where allocated by the suspend
* memory check code. We do this separately from the
* s3c_pm_check_restore() function as we cannot call any
* functions that might sleep during that resume.
*/
void s3c_pm_check_cleanup(void)
{
kfree(crcs);
crcs = NULL;
}