123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- /*
- * DMA support for Internal DMAC with SDHI SD/SDIO controller
- *
- * Copyright (C) 2016-17 Renesas Electronics Corporation
- * Copyright (C) 2016-17 Horms Solutions, Simon Horman
- *
- * 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/device.h>
- #include <linux/dma-mapping.h>
- #include <linux/io-64-nonatomic-hi-lo.h>
- #include <linux/mfd/tmio.h>
- #include <linux/mmc/host.h>
- #include <linux/mod_devicetable.h>
- #include <linux/module.h>
- #include <linux/pagemap.h>
- #include <linux/scatterlist.h>
- #include <linux/sys_soc.h>
- #include "renesas_sdhi.h"
- #include "tmio_mmc.h"
- #define DM_CM_DTRAN_MODE 0x820
- #define DM_CM_DTRAN_CTRL 0x828
- #define DM_CM_RST 0x830
- #define DM_CM_INFO1 0x840
- #define DM_CM_INFO1_MASK 0x848
- #define DM_CM_INFO2 0x850
- #define DM_CM_INFO2_MASK 0x858
- #define DM_DTRAN_ADDR 0x880
- /* DM_CM_DTRAN_MODE */
- #define DTRAN_MODE_CH_NUM_CH0 0 /* "downstream" = for write commands */
- #define DTRAN_MODE_CH_NUM_CH1 BIT(16) /* "uptream" = for read commands */
- #define DTRAN_MODE_BUS_WID_TH (BIT(5) | BIT(4))
- #define DTRAN_MODE_ADDR_MODE BIT(0) /* 1 = Increment address */
- /* DM_CM_DTRAN_CTRL */
- #define DTRAN_CTRL_DM_START BIT(0)
- /* DM_CM_RST */
- #define RST_DTRANRST1 BIT(9)
- #define RST_DTRANRST0 BIT(8)
- #define RST_RESERVED_BITS GENMASK_ULL(32, 0)
- /* DM_CM_INFO1 and DM_CM_INFO1_MASK */
- #define INFO1_CLEAR 0
- #define INFO1_DTRANEND1 BIT(17)
- #define INFO1_DTRANEND0 BIT(16)
- /* DM_CM_INFO2 and DM_CM_INFO2_MASK */
- #define INFO2_DTRANERR1 BIT(17)
- #define INFO2_DTRANERR0 BIT(16)
- /*
- * Specification of this driver:
- * - host->chan_{rx,tx} will be used as a flag of enabling/disabling the dma
- * - Since this SDHI DMAC register set has 16 but 32-bit width, we
- * need a custom accessor.
- */
- /* Definitions for sampling clocks */
- static struct renesas_sdhi_scc rcar_gen3_scc_taps[] = {
- {
- .clk_rate = 0,
- .tap = 0x00000300,
- },
- };
- static const struct renesas_sdhi_of_data of_rcar_gen3_compatible = {
- .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE |
- TMIO_MMC_CLK_ACTUAL | TMIO_MMC_HAVE_CBSY |
- TMIO_MMC_MIN_RCAR2,
- .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ |
- MMC_CAP_CMD23,
- .bus_shift = 2,
- .scc_offset = 0x1000,
- .taps = rcar_gen3_scc_taps,
- .taps_num = ARRAY_SIZE(rcar_gen3_scc_taps),
- /* Gen3 SDHI DMAC can handle 0xffffffff blk count, but seg = 1 */
- .max_blk_count = 0xffffffff,
- .max_segs = 1,
- };
- static const struct of_device_id renesas_sdhi_internal_dmac_of_match[] = {
- { .compatible = "renesas,sdhi-r8a7795", .data = &of_rcar_gen3_compatible, },
- { .compatible = "renesas,sdhi-r8a7796", .data = &of_rcar_gen3_compatible, },
- {},
- };
- MODULE_DEVICE_TABLE(of, renesas_sdhi_internal_dmac_of_match);
- static void
- renesas_sdhi_internal_dmac_dm_write(struct tmio_mmc_host *host,
- int addr, u64 val)
- {
- writeq(val, host->ctl + addr);
- }
- static void
- renesas_sdhi_internal_dmac_enable_dma(struct tmio_mmc_host *host, bool enable)
- {
- if (!host->chan_tx || !host->chan_rx)
- return;
- if (!enable)
- renesas_sdhi_internal_dmac_dm_write(host, DM_CM_INFO1,
- INFO1_CLEAR);
- if (host->dma->enable)
- host->dma->enable(host, enable);
- }
- static void
- renesas_sdhi_internal_dmac_abort_dma(struct tmio_mmc_host *host) {
- u64 val = RST_DTRANRST1 | RST_DTRANRST0;
- renesas_sdhi_internal_dmac_enable_dma(host, false);
- renesas_sdhi_internal_dmac_dm_write(host, DM_CM_RST,
- RST_RESERVED_BITS & ~val);
- renesas_sdhi_internal_dmac_dm_write(host, DM_CM_RST,
- RST_RESERVED_BITS | val);
- renesas_sdhi_internal_dmac_enable_dma(host, true);
- }
- static void
- renesas_sdhi_internal_dmac_dataend_dma(struct tmio_mmc_host *host) {
- tasklet_schedule(&host->dma_complete);
- }
- static void
- renesas_sdhi_internal_dmac_start_dma(struct tmio_mmc_host *host,
- struct mmc_data *data)
- {
- struct scatterlist *sg = host->sg_ptr;
- u32 dtran_mode = DTRAN_MODE_BUS_WID_TH | DTRAN_MODE_ADDR_MODE;
- enum dma_data_direction dir;
- int ret;
- u32 irq_mask;
- /* This DMAC cannot handle if sg_len is not 1 */
- WARN_ON(host->sg_len > 1);
- /* This DMAC cannot handle if buffer is not 8-bytes alignment */
- if (!IS_ALIGNED(sg->offset, 8)) {
- host->force_pio = true;
- renesas_sdhi_internal_dmac_enable_dma(host, false);
- return;
- }
- if (data->flags & MMC_DATA_READ) {
- dtran_mode |= DTRAN_MODE_CH_NUM_CH1;
- dir = DMA_FROM_DEVICE;
- irq_mask = TMIO_STAT_RXRDY;
- } else {
- dtran_mode |= DTRAN_MODE_CH_NUM_CH0;
- dir = DMA_TO_DEVICE;
- irq_mask = TMIO_STAT_TXRQ;
- }
- ret = dma_map_sg(&host->pdev->dev, sg, host->sg_len, dir);
- if (ret < 0)
- return;
- renesas_sdhi_internal_dmac_enable_dma(host, true);
- /* disable PIO irqs to avoid "PIO IRQ in DMA mode!" */
- tmio_mmc_disable_mmc_irqs(host, irq_mask);
- /* set dma parameters */
- renesas_sdhi_internal_dmac_dm_write(host, DM_CM_DTRAN_MODE,
- dtran_mode);
- renesas_sdhi_internal_dmac_dm_write(host, DM_DTRAN_ADDR,
- sg->dma_address);
- }
- static void renesas_sdhi_internal_dmac_issue_tasklet_fn(unsigned long arg)
- {
- struct tmio_mmc_host *host = (struct tmio_mmc_host *)arg;
- tmio_mmc_enable_mmc_irqs(host, TMIO_STAT_DATAEND);
- /* start the DMAC */
- renesas_sdhi_internal_dmac_dm_write(host, DM_CM_DTRAN_CTRL,
- DTRAN_CTRL_DM_START);
- }
- static void renesas_sdhi_internal_dmac_complete_tasklet_fn(unsigned long arg)
- {
- struct tmio_mmc_host *host = (struct tmio_mmc_host *)arg;
- enum dma_data_direction dir;
- spin_lock_irq(&host->lock);
- if (!host->data)
- goto out;
- if (host->data->flags & MMC_DATA_READ)
- dir = DMA_FROM_DEVICE;
- else
- dir = DMA_TO_DEVICE;
- renesas_sdhi_internal_dmac_enable_dma(host, false);
- dma_unmap_sg(&host->pdev->dev, host->sg_ptr, host->sg_len, dir);
- tmio_mmc_do_data_irq(host);
- out:
- spin_unlock_irq(&host->lock);
- }
- static void
- renesas_sdhi_internal_dmac_request_dma(struct tmio_mmc_host *host,
- struct tmio_mmc_data *pdata)
- {
- /* Each value is set to non-zero to assume "enabling" each DMA */
- host->chan_rx = host->chan_tx = (void *)0xdeadbeaf;
- tasklet_init(&host->dma_complete,
- renesas_sdhi_internal_dmac_complete_tasklet_fn,
- (unsigned long)host);
- tasklet_init(&host->dma_issue,
- renesas_sdhi_internal_dmac_issue_tasklet_fn,
- (unsigned long)host);
- }
- static void
- renesas_sdhi_internal_dmac_release_dma(struct tmio_mmc_host *host)
- {
- /* Each value is set to zero to assume "disabling" each DMA */
- host->chan_rx = host->chan_tx = NULL;
- }
- static const struct tmio_mmc_dma_ops renesas_sdhi_internal_dmac_dma_ops = {
- .start = renesas_sdhi_internal_dmac_start_dma,
- .enable = renesas_sdhi_internal_dmac_enable_dma,
- .request = renesas_sdhi_internal_dmac_request_dma,
- .release = renesas_sdhi_internal_dmac_release_dma,
- .abort = renesas_sdhi_internal_dmac_abort_dma,
- .dataend = renesas_sdhi_internal_dmac_dataend_dma,
- };
- /*
- * Whitelist of specific R-Car Gen3 SoC ES versions to use this DMAC
- * implementation as others may use a different implementation.
- */
- static const struct soc_device_attribute gen3_soc_whitelist[] = {
- { .soc_id = "r8a7795", .revision = "ES1.*" },
- { .soc_id = "r8a7795", .revision = "ES2.0" },
- { .soc_id = "r8a7796", .revision = "ES1.0" },
- { /* sentinel */ }
- };
- static int renesas_sdhi_internal_dmac_probe(struct platform_device *pdev)
- {
- if (!soc_device_match(gen3_soc_whitelist))
- return -ENODEV;
- return renesas_sdhi_probe(pdev, &renesas_sdhi_internal_dmac_dma_ops);
- }
- static const struct dev_pm_ops renesas_sdhi_internal_dmac_dev_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
- pm_runtime_force_resume)
- SET_RUNTIME_PM_OPS(tmio_mmc_host_runtime_suspend,
- tmio_mmc_host_runtime_resume,
- NULL)
- };
- static struct platform_driver renesas_internal_dmac_sdhi_driver = {
- .driver = {
- .name = "renesas_sdhi_internal_dmac",
- .pm = &renesas_sdhi_internal_dmac_dev_pm_ops,
- .of_match_table = renesas_sdhi_internal_dmac_of_match,
- },
- .probe = renesas_sdhi_internal_dmac_probe,
- .remove = renesas_sdhi_remove,
- };
- module_platform_driver(renesas_internal_dmac_sdhi_driver);
- MODULE_DESCRIPTION("Renesas SDHI driver for internal DMAC");
- MODULE_AUTHOR("Yoshihiro Shimoda");
- MODULE_LICENSE("GPL v2");
|