]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
OPP: Add support to find OPP for a set of keys
authorKrishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
Wed, 20 Aug 2025 08:28:47 +0000 (13:58 +0530)
committerViresh Kumar <viresh.kumar@linaro.org>
Tue, 26 Aug 2025 05:10:58 +0000 (10:40 +0530)
Some clients, such as PCIe, may operate at the same clock frequency
across different data rates by varying link width. In such cases,
frequency alone is not sufficient to uniquely identify an OPP.

To support these scenarios, introduce a new API
dev_pm_opp_find_key_exact() that allows OPP lookup with different
set of keys like freq, level & bandwidth.

Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
[ Viresh: Minor cleanups ]
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
drivers/opp/core.c
include/linux/pm_opp.h

index edbd60501cf00dfd1957f7d19b228d1c61bbbdcc..bba4f7daff8cb886b1ab23e6420c3e960f4386c0 100644 (file)
@@ -476,6 +476,16 @@ static unsigned long _read_bw(struct dev_pm_opp *opp, int index)
        return opp->bandwidth[index].peak;
 }
 
+static unsigned long _read_opp_key(struct dev_pm_opp *opp, int index,
+                                  struct dev_pm_opp_key *key)
+{
+       key->bw = opp->bandwidth ? opp->bandwidth[index].peak : 0;
+       key->freq = opp->rates[index];
+       key->level = opp->level;
+
+       return true;
+}
+
 /* Generic comparison helpers */
 static bool _compare_exact(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp,
                           unsigned long opp_key, unsigned long key)
@@ -509,6 +519,22 @@ static bool _compare_floor(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp,
        return false;
 }
 
+static bool _compare_opp_key_exact(struct dev_pm_opp **opp,
+               struct dev_pm_opp *temp_opp, struct dev_pm_opp_key *opp_key,
+               struct dev_pm_opp_key *key)
+{
+       bool level_match = (key->level == OPP_LEVEL_UNSET || opp_key->level == key->level);
+       bool freq_match = (key->freq == 0 || opp_key->freq == key->freq);
+       bool bw_match = (key->bw == 0 || opp_key->bw == key->bw);
+
+       if (freq_match && level_match && bw_match) {
+               *opp = temp_opp;
+               return true;
+       }
+
+       return false;
+}
+
 /* Generic key finding helpers */
 static struct dev_pm_opp *_opp_table_find_key(struct opp_table *opp_table,
                unsigned long *key, int index, bool available,
@@ -541,6 +567,37 @@ static struct dev_pm_opp *_opp_table_find_key(struct opp_table *opp_table,
        return opp;
 }
 
+static struct dev_pm_opp *_opp_table_find_opp_key(struct opp_table *opp_table,
+               struct dev_pm_opp_key *key, bool available,
+               unsigned long (*read)(struct dev_pm_opp *opp, int index,
+                                     struct dev_pm_opp_key *key),
+               bool (*compare)(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp,
+                               struct dev_pm_opp_key *opp_key, struct dev_pm_opp_key *key),
+               bool (*assert)(struct opp_table *opp_table, unsigned int index))
+{
+       struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
+       struct dev_pm_opp_key temp_key;
+
+       /* Assert that the requirement is met */
+       if (!assert(opp_table, 0))
+               return ERR_PTR(-EINVAL);
+
+       guard(mutex)(&opp_table->lock);
+
+       list_for_each_entry(temp_opp, &opp_table->opp_list, node) {
+               if (temp_opp->available == available) {
+                       read(temp_opp, 0, &temp_key);
+                       if (compare(&opp, temp_opp, &temp_key, key)) {
+                               /* Increment the reference count of OPP */
+                               dev_pm_opp_get(opp);
+                               break;
+                       }
+               }
+       }
+
+       return opp;
+}
+
 static struct dev_pm_opp *
 _find_key(struct device *dev, unsigned long *key, int index, bool available,
          unsigned long (*read)(struct dev_pm_opp *opp, int index),
@@ -632,6 +689,48 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_exact);
 
+/**
+ * dev_pm_opp_find_key_exact() - Search for an OPP with exact key set
+ * @dev:               Device for which the OPP is being searched
+ * @key:               OPP key set to match
+ * @available:         true/false - match for available OPP
+ *
+ * Search for an exact match of the key set in the OPP table.
+ *
+ * Return: A matching opp on success, else ERR_PTR in case of error.
+ * Possible error values:
+ * EINVAL:     for bad pointers
+ * ERANGE:     no match found for search
+ * ENODEV:     if device not found in list of registered devices
+ *
+ * Note: 'available' is a modifier for the search. If 'available' == true,
+ * then the match is for exact matching key and is available in the stored
+ * OPP table. If false, the match is for exact key which is not available.
+ *
+ * This provides a mechanism to enable an OPP which is not available currently
+ * or the opposite as well.
+ *
+ * The callers are required to call dev_pm_opp_put() for the returned OPP after
+ * use.
+ */
+struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev,
+                                            struct dev_pm_opp_key *key,
+                                            bool available)
+{
+       struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev);
+
+       if (IS_ERR(opp_table)) {
+               dev_err(dev, "%s: OPP table not found (%ld)\n", __func__,
+                       PTR_ERR(opp_table));
+               return ERR_CAST(opp_table);
+       }
+
+       return _opp_table_find_opp_key(opp_table, key, available,
+                                      _read_opp_key, _compare_opp_key_exact,
+                                      assert_single_clk);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_find_key_exact);
+
 /**
  * dev_pm_opp_find_freq_exact_indexed() - Search for an exact freq for the
  *                                      clock corresponding to the index
index cf477beae4bbede88223566df5f43d85adc5a816..789406d95e69f8b0545f1ed329fdc6629e864570 100644 (file)
@@ -98,6 +98,25 @@ struct dev_pm_opp_data {
        unsigned long u_volt;
 };
 
+/**
+ * struct dev_pm_opp_key - Key used to identify OPP entries
+ * @freq:       Frequency in Hz. Use 0 if frequency is not to be matched.
+ * @level:      Performance level associated with the OPP entry.
+ *              Use OPP_LEVEL_UNSET if level is not to be matched.
+ * @bw:         Bandwidth associated with the OPP entry.
+ *              Use 0 if bandwidth is not to be matched.
+ *
+ * This structure is used to uniquely identify an OPP entry based on
+ * frequency, performance level, and bandwidth. Each field can be
+ * selectively ignored during matching by setting it to its respective
+ * NOP value.
+ */
+struct dev_pm_opp_key {
+       unsigned long freq;
+       unsigned int level;
+       u32 bw;
+};
+
 #if defined(CONFIG_PM_OPP)
 
 struct opp_table *dev_pm_opp_get_opp_table(struct device *dev);
@@ -131,6 +150,10 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
                                              unsigned long freq,
                                              bool available);
 
+struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev,
+                                            struct dev_pm_opp_key *key,
+                                            bool available);
+
 struct dev_pm_opp *
 dev_pm_opp_find_freq_exact_indexed(struct device *dev, unsigned long freq,
                                   u32 index, bool available);
@@ -289,6 +312,13 @@ static inline struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
        return ERR_PTR(-EOPNOTSUPP);
 }
 
+static inline struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev,
+                                                          struct dev_pm_opp_key *key,
+                                                          bool available)
+{
+       return ERR_PTR(-EOPNOTSUPP);
+}
+
 static inline struct dev_pm_opp *
 dev_pm_opp_find_freq_exact_indexed(struct device *dev, unsigned long freq,
                                   u32 index, bool available)