162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Driver for MMC and SSD cards for Cavium OCTEON SOCs. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 562306a36Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 662306a36Sopenharmony_ci * for more details. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright (C) 2012-2017 Cavium Inc. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1162306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/mmc/mmc.h> 1462306a36Sopenharmony_ci#include <linux/mmc/slot-gpio.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/of.h> 1762306a36Sopenharmony_ci#include <linux/of_platform.h> 1862306a36Sopenharmony_ci#include <linux/platform_device.h> 1962306a36Sopenharmony_ci#include <asm/octeon/octeon.h> 2062306a36Sopenharmony_ci#include "cavium.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define CVMX_MIO_BOOT_CTL CVMX_ADD_IO_SEG(0x00011800000000D0ull) 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci/* 2562306a36Sopenharmony_ci * The l2c* functions below are used for the EMMC-17978 workaround. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * Due to a bug in the design of the MMC bus hardware, the 2nd to last 2862306a36Sopenharmony_ci * cache block of a DMA read must be locked into the L2 Cache. 2962306a36Sopenharmony_ci * Otherwise, data corruption may occur. 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_cistatic inline void *phys_to_ptr(u64 address) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci return (void *)(address | (1ull << 63)); /* XKPHYS */ 3462306a36Sopenharmony_ci} 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* 3762306a36Sopenharmony_ci * Lock a single line into L2. The line is zeroed before locking 3862306a36Sopenharmony_ci * to make sure no dram accesses are made. 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_cistatic void l2c_lock_line(u64 addr) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci char *addr_ptr = phys_to_ptr(addr); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci asm volatile ( 4562306a36Sopenharmony_ci "cache 31, %[line]" /* Unlock the line */ 4662306a36Sopenharmony_ci ::[line] "m" (*addr_ptr)); 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/* Unlock a single line in the L2 cache. */ 5062306a36Sopenharmony_cistatic void l2c_unlock_line(u64 addr) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci char *addr_ptr = phys_to_ptr(addr); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci asm volatile ( 5562306a36Sopenharmony_ci "cache 23, %[line]" /* Unlock the line */ 5662306a36Sopenharmony_ci ::[line] "m" (*addr_ptr)); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* Locks a memory region in the L2 cache. */ 6062306a36Sopenharmony_cistatic void l2c_lock_mem_region(u64 start, u64 len) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci u64 end; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci /* Round start/end to cache line boundaries */ 6562306a36Sopenharmony_ci end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE); 6662306a36Sopenharmony_ci start = ALIGN(start, CVMX_CACHE_LINE_SIZE); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci while (start <= end) { 6962306a36Sopenharmony_ci l2c_lock_line(start); 7062306a36Sopenharmony_ci start += CVMX_CACHE_LINE_SIZE; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci asm volatile("sync"); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* Unlock a memory region in the L2 cache. */ 7662306a36Sopenharmony_cistatic void l2c_unlock_mem_region(u64 start, u64 len) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci u64 end; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* Round start/end to cache line boundaries */ 8162306a36Sopenharmony_ci end = ALIGN(start + len - 1, CVMX_CACHE_LINE_SIZE); 8262306a36Sopenharmony_ci start = ALIGN(start, CVMX_CACHE_LINE_SIZE); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci while (start <= end) { 8562306a36Sopenharmony_ci l2c_unlock_line(start); 8662306a36Sopenharmony_ci start += CVMX_CACHE_LINE_SIZE; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic void octeon_mmc_acquire_bus(struct cvm_mmc_host *host) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci if (!host->has_ciu3) { 9362306a36Sopenharmony_ci down(&octeon_bootbus_sem); 9462306a36Sopenharmony_ci /* For CN70XX, switch the MMC controller onto the bus. */ 9562306a36Sopenharmony_ci if (OCTEON_IS_MODEL(OCTEON_CN70XX)) 9662306a36Sopenharmony_ci writeq(0, (void __iomem *)CVMX_MIO_BOOT_CTL); 9762306a36Sopenharmony_ci } else { 9862306a36Sopenharmony_ci down(&host->mmc_serializer); 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic void octeon_mmc_release_bus(struct cvm_mmc_host *host) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci if (!host->has_ciu3) 10562306a36Sopenharmony_ci up(&octeon_bootbus_sem); 10662306a36Sopenharmony_ci else 10762306a36Sopenharmony_ci up(&host->mmc_serializer); 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic void octeon_mmc_int_enable(struct cvm_mmc_host *host, u64 val) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci writeq(val, host->base + MIO_EMM_INT(host)); 11362306a36Sopenharmony_ci if (!host->has_ciu3) 11462306a36Sopenharmony_ci writeq(val, host->base + MIO_EMM_INT_EN(host)); 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic void octeon_mmc_set_shared_power(struct cvm_mmc_host *host, int dir) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci if (dir == 0) 12062306a36Sopenharmony_ci if (!atomic_dec_return(&host->shared_power_users)) 12162306a36Sopenharmony_ci gpiod_set_value_cansleep(host->global_pwr_gpiod, 0); 12262306a36Sopenharmony_ci if (dir == 1) 12362306a36Sopenharmony_ci if (atomic_inc_return(&host->shared_power_users) == 1) 12462306a36Sopenharmony_ci gpiod_set_value_cansleep(host->global_pwr_gpiod, 1); 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic void octeon_mmc_dmar_fixup(struct cvm_mmc_host *host, 12862306a36Sopenharmony_ci struct mmc_command *cmd, 12962306a36Sopenharmony_ci struct mmc_data *data, 13062306a36Sopenharmony_ci u64 addr) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci if (cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) 13362306a36Sopenharmony_ci return; 13462306a36Sopenharmony_ci if (data->blksz * data->blocks <= 1024) 13562306a36Sopenharmony_ci return; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci host->n_minus_one = addr + (data->blksz * data->blocks) - 1024; 13862306a36Sopenharmony_ci l2c_lock_mem_region(host->n_minus_one, 512); 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic void octeon_mmc_dmar_fixup_done(struct cvm_mmc_host *host) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci if (!host->n_minus_one) 14462306a36Sopenharmony_ci return; 14562306a36Sopenharmony_ci l2c_unlock_mem_region(host->n_minus_one, 512); 14662306a36Sopenharmony_ci host->n_minus_one = 0; 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic int octeon_mmc_probe(struct platform_device *pdev) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci struct device_node *cn, *node = pdev->dev.of_node; 15262306a36Sopenharmony_ci struct cvm_mmc_host *host; 15362306a36Sopenharmony_ci void __iomem *base; 15462306a36Sopenharmony_ci int mmc_irq[9]; 15562306a36Sopenharmony_ci int i, ret = 0; 15662306a36Sopenharmony_ci u64 val; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); 15962306a36Sopenharmony_ci if (!host) 16062306a36Sopenharmony_ci return -ENOMEM; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci spin_lock_init(&host->irq_handler_lock); 16362306a36Sopenharmony_ci sema_init(&host->mmc_serializer, 1); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci host->dev = &pdev->dev; 16662306a36Sopenharmony_ci host->acquire_bus = octeon_mmc_acquire_bus; 16762306a36Sopenharmony_ci host->release_bus = octeon_mmc_release_bus; 16862306a36Sopenharmony_ci host->int_enable = octeon_mmc_int_enable; 16962306a36Sopenharmony_ci host->set_shared_power = octeon_mmc_set_shared_power; 17062306a36Sopenharmony_ci if (OCTEON_IS_MODEL(OCTEON_CN6XXX) || 17162306a36Sopenharmony_ci OCTEON_IS_MODEL(OCTEON_CNF7XXX)) { 17262306a36Sopenharmony_ci host->dmar_fixup = octeon_mmc_dmar_fixup; 17362306a36Sopenharmony_ci host->dmar_fixup_done = octeon_mmc_dmar_fixup_done; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci host->sys_freq = octeon_get_io_clock_rate(); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (of_device_is_compatible(node, "cavium,octeon-7890-mmc")) { 17962306a36Sopenharmony_ci host->big_dma_addr = true; 18062306a36Sopenharmony_ci host->need_irq_handler_lock = true; 18162306a36Sopenharmony_ci host->has_ciu3 = true; 18262306a36Sopenharmony_ci host->use_sg = true; 18362306a36Sopenharmony_ci /* 18462306a36Sopenharmony_ci * First seven are the EMM_INT bits 0..6, then two for 18562306a36Sopenharmony_ci * the EMM_DMA_INT bits 18662306a36Sopenharmony_ci */ 18762306a36Sopenharmony_ci for (i = 0; i < 9; i++) { 18862306a36Sopenharmony_ci mmc_irq[i] = platform_get_irq(pdev, i); 18962306a36Sopenharmony_ci if (mmc_irq[i] < 0) 19062306a36Sopenharmony_ci return mmc_irq[i]; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* work around legacy u-boot device trees */ 19362306a36Sopenharmony_ci irq_set_irq_type(mmc_irq[i], IRQ_TYPE_EDGE_RISING); 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci } else { 19662306a36Sopenharmony_ci host->big_dma_addr = false; 19762306a36Sopenharmony_ci host->need_irq_handler_lock = false; 19862306a36Sopenharmony_ci host->has_ciu3 = false; 19962306a36Sopenharmony_ci /* First one is EMM second DMA */ 20062306a36Sopenharmony_ci for (i = 0; i < 2; i++) { 20162306a36Sopenharmony_ci mmc_irq[i] = platform_get_irq(pdev, i); 20262306a36Sopenharmony_ci if (mmc_irq[i] < 0) 20362306a36Sopenharmony_ci return mmc_irq[i]; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci host->last_slot = -1; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, 0); 21062306a36Sopenharmony_ci if (IS_ERR(base)) 21162306a36Sopenharmony_ci return PTR_ERR(base); 21262306a36Sopenharmony_ci host->base = base; 21362306a36Sopenharmony_ci host->reg_off = 0; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, 1); 21662306a36Sopenharmony_ci if (IS_ERR(base)) 21762306a36Sopenharmony_ci return PTR_ERR(base); 21862306a36Sopenharmony_ci host->dma_base = base; 21962306a36Sopenharmony_ci /* 22062306a36Sopenharmony_ci * To keep the register addresses shared we intentionaly use 22162306a36Sopenharmony_ci * a negative offset here, first register used on Octeon therefore 22262306a36Sopenharmony_ci * starts at 0x20 (MIO_EMM_DMA_CFG). 22362306a36Sopenharmony_ci */ 22462306a36Sopenharmony_ci host->reg_off_dma = -0x20; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)); 22762306a36Sopenharmony_ci if (ret) 22862306a36Sopenharmony_ci return ret; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* 23162306a36Sopenharmony_ci * Clear out any pending interrupts that may be left over from 23262306a36Sopenharmony_ci * bootloader. 23362306a36Sopenharmony_ci */ 23462306a36Sopenharmony_ci val = readq(host->base + MIO_EMM_INT(host)); 23562306a36Sopenharmony_ci writeq(val, host->base + MIO_EMM_INT(host)); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (host->has_ciu3) { 23862306a36Sopenharmony_ci /* Only CMD_DONE, DMA_DONE, CMD_ERR, DMA_ERR */ 23962306a36Sopenharmony_ci for (i = 1; i <= 4; i++) { 24062306a36Sopenharmony_ci ret = devm_request_irq(&pdev->dev, mmc_irq[i], 24162306a36Sopenharmony_ci cvm_mmc_interrupt, 24262306a36Sopenharmony_ci 0, cvm_mmc_irq_names[i], host); 24362306a36Sopenharmony_ci if (ret < 0) { 24462306a36Sopenharmony_ci dev_err(&pdev->dev, "Error: devm_request_irq %d\n", 24562306a36Sopenharmony_ci mmc_irq[i]); 24662306a36Sopenharmony_ci return ret; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci } else { 25062306a36Sopenharmony_ci ret = devm_request_irq(&pdev->dev, mmc_irq[0], 25162306a36Sopenharmony_ci cvm_mmc_interrupt, 0, KBUILD_MODNAME, 25262306a36Sopenharmony_ci host); 25362306a36Sopenharmony_ci if (ret < 0) { 25462306a36Sopenharmony_ci dev_err(&pdev->dev, "Error: devm_request_irq %d\n", 25562306a36Sopenharmony_ci mmc_irq[0]); 25662306a36Sopenharmony_ci return ret; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci host->global_pwr_gpiod = devm_gpiod_get_optional(&pdev->dev, 26162306a36Sopenharmony_ci "power", 26262306a36Sopenharmony_ci GPIOD_OUT_HIGH); 26362306a36Sopenharmony_ci if (IS_ERR(host->global_pwr_gpiod)) { 26462306a36Sopenharmony_ci dev_err(&pdev->dev, "Invalid power GPIO\n"); 26562306a36Sopenharmony_ci return PTR_ERR(host->global_pwr_gpiod); 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci platform_set_drvdata(pdev, host); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci i = 0; 27162306a36Sopenharmony_ci for_each_child_of_node(node, cn) { 27262306a36Sopenharmony_ci host->slot_pdev[i] = 27362306a36Sopenharmony_ci of_platform_device_create(cn, NULL, &pdev->dev); 27462306a36Sopenharmony_ci if (!host->slot_pdev[i]) { 27562306a36Sopenharmony_ci i++; 27662306a36Sopenharmony_ci continue; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci ret = cvm_mmc_of_slot_probe(&host->slot_pdev[i]->dev, host); 27962306a36Sopenharmony_ci if (ret) { 28062306a36Sopenharmony_ci dev_err(&pdev->dev, "Error populating slots\n"); 28162306a36Sopenharmony_ci octeon_mmc_set_shared_power(host, 0); 28262306a36Sopenharmony_ci of_node_put(cn); 28362306a36Sopenharmony_ci goto error; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci i++; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci return 0; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cierror: 29062306a36Sopenharmony_ci for (i = 0; i < CAVIUM_MAX_MMC; i++) { 29162306a36Sopenharmony_ci if (host->slot[i]) 29262306a36Sopenharmony_ci cvm_mmc_of_slot_remove(host->slot[i]); 29362306a36Sopenharmony_ci if (host->slot_pdev[i]) 29462306a36Sopenharmony_ci of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL); 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci return ret; 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_cistatic void octeon_mmc_remove(struct platform_device *pdev) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci struct cvm_mmc_host *host = platform_get_drvdata(pdev); 30262306a36Sopenharmony_ci u64 dma_cfg; 30362306a36Sopenharmony_ci int i; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci for (i = 0; i < CAVIUM_MAX_MMC; i++) 30662306a36Sopenharmony_ci if (host->slot[i]) 30762306a36Sopenharmony_ci cvm_mmc_of_slot_remove(host->slot[i]); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci dma_cfg = readq(host->dma_base + MIO_EMM_DMA_CFG(host)); 31062306a36Sopenharmony_ci dma_cfg &= ~MIO_EMM_DMA_CFG_EN; 31162306a36Sopenharmony_ci writeq(dma_cfg, host->dma_base + MIO_EMM_DMA_CFG(host)); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci octeon_mmc_set_shared_power(host, 0); 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic const struct of_device_id octeon_mmc_match[] = { 31762306a36Sopenharmony_ci { 31862306a36Sopenharmony_ci .compatible = "cavium,octeon-6130-mmc", 31962306a36Sopenharmony_ci }, 32062306a36Sopenharmony_ci { 32162306a36Sopenharmony_ci .compatible = "cavium,octeon-7890-mmc", 32262306a36Sopenharmony_ci }, 32362306a36Sopenharmony_ci {}, 32462306a36Sopenharmony_ci}; 32562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, octeon_mmc_match); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic struct platform_driver octeon_mmc_driver = { 32862306a36Sopenharmony_ci .probe = octeon_mmc_probe, 32962306a36Sopenharmony_ci .remove_new = octeon_mmc_remove, 33062306a36Sopenharmony_ci .driver = { 33162306a36Sopenharmony_ci .name = KBUILD_MODNAME, 33262306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 33362306a36Sopenharmony_ci .of_match_table = octeon_mmc_match, 33462306a36Sopenharmony_ci }, 33562306a36Sopenharmony_ci}; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_cimodule_platform_driver(octeon_mmc_driver); 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ciMODULE_AUTHOR("Cavium Inc. <support@cavium.com>"); 34062306a36Sopenharmony_ciMODULE_DESCRIPTION("Low-level driver for Cavium OCTEON MMC/SSD card"); 34162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 342