capsules_extra/
pwm.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
6use kernel::hil;
7use kernel::syscall::{CommandReturn, SyscallDriver};
8use kernel::utilities::cells::OptionalCell;
9use kernel::{ErrorCode, ProcessId};
10
11/// Syscall driver number.
12use capsules_core::driver;
13pub const DRIVER_NUM: usize = driver::NUM::Pwm as usize;
14
15// An empty app, for potential uses in future updates of the driver
16#[derive(Default)]
17pub struct App;
18
19pub struct Pwm<'a, const NUM_PINS: usize> {
20    /// The usable pwm pins.
21    pwm_pins: &'a [&'a dyn hil::pwm::PwmPin; NUM_PINS],
22    /// Per-app state.
23    apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
24    /// An array of apps associated to their reserved pins.
25    active_process: [OptionalCell<ProcessId>; NUM_PINS],
26}
27
28impl<'a, const NUM_PINS: usize> Pwm<'a, NUM_PINS> {
29    pub fn new(
30        pwm_pins: &'a [&'a dyn hil::pwm::PwmPin; NUM_PINS],
31        grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
32    ) -> Pwm<'a, NUM_PINS> {
33        assert!(u16::try_from(NUM_PINS).is_ok());
34        const EMPTY: OptionalCell<ProcessId> = OptionalCell::empty();
35        Pwm {
36            pwm_pins,
37            apps: grant,
38            active_process: [EMPTY; NUM_PINS],
39        }
40    }
41
42    pub fn claim_pin(&self, processid: ProcessId, pin: usize) -> bool {
43        // Attempt to get the app that is using the pin.
44        self.active_process[pin].map_or(true, |id| {
45            // If the app is empty, that means that there is no app currently using this pin,
46            // therefore the pin could be usable by the new app
47            if id == processid {
48                // The same app is trying to access the pin it has access to, valid
49                true
50            } else {
51                // An app is trying to access another app's pin, invalid
52                false
53            }
54        })
55    }
56
57    pub fn release_pin(&self, pin: usize) {
58        // Release the claimed pin so that it can now be used by another process.
59        self.active_process[pin].clear();
60    }
61}
62
63/// Provide an interface for userland.
64impl<const NUM_PINS: usize> SyscallDriver for Pwm<'_, NUM_PINS> {
65    /// Command interface.
66    ///
67    /// ### `command_num`
68    ///
69    /// - `0`: Driver existence check.
70    /// - `1`: Start the PWM pin output. First 16 bits of `data1` are used for
71    ///   the duty cycle, as a percentage with 2 decimals, and the last 16 bits
72    ///   of `data1` are used for the PWM channel to be controlled. `data2` is
73    ///   used for the frequency in hertz. For the duty cycle, 100% is the max
74    ///   duty cycle for this pin.
75    /// - `2`: Stop the PWM output.
76    /// - `3`: Return the maximum possible frequency for this pin.
77    /// - `4`: Return number of PWM pins if this driver is included on the platform.
78    fn command(
79        &self,
80        command_num: usize,
81        data1: usize,
82        data2: usize,
83        processid: ProcessId,
84    ) -> CommandReturn {
85        match command_num {
86            // Check existence.
87            0 => CommandReturn::success(),
88
89            // Start the pwm output.
90
91            // data1 stores the duty cycle and the pin number in the format
92            // +------------------+------------------+
93            // | duty cycle (u16) |   pwm pin (u16)  |
94            // +------------------+------------------+
95            // This format was chosen because there are only 2 parameters in the command function that can be used for storing values,
96            // but in this case, 3 values are needed (pin, frequency, duty cycle), so data1 stores two of these values that can be
97            // represented using only 16 bits.
98            1 => {
99                let pin = data1 & ((1 << 16) - 1);
100                let duty_cycle = data1 >> 16;
101                let frequency_hz = data2;
102
103                if pin >= NUM_PINS {
104                    // App asked to use a pin that doesn't exist.
105                    CommandReturn::failure(ErrorCode::INVAL)
106                } else {
107                    if !self.claim_pin(processid, pin) {
108                        // App cannot claim pin.
109                        CommandReturn::failure(ErrorCode::RESERVE)
110                    } else {
111                        // App can claim pin, start pwm pin at given frequency and duty_cycle.
112                        self.active_process[pin].set(processid);
113                        // Duty cycle is represented as a 4 digit number, so we divide by 10000 to get the percentage of the max duty cycle.
114                        // e.g.: a duty cycle of 60.5% is represented as 6050, so the actual value of the duty cycle is
115                        // 6050 * max_duty_cycle / 10000 = 0.605 * max_duty_cycle
116                        self.pwm_pins[pin]
117                            .start(
118                                frequency_hz,
119                                duty_cycle * self.pwm_pins[pin].get_maximum_duty_cycle() / 10000,
120                            )
121                            .into()
122                    }
123                }
124            }
125
126            // Stop the PWM output.
127            2 => {
128                let pin = data1;
129                if pin >= NUM_PINS {
130                    // App asked to use a pin that doesn't exist.
131                    CommandReturn::failure(ErrorCode::INVAL)
132                } else {
133                    if !self.claim_pin(processid, pin) {
134                        // App cannot claim pin.
135                        CommandReturn::failure(ErrorCode::RESERVE)
136                    } else if self.active_process[pin].is_none() {
137                        // If there is no active app, the pwm pin isn't in use.
138                        CommandReturn::failure(ErrorCode::OFF)
139                    } else {
140                        // Release the pin and stop pwm output.
141                        self.release_pin(pin);
142                        self.pwm_pins[pin].stop().into()
143                    }
144                }
145            }
146
147            // Get max frequency of pin.
148            3 => {
149                let pin = data1;
150                if pin >= NUM_PINS {
151                    CommandReturn::failure(ErrorCode::INVAL)
152                } else {
153                    CommandReturn::success_u32(self.pwm_pins[pin].get_maximum_frequency_hz() as u32)
154                }
155            }
156
157            // Return number of usable PWM pins.
158            4 => CommandReturn::success_u32(NUM_PINS as u32),
159
160            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
161        }
162    }
163
164    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
165        self.apps.enter(processid, |_, _| {})
166    }
167}