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}