| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /** |
|---|
| 2 | 3 | * SDHCI Controller driver for TI's OMAP SoCs |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (C) 2017 Texas Instruments |
|---|
| 5 | 6 | * Author: Kishon Vijay Abraham I <kishon@ti.com> |
|---|
| 6 | | - * |
|---|
| 7 | | - * This program is free software: you can redistribute it and/or modify |
|---|
| 8 | | - * it under the terms of the GNU General Public License version 2 of |
|---|
| 9 | | - * the License as published by the Free Software Foundation. |
|---|
| 10 | | - * |
|---|
| 11 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 12 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | | - * GNU General Public License for more details. |
|---|
| 15 | | - * |
|---|
| 16 | | - * You should have received a copy of the GNU General Public License |
|---|
| 17 | | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 18 | 7 | */ |
|---|
| 19 | 8 | |
|---|
| 20 | 9 | #include <linux/delay.h> |
|---|
| 10 | +#include <linux/mmc/mmc.h> |
|---|
| 21 | 11 | #include <linux/mmc/slot-gpio.h> |
|---|
| 22 | 12 | #include <linux/module.h> |
|---|
| 23 | 13 | #include <linux/of.h> |
|---|
| .. | .. |
|---|
| 72 | 62 | #define SDHCI_OMAP_IE 0x234 |
|---|
| 73 | 63 | #define INT_CC_EN BIT(0) |
|---|
| 74 | 64 | |
|---|
| 65 | +#define SDHCI_OMAP_ISE 0x238 |
|---|
| 66 | + |
|---|
| 75 | 67 | #define SDHCI_OMAP_AC12 0x23c |
|---|
| 76 | 68 | #define AC12_V1V8_SIGEN BIT(19) |
|---|
| 77 | 69 | #define AC12_SCLK_SEL BIT(23) |
|---|
| .. | .. |
|---|
| 96 | 88 | |
|---|
| 97 | 89 | /* sdhci-omap controller flags */ |
|---|
| 98 | 90 | #define SDHCI_OMAP_REQUIRE_IODELAY BIT(0) |
|---|
| 91 | +#define SDHCI_OMAP_SPECIAL_RESET BIT(1) |
|---|
| 99 | 92 | |
|---|
| 100 | 93 | struct sdhci_omap_data { |
|---|
| 101 | 94 | u32 offset; |
|---|
| .. | .. |
|---|
| 117 | 110 | struct pinctrl *pinctrl; |
|---|
| 118 | 111 | struct pinctrl_state **pinctrl_state; |
|---|
| 119 | 112 | bool is_tuning; |
|---|
| 113 | + /* Omap specific context save */ |
|---|
| 114 | + u32 con; |
|---|
| 115 | + u32 hctl; |
|---|
| 116 | + u32 sysctl; |
|---|
| 117 | + u32 capa; |
|---|
| 118 | + u32 ie; |
|---|
| 119 | + u32 ise; |
|---|
| 120 | 120 | }; |
|---|
| 121 | 121 | |
|---|
| 122 | 122 | static void sdhci_omap_start_clock(struct sdhci_omap_host *omap_host); |
|---|
| .. | .. |
|---|
| 304 | 304 | int ret = 0; |
|---|
| 305 | 305 | u32 reg; |
|---|
| 306 | 306 | int i; |
|---|
| 307 | | - |
|---|
| 308 | | - pltfm_host = sdhci_priv(host); |
|---|
| 309 | | - omap_host = sdhci_pltfm_priv(pltfm_host); |
|---|
| 310 | | - dev = omap_host->dev; |
|---|
| 311 | 307 | |
|---|
| 312 | 308 | /* clock tuning is not needed for upto 52MHz */ |
|---|
| 313 | 309 | if (ios->clock <= 52000000) |
|---|
| .. | .. |
|---|
| 701 | 697 | struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host); |
|---|
| 702 | 698 | |
|---|
| 703 | 699 | reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_CON); |
|---|
| 704 | | - reg |= CON_DMA_MASTER; |
|---|
| 700 | + reg &= ~CON_DMA_MASTER; |
|---|
| 701 | + /* Switch to DMA slave mode when using external DMA */ |
|---|
| 702 | + if (!host->use_external_dma) |
|---|
| 703 | + reg |= CON_DMA_MASTER; |
|---|
| 704 | + |
|---|
| 705 | 705 | sdhci_omap_writel(omap_host, SDHCI_OMAP_CON, reg); |
|---|
| 706 | 706 | |
|---|
| 707 | 707 | return 0; |
|---|
| .. | .. |
|---|
| 790 | 790 | sdhci_omap_start_clock(omap_host); |
|---|
| 791 | 791 | } |
|---|
| 792 | 792 | |
|---|
| 793 | | -void sdhci_omap_reset(struct sdhci_host *host, u8 mask) |
|---|
| 793 | +#define MMC_TIMEOUT_US 20000 /* 20000 micro Sec */ |
|---|
| 794 | +static void sdhci_omap_reset(struct sdhci_host *host, u8 mask) |
|---|
| 794 | 795 | { |
|---|
| 795 | 796 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
|---|
| 796 | 797 | struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host); |
|---|
| 798 | + unsigned long limit = MMC_TIMEOUT_US; |
|---|
| 799 | + unsigned long i = 0; |
|---|
| 797 | 800 | |
|---|
| 798 | 801 | /* Don't reset data lines during tuning operation */ |
|---|
| 799 | 802 | if (omap_host->is_tuning) |
|---|
| 800 | 803 | mask &= ~SDHCI_RESET_DATA; |
|---|
| 804 | + |
|---|
| 805 | + if (omap_host->flags & SDHCI_OMAP_SPECIAL_RESET) { |
|---|
| 806 | + sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET); |
|---|
| 807 | + while ((!(sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask)) && |
|---|
| 808 | + (i++ < limit)) |
|---|
| 809 | + udelay(1); |
|---|
| 810 | + i = 0; |
|---|
| 811 | + while ((sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) && |
|---|
| 812 | + (i++ < limit)) |
|---|
| 813 | + udelay(1); |
|---|
| 814 | + |
|---|
| 815 | + if (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) |
|---|
| 816 | + dev_err(mmc_dev(host->mmc), |
|---|
| 817 | + "Timeout waiting on controller reset in %s\n", |
|---|
| 818 | + __func__); |
|---|
| 819 | + return; |
|---|
| 820 | + } |
|---|
| 801 | 821 | |
|---|
| 802 | 822 | sdhci_reset(host, mask); |
|---|
| 803 | 823 | } |
|---|
| .. | .. |
|---|
| 839 | 859 | return intmask; |
|---|
| 840 | 860 | } |
|---|
| 841 | 861 | |
|---|
| 862 | +static void sdhci_omap_set_timeout(struct sdhci_host *host, |
|---|
| 863 | + struct mmc_command *cmd) |
|---|
| 864 | +{ |
|---|
| 865 | + if (cmd->opcode == MMC_ERASE) |
|---|
| 866 | + sdhci_set_data_timeout_irq(host, false); |
|---|
| 867 | + |
|---|
| 868 | + __sdhci_set_timeout(host, cmd); |
|---|
| 869 | +} |
|---|
| 870 | + |
|---|
| 842 | 871 | static struct sdhci_ops sdhci_omap_ops = { |
|---|
| 843 | 872 | .set_clock = sdhci_omap_set_clock, |
|---|
| 844 | 873 | .set_power = sdhci_omap_set_power, |
|---|
| .. | .. |
|---|
| 850 | 879 | .reset = sdhci_omap_reset, |
|---|
| 851 | 880 | .set_uhs_signaling = sdhci_omap_set_uhs_signaling, |
|---|
| 852 | 881 | .irq = sdhci_omap_irq, |
|---|
| 882 | + .set_timeout = sdhci_omap_set_timeout, |
|---|
| 853 | 883 | }; |
|---|
| 854 | 884 | |
|---|
| 855 | 885 | static int sdhci_omap_set_capabilities(struct sdhci_omap_host *omap_host) |
|---|
| .. | .. |
|---|
| 899 | 929 | .offset = 0x200, |
|---|
| 900 | 930 | }; |
|---|
| 901 | 931 | |
|---|
| 932 | +static const struct sdhci_omap_data am335_data = { |
|---|
| 933 | + .offset = 0x200, |
|---|
| 934 | + .flags = SDHCI_OMAP_SPECIAL_RESET, |
|---|
| 935 | +}; |
|---|
| 936 | + |
|---|
| 937 | +static const struct sdhci_omap_data am437_data = { |
|---|
| 938 | + .offset = 0x200, |
|---|
| 939 | + .flags = SDHCI_OMAP_SPECIAL_RESET, |
|---|
| 940 | +}; |
|---|
| 941 | + |
|---|
| 902 | 942 | static const struct sdhci_omap_data dra7_data = { |
|---|
| 903 | 943 | .offset = 0x200, |
|---|
| 904 | 944 | .flags = SDHCI_OMAP_REQUIRE_IODELAY, |
|---|
| .. | .. |
|---|
| 907 | 947 | static const struct of_device_id omap_sdhci_match[] = { |
|---|
| 908 | 948 | { .compatible = "ti,dra7-sdhci", .data = &dra7_data }, |
|---|
| 909 | 949 | { .compatible = "ti,k2g-sdhci", .data = &k2g_data }, |
|---|
| 950 | + { .compatible = "ti,am335-sdhci", .data = &am335_data }, |
|---|
| 951 | + { .compatible = "ti,am437-sdhci", .data = &am437_data }, |
|---|
| 910 | 952 | {}, |
|---|
| 911 | 953 | }; |
|---|
| 912 | 954 | MODULE_DEVICE_TABLE(of, omap_sdhci_match); |
|---|
| .. | .. |
|---|
| 1053 | 1095 | const struct of_device_id *match; |
|---|
| 1054 | 1096 | struct sdhci_omap_data *data; |
|---|
| 1055 | 1097 | const struct soc_device_attribute *soc; |
|---|
| 1098 | + struct resource *regs; |
|---|
| 1056 | 1099 | |
|---|
| 1057 | 1100 | match = of_match_device(omap_sdhci_match, dev); |
|---|
| 1058 | 1101 | if (!match) |
|---|
| .. | .. |
|---|
| 1064 | 1107 | return -EINVAL; |
|---|
| 1065 | 1108 | } |
|---|
| 1066 | 1109 | offset = data->offset; |
|---|
| 1110 | + |
|---|
| 1111 | + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|---|
| 1112 | + if (!regs) |
|---|
| 1113 | + return -ENXIO; |
|---|
| 1067 | 1114 | |
|---|
| 1068 | 1115 | host = sdhci_pltfm_init(pdev, &sdhci_omap_pdata, |
|---|
| 1069 | 1116 | sizeof(*omap_host)); |
|---|
| .. | .. |
|---|
| 1081 | 1128 | omap_host->timing = MMC_TIMING_LEGACY; |
|---|
| 1082 | 1129 | omap_host->flags = data->flags; |
|---|
| 1083 | 1130 | host->ioaddr += offset; |
|---|
| 1131 | + host->mapbase = regs->start + offset; |
|---|
| 1084 | 1132 | |
|---|
| 1085 | 1133 | mmc = host->mmc; |
|---|
| 1086 | 1134 | sdhci_get_of_property(pdev); |
|---|
| .. | .. |
|---|
| 1098 | 1146 | if (!strcmp(dev_name(dev), "480ad000.mmc")) |
|---|
| 1099 | 1147 | mmc->f_max = 48000000; |
|---|
| 1100 | 1148 | } |
|---|
| 1149 | + |
|---|
| 1150 | + if (!mmc_can_gpio_ro(mmc)) |
|---|
| 1151 | + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT; |
|---|
| 1101 | 1152 | |
|---|
| 1102 | 1153 | pltfm_host->clk = devm_clk_get(dev, "fck"); |
|---|
| 1103 | 1154 | if (IS_ERR(pltfm_host->clk)) { |
|---|
| .. | .. |
|---|
| 1140 | 1191 | goto err_put_sync; |
|---|
| 1141 | 1192 | } |
|---|
| 1142 | 1193 | |
|---|
| 1143 | | - host->mmc_host_ops.get_ro = mmc_gpio_get_ro; |
|---|
| 1144 | 1194 | host->mmc_host_ops.start_signal_voltage_switch = |
|---|
| 1145 | 1195 | sdhci_omap_start_signal_voltage_switch; |
|---|
| 1146 | 1196 | host->mmc_host_ops.set_ios = sdhci_omap_set_ios; |
|---|
| 1147 | 1197 | host->mmc_host_ops.card_busy = sdhci_omap_card_busy; |
|---|
| 1148 | 1198 | host->mmc_host_ops.execute_tuning = sdhci_omap_execute_tuning; |
|---|
| 1149 | 1199 | host->mmc_host_ops.enable_sdio_irq = sdhci_omap_enable_sdio_irq; |
|---|
| 1200 | + |
|---|
| 1201 | + /* Switch to external DMA only if there is the "dmas" property */ |
|---|
| 1202 | + if (of_find_property(dev->of_node, "dmas", NULL)) |
|---|
| 1203 | + sdhci_switch_external_dma(host, true); |
|---|
| 1150 | 1204 | |
|---|
| 1151 | 1205 | /* R1B responses is required to properly manage HW busy detection. */ |
|---|
| 1152 | 1206 | mmc->caps |= MMC_CAP_NEED_RSP_BUSY; |
|---|
| .. | .. |
|---|
| 1191 | 1245 | |
|---|
| 1192 | 1246 | return 0; |
|---|
| 1193 | 1247 | } |
|---|
| 1248 | +#ifdef CONFIG_PM_SLEEP |
|---|
| 1249 | +static void sdhci_omap_context_save(struct sdhci_omap_host *omap_host) |
|---|
| 1250 | +{ |
|---|
| 1251 | + omap_host->con = sdhci_omap_readl(omap_host, SDHCI_OMAP_CON); |
|---|
| 1252 | + omap_host->hctl = sdhci_omap_readl(omap_host, SDHCI_OMAP_HCTL); |
|---|
| 1253 | + omap_host->sysctl = sdhci_omap_readl(omap_host, SDHCI_OMAP_SYSCTL); |
|---|
| 1254 | + omap_host->capa = sdhci_omap_readl(omap_host, SDHCI_OMAP_CAPA); |
|---|
| 1255 | + omap_host->ie = sdhci_omap_readl(omap_host, SDHCI_OMAP_IE); |
|---|
| 1256 | + omap_host->ise = sdhci_omap_readl(omap_host, SDHCI_OMAP_ISE); |
|---|
| 1257 | +} |
|---|
| 1258 | + |
|---|
| 1259 | +/* Order matters here, HCTL must be restored in two phases */ |
|---|
| 1260 | +static void sdhci_omap_context_restore(struct sdhci_omap_host *omap_host) |
|---|
| 1261 | +{ |
|---|
| 1262 | + sdhci_omap_writel(omap_host, SDHCI_OMAP_HCTL, omap_host->hctl); |
|---|
| 1263 | + sdhci_omap_writel(omap_host, SDHCI_OMAP_CAPA, omap_host->capa); |
|---|
| 1264 | + sdhci_omap_writel(omap_host, SDHCI_OMAP_HCTL, omap_host->hctl); |
|---|
| 1265 | + |
|---|
| 1266 | + sdhci_omap_writel(omap_host, SDHCI_OMAP_SYSCTL, omap_host->sysctl); |
|---|
| 1267 | + sdhci_omap_writel(omap_host, SDHCI_OMAP_CON, omap_host->con); |
|---|
| 1268 | + sdhci_omap_writel(omap_host, SDHCI_OMAP_IE, omap_host->ie); |
|---|
| 1269 | + sdhci_omap_writel(omap_host, SDHCI_OMAP_ISE, omap_host->ise); |
|---|
| 1270 | +} |
|---|
| 1271 | + |
|---|
| 1272 | +static int __maybe_unused sdhci_omap_suspend(struct device *dev) |
|---|
| 1273 | +{ |
|---|
| 1274 | + struct sdhci_host *host = dev_get_drvdata(dev); |
|---|
| 1275 | + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
|---|
| 1276 | + struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host); |
|---|
| 1277 | + |
|---|
| 1278 | + sdhci_suspend_host(host); |
|---|
| 1279 | + |
|---|
| 1280 | + sdhci_omap_context_save(omap_host); |
|---|
| 1281 | + |
|---|
| 1282 | + pinctrl_pm_select_idle_state(dev); |
|---|
| 1283 | + |
|---|
| 1284 | + pm_runtime_force_suspend(dev); |
|---|
| 1285 | + |
|---|
| 1286 | + return 0; |
|---|
| 1287 | +} |
|---|
| 1288 | + |
|---|
| 1289 | +static int __maybe_unused sdhci_omap_resume(struct device *dev) |
|---|
| 1290 | +{ |
|---|
| 1291 | + struct sdhci_host *host = dev_get_drvdata(dev); |
|---|
| 1292 | + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
|---|
| 1293 | + struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host); |
|---|
| 1294 | + |
|---|
| 1295 | + pm_runtime_force_resume(dev); |
|---|
| 1296 | + |
|---|
| 1297 | + pinctrl_pm_select_default_state(dev); |
|---|
| 1298 | + |
|---|
| 1299 | + sdhci_omap_context_restore(omap_host); |
|---|
| 1300 | + |
|---|
| 1301 | + sdhci_resume_host(host); |
|---|
| 1302 | + |
|---|
| 1303 | + return 0; |
|---|
| 1304 | +} |
|---|
| 1305 | +#endif |
|---|
| 1306 | +static SIMPLE_DEV_PM_OPS(sdhci_omap_dev_pm_ops, sdhci_omap_suspend, |
|---|
| 1307 | + sdhci_omap_resume); |
|---|
| 1194 | 1308 | |
|---|
| 1195 | 1309 | static struct platform_driver sdhci_omap_driver = { |
|---|
| 1196 | 1310 | .probe = sdhci_omap_probe, |
|---|
| 1197 | 1311 | .remove = sdhci_omap_remove, |
|---|
| 1198 | 1312 | .driver = { |
|---|
| 1199 | 1313 | .name = "sdhci-omap", |
|---|
| 1314 | + .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
|---|
| 1315 | + .pm = &sdhci_omap_dev_pm_ops, |
|---|
| 1200 | 1316 | .of_match_table = omap_sdhci_match, |
|---|
| 1201 | 1317 | }, |
|---|
| 1202 | 1318 | }; |
|---|