mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2025-01-19 10:56:12 +07:00
mmc: sdhci-of-arasan: Properly set corecfg_baseclkfreq on rk3399
In the the earlier change in this series ("Documentation: mmc: sdhci-of-arasan: Add soc-ctl-syscon for corecfg regs") we can see the mechansim for specifying a syscon to properly set corecfg registers in sdhci-of-arasan. Now let's use this mechanism to properly set corecfg_baseclkfreq on rk3399. >From [1] the corecfg_baseclkfreq is supposed to be set to: Base Clock Frequency for SD Clock. This is the frequency of the xin_clk. This is a relatively easy thing to do. Note that we assume that xin_clk is not dynamic and we can check the clock at probe time. If any real devices have a dynamic xin_clk future patches could register for notifiers for the clock. At the moment, setting corecfg_baseclkfreq is only supported for rk3399 since we need a specific map for each implementation. The code is written in a generic way that should make this easy to extend to other SoCs. Note that a specific compatible string for rk3399 is already in use and so we add that to the table to match rk3399. [1]: https://arasan.com/wp-content/media/eMMC-5-1-Total-Solution_Rev-1-3.pdf Signed-off-by: Douglas Anderson <dianders@chromium.org> Reviewed-by: Heiko Stuebner <heiko@sntech.de> Reviewed-by: Shawn Lin <shawn.lin@rock-chips.com> Tested-by: Heiko Stuebner <heiko@sntech.de> Acked-by: Adrian Hunter <adrian.hunter@intel.com> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
parent
6582019927
commit
3ea4666e8d
@ -19,9 +19,11 @@
|
|||||||
* your option) any later version.
|
* your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/mfd/syscon.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/of_device.h>
|
#include <linux/of_device.h>
|
||||||
#include <linux/phy/phy.h>
|
#include <linux/phy/phy.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
#include "sdhci-pltfm.h"
|
#include "sdhci-pltfm.h"
|
||||||
|
|
||||||
#define SDHCI_ARASAN_CLK_CTRL_OFFSET 0x2c
|
#define SDHCI_ARASAN_CLK_CTRL_OFFSET 0x2c
|
||||||
@ -32,18 +34,115 @@
|
|||||||
#define CLK_CTRL_TIMEOUT_MASK (0xf << CLK_CTRL_TIMEOUT_SHIFT)
|
#define CLK_CTRL_TIMEOUT_MASK (0xf << CLK_CTRL_TIMEOUT_SHIFT)
|
||||||
#define CLK_CTRL_TIMEOUT_MIN_EXP 13
|
#define CLK_CTRL_TIMEOUT_MIN_EXP 13
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On some SoCs the syscon area has a feature where the upper 16-bits of
|
||||||
|
* each 32-bit register act as a write mask for the lower 16-bits. This allows
|
||||||
|
* atomic updates of the register without locking. This macro is used on SoCs
|
||||||
|
* that have that feature.
|
||||||
|
*/
|
||||||
|
#define HIWORD_UPDATE(val, mask, shift) \
|
||||||
|
((val) << (shift) | (mask) << ((shift) + 16))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct sdhci_arasan_soc_ctl_field - Field used in sdhci_arasan_soc_ctl_map
|
||||||
|
*
|
||||||
|
* @reg: Offset within the syscon of the register containing this field
|
||||||
|
* @width: Number of bits for this field
|
||||||
|
* @shift: Bit offset within @reg of this field (or -1 if not avail)
|
||||||
|
*/
|
||||||
|
struct sdhci_arasan_soc_ctl_field {
|
||||||
|
u32 reg;
|
||||||
|
u16 width;
|
||||||
|
s16 shift;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct sdhci_arasan_soc_ctl_map - Map in syscon to corecfg registers
|
||||||
|
*
|
||||||
|
* It's up to the licensee of the Arsan IP block to make these available
|
||||||
|
* somewhere if needed. Presumably these will be scattered somewhere that's
|
||||||
|
* accessible via the syscon API.
|
||||||
|
*
|
||||||
|
* @baseclkfreq: Where to find corecfg_baseclkfreq
|
||||||
|
* @hiword_update: If true, use HIWORD_UPDATE to access the syscon
|
||||||
|
*/
|
||||||
|
struct sdhci_arasan_soc_ctl_map {
|
||||||
|
struct sdhci_arasan_soc_ctl_field baseclkfreq;
|
||||||
|
bool hiword_update;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct sdhci_arasan_data
|
* struct sdhci_arasan_data
|
||||||
* @clk_ahb: Pointer to the AHB clock
|
* @clk_ahb: Pointer to the AHB clock
|
||||||
* @phy: Pointer to the generic phy
|
* @phy: Pointer to the generic phy
|
||||||
* @phy_on: True if the PHY is turned on.
|
* @phy_on: True if the PHY is turned on.
|
||||||
|
* @soc_ctl_base: Pointer to regmap for syscon for soc_ctl registers.
|
||||||
|
* @soc_ctl_map: Map to get offsets into soc_ctl registers.
|
||||||
*/
|
*/
|
||||||
struct sdhci_arasan_data {
|
struct sdhci_arasan_data {
|
||||||
struct clk *clk_ahb;
|
struct clk *clk_ahb;
|
||||||
struct phy *phy;
|
struct phy *phy;
|
||||||
bool phy_on;
|
bool phy_on;
|
||||||
|
|
||||||
|
struct regmap *soc_ctl_base;
|
||||||
|
const struct sdhci_arasan_soc_ctl_map *soc_ctl_map;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct sdhci_arasan_soc_ctl_map rk3399_soc_ctl_map = {
|
||||||
|
.baseclkfreq = { .reg = 0xf000, .width = 8, .shift = 8 },
|
||||||
|
.hiword_update = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sdhci_arasan_syscon_write - Write to a field in soc_ctl registers
|
||||||
|
*
|
||||||
|
* This function allows writing to fields in sdhci_arasan_soc_ctl_map.
|
||||||
|
* Note that if a field is specified as not available (shift < 0) then
|
||||||
|
* this function will silently return an error code. It will be noisy
|
||||||
|
* and print errors for any other (unexpected) errors.
|
||||||
|
*
|
||||||
|
* @host: The sdhci_host
|
||||||
|
* @fld: The field to write to
|
||||||
|
* @val: The value to write
|
||||||
|
*/
|
||||||
|
static int sdhci_arasan_syscon_write(struct sdhci_host *host,
|
||||||
|
const struct sdhci_arasan_soc_ctl_field *fld,
|
||||||
|
u32 val)
|
||||||
|
{
|
||||||
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||||
|
struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
|
||||||
|
struct regmap *soc_ctl_base = sdhci_arasan->soc_ctl_base;
|
||||||
|
u32 reg = fld->reg;
|
||||||
|
u16 width = fld->width;
|
||||||
|
s16 shift = fld->shift;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Silently return errors for shift < 0 so caller doesn't have
|
||||||
|
* to check for fields which are optional. For fields that
|
||||||
|
* are required then caller needs to do something special
|
||||||
|
* anyway.
|
||||||
|
*/
|
||||||
|
if (shift < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (sdhci_arasan->soc_ctl_map->hiword_update)
|
||||||
|
ret = regmap_write(soc_ctl_base, reg,
|
||||||
|
HIWORD_UPDATE(val, GENMASK(width, 0),
|
||||||
|
shift));
|
||||||
|
else
|
||||||
|
ret = regmap_update_bits(soc_ctl_base, reg,
|
||||||
|
GENMASK(shift + width, shift),
|
||||||
|
val << shift);
|
||||||
|
|
||||||
|
/* Yell about (unexpected) regmap errors */
|
||||||
|
if (ret)
|
||||||
|
pr_warn("%s: Regmap write fail: %d\n",
|
||||||
|
mmc_hostname(host->mmc), ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned int sdhci_arasan_get_timeout_clock(struct sdhci_host *host)
|
static unsigned int sdhci_arasan_get_timeout_clock(struct sdhci_host *host)
|
||||||
{
|
{
|
||||||
u32 div;
|
u32 div;
|
||||||
@ -191,9 +290,66 @@ static int sdhci_arasan_resume(struct device *dev)
|
|||||||
static SIMPLE_DEV_PM_OPS(sdhci_arasan_dev_pm_ops, sdhci_arasan_suspend,
|
static SIMPLE_DEV_PM_OPS(sdhci_arasan_dev_pm_ops, sdhci_arasan_suspend,
|
||||||
sdhci_arasan_resume);
|
sdhci_arasan_resume);
|
||||||
|
|
||||||
|
static const struct of_device_id sdhci_arasan_of_match[] = {
|
||||||
|
/* SoC-specific compatible strings w/ soc_ctl_map */
|
||||||
|
{
|
||||||
|
.compatible = "rockchip,rk3399-sdhci-5.1",
|
||||||
|
.data = &rk3399_soc_ctl_map,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Generic compatible below here */
|
||||||
|
{ .compatible = "arasan,sdhci-8.9a" },
|
||||||
|
{ .compatible = "arasan,sdhci-5.1" },
|
||||||
|
{ .compatible = "arasan,sdhci-4.9a" },
|
||||||
|
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sdhci_arasan_update_baseclkfreq - Set corecfg_baseclkfreq
|
||||||
|
*
|
||||||
|
* The corecfg_baseclkfreq is supposed to contain the MHz of clk_xin. This
|
||||||
|
* function can be used to make that happen.
|
||||||
|
*
|
||||||
|
* NOTES:
|
||||||
|
* - Many existing devices don't seem to do this and work fine. To keep
|
||||||
|
* compatibility for old hardware where the device tree doesn't provide a
|
||||||
|
* register map, this function is a noop if a soc_ctl_map hasn't been provided
|
||||||
|
* for this platform.
|
||||||
|
* - It's assumed that clk_xin is not dynamic and that we use the SDHCI divider
|
||||||
|
* to achieve lower clock rates. That means that this function is called once
|
||||||
|
* at probe time and never called again.
|
||||||
|
*
|
||||||
|
* @host: The sdhci_host
|
||||||
|
*/
|
||||||
|
static void sdhci_arasan_update_baseclkfreq(struct sdhci_host *host)
|
||||||
|
{
|
||||||
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||||
|
struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
|
||||||
|
const struct sdhci_arasan_soc_ctl_map *soc_ctl_map =
|
||||||
|
sdhci_arasan->soc_ctl_map;
|
||||||
|
u32 mhz = DIV_ROUND_CLOSEST(clk_get_rate(pltfm_host->clk), 1000000);
|
||||||
|
|
||||||
|
/* Having a map is optional */
|
||||||
|
if (!soc_ctl_map)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* If we have a map, we expect to have a syscon */
|
||||||
|
if (!sdhci_arasan->soc_ctl_base) {
|
||||||
|
pr_warn("%s: Have regmap, but no soc-ctl-syscon\n",
|
||||||
|
mmc_hostname(host->mmc));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdhci_arasan_syscon_write(host, &soc_ctl_map->baseclkfreq, mhz);
|
||||||
|
}
|
||||||
|
|
||||||
static int sdhci_arasan_probe(struct platform_device *pdev)
|
static int sdhci_arasan_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
const struct of_device_id *match;
|
||||||
|
struct device_node *node;
|
||||||
struct clk *clk_xin;
|
struct clk *clk_xin;
|
||||||
struct sdhci_host *host;
|
struct sdhci_host *host;
|
||||||
struct sdhci_pltfm_host *pltfm_host;
|
struct sdhci_pltfm_host *pltfm_host;
|
||||||
@ -207,6 +363,23 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
|
|||||||
pltfm_host = sdhci_priv(host);
|
pltfm_host = sdhci_priv(host);
|
||||||
sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
|
sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
|
||||||
|
|
||||||
|
match = of_match_node(sdhci_arasan_of_match, pdev->dev.of_node);
|
||||||
|
sdhci_arasan->soc_ctl_map = match->data;
|
||||||
|
|
||||||
|
node = of_parse_phandle(pdev->dev.of_node, "arasan,soc-ctl-syscon", 0);
|
||||||
|
if (node) {
|
||||||
|
sdhci_arasan->soc_ctl_base = syscon_node_to_regmap(node);
|
||||||
|
of_node_put(node);
|
||||||
|
|
||||||
|
if (IS_ERR(sdhci_arasan->soc_ctl_base)) {
|
||||||
|
ret = PTR_ERR(sdhci_arasan->soc_ctl_base);
|
||||||
|
if (ret != -EPROBE_DEFER)
|
||||||
|
dev_err(&pdev->dev, "Can't get syscon: %d\n",
|
||||||
|
ret);
|
||||||
|
goto err_pltfm_free;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sdhci_arasan->clk_ahb = devm_clk_get(&pdev->dev, "clk_ahb");
|
sdhci_arasan->clk_ahb = devm_clk_get(&pdev->dev, "clk_ahb");
|
||||||
if (IS_ERR(sdhci_arasan->clk_ahb)) {
|
if (IS_ERR(sdhci_arasan->clk_ahb)) {
|
||||||
dev_err(&pdev->dev, "clk_ahb clock not found.\n");
|
dev_err(&pdev->dev, "clk_ahb clock not found.\n");
|
||||||
@ -236,6 +409,8 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
|
|||||||
sdhci_get_of_property(pdev);
|
sdhci_get_of_property(pdev);
|
||||||
pltfm_host->clk = clk_xin;
|
pltfm_host->clk = clk_xin;
|
||||||
|
|
||||||
|
sdhci_arasan_update_baseclkfreq(host);
|
||||||
|
|
||||||
ret = mmc_of_parse(host->mmc);
|
ret = mmc_of_parse(host->mmc);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(&pdev->dev, "parsing dt failed (%u)\n", ret);
|
dev_err(&pdev->dev, "parsing dt failed (%u)\n", ret);
|
||||||
@ -301,14 +476,6 @@ static int sdhci_arasan_remove(struct platform_device *pdev)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct of_device_id sdhci_arasan_of_match[] = {
|
|
||||||
{ .compatible = "arasan,sdhci-8.9a" },
|
|
||||||
{ .compatible = "arasan,sdhci-5.1" },
|
|
||||||
{ .compatible = "arasan,sdhci-4.9a" },
|
|
||||||
{ }
|
|
||||||
};
|
|
||||||
MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match);
|
|
||||||
|
|
||||||
static struct platform_driver sdhci_arasan_driver = {
|
static struct platform_driver sdhci_arasan_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "sdhci-arasan",
|
.name = "sdhci-arasan",
|
||||||
|
Loading…
Reference in New Issue
Block a user