1
0
Fork 0

MLK-16689-03 driver: soc: Add busfreq driver for imx8mq

Add busfreq driver support on i.MX8MQ. The busfreq driver is
mainly used for dynamic DDR frequency change for power saving
feature. When there is no peripheral or DMA device has direct
access to DDR memory, we can lower the DDR frequency to save
power. Currently, we support frequency setpoint for LPDDR4:

    (1): 3200mts, the DDRC core clock is sourced from 800MHz
         dram_pll, the DDRC apb clock is 200MHz.

    (2): 400mts, the DDRC core clock is source from sys1_pll_400m,
         the DDRC apb clock is is sourced from sys1_pll_40m.

    (3): 100mts, the DDRC core clock is sourced from sys1_pll_100m,
         the DDRC apb clock is sourced from sys1_pll_40m.

In our busfreq driver, we have three mode supported:
    * high bus mode  <-----> 3200mts;
    * audio bus mode <-----> 400mts;
    * low bus mode   <-----> 100mts;

The actual DDR frequency is done in ARM trusted firmware by calling
the SMCC SiP service call.

    BuildInfo:
     - IMX-MKIMAGE: 05d3d4a7d7, ATF: 724cc2b890
     - SPL/Uboot: f72c10d2db;

Signed-off-by: Bai Ping <ping.bai@nxp.com>
Reviewed-by: Anson Huang <Anson.Huang@nxp.com>
pull/10/head
Bai Ping 2017-10-30 16:27:02 +08:00 committed by Jason Liu
parent 59086f5a97
commit 3fe4f63ff7
4 changed files with 559 additions and 1 deletions

View File

@ -3,5 +3,5 @@ obj-$(CONFIG_IMX7_PM_DOMAINS) += gpcv2.o
obj-$(CONFIG_HAVE_IMX_MU) += mu/
obj-$(CONFIG_ARCH_FSL_IMX8QM) += sc/
obj-$(CONFIG_ARCH_FSL_IMX8QM) += pm-domains.o
obj-$(CONFIG_ARCH_FSL_IMX8MQ) += gpc-psci.o
obj-$(CONFIG_ARCH_FSL_IMX8MQ) += busfreq-imx8mq.o gpc-psci.o
obj-$(CONFIG_HAVE_IMX8_SOC) += soc-imx8.o

View File

@ -0,0 +1,552 @@
/*
* Copyright 2017 NXP
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/arm-smccc.h>
#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/cpumask.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/suspend.h>
#include <soc/imx/fsl_sip.h>
#define HIGH_FREQ_3200MTS 0x0
#define AUDIO_FREQ_400MTS 0x1
#define LOW_BUS_FREQ_100MTS 0x2
#define WAIT_BUS_FREQ_DONE 0xf
static struct device *busfreq_dev;
static int low_bus_freq_mode;
static int audio_bus_freq_mode;
static int high_bus_freq_mode;
static int bus_freq_scaling_initialized;
static int bus_freq_scaling_is_active;
static int high_bus_count, audio_bus_count, low_bus_count;
static int cur_bus_freq_mode;
static int busfreq_suspended;
static bool cancel_reduce_bus_freq;
static struct clk *dram_pll_clk;
static struct clk *sys1_pll_800m;
static struct clk *sys1_pll_400m;
static struct clk *sys1_pll_100m;
static struct clk *sys1_pll_40m;
static struct clk *dram_alt_src;
static struct clk *dram_alt_root;
static struct clk *dram_core_clk;
static struct clk *dram_apb_src;
static struct clk *dram_apb_pre_div;
static struct delayed_work low_bus_freq_handler;
static struct delayed_work bus_freq_daemon;
DEFINE_MUTEX(bus_freq_mutex);
static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id)
{
struct arm_smccc_res res;
/* call smc trap to ATF */
arm_smccc_smc(FSL_SIP_DDR_DVFS, WAIT_BUS_FREQ_DONE, 0,
0, 0, 0, 0, 0, &res);
return IRQ_HANDLED;
}
static void update_bus_freq(int target_freq)
{
struct arm_smccc_res res;
u32 online_cpus = 0;
int cpu = 0;
local_irq_disable();
for_each_online_cpu(cpu) {
online_cpus |= (1 << (cpu * 8));
}
/* change the ddr freqency */
arm_smccc_smc(FSL_SIP_DDR_DVFS, target_freq, online_cpus,
0, 0, 0, 0, 0, &res);
local_irq_enable();
}
static void reduce_bus_freq(void)
{
high_bus_freq_mode = 0;
/* prepare the necessary clk before frequency change */
clk_prepare_enable(sys1_pll_40m);
clk_prepare_enable(dram_alt_root);
if (audio_bus_count) {
clk_prepare_enable(sys1_pll_400m);
update_bus_freq(AUDIO_FREQ_400MTS);
/* correct the clock tree info */
clk_disable_unprepare(sys1_pll_400m);
clk_set_parent(dram_alt_src, sys1_pll_400m);
clk_set_parent(dram_core_clk, dram_alt_root);
clk_set_parent(dram_apb_src, sys1_pll_40m);
clk_set_rate(dram_apb_pre_div, 20000000);
low_bus_freq_mode = 0;
audio_bus_freq_mode = 1;
cur_bus_freq_mode = BUS_FREQ_AUDIO;
} else {
clk_prepare_enable(sys1_pll_100m);
update_bus_freq(LOW_BUS_FREQ_100MTS);
/* correct the clock tree info */
clk_disable_unprepare(sys1_pll_100m);
clk_set_parent(dram_alt_src, sys1_pll_100m);
clk_set_parent(dram_core_clk, dram_alt_root);
clk_set_parent(dram_apb_src, sys1_pll_40m);
clk_set_rate(dram_apb_pre_div, 20000000);
low_bus_freq_mode = 1;
audio_bus_freq_mode = 0;
cur_bus_freq_mode = BUS_FREQ_LOW;
}
clk_disable_unprepare(sys1_pll_40m);
clk_disable_unprepare(dram_alt_root);
if (audio_bus_freq_mode)
printk(KERN_DEBUG "ddrc freq set to audio mode: 100MHz\n");
if (low_bus_freq_mode)
printk(KERN_DEBUG "ddrc freq set to low bus mode: 25MHz\n");
}
static void reduce_bus_freq_handler(struct work_struct *work)
{
mutex_lock(&bus_freq_mutex);
if (!cancel_reduce_bus_freq)
reduce_bus_freq();
mutex_unlock(&bus_freq_mutex);
}
static int set_low_bus_freq(void)
{
if (busfreq_suspended)
return 0;
if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active)
return 0;
cancel_reduce_bus_freq = false;
/*
* check to see if we need to got from low bus
* freq mode to audio bus freq mode.
* If so, the change needs to be done immediately.
*/
if (audio_bus_count && low_bus_freq_mode)
reduce_bus_freq();
else
schedule_delayed_work(&low_bus_freq_handler,
usecs_to_jiffies(3000000));
return 0;
}
static inline void cancel_low_bus_freq_handler(void)
{
cancel_delayed_work(&low_bus_freq_handler);
cancel_reduce_bus_freq = true;
}
static int set_high_bus_freq(int high_bus_freq)
{
if (bus_freq_scaling_initialized || bus_freq_scaling_is_active)
cancel_low_bus_freq_handler();
if (busfreq_suspended)
return 0;
if (!bus_freq_scaling_initialized || !bus_freq_scaling_is_active)
return 0;
if (high_bus_freq_mode)
return 0;
/* enable the clks needed in frequency */
clk_prepare_enable(sys1_pll_800m);
clk_prepare_enable(dram_pll_clk);
/* switch the DDR freqeuncy */
update_bus_freq(0x0);
/* correct the clock tree info */
clk_set_parent(dram_apb_src, sys1_pll_800m);
clk_set_rate(dram_apb_pre_div, 200000000);
clk_set_parent(dram_core_clk, dram_pll_clk);
clk_disable_unprepare(sys1_pll_800m);
clk_disable_unprepare(dram_pll_clk);
high_bus_freq_mode = 1;
audio_bus_freq_mode = 0;
low_bus_freq_mode = 0;
cur_bus_freq_mode = BUS_FREQ_HIGH;
if (high_bus_freq_mode)
printk(KERN_DEBUG "ddrc freq set to high mode: 800MHz\n");
return 0;
}
void request_bus_freq(enum bus_freq_mode mode)
{
mutex_lock(&bus_freq_mutex);
if (mode == BUS_FREQ_HIGH)
high_bus_count++;
else if (mode == BUS_FREQ_AUDIO)
audio_bus_count++;
else if (mode == BUS_FREQ_LOW)
low_bus_count++;
if (busfreq_suspended || !bus_freq_scaling_initialized ||
!bus_freq_scaling_is_active) {
mutex_unlock(&bus_freq_mutex);
return;
}
cancel_low_bus_freq_handler();
if ((mode == BUS_FREQ_HIGH) && (!high_bus_freq_mode)) {
set_high_bus_freq(1);
mutex_unlock(&bus_freq_mutex);
return;
}
if ((mode == BUS_FREQ_AUDIO) && (!high_bus_freq_mode) &&
(!audio_bus_freq_mode)) {
set_low_bus_freq();
mutex_unlock(&bus_freq_mutex);
return;
}
mutex_unlock(&bus_freq_mutex);
}
EXPORT_SYMBOL(request_bus_freq);
void release_bus_freq(enum bus_freq_mode mode)
{
mutex_lock(&bus_freq_mutex);
if (mode == BUS_FREQ_HIGH) {
if (high_bus_count == 0) {
dev_err(busfreq_dev, "high bus count mismatch!\n");
dump_stack();
mutex_unlock(&bus_freq_mutex);
return;
}
high_bus_count--;
} else if (mode == BUS_FREQ_AUDIO) {
if (audio_bus_count == 0) {
dev_err(busfreq_dev, "audio bus count mismatch!\n");
dump_stack();
mutex_unlock(&bus_freq_mutex);
return;
}
audio_bus_count--;
} else if (mode == BUS_FREQ_LOW) {
if (low_bus_count == 0) {
dev_err(busfreq_dev, "low bus count mismatch!\n");
dump_stack();
mutex_unlock(&bus_freq_mutex);
return;
}
low_bus_count--;
}
if (busfreq_suspended || !bus_freq_scaling_initialized ||
!bus_freq_scaling_is_active) {
mutex_unlock(&bus_freq_mutex);
return;
}
if ((!audio_bus_freq_mode) && (high_bus_count == 0) &&
(audio_bus_count != 0)) {
set_low_bus_freq();
mutex_unlock(&bus_freq_mutex);
return;
}
if ((!low_bus_freq_mode) && (high_bus_count == 0) &&
(audio_bus_count == 0)) {
set_low_bus_freq();
mutex_unlock(&bus_freq_mutex);
}
mutex_unlock(&bus_freq_mutex);
}
EXPORT_SYMBOL(release_bus_freq);
int get_bus_freq_mode(void)
{
return cur_bus_freq_mode;
}
EXPORT_SYMBOL(get_bus_freq_mode);
static void bus_freq_daemon_handler(struct work_struct *work)
{
mutex_lock(&bus_freq_mutex);
if ((!low_bus_freq_mode) && (high_bus_count == 0) &&
(audio_bus_count == 0))
set_low_bus_freq();
mutex_unlock(&bus_freq_mutex);
}
static ssize_t bus_freq_scaling_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (bus_freq_scaling_is_active)
return sprintf(buf, "Bus frequency scaling is enabled\n");
else
return sprintf(buf, "Bus frequency scaling is disabled\n");
}
static ssize_t bus_freq_scaling_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
if (strncmp(buf, "1", 1) == 0) {
bus_freq_scaling_is_active = 1;
set_high_bus_freq(1);
/*
* We set bus freq to higher at the beginning,
* so we use this daemon thread to make sure system
* can enter low bus mode if there is no high bus request pending
*/
schedule_delayed_work(&bus_freq_daemon,
usecs_to_jiffies(5000000));
} else if (strncmp(buf, "0", 1) == 0) {
if (bus_freq_scaling_is_active)
set_high_bus_freq(1);
bus_freq_scaling_is_active = 0;
}
return size;
}
static int bus_freq_pm_notify(struct notifier_block *nb, unsigned long event,
void *dummy)
{
mutex_lock(&bus_freq_mutex);
if (event == PM_SUSPEND_PREPARE) {
high_bus_count++;
set_high_bus_freq(1);
busfreq_suspended = 1;
} else if (event == PM_POST_SUSPEND) {
busfreq_suspended = 0;
high_bus_count--;
schedule_delayed_work(&bus_freq_daemon,
usecs_to_jiffies(5000000));
}
mutex_unlock(&bus_freq_mutex);
return NOTIFY_OK;
}
static int busfreq_reboot_notifier_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
/* System is rebooting. Set the system into high_bus_freq_mode. */
request_bus_freq(BUS_FREQ_HIGH);
return 0;
}
static struct notifier_block imx_bus_freq_pm_notifier = {
.notifier_call = bus_freq_pm_notify,
};
static struct notifier_block imx_busfreq_reboot_notifier = {
.notifier_call = busfreq_reboot_notifier_event,
};
static DEVICE_ATTR(enable, 0644, bus_freq_scaling_enable_show,
bus_freq_scaling_enable_store);
static int init_busfreq_irq(struct platform_device *busfreq_pdev)
{
struct device *dev = &busfreq_pdev->dev;
u32 cpu;
int err;
for_each_online_cpu(cpu) {
int irq;
/*
* set up a reserved interrupt to get all
* the active cores into a WFE state before
* changing the DDR frequency.
*/
irq = platform_get_irq(busfreq_pdev, cpu);
err = request_irq(irq, wait_in_wfe_irq,
IRQF_PERCPU, "ddrc", NULL);
if (err) {
dev_err(dev, "Busfreq request irq failed %d, err = %d\n",
irq, err);
return err;
}
err = irq_set_affinity(irq, cpumask_of(cpu));
if (err) {
dev_err(dev, "busfreq can't set irq affinity irq = %d\n", irq);
return err;
}
}
return 0;
}
static int init_busfreq_clk(struct platform_device *pdev)
{
dram_pll_clk = devm_clk_get(&pdev->dev, "dram_pll");
sys1_pll_800m = devm_clk_get(&pdev->dev, "sys1_pll_800m");
sys1_pll_400m = devm_clk_get(&pdev->dev, "sys1_pll_400m");
sys1_pll_100m = devm_clk_get(&pdev->dev, "sys1_pll_100m");
sys1_pll_40m = devm_clk_get(&pdev->dev, "sys1_pll_40m");
dram_alt_src = devm_clk_get(&pdev->dev, "dram_alt_src");
dram_alt_root = devm_clk_get(&pdev->dev, "dram_alt_root");
dram_core_clk = devm_clk_get(&pdev->dev, "dram_core");
dram_apb_src = devm_clk_get(&pdev->dev, "dram_apb_src");
dram_apb_pre_div = devm_clk_get(&pdev->dev, "dram_apb_pre_div");
if (IS_ERR(dram_pll_clk) || IS_ERR(sys1_pll_400m) || IS_ERR(sys1_pll_100m) ||
IS_ERR(sys1_pll_40m) || IS_ERR(dram_alt_src) || IS_ERR(dram_alt_root) ||
IS_ERR(dram_core_clk) || IS_ERR(dram_apb_src) || IS_ERR(dram_apb_pre_div)) {
dev_err(&pdev->dev, "failed to get busfreq clk\n");
return -EINVAL;
}
return 0;
}
/*!
* This is the probe routine for the bus frequency driver.
*
* @param pdev The platform device structure
*
* @return The function returns 0 on success
*
*/
static int busfreq_probe(struct platform_device *pdev)
{
int err;
busfreq_dev = &pdev->dev;
/* get the clock for DDRC */
err = init_busfreq_clk(pdev);
if (err) {
dev_err(busfreq_dev, "init clk failed\n");
return err;
}
/* init the irq used for ddr frequency change */
err = init_busfreq_irq(pdev);
if (err) {
dev_err(busfreq_dev, "init busfreq irq failed!\n");
return err;
}
/* create the sysfs file */
err = sysfs_create_file(&busfreq_dev->kobj, &dev_attr_enable.attr);
if (err) {
dev_err(busfreq_dev,
"Unable to register sysdev entry for BUSFREQ");
return err;
}
high_bus_freq_mode = 1;
low_bus_freq_mode = 0;
audio_bus_freq_mode = 0;
cur_bus_freq_mode = BUS_FREQ_HIGH;
bus_freq_scaling_is_active = 1;
bus_freq_scaling_initialized = 1;
INIT_DELAYED_WORK(&low_bus_freq_handler, reduce_bus_freq_handler);
INIT_DELAYED_WORK(&bus_freq_daemon, bus_freq_daemon_handler);
register_pm_notifier(&imx_bus_freq_pm_notifier);
register_reboot_notifier(&imx_busfreq_reboot_notifier);
/* enter low bus mode if no high speed device enabled */
schedule_delayed_work(&bus_freq_daemon, msecs_to_jiffies(10000));
return 0;
}
static const struct of_device_id imx_busfreq_ids[] = {
{ .compatible = "fsl,imx_busfreq", },
{ /*sentinel */}
};
static struct platform_driver busfreq_driver = {
.driver = {
.name = "imx_busfreq",
.owner = THIS_MODULE,
.of_match_table = imx_busfreq_ids,
},
.probe = busfreq_probe,
};
/*!
* Initialise the busfreq_driver.
*
* @return The function always returns 0.
*/
static int __init busfreq_init(void)
{
if (platform_driver_register(&busfreq_driver) != 0)
return -ENODEV;
printk(KERN_INFO "Bus freq driver module loaded\n");
return 0;
}
static void __exit busfreq_cleanup(void)
{
sysfs_remove_file(&busfreq_dev->kobj, &dev_attr_enable.attr);
/* Unregister the device structure */
platform_driver_unregister(&busfreq_driver);
bus_freq_scaling_initialized = 0;
}
module_init(busfreq_init);
module_exit(busfreq_cleanup);
MODULE_AUTHOR("NXP Semiconductor, Inc.");
MODULE_DESCRIPTION("Busfreq driver");
MODULE_LICENSE("GPL");

View File

@ -50,6 +50,10 @@ void release_bus_freq(enum bus_freq_mode mode);
int register_busfreq_notifier(struct notifier_block *nb);
int unregister_busfreq_notifier(struct notifier_block *nb);
int get_bus_freq_mode(void);
#elif defined(CONFIG_ARCH_FSL_IMX8MQ)
void request_bus_freq(enum bus_freq_mode mode);
void release_bus_freq(enum bus_freq_mode mode);
int get_bus_freq_mode(void);
#else
static inline void request_bus_freq(enum bus_freq_mode mode)
{

View File

@ -28,6 +28,8 @@
#define FSL_SIP_SRTC_PING_WDOG 0x04
#define FSL_SIP_SRTC_SET_TIMEOUT_WDOG 0x05
#define FSL_SIP_DDR_DVFS 0xc2000004
#define IMX8MQ_PD_MIPI 0
#define IMX8MQ_PD_PCIE1 1
#define IMX8MQ_PD_OTG1 2