blob: f7af584c9ad4c288ad0e8eeb0c7819bb8252e75e [file] [log] [blame]
//! Driver for the MLX90614 Infrared Thermometer.
//!
//! SMBus Interface
//!
//! Usage
//! -----
//!
//! ```rust
//! let mux_i2c = components::i2c::I2CMuxComponent::new(&earlgrey::i2c::I2C)
//! .finalize(components::i2c_mux_component_helper!());
//!
//! let mlx90614 = components::mlx90614::Mlx90614I2CComponent::new()
//! .finalize(components::mlx90614_i2c_component_helper!(mux_i2c));
//! ```
//!
use crate::driver;
use core::cell::Cell;
use enum_primitive::cast::FromPrimitive;
use enum_primitive::enum_from_primitive;
use kernel::common::cells::{OptionalCell, TakeCell};
use kernel::common::registers::register_bitfields;
use kernel::hil::i2c::{self, Error};
use kernel::hil::sensors;
use kernel::{AppId, Callback, Driver, ReturnCode};
/// Syscall driver number.
pub const DRIVER_NUM: usize = driver::NUM::Mlx90614 as usize;
register_bitfields![u16,
CONFIG [
IIR OFFSET(0) NUMBITS(3) [],
DUAL OFFSET(6) NUMBITS(1) [],
FIR OFFSET(8) NUMBITS(3) [],
GAIN OFFSET(11) NUMBITS(3) []
]
];
#[derive(Clone, Copy, PartialEq, Debug)]
enum State {
Idle,
IsPresent,
ReadAmbientTemp,
ReadObjTemp,
}
enum_from_primitive! {
enum Mlx90614Registers {
RAW1 = 0x04,
RAW2 = 0x05,
TA = 0x06,
TOBJ1 = 0x07,
TOBJ2 = 0x08,
EMISSIVITY = 0x24,
CONFIG = 0x25,
}
}
pub struct Mlx90614SMBus<'a> {
smbus_temp: &'a dyn i2c::SMBusDevice,
callback: OptionalCell<Callback>,
temperature_client: OptionalCell<&'a dyn sensors::TemperatureClient>,
buffer: TakeCell<'static, [u8]>,
state: Cell<State>,
}
impl<'a> Mlx90614SMBus<'_> {
pub fn new(
smbus_temp: &'a dyn i2c::SMBusDevice,
buffer: &'static mut [u8],
) -> Mlx90614SMBus<'a> {
Mlx90614SMBus {
smbus_temp,
callback: OptionalCell::empty(),
temperature_client: OptionalCell::empty(),
buffer: TakeCell::new(buffer),
state: Cell::new(State::Idle),
}
}
fn is_present(&self) {
self.state.set(State::IsPresent);
self.buffer.take().map(|buf| {
// turn on i2c to send commands
buf[0] = Mlx90614Registers::RAW1 as u8;
self.smbus_temp.smbus_write_read(buf, 1, 1).unwrap();
});
}
fn read_ambient_temperature(&self) {
self.state.set(State::ReadAmbientTemp);
self.buffer.take().map(|buf| {
buf[0] = Mlx90614Registers::TA as u8;
self.smbus_temp.smbus_write_read(buf, 1, 1).unwrap();
});
}
fn read_object_temperature(&self) {
self.state.set(State::ReadObjTemp);
self.buffer.take().map(|buf| {
buf[0] = Mlx90614Registers::TOBJ1 as u8;
self.smbus_temp.smbus_write_read(buf, 1, 2).unwrap();
});
}
}
impl<'a> i2c::I2CClient for Mlx90614SMBus<'a> {
fn command_complete(&self, buffer: &'static mut [u8], error: Error) {
match self.state.get() {
State::Idle => {
self.buffer.replace(buffer);
}
State::IsPresent => {
let present = if error == Error::CommandComplete && buffer[0] == 60 {
true
} else {
false
};
self.callback.map(|callback| {
callback.schedule(if present { 1 } else { 0 }, 0, 0);
});
self.buffer.replace(buffer);
self.state.set(State::Idle);
}
State::ReadAmbientTemp | State::ReadObjTemp => {
let mut temp: usize = 0;
let values = if error == Error::CommandComplete {
// Convert to centi celsius
temp = ((buffer[0] as usize | (buffer[1] as usize) << 8) * 2) - 27300;
self.temperature_client.map(|client| {
client.callback(temp as usize);
});
true
} else {
self.temperature_client.map(|client| {
client.callback(0);
});
false
};
if values {
self.callback.map(|callback| {
callback.schedule(temp, 0, 0);
});
} else {
self.callback.map(|callback| {
callback.schedule(0, 0, 0);
});
}
self.buffer.replace(buffer);
self.state.set(State::Idle);
}
}
}
}
impl<'a> Driver for Mlx90614SMBus<'a> {
fn command(&self, command_num: usize, _data1: usize, _data2: usize, _: AppId) -> ReturnCode {
match command_num {
0 => ReturnCode::SUCCESS,
// Check is sensor is correctly connected
1 => {
if self.state.get() == State::Idle {
self.is_present();
ReturnCode::SUCCESS
} else {
ReturnCode::EBUSY
}
}
// Read Ambient Temperature
2 => {
if self.state.get() == State::Idle {
self.read_ambient_temperature();
ReturnCode::SUCCESS
} else {
ReturnCode::EBUSY
}
}
// Read Object Temperature
3 => {
if self.state.get() == State::Idle {
self.read_object_temperature();
ReturnCode::SUCCESS
} else {
ReturnCode::EBUSY
}
}
// default
_ => ReturnCode::ENOSUPPORT,
}
}
fn subscribe(
&self,
subscribe_num: usize,
callback: Option<Callback>,
_app_id: AppId,
) -> ReturnCode {
match subscribe_num {
0 /* set the one shot callback */ => {
self.callback.insert(callback);
ReturnCode::SUCCESS
},
// default
_ => ReturnCode::ENOSUPPORT,
}
}
}
impl<'a> sensors::TemperatureDriver<'a> for Mlx90614SMBus<'a> {
fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) {
self.temperature_client.replace(temperature_client);
}
fn read_temperature(&self) -> ReturnCode {
self.read_object_temperature();
ReturnCode::SUCCESS
}
}