Skip to content

Commit

Permalink
libertas: implement if_sdio runtime power management
Browse files Browse the repository at this point in the history
The SDIO card is now fully powered down when the network interface is
brought down.

Signed-off-by: Daniel Drake <dsd@laptop.org>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
  • Loading branch information
Daniel Drake authored and John W. Linville committed Aug 9, 2011
1 parent d2e7b34 commit 7e1f79a
Showing 1 changed file with 171 additions and 106 deletions.
277 changes: 171 additions & 106 deletions drivers/net/wireless/libertas/if_sdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/host.h>
#include <linux/pm_runtime.h>

#include "host.h"
#include "decl.h"
Expand All @@ -47,6 +48,8 @@
#include "cmd.h"
#include "if_sdio.h"

static void if_sdio_interrupt(struct sdio_func *func);

/* The if_sdio_remove() callback function is called when
* user removes this module from kernel space or ejects
* the card from the slot. The driver handles these 2 cases
Expand Down Expand Up @@ -757,6 +760,136 @@ static int if_sdio_prog_firmware(struct if_sdio_card *card)
return ret;
}

/********************************************************************/
/* Power management */
/********************************************************************/

static int if_sdio_power_on(struct if_sdio_card *card)
{
struct sdio_func *func = card->func;
struct lbs_private *priv = card->priv;
struct mmc_host *host = func->card->host;
int ret;

sdio_claim_host(func);

ret = sdio_enable_func(func);
if (ret)
goto release;

/* For 1-bit transfers to the 8686 model, we need to enable the
* interrupt flag in the CCCR register. Set the MMC_QUIRK_LENIENT_FN0
* bit to allow access to non-vendor registers. */
if ((card->model == MODEL_8686) &&
(host->caps & MMC_CAP_SDIO_IRQ) &&
(host->ios.bus_width == MMC_BUS_WIDTH_1)) {
u8 reg;

func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
reg = sdio_f0_readb(func, SDIO_CCCR_IF, &ret);
if (ret)
goto disable;

reg |= SDIO_BUS_ECSI;
sdio_f0_writeb(func, reg, SDIO_CCCR_IF, &ret);
if (ret)
goto disable;
}

card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret);
if (ret)
goto disable;

card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8;
if (ret)
goto disable;

card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16;
if (ret)
goto disable;

sdio_release_host(func);
ret = if_sdio_prog_firmware(card);
sdio_claim_host(func);
if (ret)
goto disable;

/*
* Get rx_unit if the chip is SD8688 or newer.
* SD8385 & SD8686 do not have rx_unit.
*/
if ((card->model != MODEL_8385)
&& (card->model != MODEL_8686))
card->rx_unit = if_sdio_read_rx_unit(card);
else
card->rx_unit = 0;

/*
* Set up the interrupt handler late.
*
* If we set it up earlier, the (buggy) hardware generates a spurious
* interrupt, even before the interrupt has been enabled, with
* CCCR_INTx = 0.
*
* We register the interrupt handler late so that we can handle any
* spurious interrupts, and also to avoid generation of that known
* spurious interrupt in the first place.
*/
ret = sdio_claim_irq(func, if_sdio_interrupt);
if (ret)
goto disable;

/*
* Enable interrupts now that everything is set up
*/
sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret);
if (ret)
goto release_irq;

sdio_release_host(func);

/*
* FUNC_INIT is required for SD8688 WLAN/BT multiple functions
*/
if (card->model == MODEL_8688) {
struct cmd_header cmd;

memset(&cmd, 0, sizeof(cmd));

lbs_deb_sdio("send function INIT command\n");
if (__lbs_cmd(priv, CMD_FUNC_INIT, &cmd, sizeof(cmd),
lbs_cmd_copyback, (unsigned long) &cmd))
netdev_alert(priv->dev, "CMD_FUNC_INIT cmd failed\n");
}

priv->fw_ready = 1;

return 0;

release_irq:
sdio_release_irq(func);
disable:
sdio_disable_func(func);
release:
sdio_release_host(func);
return ret;
}

static int if_sdio_power_off(struct if_sdio_card *card)
{
struct sdio_func *func = card->func;
struct lbs_private *priv = card->priv;

priv->fw_ready = 0;

sdio_claim_host(func);
sdio_release_irq(func);
sdio_disable_func(func);
sdio_release_host(func);
return 0;
}


/*******************************************************************/
/* Libertas callbacks */
/*******************************************************************/
Expand Down Expand Up @@ -923,6 +1056,32 @@ static void if_sdio_reset_card(struct lbs_private *priv)
schedule_work(&card_reset_work);
}

static int if_sdio_power_save(struct lbs_private *priv)
{
struct if_sdio_card *card = priv->card;
int ret;

flush_workqueue(card->workqueue);

ret = if_sdio_power_off(card);

/* Let runtime PM know the card is powered off */
pm_runtime_put_sync(&card->func->dev);

return ret;
}

static int if_sdio_power_restore(struct lbs_private *priv)
{
struct if_sdio_card *card = priv->card;

/* Make sure the card will not be powered off by runtime PM */
pm_runtime_get_sync(&card->func->dev);

return if_sdio_power_on(card);
}


/*******************************************************************/
/* SDIO callbacks */
/*******************************************************************/
Expand Down Expand Up @@ -976,7 +1135,6 @@ static int if_sdio_probe(struct sdio_func *func,
int ret, i;
unsigned int model;
struct if_sdio_packet *packet;
struct mmc_host *host = func->card->host;

lbs_deb_enter(LBS_DEB_SDIO);

Expand Down Expand Up @@ -1033,60 +1191,18 @@ static int if_sdio_probe(struct sdio_func *func,
goto free;
}

sdio_claim_host(func);

ret = sdio_enable_func(func);
if (ret)
goto release;

/* For 1-bit transfers to the 8686 model, we need to enable the
* interrupt flag in the CCCR register. Set the MMC_QUIRK_LENIENT_FN0
* bit to allow access to non-vendor registers. */
if ((card->model == MODEL_8686) &&
(host->caps & MMC_CAP_SDIO_IRQ) &&
(host->ios.bus_width == MMC_BUS_WIDTH_1)) {
u8 reg;

func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
reg = sdio_f0_readb(func, SDIO_CCCR_IF, &ret);
if (ret)
goto release_int;

reg |= SDIO_BUS_ECSI;
sdio_f0_writeb(func, reg, SDIO_CCCR_IF, &ret);
if (ret)
goto release_int;
}

card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret);
if (ret)
goto release_int;

card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8;
if (ret)
goto release_int;

card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16;
if (ret)
goto release_int;

sdio_release_host(func);

sdio_set_drvdata(func, card);

lbs_deb_sdio("class = 0x%X, vendor = 0x%X, "
"device = 0x%X, model = 0x%X, ioport = 0x%X\n",
func->class, func->vendor, func->device,
model, (unsigned)card->ioport);

ret = if_sdio_prog_firmware(card);
if (ret)
goto reclaim;

priv = lbs_add_card(card, &func->dev);
if (!priv) {
ret = -ENOMEM;
goto reclaim;
goto free;
}

card->priv = priv;
Expand All @@ -1097,62 +1213,21 @@ static int if_sdio_probe(struct sdio_func *func,
priv->exit_deep_sleep = if_sdio_exit_deep_sleep;
priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup;
priv->reset_card = if_sdio_reset_card;
priv->power_save = if_sdio_power_save;
priv->power_restore = if_sdio_power_restore;

sdio_claim_host(func);

/*
* Get rx_unit if the chip is SD8688 or newer.
* SD8385 & SD8686 do not have rx_unit.
*/
if ((card->model != MODEL_8385)
&& (card->model != MODEL_8686))
card->rx_unit = if_sdio_read_rx_unit(card);
else
card->rx_unit = 0;

/*
* Set up the interrupt handler late.
*
* If we set it up earlier, the (buggy) hardware generates a spurious
* interrupt, even before the interrupt has been enabled, with
* CCCR_INTx = 0.
*
* We register the interrupt handler late so that we can handle any
* spurious interrupts, and also to avoid generation of that known
* spurious interrupt in the first place.
*/
ret = sdio_claim_irq(func, if_sdio_interrupt);
if (ret)
goto disable;

/*
* Enable interrupts now that everything is set up
*/
sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret);
sdio_release_host(func);
ret = if_sdio_power_on(card);
if (ret)
goto reclaim;

priv->fw_ready = 1;

/*
* FUNC_INIT is required for SD8688 WLAN/BT multiple functions
*/
if (card->model == MODEL_8688) {
struct cmd_header cmd;

memset(&cmd, 0, sizeof(cmd));

lbs_deb_sdio("send function INIT command\n");
if (__lbs_cmd(priv, CMD_FUNC_INIT, &cmd, sizeof(cmd),
lbs_cmd_copyback, (unsigned long) &cmd))
netdev_alert(priv->dev, "CMD_FUNC_INIT cmd failed\n");
}
goto err_activate_card;

ret = lbs_start_card(priv);
if_sdio_power_off(card);
if (ret)
goto err_activate_card;

/* Tell PM core that we don't need the card to be powered now */
pm_runtime_put_noidle(&func->dev);

out:
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);

Expand All @@ -1161,14 +1236,6 @@ static int if_sdio_probe(struct sdio_func *func,
err_activate_card:
flush_workqueue(card->workqueue);
lbs_remove_card(priv);
reclaim:
sdio_claim_host(func);
release_int:
sdio_release_irq(func);
disable:
sdio_disable_func(func);
release:
sdio_release_host(func);
free:
destroy_workqueue(card->workqueue);
while (card->packets) {
Expand All @@ -1195,6 +1262,9 @@ static void if_sdio_remove(struct sdio_func *func)

card = sdio_get_drvdata(func);

/* Undo decrement done above in if_sdio_probe */
pm_runtime_get_noresume(&func->dev);

if (user_rmmod && (card->model == MODEL_8688)) {
/*
* FUNC_SHUTDOWN is required for SD8688 WLAN/BT
Expand All @@ -1219,11 +1289,6 @@ static void if_sdio_remove(struct sdio_func *func)
flush_workqueue(card->workqueue);
destroy_workqueue(card->workqueue);

sdio_claim_host(func);
sdio_release_irq(func);
sdio_disable_func(func);
sdio_release_host(func);

while (card->packets) {
packet = card->packets;
card->packets = card->packets->next;
Expand Down

0 comments on commit 7e1f79a

Please sign in to comment.