1 /* 2 * Copyright (c) 2014 Tomasz Figa <t.figa@samsung.com> 3 * 4 * Based on Exynos Audio Subsystem Clock Controller driver: 5 * 6 * Copyright (c) 2013 Samsung Electronics Co., Ltd. 7 * Author: Padmavathi Venna <padma.v@samsung.com> 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 * 13 * Driver for Audio Subsystem Clock Controller of S5PV210-compatible SoCs. 14 */ 15 16 #include <linux/clkdev.h> 17 #include <linux/io.h> 18 #include <linux/clk-provider.h> 19 #include <linux/of_address.h> 20 #include <linux/syscore_ops.h> 21 #include <linux/module.h> 22 #include <linux/platform_device.h> 23 24 #include <dt-bindings/clock/s5pv210-audss.h> 25 26 static DEFINE_SPINLOCK(lock); 27 static struct clk **clk_table; 28 static void __iomem *reg_base; 29 static struct clk_onecell_data clk_data; 30 31 #define ASS_CLK_SRC 0x0 32 #define ASS_CLK_DIV 0x4 33 #define ASS_CLK_GATE 0x8 34 35 #ifdef CONFIG_PM_SLEEP 36 static unsigned long reg_save[][2] = { 37 {ASS_CLK_SRC, 0}, 38 {ASS_CLK_DIV, 0}, 39 {ASS_CLK_GATE, 0}, 40 }; 41 42 static int s5pv210_audss_clk_suspend(void) 43 { 44 int i; 45 46 for (i = 0; i < ARRAY_SIZE(reg_save); i++) 47 reg_save[i][1] = readl(reg_base + reg_save[i][0]); 48 49 return 0; 50 } 51 52 static void s5pv210_audss_clk_resume(void) 53 { 54 int i; 55 56 for (i = 0; i < ARRAY_SIZE(reg_save); i++) 57 writel(reg_save[i][1], reg_base + reg_save[i][0]); 58 } 59 60 static struct syscore_ops s5pv210_audss_clk_syscore_ops = { 61 .suspend = s5pv210_audss_clk_suspend, 62 .resume = s5pv210_audss_clk_resume, 63 }; 64 #endif /* CONFIG_PM_SLEEP */ 65 66 /* register s5pv210_audss clocks */ 67 static int s5pv210_audss_clk_probe(struct platform_device *pdev) 68 { 69 int i, ret = 0; 70 struct resource *res; 71 const char *mout_audss_p[2]; 72 const char *mout_i2s_p[3]; 73 const char *hclk_p; 74 struct clk *hclk, *pll_ref, *pll_in, *cdclk, *sclk_audio; 75 76 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 77 reg_base = devm_ioremap_resource(&pdev->dev, res); 78 if (IS_ERR(reg_base)) { 79 dev_err(&pdev->dev, "failed to map audss registers\n"); 80 return PTR_ERR(reg_base); 81 } 82 83 clk_table = devm_kzalloc(&pdev->dev, 84 sizeof(struct clk *) * AUDSS_MAX_CLKS, 85 GFP_KERNEL); 86 if (!clk_table) 87 return -ENOMEM; 88 89 clk_data.clks = clk_table; 90 clk_data.clk_num = AUDSS_MAX_CLKS; 91 92 hclk = devm_clk_get(&pdev->dev, "hclk"); 93 if (IS_ERR(hclk)) { 94 dev_err(&pdev->dev, "failed to get hclk clock\n"); 95 return PTR_ERR(hclk); 96 } 97 98 pll_in = devm_clk_get(&pdev->dev, "fout_epll"); 99 if (IS_ERR(pll_in)) { 100 dev_err(&pdev->dev, "failed to get fout_epll clock\n"); 101 return PTR_ERR(pll_in); 102 } 103 104 sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio0"); 105 if (IS_ERR(sclk_audio)) { 106 dev_err(&pdev->dev, "failed to get sclk_audio0 clock\n"); 107 return PTR_ERR(sclk_audio); 108 } 109 110 /* iiscdclk0 is an optional external I2S codec clock */ 111 cdclk = devm_clk_get(&pdev->dev, "iiscdclk0"); 112 pll_ref = devm_clk_get(&pdev->dev, "xxti"); 113 114 if (!IS_ERR(pll_ref)) 115 mout_audss_p[0] = __clk_get_name(pll_ref); 116 else 117 mout_audss_p[0] = "xxti"; 118 mout_audss_p[1] = __clk_get_name(pll_in); 119 clk_table[CLK_MOUT_AUDSS] = clk_register_mux(NULL, "mout_audss", 120 mout_audss_p, ARRAY_SIZE(mout_audss_p), 121 CLK_SET_RATE_NO_REPARENT, 122 reg_base + ASS_CLK_SRC, 0, 1, 0, &lock); 123 124 mout_i2s_p[0] = "mout_audss"; 125 if (!IS_ERR(cdclk)) 126 mout_i2s_p[1] = __clk_get_name(cdclk); 127 else 128 mout_i2s_p[1] = "iiscdclk0"; 129 mout_i2s_p[2] = __clk_get_name(sclk_audio); 130 clk_table[CLK_MOUT_I2S_A] = clk_register_mux(NULL, "mout_i2s_audss", 131 mout_i2s_p, ARRAY_SIZE(mout_i2s_p), 132 CLK_SET_RATE_NO_REPARENT, 133 reg_base + ASS_CLK_SRC, 2, 2, 0, &lock); 134 135 clk_table[CLK_DOUT_AUD_BUS] = clk_register_divider(NULL, 136 "dout_aud_bus", "mout_audss", 0, 137 reg_base + ASS_CLK_DIV, 0, 4, 0, &lock); 138 clk_table[CLK_DOUT_I2S_A] = clk_register_divider(NULL, "dout_i2s_audss", 139 "mout_i2s_audss", 0, reg_base + ASS_CLK_DIV, 140 4, 4, 0, &lock); 141 142 clk_table[CLK_I2S] = clk_register_gate(NULL, "i2s_audss", 143 "dout_i2s_audss", CLK_SET_RATE_PARENT, 144 reg_base + ASS_CLK_GATE, 6, 0, &lock); 145 146 hclk_p = __clk_get_name(hclk); 147 148 clk_table[CLK_HCLK_I2S] = clk_register_gate(NULL, "hclk_i2s_audss", 149 hclk_p, CLK_IGNORE_UNUSED, 150 reg_base + ASS_CLK_GATE, 5, 0, &lock); 151 clk_table[CLK_HCLK_UART] = clk_register_gate(NULL, "hclk_uart_audss", 152 hclk_p, CLK_IGNORE_UNUSED, 153 reg_base + ASS_CLK_GATE, 4, 0, &lock); 154 clk_table[CLK_HCLK_HWA] = clk_register_gate(NULL, "hclk_hwa_audss", 155 hclk_p, CLK_IGNORE_UNUSED, 156 reg_base + ASS_CLK_GATE, 3, 0, &lock); 157 clk_table[CLK_HCLK_DMA] = clk_register_gate(NULL, "hclk_dma_audss", 158 hclk_p, CLK_IGNORE_UNUSED, 159 reg_base + ASS_CLK_GATE, 2, 0, &lock); 160 clk_table[CLK_HCLK_BUF] = clk_register_gate(NULL, "hclk_buf_audss", 161 hclk_p, CLK_IGNORE_UNUSED, 162 reg_base + ASS_CLK_GATE, 1, 0, &lock); 163 clk_table[CLK_HCLK_RP] = clk_register_gate(NULL, "hclk_rp_audss", 164 hclk_p, CLK_IGNORE_UNUSED, 165 reg_base + ASS_CLK_GATE, 0, 0, &lock); 166 167 for (i = 0; i < clk_data.clk_num; i++) { 168 if (IS_ERR(clk_table[i])) { 169 dev_err(&pdev->dev, "failed to register clock %d\n", i); 170 ret = PTR_ERR(clk_table[i]); 171 goto unregister; 172 } 173 } 174 175 ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, 176 &clk_data); 177 if (ret) { 178 dev_err(&pdev->dev, "failed to add clock provider\n"); 179 goto unregister; 180 } 181 182 #ifdef CONFIG_PM_SLEEP 183 register_syscore_ops(&s5pv210_audss_clk_syscore_ops); 184 #endif 185 186 return 0; 187 188 unregister: 189 for (i = 0; i < clk_data.clk_num; i++) { 190 if (!IS_ERR(clk_table[i])) 191 clk_unregister(clk_table[i]); 192 } 193 194 return ret; 195 } 196 197 static int s5pv210_audss_clk_remove(struct platform_device *pdev) 198 { 199 int i; 200 201 of_clk_del_provider(pdev->dev.of_node); 202 203 for (i = 0; i < clk_data.clk_num; i++) { 204 if (!IS_ERR(clk_table[i])) 205 clk_unregister(clk_table[i]); 206 } 207 208 return 0; 209 } 210 211 static const struct of_device_id s5pv210_audss_clk_of_match[] = { 212 { .compatible = "samsung,s5pv210-audss-clock", }, 213 {}, 214 }; 215 216 static struct platform_driver s5pv210_audss_clk_driver = { 217 .driver = { 218 .name = "s5pv210-audss-clk", 219 .owner = THIS_MODULE, 220 .of_match_table = s5pv210_audss_clk_of_match, 221 }, 222 .probe = s5pv210_audss_clk_probe, 223 .remove = s5pv210_audss_clk_remove, 224 }; 225 226 static int __init s5pv210_audss_clk_init(void) 227 { 228 return platform_driver_register(&s5pv210_audss_clk_driver); 229 } 230 core_initcall(s5pv210_audss_clk_init); 231 232 static void __exit s5pv210_audss_clk_exit(void) 233 { 234 platform_driver_unregister(&s5pv210_audss_clk_driver); 235 } 236 module_exit(s5pv210_audss_clk_exit); 237 238 MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>"); 239 MODULE_DESCRIPTION("S5PV210 Audio Subsystem Clock Controller"); 240 MODULE_LICENSE("GPL v2"); 241 MODULE_ALIAS("platform:s5pv210-audss-clk"); 242