18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2014 Tomasz Figa <t.figa@samsung.com> 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Based on Exynos Audio Subsystem Clock Controller driver: 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright (c) 2013 Samsung Electronics Co., Ltd. 88c2ecf20Sopenharmony_ci * Author: Padmavathi Venna <padma.v@samsung.com> 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Driver for Audio Subsystem Clock Controller of S5PV210-compatible SoCs. 118c2ecf20Sopenharmony_ci*/ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/clk.h> 158c2ecf20Sopenharmony_ci#include <linux/clk-provider.h> 168c2ecf20Sopenharmony_ci#include <linux/of_address.h> 178c2ecf20Sopenharmony_ci#include <linux/syscore_ops.h> 188c2ecf20Sopenharmony_ci#include <linux/init.h> 198c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <dt-bindings/clock/s5pv210-audss.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(lock); 248c2ecf20Sopenharmony_cistatic void __iomem *reg_base; 258c2ecf20Sopenharmony_cistatic struct clk_hw_onecell_data *clk_data; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define ASS_CLK_SRC 0x0 288c2ecf20Sopenharmony_ci#define ASS_CLK_DIV 0x4 298c2ecf20Sopenharmony_ci#define ASS_CLK_GATE 0x8 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 328c2ecf20Sopenharmony_cistatic unsigned long reg_save[][2] = { 338c2ecf20Sopenharmony_ci {ASS_CLK_SRC, 0}, 348c2ecf20Sopenharmony_ci {ASS_CLK_DIV, 0}, 358c2ecf20Sopenharmony_ci {ASS_CLK_GATE, 0}, 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic int s5pv210_audss_clk_suspend(void) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci int i; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(reg_save); i++) 438c2ecf20Sopenharmony_ci reg_save[i][1] = readl(reg_base + reg_save[i][0]); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci return 0; 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic void s5pv210_audss_clk_resume(void) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci int i; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(reg_save); i++) 538c2ecf20Sopenharmony_ci writel(reg_save[i][1], reg_base + reg_save[i][0]); 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic struct syscore_ops s5pv210_audss_clk_syscore_ops = { 578c2ecf20Sopenharmony_ci .suspend = s5pv210_audss_clk_suspend, 588c2ecf20Sopenharmony_ci .resume = s5pv210_audss_clk_resume, 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci#endif /* CONFIG_PM_SLEEP */ 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci/* register s5pv210_audss clocks */ 638c2ecf20Sopenharmony_cistatic int s5pv210_audss_clk_probe(struct platform_device *pdev) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci int i, ret = 0; 668c2ecf20Sopenharmony_ci struct resource *res; 678c2ecf20Sopenharmony_ci const char *mout_audss_p[2]; 688c2ecf20Sopenharmony_ci const char *mout_i2s_p[3]; 698c2ecf20Sopenharmony_ci const char *hclk_p; 708c2ecf20Sopenharmony_ci struct clk_hw **clk_table; 718c2ecf20Sopenharmony_ci struct clk *hclk, *pll_ref, *pll_in, *cdclk, *sclk_audio; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 748c2ecf20Sopenharmony_ci reg_base = devm_ioremap_resource(&pdev->dev, res); 758c2ecf20Sopenharmony_ci if (IS_ERR(reg_base)) { 768c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to map audss registers\n"); 778c2ecf20Sopenharmony_ci return PTR_ERR(reg_base); 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci clk_data = devm_kzalloc(&pdev->dev, 818c2ecf20Sopenharmony_ci struct_size(clk_data, hws, AUDSS_MAX_CLKS), 828c2ecf20Sopenharmony_ci GFP_KERNEL); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (!clk_data) 858c2ecf20Sopenharmony_ci return -ENOMEM; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci clk_data->num = AUDSS_MAX_CLKS; 888c2ecf20Sopenharmony_ci clk_table = clk_data->hws; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci hclk = devm_clk_get(&pdev->dev, "hclk"); 918c2ecf20Sopenharmony_ci if (IS_ERR(hclk)) { 928c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get hclk clock\n"); 938c2ecf20Sopenharmony_ci return PTR_ERR(hclk); 948c2ecf20Sopenharmony_ci } 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci pll_in = devm_clk_get(&pdev->dev, "fout_epll"); 978c2ecf20Sopenharmony_ci if (IS_ERR(pll_in)) { 988c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get fout_epll clock\n"); 998c2ecf20Sopenharmony_ci return PTR_ERR(pll_in); 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio0"); 1038c2ecf20Sopenharmony_ci if (IS_ERR(sclk_audio)) { 1048c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to get sclk_audio0 clock\n"); 1058c2ecf20Sopenharmony_ci return PTR_ERR(sclk_audio); 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci /* iiscdclk0 is an optional external I2S codec clock */ 1098c2ecf20Sopenharmony_ci cdclk = devm_clk_get(&pdev->dev, "iiscdclk0"); 1108c2ecf20Sopenharmony_ci pll_ref = devm_clk_get(&pdev->dev, "xxti"); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci if (!IS_ERR(pll_ref)) 1138c2ecf20Sopenharmony_ci mout_audss_p[0] = __clk_get_name(pll_ref); 1148c2ecf20Sopenharmony_ci else 1158c2ecf20Sopenharmony_ci mout_audss_p[0] = "xxti"; 1168c2ecf20Sopenharmony_ci mout_audss_p[1] = __clk_get_name(pll_in); 1178c2ecf20Sopenharmony_ci clk_table[CLK_MOUT_AUDSS] = clk_hw_register_mux(NULL, "mout_audss", 1188c2ecf20Sopenharmony_ci mout_audss_p, ARRAY_SIZE(mout_audss_p), 1198c2ecf20Sopenharmony_ci CLK_SET_RATE_NO_REPARENT, 1208c2ecf20Sopenharmony_ci reg_base + ASS_CLK_SRC, 0, 1, 0, &lock); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci mout_i2s_p[0] = "mout_audss"; 1238c2ecf20Sopenharmony_ci if (!IS_ERR(cdclk)) 1248c2ecf20Sopenharmony_ci mout_i2s_p[1] = __clk_get_name(cdclk); 1258c2ecf20Sopenharmony_ci else 1268c2ecf20Sopenharmony_ci mout_i2s_p[1] = "iiscdclk0"; 1278c2ecf20Sopenharmony_ci mout_i2s_p[2] = __clk_get_name(sclk_audio); 1288c2ecf20Sopenharmony_ci clk_table[CLK_MOUT_I2S_A] = clk_hw_register_mux(NULL, "mout_i2s_audss", 1298c2ecf20Sopenharmony_ci mout_i2s_p, ARRAY_SIZE(mout_i2s_p), 1308c2ecf20Sopenharmony_ci CLK_SET_RATE_NO_REPARENT, 1318c2ecf20Sopenharmony_ci reg_base + ASS_CLK_SRC, 2, 2, 0, &lock); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci clk_table[CLK_DOUT_AUD_BUS] = clk_hw_register_divider(NULL, 1348c2ecf20Sopenharmony_ci "dout_aud_bus", "mout_audss", 0, 1358c2ecf20Sopenharmony_ci reg_base + ASS_CLK_DIV, 0, 4, 0, &lock); 1368c2ecf20Sopenharmony_ci clk_table[CLK_DOUT_I2S_A] = clk_hw_register_divider(NULL, 1378c2ecf20Sopenharmony_ci "dout_i2s_audss", "mout_i2s_audss", 0, 1388c2ecf20Sopenharmony_ci reg_base + ASS_CLK_DIV, 4, 4, 0, &lock); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci clk_table[CLK_I2S] = clk_hw_register_gate(NULL, "i2s_audss", 1418c2ecf20Sopenharmony_ci "dout_i2s_audss", CLK_SET_RATE_PARENT, 1428c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 6, 0, &lock); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci hclk_p = __clk_get_name(hclk); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci clk_table[CLK_HCLK_I2S] = clk_hw_register_gate(NULL, "hclk_i2s_audss", 1478c2ecf20Sopenharmony_ci hclk_p, CLK_IGNORE_UNUSED, 1488c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 5, 0, &lock); 1498c2ecf20Sopenharmony_ci clk_table[CLK_HCLK_UART] = clk_hw_register_gate(NULL, "hclk_uart_audss", 1508c2ecf20Sopenharmony_ci hclk_p, CLK_IGNORE_UNUSED, 1518c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 4, 0, &lock); 1528c2ecf20Sopenharmony_ci clk_table[CLK_HCLK_HWA] = clk_hw_register_gate(NULL, "hclk_hwa_audss", 1538c2ecf20Sopenharmony_ci hclk_p, CLK_IGNORE_UNUSED, 1548c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 3, 0, &lock); 1558c2ecf20Sopenharmony_ci clk_table[CLK_HCLK_DMA] = clk_hw_register_gate(NULL, "hclk_dma_audss", 1568c2ecf20Sopenharmony_ci hclk_p, CLK_IGNORE_UNUSED, 1578c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 2, 0, &lock); 1588c2ecf20Sopenharmony_ci clk_table[CLK_HCLK_BUF] = clk_hw_register_gate(NULL, "hclk_buf_audss", 1598c2ecf20Sopenharmony_ci hclk_p, CLK_IGNORE_UNUSED, 1608c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 1, 0, &lock); 1618c2ecf20Sopenharmony_ci clk_table[CLK_HCLK_RP] = clk_hw_register_gate(NULL, "hclk_rp_audss", 1628c2ecf20Sopenharmony_ci hclk_p, CLK_IGNORE_UNUSED, 1638c2ecf20Sopenharmony_ci reg_base + ASS_CLK_GATE, 0, 0, &lock); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci for (i = 0; i < clk_data->num; i++) { 1668c2ecf20Sopenharmony_ci if (IS_ERR(clk_table[i])) { 1678c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to register clock %d\n", i); 1688c2ecf20Sopenharmony_ci ret = PTR_ERR(clk_table[i]); 1698c2ecf20Sopenharmony_ci goto unregister; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci ret = of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_onecell_get, 1748c2ecf20Sopenharmony_ci clk_data); 1758c2ecf20Sopenharmony_ci if (ret) { 1768c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to add clock provider\n"); 1778c2ecf20Sopenharmony_ci goto unregister; 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 1818c2ecf20Sopenharmony_ci register_syscore_ops(&s5pv210_audss_clk_syscore_ops); 1828c2ecf20Sopenharmony_ci#endif 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci return 0; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ciunregister: 1878c2ecf20Sopenharmony_ci for (i = 0; i < clk_data->num; i++) { 1888c2ecf20Sopenharmony_ci if (!IS_ERR(clk_table[i])) 1898c2ecf20Sopenharmony_ci clk_hw_unregister(clk_table[i]); 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return ret; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic const struct of_device_id s5pv210_audss_clk_of_match[] = { 1968c2ecf20Sopenharmony_ci { .compatible = "samsung,s5pv210-audss-clock", }, 1978c2ecf20Sopenharmony_ci {}, 1988c2ecf20Sopenharmony_ci}; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic struct platform_driver s5pv210_audss_clk_driver = { 2018c2ecf20Sopenharmony_ci .driver = { 2028c2ecf20Sopenharmony_ci .name = "s5pv210-audss-clk", 2038c2ecf20Sopenharmony_ci .suppress_bind_attrs = true, 2048c2ecf20Sopenharmony_ci .of_match_table = s5pv210_audss_clk_of_match, 2058c2ecf20Sopenharmony_ci }, 2068c2ecf20Sopenharmony_ci .probe = s5pv210_audss_clk_probe, 2078c2ecf20Sopenharmony_ci}; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cistatic int __init s5pv210_audss_clk_init(void) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci return platform_driver_register(&s5pv210_audss_clk_driver); 2128c2ecf20Sopenharmony_ci} 2138c2ecf20Sopenharmony_cicore_initcall(s5pv210_audss_clk_init); 214