@@ -77,6 +77,7 @@ config ARM_EXYNOS_BUS_DEVFREQ
bool "ARM EXYNOS Generic Memory Bus DEVFREQ Driver"
depends on ARCH_EXYNOS
select DEVFREQ_GOV_SIMPLE_ONDEMAND
+ select DEVFREQ_GOV_PASSIVE
select DEVFREQ_EVENT_EXYNOS_PPMU
select PM_DEVFREQ_EVENT
select PM_OPP
@@ -1,7 +1,7 @@
/*
* Generic Exynos Bus frequency driver with DEVFREQ Framework
*
- * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
* Author : Chanwoo Choi <cw00.choi@samsung.com>
*
* This driver support Exynos Bus frequency feature by using
@@ -93,7 +93,7 @@ static int exynos_bus_get_event(struct exynos_bus *bus,
}
/*
- * Must necessary function for devfreq governor
+ * Must necessary function for devfreq simple-ondemand governor
*/
static int exynos_bus_target(struct device *dev, unsigned long *freq, u32 flags)
{
@@ -202,59 +202,80 @@ static void exynos_bus_exit(struct device *dev)
regulator_disable(bus->regulator);
dev_pm_opp_of_remove_table(dev);
+ clk_disable_unprepare(bus->clk);
}
-static int exynos_bus_parse_of(struct device_node *np,
- struct exynos_bus *bus)
+/*
+ * Must necessary function for devfreq passive governor
+ */
+static int exynos_bus_passive_target(struct device *dev, unsigned long *freq,
+ u32 flags)
{
- struct device *dev = bus->dev;
- unsigned long rate;
- int i, ret, count, size;
+ struct exynos_bus *bus = dev_get_drvdata(dev);
+ struct dev_pm_opp *new_opp;
+ unsigned long old_freq, new_freq;
+ int ret = 0;
- /* Get the clock to provide each bus with source clock */
- bus->clk = devm_clk_get(dev, "bus");
- if (IS_ERR(bus->clk)) {
- dev_err(dev, "failed to get bus clock\n");
- return PTR_ERR(bus->clk);
+ /* Get new opp-bus instance according to new bus clock */
+ rcu_read_lock();
+ new_opp = devfreq_recommended_opp(dev, freq, flags);
+ if (IS_ERR_OR_NULL(new_opp)) {
+ dev_err(dev, "failed to get recommed opp instance\n");
+ rcu_read_unlock();
+ return PTR_ERR(new_opp);
}
- ret = clk_prepare_enable(bus->clk);
- if (ret < 0) {
- dev_err(dev, "failed to get enable clock\n");
- return ret;
- }
+ new_freq = dev_pm_opp_get_freq(new_opp);
+ old_freq = dev_pm_opp_get_freq(bus->curr_opp);
+ rcu_read_unlock();
- /* Get the freq/voltage OPP table to scale the bus frequency */
- rcu_read_lock();
- ret = dev_pm_opp_of_add_table(dev);
+ if (old_freq == new_freq)
+ return 0;
+
+ /* Change the frequency according to new OPP level */
+ mutex_lock(&bus->lock);
+
+ ret = clk_set_rate(bus->clk, new_freq);
if (ret < 0) {
- dev_err(dev, "failed to get OPP table\n");
- rcu_read_unlock();
- goto err_clk;
+ dev_err(dev, "failed to set the clock of bus\n");
+ goto out;
}
- rate = clk_get_rate(bus->clk);
- bus->curr_opp = dev_pm_opp_find_freq_ceil(dev, &rate);
- if (IS_ERR(bus->curr_opp)) {
- dev_err(dev, "failed to find dev_pm_opp\n");
- rcu_read_unlock();
- ret = PTR_ERR(bus->curr_opp);
- goto err_opp;
- }
- rcu_read_unlock();
+ bus->curr_opp = new_opp;
+
+ dev_dbg(dev, "Set the frequency of bus (%lukHz -> %lukHz)\n",
+ old_freq/1000, new_freq/1000);
+out:
+ mutex_unlock(&bus->lock);
+
+ return ret;
+}
+
+static void exynos_bus_passive_exit(struct device *dev)
+{
+ struct exynos_bus *bus = dev_get_drvdata(dev);
+
+ dev_pm_opp_of_remove_table(dev);
+ clk_disable_unprepare(bus->clk);
+}
+
+static int exynos_bus_parent_parse_of(struct device_node *np,
+ struct exynos_bus *bus)
+{
+ struct device *dev = bus->dev;
+ int i, ret, count, size;
/* Get the regulator to provide each bus with the power */
bus->regulator = devm_regulator_get(dev, "vdd");
if (IS_ERR(bus->regulator)) {
dev_err(dev, "failed to get VDD regulator\n");
- ret = PTR_ERR(bus->regulator);
- goto err_opp;
+ return PTR_ERR(bus->regulator);
}
ret = regulator_enable(bus->regulator);
if (ret < 0) {
dev_err(dev, "failed to enable VDD regulator\n");
- goto err_opp;
+ return ret;
}
/*
@@ -305,6 +326,51 @@ static int exynos_bus_parse_of(struct device_node *np,
err_regulator:
regulator_disable(bus->regulator);
+
+ return ret;
+}
+
+static int exynos_bus_parse_of(struct device_node *np,
+ struct exynos_bus *bus)
+{
+ struct device *dev = bus->dev;
+ unsigned long rate;
+ int ret;
+
+ /* Get the clock to provide each bus with source clock */
+ bus->clk = devm_clk_get(dev, "bus");
+ if (IS_ERR(bus->clk)) {
+ dev_err(dev, "failed to get bus clock\n");
+ return PTR_ERR(bus->clk);
+ }
+
+ ret = clk_prepare_enable(bus->clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to get enable clock\n");
+ return ret;
+ }
+
+ /* Get the freq and voltage from OPP table to scale the bus freq */
+ rcu_read_lock();
+ ret = dev_pm_opp_of_add_table(dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to get OPP table\n");
+ rcu_read_unlock();
+ goto err_clk;
+ }
+
+ rate = clk_get_rate(bus->clk);
+ bus->curr_opp = dev_pm_opp_find_freq_ceil(dev, &rate);
+ if (IS_ERR(bus->curr_opp)) {
+ dev_err(dev, "failed to find dev_pm_opp\n");
+ rcu_read_unlock();
+ ret = PTR_ERR(bus->curr_opp);
+ goto err_opp;
+ }
+ rcu_read_unlock();
+
+ return 0;
+
err_opp:
dev_pm_opp_of_remove_table(dev);
err_clk:
@@ -319,8 +385,10 @@ static int exynos_bus_probe(struct platform_device *pdev)
struct device_node *np = dev->of_node;
struct devfreq_dev_profile *profile;
struct devfreq_simple_ondemand_data *ondemand_data;
+ struct devfreq *parent_devfreq;
struct exynos_bus *bus;
- int ret;
+ int ret, max_state;
+ unsigned long min_freq, max_freq;
if (!np) {
dev_err(dev, "failed to find devicetree node\n");
@@ -337,20 +405,33 @@ static int exynos_bus_probe(struct platform_device *pdev)
/* Parse the device-tree to get the resource information */
ret = exynos_bus_parse_of(np, bus);
if (ret < 0)
- return ret;
+ goto err;
- /* Initalize the struct profile and governor data */
profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL);
- if (!profile)
- return -ENOMEM;
+ if (!profile) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (of_parse_phandle(dev->of_node, "devfreq", 0))
+ goto passive;
+ else
+ ret = exynos_bus_parent_parse_of(np, bus);
+
+ if (ret < 0)
+ goto err;
+
+ /* Initalize the struct profile and governor data for parent device */
profile->polling_ms = 50;
profile->target = exynos_bus_target;
profile->get_dev_status = exynos_bus_get_dev_status;
profile->exit = exynos_bus_exit;
ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
- if (!ondemand_data)
- return -ENOMEM;
+ if (!ondemand_data) {
+ ret = -ENOMEM;
+ goto err;
+ }
ondemand_data->upthreshold = 40;
ondemand_data->downdifferential = 5;
@@ -359,14 +440,15 @@ static int exynos_bus_probe(struct platform_device *pdev)
ondemand_data);
if (IS_ERR_OR_NULL(bus->devfreq)) {
dev_err(dev, "failed to add devfreq device\n");
- return PTR_ERR(bus->devfreq);
+ ret = PTR_ERR(bus->devfreq);
+ goto err;
}
/* Register opp_notifier to catch the change of OPP */
ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq);
if (ret < 0) {
dev_err(dev, "failed to register opp notifier\n");
- return ret;
+ goto err;
}
/*
@@ -376,16 +458,52 @@ static int exynos_bus_probe(struct platform_device *pdev)
ret = exynos_bus_enable_edev(bus);
if (ret < 0) {
dev_err(dev, "failed to enable devfreq-event devices\n");
- return ret;
+ goto err;
}
ret = exynos_bus_set_event(bus);
if (ret < 0) {
dev_err(dev, "failed to set event to devfreq-event devices\n");
- return ret;
+ goto err;
+ }
+
+ goto out;
+passive:
+ /* Initalize the struct profile and governor data for passive device */
+ profile->target = exynos_bus_passive_target;
+ profile->exit = exynos_bus_passive_exit;
+
+ /* Get the instance of parent devfreq device */
+ parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
+ if (IS_ERR(parent_devfreq)) {
+ ret = -EPROBE_DEFER;
+ goto err;
}
+ /* Add devfreq device for exynos bus with passive governor */
+ bus->devfreq = devm_devfreq_add_device(dev, profile, "passive",
+ parent_devfreq);
+ if (IS_ERR_OR_NULL(bus->devfreq)) {
+ dev_err(dev,
+ "failed to add devfreq dev with passive governor\n");
+ ret = -EPROBE_DEFER;
+ goto err;
+ }
+
+out:
+ max_state = bus->devfreq->profile->max_state;
+ min_freq = (bus->devfreq->profile->freq_table[0] / 1000);
+ max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000);
+ pr_info("exynos-bus: new bus device registered: %s (%6ld KHz ~ %6ld KHz)\n",
+ dev_name(dev), min_freq, max_freq);
+
return 0;
+
+err:
+ dev_pm_opp_of_remove_table(dev);
+ clk_disable_unprepare(bus->clk);
+
+ return ret;
}
#ifdef CONFIG_PM_SLEEP