.. | .. |
---|
| 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 | }; |
---|