UPSTREAM: PM / devfreq: Add new DEVFREQ_TRANSITION_NOTIFIER notifier
authorChanwoo Choi <cw00.choi@samsung.com>
Tue, 26 Jan 2016 04:21:26 +0000 (13:21 +0900)
committerHuang, Tao <huangtao@rock-chips.com>
Wed, 10 Aug 2016 10:31:08 +0000 (18:31 +0800)
This patch adds the new DEVFREQ_TRANSITION_NOTIFIER notifier to send
the notification when the frequency of device is changed.
This notifier has two state as following:
- DEVFREQ_PRECHANGE  : Notify it before chaning the frequency of device
- DEVFREQ_POSTCHANGE : Notify it after changed the frequency of device

And this patch adds the resourced-managed function to release the resource
automatically when error happen.

Change-Id: Id92ce7b9b2fba0e8776b1bc6980996b16a2902f3
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
[m.reichl and linux.amoon: Tested it on exynos4412-odroidu3 board]
Tested-by: Markus Reichl <m.reichl@fivetechno.de>
Tested-by: Anand Moon <linux.amoon@gmail.com>
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Acked-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
(cherry picked from commit 0fe3a66410a3ba96679be903f1e287d7a0a264a9)

drivers/devfreq/devfreq.c
include/linux/devfreq.h

index f1df56200b0e6993bc32cd979db3552a56c5ecc1..3fa315b46c1aedb0466456b086b55d66eebd37b6 100644 (file)
@@ -149,6 +149,29 @@ static struct devfreq_governor *find_devfreq_governor(const char *name)
        return ERR_PTR(-ENODEV);
 }
 
+static int devfreq_notify_transition(struct devfreq *devfreq,
+               struct devfreq_freqs *freqs, unsigned int state)
+{
+       if (!devfreq)
+               return -EINVAL;
+
+       switch (state) {
+       case DEVFREQ_PRECHANGE:
+               srcu_notifier_call_chain(&devfreq->transition_notifier_list,
+                               DEVFREQ_PRECHANGE, freqs);
+               break;
+
+       case DEVFREQ_POSTCHANGE:
+               srcu_notifier_call_chain(&devfreq->transition_notifier_list,
+                               DEVFREQ_POSTCHANGE, freqs);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 /* Load monitoring helper functions for governors use */
 
 /**
@@ -160,7 +183,8 @@ static struct devfreq_governor *find_devfreq_governor(const char *name)
  */
 int update_devfreq(struct devfreq *devfreq)
 {
-       unsigned long freq;
+       struct devfreq_freqs freqs;
+       unsigned long freq, cur_freq;
        int err = 0;
        u32 flags = 0;
 
@@ -194,10 +218,22 @@ int update_devfreq(struct devfreq *devfreq)
                flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
        }
 
+       if (devfreq->profile->get_cur_freq)
+               devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq);
+       else
+               cur_freq = devfreq->previous_freq;
+
+       freqs.old = cur_freq;
+       freqs.new = freq;
+       devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
+
        err = devfreq->profile->target(devfreq->dev.parent, &freq, flags);
        if (err)
                return err;
 
+       freqs.new = freq;
+       devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
+
        if (devfreq->profile->freq_table)
                if (devfreq_update_status(devfreq, freq))
                        dev_err(&devfreq->dev,
@@ -496,6 +532,8 @@ struct devfreq *devfreq_add_device(struct device *dev,
                goto err_out;
        }
 
+       srcu_init_notifier_head(&devfreq->transition_notifier_list);
+
        mutex_unlock(&devfreq->lock);
 
        mutex_lock(&devfreq_list_lock);
@@ -1264,6 +1302,129 @@ void devm_devfreq_unregister_opp_notifier(struct device *dev,
 }
 EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier);
 
+/**
+ * devfreq_register_notifier() - Register a driver with devfreq
+ * @devfreq:   The devfreq object.
+ * @nb:                The notifier block to register.
+ * @list:      DEVFREQ_TRANSITION_NOTIFIER.
+ */
+int devfreq_register_notifier(struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list)
+{
+       int ret = 0;
+
+       if (!devfreq)
+               return -EINVAL;
+
+       switch (list) {
+       case DEVFREQ_TRANSITION_NOTIFIER:
+               ret = srcu_notifier_chain_register(
+                               &devfreq->transition_notifier_list, nb);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(devfreq_register_notifier);
+
+/*
+ * devfreq_unregister_notifier() - Unregister a driver with devfreq
+ * @devfreq:   The devfreq object.
+ * @nb:                The notifier block to be unregistered.
+ * @list:      DEVFREQ_TRANSITION_NOTIFIER.
+ */
+int devfreq_unregister_notifier(struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list)
+{
+       int ret = 0;
+
+       if (!devfreq)
+               return -EINVAL;
+
+       switch (list) {
+       case DEVFREQ_TRANSITION_NOTIFIER:
+               ret = srcu_notifier_chain_unregister(
+                               &devfreq->transition_notifier_list, nb);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(devfreq_unregister_notifier);
+
+struct devfreq_notifier_devres {
+       struct devfreq *devfreq;
+       struct notifier_block *nb;
+       unsigned int list;
+};
+
+static void devm_devfreq_notifier_release(struct device *dev, void *res)
+{
+       struct devfreq_notifier_devres *this = res;
+
+       devfreq_unregister_notifier(this->devfreq, this->nb, this->list);
+}
+
+/**
+ * devm_devfreq_register_notifier()
+       - Resource-managed devfreq_register_notifier()
+ * @dev:       The devfreq user device. (parent of devfreq)
+ * @devfreq:   The devfreq object.
+ * @nb:                The notifier block to be unregistered.
+ * @list:      DEVFREQ_TRANSITION_NOTIFIER.
+ */
+int devm_devfreq_register_notifier(struct device *dev,
+                               struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list)
+{
+       struct devfreq_notifier_devres *ptr;
+       int ret;
+
+       ptr = devres_alloc(devm_devfreq_notifier_release, sizeof(*ptr),
+                               GFP_KERNEL);
+       if (!ptr)
+               return -ENOMEM;
+
+       ret = devfreq_register_notifier(devfreq, nb, list);
+       if (ret) {
+               devres_free(ptr);
+               return ret;
+       }
+
+       ptr->devfreq = devfreq;
+       ptr->nb = nb;
+       ptr->list = list;
+       devres_add(dev, ptr);
+
+       return 0;
+}
+EXPORT_SYMBOL(devm_devfreq_register_notifier);
+
+/**
+ * devm_devfreq_unregister_notifier()
+       - Resource-managed devfreq_unregister_notifier()
+ * @dev:       The devfreq user device. (parent of devfreq)
+ * @devfreq:   The devfreq object.
+ * @nb:                The notifier block to be unregistered.
+ * @list:      DEVFREQ_TRANSITION_NOTIFIER.
+ */
+void devm_devfreq_unregister_notifier(struct device *dev,
+                               struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list)
+{
+       WARN_ON(devres_release(dev, devm_devfreq_notifier_release,
+                              devm_devfreq_dev_match, devfreq));
+}
+EXPORT_SYMBOL(devm_devfreq_unregister_notifier);
+
 MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
 MODULE_DESCRIPTION("devfreq class support");
 MODULE_LICENSE("GPL");
index 2f856650a74a00e7a91b96d870bdc76abd448570..073cec2659f2e2b6287320f5f5b5cfeeb1f62a7e 100644 (file)
 
 #define DEVFREQ_NAME_LEN 16
 
+/* DEVFREQ notifier interface */
+#define DEVFREQ_TRANSITION_NOTIFIER    (0)
+
+/* Transition notifiers of DEVFREQ_TRANSITION_NOTIFIER */
+#define        DEVFREQ_PRECHANGE               (0)
+#define DEVFREQ_POSTCHANGE             (1)
+
 struct devfreq;
 
 /**
@@ -143,6 +150,7 @@ struct devfreq_governor {
  * @trans_table:       Statistics of devfreq transitions
  * @time_in_state:     Statistics of devfreq states
  * @last_stat_updated: The last time stat updated
+ * @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier
  *
  * This structure stores the devfreq information for a give device.
  *
@@ -177,6 +185,13 @@ struct devfreq {
        unsigned int *trans_table;
        unsigned long *time_in_state;
        unsigned long last_stat_updated;
+
+       struct srcu_notifier_head transition_notifier_list;
+};
+
+struct devfreq_freqs {
+       unsigned long old;
+       unsigned long new;
 };
 
 #if defined(CONFIG_PM_DEVFREQ)
@@ -207,7 +222,20 @@ extern int devm_devfreq_register_opp_notifier(struct device *dev,
                                              struct devfreq *devfreq);
 extern void devm_devfreq_unregister_opp_notifier(struct device *dev,
                                                struct devfreq *devfreq);
-
+extern int devfreq_register_notifier(struct devfreq *devfreq,
+                                       struct notifier_block *nb,
+                                       unsigned int list);
+extern int devfreq_unregister_notifier(struct devfreq *devfreq,
+                                       struct notifier_block *nb,
+                                       unsigned int list);
+extern int devm_devfreq_register_notifier(struct device *dev,
+                               struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list);
+extern void devm_devfreq_unregister_notifier(struct device *dev,
+                               struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list);
 extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
                                                int index);
 
@@ -310,6 +338,35 @@ static inline void devm_devfreq_unregister_opp_notifier(struct device *dev,
 {
 }
 
+static inline int devfreq_register_notifier(struct devfreq *devfreq,
+                                       struct notifier_block *nb,
+                                       unsigned int list)
+{
+       return 0;
+}
+
+static inline int devfreq_unregister_notifier(struct devfreq *devfreq,
+                                       struct notifier_block *nb,
+                                       unsigned int list)
+{
+       return 0;
+}
+
+static inline int devm_devfreq_register_notifier(struct device *dev,
+                               struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list)
+{
+       return 0;
+}
+
+static inline void devm_devfreq_unregister_notifier(struct device *dev,
+                               struct devfreq *devfreq,
+                               struct notifier_block *nb,
+                               unsigned int list)
+{
+}
+
 static inline struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
                                                        int index)
 {