return have_chan_pwr;
 }
 
+static void ieee80211_find_cisco_dtpc(struct ieee80211_sub_if_data *sdata,
+                                     struct ieee80211_channel *channel,
+                                     const u8 *cisco_dtpc_ie,
+                                     int *pwr_level)
+{
+       /* From practical testing, the first data byte of the DTPC element
+        * seems to contain the requested dBm level, and the CLI on Cisco
+        * APs clearly state the range is -127 to 127 dBm, which indicates
+        * a signed byte, although it seemingly never actually goes negative.
+        * The other byte seems to always be zero.
+        */
+       *pwr_level = (__s8)cisco_dtpc_ie[4];
+}
+
 static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
                                       struct ieee80211_channel *channel,
                                       struct ieee80211_mgmt *mgmt,
                                       const u8 *country_ie, u8 country_ie_len,
-                                      const u8 *pwr_constr_ie)
+                                      const u8 *pwr_constr_ie,
+                                      const u8 *cisco_dtpc_ie)
 {
-       bool has_80211h_pwr = false;
-       int chan_pwr, pwr_reduction_80211h;
+       bool has_80211h_pwr = false, has_cisco_pwr = false;
+       int chan_pwr = 0, pwr_reduction_80211h = 0;
+       int pwr_level_cisco, pwr_level_80211h;
        int new_ap_level;
 
        if (country_ie && pwr_constr_ie &&
                has_80211h_pwr = ieee80211_find_80211h_pwr_constr(
                        sdata, channel, country_ie, country_ie_len,
                        pwr_constr_ie, &chan_pwr, &pwr_reduction_80211h);
-               new_ap_level = max_t(int, 0, chan_pwr - pwr_reduction_80211h);
+               pwr_level_80211h =
+                       max_t(int, 0, chan_pwr - pwr_reduction_80211h);
+       }
+
+       if (cisco_dtpc_ie) {
+               ieee80211_find_cisco_dtpc(
+                       sdata, channel, cisco_dtpc_ie, &pwr_level_cisco);
+               has_cisco_pwr = true;
        }
 
-       if (!has_80211h_pwr)
+       if (!has_80211h_pwr && !has_cisco_pwr)
                return 0;
 
-       sdata_info(sdata,
-                  "Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
-                  new_ap_level, chan_pwr, pwr_reduction_80211h,
-                  sdata->u.mgd.bssid);
+       /* If we have both 802.11h and Cisco DTPC, apply both limits
+        * by picking the smallest of the two power levels advertised.
+        */
+       if (has_80211h_pwr &&
+           (!has_cisco_pwr || pwr_level_80211h <= pwr_level_cisco)) {
+               sdata_info(sdata,
+                          "Limiting TX power to %d (%d - %d) dBm as advertised by %pM\n",
+                          pwr_level_80211h, chan_pwr, pwr_reduction_80211h,
+                          sdata->u.mgd.bssid);
+               new_ap_level = pwr_level_80211h;
+       } else {  /* has_cisco_pwr is always true here. */
+               sdata_info(sdata,
+                          "Limiting TX power to %d dBm as advertised by %pM\n",
+                          pwr_level_cisco, sdata->u.mgd.bssid);
+               new_ap_level = pwr_level_cisco;
+       }
 
        if (sdata->ap_power_level == new_ap_level)
                return 0;
 /*
  * This is the canonical list of information elements we care about,
  * the filter code also gives us all changes to the Microsoft OUI
- * (00:50:F2) vendor IE which is used for WMM which we need to track.
+ * (00:50:F2) vendor IE which is used for WMM which we need to track,
+ * as well as the DTPC IE (part of the Cisco OUI) used for signaling
+ * changes to requested client power.
  *
  * We implement beacon filtering in software since that means we can
  * avoid processing the frame here and in cfg80211, and userspace
        changed |= ieee80211_handle_pwr_constr(sdata, chan, mgmt,
                                               elems.country_elem,
                                               elems.country_elem_len,
-                                              elems.pwr_constr_elem);
+                                              elems.pwr_constr_elem,
+                                              elems.cisco_dtpc_elem);
 
        ieee80211_bss_info_change_notify(sdata, changed);
 }
 
                        }
                        elems->pwr_constr_elem = pos;
                        break;
+               case WLAN_EID_CISCO_VENDOR_SPECIFIC:
+                       /* Lots of different options exist, but we only care
+                        * about the Dynamic Transmit Power Control element.
+                        * First check for the Cisco OUI, then for the DTPC
+                        * tag (0x00).
+                        */
+                       if (elen < 4) {
+                               elem_parse_failed = true;
+                               break;
+                       }
+
+                       if (pos[0] != 0x00 || pos[1] != 0x40 ||
+                           pos[2] != 0x96 || pos[3] != 0x00)
+                               break;
+
+                       if (elen != 6) {
+                               elem_parse_failed = true;
+                               break;
+                       }
+
+                       if (calc_crc)
+                               crc = crc32_be(crc, pos - 2, elen + 2);
+
+                       elems->cisco_dtpc_elem = pos;
+                       break;
                case WLAN_EID_TIMEOUT_INTERVAL:
                        if (elen >= sizeof(struct ieee80211_timeout_interval_ie))
                                elems->timeout_int = (void *)pos;