mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-27 14:43:58 +08:00
5bb4053b59
Similarly to commitf1e9203e23
("clk: samsung: Fix Exynos 5420 pinctrl setup and clock disable failure due to domain being gated") for Exynos5420, the Exynos4412 also requires that EPLL is not disabled. Otherwise any access to MAUDIO block will silently halt. This was not visible before because EPLL on Exynos4 could not be disabled before commit6edfa11cb3
("clk: samsung: Add enable/disable operation for PLL36XX clocks"). After this commit, on Odroid U3 board one can see silent hang, usually with last (but unrelated) messages: [ 2.382741] input: gpio_keys as /devices/platform/gpio_keys/input/input0 [ 2.405686] usb 1-3: new high-speed USB device number 3 using exynos-ehci [ 2.419843] max77686-rtc max77686-rtc: setting system clock to 2017-06-21 17:04:13 UTC (1498064653) Mark Exynos4 variant as also needed EPLL to be enabled all the time. Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org> Reviewed-by: Sylwester Nawrocki <s.nawrocki@samsung.com> Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
297 lines
7.7 KiB
C
297 lines
7.7 KiB
C
/*
|
|
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
|
* Author: Padmavathi Venna <padma.v@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Common Clock Framework support for Audio Subsystem Clock Controller.
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <dt-bindings/clock/exynos-audss-clk.h>
|
|
|
|
static DEFINE_SPINLOCK(lock);
|
|
static void __iomem *reg_base;
|
|
static struct clk_hw_onecell_data *clk_data;
|
|
/*
|
|
* On Exynos5420 this will be a clock which has to be enabled before any
|
|
* access to audss registers. Typically a child of EPLL.
|
|
*
|
|
* On other platforms this will be -ENODEV.
|
|
*/
|
|
static struct clk *epll;
|
|
|
|
#define ASS_CLK_SRC 0x0
|
|
#define ASS_CLK_DIV 0x4
|
|
#define ASS_CLK_GATE 0x8
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static unsigned long reg_save[][2] = {
|
|
{ ASS_CLK_SRC, 0 },
|
|
{ ASS_CLK_DIV, 0 },
|
|
{ ASS_CLK_GATE, 0 },
|
|
};
|
|
|
|
static int exynos_audss_clk_suspend(struct device *dev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(reg_save); i++)
|
|
reg_save[i][1] = readl(reg_base + reg_save[i][0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_audss_clk_resume(struct device *dev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(reg_save); i++)
|
|
writel(reg_save[i][1], reg_base + reg_save[i][0]);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
struct exynos_audss_clk_drvdata {
|
|
unsigned int has_adma_clk:1;
|
|
unsigned int has_mst_clk:1;
|
|
unsigned int enable_epll:1;
|
|
unsigned int num_clks;
|
|
};
|
|
|
|
static const struct exynos_audss_clk_drvdata exynos4210_drvdata = {
|
|
.num_clks = EXYNOS_AUDSS_MAX_CLKS - 1,
|
|
.enable_epll = 1,
|
|
};
|
|
|
|
static const struct exynos_audss_clk_drvdata exynos5410_drvdata = {
|
|
.num_clks = EXYNOS_AUDSS_MAX_CLKS - 1,
|
|
.has_mst_clk = 1,
|
|
};
|
|
|
|
static const struct exynos_audss_clk_drvdata exynos5420_drvdata = {
|
|
.num_clks = EXYNOS_AUDSS_MAX_CLKS,
|
|
.has_adma_clk = 1,
|
|
.enable_epll = 1,
|
|
};
|
|
|
|
static const struct of_device_id exynos_audss_clk_of_match[] = {
|
|
{
|
|
.compatible = "samsung,exynos4210-audss-clock",
|
|
.data = &exynos4210_drvdata,
|
|
}, {
|
|
.compatible = "samsung,exynos5250-audss-clock",
|
|
.data = &exynos4210_drvdata,
|
|
}, {
|
|
.compatible = "samsung,exynos5410-audss-clock",
|
|
.data = &exynos5410_drvdata,
|
|
}, {
|
|
.compatible = "samsung,exynos5420-audss-clock",
|
|
.data = &exynos5420_drvdata,
|
|
},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, exynos_audss_clk_of_match);
|
|
|
|
static void exynos_audss_clk_teardown(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = EXYNOS_MOUT_AUDSS; i < EXYNOS_DOUT_SRP; i++) {
|
|
if (!IS_ERR(clk_data->hws[i]))
|
|
clk_hw_unregister_mux(clk_data->hws[i]);
|
|
}
|
|
|
|
for (; i < EXYNOS_SRP_CLK; i++) {
|
|
if (!IS_ERR(clk_data->hws[i]))
|
|
clk_hw_unregister_divider(clk_data->hws[i]);
|
|
}
|
|
|
|
for (; i < clk_data->num; i++) {
|
|
if (!IS_ERR(clk_data->hws[i]))
|
|
clk_hw_unregister_gate(clk_data->hws[i]);
|
|
}
|
|
}
|
|
|
|
/* register exynos_audss clocks */
|
|
static int exynos_audss_clk_probe(struct platform_device *pdev)
|
|
{
|
|
const char *mout_audss_p[] = {"fin_pll", "fout_epll"};
|
|
const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"};
|
|
const char *sclk_pcm_p = "sclk_pcm0";
|
|
struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in;
|
|
const struct exynos_audss_clk_drvdata *variant;
|
|
struct clk_hw **clk_table;
|
|
struct resource *res;
|
|
int i, ret = 0;
|
|
|
|
variant = of_device_get_match_data(&pdev->dev);
|
|
if (!variant)
|
|
return -EINVAL;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
reg_base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(reg_base)) {
|
|
dev_err(&pdev->dev, "failed to map audss registers\n");
|
|
return PTR_ERR(reg_base);
|
|
}
|
|
|
|
epll = ERR_PTR(-ENODEV);
|
|
|
|
clk_data = devm_kzalloc(&pdev->dev,
|
|
sizeof(*clk_data) +
|
|
sizeof(*clk_data->hws) * EXYNOS_AUDSS_MAX_CLKS,
|
|
GFP_KERNEL);
|
|
if (!clk_data)
|
|
return -ENOMEM;
|
|
|
|
clk_data->num = variant->num_clks;
|
|
clk_table = clk_data->hws;
|
|
|
|
pll_ref = devm_clk_get(&pdev->dev, "pll_ref");
|
|
pll_in = devm_clk_get(&pdev->dev, "pll_in");
|
|
if (!IS_ERR(pll_ref))
|
|
mout_audss_p[0] = __clk_get_name(pll_ref);
|
|
if (!IS_ERR(pll_in)) {
|
|
mout_audss_p[1] = __clk_get_name(pll_in);
|
|
|
|
if (variant->enable_epll) {
|
|
epll = pll_in;
|
|
|
|
ret = clk_prepare_enable(epll);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"failed to prepare the epll clock\n");
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
clk_table[EXYNOS_MOUT_AUDSS] = clk_hw_register_mux(NULL, "mout_audss",
|
|
mout_audss_p, ARRAY_SIZE(mout_audss_p),
|
|
CLK_SET_RATE_NO_REPARENT,
|
|
reg_base + ASS_CLK_SRC, 0, 1, 0, &lock);
|
|
|
|
cdclk = devm_clk_get(&pdev->dev, "cdclk");
|
|
sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio");
|
|
if (!IS_ERR(cdclk))
|
|
mout_i2s_p[1] = __clk_get_name(cdclk);
|
|
if (!IS_ERR(sclk_audio))
|
|
mout_i2s_p[2] = __clk_get_name(sclk_audio);
|
|
clk_table[EXYNOS_MOUT_I2S] = clk_hw_register_mux(NULL, "mout_i2s",
|
|
mout_i2s_p, ARRAY_SIZE(mout_i2s_p),
|
|
CLK_SET_RATE_NO_REPARENT,
|
|
reg_base + ASS_CLK_SRC, 2, 2, 0, &lock);
|
|
|
|
clk_table[EXYNOS_DOUT_SRP] = clk_hw_register_divider(NULL, "dout_srp",
|
|
"mout_audss", 0, reg_base + ASS_CLK_DIV, 0, 4,
|
|
0, &lock);
|
|
|
|
clk_table[EXYNOS_DOUT_AUD_BUS] = clk_hw_register_divider(NULL,
|
|
"dout_aud_bus", "dout_srp", 0,
|
|
reg_base + ASS_CLK_DIV, 4, 4, 0, &lock);
|
|
|
|
clk_table[EXYNOS_DOUT_I2S] = clk_hw_register_divider(NULL, "dout_i2s",
|
|
"mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0,
|
|
&lock);
|
|
|
|
clk_table[EXYNOS_SRP_CLK] = clk_hw_register_gate(NULL, "srp_clk",
|
|
"dout_srp", CLK_SET_RATE_PARENT,
|
|
reg_base + ASS_CLK_GATE, 0, 0, &lock);
|
|
|
|
clk_table[EXYNOS_I2S_BUS] = clk_hw_register_gate(NULL, "i2s_bus",
|
|
"dout_aud_bus", CLK_SET_RATE_PARENT,
|
|
reg_base + ASS_CLK_GATE, 2, 0, &lock);
|
|
|
|
clk_table[EXYNOS_SCLK_I2S] = clk_hw_register_gate(NULL, "sclk_i2s",
|
|
"dout_i2s", CLK_SET_RATE_PARENT,
|
|
reg_base + ASS_CLK_GATE, 3, 0, &lock);
|
|
|
|
clk_table[EXYNOS_PCM_BUS] = clk_hw_register_gate(NULL, "pcm_bus",
|
|
"sclk_pcm", CLK_SET_RATE_PARENT,
|
|
reg_base + ASS_CLK_GATE, 4, 0, &lock);
|
|
|
|
sclk_pcm_in = devm_clk_get(&pdev->dev, "sclk_pcm_in");
|
|
if (!IS_ERR(sclk_pcm_in))
|
|
sclk_pcm_p = __clk_get_name(sclk_pcm_in);
|
|
clk_table[EXYNOS_SCLK_PCM] = clk_hw_register_gate(NULL, "sclk_pcm",
|
|
sclk_pcm_p, CLK_SET_RATE_PARENT,
|
|
reg_base + ASS_CLK_GATE, 5, 0, &lock);
|
|
|
|
if (variant->has_adma_clk) {
|
|
clk_table[EXYNOS_ADMA] = clk_hw_register_gate(NULL, "adma",
|
|
"dout_srp", CLK_SET_RATE_PARENT,
|
|
reg_base + ASS_CLK_GATE, 9, 0, &lock);
|
|
}
|
|
|
|
for (i = 0; i < clk_data->num; i++) {
|
|
if (IS_ERR(clk_table[i])) {
|
|
dev_err(&pdev->dev, "failed to register clock %d\n", i);
|
|
ret = PTR_ERR(clk_table[i]);
|
|
goto unregister;
|
|
}
|
|
}
|
|
|
|
ret = of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_onecell_get,
|
|
clk_data);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to add clock provider\n");
|
|
goto unregister;
|
|
}
|
|
|
|
return 0;
|
|
|
|
unregister:
|
|
exynos_audss_clk_teardown();
|
|
|
|
if (!IS_ERR(epll))
|
|
clk_disable_unprepare(epll);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_audss_clk_remove(struct platform_device *pdev)
|
|
{
|
|
of_clk_del_provider(pdev->dev.of_node);
|
|
|
|
exynos_audss_clk_teardown();
|
|
|
|
if (!IS_ERR(epll))
|
|
clk_disable_unprepare(epll);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops exynos_audss_clk_pm_ops = {
|
|
SET_LATE_SYSTEM_SLEEP_PM_OPS(exynos_audss_clk_suspend,
|
|
exynos_audss_clk_resume)
|
|
};
|
|
|
|
static struct platform_driver exynos_audss_clk_driver = {
|
|
.driver = {
|
|
.name = "exynos-audss-clk",
|
|
.of_match_table = exynos_audss_clk_of_match,
|
|
.pm = &exynos_audss_clk_pm_ops,
|
|
},
|
|
.probe = exynos_audss_clk_probe,
|
|
.remove = exynos_audss_clk_remove,
|
|
};
|
|
|
|
module_platform_driver(exynos_audss_clk_driver);
|
|
|
|
MODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>");
|
|
MODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:exynos-audss-clk");
|