wm8731_another_hal/
lib.rs

1//! A thin HAL to control and configure the Cirrus Logic/Wolfson WM8731 audio codec
2//!
3//! This crate provide abstractions to communicate with the WM8731
4#![doc = concat!("[Software Control Interface](",
5    env!("CARGO_MANIFEST_DIR"),
6    "/WM8731_v4.9.pdf#%5B%7B%22num%22%3A141%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22FitH%22%7D%2C560%5D)")]
7//! and edit content of
8#![doc = concat!("[Control Registers](",
9    env!("CARGO_MANIFEST_DIR"),
10    "/WM8731_v4.9.pdf#%5B%7B%22num%22%3A139%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22FitH%22%7D%2C697%5D).")]
11//! Look [Wm8731] documentation to know how to control the codec.
12//!
13//! Part of this documentation refers to WM8731 datasheets. Copy are available in the project
14//! repository.
15#![no_std]
16#![doc(html_root_url = "https://docs.rs/embedded-hal/0.2.0/")]
17
18pub mod interface;
19pub mod prelude;
20pub mod registers;
21
22use interface::WriteFrame;
23
24#[doc(inline)]
25pub use registers::analogue_audio_path::{InselV, SideAttdB};
26#[doc(inline)]
27pub use registers::digital_audio_interface::{FormatV, IwlV, MsV};
28#[doc(inline)]
29pub use registers::digital_audio_path::DeempV;
30#[doc(inline)]
31pub use registers::headphone_out::HpVoldB;
32#[doc(inline)]
33pub use registers::line_in::InVoldB;
34#[doc(inline)]
35pub use registers::sampling::SamplingRates;
36
37use registers::active::Active;
38use registers::analogue_audio_path::AnalogueAudioPath;
39use registers::digital_audio_interface::DigitalAudioInterface;
40use registers::digital_audio_path::DigitalAudioPath;
41use registers::headphone_out::LeftHeadphoneOut;
42use registers::headphone_out::RightHeadphoneOut;
43use registers::line_in::LeftLineIn;
44use registers::line_in::RightLineIn;
45use registers::power_down::PowerDown;
46use registers::reset::Reset;
47use registers::sampling::Sampling;
48
49/// Wm8731 Control Interface Abstraction.
50///
51/// This type provide abstraction of Wm8731 Control Interface. The control is mainly done through
52/// getters and setters methods giving access to a field of a control register. Since control
53/// registers are write only, this type actually mirror registers content to simulate read/write
54/// operation to a particular field.
55///
56/// # Build an instance
57///
58/// Building a `Wm8731` instance is a two step operation. First, you need to instantiate an
59/// interface handling communication details, and then you use this interface to instantiate
60/// a `Wm8731` object. When you have a SPI or an I2C object implementing embeded_hal blocking
61/// write, this is actually fast to do.
62///
63/// ## Building with an I2C interface
64///
65/// In the following example, `i2c1` implements [`embedded_hal::blocking::i2c::Write`]
66/// ```
67/// use wm8731_another_hal::prelude::*;
68///
69/// let address = 0b001_1010; //Wm8731 address when CSB is low
70/// let wm8731 = Wm8731::new(I2CInterface::new(i2c1, address));
71/// ```
72///
73/// ## Building with a SPI interface
74///
75/// In the following example, `spi1` implements [`embedded_hal::blocking::spi::Write<u8>`] and `cs_pin`
76/// implements [`embedded_hal::digital::v2::OutputPin`]
77/// ```
78/// use wm8731_another_hal::prelude::*;
79///
80/// let wm8731 = Wm8731::new(SPIInterfaceU8::new(spi1, cs_pin));
81/// ```
82/// # Usage
83///
84/// In general, A Wm8731 object just gives you access to a control register field through one
85/// getter and one setter method. Getter is just the field name in lower case and setter is the
86/// field name prefixed with `set_`. So, `set_mutemic(true)` mute the mic at ADC input. However
87/// there is some exception:
88///  - fields affecting line and headphone volume, to provide more appropriate methods and to allow
89/// code factorisation internally.
90///  - `USB\NORMAL`, `BOSR` and `SR` fields. They are replace by [`SamplingRates`] to prevent invalid
91///  setting.
92///
93///  Please also note that all setters affecting Digital Audio Interface Format and Sampling Control
94///  register don't change field content when the device is active. This is done to prevent
95///  synchronisation issues.
96///
97///  And finally, to know effect of each field, consult the
98#[doc = concat!("[Datasheet page 50](", env!("CARGO_MANIFEST_DIR"), "/WM8731_v4.9.pdf#page=50).")]
99#[derive(Debug)]
100pub struct Wm8731<I>
101where
102    I: WriteFrame,
103{
104    interface: I,
105    left_line_in: LeftLineIn,
106    right_line_in: RightLineIn,
107    left_hpvol: HpVoldB,
108    right_hpvol: HpVoldB,
109    analogue_audio_path: AnalogueAudioPath,
110    digital_audio_path: DigitalAudioPath,
111    power_down: PowerDown,
112    digital_audio_interface: DigitalAudioInterface,
113    sampling: Sampling,
114    active: Active,
115}
116
117/// Constructor and Destructor.
118impl<I> Wm8731<I>
119where
120    I: WriteFrame,
121{
122    ///Instantiate a driver. This also reset the codec to guarantee a known coherent state.
123    pub fn new(interface: I) -> Self {
124        let mut codec = Self {
125            interface,
126            left_line_in: Default::default(),
127            right_line_in: Default::default(),
128            left_hpvol: Default::default(),
129            right_hpvol: Default::default(),
130            analogue_audio_path: Default::default(),
131            digital_audio_path: Default::default(),
132            power_down: Default::default(),
133            digital_audio_interface: Default::default(),
134            sampling: Default::default(),
135            active: Default::default(),
136        };
137        codec.interface.write(Reset::new().to_frame());
138        codec
139    }
140
141    /// Destroy the driver and release it's serial interface abstraction.
142    pub fn release(self) -> I {
143        self.interface
144    }
145}
146
147/// Active Control and Reset
148impl<I> Wm8731<I>
149where
150    I: WriteFrame,
151{
152    /// Returns `true` if the digital audio interface is running.
153    pub fn is_active(&self) -> bool {
154        self.active.get()
155    }
156
157    /// Activate digital audio interface.
158    pub fn activate(&mut self) {
159        self.interface
160            .write(self.digital_audio_interface.to_frame());
161        self.interface.write(self.sampling.to_frame());
162        self.active.set(true);
163        self.interface.write(self.active.to_frame());
164    }
165    /// Deactivate digital audio interface.
166    pub fn deactivate(&mut self) {
167        self.active.set(false);
168        self.interface.write(self.active.to_frame());
169    }
170
171    /// Reset the codec. All configuration is lost.
172    pub fn reset(&mut self) {
173        self.interface.write(Reset::new().to_frame());
174        self.left_line_in = Default::default();
175        self.right_line_in = Default::default();
176        self.left_hpvol = Default::default();
177        self.right_hpvol = Default::default();
178        self.analogue_audio_path = Default::default();
179        self.digital_audio_path = Default::default();
180        self.power_down = Default::default();
181        self.digital_audio_interface = Default::default();
182        self.sampling = Default::default();
183        self.active = Default::default();
184    }
185}
186
187/// Left and Right Line In.
188impl<I> Wm8731<I>
189where
190    I: WriteFrame,
191{
192    pub fn left_invol(&self) -> InVoldB {
193        self.left_line_in.vol()
194    }
195
196    pub fn right_invol(&self) -> InVoldB {
197        self.right_line_in.vol()
198    }
199
200    pub fn both_invol(&self) -> (InVoldB, InVoldB) {
201        (self.left_line_in.vol(), self.right_line_in.vol())
202    }
203
204    pub fn left_inmute(&self) -> bool {
205        self.left_line_in.mute()
206    }
207
208    pub fn right_inmute(&self) -> bool {
209        self.right_line_in.mute()
210    }
211
212    pub fn both_inmute(&self) -> (bool, bool) {
213        (self.left_line_in.mute(), self.right_line_in.mute())
214    }
215
216    pub fn set_left_invol(&mut self, volume: InVoldB) {
217        self.left_line_in.set_vol(volume);
218        self.left_line_in.set_both(false);
219        self.interface.write(self.left_line_in.to_frame());
220    }
221
222    pub fn set_right_invol(&mut self, volume: InVoldB) {
223        self.right_line_in.set_vol(volume);
224        self.right_line_in.set_both(false);
225        self.interface.write(self.right_line_in.to_frame());
226    }
227
228    pub fn set_both_invol(&mut self, volume: InVoldB) {
229        self.left_line_in.set_vol(volume);
230        self.right_line_in.set_vol(volume);
231        self.left_line_in.set_both(true);
232        self.interface.write(self.left_line_in.to_frame());
233    }
234
235    pub fn set_left_inmute(&mut self, mute: bool) {
236        self.left_line_in.set_mute(mute);
237        self.left_line_in.set_both(false);
238        self.interface.write(self.left_line_in.to_frame());
239    }
240
241    pub fn set_right_inmute(&mut self, mute: bool) {
242        self.right_line_in.set_mute(mute);
243        self.right_line_in.set_both(false);
244        self.interface.write(self.right_line_in.to_frame());
245    }
246
247    pub fn set_both_inmute(&mut self, mute: bool) {
248        self.left_line_in.set_mute(mute);
249        self.right_line_in.set_mute(mute);
250        self.left_line_in.set_both(true);
251        self.interface.write(self.left_line_in.to_frame());
252    }
253}
254
255/// Left and Right Headphone Out.
256impl<I> Wm8731<I>
257where
258    I: WriteFrame,
259{
260    pub fn left_hpvol(&self) -> HpVoldB {
261        self.left_hpvol
262    }
263
264    pub fn right_hpvol(&self) -> HpVoldB {
265        self.right_hpvol
266    }
267
268    pub fn both_hpvol(&self) -> (HpVoldB, HpVoldB) {
269        (self.left_hpvol, self.right_hpvol)
270    }
271
272    /// Set left headphone out volume.
273    ///
274    /// When `zcen` is `false`, volume is changed immediately.
275    ///
276    /// When `zcen` is `true`, volume is set when signal is close to zero to avoid audible
277    /// noise. The volume may never change if signal at gain stage input get never close to +/-
278    /// 20mv.
279    pub fn set_left_hpvol(&mut self, volume: HpVoldB, zcen: bool) {
280        self.left_hpvol = volume;
281        self.interface.write(
282            LeftHeadphoneOut::default()
283                .set_both(false)
284                .set_zcen(zcen)
285                .set_vol(volume)
286                .to_frame(),
287        );
288    }
289
290    /// Set right headphone out volume.
291    ///
292    /// When `zcen` is `false`, volume is changed immediately.
293    ///
294    /// When `zcen` is `true`, volume is set when signal is close to zero to avoid audible
295    /// noise. The volume may never change if signal at gain stage input get never close to +/-
296    /// 20mv.
297    pub fn set_right_hpvol(&mut self, volume: HpVoldB, zcen: bool) {
298        self.right_hpvol = volume;
299        self.interface.write(
300            RightHeadphoneOut::default()
301                .set_both(false)
302                .set_zcen(zcen)
303                .set_vol(volume)
304                .to_frame(),
305        );
306    }
307
308    /// Set both headphone out volume.
309    ///
310    /// When `zcen` is `false`, volume is changed immediately.
311    ///
312    /// When `zcen` is `true`, volume is set when signal is close to zero to avoid audible
313    /// noise. The volume may never change if signal at gain stage input get never close to +/-
314    /// 20mv.
315    pub fn set_both_hpvol(&mut self, volume: HpVoldB, zcen: bool) {
316        self.left_hpvol = volume;
317        self.right_hpvol = volume;
318        self.interface.write(
319            LeftHeadphoneOut::default()
320                .set_both(true)
321                .set_zcen(zcen)
322                .set_vol(volume)
323                .to_frame(),
324        );
325    }
326}
327
328/// Analogue Audio Path Control.
329impl<I> Wm8731<I>
330where
331    I: WriteFrame,
332{
333    pub fn micboost(&self) -> bool {
334        self.analogue_audio_path.micboost()
335    }
336
337    pub fn mutemic(&self) -> bool {
338        self.analogue_audio_path.mutemic()
339    }
340
341    pub fn insel(&self) -> InselV {
342        self.analogue_audio_path.insel()
343    }
344
345    pub fn bypass(&self) -> bool {
346        self.analogue_audio_path.bypass()
347    }
348
349    pub fn dacsel(&self) -> bool {
350        self.analogue_audio_path.dacsel()
351    }
352
353    pub fn sidetone(&self) -> bool {
354        self.analogue_audio_path.sidetone()
355    }
356
357    pub fn sideatt(&self) -> SideAttdB {
358        self.analogue_audio_path.sideatt()
359    }
360
361    pub fn set_micboost(&mut self, value: bool) {
362        self.analogue_audio_path.set_micboost(value);
363        self.interface.write(self.analogue_audio_path.to_frame());
364    }
365
366    pub fn set_mutemic(&mut self, value: bool) {
367        self.analogue_audio_path.set_mutemic(value);
368        self.interface.write(self.analogue_audio_path.to_frame());
369    }
370
371    pub fn set_insel(&mut self, value: InselV) {
372        self.analogue_audio_path.set_insel(value);
373        self.interface.write(self.analogue_audio_path.to_frame());
374    }
375
376    pub fn set_bypass(&mut self, value: bool) {
377        self.analogue_audio_path.set_bypass(value);
378        self.interface.write(self.analogue_audio_path.to_frame());
379    }
380
381    pub fn set_dacsel(&mut self, value: bool) {
382        self.analogue_audio_path.set_dacsel(value);
383        self.interface.write(self.analogue_audio_path.to_frame());
384    }
385
386    pub fn set_sidetone(&mut self, value: bool) {
387        self.analogue_audio_path.set_sidetone(value);
388        self.interface.write(self.analogue_audio_path.to_frame());
389    }
390
391    pub fn set_sideatt(&mut self, value: SideAttdB) {
392        self.analogue_audio_path.set_sideatt(value);
393        self.interface.write(self.analogue_audio_path.to_frame());
394    }
395}
396
397/// Digital Audio Path Control.
398impl<I> Wm8731<I>
399where
400    I: WriteFrame,
401{
402    ///  `true` means ADC high pass filter disabled. `false` means ADC high pass filter enabled.
403    pub fn adchpd(&self) -> bool {
404        self.digital_audio_path.adchpd()
405    }
406
407    pub fn deemp(&self) -> DeempV {
408        self.digital_audio_path.deemp()
409    }
410
411    pub fn dacmu(&self) -> bool {
412        self.digital_audio_path.dacmu()
413    }
414
415    pub fn hpor(&self) -> bool {
416        self.digital_audio_path.hpor()
417    }
418
419    /// Disable/Enable ADC high pass filter. `true` to disable it, `false` to enable it.
420    pub fn set_adchpd(&mut self, value: bool) {
421        self.digital_audio_path.set_adchpd(value);
422        self.interface.write(self.digital_audio_path.to_frame());
423    }
424
425    /// Disable or select a de-emphasis filter. It's up to user to choose the correct value.
426    ///
427    /// When using de-emphasis, the correct value of `DEEMP` should match the actual DAC sampling
428    /// frequency. It's up to user to choose the correct value because actual sampling
429    /// frequency depends on clocks and this HAL doesn't know about clocks. Setting a wrong value
430    /// is not unsafe, it just apply a filter that doesn't conform
431    /// with CD de-emphasis.
432    pub fn set_deemp(&mut self, value: DeempV) {
433        self.digital_audio_path.set_deemp(value);
434        self.interface.write(self.digital_audio_path.to_frame());
435    }
436
437    /// DAC Soft Mute Control. Doesn't work correctly with some sampling configurations.
438    ///
439    /// DAC Soft Mute Control doesn't work correctly when `SR` is `0b0111` or `0b1111`. This concern
440    /// sampling configurations where `core clock` / `sampling frequency` is less or equal to
441    /// 192.
442    pub fn set_dacmu(&mut self, value: bool) {
443        self.digital_audio_path.set_dacmu(value);
444        self.interface.write(self.digital_audio_path.to_frame());
445    }
446
447    pub fn set_hpor(&mut self, value: bool) {
448        self.digital_audio_path.set_hpor(value);
449        self.interface.write(self.digital_audio_path.to_frame());
450    }
451}
452
453/// Power Down Control.
454impl<I> Wm8731<I>
455where
456    I: WriteFrame,
457{
458    pub fn lineinpd(&self) -> bool {
459        self.power_down.lineinpd()
460    }
461    pub fn micpd(&self) -> bool {
462        self.power_down.micpd()
463    }
464    pub fn adcpd(&self) -> bool {
465        self.power_down.adcpd()
466    }
467    pub fn dacpd(&self) -> bool {
468        self.power_down.dacpd()
469    }
470    pub fn outpd(&self) -> bool {
471        self.power_down.outpd()
472    }
473    pub fn oscpd(&self) -> bool {
474        self.power_down.oscpd()
475    }
476    pub fn clkoutpd(&self) -> bool {
477        self.power_down.clkoutpd()
478    }
479    pub fn poweroff(&self) -> bool {
480        self.power_down.poweroff()
481    }
482
483    pub fn set_lineinpd(&mut self, value: bool) {
484        self.power_down.set_lineinpd(value);
485        self.interface.write(self.power_down.to_frame());
486    }
487    pub fn set_micpd(&mut self, value: bool) {
488        self.power_down.set_micpd(value);
489        self.interface.write(self.power_down.to_frame());
490    }
491    pub fn set_adcpd(&mut self, value: bool) {
492        self.power_down.set_adcpd(value);
493        self.interface.write(self.power_down.to_frame());
494    }
495    pub fn set_dacpd(&mut self, value: bool) {
496        self.power_down.set_dacpd(value);
497        self.interface.write(self.power_down.to_frame());
498    }
499    pub fn set_outpd(&mut self, value: bool) {
500        self.power_down.set_outpd(value);
501        self.interface.write(self.power_down.to_frame());
502    }
503    pub fn set_oscpd(&mut self, value: bool) {
504        self.power_down.set_oscpd(value);
505        self.interface.write(self.power_down.to_frame());
506    }
507    pub fn set_clkoutpd(&mut self, value: bool) {
508        self.power_down.set_clkoutpd(value);
509        self.interface.write(self.power_down.to_frame());
510    }
511    pub fn set_poweroff(&mut self, value: bool) {
512        self.power_down.set_poweroff(value);
513        self.interface.write(self.power_down.to_frame());
514    }
515}
516
517/// Digital Audio Interface Format. Value stored only if inactive, sent during activation.
518impl<I> Wm8731<I>
519where
520    I: WriteFrame,
521{
522    pub fn format(&self) -> FormatV {
523        self.digital_audio_interface.format()
524    }
525    pub fn iwl(&self) -> IwlV {
526        self.digital_audio_interface.iwl()
527    }
528    pub fn lrp(&self) -> bool {
529        self.digital_audio_interface.lrp()
530    }
531    pub fn lrswap(&self) -> bool {
532        self.digital_audio_interface.lrswap()
533    }
534    pub fn ms(&self) -> MsV {
535        self.digital_audio_interface.ms()
536    }
537    pub fn bclkinv(&self) -> bool {
538        self.digital_audio_interface.bclkinv()
539    }
540
541    pub fn set_format(&mut self, value: FormatV) {
542        if !self.active.get() {
543            self.digital_audio_interface.set_format(value);
544        }
545    }
546    pub fn set_iwl(&mut self, value: IwlV) {
547        if !self.active.get() {
548            self.digital_audio_interface.set_iwl(value);
549        }
550    }
551    pub fn set_lrp(&mut self, value: bool) {
552        if !self.active.get() {
553            self.digital_audio_interface.set_lrp(value);
554        }
555    }
556    pub fn set_lrswap(&mut self, value: bool) {
557        if !self.active.get() {
558            self.digital_audio_interface.set_lrswap(value);
559        }
560    }
561    pub fn set_ms(&mut self, value: MsV) {
562        if !self.active.get() {
563            self.digital_audio_interface.set_ms(value);
564        }
565    }
566    pub fn set_bclkinv(&mut self, value: bool) {
567        if !self.active.get() {
568            self.digital_audio_interface.set_bclkinv(value);
569        }
570    }
571}
572
573/// Sampling Control. Value stored only if inactive, sent only during activation.
574impl<I> Wm8731<I>
575where
576    I: WriteFrame,
577{
578    /// Get Sampling Rates configuration.
579    pub fn sampling_rates(&self) -> SamplingRates {
580        self.sampling.sampling_rates()
581    }
582    pub fn clkidiv2(&self) -> bool {
583        self.sampling.clkidiv2()
584    }
585    pub fn clkodiv2(&self) -> bool {
586        self.sampling.clkodiv2()
587    }
588
589    /// Set Sampling Rates.
590    pub fn set_sampling_rates(&mut self, value: SamplingRates) {
591        if !self.active.get() {
592            self.sampling.set_sampling_rates(value);
593        }
594    }
595    pub fn set_clkidiv2(&mut self, value: bool) {
596        if !self.active.get() {
597            self.sampling.set_clkidiv2(value);
598        }
599    }
600    pub fn set_clkodiv2(&mut self, value: bool) {
601        if !self.active.get() {
602            self.sampling.set_clkodiv2(value);
603        }
604    }
605}