| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (C) 2016 Socionext Inc. |
|---|
| 3 | 4 | * Author: Masahiro Yamada <yamada.masahiro@socionext.com> |
|---|
| 4 | | - * |
|---|
| 5 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 6 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 7 | | - * the Free Software Foundation; either version 2 of the License, or |
|---|
| 8 | | - * (at your option) any later version. |
|---|
| 9 | | - * |
|---|
| 10 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 11 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | | - * GNU General Public License for more details. |
|---|
| 14 | 5 | */ |
|---|
| 15 | 6 | |
|---|
| 16 | 7 | #include <linux/bitfield.h> |
|---|
| 17 | | -#include <linux/bitops.h> |
|---|
| 8 | +#include <linux/bits.h> |
|---|
| 18 | 9 | #include <linux/iopoll.h> |
|---|
| 19 | 10 | #include <linux/module.h> |
|---|
| 20 | 11 | #include <linux/mmc/host.h> |
|---|
| 21 | 12 | #include <linux/mmc/mmc.h> |
|---|
| 22 | 13 | #include <linux/of.h> |
|---|
| 14 | +#include <linux/of_device.h> |
|---|
| 23 | 15 | |
|---|
| 24 | 16 | #include "sdhci-pltfm.h" |
|---|
| 25 | 17 | |
|---|
| .. | .. |
|---|
| 76 | 68 | void __iomem *hrs_addr; |
|---|
| 77 | 69 | bool enhanced_strobe; |
|---|
| 78 | 70 | unsigned int nr_phy_params; |
|---|
| 79 | | - struct sdhci_cdns_phy_param phy_params[0]; |
|---|
| 71 | + struct sdhci_cdns_phy_param phy_params[]; |
|---|
| 80 | 72 | }; |
|---|
| 81 | 73 | |
|---|
| 82 | 74 | struct sdhci_cdns_phy_cfg { |
|---|
| .. | .. |
|---|
| 105 | 97 | u32 tmp; |
|---|
| 106 | 98 | int ret; |
|---|
| 107 | 99 | |
|---|
| 100 | + ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), |
|---|
| 101 | + 0, 10); |
|---|
| 102 | + if (ret) |
|---|
| 103 | + return ret; |
|---|
| 104 | + |
|---|
| 108 | 105 | tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) | |
|---|
| 109 | 106 | FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); |
|---|
| 110 | 107 | writel(tmp, reg); |
|---|
| .. | .. |
|---|
| 119 | 116 | tmp &= ~SDHCI_CDNS_HRS04_WR; |
|---|
| 120 | 117 | writel(tmp, reg); |
|---|
| 121 | 118 | |
|---|
| 122 | | - return 0; |
|---|
| 119 | + ret = readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS04_ACK), |
|---|
| 120 | + 0, 10); |
|---|
| 121 | + |
|---|
| 122 | + return ret; |
|---|
| 123 | 123 | } |
|---|
| 124 | 124 | |
|---|
| 125 | 125 | static unsigned int sdhci_cdns_phy_param_count(struct device_node *np) |
|---|
| .. | .. |
|---|
| 167 | 167 | return 0; |
|---|
| 168 | 168 | } |
|---|
| 169 | 169 | |
|---|
| 170 | | -static inline void *sdhci_cdns_priv(struct sdhci_host *host) |
|---|
| 170 | +static void *sdhci_cdns_priv(struct sdhci_host *host) |
|---|
| 171 | 171 | { |
|---|
| 172 | 172 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
|---|
| 173 | 173 | |
|---|
| .. | .. |
|---|
| 200 | 200 | |
|---|
| 201 | 201 | tmp = readl(priv->hrs_addr + SDHCI_CDNS_HRS06); |
|---|
| 202 | 202 | return FIELD_GET(SDHCI_CDNS_HRS06_MODE, tmp); |
|---|
| 203 | +} |
|---|
| 204 | + |
|---|
| 205 | +static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) |
|---|
| 206 | +{ |
|---|
| 207 | + struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); |
|---|
| 208 | + void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06; |
|---|
| 209 | + u32 tmp; |
|---|
| 210 | + int i, ret; |
|---|
| 211 | + |
|---|
| 212 | + if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) |
|---|
| 213 | + return -EINVAL; |
|---|
| 214 | + |
|---|
| 215 | + tmp = readl(reg); |
|---|
| 216 | + tmp &= ~SDHCI_CDNS_HRS06_TUNE; |
|---|
| 217 | + tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val); |
|---|
| 218 | + |
|---|
| 219 | + /* |
|---|
| 220 | + * Workaround for IP errata: |
|---|
| 221 | + * The IP6116 SD/eMMC PHY design has a timing issue on receive data |
|---|
| 222 | + * path. Send tune request twice. |
|---|
| 223 | + */ |
|---|
| 224 | + for (i = 0; i < 2; i++) { |
|---|
| 225 | + tmp |= SDHCI_CDNS_HRS06_TUNE_UP; |
|---|
| 226 | + writel(tmp, reg); |
|---|
| 227 | + |
|---|
| 228 | + ret = readl_poll_timeout(reg, tmp, |
|---|
| 229 | + !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), |
|---|
| 230 | + 0, 1); |
|---|
| 231 | + if (ret) |
|---|
| 232 | + return ret; |
|---|
| 233 | + } |
|---|
| 234 | + |
|---|
| 235 | + return 0; |
|---|
| 236 | +} |
|---|
| 237 | + |
|---|
| 238 | +/* |
|---|
| 239 | + * In SD mode, software must not use the hardware tuning and instead perform |
|---|
| 240 | + * an almost identical procedure to eMMC. |
|---|
| 241 | + */ |
|---|
| 242 | +static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode) |
|---|
| 243 | +{ |
|---|
| 244 | + int cur_streak = 0; |
|---|
| 245 | + int max_streak = 0; |
|---|
| 246 | + int end_of_streak = 0; |
|---|
| 247 | + int i; |
|---|
| 248 | + |
|---|
| 249 | + /* |
|---|
| 250 | + * Do not execute tuning for UHS_SDR50 or UHS_DDR50. |
|---|
| 251 | + * The delay is set by probe, based on the DT properties. |
|---|
| 252 | + */ |
|---|
| 253 | + if (host->timing != MMC_TIMING_MMC_HS200 && |
|---|
| 254 | + host->timing != MMC_TIMING_UHS_SDR104) |
|---|
| 255 | + return 0; |
|---|
| 256 | + |
|---|
| 257 | + for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { |
|---|
| 258 | + if (sdhci_cdns_set_tune_val(host, i) || |
|---|
| 259 | + mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ |
|---|
| 260 | + cur_streak = 0; |
|---|
| 261 | + } else { /* good */ |
|---|
| 262 | + cur_streak++; |
|---|
| 263 | + if (cur_streak > max_streak) { |
|---|
| 264 | + max_streak = cur_streak; |
|---|
| 265 | + end_of_streak = i; |
|---|
| 266 | + } |
|---|
| 267 | + } |
|---|
| 268 | + } |
|---|
| 269 | + |
|---|
| 270 | + if (!max_streak) { |
|---|
| 271 | + dev_err(mmc_dev(host->mmc), "no tuning point found\n"); |
|---|
| 272 | + return -EIO; |
|---|
| 273 | + } |
|---|
| 274 | + |
|---|
| 275 | + return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); |
|---|
| 203 | 276 | } |
|---|
| 204 | 277 | |
|---|
| 205 | 278 | static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host, |
|---|
| .. | .. |
|---|
| 241 | 314 | .get_timeout_clock = sdhci_cdns_get_timeout_clock, |
|---|
| 242 | 315 | .set_bus_width = sdhci_set_bus_width, |
|---|
| 243 | 316 | .reset = sdhci_reset, |
|---|
| 317 | + .platform_execute_tuning = sdhci_cdns_execute_tuning, |
|---|
| 244 | 318 | .set_uhs_signaling = sdhci_cdns_set_uhs_signaling, |
|---|
| 319 | +}; |
|---|
| 320 | + |
|---|
| 321 | +static const struct sdhci_pltfm_data sdhci_cdns_uniphier_pltfm_data = { |
|---|
| 322 | + .ops = &sdhci_cdns_ops, |
|---|
| 323 | + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, |
|---|
| 245 | 324 | }; |
|---|
| 246 | 325 | |
|---|
| 247 | 326 | static const struct sdhci_pltfm_data sdhci_cdns_pltfm_data = { |
|---|
| 248 | 327 | .ops = &sdhci_cdns_ops, |
|---|
| 249 | 328 | }; |
|---|
| 250 | | - |
|---|
| 251 | | -static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val) |
|---|
| 252 | | -{ |
|---|
| 253 | | - struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host); |
|---|
| 254 | | - void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS06; |
|---|
| 255 | | - u32 tmp; |
|---|
| 256 | | - int i, ret; |
|---|
| 257 | | - |
|---|
| 258 | | - if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) |
|---|
| 259 | | - return -EINVAL; |
|---|
| 260 | | - |
|---|
| 261 | | - tmp = readl(reg); |
|---|
| 262 | | - tmp &= ~SDHCI_CDNS_HRS06_TUNE; |
|---|
| 263 | | - tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val); |
|---|
| 264 | | - |
|---|
| 265 | | - /* |
|---|
| 266 | | - * Workaround for IP errata: |
|---|
| 267 | | - * The IP6116 SD/eMMC PHY design has a timing issue on receive data |
|---|
| 268 | | - * path. Send tune request twice. |
|---|
| 269 | | - */ |
|---|
| 270 | | - for (i = 0; i < 2; i++) { |
|---|
| 271 | | - tmp |= SDHCI_CDNS_HRS06_TUNE_UP; |
|---|
| 272 | | - writel(tmp, reg); |
|---|
| 273 | | - |
|---|
| 274 | | - ret = readl_poll_timeout(reg, tmp, |
|---|
| 275 | | - !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), |
|---|
| 276 | | - 0, 1); |
|---|
| 277 | | - if (ret) |
|---|
| 278 | | - return ret; |
|---|
| 279 | | - } |
|---|
| 280 | | - |
|---|
| 281 | | - return 0; |
|---|
| 282 | | -} |
|---|
| 283 | | - |
|---|
| 284 | | -static int sdhci_cdns_execute_tuning(struct mmc_host *mmc, u32 opcode) |
|---|
| 285 | | -{ |
|---|
| 286 | | - struct sdhci_host *host = mmc_priv(mmc); |
|---|
| 287 | | - int cur_streak = 0; |
|---|
| 288 | | - int max_streak = 0; |
|---|
| 289 | | - int end_of_streak = 0; |
|---|
| 290 | | - int i; |
|---|
| 291 | | - |
|---|
| 292 | | - /* |
|---|
| 293 | | - * This handler only implements the eMMC tuning that is specific to |
|---|
| 294 | | - * this controller. Fall back to the standard method for SD timing. |
|---|
| 295 | | - */ |
|---|
| 296 | | - if (host->timing != MMC_TIMING_MMC_HS200) |
|---|
| 297 | | - return sdhci_execute_tuning(mmc, opcode); |
|---|
| 298 | | - |
|---|
| 299 | | - if (WARN_ON(opcode != MMC_SEND_TUNING_BLOCK_HS200)) |
|---|
| 300 | | - return -EINVAL; |
|---|
| 301 | | - |
|---|
| 302 | | - for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { |
|---|
| 303 | | - if (sdhci_cdns_set_tune_val(host, i) || |
|---|
| 304 | | - mmc_send_tuning(host->mmc, opcode, NULL)) { /* bad */ |
|---|
| 305 | | - cur_streak = 0; |
|---|
| 306 | | - } else { /* good */ |
|---|
| 307 | | - cur_streak++; |
|---|
| 308 | | - if (cur_streak > max_streak) { |
|---|
| 309 | | - max_streak = cur_streak; |
|---|
| 310 | | - end_of_streak = i; |
|---|
| 311 | | - } |
|---|
| 312 | | - } |
|---|
| 313 | | - } |
|---|
| 314 | | - |
|---|
| 315 | | - if (!max_streak) { |
|---|
| 316 | | - dev_err(mmc_dev(host->mmc), "no tuning point found\n"); |
|---|
| 317 | | - return -EIO; |
|---|
| 318 | | - } |
|---|
| 319 | | - |
|---|
| 320 | | - return sdhci_cdns_set_tune_val(host, end_of_streak - max_streak / 2); |
|---|
| 321 | | -} |
|---|
| 322 | 329 | |
|---|
| 323 | 330 | static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc, |
|---|
| 324 | 331 | struct mmc_ios *ios) |
|---|
| .. | .. |
|---|
| 343 | 350 | static int sdhci_cdns_probe(struct platform_device *pdev) |
|---|
| 344 | 351 | { |
|---|
| 345 | 352 | struct sdhci_host *host; |
|---|
| 353 | + const struct sdhci_pltfm_data *data; |
|---|
| 346 | 354 | struct sdhci_pltfm_host *pltfm_host; |
|---|
| 347 | 355 | struct sdhci_cdns_priv *priv; |
|---|
| 348 | 356 | struct clk *clk; |
|---|
| 349 | | - size_t priv_size; |
|---|
| 350 | 357 | unsigned int nr_phy_params; |
|---|
| 351 | 358 | int ret; |
|---|
| 352 | 359 | struct device *dev = &pdev->dev; |
|---|
| 360 | + static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT; |
|---|
| 353 | 361 | |
|---|
| 354 | 362 | clk = devm_clk_get(dev, NULL); |
|---|
| 355 | 363 | if (IS_ERR(clk)) |
|---|
| .. | .. |
|---|
| 359 | 367 | if (ret) |
|---|
| 360 | 368 | return ret; |
|---|
| 361 | 369 | |
|---|
| 370 | + data = of_device_get_match_data(dev); |
|---|
| 371 | + if (!data) |
|---|
| 372 | + data = &sdhci_cdns_pltfm_data; |
|---|
| 373 | + |
|---|
| 362 | 374 | nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node); |
|---|
| 363 | | - priv_size = sizeof(*priv) + sizeof(priv->phy_params[0]) * nr_phy_params; |
|---|
| 364 | | - host = sdhci_pltfm_init(pdev, &sdhci_cdns_pltfm_data, priv_size); |
|---|
| 375 | + host = sdhci_pltfm_init(pdev, data, |
|---|
| 376 | + struct_size(priv, phy_params, nr_phy_params)); |
|---|
| 365 | 377 | if (IS_ERR(host)) { |
|---|
| 366 | 378 | ret = PTR_ERR(host); |
|---|
| 367 | 379 | goto disable_clk; |
|---|
| .. | .. |
|---|
| 375 | 387 | priv->hrs_addr = host->ioaddr; |
|---|
| 376 | 388 | priv->enhanced_strobe = false; |
|---|
| 377 | 389 | host->ioaddr += SDHCI_CDNS_SRS_BASE; |
|---|
| 378 | | - host->mmc_host_ops.execute_tuning = sdhci_cdns_execute_tuning; |
|---|
| 379 | 390 | host->mmc_host_ops.hs400_enhanced_strobe = |
|---|
| 380 | 391 | sdhci_cdns_hs400_enhanced_strobe; |
|---|
| 392 | + sdhci_enable_v4_mode(host); |
|---|
| 393 | + __sdhci_read_caps(host, &version, NULL, NULL); |
|---|
| 381 | 394 | |
|---|
| 382 | 395 | sdhci_get_of_property(pdev); |
|---|
| 383 | 396 | |
|---|
| .. | .. |
|---|
| 438 | 451 | }; |
|---|
| 439 | 452 | |
|---|
| 440 | 453 | static const struct of_device_id sdhci_cdns_match[] = { |
|---|
| 441 | | - { .compatible = "socionext,uniphier-sd4hc" }, |
|---|
| 454 | + { |
|---|
| 455 | + .compatible = "socionext,uniphier-sd4hc", |
|---|
| 456 | + .data = &sdhci_cdns_uniphier_pltfm_data, |
|---|
| 457 | + }, |
|---|
| 442 | 458 | { .compatible = "cdns,sd4hc" }, |
|---|
| 443 | 459 | { /* sentinel */ } |
|---|
| 444 | 460 | }; |
|---|
| .. | .. |
|---|
| 447 | 463 | static struct platform_driver sdhci_cdns_driver = { |
|---|
| 448 | 464 | .driver = { |
|---|
| 449 | 465 | .name = "sdhci-cdns", |
|---|
| 466 | + .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
|---|
| 450 | 467 | .pm = &sdhci_cdns_pm_ops, |
|---|
| 451 | 468 | .of_match_table = sdhci_cdns_match, |
|---|
| 452 | 469 | }, |
|---|