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