{
        struct opp_table *opp_table;
        unsigned long freq, old_freq;
+       int (*set_opp)(struct dev_pm_set_opp_data *data);
        struct dev_pm_opp *old_opp, *opp;
        struct regulator **regulators;
        struct dev_pm_set_opp_data *data;
                return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
        }
 
+       if (opp_table->set_opp)
+               set_opp = opp_table->set_opp;
+       else
+               set_opp = _generic_set_opp;
+
        data = opp_table->set_opp_data;
        data->regulators = regulators;
        data->regulator_count = opp_table->regulator_count;
 
        rcu_read_unlock();
 
-       return _generic_set_opp(data);
+       return set_opp(data);
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);
 
        if (opp_table->regulators)
                return;
 
+       if (opp_table->set_opp)
+               return;
+
        /* Release clk */
        if (!IS_ERR(opp_table->clk))
                clk_put(opp_table->clk);
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulators);
 
+/**
+ * dev_pm_opp_register_set_opp_helper() - Register custom set OPP helper
+ * @dev: Device for which the helper is getting registered.
+ * @set_opp: Custom set OPP helper.
+ *
+ * This is useful to support complex platforms (like platforms with multiple
+ * regulators per device), instead of the generic OPP set rate helper.
+ *
+ * This must be called before any OPPs are initialized for the device.
+ *
+ * Locking: The internal opp_table and opp structures are RCU protected.
+ * Hence this function internally uses RCU updater strategy with mutex locks
+ * to keep the integrity of the internal data structures. Callers should ensure
+ * that this function is *NOT* called under RCU protection or in contexts where
+ * mutex cannot be locked.
+ */
+int dev_pm_opp_register_set_opp_helper(struct device *dev,
+                       int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+       struct opp_table *opp_table;
+       int ret;
+
+       if (!set_opp)
+               return -EINVAL;
+
+       mutex_lock(&opp_table_lock);
+
+       opp_table = _add_opp_table(dev);
+       if (!opp_table) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       /* This should be called before OPPs are initialized */
+       if (WARN_ON(!list_empty(&opp_table->opp_list))) {
+               ret = -EBUSY;
+               goto err;
+       }
+
+       /* Already have custom set_opp helper */
+       if (WARN_ON(opp_table->set_opp)) {
+               ret = -EBUSY;
+               goto err;
+       }
+
+       opp_table->set_opp = set_opp;
+
+       mutex_unlock(&opp_table_lock);
+       return 0;
+
+err:
+       _remove_opp_table(opp_table);
+unlock:
+       mutex_unlock(&opp_table_lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_register_set_opp_helper);
+
+/**
+ * dev_pm_opp_register_put_opp_helper() - Releases resources blocked for
+ *                                        set_opp helper
+ * @dev: Device for which custom set_opp helper has to be cleared.
+ *
+ * Locking: The internal opp_table and opp structures are RCU protected.
+ * Hence this function internally uses RCU updater strategy with mutex locks
+ * to keep the integrity of the internal data structures. Callers should ensure
+ * that this function is *NOT* called under RCU protection or in contexts where
+ * mutex cannot be locked.
+ */
+void dev_pm_opp_register_put_opp_helper(struct device *dev)
+{
+       struct opp_table *opp_table;
+
+       mutex_lock(&opp_table_lock);
+
+       /* Check for existing table for 'dev' first */
+       opp_table = _find_opp_table(dev);
+       if (IS_ERR(opp_table)) {
+               dev_err(dev, "Failed to find opp_table: %ld\n",
+                       PTR_ERR(opp_table));
+               goto unlock;
+       }
+
+       if (!opp_table->set_opp) {
+               dev_err(dev, "%s: Doesn't have custom set_opp helper set\n",
+                       __func__);
+               goto unlock;
+       }
+
+       /* Make sure there are no concurrent readers while updating opp_table */
+       WARN_ON(!list_empty(&opp_table->opp_list));
+
+       opp_table->set_opp = NULL;
+
+       /* Try freeing opp_table if this was the last blocking resource */
+       _remove_opp_table(opp_table);
+
+unlock:
+       mutex_unlock(&opp_table_lock);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_register_put_opp_helper);
+
 /**
  * dev_pm_opp_add()  - Add an OPP table from a table definitions
  * @dev:       device for which we do this operation
 
 void dev_pm_opp_put_prop_name(struct device *dev);
 struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count);
 void dev_pm_opp_put_regulators(struct opp_table *opp_table);
+int dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
+void dev_pm_opp_register_put_opp_helper(struct device *dev);
 int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
 int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask);
 int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
 
 static inline void dev_pm_opp_put_supported_hw(struct device *dev) {}
 
+static inline int dev_pm_opp_register_set_opp_helper(struct device *dev,
+                       int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+       return -ENOTSUPP;
+}
+
+static inline void dev_pm_opp_register_put_opp_helper(struct device *dev) {}
+
 static inline int dev_pm_opp_set_prop_name(struct device *dev, const char *name)
 {
        return -ENOTSUPP;