iwlwifi: mvm: Add device wide power command
authorAlexander Bondar <alexander.bondar@intel.com>
Tue, 3 Sep 2013 11:18:03 +0000 (14:18 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 11 Oct 2013 07:56:57 +0000 (09:56 +0200)
FW starts using legacy power table command (0x77) for device wide power
settings. Currently this command contains only option flags field.
It can configure the following: CAM (Continuous Active Mode) and
POWER_SAVE_ENABLE debug option. Send this command when firmware is
loaded - D0 and D3.
Note: Setting this command is important to avoid unwanted FW behavior.
It particularly fixes a bug when a device does not drop to low power
after disassociation from AP.

Signed-off-by: Alexander Bondar <alexander.bondar@intel.com>
Reviewed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/iwlwifi/iwl-fw.h
drivers/net/wireless/iwlwifi/mvm/d3.c
drivers/net/wireless/iwlwifi/mvm/debugfs.c
drivers/net/wireless/iwlwifi/mvm/fw-api-power.h
drivers/net/wireless/iwlwifi/mvm/fw.c
drivers/net/wireless/iwlwifi/mvm/mvm.h
drivers/net/wireless/iwlwifi/mvm/power.c

index 761794a5e91645990151a09a03f720ad4f9f0025..7fd886f5a5e01d247c5ea18b2c0db1d74bfd01f3 100644 (file)
@@ -88,6 +88,8 @@
  * @IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE: new NS offload (large version)
  * @IWL_UCODE_TLV_FLAGS_SCHED_SCAN: this uCode image supports scheduled scan.
  * @IWL_UCODE_TLV_FLAGS_STA_KEY_CMD: new ADD_STA and ADD_STA_KEY command API
+ * @IWL_UCODE_TLV_FLAGS_DEVICE_PS_CMD: support device wide power command
+ *     containing CAM (Continuous Active Mode) indication.
  */
 enum iwl_ucode_tlv_flag {
        IWL_UCODE_TLV_FLAGS_PAN                 = BIT(0),
@@ -107,6 +109,7 @@ enum iwl_ucode_tlv_flag {
        IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE    = BIT(16),
        IWL_UCODE_TLV_FLAGS_SCHED_SCAN          = BIT(17),
        IWL_UCODE_TLV_FLAGS_STA_KEY_CMD         = BIT(19),
+       IWL_UCODE_TLV_FLAGS_DEVICE_PS_CMD       = BIT(20),
 };
 
 /* The default calibrate table size if not specified by firmware file */
index d08c12b930e5e7eda40482974c8b1def7610affd..ab5c1f0e062250faa7bf8fbcb4d60c5d44820aea 100644 (file)
@@ -1149,6 +1149,10 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,
        if (ret)
                goto out;
 
+       ret = iwl_mvm_power_update_device_mode(mvm);
+       if (ret)
+               goto out;
+
        ret = iwl_mvm_power_update_mode(mvm, vif);
        if (ret)
                goto out;
index e943ee125d2321c7f9ed04462082ff1c35dc76df..a5813437d262c2b721d7a81f1f269d85f896b7fe 100644 (file)
@@ -246,60 +246,56 @@ static ssize_t iwl_dbgfs_stations_read(struct file *file, char __user *user_buf,
        return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
 }
 
-static ssize_t iwl_dbgfs_power_down_allow_write(struct file *file,
-                                               const char __user *user_buf,
+static ssize_t iwl_dbgfs_disable_power_off_read(struct file *file,
+                                               char __user *user_buf,
                                                size_t count, loff_t *ppos)
 {
        struct iwl_mvm *mvm = file->private_data;
-       char buf[8] = {};
-       int allow;
-
-       if (!mvm->ucode_loaded)
-               return -EIO;
-
-       count = min_t(size_t, count, sizeof(buf) - 1);
-       if (copy_from_user(buf, user_buf, count))
-               return -EFAULT;
-
-       if (sscanf(buf, "%d", &allow) != 1)
-               return -EINVAL;
+       char buf[64];
+       int bufsz = sizeof(buf);
+       int pos = 0;
 
-       IWL_DEBUG_POWER(mvm, "%s device power down\n",
-                       allow ? "allow" : "prevent");
+       pos += scnprintf(buf+pos, bufsz-pos, "disable_power_off_d0=%d\n",
+                        mvm->disable_power_off);
+       pos += scnprintf(buf+pos, bufsz-pos, "disable_power_off_d3=%d\n",
+                        mvm->disable_power_off_d3);
 
-       /*
-        * TODO: Send REPLY_DEBUG_CMD (0xf0) when FW support it
-        */
-
-       return count;
+       return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
 }
 
-static ssize_t iwl_dbgfs_power_down_d3_allow_write(struct file *file,
-                                                  const char __user *user_buf,
-                                                  size_t count, loff_t *ppos)
+static ssize_t iwl_dbgfs_disable_power_off_write(struct file *file,
+                                                const char __user *user_buf,
+                                                size_t count, loff_t *ppos)
 {
        struct iwl_mvm *mvm = file->private_data;
-       char buf[8] = {};
-       int allow;
+       char buf[64] = {};
+       int ret;
+       int val;
+
+       if (!mvm->ucode_loaded)
+               return -EIO;
 
        count = min_t(size_t, count, sizeof(buf) - 1);
        if (copy_from_user(buf, user_buf, count))
                return -EFAULT;
 
-       if (sscanf(buf, "%d", &allow) != 1)
+       if (!strncmp("disable_power_off_d0=", buf, 21)) {
+               if (sscanf(buf + 21, "%d", &val) != 1)
+                       return -EINVAL;
+               mvm->disable_power_off = val;
+       } else if (!strncmp("disable_power_off_d3=", buf, 21)) {
+               if (sscanf(buf + 21, "%d", &val) != 1)
+                       return -EINVAL;
+               mvm->disable_power_off_d3 = val;
+       } else {
                return -EINVAL;
+       }
 
-       IWL_DEBUG_POWER(mvm, "%s device power down in d3\n",
-                       allow ? "allow" : "prevent");
-
-       /*
-        * TODO: When WoWLAN FW alive notification happens, driver will send
-        * REPLY_DEBUG_CMD setting power_down_allow flag according to
-        * mvm->prevent_power_down_d3
-        */
-       mvm->prevent_power_down_d3 = !allow;
+       mutex_lock(&mvm->mutex);
+       ret = iwl_mvm_power_update_device_mode(mvm);
+       mutex_unlock(&mvm->mutex);
 
-       return count;
+       return ret ?: count;
 }
 
 static void iwl_dbgfs_update_pm(struct iwl_mvm *mvm,
@@ -397,7 +393,9 @@ static ssize_t iwl_dbgfs_pm_params_write(struct file *file,
                if (sscanf(buf + 16, "%d", &val) != 1)
                        return -EINVAL;
                param = MVM_DEBUGFS_PM_TX_DATA_TIMEOUT;
-       } else if (!strncmp("disable_power_off=", buf, 18)) {
+       } else if (!strncmp("disable_power_off=", buf, 18) &&
+                  !(mvm->fw->ucode_capa.flags &
+                    IWL_UCODE_TLV_FLAGS_DEVICE_PS_CMD)) {
                if (sscanf(buf + 18, "%d", &val) != 1)
                        return -EINVAL;
                param = MVM_DEBUGFS_PM_DISABLE_POWER_OFF;
@@ -1159,8 +1157,7 @@ MVM_DEBUGFS_READ_WRITE_FILE_OPS(sram);
 MVM_DEBUGFS_READ_FILE_OPS(stations);
 MVM_DEBUGFS_READ_FILE_OPS(bt_notif);
 MVM_DEBUGFS_READ_FILE_OPS(bt_cmd);
-MVM_DEBUGFS_WRITE_FILE_OPS(power_down_allow);
-MVM_DEBUGFS_WRITE_FILE_OPS(power_down_d3_allow);
+MVM_DEBUGFS_READ_WRITE_FILE_OPS(disable_power_off);
 MVM_DEBUGFS_READ_FILE_OPS(fw_rx_stats);
 MVM_DEBUGFS_WRITE_FILE_OPS(fw_restart);
 MVM_DEBUGFS_READ_WRITE_FILE_OPS(scan_ant_rxchain);
@@ -1186,8 +1183,9 @@ int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct dentry *dbgfs_dir)
        MVM_DEBUGFS_ADD_FILE(stations, dbgfs_dir, S_IRUSR);
        MVM_DEBUGFS_ADD_FILE(bt_notif, dbgfs_dir, S_IRUSR);
        MVM_DEBUGFS_ADD_FILE(bt_cmd, dbgfs_dir, S_IRUSR);
-       MVM_DEBUGFS_ADD_FILE(power_down_allow, mvm->debugfs_dir, S_IWUSR);
-       MVM_DEBUGFS_ADD_FILE(power_down_d3_allow, mvm->debugfs_dir, S_IWUSR);
+       if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_DEVICE_PS_CMD)
+               MVM_DEBUGFS_ADD_FILE(disable_power_off, mvm->debugfs_dir,
+                                    S_IRUSR | S_IWUSR);
        MVM_DEBUGFS_ADD_FILE(fw_rx_stats, mvm->debugfs_dir, S_IRUSR);
        MVM_DEBUGFS_ADD_FILE(fw_restart, mvm->debugfs_dir, S_IWUSR);
        MVM_DEBUGFS_ADD_FILE(scan_ant_rxchain, mvm->debugfs_dir,
index eac7a68c802edc94fa2d6c81251712a8ae2a679d..5cb93ae5cd2f73bfff7a1daf5d381f6e9ccc987b 100644 (file)
@@ -131,6 +131,33 @@ struct iwl_powertable_cmd {
        __le32 lprx_rssi_threshold;
 } __packed;
 
+/**
+ * enum iwl_device_power_flags - masks for device power command flags
+ * @DEVIC_POWER_FLAGS_POWER_SAVE_ENA_MSK: '1' Allow to save power by turning off
+ *     receiver and transmitter. '0' - does not allow. This flag should be
+ *     always set to '1' unless one need to disable actual power down for debug
+ *     purposes.
+ * @DEVICE_POWER_FLAGS_CAM_MSK: '1' CAM (Continuous Active Mode) is set, meaning
+ *     that power management is disabled. '0' Power management is enabled, one
+ *     of power schemes is applied.
+*/
+enum iwl_device_power_flags {
+       DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK   = BIT(0),
+       DEVICE_POWER_FLAGS_CAM_MSK              = BIT(13),
+};
+
+/**
+ * struct iwl_device_power_cmd - device wide power command.
+ * DEVICE_POWER_CMD = 0x77 (command, has simple generic response)
+ *
+ * @flags:     Power table command flags from DEVICE_POWER_FLAGS_*
+ */
+struct iwl_device_power_cmd {
+       /* PM_POWER_TABLE_CMD_API_S_VER_6 */
+       __le16 flags;
+       __le16 reserved;
+} __packed;
+
 /**
  * struct iwl_mac_power_cmd - New power command containing uAPSD support
  * MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response)
index f96186f2803580bea1959f7a2bd10dc8d630bd62..8c784e622802675d05a59a43cd1ef207a5f98b4d 100644 (file)
@@ -428,6 +428,10 @@ int iwl_mvm_up(struct iwl_mvm *mvm)
                        goto error;
        }
 
+       ret = iwl_mvm_power_update_device_mode(mvm);
+       if (ret)
+               goto error;
+
        IWL_DEBUG_INFO(mvm, "RT uCode started.\n");
        return 0;
  error:
index 6387d2d7722b2097742390765768f774d5112902..a5d609749755cc50cd4859fe44058c21dd070ea3 100644 (file)
@@ -162,6 +162,7 @@ enum iwl_power_scheme {
 struct iwl_mvm_power_ops {
        int (*power_update_mode)(struct iwl_mvm *mvm,
                                 struct ieee80211_vif *vif);
+       int (*power_update_device_mode)(struct iwl_mvm *mvm);
        int (*power_disable)(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 #ifdef CONFIG_IWLWIFI_DEBUGFS
        int (*power_dbgfs_read)(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
@@ -489,7 +490,8 @@ struct iwl_mvm {
 #ifdef CONFIG_IWLWIFI_DEBUGFS
        struct dentry *debugfs_dir;
        u32 dbgfs_sram_offset, dbgfs_sram_len;
-       bool prevent_power_down_d3;
+       bool disable_power_off;
+       bool disable_power_off_d3;
 #endif
 
        struct iwl_mvm_phy_ctxt phy_ctxts[NUM_PHY_CTX];
@@ -756,6 +758,13 @@ static inline int iwl_mvm_power_disable(struct iwl_mvm *mvm,
        return mvm->pm_ops->power_disable(mvm, vif);
 }
 
+static inline int iwl_mvm_power_update_device_mode(struct iwl_mvm *mvm)
+{
+       if (mvm->pm_ops->power_update_device_mode)
+               return mvm->pm_ops->power_update_device_mode(mvm);
+       return 0;
+}
+
 #ifdef CONFIG_IWLWIFI_DEBUGFS
 static inline int iwl_mvm_power_dbgfs_read(struct iwl_mvm *mvm,
                                            struct ieee80211_vif *vif,
index 3752ddd0b2e09a0a99c2b1a18bbbe03966d34b61..80d5f88a9d321b080c8cc63a4f0b58b0a35f0d0f 100644 (file)
@@ -443,6 +443,32 @@ static int iwl_mvm_power_mac_disable(struct iwl_mvm *mvm,
                                    sizeof(cmd), &cmd);
 }
 
+static int iwl_mvm_power_update_device(struct iwl_mvm *mvm)
+{
+       struct iwl_device_power_cmd cmd = {
+               .flags = cpu_to_le16(DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK),
+       };
+
+       if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_DEVICE_PS_CMD))
+               return 0;
+
+       if (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM)
+               cmd.flags |= cpu_to_le16(DEVICE_POWER_FLAGS_CAM_MSK);
+
+#ifdef CONFIG_IWLWIFI_DEBUGFS
+       if ((mvm->cur_ucode == IWL_UCODE_WOWLAN) ? mvm->disable_power_off_d3 :
+           mvm->disable_power_off)
+               cmd.flags &=
+                       cpu_to_le16(~DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK);
+#endif
+       IWL_DEBUG_POWER(mvm,
+                       "Sending device power command with flags = 0x%X\n",
+                       cmd.flags);
+
+       return iwl_mvm_send_cmd_pdu(mvm, POWER_TABLE_CMD, CMD_SYNC, sizeof(cmd),
+                                   &cmd);
+}
+
 #ifdef CONFIG_IWLWIFI_DEBUGFS
 static int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm,
                                        struct ieee80211_vif *vif, char *buf,
@@ -453,10 +479,11 @@ static int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm,
 
        iwl_mvm_power_build_cmd(mvm, vif, &cmd);
 
-       pos += scnprintf(buf+pos, bufsz-pos, "disable_power_off = %d\n",
-                        (cmd.flags &
-                        cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK)) ?
-                        0 : 1);
+       if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_DEVICE_PS_CMD))
+               pos += scnprintf(buf+pos, bufsz-pos, "disable_power_off = %d\n",
+                                (cmd.flags &
+                                cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK)) ?
+                                0 : 1);
        pos += scnprintf(buf+pos, bufsz-pos, "power_scheme = %d\n",
                         iwlmvm_mod_params.power_scheme);
        pos += scnprintf(buf+pos, bufsz-pos, "flags = 0x%x\n",
@@ -622,6 +649,7 @@ int iwl_mvm_update_beacon_filter(struct iwl_mvm *mvm,
 
 const struct iwl_mvm_power_ops pm_mac_ops = {
        .power_update_mode = iwl_mvm_power_mac_update_mode,
+       .power_update_device_mode = iwl_mvm_power_update_device,
        .power_disable = iwl_mvm_power_mac_disable,
 #ifdef CONFIG_IWLWIFI_DEBUGFS
        .power_dbgfs_read = iwl_mvm_power_mac_dbgfs_read,