#include <linux/bitfield.h>
#include <linux/pci.h>
#include <linux/platform_profile.h>
#include "processor_thermal_device.h"
#define SOC_POWER_SLIDER_OFFSET 0x5B38
enum power_slider_preference {
SOC_POWER_SLIDER_PERFORMANCE,
SOC_POWER_SLIDER_BALANCE,
SOC_POWER_SLIDER_POWERSAVE,
};
#define SOC_SLIDER_VALUE_MINIMUM 0x00
#define SOC_SLIDER_VALUE_BALANCE 0x03
#define SOC_SLIDER_VALUE_MAXIMUM 0x06
#define SLIDER_MASK GENMASK_ULL(2, 0)
#define SLIDER_ENABLE_BIT 7
static u8 slider_values[] = {
[SOC_POWER_SLIDER_PERFORMANCE] = SOC_SLIDER_VALUE_MINIMUM,
[SOC_POWER_SLIDER_BALANCE] = SOC_SLIDER_VALUE_BALANCE,
[SOC_POWER_SLIDER_POWERSAVE] = SOC_SLIDER_VALUE_MAXIMUM,
};
static DEFINE_MUTEX(slider_param_lock);
static int slider_balanced_param = SOC_SLIDER_VALUE_BALANCE;
static int slider_def_balance_set(const char *arg, const struct kernel_param *kp)
{
u8 slider_val;
int ret;
guard(mutex)(&slider_param_lock);
ret = kstrtou8(arg, 16, &slider_val);
if (!ret) {
if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] ||
slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE])
return -EINVAL;
slider_balanced_param = slider_val;
}
return ret;
}
static int slider_def_balance_get(char *buf, const struct kernel_param *kp)
{
guard(mutex)(&slider_param_lock);
return sysfs_emit(buf, "%02x\n", slider_values[SOC_POWER_SLIDER_BALANCE]);
}
static const struct kernel_param_ops slider_def_balance_ops = {
.set = slider_def_balance_set,
.get = slider_def_balance_get,
};
module_param_cb(slider_balance, &slider_def_balance_ops, NULL, 0644);
MODULE_PARM_DESC(slider_balance, "Set slider default value for balance");
static u8 slider_offset;
static int slider_def_offset_set(const char *arg, const struct kernel_param *kp)
{
u8 offset;
int ret;
guard(mutex)(&slider_param_lock);
ret = kstrtou8(arg, 16, &offset);
if (!ret) {
if (offset > SOC_SLIDER_VALUE_MAXIMUM)
return -EINVAL;
slider_offset = offset;
}
return ret;
}
static int slider_def_offset_get(char *buf, const struct kernel_param *kp)
{
guard(mutex)(&slider_param_lock);
return sysfs_emit(buf, "%02x\n", slider_offset);
}
static const struct kernel_param_ops slider_offset_ops = {
.set = slider_def_offset_set,
.get = slider_def_offset_get,
};
module_param_cb(slider_offset, &slider_offset_ops, NULL, 0644);
MODULE_PARM_DESC(slider_offset, "Set slider offset");
static int convert_profile_to_power_slider(enum platform_profile_option profile)
{
switch (profile) {
case PLATFORM_PROFILE_LOW_POWER:
return slider_values[SOC_POWER_SLIDER_POWERSAVE];
case PLATFORM_PROFILE_BALANCED:
return slider_values[SOC_POWER_SLIDER_BALANCE];
case PLATFORM_PROFILE_PERFORMANCE:
return slider_values[SOC_POWER_SLIDER_PERFORMANCE];
default:
break;
}
return -EOPNOTSUPP;
}
static int convert_power_slider_to_profile(u8 slider)
{
if (slider == slider_values[SOC_POWER_SLIDER_PERFORMANCE])
return PLATFORM_PROFILE_PERFORMANCE;
if (slider == slider_values[SOC_POWER_SLIDER_BALANCE])
return PLATFORM_PROFILE_BALANCED;
if (slider == slider_values[SOC_POWER_SLIDER_POWERSAVE])
return PLATFORM_PROFILE_LOW_POWER;
return -EOPNOTSUPP;
}
static inline u64 read_soc_slider(struct proc_thermal_device *proc_priv)
{
return readq(proc_priv->mmio_base + SOC_POWER_SLIDER_OFFSET);
}
static inline void write_soc_slider(struct proc_thermal_device *proc_priv, u64 val)
{
writeq(val, proc_priv->mmio_base + SOC_POWER_SLIDER_OFFSET);
}
#define SLIDER_OFFSET_MASK GENMASK_ULL(6, 4)
static void set_soc_power_profile(struct proc_thermal_device *proc_priv, int slider)
{
u8 offset;
u64 val;
val = read_soc_slider(proc_priv);
val &= ~SLIDER_MASK;
val |= FIELD_PREP(SLIDER_MASK, slider) | BIT(SLIDER_ENABLE_BIT);
if (slider == SOC_SLIDER_VALUE_MINIMUM || slider == SOC_SLIDER_VALUE_MAXIMUM)
offset = 0;
else
offset = slider_offset;
val &= ~SLIDER_OFFSET_MASK;
val |= FIELD_PREP(SLIDER_OFFSET_MASK, offset);
write_soc_slider(proc_priv, val);
}
static int power_slider_platform_profile_set(struct device *dev,
enum platform_profile_option profile)
{
struct proc_thermal_device *proc_priv;
int slider;
proc_priv = dev_get_drvdata(dev);
if (!proc_priv)
return -EOPNOTSUPP;
guard(mutex)(&slider_param_lock);
slider_values[SOC_POWER_SLIDER_BALANCE] = slider_balanced_param;
slider = convert_profile_to_power_slider(profile);
if (slider < 0)
return slider;
set_soc_power_profile(proc_priv, slider);
return 0;
}
static int power_slider_platform_profile_get(struct device *dev,
enum platform_profile_option *profile)
{
struct proc_thermal_device *proc_priv;
int slider, ret;
u64 val;
proc_priv = dev_get_drvdata(dev);
if (!proc_priv)
return -EOPNOTSUPP;
val = read_soc_slider(proc_priv);
slider = FIELD_GET(SLIDER_MASK, val);
ret = convert_power_slider_to_profile(slider);
if (ret < 0)
return ret;
*profile = ret;
return 0;
}
static int power_slider_platform_profile_probe(void *drvdata, unsigned long *choices)
{
set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
set_bit(PLATFORM_PROFILE_BALANCED, choices);
set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
return 0;
}
static const struct platform_profile_ops power_slider_platform_profile_ops = {
.probe = power_slider_platform_profile_probe,
.profile_get = power_slider_platform_profile_get,
.profile_set = power_slider_platform_profile_set,
};
int proc_thermal_soc_power_slider_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
{
struct device *ppdev;
set_soc_power_profile(proc_priv, slider_values[SOC_POWER_SLIDER_BALANCE]);
ppdev = devm_platform_profile_register(&pdev->dev, "SoC Power Slider", proc_priv,
&power_slider_platform_profile_ops);
return PTR_ERR_OR_ZERO(ppdev);
}
EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_add, "INT340X_THERMAL");
static u64 soc_slider_save;
void proc_thermal_soc_power_slider_suspend(struct proc_thermal_device *proc_priv)
{
soc_slider_save = read_soc_slider(proc_priv);
}
EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_suspend, "INT340X_THERMAL");
void proc_thermal_soc_power_slider_resume(struct proc_thermal_device *proc_priv)
{
write_soc_slider(proc_priv, soc_slider_save);
}
EXPORT_SYMBOL_NS_GPL(proc_thermal_soc_power_slider_resume, "INT340X_THERMAL");
MODULE_IMPORT_NS("INT340X_THERMAL");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Processor Thermal Power Slider Interface");