linux/drivers/net/wireless/wl1251/ps.c
<<
>>
Prefs
   1/*
   2 * This file is part of wl1251
   3 *
   4 * Copyright (C) 2008 Nokia Corporation
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License
   8 * version 2 as published by the Free Software Foundation.
   9 *
  10 * This program is distributed in the hope that it will be useful, but
  11 * WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13 * General Public License for more details.
  14 *
  15 * You should have received a copy of the GNU General Public License
  16 * along with this program; if not, write to the Free Software
  17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  18 * 02110-1301 USA
  19 *
  20 */
  21
  22#include "reg.h"
  23#include "ps.h"
  24#include "cmd.h"
  25#include "io.h"
  26
  27/* in ms */
  28#define WL1251_WAKEUP_TIMEOUT 100
  29
  30void wl1251_elp_work(struct work_struct *work)
  31{
  32        struct delayed_work *dwork;
  33        struct wl1251 *wl;
  34
  35        dwork = container_of(work, struct delayed_work, work);
  36        wl = container_of(dwork, struct wl1251, elp_work);
  37
  38        wl1251_debug(DEBUG_PSM, "elp work");
  39
  40        mutex_lock(&wl->mutex);
  41
  42        if (wl->elp || !wl->psm)
  43                goto out;
  44
  45        wl1251_debug(DEBUG_PSM, "chip to elp");
  46        wl1251_write_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_SLEEP);
  47        wl->elp = true;
  48
  49out:
  50        mutex_unlock(&wl->mutex);
  51}
  52
  53#define ELP_ENTRY_DELAY  5
  54
  55/* Routines to toggle sleep mode while in ELP */
  56void wl1251_ps_elp_sleep(struct wl1251 *wl)
  57{
  58        unsigned long delay;
  59
  60        if (wl->psm) {
  61                cancel_delayed_work(&wl->elp_work);
  62                delay = msecs_to_jiffies(ELP_ENTRY_DELAY);
  63                ieee80211_queue_delayed_work(wl->hw, &wl->elp_work, delay);
  64        }
  65}
  66
  67int wl1251_ps_elp_wakeup(struct wl1251 *wl)
  68{
  69        unsigned long timeout, start;
  70        u32 elp_reg;
  71
  72        if (!wl->elp)
  73                return 0;
  74
  75        wl1251_debug(DEBUG_PSM, "waking up chip from elp");
  76
  77        start = jiffies;
  78        timeout = jiffies + msecs_to_jiffies(WL1251_WAKEUP_TIMEOUT);
  79
  80        wl1251_write_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_WAKE_UP);
  81
  82        elp_reg = wl1251_read_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR);
  83
  84        /*
  85         * FIXME: we should wait for irq from chip but, as a temporary
  86         * solution to simplify locking, let's poll instead
  87         */
  88        while (!(elp_reg & ELPCTRL_WLAN_READY)) {
  89                if (time_after(jiffies, timeout)) {
  90                        wl1251_error("elp wakeup timeout");
  91                        return -ETIMEDOUT;
  92                }
  93                msleep(1);
  94                elp_reg = wl1251_read_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR);
  95        }
  96
  97        wl1251_debug(DEBUG_PSM, "wakeup time: %u ms",
  98                     jiffies_to_msecs(jiffies - start));
  99
 100        wl->elp = false;
 101
 102        return 0;
 103}
 104
 105static int wl1251_ps_set_elp(struct wl1251 *wl, bool enable)
 106{
 107        int ret;
 108
 109        if (enable) {
 110                wl1251_debug(DEBUG_PSM, "sleep auth psm/elp");
 111
 112                ret = wl1251_acx_sleep_auth(wl, WL1251_PSM_ELP);
 113                if (ret < 0)
 114                        return ret;
 115
 116                wl1251_ps_elp_sleep(wl);
 117        } else {
 118                wl1251_debug(DEBUG_PSM, "sleep auth cam");
 119
 120                /*
 121                 * When the target is in ELP, we can only
 122                 * access the ELP control register. Thus,
 123                 * we have to wake the target up before
 124                 * changing the power authorization.
 125                 */
 126
 127                wl1251_ps_elp_wakeup(wl);
 128
 129                ret = wl1251_acx_sleep_auth(wl, WL1251_PSM_CAM);
 130                if (ret < 0)
 131                        return ret;
 132        }
 133
 134        return 0;
 135}
 136
 137int wl1251_ps_set_mode(struct wl1251 *wl, enum wl1251_cmd_ps_mode mode)
 138{
 139        int ret;
 140
 141        switch (mode) {
 142        case STATION_POWER_SAVE_MODE:
 143                wl1251_debug(DEBUG_PSM, "entering psm");
 144
 145                /* enable beacon filtering */
 146                ret = wl1251_acx_beacon_filter_opt(wl, true);
 147                if (ret < 0)
 148                        return ret;
 149
 150                ret = wl1251_acx_wake_up_conditions(wl,
 151                                                    WAKE_UP_EVENT_DTIM_BITMAP,
 152                                                    wl->listen_int);
 153                if (ret < 0)
 154                        return ret;
 155
 156                ret = wl1251_cmd_ps_mode(wl, STATION_POWER_SAVE_MODE);
 157                if (ret < 0)
 158                        return ret;
 159
 160                ret = wl1251_ps_set_elp(wl, true);
 161                if (ret < 0)
 162                        return ret;
 163
 164                wl->psm = 1;
 165                break;
 166        case STATION_ACTIVE_MODE:
 167        default:
 168                wl1251_debug(DEBUG_PSM, "leaving psm");
 169                ret = wl1251_ps_set_elp(wl, false);
 170                if (ret < 0)
 171                        return ret;
 172
 173                /* disable beacon filtering */
 174                ret = wl1251_acx_beacon_filter_opt(wl, false);
 175                if (ret < 0)
 176                        return ret;
 177
 178                ret = wl1251_acx_wake_up_conditions(wl,
 179                                                    WAKE_UP_EVENT_DTIM_BITMAP,
 180                                                    wl->listen_int);
 181                if (ret < 0)
 182                        return ret;
 183
 184                ret = wl1251_cmd_ps_mode(wl, STATION_ACTIVE_MODE);
 185                if (ret < 0)
 186                        return ret;
 187
 188                wl->psm = 0;
 189                break;
 190        }
 191
 192        return ret;
 193}
 194
 195