UPSTREAM: PM / devfreq: Add new DEVFREQ_TRANSITION_NOTIFIER notifier
[firefly-linux-kernel-4.4.55.git] / drivers / devfreq / devfreq.c
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");