libertas SDIO: convert to asynchronous firmware loading
Signed-off-by: Daniel Drake <dsd@laptop.org> Acked-by: Dan Williams <dcbw@redhat.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
534111c78c
commit
66d647efe5
1 changed files with 131 additions and 95 deletions
|
@ -117,6 +117,8 @@ struct if_sdio_card {
|
||||||
int model;
|
int model;
|
||||||
unsigned long ioport;
|
unsigned long ioport;
|
||||||
unsigned int scratch_reg;
|
unsigned int scratch_reg;
|
||||||
|
bool started;
|
||||||
|
wait_queue_head_t pwron_waitq;
|
||||||
|
|
||||||
u8 buffer[65536] __attribute__((aligned(4)));
|
u8 buffer[65536] __attribute__((aligned(4)));
|
||||||
|
|
||||||
|
@ -129,6 +131,9 @@ struct if_sdio_card {
|
||||||
u8 rx_unit;
|
u8 rx_unit;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void if_sdio_finish_power_on(struct if_sdio_card *card);
|
||||||
|
static int if_sdio_power_off(struct if_sdio_card *card);
|
||||||
|
|
||||||
/********************************************************************/
|
/********************************************************************/
|
||||||
/* I/O */
|
/* I/O */
|
||||||
/********************************************************************/
|
/********************************************************************/
|
||||||
|
@ -669,12 +674,39 @@ out:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void if_sdio_do_prog_firmware(struct lbs_private *priv, int ret,
|
||||||
|
const struct firmware *helper,
|
||||||
|
const struct firmware *mainfw)
|
||||||
|
{
|
||||||
|
struct if_sdio_card *card = priv->card;
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
pr_err("failed to find firmware (%d)\n", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = if_sdio_prog_helper(card, helper);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
lbs_deb_sdio("Helper firmware loaded\n");
|
||||||
|
|
||||||
|
ret = if_sdio_prog_real(card, mainfw);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
lbs_deb_sdio("Firmware loaded\n");
|
||||||
|
if_sdio_finish_power_on(card);
|
||||||
|
|
||||||
|
out:
|
||||||
|
release_firmware(helper);
|
||||||
|
release_firmware(mainfw);
|
||||||
|
}
|
||||||
|
|
||||||
static int if_sdio_prog_firmware(struct if_sdio_card *card)
|
static int if_sdio_prog_firmware(struct if_sdio_card *card)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
u16 scratch;
|
u16 scratch;
|
||||||
const struct firmware *helper = NULL;
|
|
||||||
const struct firmware *mainfw = NULL;
|
|
||||||
|
|
||||||
lbs_deb_enter(LBS_DEB_SDIO);
|
lbs_deb_enter(LBS_DEB_SDIO);
|
||||||
|
|
||||||
|
@ -708,41 +740,18 @@ static int if_sdio_prog_firmware(struct if_sdio_card *card)
|
||||||
*/
|
*/
|
||||||
if (scratch == IF_SDIO_FIRMWARE_OK) {
|
if (scratch == IF_SDIO_FIRMWARE_OK) {
|
||||||
lbs_deb_sdio("firmware already loaded\n");
|
lbs_deb_sdio("firmware already loaded\n");
|
||||||
goto success;
|
if_sdio_finish_power_on(card);
|
||||||
|
return 0;
|
||||||
} else if ((card->model == MODEL_8686) && (scratch & 0x7fff)) {
|
} else if ((card->model == MODEL_8686) && (scratch & 0x7fff)) {
|
||||||
lbs_deb_sdio("firmware may be running\n");
|
lbs_deb_sdio("firmware may be running\n");
|
||||||
goto success;
|
if_sdio_finish_power_on(card);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = lbs_get_firmware(&card->func->dev, card->model, &fw_table[0],
|
ret = lbs_get_firmware_async(card->priv, &card->func->dev, card->model,
|
||||||
&helper, &mainfw);
|
fw_table, if_sdio_do_prog_firmware);
|
||||||
if (ret) {
|
|
||||||
pr_err("failed to find firmware (%d)\n", ret);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = if_sdio_prog_helper(card, helper);
|
|
||||||
if (ret)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
lbs_deb_sdio("Helper firmware loaded\n");
|
|
||||||
|
|
||||||
ret = if_sdio_prog_real(card, mainfw);
|
|
||||||
if (ret)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
lbs_deb_sdio("Firmware loaded\n");
|
|
||||||
|
|
||||||
success:
|
|
||||||
sdio_claim_host(card->func);
|
|
||||||
sdio_set_block_size(card->func, IF_SDIO_BLOCK_SIZE);
|
|
||||||
sdio_release_host(card->func);
|
|
||||||
ret = 0;
|
|
||||||
|
|
||||||
out:
|
out:
|
||||||
release_firmware(helper);
|
|
||||||
release_firmware(mainfw);
|
|
||||||
|
|
||||||
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -751,10 +760,89 @@ out:
|
||||||
/* Power management */
|
/* Power management */
|
||||||
/********************************************************************/
|
/********************************************************************/
|
||||||
|
|
||||||
static int if_sdio_power_on(struct if_sdio_card *card)
|
/* Finish power on sequence (after firmware is loaded) */
|
||||||
|
static void if_sdio_finish_power_on(struct if_sdio_card *card)
|
||||||
{
|
{
|
||||||
struct sdio_func *func = card->func;
|
struct sdio_func *func = card->func;
|
||||||
struct lbs_private *priv = card->priv;
|
struct lbs_private *priv = card->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
sdio_claim_host(func);
|
||||||
|
sdio_set_block_size(card->func, IF_SDIO_BLOCK_SIZE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 release;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
wake_up(&card->pwron_waitq);
|
||||||
|
|
||||||
|
if (!card->started) {
|
||||||
|
ret = lbs_start_card(priv);
|
||||||
|
if_sdio_power_off(card);
|
||||||
|
if (ret == 0) {
|
||||||
|
card->started = true;
|
||||||
|
/* Tell PM core that we don't need the card to be
|
||||||
|
* powered now */
|
||||||
|
pm_runtime_put_noidle(&func->dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
release_irq:
|
||||||
|
sdio_release_irq(func);
|
||||||
|
release:
|
||||||
|
sdio_release_host(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int if_sdio_power_on(struct if_sdio_card *card)
|
||||||
|
{
|
||||||
|
struct sdio_func *func = card->func;
|
||||||
struct mmc_host *host = func->card->host;
|
struct mmc_host *host = func->card->host;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -797,64 +885,13 @@ static int if_sdio_power_on(struct if_sdio_card *card)
|
||||||
|
|
||||||
sdio_release_host(func);
|
sdio_release_host(func);
|
||||||
ret = if_sdio_prog_firmware(card);
|
ret = if_sdio_prog_firmware(card);
|
||||||
sdio_claim_host(func);
|
if (ret) {
|
||||||
if (ret)
|
sdio_disable_func(func);
|
||||||
goto disable;
|
return ret;
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
return 0;
|
||||||
|
|
||||||
release_irq:
|
|
||||||
sdio_release_irq(func);
|
|
||||||
disable:
|
disable:
|
||||||
sdio_disable_func(func);
|
sdio_disable_func(func);
|
||||||
release:
|
release:
|
||||||
|
@ -1061,11 +1098,17 @@ static int if_sdio_power_save(struct lbs_private *priv)
|
||||||
static int if_sdio_power_restore(struct lbs_private *priv)
|
static int if_sdio_power_restore(struct lbs_private *priv)
|
||||||
{
|
{
|
||||||
struct if_sdio_card *card = priv->card;
|
struct if_sdio_card *card = priv->card;
|
||||||
|
int r;
|
||||||
|
|
||||||
/* Make sure the card will not be powered off by runtime PM */
|
/* Make sure the card will not be powered off by runtime PM */
|
||||||
pm_runtime_get_sync(&card->func->dev);
|
pm_runtime_get_sync(&card->func->dev);
|
||||||
|
|
||||||
return if_sdio_power_on(card);
|
r = if_sdio_power_on(card);
|
||||||
|
if (r)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
wait_event(card->pwron_waitq, priv->fw_ready);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1166,6 +1209,7 @@ static int if_sdio_probe(struct sdio_func *func,
|
||||||
spin_lock_init(&card->lock);
|
spin_lock_init(&card->lock);
|
||||||
card->workqueue = create_workqueue("libertas_sdio");
|
card->workqueue = create_workqueue("libertas_sdio");
|
||||||
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
|
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
|
||||||
|
init_waitqueue_head(&card->pwron_waitq);
|
||||||
|
|
||||||
/* Check if we support this card */
|
/* Check if we support this card */
|
||||||
for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
|
for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
|
||||||
|
@ -1207,14 +1251,6 @@ static int if_sdio_probe(struct sdio_func *func,
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err_activate_card;
|
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:
|
out:
|
||||||
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue