1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Atmel SSC driver |
4 | * |
5 | * Copyright (C) 2007 Atmel Corporation |
6 | */ |
7 | |
8 | #include <linux/platform_device.h> |
9 | #include <linux/list.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/mutex.h> |
14 | #include <linux/atmel-ssc.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/module.h> |
17 | |
18 | #include <linux/of.h> |
19 | |
20 | #include "../../sound/soc/atmel/atmel_ssc_dai.h" |
21 | |
22 | /* Serialize access to ssc_list and user count */ |
23 | static DEFINE_MUTEX(user_lock); |
24 | static LIST_HEAD(ssc_list); |
25 | |
26 | struct ssc_device *ssc_request(unsigned int ssc_num) |
27 | { |
28 | int ssc_valid = 0; |
29 | struct ssc_device *ssc; |
30 | |
31 | mutex_lock(&user_lock); |
32 | list_for_each_entry(ssc, &ssc_list, list) { |
33 | if (ssc->pdev->dev.of_node) { |
34 | if (of_alias_get_id(np: ssc->pdev->dev.of_node, stem: "ssc" ) |
35 | == ssc_num) { |
36 | ssc->pdev->id = ssc_num; |
37 | ssc_valid = 1; |
38 | break; |
39 | } |
40 | } else if (ssc->pdev->id == ssc_num) { |
41 | ssc_valid = 1; |
42 | break; |
43 | } |
44 | } |
45 | |
46 | if (!ssc_valid) { |
47 | mutex_unlock(lock: &user_lock); |
48 | pr_err("ssc: ssc%d platform device is missing\n" , ssc_num); |
49 | return ERR_PTR(error: -ENODEV); |
50 | } |
51 | |
52 | if (ssc->user) { |
53 | mutex_unlock(lock: &user_lock); |
54 | dev_dbg(&ssc->pdev->dev, "module busy\n" ); |
55 | return ERR_PTR(error: -EBUSY); |
56 | } |
57 | ssc->user++; |
58 | mutex_unlock(lock: &user_lock); |
59 | |
60 | clk_prepare(clk: ssc->clk); |
61 | |
62 | return ssc; |
63 | } |
64 | EXPORT_SYMBOL(ssc_request); |
65 | |
66 | void ssc_free(struct ssc_device *ssc) |
67 | { |
68 | bool disable_clk = true; |
69 | |
70 | mutex_lock(&user_lock); |
71 | if (ssc->user) |
72 | ssc->user--; |
73 | else { |
74 | disable_clk = false; |
75 | dev_dbg(&ssc->pdev->dev, "device already free\n" ); |
76 | } |
77 | mutex_unlock(lock: &user_lock); |
78 | |
79 | if (disable_clk) |
80 | clk_unprepare(clk: ssc->clk); |
81 | } |
82 | EXPORT_SYMBOL(ssc_free); |
83 | |
84 | static struct atmel_ssc_platform_data at91rm9200_config = { |
85 | .use_dma = 0, |
86 | .has_fslen_ext = 0, |
87 | }; |
88 | |
89 | static struct atmel_ssc_platform_data at91sam9rl_config = { |
90 | .use_dma = 0, |
91 | .has_fslen_ext = 1, |
92 | }; |
93 | |
94 | static struct atmel_ssc_platform_data at91sam9g45_config = { |
95 | .use_dma = 1, |
96 | .has_fslen_ext = 1, |
97 | }; |
98 | |
99 | static const struct platform_device_id atmel_ssc_devtypes[] = { |
100 | { |
101 | .name = "at91rm9200_ssc" , |
102 | .driver_data = (unsigned long) &at91rm9200_config, |
103 | }, { |
104 | .name = "at91sam9rl_ssc" , |
105 | .driver_data = (unsigned long) &at91sam9rl_config, |
106 | }, { |
107 | .name = "at91sam9g45_ssc" , |
108 | .driver_data = (unsigned long) &at91sam9g45_config, |
109 | }, { |
110 | /* sentinel */ |
111 | } |
112 | }; |
113 | |
114 | #ifdef CONFIG_OF |
115 | static const struct of_device_id atmel_ssc_dt_ids[] = { |
116 | { |
117 | .compatible = "atmel,at91rm9200-ssc" , |
118 | .data = &at91rm9200_config, |
119 | }, { |
120 | .compatible = "atmel,at91sam9rl-ssc" , |
121 | .data = &at91sam9rl_config, |
122 | }, { |
123 | .compatible = "atmel,at91sam9g45-ssc" , |
124 | .data = &at91sam9g45_config, |
125 | }, { |
126 | /* sentinel */ |
127 | } |
128 | }; |
129 | MODULE_DEVICE_TABLE(of, atmel_ssc_dt_ids); |
130 | #endif |
131 | |
132 | static inline const struct atmel_ssc_platform_data * |
133 | atmel_ssc_get_driver_data(struct platform_device *pdev) |
134 | { |
135 | if (pdev->dev.of_node) { |
136 | const struct of_device_id *match; |
137 | match = of_match_node(matches: atmel_ssc_dt_ids, node: pdev->dev.of_node); |
138 | if (match == NULL) |
139 | return NULL; |
140 | return match->data; |
141 | } |
142 | |
143 | return (struct atmel_ssc_platform_data *) |
144 | platform_get_device_id(pdev)->driver_data; |
145 | } |
146 | |
147 | #ifdef CONFIG_SND_ATMEL_SOC_SSC |
148 | static int ssc_sound_dai_probe(struct ssc_device *ssc) |
149 | { |
150 | struct device_node *np = ssc->pdev->dev.of_node; |
151 | int ret; |
152 | int id; |
153 | |
154 | ssc->sound_dai = false; |
155 | |
156 | if (!of_property_read_bool(np, propname: "#sound-dai-cells" )) |
157 | return 0; |
158 | |
159 | id = of_alias_get_id(np, stem: "ssc" ); |
160 | if (id < 0) |
161 | return id; |
162 | |
163 | ret = atmel_ssc_set_audio(ssc_id: id); |
164 | ssc->sound_dai = !ret; |
165 | |
166 | return ret; |
167 | } |
168 | |
169 | static void ssc_sound_dai_remove(struct ssc_device *ssc) |
170 | { |
171 | if (!ssc->sound_dai) |
172 | return; |
173 | |
174 | atmel_ssc_put_audio(ssc_id: of_alias_get_id(np: ssc->pdev->dev.of_node, stem: "ssc" )); |
175 | } |
176 | #else |
177 | static inline int ssc_sound_dai_probe(struct ssc_device *ssc) |
178 | { |
179 | if (of_property_read_bool(ssc->pdev->dev.of_node, "#sound-dai-cells" )) |
180 | return -ENOTSUPP; |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | static inline void ssc_sound_dai_remove(struct ssc_device *ssc) |
186 | { |
187 | } |
188 | #endif |
189 | |
190 | static int ssc_probe(struct platform_device *pdev) |
191 | { |
192 | struct resource *regs; |
193 | struct ssc_device *ssc; |
194 | const struct atmel_ssc_platform_data *plat_dat; |
195 | |
196 | ssc = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct ssc_device), GFP_KERNEL); |
197 | if (!ssc) { |
198 | dev_dbg(&pdev->dev, "out of memory\n" ); |
199 | return -ENOMEM; |
200 | } |
201 | |
202 | ssc->pdev = pdev; |
203 | |
204 | plat_dat = atmel_ssc_get_driver_data(pdev); |
205 | if (!plat_dat) |
206 | return -ENODEV; |
207 | ssc->pdata = (struct atmel_ssc_platform_data *)plat_dat; |
208 | |
209 | if (pdev->dev.of_node) { |
210 | struct device_node *np = pdev->dev.of_node; |
211 | ssc->clk_from_rk_pin = |
212 | of_property_read_bool(np, propname: "atmel,clk-from-rk-pin" ); |
213 | } |
214 | |
215 | ssc->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: ®s); |
216 | if (IS_ERR(ptr: ssc->regs)) |
217 | return PTR_ERR(ptr: ssc->regs); |
218 | |
219 | ssc->phybase = regs->start; |
220 | |
221 | ssc->clk = devm_clk_get(dev: &pdev->dev, id: "pclk" ); |
222 | if (IS_ERR(ptr: ssc->clk)) { |
223 | dev_dbg(&pdev->dev, "no pclk clock defined\n" ); |
224 | return -ENXIO; |
225 | } |
226 | |
227 | /* disable all interrupts */ |
228 | clk_prepare_enable(clk: ssc->clk); |
229 | ssc_writel(ssc->regs, IDR, -1); |
230 | ssc_readl(ssc->regs, SR); |
231 | clk_disable_unprepare(clk: ssc->clk); |
232 | |
233 | ssc->irq = platform_get_irq(pdev, 0); |
234 | if (ssc->irq < 0) { |
235 | dev_dbg(&pdev->dev, "could not get irq\n" ); |
236 | return ssc->irq; |
237 | } |
238 | |
239 | mutex_lock(&user_lock); |
240 | list_add_tail(new: &ssc->list, head: &ssc_list); |
241 | mutex_unlock(lock: &user_lock); |
242 | |
243 | platform_set_drvdata(pdev, data: ssc); |
244 | |
245 | dev_info(&pdev->dev, "Atmel SSC device at 0x%p (irq %d)\n" , |
246 | ssc->regs, ssc->irq); |
247 | |
248 | if (ssc_sound_dai_probe(ssc)) |
249 | dev_err(&pdev->dev, "failed to auto-setup ssc for audio\n" ); |
250 | |
251 | return 0; |
252 | } |
253 | |
254 | static int ssc_remove(struct platform_device *pdev) |
255 | { |
256 | struct ssc_device *ssc = platform_get_drvdata(pdev); |
257 | |
258 | ssc_sound_dai_remove(ssc); |
259 | |
260 | mutex_lock(&user_lock); |
261 | list_del(entry: &ssc->list); |
262 | mutex_unlock(lock: &user_lock); |
263 | |
264 | return 0; |
265 | } |
266 | |
267 | static struct platform_driver ssc_driver = { |
268 | .driver = { |
269 | .name = "ssc" , |
270 | .of_match_table = of_match_ptr(atmel_ssc_dt_ids), |
271 | }, |
272 | .id_table = atmel_ssc_devtypes, |
273 | .probe = ssc_probe, |
274 | .remove = ssc_remove, |
275 | }; |
276 | module_platform_driver(ssc_driver); |
277 | |
278 | MODULE_AUTHOR("Hans-Christian Noren Egtvedt <egtvedt@samfundet.no>" ); |
279 | MODULE_DESCRIPTION("SSC driver for Atmel AT91" ); |
280 | MODULE_LICENSE("GPL" ); |
281 | MODULE_ALIAS("platform:ssc" ); |
282 | |