capsules_extra/
mlx90614.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
5//! SyscallDriver for the MLX90614 Infrared Thermometer.
6//!
7//! SMBus Interface
8//!
9//! Usage
10//! -----
11//!
12//! ```rust,ignore
13//! let mux_i2c = components::i2c::I2CMuxComponent::new(&earlgrey::i2c::I2C)
14//!     .finalize(components::i2c_mux_component_helper!());
15//!
16//! let mlx90614 = components::mlx90614::Mlx90614I2CComponent::new()
17//!    .finalize(components::mlx90614_i2c_component_helper!(mux_i2c));
18//! ```
19//!
20
21use core::cell::Cell;
22
23use enum_primitive::cast::FromPrimitive;
24use enum_primitive::enum_from_primitive;
25
26use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
27use kernel::hil::i2c;
28use kernel::hil::sensors;
29use kernel::syscall::{CommandReturn, SyscallDriver};
30use kernel::utilities::cells::{OptionalCell, TakeCell};
31use kernel::utilities::registers::register_bitfields;
32use kernel::{ErrorCode, ProcessId};
33
34use capsules_core::driver;
35
36/// Syscall driver number.
37pub const DRIVER_NUM: usize = driver::NUM::Mlx90614 as usize;
38
39register_bitfields![u16,
40    CONFIG [
41        IIR OFFSET(0) NUMBITS(3) [],
42        DUAL OFFSET(6) NUMBITS(1) [],
43        FIR OFFSET(8) NUMBITS(3) [],
44        GAIN OFFSET(11) NUMBITS(3) []
45    ]
46];
47
48#[derive(Clone, Copy, PartialEq, Debug)]
49enum State {
50    Idle,
51    IsPresent,
52    ReadAmbientTemp,
53    ReadObjTemp,
54}
55
56enum_from_primitive! {
57    enum Mlx90614Registers {
58        RAW1 = 0x04,
59        RAW2 = 0x05,
60        TA = 0x06,
61        TOBJ1 = 0x07,
62        TOBJ2 = 0x08,
63        EMISSIVITY = 0x24,
64        CONFIG = 0x25,
65    }
66}
67
68#[derive(Default)]
69pub struct App {}
70
71pub struct Mlx90614SMBus<'a, S: i2c::SMBusDevice> {
72    smbus_temp: &'a S,
73    temperature_client: OptionalCell<&'a dyn sensors::TemperatureClient>,
74    buffer: TakeCell<'static, [u8]>,
75    state: Cell<State>,
76    apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
77    owning_process: OptionalCell<ProcessId>,
78}
79
80impl<'a, S: i2c::SMBusDevice> Mlx90614SMBus<'a, S> {
81    pub fn new(
82        smbus_temp: &'a S,
83        buffer: &'static mut [u8],
84        grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
85    ) -> Mlx90614SMBus<'a, S> {
86        Mlx90614SMBus {
87            smbus_temp,
88            temperature_client: OptionalCell::empty(),
89            buffer: TakeCell::new(buffer),
90            state: Cell::new(State::Idle),
91            apps: grant,
92            owning_process: OptionalCell::empty(),
93        }
94    }
95
96    fn is_present(&self) {
97        self.state.set(State::IsPresent);
98        self.buffer.take().map(|buf| {
99            // turn on i2c to send commands
100            buf[0] = Mlx90614Registers::RAW1 as u8;
101            self.smbus_temp.smbus_write_read(buf, 1, 1).unwrap();
102        });
103    }
104
105    fn read_ambient_temperature(&self) {
106        self.state.set(State::ReadAmbientTemp);
107        self.buffer.take().map(|buf| {
108            buf[0] = Mlx90614Registers::TA as u8;
109            self.smbus_temp.smbus_write_read(buf, 1, 1).unwrap();
110        });
111    }
112
113    fn read_object_temperature(&self) {
114        self.state.set(State::ReadObjTemp);
115        self.buffer.take().map(|buf| {
116            buf[0] = Mlx90614Registers::TOBJ1 as u8;
117            self.smbus_temp.smbus_write_read(buf, 1, 2).unwrap();
118        });
119    }
120}
121
122impl<S: i2c::SMBusDevice> i2c::I2CClient for Mlx90614SMBus<'_, S> {
123    fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) {
124        match self.state.get() {
125            State::Idle => {
126                self.buffer.replace(buffer);
127            }
128            State::IsPresent => {
129                let present = status.is_ok() && buffer[0] == 60;
130
131                self.owning_process.map(|pid| {
132                    let _ = self.apps.enter(pid, |_app, upcalls| {
133                        upcalls
134                            .schedule_upcall(0, (usize::from(present), 0, 0))
135                            .ok();
136                    });
137                });
138                self.buffer.replace(buffer);
139                self.state.set(State::Idle);
140            }
141            State::ReadAmbientTemp | State::ReadObjTemp => {
142                let values = match status {
143                    Ok(()) =>
144                    // Convert to centi celsius
145                    {
146                        Ok(((buffer[0] as usize | (buffer[1] as usize) << 8) * 2) as i32 - 27300)
147                    }
148                    Err(i2c_error) => Err(i2c_error.into()),
149                };
150                self.temperature_client.map(|client| {
151                    client.callback(values);
152                });
153                if let Ok(temp) = values {
154                    self.owning_process.map(|pid| {
155                        let _ = self.apps.enter(pid, |_app, upcalls| {
156                            upcalls.schedule_upcall(0, (temp as usize, 0, 0)).ok();
157                        });
158                    });
159                } else {
160                    self.owning_process.map(|pid| {
161                        let _ = self.apps.enter(pid, |_app, upcalls| {
162                            upcalls.schedule_upcall(0, (0, 0, 0)).ok();
163                        });
164                    });
165                }
166                self.buffer.replace(buffer);
167                self.state.set(State::Idle);
168            }
169        }
170    }
171}
172
173impl<S: i2c::SMBusDevice> SyscallDriver for Mlx90614SMBus<'_, S> {
174    fn command(
175        &self,
176        command_num: usize,
177        _data1: usize,
178        _data2: usize,
179        process_id: ProcessId,
180    ) -> CommandReturn {
181        if command_num == 0 {
182            // Handle this first as it should be returned
183            // unconditionally
184            return CommandReturn::success();
185        }
186        // Check if this non-virtualized driver is already in use by
187        // some (alive) process
188        let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| {
189            self.apps
190                .enter(current_process, |_, _| current_process == process_id)
191                .unwrap_or(true)
192        });
193        if match_or_empty_or_nonexistant {
194            self.owning_process.set(process_id);
195        } else {
196            return CommandReturn::failure(ErrorCode::NOMEM);
197        }
198
199        match command_num {
200            0 => CommandReturn::success(),
201            // Check is sensor is correctly connected
202            1 => {
203                if self.state.get() == State::Idle {
204                    self.is_present();
205                    CommandReturn::success()
206                } else {
207                    CommandReturn::failure(ErrorCode::BUSY)
208                }
209            }
210            // Read Ambient Temperature
211            2 => {
212                if self.state.get() == State::Idle {
213                    self.read_ambient_temperature();
214                    CommandReturn::success()
215                } else {
216                    CommandReturn::failure(ErrorCode::BUSY)
217                }
218            }
219            // Read Object Temperature
220            3 => {
221                if self.state.get() == State::Idle {
222                    self.read_object_temperature();
223                    CommandReturn::success()
224                } else {
225                    CommandReturn::failure(ErrorCode::BUSY)
226                }
227            }
228            // default
229            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
230        }
231    }
232
233    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
234        self.apps.enter(processid, |_, _| {})
235    }
236}
237
238impl<'a, S: i2c::SMBusDevice> sensors::TemperatureDriver<'a> for Mlx90614SMBus<'a, S> {
239    fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) {
240        self.temperature_client.replace(temperature_client);
241    }
242
243    fn read_temperature(&self) -> Result<(), ErrorCode> {
244        self.read_object_temperature();
245        Ok(())
246    }
247}