*
  * @IEEE80211_HW_AMPDU_AGGREGATION:
  *     Hardware supports 11n A-MPDU aggregation.
+ *
+ * @IEEE80211_HW_NO_STACK_DYNAMIC_PS:
+ *     Hardware which has dynamic power save support, meaning
+ *     that power save is enabled in idle periods, and don't need support
+ *     from stack.
  */
 enum ieee80211_hw_flags {
        IEEE80211_HW_RX_INCLUDES_FCS                    = 1<<1,
        IEEE80211_HW_NOISE_DBM                          = 1<<8,
        IEEE80211_HW_SPECTRUM_MGMT                      = 1<<9,
        IEEE80211_HW_AMPDU_AGGREGATION                  = 1<<10,
+       IEEE80211_HW_NO_STACK_DYNAMIC_PS                = 1<<11,
 };
 
 /**
 
 
 enum queue_stop_reason {
        IEEE80211_QUEUE_STOP_REASON_DRIVER,
+       IEEE80211_QUEUE_STOP_REASON_PS,
 };
 
 /* maximum number of hardware queues we support. */
                                */
        int wifi_wme_noack_test;
        unsigned int wmm_acm; /* bit field of ACM bits (BIT(802.1D tag)) */
+
        bool powersave;
+       int dynamic_ps_timeout;
+       struct work_struct dynamic_ps_enable_work;
+       struct work_struct dynamic_ps_disable_work;
+       struct timer_list dynamic_ps_timer;
 
 #ifdef CONFIG_MAC80211_DEBUGFS
        struct local_debugfsdentries {
 u64 ieee80211_mandatory_rates(struct ieee80211_local *local,
                              enum ieee80211_band band);
 
+void ieee80211_dynamic_ps_enable_work(struct work_struct *work);
+void ieee80211_dynamic_ps_disable_work(struct work_struct *work);
+void ieee80211_dynamic_ps_timer(unsigned long data);
+
 void ieee80211_wake_queues_by_reason(struct ieee80211_hw *hw,
                                     enum queue_stop_reason reason);
 void ieee80211_stop_queues_by_reason(struct ieee80211_hw *hw,
 
 
        INIT_DELAYED_WORK(&local->scan_work, ieee80211_scan_work);
 
+       INIT_WORK(&local->dynamic_ps_enable_work,
+                 ieee80211_dynamic_ps_enable_work);
+       INIT_WORK(&local->dynamic_ps_disable_work,
+                 ieee80211_dynamic_ps_disable_work);
+       setup_timer(&local->dynamic_ps_timer,
+                   ieee80211_dynamic_ps_timer, (unsigned long) local);
+
        sta_info_init(local);
 
        tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,
 
        ieee80211_bss_info_change_notify(sdata, bss_info_changed);
 
        if (local->powersave) {
-               local->hw.conf.flags |= IEEE80211_CONF_PS;
-               ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+               if (local->dynamic_ps_timeout > 0)
+                       mod_timer(&local->dynamic_ps_timer, jiffies +
+                                 msecs_to_jiffies(local->dynamic_ps_timeout));
+               else {
+                       conf->flags |= IEEE80211_CONF_PS;
+                       ieee80211_hw_config(local,
+                                           IEEE80211_CONF_CHANGE_PS);
+               }
        }
 
        netif_tx_start_all_queues(sdata->dev);
        local->oper_channel_type = NL80211_CHAN_NO_HT;
        config_changed |= IEEE80211_CONF_CHANGE_HT;
 
+       del_timer_sync(&local->dynamic_ps_timer);
+       cancel_work_sync(&local->dynamic_ps_enable_work);
+
        if (local->hw.conf.flags & IEEE80211_CONF_PS) {
                local->hw.conf.flags &= ~IEEE80211_CONF_PS;
                config_changed |= IEEE80211_CONF_CHANGE_PS;
                ieee80211_restart_sta_timer(sdata);
        rcu_read_unlock();
 }
+
+void ieee80211_dynamic_ps_disable_work(struct work_struct *work)
+{
+       struct ieee80211_local *local =
+               container_of(work, struct ieee80211_local,
+                            dynamic_ps_disable_work);
+
+       if (local->hw.conf.flags & IEEE80211_CONF_PS) {
+               local->hw.conf.flags &= ~IEEE80211_CONF_PS;
+               ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+       }
+
+       ieee80211_wake_queues_by_reason(&local->hw,
+                                       IEEE80211_QUEUE_STOP_REASON_PS);
+}
+
+void ieee80211_dynamic_ps_enable_work(struct work_struct *work)
+{
+       struct ieee80211_local *local =
+               container_of(work, struct ieee80211_local,
+                            dynamic_ps_enable_work);
+
+       if (local->hw.conf.flags & IEEE80211_CONF_PS)
+               return;
+
+       local->hw.conf.flags |= IEEE80211_CONF_PS;
+
+       ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+}
+
+void ieee80211_dynamic_ps_timer(unsigned long data)
+{
+       struct ieee80211_local *local = (void *) data;
+
+       queue_work(local->hw.workqueue, &local->dynamic_ps_enable_work);
+}
 
                goto fail;
        }
 
+       if (!(local->hw.flags & IEEE80211_HW_NO_STACK_DYNAMIC_PS) &&
+           local->dynamic_ps_timeout > 0) {
+               if (local->hw.conf.flags & IEEE80211_CONF_PS) {
+                       ieee80211_stop_queues_by_reason(&local->hw,
+                                                       IEEE80211_QUEUE_STOP_REASON_PS);
+                       queue_work(local->hw.workqueue,
+                                  &local->dynamic_ps_disable_work);
+               }
+
+               mod_timer(&local->dynamic_ps_timer, jiffies +
+                         msecs_to_jiffies(local->dynamic_ps_timeout));
+       }
+
        nh_pos = skb_network_header(skb) - skb->data;
        h_pos = skb_transport_header(skb) - skb->data;
 
 
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
        struct ieee80211_conf *conf = &local->hw.conf;
-       int ret = 0;
+       int ret = 0, timeout = 0;
        bool ps;
 
        if (sdata->vif.type != NL80211_IFTYPE_STATION)
 
        if (wrq->disabled) {
                ps = false;
+               timeout = 0;
                goto set;
        }
 
        case IW_POWER_ALL_R:    /* If explicitely state all */
                ps = true;
                break;
-       default:                /* Otherwise we don't support it */
-               return -EINVAL;
+       default:                /* Otherwise we ignore */
+               break;
        }
 
-       if (ps == local->powersave)
-               return ret;
+       if (wrq->flags & IW_POWER_TIMEOUT)
+               timeout = wrq->value / 1000;
 
 set:
+       if (ps == local->powersave && timeout == local->dynamic_ps_timeout)
+               return ret;
+
        local->powersave = ps;
+       local->dynamic_ps_timeout = timeout;
 
        if (sdata->u.sta.flags & IEEE80211_STA_ASSOCIATED) {
-               if (local->powersave)
-                       conf->flags |= IEEE80211_CONF_PS;
-               else
-                       conf->flags &= ~IEEE80211_CONF_PS;
-
+               if (!(local->hw.flags & IEEE80211_HW_NO_STACK_DYNAMIC_PS) &&
+                   local->dynamic_ps_timeout > 0)
+                       mod_timer(&local->dynamic_ps_timer, jiffies +
+                                 msecs_to_jiffies(local->dynamic_ps_timeout));
+               else {
+                       if (local->powersave)
+                               conf->flags |= IEEE80211_CONF_PS;
+                       else
+                               conf->flags &= ~IEEE80211_CONF_PS;
+               }
                ret = ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
        }