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}