162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * StarFive Designware Mobile Storage Host Controller Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2022 StarFive Technology Co., Ltd. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 1162306a36Sopenharmony_ci#include <linux/mmc/host.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of_address.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "dw_mmc.h" 1862306a36Sopenharmony_ci#include "dw_mmc-pltfm.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define ALL_INT_CLR 0x1ffff 2162306a36Sopenharmony_ci#define MAX_DELAY_CHAIN 32 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct starfive_priv { 2462306a36Sopenharmony_ci struct device *dev; 2562306a36Sopenharmony_ci struct regmap *reg_syscon; 2662306a36Sopenharmony_ci u32 syscon_offset; 2762306a36Sopenharmony_ci u32 syscon_shift; 2862306a36Sopenharmony_ci u32 syscon_mask; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic void dw_mci_starfive_set_ios(struct dw_mci *host, struct mmc_ios *ios) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci int ret; 3462306a36Sopenharmony_ci unsigned int clock; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (ios->timing == MMC_TIMING_MMC_DDR52 || ios->timing == MMC_TIMING_UHS_DDR50) { 3762306a36Sopenharmony_ci clock = (ios->clock > 50000000 && ios->clock <= 52000000) ? 100000000 : ios->clock; 3862306a36Sopenharmony_ci ret = clk_set_rate(host->ciu_clk, clock); 3962306a36Sopenharmony_ci if (ret) 4062306a36Sopenharmony_ci dev_dbg(host->dev, "Use an external frequency divider %uHz\n", ios->clock); 4162306a36Sopenharmony_ci host->bus_hz = clk_get_rate(host->ciu_clk); 4262306a36Sopenharmony_ci } else { 4362306a36Sopenharmony_ci dev_dbg(host->dev, "Using the internal divider\n"); 4462306a36Sopenharmony_ci } 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot, 4862306a36Sopenharmony_ci u32 opcode) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci static const int grade = MAX_DELAY_CHAIN; 5162306a36Sopenharmony_ci struct dw_mci *host = slot->host; 5262306a36Sopenharmony_ci struct starfive_priv *priv = host->priv; 5362306a36Sopenharmony_ci int rise_point = -1, fall_point = -1; 5462306a36Sopenharmony_ci int err, prev_err = 0; 5562306a36Sopenharmony_ci int i; 5662306a36Sopenharmony_ci bool found = 0; 5762306a36Sopenharmony_ci u32 regval; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci /* 6062306a36Sopenharmony_ci * Use grade as the max delay chain, and use the rise_point and 6162306a36Sopenharmony_ci * fall_point to ensure the best sampling point of a data input 6262306a36Sopenharmony_ci * signals. 6362306a36Sopenharmony_ci */ 6462306a36Sopenharmony_ci for (i = 0; i < grade; i++) { 6562306a36Sopenharmony_ci regval = i << priv->syscon_shift; 6662306a36Sopenharmony_ci err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, 6762306a36Sopenharmony_ci priv->syscon_mask, regval); 6862306a36Sopenharmony_ci if (err) 6962306a36Sopenharmony_ci return err; 7062306a36Sopenharmony_ci mci_writel(host, RINTSTS, ALL_INT_CLR); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci err = mmc_send_tuning(slot->mmc, opcode, NULL); 7362306a36Sopenharmony_ci if (!err) 7462306a36Sopenharmony_ci found = 1; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (i > 0) { 7762306a36Sopenharmony_ci if (err && !prev_err) 7862306a36Sopenharmony_ci fall_point = i - 1; 7962306a36Sopenharmony_ci if (!err && prev_err) 8062306a36Sopenharmony_ci rise_point = i; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (rise_point != -1 && fall_point != -1) 8462306a36Sopenharmony_ci goto tuning_out; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci prev_err = err; 8762306a36Sopenharmony_ci err = 0; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cituning_out: 9162306a36Sopenharmony_ci if (found) { 9262306a36Sopenharmony_ci if (rise_point == -1) 9362306a36Sopenharmony_ci rise_point = 0; 9462306a36Sopenharmony_ci if (fall_point == -1) 9562306a36Sopenharmony_ci fall_point = grade - 1; 9662306a36Sopenharmony_ci if (fall_point < rise_point) { 9762306a36Sopenharmony_ci if ((rise_point + fall_point) > 9862306a36Sopenharmony_ci (grade - 1)) 9962306a36Sopenharmony_ci i = fall_point / 2; 10062306a36Sopenharmony_ci else 10162306a36Sopenharmony_ci i = (rise_point + grade - 1) / 2; 10262306a36Sopenharmony_ci } else { 10362306a36Sopenharmony_ci i = (rise_point + fall_point) / 2; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci regval = i << priv->syscon_shift; 10762306a36Sopenharmony_ci err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, 10862306a36Sopenharmony_ci priv->syscon_mask, regval); 10962306a36Sopenharmony_ci if (err) 11062306a36Sopenharmony_ci return err; 11162306a36Sopenharmony_ci mci_writel(host, RINTSTS, ALL_INT_CLR); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci dev_info(host->dev, "Found valid delay chain! use it [delay=%d]\n", i); 11462306a36Sopenharmony_ci } else { 11562306a36Sopenharmony_ci dev_err(host->dev, "No valid delay chain! use default\n"); 11662306a36Sopenharmony_ci err = -EINVAL; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci mci_writel(host, RINTSTS, ALL_INT_CLR); 12062306a36Sopenharmony_ci return err; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic int dw_mci_starfive_parse_dt(struct dw_mci *host) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci struct of_phandle_args args; 12662306a36Sopenharmony_ci struct starfive_priv *priv; 12762306a36Sopenharmony_ci int ret; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); 13062306a36Sopenharmony_ci if (!priv) 13162306a36Sopenharmony_ci return -ENOMEM; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci ret = of_parse_phandle_with_fixed_args(host->dev->of_node, 13462306a36Sopenharmony_ci "starfive,sysreg", 3, 0, &args); 13562306a36Sopenharmony_ci if (ret) { 13662306a36Sopenharmony_ci dev_err(host->dev, "Failed to parse starfive,sysreg\n"); 13762306a36Sopenharmony_ci return -EINVAL; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci priv->reg_syscon = syscon_node_to_regmap(args.np); 14162306a36Sopenharmony_ci of_node_put(args.np); 14262306a36Sopenharmony_ci if (IS_ERR(priv->reg_syscon)) 14362306a36Sopenharmony_ci return PTR_ERR(priv->reg_syscon); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci priv->syscon_offset = args.args[0]; 14662306a36Sopenharmony_ci priv->syscon_shift = args.args[1]; 14762306a36Sopenharmony_ci priv->syscon_mask = args.args[2]; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci host->priv = priv; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return 0; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic const struct dw_mci_drv_data starfive_data = { 15562306a36Sopenharmony_ci .common_caps = MMC_CAP_CMD23, 15662306a36Sopenharmony_ci .set_ios = dw_mci_starfive_set_ios, 15762306a36Sopenharmony_ci .parse_dt = dw_mci_starfive_parse_dt, 15862306a36Sopenharmony_ci .execute_tuning = dw_mci_starfive_execute_tuning, 15962306a36Sopenharmony_ci}; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic const struct of_device_id dw_mci_starfive_match[] = { 16262306a36Sopenharmony_ci { .compatible = "starfive,jh7110-mmc", 16362306a36Sopenharmony_ci .data = &starfive_data }, 16462306a36Sopenharmony_ci {}, 16562306a36Sopenharmony_ci}; 16662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, dw_mci_starfive_match); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic int dw_mci_starfive_probe(struct platform_device *pdev) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci return dw_mci_pltfm_register(pdev, &starfive_data); 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic struct platform_driver dw_mci_starfive_driver = { 17462306a36Sopenharmony_ci .probe = dw_mci_starfive_probe, 17562306a36Sopenharmony_ci .remove_new = dw_mci_pltfm_remove, 17662306a36Sopenharmony_ci .driver = { 17762306a36Sopenharmony_ci .name = "dwmmc_starfive", 17862306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 17962306a36Sopenharmony_ci .of_match_table = dw_mci_starfive_match, 18062306a36Sopenharmony_ci }, 18162306a36Sopenharmony_ci}; 18262306a36Sopenharmony_cimodule_platform_driver(dw_mci_starfive_driver); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ciMODULE_DESCRIPTION("StarFive JH7110 Specific DW-MSHC Driver Extension"); 18562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 18662306a36Sopenharmony_ciMODULE_ALIAS("platform:dwmmc_starfive"); 187