18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2013 Samsung Electronics Co., Ltd. 48c2ecf20Sopenharmony_ci * Author: Padmavathi Venna <padma.v@samsung.com> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Common Clock Framework support for Audio Subsystem Clock Controller. 78c2ecf20Sopenharmony_ci*/ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/slab.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/clk.h> 128c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 138c2ecf20Sopenharmony_ci#include <linux/of_address.h> 148c2ecf20Sopenharmony_ci#include <linux/of_device.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <dt-bindings/clock/exynos-audss-clk.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(lock); 228c2ecf20Sopenharmony_cistatic void __iomem *reg_base; 238c2ecf20Sopenharmony_cistatic struct clk_hw_onecell_data *clk_data; 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * On Exynos5420 this will be a clock which has to be enabled before any 268c2ecf20Sopenharmony_ci * access to audss registers. Typically a child of EPLL. 278c2ecf20Sopenharmony_ci * 288c2ecf20Sopenharmony_ci * On other platforms this will be -ENODEV. 298c2ecf20Sopenharmony_ci */ 308c2ecf20Sopenharmony_cistatic struct clk *epll; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define ASS_CLK_SRC 0x0 338c2ecf20Sopenharmony_ci#define ASS_CLK_DIV 0x4 348c2ecf20Sopenharmony_ci#define ASS_CLK_GATE 0x8 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic unsigned long reg_save[][2] = { 378c2ecf20Sopenharmony_ci { ASS_CLK_SRC, 0 }, 388c2ecf20Sopenharmony_ci { ASS_CLK_DIV, 0 }, 398c2ecf20Sopenharmony_ci { ASS_CLK_GATE, 0 }, 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int __maybe_unused exynos_audss_clk_suspend(struct device *dev) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci int i; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(reg_save); i++) 478c2ecf20Sopenharmony_ci reg_save[i][1] = readl(reg_base + reg_save[i][0]); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int __maybe_unused exynos_audss_clk_resume(struct device *dev) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci int i; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(reg_save); i++) 578c2ecf20Sopenharmony_ci writel(reg_save[i][1], reg_base + reg_save[i][0]); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci return 0; 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct exynos_audss_clk_drvdata { 638c2ecf20Sopenharmony_ci unsigned int has_adma_clk:1; 648c2ecf20Sopenharmony_ci unsigned int has_mst_clk:1; 658c2ecf20Sopenharmony_ci unsigned int enable_epll:1; 668c2ecf20Sopenharmony_ci unsigned int num_clks; 678c2ecf20Sopenharmony_ci}; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic const struct exynos_audss_clk_drvdata exynos4210_drvdata = { 708c2ecf20Sopenharmony_ci .num_clks = EXYNOS_AUDSS_MAX_CLKS - 1, 718c2ecf20Sopenharmony_ci .enable_epll = 1, 728c2ecf20Sopenharmony_ci}; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic const struct exynos_audss_clk_drvdata exynos5410_drvdata = { 758c2ecf20Sopenharmony_ci .num_clks = EXYNOS_AUDSS_MAX_CLKS - 1, 768c2ecf20Sopenharmony_ci .has_mst_clk = 1, 778c2ecf20Sopenharmony_ci}; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic const struct exynos_audss_clk_drvdata exynos5420_drvdata = { 808c2ecf20Sopenharmony_ci .num_clks = EXYNOS_AUDSS_MAX_CLKS, 818c2ecf20Sopenharmony_ci .has_adma_clk = 1, 828c2ecf20Sopenharmony_ci .enable_epll = 1, 838c2ecf20Sopenharmony_ci}; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic const struct of_device_id exynos_audss_clk_of_match[] = { 868c2ecf20Sopenharmony_ci { 878c2ecf20Sopenharmony_ci .compatible = "samsung,exynos4210-audss-clock", 888c2ecf20Sopenharmony_ci .data = &exynos4210_drvdata, 898c2ecf20Sopenharmony_ci }, { 908c2ecf20Sopenharmony_ci .compatible = "samsung,exynos5250-audss-clock", 918c2ecf20Sopenharmony_ci .data = &exynos4210_drvdata, 928c2ecf20Sopenharmony_ci }, { 938c2ecf20Sopenharmony_ci .compatible = "samsung,exynos5410-audss-clock", 948c2ecf20Sopenharmony_ci .data = &exynos5410_drvdata, 958c2ecf20Sopenharmony_ci }, { 968c2ecf20Sopenharmony_ci .compatible = "samsung,exynos5420-audss-clock", 978c2ecf20Sopenharmony_ci .data = &exynos5420_drvdata, 988c2ecf20Sopenharmony_ci }, 998c2ecf20Sopenharmony_ci { }, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, exynos_audss_clk_of_match); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic void exynos_audss_clk_teardown(void) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci int i; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci for (i = EXYNOS_MOUT_AUDSS; i < EXYNOS_DOUT_SRP; i++) { 1088c2ecf20Sopenharmony_ci if (!IS_ERR(clk_data->hws[i])) 1098c2ecf20Sopenharmony_ci clk_hw_unregister_mux(clk_data->hws[i]); 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci for (; i < EXYNOS_SRP_CLK; i++) { 1138c2ecf20Sopenharmony_ci if (!IS_ERR(clk_data->hws[i])) 1148c2ecf20Sopenharmony_ci clk_hw_unregister_divider(clk_data->hws[i]); 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci for (; i < clk_data->num; i++) { 1188c2ecf20Sopenharmony_ci if (!IS_ERR(clk_data->hws[i])) 1198c2ecf20Sopenharmony_ci clk_hw_unregister_gate(clk_data->hws[i]); 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci/* register exynos_audss clocks */ 1248c2ecf20Sopenharmony_cistatic int exynos_audss_clk_probe(struct platform_device *pdev) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci const char *mout_audss_p[] = {"fin_pll", "fout_epll"}; 1278c2ecf20Sopenharmony_ci const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"}; 1288c2ecf20Sopenharmony_ci const char *sclk_pcm_p = "sclk_pcm0"; 1298c2ecf20Sopenharmony_ci struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in; 1308c2ecf20Sopenharmony_ci const struct exynos_audss_clk_drvdata *variant; 1318c2ecf20Sopenharmony_ci struct clk_hw **clk_table; 1328c2ecf20Sopenharmony_ci struct resource *res; 1338c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1348c2ecf20Sopenharmony_ci int i, ret = 0; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci variant = of_device_get_match_data(&pdev->dev); 1378c2ecf20Sopenharmony_ci if (!variant) 1388c2ecf20Sopenharmony_ci return -EINVAL; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1418c2ecf20Sopenharmony_ci reg_base = devm_ioremap_resource(dev, res); 1428c2ecf20Sopenharmony_ci if (IS_ERR(reg_base)) 1438c2ecf20Sopenharmony_ci return PTR_ERR(reg_base); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci epll = ERR_PTR(-ENODEV); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci clk_data = devm_kzalloc(dev, 1488c2ecf20Sopenharmony_ci struct_size(clk_data, hws, 1498c2ecf20Sopenharmony_ci EXYNOS_AUDSS_MAX_CLKS), 1508c2ecf20Sopenharmony_ci GFP_KERNEL); 1518c2ecf20Sopenharmony_ci if (!clk_data) 1528c2ecf20Sopenharmony_ci return -ENOMEM; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci clk_data->num = variant->num_clks; 1558c2ecf20Sopenharmony_ci clk_table = clk_data->hws; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci pll_ref = devm_clk_get(dev, "pll_ref"); 1588c2ecf20Sopenharmony_ci pll_in = devm_clk_get(dev, "pll_in"); 1598c2ecf20Sopenharmony_ci if (!IS_ERR(pll_ref)) 1608c2ecf20Sopenharmony_ci mout_audss_p[0] = __clk_get_name(pll_ref); 1618c2ecf20Sopenharmony_ci if (!IS_ERR(pll_in)) { 1628c2ecf20Sopenharmony_ci mout_audss_p[1] = __clk_get_name(pll_in); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci if (variant->enable_epll) { 1658c2ecf20Sopenharmony_ci epll = pll_in; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci ret = clk_prepare_enable(epll); 1688c2ecf20Sopenharmony_ci if (ret) { 1698c2ecf20Sopenharmony_ci dev_err(dev, 1708c2ecf20Sopenharmony_ci "failed to prepare the epll clock\n"); 1718c2ecf20Sopenharmony_ci return ret; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* 1778c2ecf20Sopenharmony_ci * Enable runtime PM here to allow the clock core using runtime PM 1788c2ecf20Sopenharmony_ci * for the registered clocks. Additionally, we increase the runtime 1798c2ecf20Sopenharmony_ci * PM usage count before registering the clocks, to prevent the 1808c2ecf20Sopenharmony_ci * clock core from runtime suspending the device. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ci pm_runtime_get_noresume(dev); 1838c2ecf20Sopenharmony_ci pm_runtime_set_active(dev); 1848c2ecf20Sopenharmony_ci pm_runtime_enable(dev); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci clk_table[EXYNOS_MOUT_AUDSS] = clk_hw_register_mux(dev, "mout_audss", 1878c2ecf20Sopenharmony_ci mout_audss_p, ARRAY_SIZE(mout_audss_p), 1888c2ecf20Sopenharmony_ci CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, 1898c2ecf20Sopenharmony_ci reg_base + ASS_CLK_SRC, 0, 1, 0, &lock); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci cdclk = devm_clk_get(dev, "cdclk"); 1928c2ecf20Sopenharmony_ci sclk_audio = devm_clk_get(dev, "sclk_audio"); 1938c2ecf20Sopenharmony_ci if (!IS_ERR(cdclk)) 1948c2ecf20Sopenharmony_ci mout_i2s_p[1] = __clk_get_name(cdclk); 1958c2ecf20Sopenharmony_ci if (!IS_ERR(sclk_audio)) 1968c2ecf20Sopenharmony_ci mout_i2s_p[2] = __clk_get_name(sclk_audio); 1978c2ecf20Sopenharmony_ci clk_table[EXYNOS_MOUT_I2S] = clk_hw_register_mux(dev, "mout_i2s", 1988c2ecf20Sopenharmony_ci mout_i2s_p, ARRAY_SIZE(mout_i2s_p), 1998c2ecf20Sopenharmony_ci CLK_SET_RATE_NO_REPARENT, 2008c2ecf20Sopenharmony_ci reg_base + ASS_CLK_SRC, 2, 2, 0, &lock); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci clk_table[EXYNOS_DOUT_SRP] = clk_hw_register_divider(dev, "dout_srp", 2038c2ecf20Sopenharmony_ci "mout_audss", CLK_SET_RATE_PARENT, 2048c2ecf20Sopenharmony_ci reg_base + ASS_CLK_DIV, 0, 4, 0, &lock); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci clk_table[EXYNOS_DOUT_AUD_BUS] = clk_hw_register_divider(dev, 2078c2ecf20Sopenharmony_ci "dout_aud_bus", "dout_srp", CLK_SET_RATE_PARENT, 2088c2ecf20Sopenharmony_ci reg_base + ASS_CLK_DIV, 4, 4, 0, &lock); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci clk_table[EXYNOS_DOUT_I2S] = clk_hw_register_divider(dev, "dout_i2s", 2118c2ecf20Sopenharmony_ci "mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0, 2128c2ecf20Sopenharmony_ci &lock); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci clk_table[EXYNOS_SRP_CLK] = clk_hw_register_gate(dev, "srp_clk", 2158c2ecf20Sopenharmony_ci "dout_srp", CLK_SET_RATE_PARENT, 2168c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 0, 0, &lock); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci clk_table[EXYNOS_I2S_BUS] = clk_hw_register_gate(dev, "i2s_bus", 2198c2ecf20Sopenharmony_ci "dout_aud_bus", CLK_SET_RATE_PARENT, 2208c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 2, 0, &lock); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci clk_table[EXYNOS_SCLK_I2S] = clk_hw_register_gate(dev, "sclk_i2s", 2238c2ecf20Sopenharmony_ci "dout_i2s", CLK_SET_RATE_PARENT, 2248c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 3, 0, &lock); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci clk_table[EXYNOS_PCM_BUS] = clk_hw_register_gate(dev, "pcm_bus", 2278c2ecf20Sopenharmony_ci "sclk_pcm", CLK_SET_RATE_PARENT, 2288c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 4, 0, &lock); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci sclk_pcm_in = devm_clk_get(dev, "sclk_pcm_in"); 2318c2ecf20Sopenharmony_ci if (!IS_ERR(sclk_pcm_in)) 2328c2ecf20Sopenharmony_ci sclk_pcm_p = __clk_get_name(sclk_pcm_in); 2338c2ecf20Sopenharmony_ci clk_table[EXYNOS_SCLK_PCM] = clk_hw_register_gate(dev, "sclk_pcm", 2348c2ecf20Sopenharmony_ci sclk_pcm_p, CLK_SET_RATE_PARENT, 2358c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 5, 0, &lock); 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci if (variant->has_adma_clk) { 2388c2ecf20Sopenharmony_ci clk_table[EXYNOS_ADMA] = clk_hw_register_gate(dev, "adma", 2398c2ecf20Sopenharmony_ci "dout_srp", CLK_SET_RATE_PARENT, 2408c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 9, 0, &lock); 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci for (i = 0; i < clk_data->num; i++) { 2448c2ecf20Sopenharmony_ci if (IS_ERR(clk_table[i])) { 2458c2ecf20Sopenharmony_ci dev_err(dev, "failed to register clock %d\n", i); 2468c2ecf20Sopenharmony_ci ret = PTR_ERR(clk_table[i]); 2478c2ecf20Sopenharmony_ci goto unregister; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, 2528c2ecf20Sopenharmony_ci clk_data); 2538c2ecf20Sopenharmony_ci if (ret) { 2548c2ecf20Sopenharmony_ci dev_err(dev, "failed to add clock provider\n"); 2558c2ecf20Sopenharmony_ci goto unregister; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci pm_runtime_put_sync(dev); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci return 0; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ciunregister: 2638c2ecf20Sopenharmony_ci exynos_audss_clk_teardown(); 2648c2ecf20Sopenharmony_ci pm_runtime_put_sync(dev); 2658c2ecf20Sopenharmony_ci pm_runtime_disable(dev); 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci if (!IS_ERR(epll)) 2688c2ecf20Sopenharmony_ci clk_disable_unprepare(epll); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci return ret; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cistatic int exynos_audss_clk_remove(struct platform_device *pdev) 2748c2ecf20Sopenharmony_ci{ 2758c2ecf20Sopenharmony_ci of_clk_del_provider(pdev->dev.of_node); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci exynos_audss_clk_teardown(); 2788c2ecf20Sopenharmony_ci pm_runtime_disable(&pdev->dev); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci if (!IS_ERR(epll)) 2818c2ecf20Sopenharmony_ci clk_disable_unprepare(epll); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic const struct dev_pm_ops exynos_audss_clk_pm_ops = { 2878c2ecf20Sopenharmony_ci SET_RUNTIME_PM_OPS(exynos_audss_clk_suspend, exynos_audss_clk_resume, 2888c2ecf20Sopenharmony_ci NULL) 2898c2ecf20Sopenharmony_ci SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 2908c2ecf20Sopenharmony_ci pm_runtime_force_resume) 2918c2ecf20Sopenharmony_ci}; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic struct platform_driver exynos_audss_clk_driver = { 2948c2ecf20Sopenharmony_ci .driver = { 2958c2ecf20Sopenharmony_ci .name = "exynos-audss-clk", 2968c2ecf20Sopenharmony_ci .of_match_table = exynos_audss_clk_of_match, 2978c2ecf20Sopenharmony_ci .pm = &exynos_audss_clk_pm_ops, 2988c2ecf20Sopenharmony_ci }, 2998c2ecf20Sopenharmony_ci .probe = exynos_audss_clk_probe, 3008c2ecf20Sopenharmony_ci .remove = exynos_audss_clk_remove, 3018c2ecf20Sopenharmony_ci}; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_cimodule_platform_driver(exynos_audss_clk_driver); 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ciMODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>"); 3068c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller"); 3078c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3088c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:exynos-audss-clk"); 309