ps.c 6.8 KB
Newer Older
L
Luciano Coelho 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * This file is part of wl1271
 *
 * Copyright (C) 2008-2009 Nokia Corporation
 *
 * Contact: Luciano Coelho <luciano.coelho@nokia.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

S
Shahar Levi 已提交
24 25 26
#include "reg.h"
#include "ps.h"
#include "io.h"
27
#include "tx.h"
L
Luciano Coelho 已提交
28 29 30

#define WL1271_WAKEUP_TIMEOUT 500

31
void wl1271_elp_work(struct work_struct *work)
L
Luciano Coelho 已提交
32
{
33 34
	struct delayed_work *dwork;
	struct wl1271 *wl;
35
	struct wl12xx_vif *wlvif;
36 37 38 39 40 41 42 43

	dwork = container_of(work, struct delayed_work, work);
	wl = container_of(dwork, struct wl1271, elp_work);

	wl1271_debug(DEBUG_PSM, "elp work");

	mutex_lock(&wl->mutex);

44 45 46
	if (unlikely(wl->state == WL1271_STATE_OFF))
		goto out;

47 48 49 50
	/* our work might have been already cancelled */
	if (unlikely(!test_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags)))
		goto out;

51
	if (test_bit(WL1271_FLAG_IN_ELP, &wl->flags))
52
		goto out;
L
Luciano Coelho 已提交
53

54 55 56 57 58 59
	wl12xx_for_each_wlvif(wl, wlvif) {
		if (!test_bit(WLVIF_FLAG_PSM, &wlvif->flags) &&
		    !test_bit(WL1271_FLAG_IDLE, &wl->flags))
			goto out;
	}

60
	wl1271_debug(DEBUG_PSM, "chip to elp");
61
	wl1271_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_SLEEP);
62
	set_bit(WL1271_FLAG_IN_ELP, &wl->flags);
63 64 65 66 67 68 69 70 71 72

out:
	mutex_unlock(&wl->mutex);
}

#define ELP_ENTRY_DELAY  5

/* Routines to toggle sleep mode while in ELP */
void wl1271_ps_elp_sleep(struct wl1271 *wl)
{
73 74
	struct wl12xx_vif *wlvif;

75 76 77 78
	/* we shouldn't get consecutive sleep requests */
	if (WARN_ON(test_and_set_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags)))
		return;

79 80 81 82 83
	wl12xx_for_each_wlvif(wl, wlvif) {
		if (!test_bit(WLVIF_FLAG_PSM, &wlvif->flags) &&
		    !test_bit(WL1271_FLAG_IDLE, &wl->flags))
			return;
	}
84 85 86

	ieee80211_queue_delayed_work(wl->hw, &wl->elp_work,
				     msecs_to_jiffies(ELP_ENTRY_DELAY));
L
Luciano Coelho 已提交
87 88
}

89
int wl1271_ps_elp_wakeup(struct wl1271 *wl)
L
Luciano Coelho 已提交
90 91 92 93 94 95 96
{
	DECLARE_COMPLETION_ONSTACK(compl);
	unsigned long flags;
	int ret;
	u32 start_time = jiffies;
	bool pending = false;

97 98 99 100 101 102 103 104 105 106
	/*
	 * we might try to wake up even if we didn't go to sleep
	 * before (e.g. on boot)
	 */
	if (!test_and_clear_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags))
		return 0;

	/* don't cancel_sync as it might contend for a mutex and deadlock */
	cancel_delayed_work(&wl->elp_work);

107
	if (!test_bit(WL1271_FLAG_IN_ELP, &wl->flags))
L
Luciano Coelho 已提交
108 109 110 111 112 113 114 115 116
		return 0;

	wl1271_debug(DEBUG_PSM, "waking up chip from elp");

	/*
	 * The spinlock is required here to synchronize both the work and
	 * the completion variable in one entity.
	 */
	spin_lock_irqsave(&wl->wl_lock, flags);
117
	if (test_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags))
L
Luciano Coelho 已提交
118 119 120 121 122
		pending = true;
	else
		wl->elp_compl = &compl;
	spin_unlock_irqrestore(&wl->wl_lock, flags);

123
	wl1271_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_WAKE_UP);
L
Luciano Coelho 已提交
124 125 126 127 128 129

	if (!pending) {
		ret = wait_for_completion_timeout(
			&compl, msecs_to_jiffies(WL1271_WAKEUP_TIMEOUT));
		if (ret == 0) {
			wl1271_error("ELP wakeup timeout!");
130
			wl12xx_queue_recovery_work(wl);
L
Luciano Coelho 已提交
131 132 133 134 135 136 137 138
			ret = -ETIMEDOUT;
			goto err;
		} else if (ret < 0) {
			wl1271_error("ELP wakeup completion error.");
			goto err;
		}
	}

139
	clear_bit(WL1271_FLAG_IN_ELP, &wl->flags);
L
Luciano Coelho 已提交
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

	wl1271_debug(DEBUG_PSM, "wakeup time: %u ms",
		     jiffies_to_msecs(jiffies - start_time));
	goto out;

err:
	spin_lock_irqsave(&wl->wl_lock, flags);
	wl->elp_compl = NULL;
	spin_unlock_irqrestore(&wl->wl_lock, flags);
	return ret;

out:
	return 0;
}

E
Eliad Peller 已提交
155 156
int wl1271_ps_set_mode(struct wl1271 *wl, struct wl12xx_vif *wlvif,
		       enum wl1271_cmd_ps_mode mode, u32 rates, bool send)
L
Luciano Coelho 已提交
157 158 159 160 161 162
{
	int ret;

	switch (mode) {
	case STATION_POWER_SAVE_MODE:
		wl1271_debug(DEBUG_PSM, "entering psm");
163

E
Eliad Peller 已提交
164
		ret = wl1271_acx_wake_up_conditions(wl, wlvif);
165 166 167 168 169
		if (ret < 0) {
			wl1271_error("couldn't set wake up conditions");
			return ret;
		}

E
Eliad Peller 已提交
170
		ret = wl1271_cmd_ps_mode(wl, wlvif, STATION_POWER_SAVE_MODE);
L
Luciano Coelho 已提交
171 172 173
		if (ret < 0)
			return ret;

174
		set_bit(WLVIF_FLAG_PSM, &wlvif->flags);
L
Luciano Coelho 已提交
175 176 177 178 179
		break;
	case STATION_ACTIVE_MODE:
	default:
		wl1271_debug(DEBUG_PSM, "leaving psm");

180
		/* disable beacon early termination */
181
		if (wl->band == IEEE80211_BAND_2GHZ) {
E
Eliad Peller 已提交
182
			ret = wl1271_acx_bet_enable(wl, wlvif, false);
183 184 185
			if (ret < 0)
				return ret;
		}
186

187
		/* disable beacon filtering */
E
Eliad Peller 已提交
188
		ret = wl1271_acx_beacon_filter_opt(wl, wlvif, false);
189 190 191
		if (ret < 0)
			return ret;

E
Eliad Peller 已提交
192
		ret = wl1271_cmd_ps_mode(wl, wlvif, STATION_ACTIVE_MODE);
L
Luciano Coelho 已提交
193 194 195
		if (ret < 0)
			return ret;

196
		clear_bit(WLVIF_FLAG_PSM, &wlvif->flags);
L
Luciano Coelho 已提交
197 198 199 200 201
		break;
	}

	return ret;
}
202 203 204

static void wl1271_ps_filter_frames(struct wl1271 *wl, u8 hlid)
{
205
	int i;
206 207 208
	struct sk_buff *skb;
	struct ieee80211_tx_info *info;
	unsigned long flags;
209
	int filtered[NUM_TX_QUEUES];
210

211
	/* filter all frames currently in the low level queues for this hlid */
212
	for (i = 0; i < NUM_TX_QUEUES; i++) {
213
		filtered[i] = 0;
214
		while ((skb = skb_dequeue(&wl->links[hlid].tx_queue[i]))) {
215 216 217 218 219
			filtered[i]++;

			if (WARN_ON(wl12xx_is_dummy_packet(wl, skb)))
				continue;

220 221 222
			info = IEEE80211_SKB_CB(skb);
			info->flags |= IEEE80211_TX_STAT_TX_FILTERED;
			info->status.rates[0].idx = -1;
223
			ieee80211_tx_status_ni(wl->hw, skb);
224 225 226 227
		}
	}

	spin_lock_irqsave(&wl->wl_lock, flags);
228 229
	for (i = 0; i < NUM_TX_QUEUES; i++)
		wl->tx_queue_count[i] -= filtered[i];
230 231 232 233 234 235 236 237 238 239 240 241
	spin_unlock_irqrestore(&wl->wl_lock, flags);

	wl1271_handle_tx_low_watermark(wl);
}

void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues)
{
	struct ieee80211_sta *sta;

	if (test_bit(hlid, &wl->ap_ps_map))
		return;

242 243
	wl1271_debug(DEBUG_PSM, "start mac80211 PSM on hlid %d pkts %d "
		     "clean_queues %d", hlid, wl->links[hlid].allocated_pkts,
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
		     clean_queues);

	rcu_read_lock();
	sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr);
	if (!sta) {
		wl1271_error("could not find sta %pM for starting ps",
			     wl->links[hlid].addr);
		rcu_read_unlock();
		return;
	}

	ieee80211_sta_ps_transition_ni(sta, true);
	rcu_read_unlock();

	/* do we want to filter all frames from this link's queues? */
	if (clean_queues)
		wl1271_ps_filter_frames(wl, hlid);

	__set_bit(hlid, &wl->ap_ps_map);
}

void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid)
{
	struct ieee80211_sta *sta;

	if (!test_bit(hlid, &wl->ap_ps_map))
		return;

	wl1271_debug(DEBUG_PSM, "end mac80211 PSM on hlid %d", hlid);

	__clear_bit(hlid, &wl->ap_ps_map);

	rcu_read_lock();
	sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr);
	if (!sta) {
		wl1271_error("could not find sta %pM for ending ps",
			     wl->links[hlid].addr);
		goto end;
	}

	ieee80211_sta_ps_transition_ni(sta, false);
end:
	rcu_read_unlock();
}