blob: 19a0d874931e233264bc9998905c698f72b22711 [file] [log] [blame]
Padmavathi Venna1241ef92013-06-18 00:02:17 +09001/*
2 * Copyright (c) 2013 Samsung Electronics Co., Ltd.
3 * Author: Padmavathi Venna <padma.v@samsung.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *
9 * Common Clock Framework support for Audio Subsystem Clock Controller.
10*/
11
12#include <linux/clkdev.h>
13#include <linux/io.h>
14#include <linux/clk-provider.h>
15#include <linux/of_address.h>
16#include <linux/syscore_ops.h>
Andrew Brestickerb37a4222013-09-25 14:12:47 -070017#include <linux/module.h>
18#include <linux/platform_device.h>
Padmavathi Venna1241ef92013-06-18 00:02:17 +090019
20#include <dt-bindings/clk/exynos-audss-clk.h>
21
22static DEFINE_SPINLOCK(lock);
23static struct clk **clk_table;
24static void __iomem *reg_base;
25static struct clk_onecell_data clk_data;
26
27#define ASS_CLK_SRC 0x0
28#define ASS_CLK_DIV 0x4
29#define ASS_CLK_GATE 0x8
30
Krzysztof Kozlowski3fd68c92013-12-17 10:56:39 +010031#ifdef CONFIG_PM_SLEEP
Padmavathi Venna1241ef92013-06-18 00:02:17 +090032static unsigned long reg_save[][2] = {
33 {ASS_CLK_SRC, 0},
34 {ASS_CLK_DIV, 0},
35 {ASS_CLK_GATE, 0},
36};
37
Padmavathi Venna1241ef92013-06-18 00:02:17 +090038static int exynos_audss_clk_suspend(void)
39{
40 int i;
41
42 for (i = 0; i < ARRAY_SIZE(reg_save); i++)
43 reg_save[i][1] = readl(reg_base + reg_save[i][0]);
44
45 return 0;
46}
47
48static void exynos_audss_clk_resume(void)
49{
50 int i;
51
52 for (i = 0; i < ARRAY_SIZE(reg_save); i++)
53 writel(reg_save[i][1], reg_base + reg_save[i][0]);
54}
55
56static struct syscore_ops exynos_audss_clk_syscore_ops = {
57 .suspend = exynos_audss_clk_suspend,
58 .resume = exynos_audss_clk_resume,
59};
60#endif /* CONFIG_PM_SLEEP */
61
62/* register exynos_audss clocks */
Andrew Brestickerb37a4222013-09-25 14:12:47 -070063static int exynos_audss_clk_probe(struct platform_device *pdev)
Padmavathi Venna1241ef92013-06-18 00:02:17 +090064{
Andrew Brestickerb37a4222013-09-25 14:12:47 -070065 int i, ret = 0;
66 struct resource *res;
Andrew Bresticker547f3352013-09-25 14:12:48 -070067 const char *mout_audss_p[] = {"fin_pll", "fout_epll"};
68 const char *mout_i2s_p[] = {"mout_audss", "cdclk0", "sclk_audio0"};
69 const char *sclk_pcm_p = "sclk_pcm0";
70 struct clk *pll_ref, *pll_in, *cdclk, *sclk_audio, *sclk_pcm_in;
Andrew Brestickerb37a4222013-09-25 14:12:47 -070071
72 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
73 reg_base = devm_ioremap_resource(&pdev->dev, res);
74 if (IS_ERR(reg_base)) {
75 dev_err(&pdev->dev, "failed to map audss registers\n");
76 return PTR_ERR(reg_base);
Padmavathi Venna1241ef92013-06-18 00:02:17 +090077 }
78
Andrew Brestickerb37a4222013-09-25 14:12:47 -070079 clk_table = devm_kzalloc(&pdev->dev,
80 sizeof(struct clk *) * EXYNOS_AUDSS_MAX_CLKS,
Padmavathi Venna1241ef92013-06-18 00:02:17 +090081 GFP_KERNEL);
Andrew Brestickerb37a4222013-09-25 14:12:47 -070082 if (!clk_table)
83 return -ENOMEM;
Padmavathi Venna1241ef92013-06-18 00:02:17 +090084
85 clk_data.clks = clk_table;
86 clk_data.clk_num = EXYNOS_AUDSS_MAX_CLKS;
Padmavathi Venna1241ef92013-06-18 00:02:17 +090087
Andrew Bresticker547f3352013-09-25 14:12:48 -070088 pll_ref = devm_clk_get(&pdev->dev, "pll_ref");
89 pll_in = devm_clk_get(&pdev->dev, "pll_in");
90 if (!IS_ERR(pll_ref))
91 mout_audss_p[0] = __clk_get_name(pll_ref);
92 if (!IS_ERR(pll_in))
93 mout_audss_p[1] = __clk_get_name(pll_in);
Padmavathi Venna1241ef92013-06-18 00:02:17 +090094 clk_table[EXYNOS_MOUT_AUDSS] = clk_register_mux(NULL, "mout_audss",
James Hogan819c1de2013-07-29 12:25:01 +010095 mout_audss_p, ARRAY_SIZE(mout_audss_p),
96 CLK_SET_RATE_NO_REPARENT,
Padmavathi Venna1241ef92013-06-18 00:02:17 +090097 reg_base + ASS_CLK_SRC, 0, 1, 0, &lock);
98
Andrew Bresticker547f3352013-09-25 14:12:48 -070099 cdclk = devm_clk_get(&pdev->dev, "cdclk");
100 sclk_audio = devm_clk_get(&pdev->dev, "sclk_audio");
101 if (!IS_ERR(cdclk))
102 mout_i2s_p[1] = __clk_get_name(cdclk);
103 if (!IS_ERR(sclk_audio))
104 mout_i2s_p[2] = __clk_get_name(sclk_audio);
Padmavathi Venna1241ef92013-06-18 00:02:17 +0900105 clk_table[EXYNOS_MOUT_I2S] = clk_register_mux(NULL, "mout_i2s",
James Hogan819c1de2013-07-29 12:25:01 +0100106 mout_i2s_p, ARRAY_SIZE(mout_i2s_p),
107 CLK_SET_RATE_NO_REPARENT,
Padmavathi Venna1241ef92013-06-18 00:02:17 +0900108 reg_base + ASS_CLK_SRC, 2, 2, 0, &lock);
109
110 clk_table[EXYNOS_DOUT_SRP] = clk_register_divider(NULL, "dout_srp",
111 "mout_audss", 0, reg_base + ASS_CLK_DIV, 0, 4,
112 0, &lock);
113
114 clk_table[EXYNOS_DOUT_AUD_BUS] = clk_register_divider(NULL,
115 "dout_aud_bus", "dout_srp", 0,
116 reg_base + ASS_CLK_DIV, 4, 4, 0, &lock);
117
118 clk_table[EXYNOS_DOUT_I2S] = clk_register_divider(NULL, "dout_i2s",
119 "mout_i2s", 0, reg_base + ASS_CLK_DIV, 8, 4, 0,
120 &lock);
121
122 clk_table[EXYNOS_SRP_CLK] = clk_register_gate(NULL, "srp_clk",
123 "dout_srp", CLK_SET_RATE_PARENT,
124 reg_base + ASS_CLK_GATE, 0, 0, &lock);
125
126 clk_table[EXYNOS_I2S_BUS] = clk_register_gate(NULL, "i2s_bus",
127 "dout_aud_bus", CLK_SET_RATE_PARENT,
128 reg_base + ASS_CLK_GATE, 2, 0, &lock);
129
130 clk_table[EXYNOS_SCLK_I2S] = clk_register_gate(NULL, "sclk_i2s",
131 "dout_i2s", CLK_SET_RATE_PARENT,
132 reg_base + ASS_CLK_GATE, 3, 0, &lock);
133
134 clk_table[EXYNOS_PCM_BUS] = clk_register_gate(NULL, "pcm_bus",
135 "sclk_pcm", CLK_SET_RATE_PARENT,
136 reg_base + ASS_CLK_GATE, 4, 0, &lock);
137
Andrew Bresticker547f3352013-09-25 14:12:48 -0700138 sclk_pcm_in = devm_clk_get(&pdev->dev, "sclk_pcm_in");
139 if (!IS_ERR(sclk_pcm_in))
140 sclk_pcm_p = __clk_get_name(sclk_pcm_in);
Padmavathi Venna1241ef92013-06-18 00:02:17 +0900141 clk_table[EXYNOS_SCLK_PCM] = clk_register_gate(NULL, "sclk_pcm",
Andrew Bresticker547f3352013-09-25 14:12:48 -0700142 sclk_pcm_p, CLK_SET_RATE_PARENT,
Padmavathi Venna1241ef92013-06-18 00:02:17 +0900143 reg_base + ASS_CLK_GATE, 5, 0, &lock);
144
Andrew Brestickerb37a4222013-09-25 14:12:47 -0700145 for (i = 0; i < clk_data.clk_num; i++) {
146 if (IS_ERR(clk_table[i])) {
147 dev_err(&pdev->dev, "failed to register clock %d\n", i);
148 ret = PTR_ERR(clk_table[i]);
149 goto unregister;
150 }
151 }
152
153 ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get,
154 &clk_data);
155 if (ret) {
156 dev_err(&pdev->dev, "failed to add clock provider\n");
157 goto unregister;
158 }
159
Padmavathi Venna1241ef92013-06-18 00:02:17 +0900160#ifdef CONFIG_PM_SLEEP
161 register_syscore_ops(&exynos_audss_clk_syscore_ops);
162#endif
163
Andrew Brestickerb37a4222013-09-25 14:12:47 -0700164 dev_info(&pdev->dev, "setup completed\n");
165
166 return 0;
167
168unregister:
169 for (i = 0; i < clk_data.clk_num; i++) {
170 if (!IS_ERR(clk_table[i]))
171 clk_unregister(clk_table[i]);
172 }
173
174 return ret;
Padmavathi Venna1241ef92013-06-18 00:02:17 +0900175}
Andrew Brestickerb37a4222013-09-25 14:12:47 -0700176
177static int exynos_audss_clk_remove(struct platform_device *pdev)
178{
179 int i;
180
181 of_clk_del_provider(pdev->dev.of_node);
182
183 for (i = 0; i < clk_data.clk_num; i++) {
184 if (!IS_ERR(clk_table[i]))
185 clk_unregister(clk_table[i]);
186 }
187
188 return 0;
189}
190
191static const struct of_device_id exynos_audss_clk_of_match[] = {
192 { .compatible = "samsung,exynos4210-audss-clock", },
193 { .compatible = "samsung,exynos5250-audss-clock", },
194 {},
195};
196
197static struct platform_driver exynos_audss_clk_driver = {
198 .driver = {
199 .name = "exynos-audss-clk",
200 .owner = THIS_MODULE,
201 .of_match_table = exynos_audss_clk_of_match,
202 },
203 .probe = exynos_audss_clk_probe,
204 .remove = exynos_audss_clk_remove,
205};
206
207static int __init exynos_audss_clk_init(void)
208{
209 return platform_driver_register(&exynos_audss_clk_driver);
210}
211core_initcall(exynos_audss_clk_init);
212
213static void __exit exynos_audss_clk_exit(void)
214{
215 platform_driver_unregister(&exynos_audss_clk_driver);
216}
217module_exit(exynos_audss_clk_exit);
218
219MODULE_AUTHOR("Padmavathi Venna <padma.v@samsung.com>");
220MODULE_DESCRIPTION("Exynos Audio Subsystem Clock Controller");
221MODULE_LICENSE("GPL v2");
222MODULE_ALIAS("platform:exynos-audss-clk");