203 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2018 HiSilicon Technologies Co., Ltd.4 */5 6#include <linux/clk.h>7#include <linux/mfd/syscon.h>8#include <linux/mmc/host.h>9#include <linux/module.h>10#include <linux/of_address.h>11#include <linux/platform_device.h>12#include <linux/pm_runtime.h>13#include <linux/regmap.h>14#include <linux/regulator/consumer.h>15 16#include "dw_mmc.h"17#include "dw_mmc-pltfm.h"18 19#define ALL_INT_CLR 0x1ffff20 21struct hi3798cv200_priv {22 struct clk *sample_clk;23 struct clk *drive_clk;24};25 26static void dw_mci_hi3798cv200_set_ios(struct dw_mci *host, struct mmc_ios *ios)27{28 struct hi3798cv200_priv *priv = host->priv;29 u32 val;30 31 val = mci_readl(host, UHS_REG);32 if (ios->timing == MMC_TIMING_MMC_DDR52 ||33 ios->timing == MMC_TIMING_UHS_DDR50)34 val |= SDMMC_UHS_DDR;35 else36 val &= ~SDMMC_UHS_DDR;37 mci_writel(host, UHS_REG, val);38 39 val = mci_readl(host, ENABLE_SHIFT);40 if (ios->timing == MMC_TIMING_MMC_DDR52)41 val |= SDMMC_ENABLE_PHASE;42 else43 val &= ~SDMMC_ENABLE_PHASE;44 mci_writel(host, ENABLE_SHIFT, val);45 46 val = mci_readl(host, DDR_REG);47 if (ios->timing == MMC_TIMING_MMC_HS400)48 val |= SDMMC_DDR_HS400;49 else50 val &= ~SDMMC_DDR_HS400;51 mci_writel(host, DDR_REG, val);52 53 if (ios->timing == MMC_TIMING_MMC_HS ||54 ios->timing == MMC_TIMING_LEGACY)55 clk_set_phase(priv->drive_clk, 180);56 else if (ios->timing == MMC_TIMING_MMC_HS200)57 clk_set_phase(priv->drive_clk, 135);58}59 60static int dw_mci_hi3798cv200_execute_tuning(struct dw_mci_slot *slot,61 u32 opcode)62{63 static const int degrees[] = { 0, 45, 90, 135, 180, 225, 270, 315 };64 struct dw_mci *host = slot->host;65 struct hi3798cv200_priv *priv = host->priv;66 int raise_point = -1, fall_point = -1;67 int err, prev_err = -1;68 int found = 0;69 int i;70 71 for (i = 0; i < ARRAY_SIZE(degrees); i++) {72 clk_set_phase(priv->sample_clk, degrees[i]);73 mci_writel(host, RINTSTS, ALL_INT_CLR);74 75 err = mmc_send_tuning(slot->mmc, opcode, NULL);76 if (!err)77 found = 1;78 79 if (i > 0) {80 if (err && !prev_err)81 fall_point = i - 1;82 if (!err && prev_err)83 raise_point = i;84 }85 86 if (raise_point != -1 && fall_point != -1)87 goto tuning_out;88 89 prev_err = err;90 }91 92tuning_out:93 if (found) {94 if (raise_point == -1)95 raise_point = 0;96 if (fall_point == -1)97 fall_point = ARRAY_SIZE(degrees) - 1;98 if (fall_point < raise_point) {99 if ((raise_point + fall_point) >100 (ARRAY_SIZE(degrees) - 1))101 i = fall_point / 2;102 else103 i = (raise_point + ARRAY_SIZE(degrees) - 1) / 2;104 } else {105 i = (raise_point + fall_point) / 2;106 }107 108 clk_set_phase(priv->sample_clk, degrees[i]);109 dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n",110 raise_point, fall_point, degrees[i]);111 } else {112 dev_err(host->dev, "No valid clk_sample shift! use default\n");113 err = -EINVAL;114 }115 116 mci_writel(host, RINTSTS, ALL_INT_CLR);117 return err;118}119 120static int dw_mci_hi3798cv200_init(struct dw_mci *host)121{122 struct hi3798cv200_priv *priv;123 int ret;124 125 priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);126 if (!priv)127 return -ENOMEM;128 129 priv->sample_clk = devm_clk_get(host->dev, "ciu-sample");130 if (IS_ERR(priv->sample_clk)) {131 dev_err(host->dev, "failed to get ciu-sample clock\n");132 return PTR_ERR(priv->sample_clk);133 }134 135 priv->drive_clk = devm_clk_get(host->dev, "ciu-drive");136 if (IS_ERR(priv->drive_clk)) {137 dev_err(host->dev, "failed to get ciu-drive clock\n");138 return PTR_ERR(priv->drive_clk);139 }140 141 ret = clk_prepare_enable(priv->sample_clk);142 if (ret) {143 dev_err(host->dev, "failed to enable ciu-sample clock\n");144 return ret;145 }146 147 ret = clk_prepare_enable(priv->drive_clk);148 if (ret) {149 dev_err(host->dev, "failed to enable ciu-drive clock\n");150 goto disable_sample_clk;151 }152 153 host->priv = priv;154 return 0;155 156disable_sample_clk:157 clk_disable_unprepare(priv->sample_clk);158 return ret;159}160 161static const struct dw_mci_drv_data hi3798cv200_data = {162 .common_caps = MMC_CAP_CMD23,163 .init = dw_mci_hi3798cv200_init,164 .set_ios = dw_mci_hi3798cv200_set_ios,165 .execute_tuning = dw_mci_hi3798cv200_execute_tuning,166};167 168static int dw_mci_hi3798cv200_probe(struct platform_device *pdev)169{170 return dw_mci_pltfm_register(pdev, &hi3798cv200_data);171}172 173static void dw_mci_hi3798cv200_remove(struct platform_device *pdev)174{175 struct dw_mci *host = platform_get_drvdata(pdev);176 struct hi3798cv200_priv *priv = host->priv;177 178 clk_disable_unprepare(priv->drive_clk);179 clk_disable_unprepare(priv->sample_clk);180 181 dw_mci_pltfm_remove(pdev);182}183 184static const struct of_device_id dw_mci_hi3798cv200_match[] = {185 { .compatible = "hisilicon,hi3798cv200-dw-mshc", },186 {},187};188 189MODULE_DEVICE_TABLE(of, dw_mci_hi3798cv200_match);190static struct platform_driver dw_mci_hi3798cv200_driver = {191 .probe = dw_mci_hi3798cv200_probe,192 .remove_new = dw_mci_hi3798cv200_remove,193 .driver = {194 .name = "dwmmc_hi3798cv200",195 .probe_type = PROBE_PREFER_ASYNCHRONOUS,196 .of_match_table = dw_mci_hi3798cv200_match,197 },198};199module_platform_driver(dw_mci_hi3798cv200_driver);200 201MODULE_DESCRIPTION("HiSilicon Hi3798CV200 Specific DW-MSHC Driver Extension");202MODULE_LICENSE("GPL v2");203