162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2013 Samsung Electronics Co., Ltd. 462306a36Sopenharmony_ci * Author: Padmavathi Venna <padma.v@samsung.com> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Common Clock Framework support for Audio Subsystem Clock Controller. 762306a36Sopenharmony_ci*/ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci#include <linux/io.h> 1162306a36Sopenharmony_ci#include <linux/clk.h> 1262306a36Sopenharmony_ci#include <linux/clk-provider.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <dt-bindings/clock/exynos-audss-clk.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic DEFINE_SPINLOCK(lock); 2162306a36Sopenharmony_cistatic void __iomem *reg_base; 2262306a36Sopenharmony_cistatic struct clk_hw_onecell_data *clk_data; 2362306a36Sopenharmony_ci/* 2462306a36Sopenharmony_ci * On Exynos5420 this will be a clock which has to be enabled before any 2562306a36Sopenharmony_ci * access to audss registers. Typically a child of EPLL. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * On other platforms this will be -ENODEV. 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_cistatic struct clk *epll; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define ASS_CLK_SRC 0x0 3262306a36Sopenharmony_ci#define ASS_CLK_DIV 0x4 3362306a36Sopenharmony_ci#define ASS_CLK_GATE 0x8 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic unsigned long reg_save[][2] = { 3662306a36Sopenharmony_ci { ASS_CLK_SRC, 0 }, 3762306a36Sopenharmony_ci { ASS_CLK_DIV, 0 }, 3862306a36Sopenharmony_ci { ASS_CLK_GATE, 0 }, 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic int __maybe_unused exynos_audss_clk_suspend(struct device *dev) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci int i; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(reg_save); i++) 4662306a36Sopenharmony_ci reg_save[i][1] = readl(reg_base + reg_save[i][0]); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci return 0; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic int __maybe_unused exynos_audss_clk_resume(struct device *dev) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci int i; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(reg_save); i++) 5662306a36Sopenharmony_ci writel(reg_save[i][1], reg_base + reg_save[i][0]); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci return 0; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistruct exynos_audss_clk_drvdata { 6262306a36Sopenharmony_ci unsigned int has_adma_clk:1; 6362306a36Sopenharmony_ci unsigned int has_mst_clk:1; 6462306a36Sopenharmony_ci unsigned int enable_epll:1; 6562306a36Sopenharmony_ci unsigned int num_clks; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic const struct exynos_audss_clk_drvdata exynos4210_drvdata = { 6962306a36Sopenharmony_ci .num_clks = EXYNOS_AUDSS_MAX_CLKS - 1, 7062306a36Sopenharmony_ci .enable_epll = 1, 7162306a36Sopenharmony_ci}; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic const struct exynos_audss_clk_drvdata exynos5410_drvdata = { 7462306a36Sopenharmony_ci .num_clks = EXYNOS_AUDSS_MAX_CLKS - 1, 7562306a36Sopenharmony_ci .has_mst_clk = 1, 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic const struct exynos_audss_clk_drvdata exynos5420_drvdata = { 7962306a36Sopenharmony_ci .num_clks = EXYNOS_AUDSS_MAX_CLKS, 8062306a36Sopenharmony_ci .has_adma_clk = 1, 8162306a36Sopenharmony_ci .enable_epll = 1, 8262306a36Sopenharmony_ci}; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic const struct of_device_id exynos_audss_clk_of_match[] = { 8562306a36Sopenharmony_ci { 8662306a36Sopenharmony_ci .compatible = "samsung,exynos4210-audss-clock", 8762306a36Sopenharmony_ci .data = &exynos4210_drvdata, 8862306a36Sopenharmony_ci }, { 8962306a36Sopenharmony_ci .compatible = "samsung,exynos5250-audss-clock", 9062306a36Sopenharmony_ci .data = &exynos4210_drvdata, 9162306a36Sopenharmony_ci }, { 9262306a36Sopenharmony_ci .compatible = "samsung,exynos5410-audss-clock", 9362306a36Sopenharmony_ci .data = &exynos5410_drvdata, 9462306a36Sopenharmony_ci }, { 9562306a36Sopenharmony_ci .compatible = "samsung,exynos5420-audss-clock", 9662306a36Sopenharmony_ci .data = &exynos5420_drvdata, 9762306a36Sopenharmony_ci }, 9862306a36Sopenharmony_ci { }, 9962306a36Sopenharmony_ci}; 10062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, exynos_audss_clk_of_match); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic void exynos_audss_clk_teardown(void) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci int i; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci for (i = EXYNOS_MOUT_AUDSS; i < EXYNOS_DOUT_SRP; i++) { 10762306a36Sopenharmony_ci if (!IS_ERR(clk_data->hws[i])) 10862306a36Sopenharmony_ci clk_hw_unregister_mux(clk_data->hws[i]); 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci for (; i < EXYNOS_SRP_CLK; i++) { 11262306a36Sopenharmony_ci if (!IS_ERR(clk_data->hws[i])) 11362306a36Sopenharmony_ci clk_hw_unregister_divider(clk_data->hws[i]); 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci for (; i < clk_data->num; i++) { 11762306a36Sopenharmony_ci if (!IS_ERR(clk_data->hws[i])) 11862306a36Sopenharmony_ci clk_hw_unregister_gate(clk_data->hws[i]); 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci/* register exynos_audss clocks */ 12362306a36Sopenharmony_cistatic int exynos_audss_clk_probe(struct platform_device *pdev) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci const char *mout_audss_p[] = {"fin_pll", "fout_epll"}; 12662306a36Sopenharmony_ci const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"}; 12762306a36Sopenharmony_ci const char *sclk_pcm_p = "sclk_pcm0"; 12862306a36Sopenharmony_ci struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in; 12962306a36Sopenharmony_ci const struct exynos_audss_clk_drvdata *variant; 13062306a36Sopenharmony_ci struct clk_hw **clk_table; 13162306a36Sopenharmony_ci struct device *dev = &pdev->dev; 13262306a36Sopenharmony_ci int i, ret = 0; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci variant = of_device_get_match_data(&pdev->dev); 13562306a36Sopenharmony_ci if (!variant) 13662306a36Sopenharmony_ci return -EINVAL; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci reg_base = devm_platform_ioremap_resource(pdev, 0); 13962306a36Sopenharmony_ci if (IS_ERR(reg_base)) 14062306a36Sopenharmony_ci return PTR_ERR(reg_base); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci epll = ERR_PTR(-ENODEV); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci clk_data = devm_kzalloc(dev, 14562306a36Sopenharmony_ci struct_size(clk_data, hws, 14662306a36Sopenharmony_ci EXYNOS_AUDSS_MAX_CLKS), 14762306a36Sopenharmony_ci GFP_KERNEL); 14862306a36Sopenharmony_ci if (!clk_data) 14962306a36Sopenharmony_ci return -ENOMEM; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci clk_data->num = variant->num_clks; 15262306a36Sopenharmony_ci clk_table = clk_data->hws; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci pll_ref = devm_clk_get(dev, "pll_ref"); 15562306a36Sopenharmony_ci pll_in = devm_clk_get(dev, "pll_in"); 15662306a36Sopenharmony_ci if (!IS_ERR(pll_ref)) 15762306a36Sopenharmony_ci mout_audss_p[0] = __clk_get_name(pll_ref); 15862306a36Sopenharmony_ci if (!IS_ERR(pll_in)) { 15962306a36Sopenharmony_ci mout_audss_p[1] = __clk_get_name(pll_in); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci if (variant->enable_epll) { 16262306a36Sopenharmony_ci epll = pll_in; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci ret = clk_prepare_enable(epll); 16562306a36Sopenharmony_ci if (ret) { 16662306a36Sopenharmony_ci dev_err(dev, 16762306a36Sopenharmony_ci "failed to prepare the epll clock\n"); 16862306a36Sopenharmony_ci return ret; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci /* 17462306a36Sopenharmony_ci * Enable runtime PM here to allow the clock core using runtime PM 17562306a36Sopenharmony_ci * for the registered clocks. Additionally, we increase the runtime 17662306a36Sopenharmony_ci * PM usage count before registering the clocks, to prevent the 17762306a36Sopenharmony_ci * clock core from runtime suspending the device. 17862306a36Sopenharmony_ci */ 17962306a36Sopenharmony_ci pm_runtime_get_noresume(dev); 18062306a36Sopenharmony_ci pm_runtime_set_active(dev); 18162306a36Sopenharmony_ci pm_runtime_enable(dev); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci clk_table[EXYNOS_MOUT_AUDSS] = clk_hw_register_mux(dev, "mout_audss", 18462306a36Sopenharmony_ci mout_audss_p, ARRAY_SIZE(mout_audss_p), 18562306a36Sopenharmony_ci CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, 18662306a36Sopenharmony_ci reg_base + ASS_CLK_SRC, 0, 1, 0, &lock); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci cdclk = devm_clk_get(dev, "cdclk"); 18962306a36Sopenharmony_ci sclk_audio = devm_clk_get(dev, "sclk_audio"); 19062306a36Sopenharmony_ci if (!IS_ERR(cdclk)) 19162306a36Sopenharmony_ci mout_i2s_p[1] = __clk_get_name(cdclk); 19262306a36Sopenharmony_ci if (!IS_ERR(sclk_audio)) 19362306a36Sopenharmony_ci mout_i2s_p[2] = __clk_get_name(sclk_audio); 19462306a36Sopenharmony_ci clk_table[EXYNOS_MOUT_I2S] = clk_hw_register_mux(dev, "mout_i2s", 19562306a36Sopenharmony_ci mout_i2s_p, ARRAY_SIZE(mout_i2s_p), 19662306a36Sopenharmony_ci CLK_SET_RATE_NO_REPARENT, 19762306a36Sopenharmony_ci reg_base + ASS_CLK_SRC, 2, 2, 0, &lock); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci clk_table[EXYNOS_DOUT_SRP] = clk_hw_register_divider(dev, "dout_srp", 20062306a36Sopenharmony_ci "mout_audss", CLK_SET_RATE_PARENT, 20162306a36Sopenharmony_ci reg_base + ASS_CLK_DIV, 0, 4, 0, &lock); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci clk_table[EXYNOS_DOUT_AUD_BUS] = clk_hw_register_divider(dev, 20462306a36Sopenharmony_ci "dout_aud_bus", "dout_srp", CLK_SET_RATE_PARENT, 20562306a36Sopenharmony_ci reg_base + ASS_CLK_DIV, 4, 4, 0, &lock); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci clk_table[EXYNOS_DOUT_I2S] = clk_hw_register_divider(dev, "dout_i2s", 20862306a36Sopenharmony_ci "mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0, 20962306a36Sopenharmony_ci &lock); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci clk_table[EXYNOS_SRP_CLK] = clk_hw_register_gate(dev, "srp_clk", 21262306a36Sopenharmony_ci "dout_srp", CLK_SET_RATE_PARENT, 21362306a36Sopenharmony_ci reg_base + ASS_CLK_GATE, 0, 0, &lock); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci clk_table[EXYNOS_I2S_BUS] = clk_hw_register_gate(dev, "i2s_bus", 21662306a36Sopenharmony_ci "dout_aud_bus", CLK_SET_RATE_PARENT, 21762306a36Sopenharmony_ci reg_base + ASS_CLK_GATE, 2, 0, &lock); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci clk_table[EXYNOS_SCLK_I2S] = clk_hw_register_gate(dev, "sclk_i2s", 22062306a36Sopenharmony_ci "dout_i2s", CLK_SET_RATE_PARENT, 22162306a36Sopenharmony_ci reg_base + ASS_CLK_GATE, 3, 0, &lock); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci clk_table[EXYNOS_PCM_BUS] = clk_hw_register_gate(dev, "pcm_bus", 22462306a36Sopenharmony_ci "sclk_pcm", CLK_SET_RATE_PARENT, 22562306a36Sopenharmony_ci reg_base + ASS_CLK_GATE, 4, 0, &lock); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci sclk_pcm_in = devm_clk_get(dev, "sclk_pcm_in"); 22862306a36Sopenharmony_ci if (!IS_ERR(sclk_pcm_in)) 22962306a36Sopenharmony_ci sclk_pcm_p = __clk_get_name(sclk_pcm_in); 23062306a36Sopenharmony_ci clk_table[EXYNOS_SCLK_PCM] = clk_hw_register_gate(dev, "sclk_pcm", 23162306a36Sopenharmony_ci sclk_pcm_p, CLK_SET_RATE_PARENT, 23262306a36Sopenharmony_ci reg_base + ASS_CLK_GATE, 5, 0, &lock); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci if (variant->has_adma_clk) { 23562306a36Sopenharmony_ci clk_table[EXYNOS_ADMA] = clk_hw_register_gate(dev, "adma", 23662306a36Sopenharmony_ci "dout_srp", CLK_SET_RATE_PARENT, 23762306a36Sopenharmony_ci reg_base + ASS_CLK_GATE, 9, 0, &lock); 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci for (i = 0; i < clk_data->num; i++) { 24162306a36Sopenharmony_ci if (IS_ERR(clk_table[i])) { 24262306a36Sopenharmony_ci dev_err(dev, "failed to register clock %d\n", i); 24362306a36Sopenharmony_ci ret = PTR_ERR(clk_table[i]); 24462306a36Sopenharmony_ci goto unregister; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, 24962306a36Sopenharmony_ci clk_data); 25062306a36Sopenharmony_ci if (ret) { 25162306a36Sopenharmony_ci dev_err(dev, "failed to add clock provider\n"); 25262306a36Sopenharmony_ci goto unregister; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci pm_runtime_put_sync(dev); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci return 0; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ciunregister: 26062306a36Sopenharmony_ci exynos_audss_clk_teardown(); 26162306a36Sopenharmony_ci pm_runtime_put_sync(dev); 26262306a36Sopenharmony_ci pm_runtime_disable(dev); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci if (!IS_ERR(epll)) 26562306a36Sopenharmony_ci clk_disable_unprepare(epll); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return ret; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic void exynos_audss_clk_remove(struct platform_device *pdev) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci of_clk_del_provider(pdev->dev.of_node); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci exynos_audss_clk_teardown(); 27562306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (!IS_ERR(epll)) 27862306a36Sopenharmony_ci clk_disable_unprepare(epll); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic const struct dev_pm_ops exynos_audss_clk_pm_ops = { 28262306a36Sopenharmony_ci SET_RUNTIME_PM_OPS(exynos_audss_clk_suspend, exynos_audss_clk_resume, 28362306a36Sopenharmony_ci NULL) 28462306a36Sopenharmony_ci SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 28562306a36Sopenharmony_ci pm_runtime_force_resume) 28662306a36Sopenharmony_ci}; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic struct platform_driver exynos_audss_clk_driver = { 28962306a36Sopenharmony_ci .driver = { 29062306a36Sopenharmony_ci .name = "exynos-audss-clk", 29162306a36Sopenharmony_ci .of_match_table = exynos_audss_clk_of_match, 29262306a36Sopenharmony_ci .pm = &exynos_audss_clk_pm_ops, 29362306a36Sopenharmony_ci }, 29462306a36Sopenharmony_ci .probe = exynos_audss_clk_probe, 29562306a36Sopenharmony_ci .remove_new = exynos_audss_clk_remove, 29662306a36Sopenharmony_ci}; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cimodule_platform_driver(exynos_audss_clk_driver); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ciMODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>"); 30162306a36Sopenharmony_ciMODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller"); 30262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 30362306a36Sopenharmony_ciMODULE_ALIAS("platform:exynos-audss-clk"); 304