GNU Linux-libre 5.10.76-gnu1
[releases.git] / sound / soc / tegra / tegra_asoc_utils.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * tegra_asoc_utils.c - Harmony machine ASoC driver
4  *
5  * Author: Stephen Warren <swarren@nvidia.com>
6  * Copyright (C) 2010,2012 - NVIDIA, Inc.
7  */
8
9 #include <linux/clk.h>
10 #include <linux/device.h>
11 #include <linux/err.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/of.h>
15
16 #include "tegra_asoc_utils.h"
17
18 int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
19                               int mclk)
20 {
21         int new_baseclock;
22         bool clk_change;
23         int err;
24
25         switch (srate) {
26         case 11025:
27         case 22050:
28         case 44100:
29         case 88200:
30                 if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20)
31                         new_baseclock = 56448000;
32                 else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30)
33                         new_baseclock = 564480000;
34                 else
35                         new_baseclock = 282240000;
36                 break;
37         case 8000:
38         case 16000:
39         case 32000:
40         case 48000:
41         case 64000:
42         case 96000:
43                 if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20)
44                         new_baseclock = 73728000;
45                 else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30)
46                         new_baseclock = 552960000;
47                 else
48                         new_baseclock = 368640000;
49                 break;
50         default:
51                 return -EINVAL;
52         }
53
54         clk_change = ((new_baseclock != data->set_baseclock) ||
55                         (mclk != data->set_mclk));
56         if (!clk_change)
57                 return 0;
58
59         data->set_baseclock = 0;
60         data->set_mclk = 0;
61
62         clk_disable_unprepare(data->clk_cdev1);
63
64         err = clk_set_rate(data->clk_pll_a, new_baseclock);
65         if (err) {
66                 dev_err(data->dev, "Can't set pll_a rate: %d\n", err);
67                 return err;
68         }
69
70         err = clk_set_rate(data->clk_pll_a_out0, mclk);
71         if (err) {
72                 dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err);
73                 return err;
74         }
75
76         /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */
77
78         err = clk_prepare_enable(data->clk_cdev1);
79         if (err) {
80                 dev_err(data->dev, "Can't enable cdev1: %d\n", err);
81                 return err;
82         }
83
84         data->set_baseclock = new_baseclock;
85         data->set_mclk = mclk;
86
87         return 0;
88 }
89 EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate);
90
91 int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data)
92 {
93         const int pll_rate = 73728000;
94         const int ac97_rate = 24576000;
95         int err;
96
97         clk_disable_unprepare(data->clk_cdev1);
98
99         /*
100          * AC97 rate is fixed at 24.576MHz and is used for both the host
101          * controller and the external codec
102          */
103         err = clk_set_rate(data->clk_pll_a, pll_rate);
104         if (err) {
105                 dev_err(data->dev, "Can't set pll_a rate: %d\n", err);
106                 return err;
107         }
108
109         err = clk_set_rate(data->clk_pll_a_out0, ac97_rate);
110         if (err) {
111                 dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err);
112                 return err;
113         }
114
115         /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */
116
117         err = clk_prepare_enable(data->clk_cdev1);
118         if (err) {
119                 dev_err(data->dev, "Can't enable cdev1: %d\n", err);
120                 return err;
121         }
122
123         data->set_baseclock = pll_rate;
124         data->set_mclk = ac97_rate;
125
126         return 0;
127 }
128 EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_ac97_rate);
129
130 int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
131                           struct device *dev)
132 {
133         struct clk *clk_out_1, *clk_extern1;
134         int ret;
135
136         data->dev = dev;
137
138         if (of_machine_is_compatible("nvidia,tegra20"))
139                 data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20;
140         else if (of_machine_is_compatible("nvidia,tegra30"))
141                 data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA30;
142         else if (of_machine_is_compatible("nvidia,tegra114"))
143                 data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA114;
144         else if (of_machine_is_compatible("nvidia,tegra124"))
145                 data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA124;
146         else {
147                 dev_err(data->dev, "SoC unknown to Tegra ASoC utils\n");
148                 return -EINVAL;
149         }
150
151         data->clk_pll_a = devm_clk_get(dev, "pll_a");
152         if (IS_ERR(data->clk_pll_a)) {
153                 dev_err(data->dev, "Can't retrieve clk pll_a\n");
154                 return PTR_ERR(data->clk_pll_a);
155         }
156
157         data->clk_pll_a_out0 = devm_clk_get(dev, "pll_a_out0");
158         if (IS_ERR(data->clk_pll_a_out0)) {
159                 dev_err(data->dev, "Can't retrieve clk pll_a_out0\n");
160                 return PTR_ERR(data->clk_pll_a_out0);
161         }
162
163         data->clk_cdev1 = devm_clk_get(dev, "mclk");
164         if (IS_ERR(data->clk_cdev1)) {
165                 dev_err(data->dev, "Can't retrieve clk cdev1\n");
166                 return PTR_ERR(data->clk_cdev1);
167         }
168
169         /*
170          * If clock parents are not set in DT, configure here to use clk_out_1
171          * as mclk and extern1 as parent for Tegra30 and higher.
172          */
173         if (!of_find_property(dev->of_node, "assigned-clock-parents", NULL) &&
174             data->soc > TEGRA_ASOC_UTILS_SOC_TEGRA20) {
175                 dev_warn(data->dev,
176                          "Configuring clocks for a legacy device-tree\n");
177                 dev_warn(data->dev,
178                          "Please update DT to use assigned-clock-parents\n");
179                 clk_extern1 = devm_clk_get(dev, "extern1");
180                 if (IS_ERR(clk_extern1)) {
181                         dev_err(data->dev, "Can't retrieve clk extern1\n");
182                         return PTR_ERR(clk_extern1);
183                 }
184
185                 ret = clk_set_parent(clk_extern1, data->clk_pll_a_out0);
186                 if (ret < 0) {
187                         dev_err(data->dev,
188                                 "Set parent failed for clk extern1\n");
189                         return ret;
190                 }
191
192                 clk_out_1 = devm_clk_get(dev, "pmc_clk_out_1");
193                 if (IS_ERR(clk_out_1)) {
194                         dev_err(data->dev, "Can't retrieve pmc_clk_out_1\n");
195                         return PTR_ERR(clk_out_1);
196                 }
197
198                 ret = clk_set_parent(clk_out_1, clk_extern1);
199                 if (ret < 0) {
200                         dev_err(data->dev,
201                                 "Set parent failed for pmc_clk_out_1\n");
202                         return ret;
203                 }
204
205                 data->clk_cdev1 = clk_out_1;
206         }
207
208         /*
209          * FIXME: There is some unknown dependency between audio mclk disable
210          * and suspend-resume functionality on Tegra30, although audio mclk is
211          * only needed for audio.
212          */
213         ret = clk_prepare_enable(data->clk_cdev1);
214         if (ret) {
215                 dev_err(data->dev, "Can't enable cdev1: %d\n", ret);
216                 return ret;
217         }
218
219         return 0;
220 }
221 EXPORT_SYMBOL_GPL(tegra_asoc_utils_init);
222
223 MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
224 MODULE_DESCRIPTION("Tegra ASoC utility code");
225 MODULE_LICENSE("GPL");