diff options
author | Arnaud Pouliquen <arnaud.pouliquen@st.com> | 2018-01-10 05:13:10 -0500 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2018-01-10 05:30:11 -0500 |
commit | bed73904e76fc08d0ec3a7ba3eb2ddbb2e38862c (patch) | |
tree | 99193706b1e4dc8d0a1906f827af83428dbcbc6f /drivers/iio/adc/stm32-dfsdm-core.c | |
parent | 6c82f947fc9784b774cad0b90c20a5d703f9b763 (diff) |
IIO: ADC: add stm32 DFSDM core support
Add driver for stm32 DFSDM pheripheral. Its converts a sigma delta
stream in n bit samples through a low pass filter and an integrator.
stm32-dfsdm-core driver is the core part supporting the filter
instances dedicated to sigma-delta ADC or audio PDM microphone purpose.
Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'drivers/iio/adc/stm32-dfsdm-core.c')
-rw-r--r-- | drivers/iio/adc/stm32-dfsdm-core.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c new file mode 100644 index 000000000000..72427414db7f --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-core.c | |||
@@ -0,0 +1,309 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * This file is part the core part STM32 DFSDM driver | ||
4 | * | ||
5 | * Copyright (C) 2017, STMicroelectronics - All Rights Reserved | ||
6 | * Author(s): Arnaud Pouliquen <arnaud.pouliquen@st.com> for STMicroelectronics. | ||
7 | */ | ||
8 | |||
9 | #include <linux/clk.h> | ||
10 | #include <linux/iio/iio.h> | ||
11 | #include <linux/iio/sysfs.h> | ||
12 | #include <linux/interrupt.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/of_device.h> | ||
15 | #include <linux/regmap.h> | ||
16 | #include <linux/slab.h> | ||
17 | |||
18 | #include "stm32-dfsdm.h" | ||
19 | |||
20 | struct stm32_dfsdm_dev_data { | ||
21 | unsigned int num_filters; | ||
22 | unsigned int num_channels; | ||
23 | const struct regmap_config *regmap_cfg; | ||
24 | }; | ||
25 | |||
26 | #define STM32H7_DFSDM_NUM_FILTERS 4 | ||
27 | #define STM32H7_DFSDM_NUM_CHANNELS 8 | ||
28 | |||
29 | static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) | ||
30 | { | ||
31 | if (reg < DFSDM_FILTER_BASE_ADR) | ||
32 | return false; | ||
33 | |||
34 | /* | ||
35 | * Mask is done on register to avoid to list registers of all | ||
36 | * filter instances. | ||
37 | */ | ||
38 | switch (reg & DFSDM_FILTER_REG_MASK) { | ||
39 | case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK: | ||
40 | case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK: | ||
41 | case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK: | ||
42 | case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK: | ||
43 | return true; | ||
44 | } | ||
45 | |||
46 | return false; | ||
47 | } | ||
48 | |||
49 | static const struct regmap_config stm32h7_dfsdm_regmap_cfg = { | ||
50 | .reg_bits = 32, | ||
51 | .val_bits = 32, | ||
52 | .reg_stride = sizeof(u32), | ||
53 | .max_register = 0x2B8, | ||
54 | .volatile_reg = stm32_dfsdm_volatile_reg, | ||
55 | .fast_io = true, | ||
56 | }; | ||
57 | |||
58 | static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = { | ||
59 | .num_filters = STM32H7_DFSDM_NUM_FILTERS, | ||
60 | .num_channels = STM32H7_DFSDM_NUM_CHANNELS, | ||
61 | .regmap_cfg = &stm32h7_dfsdm_regmap_cfg, | ||
62 | }; | ||
63 | |||
64 | struct dfsdm_priv { | ||
65 | struct platform_device *pdev; /* platform device */ | ||
66 | |||
67 | struct stm32_dfsdm dfsdm; /* common data exported for all instances */ | ||
68 | |||
69 | unsigned int spi_clk_out_div; /* SPI clkout divider value */ | ||
70 | atomic_t n_active_ch; /* number of current active channels */ | ||
71 | |||
72 | struct clk *clk; /* DFSDM clock */ | ||
73 | struct clk *aclk; /* audio clock */ | ||
74 | }; | ||
75 | |||
76 | /** | ||
77 | * stm32_dfsdm_start_dfsdm - start global dfsdm interface. | ||
78 | * | ||
79 | * Enable interface if n_active_ch is not null. | ||
80 | * @dfsdm: Handle used to retrieve dfsdm context. | ||
81 | */ | ||
82 | int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) | ||
83 | { | ||
84 | struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); | ||
85 | struct device *dev = &priv->pdev->dev; | ||
86 | unsigned int clk_div = priv->spi_clk_out_div; | ||
87 | int ret; | ||
88 | |||
89 | if (atomic_inc_return(&priv->n_active_ch) == 1) { | ||
90 | ret = clk_prepare_enable(priv->clk); | ||
91 | if (ret < 0) { | ||
92 | dev_err(dev, "Failed to start clock\n"); | ||
93 | goto error_ret; | ||
94 | } | ||
95 | if (priv->aclk) { | ||
96 | ret = clk_prepare_enable(priv->aclk); | ||
97 | if (ret < 0) { | ||
98 | dev_err(dev, "Failed to start audio clock\n"); | ||
99 | goto disable_clk; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | /* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */ | ||
104 | ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), | ||
105 | DFSDM_CHCFGR1_CKOUTDIV_MASK, | ||
106 | DFSDM_CHCFGR1_CKOUTDIV(clk_div)); | ||
107 | if (ret < 0) | ||
108 | goto disable_aclk; | ||
109 | |||
110 | /* Global enable of DFSDM interface */ | ||
111 | ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), | ||
112 | DFSDM_CHCFGR1_DFSDMEN_MASK, | ||
113 | DFSDM_CHCFGR1_DFSDMEN(1)); | ||
114 | if (ret < 0) | ||
115 | goto disable_aclk; | ||
116 | } | ||
117 | |||
118 | dev_dbg(dev, "%s: n_active_ch %d\n", __func__, | ||
119 | atomic_read(&priv->n_active_ch)); | ||
120 | |||
121 | return 0; | ||
122 | |||
123 | disable_aclk: | ||
124 | clk_disable_unprepare(priv->aclk); | ||
125 | disable_clk: | ||
126 | clk_disable_unprepare(priv->clk); | ||
127 | |||
128 | error_ret: | ||
129 | atomic_dec(&priv->n_active_ch); | ||
130 | |||
131 | return ret; | ||
132 | } | ||
133 | EXPORT_SYMBOL_GPL(stm32_dfsdm_start_dfsdm); | ||
134 | |||
135 | /** | ||
136 | * stm32_dfsdm_stop_dfsdm - stop global DFSDM interface. | ||
137 | * | ||
138 | * Disable interface if n_active_ch is null | ||
139 | * @dfsdm: Handle used to retrieve dfsdm context. | ||
140 | */ | ||
141 | int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) | ||
142 | { | ||
143 | struct dfsdm_priv *priv = container_of(dfsdm, struct dfsdm_priv, dfsdm); | ||
144 | int ret; | ||
145 | |||
146 | if (atomic_dec_and_test(&priv->n_active_ch)) { | ||
147 | /* Global disable of DFSDM interface */ | ||
148 | ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), | ||
149 | DFSDM_CHCFGR1_DFSDMEN_MASK, | ||
150 | DFSDM_CHCFGR1_DFSDMEN(0)); | ||
151 | if (ret < 0) | ||
152 | return ret; | ||
153 | |||
154 | /* Stop SPI CLKOUT */ | ||
155 | ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), | ||
156 | DFSDM_CHCFGR1_CKOUTDIV_MASK, | ||
157 | DFSDM_CHCFGR1_CKOUTDIV(0)); | ||
158 | if (ret < 0) | ||
159 | return ret; | ||
160 | |||
161 | clk_disable_unprepare(priv->clk); | ||
162 | if (priv->aclk) | ||
163 | clk_disable_unprepare(priv->aclk); | ||
164 | } | ||
165 | dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__, | ||
166 | atomic_read(&priv->n_active_ch)); | ||
167 | |||
168 | return 0; | ||
169 | } | ||
170 | EXPORT_SYMBOL_GPL(stm32_dfsdm_stop_dfsdm); | ||
171 | |||
172 | static int stm32_dfsdm_parse_of(struct platform_device *pdev, | ||
173 | struct dfsdm_priv *priv) | ||
174 | { | ||
175 | struct device_node *node = pdev->dev.of_node; | ||
176 | struct resource *res; | ||
177 | unsigned long clk_freq; | ||
178 | unsigned int spi_freq, rem; | ||
179 | int ret; | ||
180 | |||
181 | if (!node) | ||
182 | return -EINVAL; | ||
183 | |||
184 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
185 | if (!res) { | ||
186 | dev_err(&pdev->dev, "Failed to get memory resource\n"); | ||
187 | return -ENODEV; | ||
188 | } | ||
189 | priv->dfsdm.phys_base = res->start; | ||
190 | priv->dfsdm.base = devm_ioremap_resource(&pdev->dev, res); | ||
191 | |||
192 | /* | ||
193 | * "dfsdm" clock is mandatory for DFSDM peripheral clocking. | ||
194 | * "dfsdm" or "audio" clocks can be used as source clock for | ||
195 | * the SPI clock out signal and internal processing, depending | ||
196 | * on use case. | ||
197 | */ | ||
198 | priv->clk = devm_clk_get(&pdev->dev, "dfsdm"); | ||
199 | if (IS_ERR(priv->clk)) { | ||
200 | dev_err(&pdev->dev, "No stm32_dfsdm_clk clock found\n"); | ||
201 | return -EINVAL; | ||
202 | } | ||
203 | |||
204 | priv->aclk = devm_clk_get(&pdev->dev, "audio"); | ||
205 | if (IS_ERR(priv->aclk)) | ||
206 | priv->aclk = NULL; | ||
207 | |||
208 | if (priv->aclk) | ||
209 | clk_freq = clk_get_rate(priv->aclk); | ||
210 | else | ||
211 | clk_freq = clk_get_rate(priv->clk); | ||
212 | |||
213 | /* SPI clock out frequency */ | ||
214 | ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency", | ||
215 | &spi_freq); | ||
216 | if (ret < 0) { | ||
217 | /* No SPI master mode */ | ||
218 | return 0; | ||
219 | } | ||
220 | |||
221 | priv->spi_clk_out_div = div_u64_rem(clk_freq, spi_freq, &rem) - 1; | ||
222 | priv->dfsdm.spi_master_freq = spi_freq; | ||
223 | |||
224 | if (rem) { | ||
225 | dev_warn(&pdev->dev, "SPI clock not accurate\n"); | ||
226 | dev_warn(&pdev->dev, "%ld = %d * %d + %d\n", | ||
227 | clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem); | ||
228 | } | ||
229 | |||
230 | return 0; | ||
231 | }; | ||
232 | |||
233 | static const struct of_device_id stm32_dfsdm_of_match[] = { | ||
234 | { | ||
235 | .compatible = "st,stm32h7-dfsdm", | ||
236 | .data = &stm32h7_dfsdm_data, | ||
237 | }, | ||
238 | {} | ||
239 | }; | ||
240 | MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match); | ||
241 | |||
242 | static int stm32_dfsdm_probe(struct platform_device *pdev) | ||
243 | { | ||
244 | struct dfsdm_priv *priv; | ||
245 | struct device_node *pnode = pdev->dev.of_node; | ||
246 | const struct of_device_id *of_id; | ||
247 | const struct stm32_dfsdm_dev_data *dev_data; | ||
248 | struct stm32_dfsdm *dfsdm; | ||
249 | int ret; | ||
250 | |||
251 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | ||
252 | if (!priv) | ||
253 | return -ENOMEM; | ||
254 | |||
255 | priv->pdev = pdev; | ||
256 | |||
257 | of_id = of_match_node(stm32_dfsdm_of_match, pnode); | ||
258 | if (!of_id->data) { | ||
259 | dev_err(&pdev->dev, "Data associated to device is missing\n"); | ||
260 | return -EINVAL; | ||
261 | } | ||
262 | |||
263 | dev_data = (const struct stm32_dfsdm_dev_data *)of_id->data; | ||
264 | dfsdm = &priv->dfsdm; | ||
265 | dfsdm->fl_list = devm_kcalloc(&pdev->dev, dev_data->num_filters, | ||
266 | sizeof(*dfsdm->fl_list), GFP_KERNEL); | ||
267 | if (!dfsdm->fl_list) | ||
268 | return -ENOMEM; | ||
269 | |||
270 | dfsdm->num_fls = dev_data->num_filters; | ||
271 | dfsdm->ch_list = devm_kcalloc(&pdev->dev, dev_data->num_channels, | ||
272 | sizeof(*dfsdm->ch_list), | ||
273 | GFP_KERNEL); | ||
274 | if (!dfsdm->ch_list) | ||
275 | return -ENOMEM; | ||
276 | dfsdm->num_chs = dev_data->num_channels; | ||
277 | |||
278 | ret = stm32_dfsdm_parse_of(pdev, priv); | ||
279 | if (ret < 0) | ||
280 | return ret; | ||
281 | |||
282 | dfsdm->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "dfsdm", | ||
283 | dfsdm->base, | ||
284 | &stm32h7_dfsdm_regmap_cfg); | ||
285 | if (IS_ERR(dfsdm->regmap)) { | ||
286 | ret = PTR_ERR(dfsdm->regmap); | ||
287 | dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", | ||
288 | __func__, ret); | ||
289 | return ret; | ||
290 | } | ||
291 | |||
292 | platform_set_drvdata(pdev, dfsdm); | ||
293 | |||
294 | return devm_of_platform_populate(&pdev->dev); | ||
295 | } | ||
296 | |||
297 | static struct platform_driver stm32_dfsdm_driver = { | ||
298 | .probe = stm32_dfsdm_probe, | ||
299 | .driver = { | ||
300 | .name = "stm32-dfsdm", | ||
301 | .of_match_table = stm32_dfsdm_of_match, | ||
302 | }, | ||
303 | }; | ||
304 | |||
305 | module_platform_driver(stm32_dfsdm_driver); | ||
306 | |||
307 | MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>"); | ||
308 | MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); | ||
309 | MODULE_LICENSE("GPL v2"); | ||