Commit dfc9403b7c1f566bb099a12c58aee20589e390f1
Committed by
Mark Brown
1 parent
07539c1de8
Exists in
smarc-imx_3.14.28_1.0.0_ga
and in
1 other branch
ASoC: add Renesas R-Car ADG feature
Renesas R-Car series sound circuit consists of SSI and its peripheral. But this peripheral circuit is different between R-Car Generation1 (E1/M1/H1) and Generation2 (E2/M2/H2) (Actually, there are many difference in Generation1 chips) This patch adds ADG feature which controls sound clock Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Signed-off-by: Mark Brown <broonie@linaro.org>
Showing 6 changed files with 288 additions and 4 deletions Side-by-side Diff
include/sound/rcar_snd.h
... | ... | @@ -15,10 +15,12 @@ |
15 | 15 | #include <linux/sh_clk.h> |
16 | 16 | |
17 | 17 | #define RSND_GEN1_SRU 0 |
18 | +#define RSND_GEN1_ADG 1 | |
18 | 19 | |
19 | 20 | #define RSND_GEN2_SRU 0 |
21 | +#define RSND_GEN2_ADG 1 | |
20 | 22 | |
21 | -#define RSND_BASE_MAX 1 | |
23 | +#define RSND_BASE_MAX 2 | |
22 | 24 | |
23 | 25 | struct rsnd_scu_platform_info { |
24 | 26 | u32 flags; |
sound/soc/sh/rcar/Makefile
sound/soc/sh/rcar/adg.c
1 | +/* | |
2 | + * Helper routines for R-Car sound ADG. | |
3 | + * | |
4 | + * Copyright (C) 2013 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
5 | + * | |
6 | + * This file is subject to the terms and conditions of the GNU General Public | |
7 | + * License. See the file "COPYING" in the main directory of this archive | |
8 | + * for more details. | |
9 | + */ | |
10 | +#include <linux/sh_clk.h> | |
11 | +#include <mach/clock.h> | |
12 | +#include "rsnd.h" | |
13 | + | |
14 | +#define CLKA 0 | |
15 | +#define CLKB 1 | |
16 | +#define CLKC 2 | |
17 | +#define CLKI 3 | |
18 | +#define CLKMAX 4 | |
19 | + | |
20 | +struct rsnd_adg { | |
21 | + struct clk *clk[CLKMAX]; | |
22 | + | |
23 | + int rate_of_441khz_div_6; | |
24 | + int rate_of_48khz_div_6; | |
25 | +}; | |
26 | + | |
27 | +#define for_each_rsnd_clk(pos, adg, i) \ | |
28 | + for (i = 0, (pos) = adg->clk[i]; \ | |
29 | + i < CLKMAX; \ | |
30 | + i++, (pos) = adg->clk[i]) | |
31 | +#define rsnd_priv_to_adg(priv) ((struct rsnd_adg *)(priv)->adg) | |
32 | + | |
33 | +static enum rsnd_reg rsnd_adg_ssi_reg_get(int id) | |
34 | +{ | |
35 | + enum rsnd_reg reg; | |
36 | + | |
37 | + /* | |
38 | + * SSI 8 is not connected to ADG. | |
39 | + * it works with SSI 7 | |
40 | + */ | |
41 | + if (id == 8) | |
42 | + return RSND_REG_MAX; | |
43 | + | |
44 | + if (0 <= id && id <= 3) | |
45 | + reg = RSND_REG_AUDIO_CLK_SEL0; | |
46 | + else if (4 <= id && id <= 7) | |
47 | + reg = RSND_REG_AUDIO_CLK_SEL1; | |
48 | + else | |
49 | + reg = RSND_REG_AUDIO_CLK_SEL2; | |
50 | + | |
51 | + return reg; | |
52 | +} | |
53 | + | |
54 | +int rsnd_adg_ssi_clk_stop(struct rsnd_mod *mod) | |
55 | +{ | |
56 | + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); | |
57 | + enum rsnd_reg reg; | |
58 | + int id; | |
59 | + | |
60 | + /* | |
61 | + * "mod" = "ssi" here. | |
62 | + * we can get "ssi id" from mod | |
63 | + */ | |
64 | + id = rsnd_mod_id(mod); | |
65 | + reg = rsnd_adg_ssi_reg_get(id); | |
66 | + | |
67 | + rsnd_write(priv, mod, reg, 0); | |
68 | + | |
69 | + return 0; | |
70 | +} | |
71 | + | |
72 | +int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *mod, unsigned int rate) | |
73 | +{ | |
74 | + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); | |
75 | + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); | |
76 | + struct device *dev = rsnd_priv_to_dev(priv); | |
77 | + struct clk *clk; | |
78 | + enum rsnd_reg reg; | |
79 | + int id, shift, i; | |
80 | + u32 data; | |
81 | + int sel_table[] = { | |
82 | + [CLKA] = 0x1, | |
83 | + [CLKB] = 0x2, | |
84 | + [CLKC] = 0x3, | |
85 | + [CLKI] = 0x0, | |
86 | + }; | |
87 | + | |
88 | + dev_dbg(dev, "request clock = %d\n", rate); | |
89 | + | |
90 | + /* | |
91 | + * find suitable clock from | |
92 | + * AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC/AUDIO_CLKI. | |
93 | + */ | |
94 | + data = 0; | |
95 | + for_each_rsnd_clk(clk, adg, i) { | |
96 | + if (rate == clk_get_rate(clk)) { | |
97 | + data = sel_table[i]; | |
98 | + goto found_clock; | |
99 | + } | |
100 | + } | |
101 | + | |
102 | + /* | |
103 | + * find 1/6 clock from BRGA/BRGB | |
104 | + */ | |
105 | + if (rate == adg->rate_of_441khz_div_6) { | |
106 | + data = 0x10; | |
107 | + goto found_clock; | |
108 | + } | |
109 | + | |
110 | + if (rate == adg->rate_of_48khz_div_6) { | |
111 | + data = 0x20; | |
112 | + goto found_clock; | |
113 | + } | |
114 | + | |
115 | + return -EIO; | |
116 | + | |
117 | +found_clock: | |
118 | + | |
119 | + /* | |
120 | + * This "mod" = "ssi" here. | |
121 | + * we can get "ssi id" from mod | |
122 | + */ | |
123 | + id = rsnd_mod_id(mod); | |
124 | + reg = rsnd_adg_ssi_reg_get(id); | |
125 | + | |
126 | + dev_dbg(dev, "ADG: ssi%d selects clk%d = %d", id, i, rate); | |
127 | + | |
128 | + /* | |
129 | + * Enable SSIx clock | |
130 | + */ | |
131 | + shift = (id % 4) * 8; | |
132 | + | |
133 | + rsnd_bset(priv, mod, reg, | |
134 | + 0xFF << shift, | |
135 | + data << shift); | |
136 | + | |
137 | + return 0; | |
138 | +} | |
139 | + | |
140 | +static void rsnd_adg_ssi_clk_init(struct rsnd_priv *priv, struct rsnd_adg *adg) | |
141 | +{ | |
142 | + struct clk *clk; | |
143 | + unsigned long rate; | |
144 | + u32 ckr; | |
145 | + int i; | |
146 | + int brg_table[] = { | |
147 | + [CLKA] = 0x0, | |
148 | + [CLKB] = 0x1, | |
149 | + [CLKC] = 0x4, | |
150 | + [CLKI] = 0x2, | |
151 | + }; | |
152 | + | |
153 | + /* | |
154 | + * This driver is assuming that AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC | |
155 | + * have 44.1kHz or 48kHz base clocks for now. | |
156 | + * | |
157 | + * SSI itself can divide parent clock by 1/1 - 1/16 | |
158 | + * So, BRGA outputs 44.1kHz base parent clock 1/32, | |
159 | + * and, BRGB outputs 48.0kHz base parent clock 1/32 here. | |
160 | + * see | |
161 | + * rsnd_adg_ssi_clk_try_start() | |
162 | + */ | |
163 | + ckr = 0; | |
164 | + adg->rate_of_441khz_div_6 = 0; | |
165 | + adg->rate_of_48khz_div_6 = 0; | |
166 | + for_each_rsnd_clk(clk, adg, i) { | |
167 | + rate = clk_get_rate(clk); | |
168 | + | |
169 | + if (0 == rate) /* not used */ | |
170 | + continue; | |
171 | + | |
172 | + /* RBGA */ | |
173 | + if (!adg->rate_of_441khz_div_6 && (0 == rate % 44100)) { | |
174 | + adg->rate_of_441khz_div_6 = rate / 6; | |
175 | + ckr |= brg_table[i] << 20; | |
176 | + } | |
177 | + | |
178 | + /* RBGB */ | |
179 | + if (!adg->rate_of_48khz_div_6 && (0 == rate % 48000)) { | |
180 | + adg->rate_of_48khz_div_6 = rate / 6; | |
181 | + ckr |= brg_table[i] << 16; | |
182 | + } | |
183 | + } | |
184 | + | |
185 | + rsnd_priv_bset(priv, SSICKR, 0x00FF0000, ckr); | |
186 | + rsnd_priv_write(priv, BRRA, 0x00000002); /* 1/6 */ | |
187 | + rsnd_priv_write(priv, BRRB, 0x00000002); /* 1/6 */ | |
188 | +} | |
189 | + | |
190 | +int rsnd_adg_probe(struct platform_device *pdev, | |
191 | + struct rcar_snd_info *info, | |
192 | + struct rsnd_priv *priv) | |
193 | +{ | |
194 | + struct rsnd_adg *adg; | |
195 | + struct device *dev = rsnd_priv_to_dev(priv); | |
196 | + struct clk *clk; | |
197 | + int i; | |
198 | + | |
199 | + adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL); | |
200 | + if (!adg) { | |
201 | + dev_err(dev, "ADG allocate failed\n"); | |
202 | + return -ENOMEM; | |
203 | + } | |
204 | + | |
205 | + adg->clk[CLKA] = clk_get(NULL, "audio_clk_a"); | |
206 | + adg->clk[CLKB] = clk_get(NULL, "audio_clk_b"); | |
207 | + adg->clk[CLKC] = clk_get(NULL, "audio_clk_c"); | |
208 | + adg->clk[CLKI] = clk_get(NULL, "audio_clk_internal"); | |
209 | + for_each_rsnd_clk(clk, adg, i) { | |
210 | + if (IS_ERR(clk)) { | |
211 | + dev_err(dev, "Audio clock failed\n"); | |
212 | + return -EIO; | |
213 | + } | |
214 | + } | |
215 | + | |
216 | + rsnd_adg_ssi_clk_init(priv, adg); | |
217 | + | |
218 | + priv->adg = adg; | |
219 | + | |
220 | + dev_dbg(dev, "adg probed\n"); | |
221 | + | |
222 | + return 0; | |
223 | +} | |
224 | + | |
225 | +void rsnd_adg_remove(struct platform_device *pdev, | |
226 | + struct rsnd_priv *priv) | |
227 | +{ | |
228 | + struct rsnd_adg *adg = priv->adg; | |
229 | + struct clk *clk; | |
230 | + int i; | |
231 | + | |
232 | + for_each_rsnd_clk(clk, adg, i) | |
233 | + clk_put(clk); | |
234 | +} |
sound/soc/sh/rcar/core.c
... | ... | @@ -635,6 +635,10 @@ |
635 | 635 | if (ret < 0) |
636 | 636 | return ret; |
637 | 637 | |
638 | + ret = rsnd_adg_probe(pdev, info, priv); | |
639 | + if (ret < 0) | |
640 | + return ret; | |
641 | + | |
638 | 642 | /* |
639 | 643 | * asoc register |
640 | 644 | */ |
... | ... | @@ -673,6 +677,7 @@ |
673 | 677 | /* |
674 | 678 | * remove each module |
675 | 679 | */ |
680 | + rsnd_adg_remove(pdev, priv); | |
676 | 681 | rsnd_scu_remove(pdev, priv); |
677 | 682 | rsnd_dai_remove(pdev, priv); |
678 | 683 | rsnd_gen_remove(pdev, priv); |
sound/soc/sh/rcar/gen.c
... | ... | @@ -111,6 +111,15 @@ |
111 | 111 | { |
112 | 112 | RSND_GEN1_REG_MAP(gen, SRU, SSI_MODE0, 0x0, 0xD0); |
113 | 113 | RSND_GEN1_REG_MAP(gen, SRU, SSI_MODE1, 0x0, 0xD4); |
114 | + | |
115 | + RSND_GEN1_REG_MAP(gen, ADG, BRRA, 0x0, 0x00); | |
116 | + RSND_GEN1_REG_MAP(gen, ADG, BRRB, 0x0, 0x04); | |
117 | + RSND_GEN1_REG_MAP(gen, ADG, SSICKR, 0x0, 0x08); | |
118 | + RSND_GEN1_REG_MAP(gen, ADG, AUDIO_CLK_SEL0, 0x0, 0x0c); | |
119 | + RSND_GEN1_REG_MAP(gen, ADG, AUDIO_CLK_SEL1, 0x0, 0x10); | |
120 | + RSND_GEN1_REG_MAP(gen, ADG, AUDIO_CLK_SEL3, 0x0, 0x18); | |
121 | + RSND_GEN1_REG_MAP(gen, ADG, AUDIO_CLK_SEL4, 0x0, 0x1c); | |
122 | + RSND_GEN1_REG_MAP(gen, ADG, AUDIO_CLK_SEL5, 0x0, 0x20); | |
114 | 123 | } |
115 | 124 | |
116 | 125 | static int rsnd_gen1_probe(struct platform_device *pdev, |
117 | 126 | |
... | ... | @@ -120,12 +129,15 @@ |
120 | 129 | struct device *dev = rsnd_priv_to_dev(priv); |
121 | 130 | struct rsnd_gen *gen = rsnd_priv_to_gen(priv); |
122 | 131 | struct resource *sru_res; |
132 | + struct resource *adg_res; | |
123 | 133 | |
124 | 134 | /* |
125 | 135 | * map address |
126 | 136 | */ |
127 | 137 | sru_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_SRU); |
128 | - if (!sru_res) { | |
138 | + adg_res = platform_get_resource(pdev, IORESOURCE_MEM, RSND_GEN1_ADG); | |
139 | + if (!sru_res || | |
140 | + !adg_res) { | |
129 | 141 | dev_err(dev, "Not enough SRU/SSI/ADG platform resources.\n"); |
130 | 142 | return -ENODEV; |
131 | 143 | } |
... | ... | @@ -133,7 +145,9 @@ |
133 | 145 | gen->ops = &rsnd_gen1_ops; |
134 | 146 | |
135 | 147 | gen->base[RSND_GEN1_SRU] = devm_ioremap_resource(dev, sru_res); |
136 | - if (!gen->base[RSND_GEN1_SRU]) { | |
148 | + gen->base[RSND_GEN1_ADG] = devm_ioremap_resource(dev, adg_res); | |
149 | + if (!gen->base[RSND_GEN1_SRU] || | |
150 | + !gen->base[RSND_GEN1_ADG]) { | |
137 | 151 | dev_err(dev, "SRU/SSI/ADG ioremap failed\n"); |
138 | 152 | return -ENODEV; |
139 | 153 | } |
... | ... | @@ -143,6 +157,8 @@ |
143 | 157 | dev_dbg(dev, "Gen1 device probed\n"); |
144 | 158 | dev_dbg(dev, "SRU : %08x => %p\n", sru_res->start, |
145 | 159 | gen->base[RSND_GEN1_SRU]); |
160 | + dev_dbg(dev, "ADG : %08x => %p\n", adg_res->start, | |
161 | + gen->base[RSND_GEN1_ADG]); | |
146 | 162 | |
147 | 163 | return 0; |
148 | 164 | } |
sound/soc/sh/rcar/rsnd.h
... | ... | @@ -32,6 +32,17 @@ |
32 | 32 | RSND_REG_SSI_MODE0, |
33 | 33 | RSND_REG_SSI_MODE1, |
34 | 34 | |
35 | + /* ADG */ | |
36 | + RSND_REG_BRRA, | |
37 | + RSND_REG_BRRB, | |
38 | + RSND_REG_SSICKR, | |
39 | + RSND_REG_AUDIO_CLK_SEL0, | |
40 | + RSND_REG_AUDIO_CLK_SEL1, | |
41 | + RSND_REG_AUDIO_CLK_SEL2, | |
42 | + RSND_REG_AUDIO_CLK_SEL3, | |
43 | + RSND_REG_AUDIO_CLK_SEL4, | |
44 | + RSND_REG_AUDIO_CLK_SEL5, | |
45 | + | |
35 | 46 | RSND_REG_MAX, |
36 | 47 | }; |
37 | 48 | |
... | ... | @@ -163,6 +174,17 @@ |
163 | 174 | enum rsnd_reg reg); |
164 | 175 | |
165 | 176 | /* |
177 | + * R-Car ADG | |
178 | + */ | |
179 | +int rsnd_adg_ssi_clk_stop(struct rsnd_mod *mod); | |
180 | +int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *mod, unsigned int rate); | |
181 | +int rsnd_adg_probe(struct platform_device *pdev, | |
182 | + struct rcar_snd_info *info, | |
183 | + struct rsnd_priv *priv); | |
184 | +void rsnd_adg_remove(struct platform_device *pdev, | |
185 | + struct rsnd_priv *priv); | |
186 | + | |
187 | +/* | |
166 | 188 | * R-Car sound priv |
167 | 189 | */ |
168 | 190 | struct rsnd_priv { |
... | ... | @@ -181,6 +203,11 @@ |
181 | 203 | */ |
182 | 204 | void *scu; |
183 | 205 | int scu_nr; |
206 | + | |
207 | + /* | |
208 | + * below value will be filled on rsnd_adg_probe() | |
209 | + */ | |
210 | + void *adg; | |
184 | 211 | |
185 | 212 | /* |
186 | 213 | * below value will be filled on rsnd_dai_probe() |