[PATCH 2/2] libertas: implement if_sdio runtime power management

Vasily Khoruzhick anarsoul at gmail.com
Tue Aug 2 05:04:08 EDT 2011


On Monday 01 August 2011 18:43:27 Daniel Drake wrote:
> The SDIO card is now fully powered down when the network interface is
> brought down.
> 
> Signed-off-by: Daniel Drake <dsd at laptop.org>
> ---
>  drivers/net/wireless/libertas/if_sdio.c |  277
> +++++++++++++++++++------------ 1 files changed, 171 insertions(+), 106
> deletions(-)
> 
> diff --git a/drivers/net/wireless/libertas/if_sdio.c
> b/drivers/net/wireless/libertas/if_sdio.c index 387786e..c962e21 100644
> --- a/drivers/net/wireless/libertas/if_sdio.c
> +++ b/drivers/net/wireless/libertas/if_sdio.c
> @@ -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"
> @@ -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
> @@ -757,6 +760,136 @@ out:
>  	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                                              */
>  /*******************************************************************/
> @@ -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                                                  */
>  /*******************************************************************/
> @@ -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);
> 
> @@ -1033,45 +1191,6 @@ 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, "
> @@ -1079,14 +1198,11 @@ static int if_sdio_probe(struct sdio_func *func,
>  			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;
> @@ -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);

Why do you power on card here? Maybe it's possible do it in lbs_start_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);

Same for power off.

>  	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);
> 
> @@ -1161,14 +1236,6 @@ out:
>  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) {
> @@ -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
> @@ -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;

Regards
Vasily



More information about the libertas-dev mailing list