[PATCH RFC] mmc: meson-gx: improve clock privisioning
Heiner Kallweit
hkallweit1 at gmail.com
Sat Feb 18 12:07:11 PST 2023
Motivation for dealing with this code is that the driver doesn't work
on my SC2-based test system (HK1 RBOX X4S, based on ah212 board).
The current code makes the assumption that clkin0 is set to XTAL rate
(24MHz) or less, otherwise the initial frequency of 400kHz can't be set,
considering that the maximum divider value is 63.
Currently there's no code for changing the rate of clkin0.
On my system clkin0 is set to 1Ghz (fclkdiv2) when meson-gx mmc driver
is loaded. Therefore the driver doesn't work for me as-is.
Further facts to consider:
The MMC block internal divider isn't strictly needed for clock generation
because the clkin0 hierarchy includes a better (wider) divider that
we could use. It's primary purpose is supporting resampling. The bigger
the divider value the more granularity we have for resampling.
clkin1 is fclkdiv2, and this clock is one parent of clkin0 anyway.
Therefore the MMC block internal mux isn't strictly needed.
What the proposed change does:
- Ignore the MMC block internal mux and use clkin0 only.
- Before setting rate of mmc_clk, set clkin0 to the rate closest to
63 (max_div) * requested_rate. This allows for maximum divider value
and therefore most granularity for resampling.
The changed driver works fine on my system.
I have limited insight in the other Amlogic families supported by this
driver. Therefore patch comes as RFC.
Signed-off-by: Heiner Kallweit <hkallweit1 at gmail.com>
---
drivers/mmc/host/meson-gx-mmc.c | 77 +++++++++++++--------------------
1 file changed, 30 insertions(+), 47 deletions(-)
diff --git a/drivers/mmc/host/meson-gx-mmc.c b/drivers/mmc/host/meson-gx-mmc.c
index 2b963a81c..83d849db6 100644
--- a/drivers/mmc/host/meson-gx-mmc.c
+++ b/drivers/mmc/host/meson-gx-mmc.c
@@ -32,6 +32,7 @@
#define SD_EMMC_CLOCK 0x0
#define CLK_DIV_MASK GENMASK(5, 0)
+#define CLK_DIV_MASK_WIDTH __builtin_popcountl(CLK_DIV_MASK)
#define CLK_SRC_MASK GENMASK(7, 6)
#define CLK_CORE_PHASE_MASK GENMASK(9, 8)
#define CLK_TX_PHASE_MASK GENMASK(11, 10)
@@ -131,8 +132,6 @@
#define SD_EMMC_PRE_REQ_DONE BIT(0)
#define SD_EMMC_DESC_CHAIN_MODE BIT(1)
-#define MUX_CLK_NUM_PARENTS 2
-
struct meson_mmc_data {
unsigned int tx_delay_mask;
unsigned int rx_delay_mask;
@@ -155,7 +154,7 @@ struct meson_host {
struct mmc_command *cmd;
void __iomem *regs;
- struct clk *mux_clk;
+ struct clk *clkin;
struct clk *mmc_clk;
unsigned long req_rate;
bool ddr;
@@ -203,6 +202,21 @@ struct meson_host {
#define CMD_RESP_MASK GENMASK(31, 1)
#define CMD_RESP_SRAM BIT(0)
+static int meson_mmc_clk_set_rate(struct meson_host *host, unsigned long rate)
+{
+ unsigned long max_div;
+ int ret;
+
+ /* maximize divider value, this improves resampling granularity */
+ max_div = min(ULONG_MAX / rate, (1UL << CLK_DIV_MASK_WIDTH) - 1);
+
+ ret = clk_set_rate(host->clkin, rate * max_div);
+ if (ret)
+ return ret;
+
+ return clk_set_rate(host->mmc_clk, rate);
+}
+
static unsigned int meson_mmc_get_timeout_msecs(struct mmc_data *data)
{
unsigned int timeout = data->timeout_ns / NSEC_PER_MSEC;
@@ -386,7 +400,7 @@ static int meson_mmc_clk_set(struct meson_host *host, unsigned long rate,
writel(cfg, host->regs + SD_EMMC_CFG);
host->ddr = ddr;
- ret = clk_set_rate(host->mmc_clk, rate);
+ ret = meson_mmc_clk_set_rate(host, rate);
if (ret) {
dev_err(host->dev, "Unable to set cfg_div_clk to %lu. ret=%d\n",
rate, ret);
@@ -420,11 +434,9 @@ static int meson_mmc_clk_set(struct meson_host *host, unsigned long rate,
static int meson_mmc_clk_init(struct meson_host *host)
{
struct clk_init_data init;
- struct clk_mux *mux;
struct clk_divider *div;
char clk_name[32];
- int i, ret = 0;
- const char *mux_parent_names[MUX_CLK_NUM_PARENTS];
+ int ret = 0;
const char *clk_parent[1];
u32 clk_reg;
@@ -438,40 +450,10 @@ static int meson_mmc_clk_init(struct meson_host *host)
clk_reg |= CLK_IRQ_SDIO_SLEEP(host);
writel(clk_reg, host->regs + SD_EMMC_CLOCK);
- /* get the mux parents */
- for (i = 0; i < MUX_CLK_NUM_PARENTS; i++) {
- struct clk *clk;
- char name[16];
-
- snprintf(name, sizeof(name), "clkin%d", i);
- clk = devm_clk_get(host->dev, name);
- if (IS_ERR(clk))
- return dev_err_probe(host->dev, PTR_ERR(clk),
- "Missing clock %s\n", name);
-
- mux_parent_names[i] = __clk_get_name(clk);
- }
-
- /* create the mux */
- mux = devm_kzalloc(host->dev, sizeof(*mux), GFP_KERNEL);
- if (!mux)
- return -ENOMEM;
-
- snprintf(clk_name, sizeof(clk_name), "%s#mux", dev_name(host->dev));
- init.name = clk_name;
- init.ops = &clk_mux_ops;
- init.flags = 0;
- init.parent_names = mux_parent_names;
- init.num_parents = MUX_CLK_NUM_PARENTS;
-
- mux->reg = host->regs + SD_EMMC_CLOCK;
- mux->shift = __ffs(CLK_SRC_MASK);
- mux->mask = CLK_SRC_MASK >> mux->shift;
- mux->hw.init = &init;
-
- host->mux_clk = devm_clk_register(host->dev, &mux->hw);
- if (WARN_ON(IS_ERR(host->mux_clk)))
- return PTR_ERR(host->mux_clk);
+ host->clkin = devm_clk_get(host->dev, "clkin0");
+ if (IS_ERR(host->clkin))
+ return dev_err_probe(host->dev, PTR_ERR(host->clkin),
+ "Missing clkin0\n");
/* create the divider */
div = devm_kzalloc(host->dev, sizeof(*div), GFP_KERNEL);
@@ -481,14 +463,14 @@ static int meson_mmc_clk_init(struct meson_host *host)
snprintf(clk_name, sizeof(clk_name), "%s#div", dev_name(host->dev));
init.name = clk_name;
init.ops = &clk_divider_ops;
- init.flags = CLK_SET_RATE_PARENT;
- clk_parent[0] = __clk_get_name(host->mux_clk);
+ init.flags = 0;
+ clk_parent[0] = __clk_get_name(host->clkin);
init.parent_names = clk_parent;
init.num_parents = 1;
div->reg = host->regs + SD_EMMC_CLOCK;
div->shift = __ffs(CLK_DIV_MASK);
- div->width = __builtin_popcountl(CLK_DIV_MASK);
+ div->width = CLK_DIV_MASK_WIDTH;
div->hw.init = &init;
div->flags = CLK_DIVIDER_ONE_BASED;
@@ -497,11 +479,12 @@ static int meson_mmc_clk_init(struct meson_host *host)
return PTR_ERR(host->mmc_clk);
/* init SD_EMMC_CLOCK to sane defaults w/min clock rate */
- host->mmc->f_min = clk_round_rate(host->mmc_clk, 400000);
- ret = clk_set_rate(host->mmc_clk, host->mmc->f_min);
+ ret = meson_mmc_clk_set_rate(host, 400000);
if (ret)
return ret;
+ host->mmc->f_min = clk_get_rate(host->mmc_clk);
+
return clk_prepare_enable(host->mmc_clk);
}
@@ -531,7 +514,7 @@ static int meson_mmc_resampling_tuning(struct mmc_host *mmc, u32 opcode)
int ret;
/* Resampling is done using the source clock */
- max_dly = DIV_ROUND_UP(clk_get_rate(host->mux_clk),
+ max_dly = DIV_ROUND_UP(clk_get_rate(host->clkin),
clk_get_rate(host->mmc_clk));
val = readl(host->regs + host->data->adjust);
--
2.39.2
More information about the linux-amlogic
mailing list