diff options
Diffstat (limited to 'drivers/mmc/host/omap.c')
| -rw-r--r-- | drivers/mmc/host/omap.c | 227 |
1 files changed, 142 insertions, 85 deletions
diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index bd49f34d7654..527b89a5ed70 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * linux/drivers/mmc/host/omap.c * @@ -5,10 +6,6 @@ * Written by Tuukka Tikkanen and Juha Yrjölä<juha.yrjola@nokia.com> * Misc hacks here and there by Tony Lindgren <tony@atomide.com> * Other hacks (DMA, SD, etc) by David Brownell - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #include <linux/module.h> @@ -29,7 +26,9 @@ #include <linux/clk.h> #include <linux/scatterlist.h> #include <linux/slab.h> +#include <linux/gpio/consumer.h> #include <linux/platform_data/mmc-omap.h> +#include <linux/workqueue.h> #define OMAP_MMC_REG_CMD 0x00 @@ -104,15 +103,19 @@ struct mmc_omap_slot { unsigned int vdd; u16 saved_con; u16 bus_mode; + u16 power_mode; unsigned int fclk_freq; - struct tasklet_struct cover_tasklet; + struct work_struct cover_bh_work; struct timer_list cover_timer; unsigned cover_open; struct mmc_request *mrq; struct mmc_omap_host *host; struct mmc_host *mmc; + struct gpio_desc *vsd; + struct gpio_desc *vio; + struct gpio_desc *cover; struct omap_mmc_slot_data *pdata; }; @@ -135,6 +138,7 @@ struct mmc_omap_host { int irq; unsigned char bus_mode; unsigned int reg_shift; + struct gpio_desc *slot_switch; struct work_struct cmd_abort_work; unsigned abort:1; @@ -145,10 +149,8 @@ struct mmc_omap_host { struct work_struct send_stop_work; struct mmc_data *stop_data; + struct sg_mapping_iter sg_miter; unsigned int sg_len; - int sg_idx; - u16 * buffer; - u32 buffer_bytes_left; u32 total_bytes_left; unsigned features; @@ -212,14 +214,19 @@ static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed) host->mmc = slot->mmc; spin_unlock_irqrestore(&host->slot_lock, flags); no_claim: - del_timer(&host->clk_timer); + timer_delete(&host->clk_timer); if (host->current_slot != slot || !claimed) mmc_omap_fclk_offdelay(host->current_slot); if (host->current_slot != slot) { OMAP_MMC_WRITE(host, CON, slot->saved_con & 0xFC00); - if (host->pdata->switch_slot != NULL) - host->pdata->switch_slot(mmc_dev(slot->mmc), slot->id); + if (host->slot_switch) + /* + * With two slots and a simple GPIO switch, setting + * the GPIO to 0 selects slot ID 0, setting it to 1 + * selects slot ID 1. + */ + gpiod_set_value(host->slot_switch, slot->id); host->current_slot = slot; } @@ -266,7 +273,7 @@ static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled) /* Keeps clock running for at least 8 cycles on valid freq */ mod_timer(&host->clk_timer, jiffies + HZ/10); else { - del_timer(&host->clk_timer); + timer_delete(&host->clk_timer); mmc_omap_fclk_offdelay(slot); mmc_omap_fclk_enable(host, 0); } @@ -299,6 +306,9 @@ static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled) static inline int mmc_omap_cover_is_open(struct mmc_omap_slot *slot) { + /* If we have a GPIO then use that */ + if (slot->cover) + return gpiod_get_value(slot->cover); if (slot->pdata->get_cover_state) return slot->pdata->get_cover_state(mmc_dev(slot->mmc), slot->id); @@ -316,7 +326,7 @@ mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr, "closed"); } -static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL); +static DEVICE_ATTR(cover_switch, 0444, mmc_omap_show_cover_switch, NULL); static ssize_t mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr, @@ -328,7 +338,7 @@ mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%s\n", slot->pdata->name); } -static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL); +static DEVICE_ATTR(slot_name, 0444, mmc_omap_show_slot_name, NULL); static void mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd) @@ -445,6 +455,8 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) { if (host->dma_in_use) mmc_omap_release_dma(host, data, data->error); + else + sg_miter_stop(&host->sg_miter); host->data = NULL; host->sg_len = 0; @@ -552,7 +564,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) { host->cmd = NULL; - del_timer(&host->cmd_abort_timer); + timer_delete(&host->cmd_abort_timer); if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { @@ -625,9 +637,10 @@ static void mmc_omap_abort_command(struct work_struct *work) } static void -mmc_omap_cmd_timer(unsigned long data) +mmc_omap_cmd_timer(struct timer_list *t) { - struct mmc_omap_host *host = (struct mmc_omap_host *) data; + struct mmc_omap_host *host = timer_container_of(host, t, + cmd_abort_timer); unsigned long flags; spin_lock_irqsave(&host->slot_lock, flags); @@ -640,23 +653,10 @@ mmc_omap_cmd_timer(unsigned long data) spin_unlock_irqrestore(&host->slot_lock, flags); } -/* PIO only */ static void -mmc_omap_sg_to_buf(struct mmc_omap_host *host) +mmc_omap_clk_timer(struct timer_list *t) { - struct scatterlist *sg; - - sg = host->data->sg + host->sg_idx; - host->buffer_bytes_left = sg->length; - host->buffer = sg_virt(sg); - if (host->buffer_bytes_left > host->total_bytes_left) - host->buffer_bytes_left = host->total_bytes_left; -} - -static void -mmc_omap_clk_timer(unsigned long data) -{ - struct mmc_omap_host *host = (struct mmc_omap_host *) data; + struct mmc_omap_host *host = timer_container_of(host, t, clk_timer); mmc_omap_fclk_enable(host, 0); } @@ -665,33 +665,37 @@ mmc_omap_clk_timer(unsigned long data) static void mmc_omap_xfer_data(struct mmc_omap_host *host, int write) { + struct sg_mapping_iter *sgm = &host->sg_miter; int n, nwords; + u16 *buffer; - if (host->buffer_bytes_left == 0) { - host->sg_idx++; - BUG_ON(host->sg_idx == host->sg_len); - mmc_omap_sg_to_buf(host); + if (!sg_miter_next(sgm)) { + /* This should not happen */ + dev_err(mmc_dev(host->mmc), "ran out of scatterlist prematurely\n"); + return; } + buffer = sgm->addr; + n = 64; - if (n > host->buffer_bytes_left) - n = host->buffer_bytes_left; + if (n > sgm->length) + n = sgm->length; + if (n > host->total_bytes_left) + n = host->total_bytes_left; /* Round up to handle odd number of bytes to transfer */ nwords = DIV_ROUND_UP(n, 2); - host->buffer_bytes_left -= n; + sgm->consumed = n; host->total_bytes_left -= n; host->data->bytes_xfered += n; if (write) { __raw_writesw(host->virt_base + OMAP_MMC_REG(host, DATA), - host->buffer, nwords); + buffer, nwords); } else { __raw_readsw(host->virt_base + OMAP_MMC_REG(host, DATA), - host->buffer, nwords); + buffer, nwords); } - - host->buffer += nwords; } #ifdef CONFIG_MMC_DEBUG @@ -833,7 +837,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) } if (cmd_error && host->data) { - del_timer(&host->cmd_abort_timer); + timer_delete(&host->cmd_abort_timer); host->abort = 1; OMAP_MMC_WRITE(host, IE, 0); disable_irq_nosync(host->irq); @@ -871,18 +875,18 @@ void omap_mmc_notify_cover_event(struct device *dev, int num, int is_closed) sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch"); } - tasklet_hi_schedule(&slot->cover_tasklet); + queue_work(system_bh_highpri_wq, &slot->cover_bh_work); } -static void mmc_omap_cover_timer(unsigned long arg) +static void mmc_omap_cover_timer(struct timer_list *t) { - struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg; - tasklet_schedule(&slot->cover_tasklet); + struct mmc_omap_slot *slot = timer_container_of(slot, t, cover_timer); + queue_work(system_bh_wq, &slot->cover_bh_work); } -static void mmc_omap_cover_handler(unsigned long param) +static void mmc_omap_cover_bh_handler(struct work_struct *t) { - struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param; + struct mmc_omap_slot *slot = from_work(slot, t, cover_bh_work); int cover_open = mmc_omap_cover_is_open(slot); mmc_detect_change(slot->mmc, 0); @@ -919,7 +923,7 @@ static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_reques reg &= ~(1 << 5); OMAP_MMC_WRITE(host, SDIO, reg); /* Set maximum timeout */ - OMAP_MMC_WRITE(host, CTO, 0xff); + OMAP_MMC_WRITE(host, CTO, 0xfd); } static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_request *req) @@ -945,6 +949,7 @@ static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_reque static void mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) { + unsigned int miter_flags = SG_MITER_ATOMIC; /* Used from IRQ */ struct mmc_data *data = req->data; int i, use_dma = 1, block_size; struct scatterlist *sg; @@ -979,7 +984,6 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) } } - host->sg_idx = 0; if (use_dma) { enum dma_data_direction dma_data_dir; struct dma_async_tx_descriptor *tx; @@ -1060,7 +1064,11 @@ mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req) OMAP_MMC_WRITE(host, BUF, 0x1f1f); host->total_bytes_left = data->blocks * block_size; host->sg_len = sg_len; - mmc_omap_sg_to_buf(host); + if (data->flags & MMC_DATA_READ) + miter_flags |= SG_MITER_TO_SG; + else + miter_flags |= SG_MITER_FROM_SG; + sg_miter_start(&host->sg_miter, data->sg, data->sg_len, miter_flags); host->dma_in_use = 0; } @@ -1108,6 +1116,26 @@ static void mmc_omap_set_power(struct mmc_omap_slot *slot, int power_on, host = slot->host; + if (power_on) { + if (slot->vsd) { + gpiod_set_value(slot->vsd, power_on); + msleep(1); + } + if (slot->vio) { + gpiod_set_value(slot->vio, power_on); + msleep(1); + } + } else { + if (slot->vio) { + gpiod_set_value(slot->vio, power_on); + msleep(50); + } + if (slot->vsd) { + gpiod_set_value(slot->vsd, power_on); + msleep(50); + } + } + if (slot->pdata->set_power != NULL) slot->pdata->set_power(mmc_dev(slot->mmc), slot->id, power_on, vdd); @@ -1157,7 +1185,7 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) struct mmc_omap_slot *slot = mmc_priv(mmc); struct mmc_omap_host *host = slot->host; int i, dsor; - int clk_enabled; + int clk_enabled, init_stream; mmc_omap_select_slot(slot, 0); @@ -1167,6 +1195,7 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) slot->vdd = ios->vdd; clk_enabled = 0; + init_stream = 0; switch (ios->power_mode) { case MMC_POWER_OFF: mmc_omap_set_power(slot, 0, ios->vdd); @@ -1174,13 +1203,17 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) case MMC_POWER_UP: /* Cannot touch dsor yet, just power up MMC */ mmc_omap_set_power(slot, 1, ios->vdd); + slot->power_mode = ios->power_mode; goto exit; case MMC_POWER_ON: mmc_omap_fclk_enable(host, 1); clk_enabled = 1; dsor |= 1 << 11; + if (slot->power_mode != MMC_POWER_ON) + init_stream = 1; break; } + slot->power_mode = ios->power_mode; if (slot->bus_mode != ios->bus_mode) { if (slot->pdata->set_bus_mode != NULL) @@ -1196,7 +1229,7 @@ static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) for (i = 0; i < 2; i++) OMAP_MMC_WRITE(host, CON, dsor); slot->saved_con = dsor; - if (ios->power_mode == MMC_POWER_ON) { + if (init_stream) { /* worst case at 400kHz, 80 cycles makes 200 microsecs */ int usecs = 250; @@ -1226,7 +1259,7 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) struct mmc_host *mmc; int r; - mmc = mmc_alloc_host(sizeof(struct mmc_omap_slot), host->dev); + mmc = devm_mmc_alloc_host(host->dev, sizeof(*slot)); if (mmc == NULL) return -ENOMEM; @@ -1234,13 +1267,33 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) slot->host = host; slot->mmc = mmc; slot->id = id; + slot->power_mode = MMC_POWER_UNDEFINED; slot->pdata = &host->pdata->slots[id]; + /* Check for some optional GPIO controls */ + slot->vsd = devm_gpiod_get_index_optional(host->dev, "vsd", + id, GPIOD_OUT_LOW); + if (IS_ERR(slot->vsd)) + return dev_err_probe(host->dev, PTR_ERR(slot->vsd), + "error looking up VSD GPIO\n"); + + slot->vio = devm_gpiod_get_index_optional(host->dev, "vio", + id, GPIOD_OUT_LOW); + if (IS_ERR(slot->vio)) + return dev_err_probe(host->dev, PTR_ERR(slot->vio), + "error looking up VIO GPIO\n"); + + slot->cover = devm_gpiod_get_index_optional(host->dev, "cover", + id, GPIOD_IN); + if (IS_ERR(slot->cover)) + return dev_err_probe(host->dev, PTR_ERR(slot->cover), + "error looking up cover switch GPIO\n"); + host->slots[id] = slot; mmc->caps = 0; if (host->pdata->slots[id].wires >= 4) - mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_ERASE; + mmc->caps |= MMC_CAP_4_BIT_DATA; mmc->ops = &mmc_omap_ops; mmc->f_min = 400000; @@ -1264,10 +1317,8 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) mmc->max_seg_size = mmc->max_req_size; if (slot->pdata->get_cover_state != NULL) { - setup_timer(&slot->cover_timer, mmc_omap_cover_timer, - (unsigned long)slot); - tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler, - (unsigned long)slot); + timer_setup(&slot->cover_timer, mmc_omap_cover_timer, 0); + INIT_WORK(&slot->cover_bh_work, mmc_omap_cover_bh_handler); } r = mmc_add_host(mmc); @@ -1286,7 +1337,7 @@ static int mmc_omap_new_slot(struct mmc_omap_host *host, int id) &dev_attr_cover_switch); if (r < 0) goto err_remove_slot_name; - tasklet_schedule(&slot->cover_tasklet); + queue_work(system_bh_wq, &slot->cover_bh_work); } return 0; @@ -1296,7 +1347,6 @@ err_remove_slot_name: device_remove_file(&mmc->class_dev, &dev_attr_slot_name); err_remove_host: mmc_remove_host(mmc); - mmc_free_host(mmc); return r; } @@ -1309,12 +1359,11 @@ static void mmc_omap_remove_slot(struct mmc_omap_slot *slot) if (slot->pdata->get_cover_state != NULL) device_remove_file(&mmc->class_dev, &dev_attr_cover_switch); - tasklet_kill(&slot->cover_tasklet); - del_timer_sync(&slot->cover_timer); + cancel_work_sync(&slot->cover_bh_work); + timer_delete_sync(&slot->cover_timer); flush_workqueue(slot->host->mmc_omap_wq); mmc_remove_host(mmc); - mmc_free_host(mmc); } static int mmc_omap_probe(struct platform_device *pdev) @@ -1341,10 +1390,9 @@ static int mmc_omap_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) - return -ENXIO; + return irq; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - host->virt_base = devm_ioremap_resource(&pdev->dev, res); + host->virt_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(host->virt_base)) return PTR_ERR(host->virt_base); @@ -1352,11 +1400,10 @@ static int mmc_omap_probe(struct platform_device *pdev) INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work); INIT_WORK(&host->cmd_abort_work, mmc_omap_abort_command); - setup_timer(&host->cmd_abort_timer, mmc_omap_cmd_timer, - (unsigned long) host); + timer_setup(&host->cmd_abort_timer, mmc_omap_cmd_timer, 0); spin_lock_init(&host->clk_lock); - setup_timer(&host->clk_timer, mmc_omap_clk_timer, (unsigned long) host); + timer_setup(&host->clk_timer, mmc_omap_clk_timer, 0); spin_lock_init(&host->dma_lock); spin_lock_init(&host->slot_lock); @@ -1367,13 +1414,19 @@ static int mmc_omap_probe(struct platform_device *pdev) host->dev = &pdev->dev; platform_set_drvdata(pdev, host); + host->slot_switch = devm_gpiod_get_optional(host->dev, "switch", + GPIOD_OUT_LOW); + if (IS_ERR(host->slot_switch)) + return dev_err_probe(host->dev, PTR_ERR(host->slot_switch), + "error looking up slot switch GPIO\n"); + host->id = pdev->id; host->irq = irq; host->phys_base = res->start; host->iclk = clk_get(&pdev->dev, "ick"); if (IS_ERR(host->iclk)) return PTR_ERR(host->iclk); - clk_enable(host->iclk); + clk_prepare_enable(host->iclk); host->fclk = clk_get(&pdev->dev, "fck"); if (IS_ERR(host->fclk)) { @@ -1381,16 +1434,18 @@ static int mmc_omap_probe(struct platform_device *pdev) goto err_free_iclk; } + ret = clk_prepare(host->fclk); + if (ret) + goto err_put_fclk; + host->dma_tx_burst = -1; host->dma_rx_burst = -1; host->dma_tx = dma_request_chan(&pdev->dev, "tx"); if (IS_ERR(host->dma_tx)) { ret = PTR_ERR(host->dma_tx); - if (ret == -EPROBE_DEFER) { - clk_put(host->fclk); - goto err_free_iclk; - } + if (ret == -EPROBE_DEFER) + goto err_free_fclk; host->dma_tx = NULL; dev_warn(host->dev, "TX DMA channel request failed\n"); @@ -1402,8 +1457,7 @@ static int mmc_omap_probe(struct platform_device *pdev) if (ret == -EPROBE_DEFER) { if (host->dma_tx) dma_release_channel(host->dma_tx); - clk_put(host->fclk); - goto err_free_iclk; + goto err_free_fclk; } host->dma_rx = NULL; @@ -1423,7 +1477,7 @@ static int mmc_omap_probe(struct platform_device *pdev) host->nr_slots = pdata->nr_slots; host->reg_shift = (mmc_omap7xx() ? 1 : 2); - host->mmc_omap_wq = alloc_workqueue("mmc_omap", 0, 0); + host->mmc_omap_wq = alloc_workqueue("mmc_omap", WQ_PERCPU, 0); if (!host->mmc_omap_wq) { ret = -ENOMEM; goto err_plat_cleanup; @@ -1453,14 +1507,17 @@ err_free_dma: dma_release_channel(host->dma_tx); if (host->dma_rx) dma_release_channel(host->dma_rx); +err_free_fclk: + clk_unprepare(host->fclk); +err_put_fclk: clk_put(host->fclk); err_free_iclk: - clk_disable(host->iclk); + clk_disable_unprepare(host->iclk); clk_put(host->iclk); return ret; } -static int mmc_omap_remove(struct platform_device *pdev) +static void mmc_omap_remove(struct platform_device *pdev) { struct mmc_omap_host *host = platform_get_drvdata(pdev); int i; @@ -1475,8 +1532,9 @@ static int mmc_omap_remove(struct platform_device *pdev) mmc_omap_fclk_enable(host, 0); free_irq(host->irq, host); + clk_unprepare(host->fclk); clk_put(host->fclk); - clk_disable(host->iclk); + clk_disable_unprepare(host->iclk); clk_put(host->iclk); if (host->dma_tx) @@ -1485,8 +1543,6 @@ static int mmc_omap_remove(struct platform_device *pdev) dma_release_channel(host->dma_rx); destroy_workqueue(host->mmc_omap_wq); - - return 0; } #if IS_BUILTIN(CONFIG_OF) @@ -1502,6 +1558,7 @@ static struct platform_driver mmc_omap_driver = { .remove = mmc_omap_remove, .driver = { .name = DRIVER_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, .of_match_table = of_match_ptr(mmc_omap_match), }, }; |
