1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // CS35L56 ALSA SoC audio driver SoundWire binding |
4 | // |
5 | // Copyright (C) 2023 Cirrus Logic, Inc. and |
6 | // Cirrus Logic International Semiconductor Ltd. |
7 | |
8 | #include <linux/delay.h> |
9 | #include <linux/device.h> |
10 | #include <linux/err.h> |
11 | #include <linux/module.h> |
12 | #include <linux/pm_runtime.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/soundwire/sdw.h> |
15 | #include <linux/soundwire/sdw_registers.h> |
16 | #include <linux/soundwire/sdw_type.h> |
17 | #include <linux/swab.h> |
18 | #include <linux/types.h> |
19 | #include <linux/workqueue.h> |
20 | |
21 | #include "cs35l56.h" |
22 | |
23 | /* Register addresses are offset when sent over SoundWire */ |
24 | #define CS35L56_SDW_ADDR_OFFSET 0x8000 |
25 | |
26 | static int cs35l56_sdw_read_one(struct sdw_slave *peripheral, unsigned int reg, void *buf) |
27 | { |
28 | int ret; |
29 | |
30 | ret = sdw_nread_no_pm(slave: peripheral, addr: reg, count: 4, val: (u8 *)buf); |
31 | if (ret != 0) { |
32 | dev_err(&peripheral->dev, "Read failed @%#x:%d\n" , reg, ret); |
33 | return ret; |
34 | } |
35 | |
36 | swab32s(p: (u32 *)buf); |
37 | |
38 | return 0; |
39 | } |
40 | |
41 | static int cs35l56_sdw_read(void *context, const void *reg_buf, |
42 | const size_t reg_size, void *val_buf, |
43 | size_t val_size) |
44 | { |
45 | struct sdw_slave *peripheral = context; |
46 | u8 *buf8 = val_buf; |
47 | unsigned int reg, bytes; |
48 | int ret; |
49 | |
50 | reg = le32_to_cpu(*(const __le32 *)reg_buf); |
51 | reg += CS35L56_SDW_ADDR_OFFSET; |
52 | |
53 | if (val_size == 4) |
54 | return cs35l56_sdw_read_one(peripheral, reg, buf: val_buf); |
55 | |
56 | while (val_size) { |
57 | bytes = SDW_REG_NO_PAGE - (reg & SDW_REGADDR); /* to end of page */ |
58 | if (bytes > val_size) |
59 | bytes = val_size; |
60 | |
61 | ret = sdw_nread_no_pm(slave: peripheral, addr: reg, count: bytes, val: buf8); |
62 | if (ret != 0) { |
63 | dev_err(&peripheral->dev, "Read failed @%#x..%#x:%d\n" , |
64 | reg, reg + bytes - 1, ret); |
65 | return ret; |
66 | } |
67 | |
68 | swab32_array(buf: (u32 *)buf8, words: bytes / 4); |
69 | val_size -= bytes; |
70 | reg += bytes; |
71 | buf8 += bytes; |
72 | } |
73 | |
74 | return 0; |
75 | } |
76 | |
77 | static inline void cs35l56_swab_copy(void *dest, const void *src, size_t nbytes) |
78 | { |
79 | u32 *dest32 = dest; |
80 | const u32 *src32 = src; |
81 | |
82 | for (; nbytes > 0; nbytes -= 4) |
83 | *dest32++ = swab32(*src32++); |
84 | } |
85 | |
86 | static int cs35l56_sdw_write_one(struct sdw_slave *peripheral, unsigned int reg, const void *buf) |
87 | { |
88 | u32 val_le = swab32(*(u32 *)buf); |
89 | int ret; |
90 | |
91 | ret = sdw_nwrite_no_pm(slave: peripheral, addr: reg, count: 4, val: (u8 *)&val_le); |
92 | if (ret != 0) { |
93 | dev_err(&peripheral->dev, "Write failed @%#x:%d\n" , reg, ret); |
94 | return ret; |
95 | } |
96 | |
97 | return 0; |
98 | } |
99 | |
100 | static int cs35l56_sdw_gather_write(void *context, |
101 | const void *reg_buf, size_t reg_size, |
102 | const void *val_buf, size_t val_size) |
103 | { |
104 | struct sdw_slave *peripheral = context; |
105 | const u8 *src_be = val_buf; |
106 | u32 val_le_buf[64]; /* Define u32 so it is 32-bit aligned */ |
107 | unsigned int reg, bytes; |
108 | int ret; |
109 | |
110 | reg = le32_to_cpu(*(const __le32 *)reg_buf); |
111 | reg += CS35L56_SDW_ADDR_OFFSET; |
112 | |
113 | if (val_size == 4) |
114 | return cs35l56_sdw_write_one(peripheral, reg, buf: src_be); |
115 | |
116 | while (val_size) { |
117 | bytes = SDW_REG_NO_PAGE - (reg & SDW_REGADDR); /* to end of page */ |
118 | if (bytes > val_size) |
119 | bytes = val_size; |
120 | if (bytes > sizeof(val_le_buf)) |
121 | bytes = sizeof(val_le_buf); |
122 | |
123 | cs35l56_swab_copy(dest: val_le_buf, src: src_be, nbytes: bytes); |
124 | |
125 | ret = sdw_nwrite_no_pm(slave: peripheral, addr: reg, count: bytes, val: (u8 *)val_le_buf); |
126 | if (ret != 0) { |
127 | dev_err(&peripheral->dev, "Write failed @%#x..%#x:%d\n" , |
128 | reg, reg + bytes - 1, ret); |
129 | return ret; |
130 | } |
131 | |
132 | val_size -= bytes; |
133 | reg += bytes; |
134 | src_be += bytes; |
135 | } |
136 | |
137 | return 0; |
138 | } |
139 | |
140 | static int cs35l56_sdw_write(void *context, const void *val_buf, size_t val_size) |
141 | { |
142 | const u8 *src_buf = val_buf; |
143 | |
144 | /* First word of val_buf contains the destination address */ |
145 | return cs35l56_sdw_gather_write(context, reg_buf: &src_buf[0], reg_size: 4, val_buf: &src_buf[4], val_size: val_size - 4); |
146 | } |
147 | |
148 | /* |
149 | * Registers are big-endian on I2C and SPI but little-endian on SoundWire. |
150 | * Exported firmware controls are big-endian on I2C/SPI but little-endian on |
151 | * SoundWire. Firmware files are always big-endian and are opaque blobs. |
152 | * Present a big-endian regmap and hide the endianness swap, so that the ALSA |
153 | * byte controls always have the same byte order, and firmware file blobs |
154 | * can be written verbatim. |
155 | */ |
156 | static const struct regmap_bus cs35l56_regmap_bus_sdw = { |
157 | .read = cs35l56_sdw_read, |
158 | .write = cs35l56_sdw_write, |
159 | .gather_write = cs35l56_sdw_gather_write, |
160 | .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, |
161 | .val_format_endian_default = REGMAP_ENDIAN_BIG, |
162 | }; |
163 | |
164 | static int cs35l56_sdw_set_cal_index(struct cs35l56_private *cs35l56) |
165 | { |
166 | int ret; |
167 | |
168 | /* SoundWire UniqueId is used to index the calibration array */ |
169 | ret = sdw_read_no_pm(slave: cs35l56->sdw_peripheral, SDW_SCP_DEVID_0); |
170 | if (ret < 0) |
171 | return ret; |
172 | |
173 | cs35l56->base.cal_index = ret & 0xf; |
174 | |
175 | return 0; |
176 | } |
177 | |
178 | static void cs35l56_sdw_init(struct sdw_slave *peripheral) |
179 | { |
180 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev: &peripheral->dev); |
181 | int ret; |
182 | |
183 | pm_runtime_get_noresume(dev: cs35l56->base.dev); |
184 | |
185 | if (cs35l56->base.cal_index < 0) { |
186 | ret = cs35l56_sdw_set_cal_index(cs35l56); |
187 | if (ret < 0) |
188 | goto out; |
189 | } |
190 | |
191 | regcache_cache_only(map: cs35l56->base.regmap, enable: false); |
192 | |
193 | ret = cs35l56_init(cs35l56); |
194 | if (ret < 0) { |
195 | regcache_cache_only(map: cs35l56->base.regmap, enable: true); |
196 | goto out; |
197 | } |
198 | |
199 | /* |
200 | * cs35l56_init can return with !init_done if it triggered |
201 | * a soft reset. |
202 | */ |
203 | if (cs35l56->base.init_done) { |
204 | /* Enable SoundWire interrupts */ |
205 | sdw_write_no_pm(slave: peripheral, CS35L56_SDW_GEN_INT_MASK_1, |
206 | CS35L56_SDW_INT_MASK_CODEC_IRQ); |
207 | } |
208 | |
209 | out: |
210 | pm_runtime_mark_last_busy(dev: cs35l56->base.dev); |
211 | pm_runtime_put_autosuspend(dev: cs35l56->base.dev); |
212 | } |
213 | |
214 | static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, |
215 | struct sdw_slave_intr_status *status) |
216 | { |
217 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev: &peripheral->dev); |
218 | |
219 | /* SoundWire core holds our pm_runtime when calling this function. */ |
220 | |
221 | dev_dbg(cs35l56->base.dev, "int control_port=%#x\n" , status->control_port); |
222 | |
223 | if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) |
224 | return 0; |
225 | |
226 | /* |
227 | * Prevent bus manager suspending and possibly issuing a |
228 | * bus-reset before the queued work has run. |
229 | */ |
230 | pm_runtime_get_noresume(dev: cs35l56->base.dev); |
231 | |
232 | /* |
233 | * Mask and clear until it has been handled. The read of GEN_INT_STAT_1 |
234 | * is required as per the SoundWire spec for interrupt status bits |
235 | * to clear. GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1. |
236 | * None of the interrupts are time-critical so use the |
237 | * power-efficient queue. |
238 | */ |
239 | sdw_write_no_pm(slave: peripheral, CS35L56_SDW_GEN_INT_MASK_1, value: 0); |
240 | sdw_read_no_pm(slave: peripheral, CS35L56_SDW_GEN_INT_STAT_1); |
241 | sdw_write_no_pm(slave: peripheral, CS35L56_SDW_GEN_INT_STAT_1, value: 0xFF); |
242 | queue_work(wq: system_power_efficient_wq, work: &cs35l56->sdw_irq_work); |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static void cs35l56_sdw_irq_work(struct work_struct *work) |
248 | { |
249 | struct cs35l56_private *cs35l56 = container_of(work, |
250 | struct cs35l56_private, |
251 | sdw_irq_work); |
252 | |
253 | cs35l56_irq(irq: -1, data: &cs35l56->base); |
254 | |
255 | /* unmask interrupts */ |
256 | if (!cs35l56->sdw_irq_no_unmask) |
257 | sdw_write_no_pm(slave: cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, |
258 | CS35L56_SDW_INT_MASK_CODEC_IRQ); |
259 | |
260 | pm_runtime_put_autosuspend(dev: cs35l56->base.dev); |
261 | } |
262 | |
263 | static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) |
264 | { |
265 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev: &peripheral->dev); |
266 | struct sdw_slave_prop *prop = &peripheral->prop; |
267 | struct sdw_dpn_prop *ports; |
268 | |
269 | ports = devm_kcalloc(dev: cs35l56->base.dev, n: 2, size: sizeof(*ports), GFP_KERNEL); |
270 | if (!ports) |
271 | return -ENOMEM; |
272 | |
273 | prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); |
274 | prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); |
275 | prop->paging_support = true; |
276 | prop->clk_stop_mode1 = false; |
277 | prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; |
278 | prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF; |
279 | |
280 | /* DP1 - playback */ |
281 | ports[0].num = CS35L56_SDW1_PLAYBACK_PORT; |
282 | ports[0].type = SDW_DPN_FULL; |
283 | ports[0].ch_prep_timeout = 10; |
284 | prop->sink_dpn_prop = &ports[0]; |
285 | |
286 | /* DP3 - capture */ |
287 | ports[1].num = CS35L56_SDW1_CAPTURE_PORT; |
288 | ports[1].type = SDW_DPN_FULL; |
289 | ports[1].ch_prep_timeout = 10; |
290 | prop->src_dpn_prop = &ports[1]; |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | static int cs35l56_sdw_update_status(struct sdw_slave *peripheral, |
296 | enum sdw_slave_status status) |
297 | { |
298 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev: &peripheral->dev); |
299 | |
300 | switch (status) { |
301 | case SDW_SLAVE_ATTACHED: |
302 | dev_dbg(cs35l56->base.dev, "%s: ATTACHED\n" , __func__); |
303 | if (cs35l56->sdw_attached) |
304 | break; |
305 | |
306 | if (!cs35l56->base.init_done || cs35l56->soft_resetting) |
307 | cs35l56_sdw_init(peripheral); |
308 | |
309 | cs35l56->sdw_attached = true; |
310 | break; |
311 | case SDW_SLAVE_UNATTACHED: |
312 | dev_dbg(cs35l56->base.dev, "%s: UNATTACHED\n" , __func__); |
313 | cs35l56->sdw_attached = false; |
314 | break; |
315 | default: |
316 | break; |
317 | } |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | static int cs35l56_a1_kick_divider(struct cs35l56_private *cs35l56, |
323 | struct sdw_slave *peripheral) |
324 | { |
325 | unsigned int curr_scale_reg, next_scale_reg; |
326 | int curr_scale, next_scale, ret; |
327 | |
328 | if (!cs35l56->base.init_done) |
329 | return 0; |
330 | |
331 | if (peripheral->bus->params.curr_bank) { |
332 | curr_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B1; |
333 | next_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B0; |
334 | } else { |
335 | curr_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B0; |
336 | next_scale_reg = SDW_SCP_BUSCLOCK_SCALE_B1; |
337 | } |
338 | |
339 | /* |
340 | * Current clock scale value must be different to new value. |
341 | * Modify current to guarantee this. If next still has the dummy |
342 | * value we wrote when it was current, the core code has not set |
343 | * a new scale so restore its original good value |
344 | */ |
345 | curr_scale = sdw_read_no_pm(slave: peripheral, addr: curr_scale_reg); |
346 | if (curr_scale < 0) { |
347 | dev_err(cs35l56->base.dev, "Failed to read current clock scale: %d\n" , curr_scale); |
348 | return curr_scale; |
349 | } |
350 | |
351 | next_scale = sdw_read_no_pm(slave: peripheral, addr: next_scale_reg); |
352 | if (next_scale < 0) { |
353 | dev_err(cs35l56->base.dev, "Failed to read next clock scale: %d\n" , next_scale); |
354 | return next_scale; |
355 | } |
356 | |
357 | if (next_scale == CS35L56_SDW_INVALID_BUS_SCALE) { |
358 | next_scale = cs35l56->old_sdw_clock_scale; |
359 | ret = sdw_write_no_pm(slave: peripheral, addr: next_scale_reg, value: next_scale); |
360 | if (ret < 0) { |
361 | dev_err(cs35l56->base.dev, "Failed to modify current clock scale: %d\n" , |
362 | ret); |
363 | return ret; |
364 | } |
365 | } |
366 | |
367 | cs35l56->old_sdw_clock_scale = curr_scale; |
368 | ret = sdw_write_no_pm(slave: peripheral, addr: curr_scale_reg, CS35L56_SDW_INVALID_BUS_SCALE); |
369 | if (ret < 0) { |
370 | dev_err(cs35l56->base.dev, "Failed to modify current clock scale: %d\n" , ret); |
371 | return ret; |
372 | } |
373 | |
374 | dev_dbg(cs35l56->base.dev, "Next bus scale: %#x\n" , next_scale); |
375 | |
376 | return 0; |
377 | } |
378 | |
379 | static int cs35l56_sdw_bus_config(struct sdw_slave *peripheral, |
380 | struct sdw_bus_params *params) |
381 | { |
382 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev: &peripheral->dev); |
383 | int sclk; |
384 | |
385 | sclk = params->curr_dr_freq / 2; |
386 | dev_dbg(cs35l56->base.dev, "%s: sclk=%u c=%u r=%u\n" , |
387 | __func__, sclk, params->col, params->row); |
388 | |
389 | if ((cs35l56->base.type == 0x56) && (cs35l56->base.rev < 0xb0)) |
390 | return cs35l56_a1_kick_divider(cs35l56, peripheral); |
391 | |
392 | return 0; |
393 | } |
394 | |
395 | static int __maybe_unused cs35l56_sdw_clk_stop(struct sdw_slave *peripheral, |
396 | enum sdw_clk_stop_mode mode, |
397 | enum sdw_clk_stop_type type) |
398 | { |
399 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev: &peripheral->dev); |
400 | |
401 | dev_dbg(cs35l56->base.dev, "%s: mode:%d type:%d\n" , __func__, mode, type); |
402 | |
403 | return 0; |
404 | } |
405 | |
406 | static const struct sdw_slave_ops cs35l56_sdw_ops = { |
407 | .read_prop = cs35l56_sdw_read_prop, |
408 | .interrupt_callback = cs35l56_sdw_interrupt, |
409 | .update_status = cs35l56_sdw_update_status, |
410 | .bus_config = cs35l56_sdw_bus_config, |
411 | #ifdef DEBUG |
412 | .clk_stop = cs35l56_sdw_clk_stop, |
413 | #endif |
414 | }; |
415 | |
416 | static int __maybe_unused cs35l56_sdw_handle_unattach(struct cs35l56_private *cs35l56) |
417 | { |
418 | struct sdw_slave *peripheral = cs35l56->sdw_peripheral; |
419 | |
420 | if (peripheral->unattach_request) { |
421 | /* Cannot access registers until bus is re-initialized. */ |
422 | dev_dbg(cs35l56->base.dev, "Wait for initialization_complete\n" ); |
423 | if (!wait_for_completion_timeout(x: &peripheral->initialization_complete, |
424 | timeout: msecs_to_jiffies(m: 5000))) { |
425 | dev_err(cs35l56->base.dev, "initialization_complete timed out\n" ); |
426 | return -ETIMEDOUT; |
427 | } |
428 | |
429 | peripheral->unattach_request = 0; |
430 | |
431 | /* |
432 | * Don't call regcache_mark_dirty(), we can't be sure that the |
433 | * Manager really did issue a Bus Reset. |
434 | */ |
435 | } |
436 | |
437 | return 0; |
438 | } |
439 | |
440 | static int __maybe_unused cs35l56_sdw_runtime_suspend(struct device *dev) |
441 | { |
442 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); |
443 | |
444 | if (!cs35l56->base.init_done) |
445 | return 0; |
446 | |
447 | return cs35l56_runtime_suspend_common(cs35l56_base: &cs35l56->base); |
448 | } |
449 | |
450 | static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) |
451 | { |
452 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); |
453 | int ret; |
454 | |
455 | dev_dbg(dev, "Runtime resume\n" ); |
456 | |
457 | if (!cs35l56->base.init_done) |
458 | return 0; |
459 | |
460 | ret = cs35l56_sdw_handle_unattach(cs35l56); |
461 | if (ret < 0) |
462 | return ret; |
463 | |
464 | ret = cs35l56_runtime_resume_common(cs35l56_base: &cs35l56->base, is_soundwire: true); |
465 | if (ret) |
466 | return ret; |
467 | |
468 | /* Re-enable SoundWire interrupts */ |
469 | sdw_write_no_pm(slave: cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, |
470 | CS35L56_SDW_INT_MASK_CODEC_IRQ); |
471 | |
472 | return 0; |
473 | } |
474 | |
475 | static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) |
476 | { |
477 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); |
478 | |
479 | if (!cs35l56->base.init_done) |
480 | return 0; |
481 | |
482 | /* |
483 | * Disable SoundWire interrupts. |
484 | * Flush - don't cancel because that could leave an unbalanced pm_runtime_get. |
485 | */ |
486 | cs35l56->sdw_irq_no_unmask = true; |
487 | flush_work(work: &cs35l56->sdw_irq_work); |
488 | |
489 | /* Mask interrupts and flush in case sdw_irq_work was queued again */ |
490 | sdw_write_no_pm(slave: cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, value: 0); |
491 | sdw_read_no_pm(slave: cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1); |
492 | sdw_write_no_pm(slave: cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, value: 0xFF); |
493 | flush_work(work: &cs35l56->sdw_irq_work); |
494 | |
495 | return cs35l56_system_suspend(dev); |
496 | } |
497 | |
498 | static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev) |
499 | { |
500 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); |
501 | |
502 | cs35l56->sdw_irq_no_unmask = false; |
503 | /* runtime_resume re-enables the interrupt */ |
504 | |
505 | return cs35l56_system_resume(dev); |
506 | } |
507 | |
508 | static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id) |
509 | { |
510 | struct device *dev = &peripheral->dev; |
511 | struct cs35l56_private *cs35l56; |
512 | int ret; |
513 | |
514 | cs35l56 = devm_kzalloc(dev, size: sizeof(*cs35l56), GFP_KERNEL); |
515 | if (!cs35l56) |
516 | return -ENOMEM; |
517 | |
518 | cs35l56->base.dev = dev; |
519 | cs35l56->sdw_peripheral = peripheral; |
520 | INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); |
521 | |
522 | dev_set_drvdata(dev, data: cs35l56); |
523 | |
524 | cs35l56->base.regmap = devm_regmap_init(dev, &cs35l56_regmap_bus_sdw, |
525 | peripheral, &cs35l56_regmap_sdw); |
526 | if (IS_ERR(ptr: cs35l56->base.regmap)) { |
527 | ret = PTR_ERR(ptr: cs35l56->base.regmap); |
528 | return dev_err_probe(dev, err: ret, fmt: "Failed to allocate register map\n" ); |
529 | } |
530 | |
531 | /* Start in cache-only until device is enumerated */ |
532 | regcache_cache_only(map: cs35l56->base.regmap, enable: true); |
533 | |
534 | ret = cs35l56_common_probe(cs35l56); |
535 | if (ret != 0) |
536 | return ret; |
537 | |
538 | return 0; |
539 | } |
540 | |
541 | static int cs35l56_sdw_remove(struct sdw_slave *peripheral) |
542 | { |
543 | struct cs35l56_private *cs35l56 = dev_get_drvdata(dev: &peripheral->dev); |
544 | |
545 | /* Disable SoundWire interrupts */ |
546 | cs35l56->sdw_irq_no_unmask = true; |
547 | cancel_work_sync(work: &cs35l56->sdw_irq_work); |
548 | sdw_write_no_pm(slave: peripheral, CS35L56_SDW_GEN_INT_MASK_1, value: 0); |
549 | sdw_read_no_pm(slave: peripheral, CS35L56_SDW_GEN_INT_STAT_1); |
550 | sdw_write_no_pm(slave: peripheral, CS35L56_SDW_GEN_INT_STAT_1, value: 0xFF); |
551 | |
552 | cs35l56_remove(cs35l56); |
553 | |
554 | return 0; |
555 | } |
556 | |
557 | static const struct dev_pm_ops cs35l56_sdw_pm = { |
558 | SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL) |
559 | SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume) |
560 | LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early) |
561 | /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */ |
562 | }; |
563 | |
564 | static const struct sdw_device_id cs35l56_sdw_id[] = { |
565 | SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0), |
566 | SDW_SLAVE_ENTRY(0x01FA, 0x3557, 0), |
567 | {}, |
568 | }; |
569 | MODULE_DEVICE_TABLE(sdw, cs35l56_sdw_id); |
570 | |
571 | static struct sdw_driver cs35l56_sdw_driver = { |
572 | .driver = { |
573 | .name = "cs35l56" , |
574 | .pm = pm_ptr(&cs35l56_sdw_pm), |
575 | }, |
576 | .probe = cs35l56_sdw_probe, |
577 | .remove = cs35l56_sdw_remove, |
578 | .ops = &cs35l56_sdw_ops, |
579 | .id_table = cs35l56_sdw_id, |
580 | }; |
581 | |
582 | module_sdw_driver(cs35l56_sdw_driver); |
583 | |
584 | MODULE_DESCRIPTION("ASoC CS35L56 SoundWire driver" ); |
585 | MODULE_IMPORT_NS(SND_SOC_CS35L56_CORE); |
586 | MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED); |
587 | MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>" ); |
588 | MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>" ); |
589 | MODULE_LICENSE("GPL" ); |
590 | |