mmc: dw_mmc: Honor requests to set the clock to 0
Previously the dw_mmc driver would ignore any requests to disable the card's clock. This doesn't seem like a good thing in general, but had one extra bad side effect in the following situation: * mmc core would set clk to 400kHz at boot time while scanning * mmc core would set clk to 0 since no card, but it would be ignored. * suspend to ram and resume; clocks in the dw_mmc IP block are now 0 but dw_mmc thinks that they're 400kHz (it ignored the set to 0). * insert card * mmc core would set clk to 400kHz which would be considered a no-op. Note that if there is no card in the slot and we do a suspend/resume cycle, we _do_ still end up with differences in a dw_mmc register dump, but the differences are clock related and we've got the clock disabled both before and after, so this should be OK. Signed-off-by: Doug Anderson <dianders@chromium.org> Signed-off-by: Seungwon Jeon <tgih.jun@samsung.com> Signed-off-by: Chris Ball <cjb@laptop.org>
This commit is contained in:
parent
e2c635999f
commit
fdf492a1cc
@ -88,6 +88,9 @@ struct idmac_desc {
|
|||||||
* @queue_node: List node for placing this node in the @queue list of
|
* @queue_node: List node for placing this node in the @queue list of
|
||||||
* &struct dw_mci.
|
* &struct dw_mci.
|
||||||
* @clock: Clock rate configured by set_ios(). Protected by host->lock.
|
* @clock: Clock rate configured by set_ios(). Protected by host->lock.
|
||||||
|
* @__clk_old: The last updated clock with reflecting clock divider.
|
||||||
|
* Keeping track of this helps us to avoid spamming the console
|
||||||
|
* with CONFIG_MMC_CLKGATE.
|
||||||
* @flags: Random state bits associated with the slot.
|
* @flags: Random state bits associated with the slot.
|
||||||
* @id: Number of this slot.
|
* @id: Number of this slot.
|
||||||
* @last_detect_state: Most recently observed card detect state.
|
* @last_detect_state: Most recently observed card detect state.
|
||||||
@ -105,6 +108,7 @@ struct dw_mci_slot {
|
|||||||
struct list_head queue_node;
|
struct list_head queue_node;
|
||||||
|
|
||||||
unsigned int clock;
|
unsigned int clock;
|
||||||
|
unsigned int __clk_old;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
#define DW_MMC_CARD_PRESENT 0
|
#define DW_MMC_CARD_PRESENT 0
|
||||||
#define DW_MMC_CARD_NEED_INIT 1
|
#define DW_MMC_CARD_NEED_INIT 1
|
||||||
@ -632,24 +636,31 @@ static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg)
|
|||||||
static void dw_mci_setup_bus(struct dw_mci_slot *slot, bool force_clkinit)
|
static void dw_mci_setup_bus(struct dw_mci_slot *slot, bool force_clkinit)
|
||||||
{
|
{
|
||||||
struct dw_mci *host = slot->host;
|
struct dw_mci *host = slot->host;
|
||||||
|
unsigned int clock = slot->clock;
|
||||||
u32 div;
|
u32 div;
|
||||||
u32 clk_en_a;
|
u32 clk_en_a;
|
||||||
|
|
||||||
if (slot->clock != host->current_speed || force_clkinit) {
|
if (!clock) {
|
||||||
div = host->bus_hz / slot->clock;
|
mci_writel(host, CLKENA, 0);
|
||||||
if (host->bus_hz % slot->clock && host->bus_hz > slot->clock)
|
mci_send_cmd(slot,
|
||||||
|
SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0);
|
||||||
|
} else if (clock != host->current_speed || force_clkinit) {
|
||||||
|
div = host->bus_hz / clock;
|
||||||
|
if (host->bus_hz % clock && host->bus_hz > clock)
|
||||||
/*
|
/*
|
||||||
* move the + 1 after the divide to prevent
|
* move the + 1 after the divide to prevent
|
||||||
* over-clocking the card.
|
* over-clocking the card.
|
||||||
*/
|
*/
|
||||||
div += 1;
|
div += 1;
|
||||||
|
|
||||||
div = (host->bus_hz != slot->clock) ? DIV_ROUND_UP(div, 2) : 0;
|
div = (host->bus_hz != clock) ? DIV_ROUND_UP(div, 2) : 0;
|
||||||
|
|
||||||
dev_info(&slot->mmc->class_dev,
|
if ((clock << div) != slot->__clk_old || force_clkinit)
|
||||||
"Bus speed (slot %d) = %dHz (slot req %dHz, actual %dHZ"
|
dev_info(&slot->mmc->class_dev,
|
||||||
" div = %d)\n", slot->id, host->bus_hz, slot->clock,
|
"Bus speed (slot %d) = %dHz (slot req %dHz, actual %dHZ div = %d)\n",
|
||||||
div ? ((host->bus_hz / div) >> 1) : host->bus_hz, div);
|
slot->id, host->bus_hz, clock,
|
||||||
|
div ? ((host->bus_hz / div) >> 1) :
|
||||||
|
host->bus_hz, div);
|
||||||
|
|
||||||
/* disable clock */
|
/* disable clock */
|
||||||
mci_writel(host, CLKENA, 0);
|
mci_writel(host, CLKENA, 0);
|
||||||
@ -676,9 +687,12 @@ static void dw_mci_setup_bus(struct dw_mci_slot *slot, bool force_clkinit)
|
|||||||
mci_send_cmd(slot,
|
mci_send_cmd(slot,
|
||||||
SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0);
|
SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0);
|
||||||
|
|
||||||
host->current_speed = slot->clock;
|
/* keep the clock with reflecting clock dividor */
|
||||||
|
slot->__clk_old = clock << div;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host->current_speed = clock;
|
||||||
|
|
||||||
/* Set the current slot bus width */
|
/* Set the current slot bus width */
|
||||||
mci_writel(host, CTYPE, (slot->ctype << slot->id));
|
mci_writel(host, CTYPE, (slot->ctype << slot->id));
|
||||||
}
|
}
|
||||||
@ -807,13 +821,11 @@ static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|||||||
|
|
||||||
mci_writel(slot->host, UHS_REG, regs);
|
mci_writel(slot->host, UHS_REG, regs);
|
||||||
|
|
||||||
if (ios->clock) {
|
/*
|
||||||
/*
|
* Use mirror of ios->clock to prevent race with mmc
|
||||||
* Use mirror of ios->clock to prevent race with mmc
|
* core ios update when finding the minimum.
|
||||||
* core ios update when finding the minimum.
|
*/
|
||||||
*/
|
slot->clock = ios->clock;
|
||||||
slot->clock = ios->clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drv_data && drv_data->set_ios)
|
if (drv_data && drv_data->set_ios)
|
||||||
drv_data->set_ios(slot->host, ios);
|
drv_data->set_ios(slot->host, ios);
|
||||||
|
Loading…
Reference in New Issue
Block a user