1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * StarFive Designware Mobile Storage Host Controller Driver 4 * 5 * Copyright (c) 2022 StarFive Technology Co., Ltd. 6 */ 7 8 #include <linux/bitfield.h> 9 #include <linux/clk.h> 10 #include <linux/delay.h> 11 #include <linux/mfd/syscon.h> 12 #include <linux/mmc/host.h> 13 #include <linux/module.h> 14 #include <linux/of_address.h> 15 #include <linux/platform_device.h> 16 #include <linux/regmap.h> 17 18 #include "dw_mmc.h" 19 #include "dw_mmc-pltfm.h" 20 21 #define ALL_INT_CLR 0x1ffff 22 #define MAX_DELAY_CHAIN 32 23 24 #define STARFIVE_SMPL_PHASE GENMASK(20, 16) 25 26 static void dw_mci_starfive_set_ios(struct dw_mci *host, struct mmc_ios *ios) 27 { 28 int ret; 29 unsigned int clock; 30 31 if (ios->timing == MMC_TIMING_MMC_DDR52 || ios->timing == MMC_TIMING_UHS_DDR50) { 32 clock = (ios->clock > 50000000 && ios->clock <= 52000000) ? 100000000 : ios->clock; 33 ret = clk_set_rate(host->ciu_clk, clock); 34 if (ret) 35 dev_dbg(host->dev, "Use an external frequency divider %uHz\n", ios->clock); 36 host->bus_hz = clk_get_rate(host->ciu_clk); 37 } else { 38 dev_dbg(host->dev, "Using the internal divider\n"); 39 } 40 } 41 42 static void dw_mci_starfive_set_sample_phase(struct dw_mci *host, u32 smpl_phase) 43 { 44 /* change driver phase and sample phase */ 45 u32 reg_value = mci_readl(host, UHS_REG_EXT); 46 47 /* In UHS_REG_EXT, only 5 bits valid in DRV_PHASE and SMPL_PHASE */ 48 reg_value &= ~STARFIVE_SMPL_PHASE; 49 reg_value |= FIELD_PREP(STARFIVE_SMPL_PHASE, smpl_phase); 50 mci_writel(host, UHS_REG_EXT, reg_value); 51 52 /* We should delay 1ms wait for timing setting finished. */ 53 mdelay(1); 54 } 55 56 static int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot, 57 u32 opcode) 58 { 59 static const int grade = MAX_DELAY_CHAIN; 60 struct dw_mci *host = slot->host; 61 int smpl_phase, smpl_raise = -1, smpl_fall = -1; 62 int ret; 63 64 for (smpl_phase = 0; smpl_phase < grade; smpl_phase++) { 65 dw_mci_starfive_set_sample_phase(host, smpl_phase); 66 mci_writel(host, RINTSTS, ALL_INT_CLR); 67 68 ret = mmc_send_tuning(slot->mmc, opcode, NULL); 69 70 if (!ret && smpl_raise < 0) { 71 smpl_raise = smpl_phase; 72 } else if (ret && smpl_raise >= 0) { 73 smpl_fall = smpl_phase - 1; 74 break; 75 } 76 } 77 78 if (smpl_phase >= grade) 79 smpl_fall = grade - 1; 80 81 if (smpl_raise < 0) { 82 smpl_phase = 0; 83 dev_err(host->dev, "No valid delay chain! use default\n"); 84 ret = -EINVAL; 85 goto out; 86 } 87 88 smpl_phase = (smpl_raise + smpl_fall) / 2; 89 dev_dbg(host->dev, "Found valid delay chain! use it [delay=%d]\n", smpl_phase); 90 ret = 0; 91 92 out: 93 dw_mci_starfive_set_sample_phase(host, smpl_phase); 94 mci_writel(host, RINTSTS, ALL_INT_CLR); 95 return ret; 96 } 97 98 static const struct dw_mci_drv_data starfive_data = { 99 .common_caps = MMC_CAP_CMD23, 100 .set_ios = dw_mci_starfive_set_ios, 101 .execute_tuning = dw_mci_starfive_execute_tuning, 102 }; 103 104 static const struct of_device_id dw_mci_starfive_match[] = { 105 { .compatible = "starfive,jh7110-mmc", 106 .data = &starfive_data }, 107 {}, 108 }; 109 MODULE_DEVICE_TABLE(of, dw_mci_starfive_match); 110 111 static int dw_mci_starfive_probe(struct platform_device *pdev) 112 { 113 return dw_mci_pltfm_register(pdev, &starfive_data); 114 } 115 116 static struct platform_driver dw_mci_starfive_driver = { 117 .probe = dw_mci_starfive_probe, 118 .remove = dw_mci_pltfm_remove, 119 .driver = { 120 .name = "dwmmc_starfive", 121 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 122 .of_match_table = dw_mci_starfive_match, 123 }, 124 }; 125 module_platform_driver(dw_mci_starfive_driver); 126 127 MODULE_DESCRIPTION("StarFive JH7110 Specific DW-MSHC Driver Extension"); 128 MODULE_LICENSE("GPL"); 129 MODULE_ALIAS("platform:dwmmc_starfive"); 130