diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-11 14:34:22 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-11 14:34:22 +0400 |
commit | f43b179bbd84d2106c6fb8c7f2db1bfacff19f62 (patch) | |
tree | 581e60846d1298276fac0b768ba12e06d7600d1e /drivers/mmc/host/jz4740_mmc.c | |
parent | a2ce35273c2f1aa0dcddd8822681d64ee5f31852 (diff) | |
parent | 583f958b8a23343c904ec0790c25c96f5f82d409 (diff) | |
download | linux-f43b179bbd84d2106c6fb8c7f2db1bfacff19f62.tar.xz |
Merge tag 'mmc-v3.18-1' of git://git.linaro.org/people/ulf.hansson/mmc
Pull MMC updates from Ulf Hansson:
"MMC core:
- Fix SDIO IRQ bug
- MMC regulator improvements
- Fix slot-gpio card detect bug
- Add support for Driver Stage Register
- Convert the common MMC OF parser to use GPIO descriptors
- Convert MMC_CAP2_NO_MULTI_READ into a callback, ->multi_io_quirk()
- Some additional minor fixes
MMC host:
- mmci: Support Qualcomm specific DML layer for DMA
- dw_mmc: Use common MMC regulators
- dw_mmc: Add support for Rock-chips RK3288
- tmio: Enable runtime PM support
- tmio: Add support for R-Car Gen2 SoCs
- tmio: Several fixes and improvements
- omap_hsmmc: Removed Balaji from MAINTAINERS
- jz4740: add DMA and pre/post support
- sdhci: Add support for Intel Braswell
- sdhci: Several fixes and improvements"
* tag 'mmc-v3.18-1' of git://git.linaro.org/people/ulf.hansson/mmc: (119 commits)
ARM: dts: fix MMC2 regulators for Exynos5420 Arndale Octa board
mmc: sdhci-acpi: Fix Braswell eMMC timeout clock frequency
mmc: sdhci-acpi: Pass HID and UID to probe_slot
mmc: sdhci-acpi: Get UID directly from acpi_device
mmc, sdhci, bcm-kona, LLVMLinux: Remove use of __initconst
mmc: sdhci-pci: Fix Braswell eMMC timeout clock frequency
mmc: sdhci: Let a driver override timeout clock frequency
mmc: sdhci-pci: Add Bay Trail and Braswell SD card detect
mmc: sdhci-pci: Set SDHCI_QUIRK2_STOP_WITH_TC for Intel BYT host controllers
mmc: sdhci-acpi: Add a HID and UID for a SD Card host controller
mmc: sdhci-acpi: Set SDHCI_QUIRK2_STOP_WITH_TC for Intel host controllers
mmc: sdhci: Add quirk for always getting TC with stop cmd
mmc: core: restore detect line inversion semantics
mmc: Fix incorrect warning when setting 0 Hz via debugfs
mmc: Fix use of wrong device in mmc_gpiod_free_cd()
mmc: atmel-mci: fix mismatched section on atmci_cleanup_slot
mmc: rtsx_pci: Set power related cap2 macros
mmc: core: Add new power_mode MMC_POWER_UNDEFINED
mmc: sdhci: execute tuning when device is not busy
mmc: atmel-mci: Release mmc resources on failure in probe
..
Diffstat (limited to 'drivers/mmc/host/jz4740_mmc.c')
-rw-r--r-- | drivers/mmc/host/jz4740_mmc.c | 269 |
1 files changed, 260 insertions, 9 deletions
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c index 537d6c7a5ae4..76e8bce6f46e 100644 --- a/drivers/mmc/host/jz4740_mmc.c +++ b/drivers/mmc/host/jz4740_mmc.c @@ -30,7 +30,9 @@ #include <asm/mach-jz4740/gpio.h> #include <asm/cacheflush.h> #include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <asm/mach-jz4740/dma.h> #include <asm/mach-jz4740/jz4740_mmc.h> #define JZ_REG_MMC_STRPCL 0x00 @@ -112,6 +114,11 @@ enum jz4740_mmc_state { JZ4740_MMC_STATE_DONE, }; +struct jz4740_mmc_host_next { + int sg_len; + s32 cookie; +}; + struct jz4740_mmc_host { struct mmc_host *mmc; struct platform_device *pdev; @@ -122,6 +129,7 @@ struct jz4740_mmc_host { int card_detect_irq; void __iomem *base; + struct resource *mem_res; struct mmc_request *req; struct mmc_command *cmd; @@ -136,8 +144,220 @@ struct jz4740_mmc_host { struct timer_list timeout_timer; struct sg_mapping_iter miter; enum jz4740_mmc_state state; + + /* DMA support */ + struct dma_chan *dma_rx; + struct dma_chan *dma_tx; + struct jz4740_mmc_host_next next_data; + bool use_dma; + int sg_len; + +/* The DMA trigger level is 8 words, that is to say, the DMA read + * trigger is when data words in MSC_RXFIFO is >= 8 and the DMA write + * trigger is when data words in MSC_TXFIFO is < 8. + */ +#define JZ4740_MMC_FIFO_HALF_SIZE 8 }; +/*----------------------------------------------------------------------------*/ +/* DMA infrastructure */ + +static void jz4740_mmc_release_dma_channels(struct jz4740_mmc_host *host) +{ + if (!host->use_dma) + return; + + dma_release_channel(host->dma_tx); + dma_release_channel(host->dma_rx); +} + +static int jz4740_mmc_acquire_dma_channels(struct jz4740_mmc_host *host) +{ + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + host->dma_tx = dma_request_channel(mask, NULL, host); + if (!host->dma_tx) { + dev_err(mmc_dev(host->mmc), "Failed to get dma_tx channel\n"); + return -ENODEV; + } + + host->dma_rx = dma_request_channel(mask, NULL, host); + if (!host->dma_rx) { + dev_err(mmc_dev(host->mmc), "Failed to get dma_rx channel\n"); + goto free_master_write; + } + + /* Initialize DMA pre request cookie */ + host->next_data.cookie = 1; + + return 0; + +free_master_write: + dma_release_channel(host->dma_tx); + return -ENODEV; +} + +static inline int jz4740_mmc_get_dma_dir(struct mmc_data *data) +{ + return (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; +} + +static inline struct dma_chan *jz4740_mmc_get_dma_chan(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + return (data->flags & MMC_DATA_READ) ? host->dma_rx : host->dma_tx; +} + +static void jz4740_mmc_dma_unmap(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct dma_chan *chan = jz4740_mmc_get_dma_chan(host, data); + enum dma_data_direction dir = jz4740_mmc_get_dma_dir(data); + + dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); +} + +/* Prepares DMA data for current/next transfer, returns non-zero on failure */ +static int jz4740_mmc_prepare_dma_data(struct jz4740_mmc_host *host, + struct mmc_data *data, + struct jz4740_mmc_host_next *next, + struct dma_chan *chan) +{ + struct jz4740_mmc_host_next *next_data = &host->next_data; + enum dma_data_direction dir = jz4740_mmc_get_dma_dir(data); + int sg_len; + + if (!next && data->host_cookie && + data->host_cookie != host->next_data.cookie) { + dev_warn(mmc_dev(host->mmc), + "[%s] invalid cookie: data->host_cookie %d host->next_data.cookie %d\n", + __func__, + data->host_cookie, + host->next_data.cookie); + data->host_cookie = 0; + } + + /* Check if next job is already prepared */ + if (next || data->host_cookie != host->next_data.cookie) { + sg_len = dma_map_sg(chan->device->dev, + data->sg, + data->sg_len, + dir); + + } else { + sg_len = next_data->sg_len; + next_data->sg_len = 0; + } + + if (sg_len <= 0) { + dev_err(mmc_dev(host->mmc), + "Failed to map scatterlist for DMA operation\n"); + return -EINVAL; + } + + if (next) { + next->sg_len = sg_len; + data->host_cookie = ++next->cookie < 0 ? 1 : next->cookie; + } else + host->sg_len = sg_len; + + return 0; +} + +static int jz4740_mmc_start_dma_transfer(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + int ret; + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + struct dma_slave_config conf = { + .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .src_maxburst = JZ4740_MMC_FIFO_HALF_SIZE, + .dst_maxburst = JZ4740_MMC_FIFO_HALF_SIZE, + }; + + if (data->flags & MMC_DATA_WRITE) { + conf.direction = DMA_MEM_TO_DEV; + conf.dst_addr = host->mem_res->start + JZ_REG_MMC_TXFIFO; + conf.slave_id = JZ4740_DMA_TYPE_MMC_TRANSMIT; + chan = host->dma_tx; + } else { + conf.direction = DMA_DEV_TO_MEM; + conf.src_addr = host->mem_res->start + JZ_REG_MMC_RXFIFO; + conf.slave_id = JZ4740_DMA_TYPE_MMC_RECEIVE; + chan = host->dma_rx; + } + + ret = jz4740_mmc_prepare_dma_data(host, data, NULL, chan); + if (ret) + return ret; + + dmaengine_slave_config(chan, &conf); + desc = dmaengine_prep_slave_sg(chan, + data->sg, + host->sg_len, + conf.direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(mmc_dev(host->mmc), + "Failed to allocate DMA %s descriptor", + conf.direction == DMA_MEM_TO_DEV ? "TX" : "RX"); + goto dma_unmap; + } + + dmaengine_submit(desc); + dma_async_issue_pending(chan); + + return 0; + +dma_unmap: + jz4740_mmc_dma_unmap(host, data); + return -ENOMEM; +} + +static void jz4740_mmc_pre_request(struct mmc_host *mmc, + struct mmc_request *mrq, + bool is_first_req) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + struct jz4740_mmc_host_next *next_data = &host->next_data; + + BUG_ON(data->host_cookie); + + if (host->use_dma) { + struct dma_chan *chan = jz4740_mmc_get_dma_chan(host, data); + + if (jz4740_mmc_prepare_dma_data(host, data, next_data, chan)) + data->host_cookie = 0; + } +} + +static void jz4740_mmc_post_request(struct mmc_host *mmc, + struct mmc_request *mrq, + int err) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (host->use_dma && data->host_cookie) { + jz4740_mmc_dma_unmap(host, data); + data->host_cookie = 0; + } + + if (err) { + struct dma_chan *chan = jz4740_mmc_get_dma_chan(host, data); + + dmaengine_terminate_all(chan); + } +} + +/*----------------------------------------------------------------------------*/ + static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host, unsigned int irq, bool enabled) { @@ -442,6 +662,8 @@ static void jz4740_mmc_send_command(struct jz4740_mmc_host *host, cmdat |= JZ_MMC_CMDAT_WRITE; if (cmd->data->flags & MMC_DATA_STREAM) cmdat |= JZ_MMC_CMDAT_STREAM; + if (host->use_dma) + cmdat |= JZ_MMC_CMDAT_DMA_EN; writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN); writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB); @@ -474,6 +696,7 @@ static irqreturn_t jz_mmc_irq_worker(int irq, void *devid) struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid; struct mmc_command *cmd = host->req->cmd; struct mmc_request *req = host->req; + struct mmc_data *data = cmd->data; bool timeout = false; if (cmd->error) @@ -484,23 +707,37 @@ static irqreturn_t jz_mmc_irq_worker(int irq, void *devid) if (cmd->flags & MMC_RSP_PRESENT) jz4740_mmc_read_response(host, cmd); - if (!cmd->data) + if (!data) break; jz_mmc_prepare_data_transfer(host); case JZ4740_MMC_STATE_TRANSFER_DATA: - if (cmd->data->flags & MMC_DATA_READ) - timeout = jz4740_mmc_read_data(host, cmd->data); + if (host->use_dma) { + /* Use DMA if enabled. + * Data transfer direction is defined later by + * relying on data flags in + * jz4740_mmc_prepare_dma_data() and + * jz4740_mmc_start_dma_transfer(). + */ + timeout = jz4740_mmc_start_dma_transfer(host, data); + data->bytes_xfered = data->blocks * data->blksz; + } else if (data->flags & MMC_DATA_READ) + /* Use PIO if DMA is not enabled. + * Data transfer direction was defined before + * by relying on data flags in + * jz_mmc_prepare_data_transfer(). + */ + timeout = jz4740_mmc_read_data(host, data); else - timeout = jz4740_mmc_write_data(host, cmd->data); + timeout = jz4740_mmc_write_data(host, data); if (unlikely(timeout)) { host->state = JZ4740_MMC_STATE_TRANSFER_DATA; break; } - jz4740_mmc_transfer_check_state(host, cmd->data); + jz4740_mmc_transfer_check_state(host, data); timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE); if (unlikely(timeout)) { @@ -664,6 +901,8 @@ static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) static const struct mmc_host_ops jz4740_mmc_ops = { .request = jz4740_mmc_request, + .pre_req = jz4740_mmc_pre_request, + .post_req = jz4740_mmc_post_request, .set_ios = jz4740_mmc_set_ios, .get_ro = mmc_gpio_get_ro, .get_cd = mmc_gpio_get_cd, @@ -757,7 +996,6 @@ static int jz4740_mmc_probe(struct platform_device* pdev) struct mmc_host *mmc; struct jz4740_mmc_host *host; struct jz4740_mmc_platform_data *pdata; - struct resource *res; pdata = pdev->dev.platform_data; @@ -784,10 +1022,11 @@ static int jz4740_mmc_probe(struct platform_device* pdev) goto err_free_host; } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - host->base = devm_ioremap_resource(&pdev->dev, res); + host->mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->base = devm_ioremap_resource(&pdev->dev, host->mem_res); if (IS_ERR(host->base)) { ret = PTR_ERR(host->base); + dev_err(&pdev->dev, "Failed to ioremap base memory\n"); goto err_free_host; } @@ -834,6 +1073,10 @@ static int jz4740_mmc_probe(struct platform_device* pdev) /* It is not important when it times out, it just needs to timeout. */ set_timer_slack(&host->timeout_timer, HZ); + host->use_dma = true; + if (host->use_dma && jz4740_mmc_acquire_dma_channels(host) != 0) + host->use_dma = false; + platform_set_drvdata(pdev, host); ret = mmc_add_host(mmc); @@ -843,6 +1086,10 @@ static int jz4740_mmc_probe(struct platform_device* pdev) } dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n"); + dev_info(&pdev->dev, "Using %s, %d-bit mode\n", + host->use_dma ? "DMA" : "PIO", + (mmc->caps & MMC_CAP_4_BIT_DATA) ? 4 : 1); + return 0; err_free_irq: @@ -850,6 +1097,8 @@ err_free_irq: err_free_gpios: jz4740_mmc_free_gpios(pdev); err_gpio_bulk_free: + if (host->use_dma) + jz4740_mmc_release_dma_channels(host); jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); err_free_host: mmc_free_host(mmc); @@ -872,6 +1121,9 @@ static int jz4740_mmc_remove(struct platform_device *pdev) jz4740_mmc_free_gpios(pdev); jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + if (host->use_dma) + jz4740_mmc_release_dma_channels(host); + mmc_free_host(host->mmc); return 0; @@ -909,7 +1161,6 @@ static struct platform_driver jz4740_mmc_driver = { .remove = jz4740_mmc_remove, .driver = { .name = "jz4740-mmc", - .owner = THIS_MODULE, .pm = JZ4740_MMC_PM_OPS, }, }; |