capsules_extra/
ltc294x.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 LTC294X line of coulomb counters.
6//!
7//! - <http://www.linear.com/product/LTC2941>
8//! - <http://www.linear.com/product/LTC2942>
9//! - <http://www.linear.com/product/LTC2943>
10//!
11//! > The LTC2941 measures battery charge state in battery-supplied handheld PC
12//! > and portable product applications. Its operating range is perfectly suited
13//! > for single-cell Li-Ion batteries. A precision coulomb counter integrates
14//! > current through a sense resistor between the battery’s positive terminal
15//! > and the load or charger. The measured charge is stored in internal
16//! > registers. An SMBus/I2C interface accesses and configures the device.
17//!
18//! Structure
19//! ---------
20//!
21//! This file implements the LTC294X driver in two objects. First is the
22//! `LTC294X` struct. This implements all of the actual logic for the
23//! chip. The second is the `LTC294XDriver` struct. This implements the
24//! userland facing syscall interface. These are split to allow the kernel
25//! to potentially interface with the LTC294X chip rather than only provide
26//! it to userspace.
27//!
28//! Usage
29//! -----
30//!
31//! Here is a sample usage of this capsule in a board's main.rs file:
32//!
33//! ```rust,ignore
34//! # use kernel::static_init;
35//!
36//! let buffer = static_init!([u8; capsules::ltc294x::BUF_LEN], [0; capsules::ltc294x::BUF_LEN]);
37//! let ltc294x_i2c = static_init!(
38//!     capsules::virtual_i2c::I2CDevice,
39//!     capsules::virtual_i2c::I2CDevice::new(i2c_mux, 0x64));
40//! let ltc294x = static_init!(
41//!     capsules::ltc294x::LTC294X<'static>,
42//!     capsules::ltc294x::LTC294X::new(ltc294x_i2c, None, buffer));
43//! ltc294x_i2c.set_client(ltc294x);
44//!
45//! // Optionally create the object that provides an interface for the coulomb
46//! // counter for applications.
47//! let ltc294x_driver = static_init!(
48//!     capsules::ltc294x::LTC294XDriver<'static>,
49//!     capsules::ltc294x::LTC294XDriver::new(ltc294x));
50//! ltc294x.set_client(ltc294x_driver);
51//! ```
52
53use core::cell::Cell;
54
55use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
56use kernel::hil::gpio;
57use kernel::hil::i2c;
58use kernel::syscall::{CommandReturn, SyscallDriver};
59use kernel::utilities::cells::{OptionalCell, TakeCell};
60use kernel::{ErrorCode, ProcessId};
61
62/// Syscall driver number.
63use capsules_core::driver;
64pub const DRIVER_NUM: usize = driver::NUM::Ltc294x as usize;
65
66pub const BUF_LEN: usize = 20;
67
68#[allow(dead_code)]
69enum Registers {
70    Status = 0x00,
71    Control = 0x01,
72    AccumulatedChargeMSB = 0x02,
73    AccumulatedChargeLSB = 0x03,
74    ChargeThresholdHighMSB = 0x04,
75    ChargeThresholdHighLSB = 0x05,
76    ChargeThresholdLowMSB = 0x06,
77    ChargeThresholdLowLSB = 0x07,
78    VoltageMSB = 0x08,
79    VoltageLSB = 0x09,
80    CurrentMSB = 0x0E,
81    CurrentLSB = 0x0F,
82}
83
84#[derive(Clone, Copy, PartialEq)]
85enum State {
86    Idle,
87
88    /// Simple read states
89    ReadStatus,
90    ReadCharge,
91    ReadVoltage,
92    ReadCurrent,
93    ReadShutdown,
94
95    Done,
96}
97
98/// Which version of the chip we are actually using.
99#[derive(Clone, Copy)]
100pub enum ChipModel {
101    LTC2941 = 1,
102    LTC2942 = 2,
103    LTC2943 = 3,
104}
105
106/// Settings for which interrupt we want.
107pub enum InterruptPinConf {
108    Disabled = 0x00,
109    ChargeCompleteMode = 0x01,
110    AlertMode = 0x02,
111}
112
113/// Threshold options for battery alerts.
114pub enum VBatAlert {
115    Off = 0x00,
116    Threshold2V8 = 0x01,
117    Threshold2V9 = 0x02,
118    Threshold3V0 = 0x03,
119}
120
121#[derive(Default)]
122pub struct App {}
123
124/// Supported events for the LTC294X.
125pub trait LTC294XClient {
126    fn interrupt(&self);
127    fn status(
128        &self,
129        undervolt_lockout: bool,
130        vbat_alert: bool,
131        charge_alert_low: bool,
132        charge_alert_high: bool,
133        accumulated_charge_overflow: bool,
134    );
135    fn charge(&self, charge: u16);
136    fn voltage(&self, voltage: u16);
137    fn current(&self, current: u16);
138    fn done(&self);
139}
140
141/// Implementation of a driver for the LTC294X coulomb counters.
142pub struct LTC294X<'a, I: i2c::I2CDevice> {
143    i2c: &'a I,
144    interrupt_pin: Option<&'a dyn gpio::InterruptPin<'a>>,
145    model: Cell<ChipModel>,
146    state: Cell<State>,
147    buffer: TakeCell<'static, [u8]>,
148    client: OptionalCell<&'static dyn LTC294XClient>,
149}
150
151impl<'a, I: i2c::I2CDevice> LTC294X<'a, I> {
152    pub fn new(
153        i2c: &'a I,
154        interrupt_pin: Option<&'a dyn gpio::InterruptPin<'a>>,
155        buffer: &'static mut [u8],
156    ) -> LTC294X<'a, I> {
157        LTC294X {
158            i2c,
159            interrupt_pin,
160            model: Cell::new(ChipModel::LTC2941),
161            state: Cell::new(State::Idle),
162            buffer: TakeCell::new(buffer),
163            client: OptionalCell::empty(),
164        }
165    }
166
167    pub fn set_client<C: LTC294XClient>(&self, client: &'static C) {
168        self.client.set(client);
169
170        self.interrupt_pin.map(|interrupt_pin| {
171            interrupt_pin.make_input();
172            interrupt_pin.enable_interrupts(gpio::InterruptEdge::FallingEdge);
173        });
174    }
175
176    pub fn read_status(&self) -> Result<(), ErrorCode> {
177        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
178            self.i2c.enable();
179
180            // Address pointer automatically resets to the status register.
181            // TODO verify errors
182            let _ = self.i2c.read(buffer, 1);
183            self.state.set(State::ReadStatus);
184
185            Ok(())
186        })
187    }
188
189    fn configure(
190        &self,
191        int_pin_conf: InterruptPinConf,
192        prescaler: u8,
193        vbat_alert: VBatAlert,
194    ) -> Result<(), ErrorCode> {
195        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
196            self.i2c.enable();
197
198            buffer[0] = Registers::Control as u8;
199            buffer[1] = ((int_pin_conf as u8) << 1) | (prescaler << 3) | ((vbat_alert as u8) << 6);
200
201            // TODO verify errors
202            let _ = self.i2c.write(buffer, 2);
203            self.state.set(State::Done);
204
205            Ok(())
206        })
207    }
208
209    /// Set the accumulated charge to 0
210    fn reset_charge(&self) -> Result<(), ErrorCode> {
211        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
212            self.i2c.enable();
213
214            buffer[0] = Registers::AccumulatedChargeMSB as u8;
215            buffer[1] = 0;
216            buffer[2] = 0;
217
218            // TODO verify errors
219            let _ = self.i2c.write(buffer, 3);
220            self.state.set(State::Done);
221
222            Ok(())
223        })
224    }
225
226    fn set_high_threshold(&self, threshold: u16) -> Result<(), ErrorCode> {
227        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
228            self.i2c.enable();
229
230            buffer[0] = Registers::ChargeThresholdHighMSB as u8;
231            buffer[1] = ((threshold & 0xFF00) >> 8) as u8;
232            buffer[2] = (threshold & 0xFF) as u8;
233
234            // TODO verify errors
235            let _ = self.i2c.write(buffer, 3);
236            self.state.set(State::Done);
237
238            Ok(())
239        })
240    }
241
242    fn set_low_threshold(&self, threshold: u16) -> Result<(), ErrorCode> {
243        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
244            self.i2c.enable();
245
246            buffer[0] = Registers::ChargeThresholdLowMSB as u8;
247            buffer[1] = ((threshold & 0xFF00) >> 8) as u8;
248            buffer[2] = (threshold & 0xFF) as u8;
249
250            // TODO verify errors
251            let _ = self.i2c.write(buffer, 3);
252            self.state.set(State::Done);
253
254            Ok(())
255        })
256    }
257
258    /// Get the cumulative charge as measured by the LTC2941.
259    fn get_charge(&self) -> Result<(), ErrorCode> {
260        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
261            self.i2c.enable();
262
263            // Read all of the first four registers rather than wasting
264            // time writing an address.
265            // TODO verify errors
266            let _ = self.i2c.read(buffer, 4);
267            self.state.set(State::ReadCharge);
268
269            Ok(())
270        })
271    }
272
273    /// Get the voltage at sense+
274    fn get_voltage(&self) -> Result<(), ErrorCode> {
275        // Not supported on all versions
276        match self.model.get() {
277            ChipModel::LTC2942 | ChipModel::LTC2943 => {
278                self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
279                    self.i2c.enable();
280
281                    // TODO verify errors
282                    let _ = self.i2c.read(buffer, 10);
283                    self.state.set(State::ReadVoltage);
284
285                    Ok(())
286                })
287            }
288            _ => Err(ErrorCode::NOSUPPORT),
289        }
290    }
291
292    /// Get the current sensed by the resistor
293    fn get_current(&self) -> Result<(), ErrorCode> {
294        // Not supported on all versions
295        match self.model.get() {
296            ChipModel::LTC2943 => self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
297                self.i2c.enable();
298
299                // TODO verify errors
300                let _ = self.i2c.read(buffer, 16);
301                self.state.set(State::ReadCurrent);
302
303                Ok(())
304            }),
305            _ => Err(ErrorCode::NOSUPPORT),
306        }
307    }
308
309    /// Put the LTC294X in a low power state.
310    fn shutdown(&self) -> Result<(), ErrorCode> {
311        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
312            self.i2c.enable();
313
314            // Read both the status and control register rather than
315            // writing an address.
316            // TODO verify errors
317            let _ = self.i2c.read(buffer, 2);
318            self.state.set(State::ReadShutdown);
319
320            Ok(())
321        })
322    }
323
324    /// Set the LTC294X model actually on the board.
325    fn set_model(&self, model_num: usize) -> Result<(), ErrorCode> {
326        match model_num {
327            1 => {
328                self.model.set(ChipModel::LTC2941);
329                Ok(())
330            }
331            2 => {
332                self.model.set(ChipModel::LTC2942);
333                Ok(())
334            }
335            3 => {
336                self.model.set(ChipModel::LTC2943);
337                Ok(())
338            }
339            _ => Err(ErrorCode::NODEVICE),
340        }
341    }
342}
343
344impl<I: i2c::I2CDevice> i2c::I2CClient for LTC294X<'_, I> {
345    fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) {
346        match self.state.get() {
347            State::ReadStatus => {
348                let status = buffer[0];
349                let uvlock = (status & 0x01) > 0;
350                let vbata = (status & 0x02) > 0;
351                let ca_low = (status & 0x04) > 0;
352                let ca_high = (status & 0x08) > 0;
353                let accover = (status & 0x20) > 0;
354                self.client.map(|client| {
355                    client.status(uvlock, vbata, ca_low, ca_high, accover);
356                });
357
358                self.buffer.replace(buffer);
359                self.i2c.disable();
360                self.state.set(State::Idle);
361            }
362            State::ReadCharge => {
363                // Charge is calculated in user space
364                let charge = ((buffer[2] as u16) << 8) | (buffer[3] as u16);
365                self.client.map(|client| {
366                    client.charge(charge);
367                });
368
369                self.buffer.replace(buffer);
370                self.i2c.disable();
371                self.state.set(State::Idle);
372            }
373            State::ReadVoltage => {
374                let voltage = ((buffer[8] as u16) << 8) | (buffer[9] as u16);
375                self.client.map(|client| {
376                    client.voltage(voltage);
377                });
378
379                self.buffer.replace(buffer);
380                self.i2c.disable();
381                self.state.set(State::Idle);
382            }
383            State::ReadCurrent => {
384                let current = ((buffer[14] as u16) << 8) | (buffer[15] as u16);
385                self.client.map(|client| {
386                    client.current(current);
387                });
388
389                self.buffer.replace(buffer);
390                self.i2c.disable();
391                self.state.set(State::Idle);
392            }
393            State::ReadShutdown => {
394                // Set the shutdown pin to 1
395                buffer[1] |= 0x01;
396
397                // Write the control register back but with a 1 in the shutdown
398                // bit.
399                buffer[0] = Registers::Control as u8;
400                // TODO verify errors
401                let _ = self.i2c.write(buffer, 2);
402                self.state.set(State::Done);
403            }
404            State::Done => {
405                self.client.map(|client| {
406                    client.done();
407                });
408
409                self.buffer.replace(buffer);
410                self.i2c.disable();
411                self.state.set(State::Idle);
412            }
413            _ => {}
414        }
415    }
416}
417
418impl<I: i2c::I2CDevice> gpio::Client for LTC294X<'_, I> {
419    fn fired(&self) {
420        self.client.map(|client| {
421            client.interrupt();
422        });
423    }
424}
425
426/// IDs for subscribed upcalls.
427mod upcall {
428    /// The callback that that is triggered when events finish and when readings
429    /// are ready. The first argument represents which callback was triggered.
430    ///
431    /// - `0`: Interrupt occurred from the LTC294X.
432    /// - `1`: Got the status.
433    /// - `2`: Read the charge used.
434    /// - `3`: `done()` was called.
435    /// - `4`: Read the voltage.
436    /// - `5`: Read the current.
437    pub const EVENT_FINISHED: usize = 0;
438    /// Number of upcalls.
439    pub const COUNT: u8 = 1;
440}
441
442/// Default implementation of the LTC2941 driver that provides a Driver
443/// interface for providing access to applications.
444pub struct LTC294XDriver<'a, I: i2c::I2CDevice> {
445    ltc294x: &'a LTC294X<'a, I>,
446    grants: Grant<App, UpcallCount<{ upcall::COUNT }>, AllowRoCount<0>, AllowRwCount<0>>,
447    owning_process: OptionalCell<ProcessId>,
448}
449
450impl<'a, I: i2c::I2CDevice> LTC294XDriver<'a, I> {
451    pub fn new(
452        ltc: &'a LTC294X<'a, I>,
453        grants: Grant<App, UpcallCount<{ upcall::COUNT }>, AllowRoCount<0>, AllowRwCount<0>>,
454    ) -> LTC294XDriver<'a, I> {
455        LTC294XDriver {
456            ltc294x: ltc,
457            grants,
458            owning_process: OptionalCell::empty(),
459        }
460    }
461}
462
463impl<I: i2c::I2CDevice> LTC294XClient for LTC294XDriver<'_, I> {
464    fn interrupt(&self) {
465        self.owning_process.map(|pid| {
466            let _res = self.grants.enter(pid, |_app, upcalls| {
467                upcalls
468                    .schedule_upcall(upcall::EVENT_FINISHED, (0, 0, 0))
469                    .ok();
470            });
471        });
472    }
473
474    fn status(
475        &self,
476        undervolt_lockout: bool,
477        vbat_alert: bool,
478        charge_alert_low: bool,
479        charge_alert_high: bool,
480        accumulated_charge_overflow: bool,
481    ) {
482        let ret = (undervolt_lockout as usize)
483            | ((vbat_alert as usize) << 1)
484            | ((charge_alert_low as usize) << 2)
485            | ((charge_alert_high as usize) << 3)
486            | ((accumulated_charge_overflow as usize) << 4);
487        self.owning_process.map(|pid| {
488            let _res = self.grants.enter(pid, |_app, upcalls| {
489                upcalls
490                    .schedule_upcall(
491                        upcall::EVENT_FINISHED,
492                        (1, ret, self.ltc294x.model.get() as usize),
493                    )
494                    .ok();
495            });
496        });
497    }
498
499    fn charge(&self, charge: u16) {
500        self.owning_process.map(|pid| {
501            let _res = self.grants.enter(pid, |_app, upcalls| {
502                upcalls
503                    .schedule_upcall(upcall::EVENT_FINISHED, (2, charge as usize, 0))
504                    .ok();
505            });
506        });
507    }
508
509    fn done(&self) {
510        self.owning_process.map(|pid| {
511            let _res = self.grants.enter(pid, |_app, upcalls| {
512                upcalls
513                    .schedule_upcall(upcall::EVENT_FINISHED, (3, 0, 0))
514                    .ok();
515            });
516        });
517    }
518
519    fn voltage(&self, voltage: u16) {
520        self.owning_process.map(|pid| {
521            let _res = self.grants.enter(pid, |_app, upcalls| {
522                upcalls
523                    .schedule_upcall(upcall::EVENT_FINISHED, (4, voltage as usize, 0))
524                    .ok();
525            });
526        });
527    }
528
529    fn current(&self, current: u16) {
530        self.owning_process.map(|pid| {
531            let _res = self.grants.enter(pid, |_app, upcalls| {
532                upcalls
533                    .schedule_upcall(upcall::EVENT_FINISHED, (5, current as usize, 0))
534                    .ok();
535            });
536        });
537    }
538}
539
540impl<I: i2c::I2CDevice> SyscallDriver for LTC294XDriver<'_, I> {
541    /// Request operations for the LTC294X chip.
542    ///
543    /// ### `command_num`
544    ///
545    /// - `0`: Driver existence check.
546    /// - `1`: Get status of the chip.
547    /// - `2`: Configure settings of the chip.
548    /// - `3`: Reset accumulated charge measurement to zero.
549    /// - `4`: Set the upper threshold for charge.
550    /// - `5`: Set the lower threshold for charge.
551    /// - `6`: Get the current charge accumulated.
552    /// - `7`: Shutdown the chip.
553    /// - `8`: Get the voltage reading. Only supported on the LTC2942 and
554    ///   LTC2943.
555    /// - `9`: Get the current reading. Only supported on the LTC2943.
556    /// - `10`: Set the model of the LTC294X actually being used. `data` is the
557    ///   value of the X.
558    fn command(
559        &self,
560        command_num: usize,
561        data: usize,
562        _: usize,
563        process_id: ProcessId,
564    ) -> CommandReturn {
565        if command_num == 0 {
566            // Handle this first as it should be returned
567            // unconditionally
568            return CommandReturn::success();
569        }
570
571        let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| {
572            self.grants
573                .enter(current_process, |_, _| current_process == process_id)
574                .unwrap_or(true)
575        });
576        if match_or_empty_or_nonexistant {
577            self.owning_process.set(process_id);
578        } else {
579            return CommandReturn::failure(ErrorCode::NOMEM);
580        }
581
582        match command_num {
583            // Get status.
584            1 => self.ltc294x.read_status().into(),
585
586            // Configure.
587            2 => {
588                let int_pin_raw = data & 0x03;
589                let prescaler = (data >> 2) & 0x07;
590                let vbat_raw = (data >> 5) & 0x03;
591                let int_pin_conf = match int_pin_raw {
592                    0 => InterruptPinConf::Disabled,
593                    1 => InterruptPinConf::ChargeCompleteMode,
594                    2 => InterruptPinConf::AlertMode,
595                    _ => InterruptPinConf::Disabled,
596                };
597                let vbat_alert = match vbat_raw {
598                    0 => VBatAlert::Off,
599                    1 => VBatAlert::Threshold2V8,
600                    2 => VBatAlert::Threshold2V9,
601                    3 => VBatAlert::Threshold3V0,
602                    _ => VBatAlert::Off,
603                };
604
605                self.ltc294x
606                    .configure(int_pin_conf, prescaler as u8, vbat_alert)
607                    .into()
608            }
609
610            // Reset charge.
611            3 => self.ltc294x.reset_charge().into(),
612
613            // Set high threshold
614            4 => self.ltc294x.set_high_threshold(data as u16).into(),
615
616            // Set low threshold
617            5 => self.ltc294x.set_low_threshold(data as u16).into(),
618
619            // Get charge
620            6 => self.ltc294x.get_charge().into(),
621
622            // Shutdown
623            7 => self.ltc294x.shutdown().into(),
624
625            // Get voltage
626            8 => self.ltc294x.get_voltage().into(),
627
628            // Get current
629            9 => self.ltc294x.get_current().into(),
630
631            // Set the current chip model
632            10 => self.ltc294x.set_model(data).into(),
633
634            // default
635            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
636        }
637    }
638
639    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
640        self.grants.enter(processid, |_, _| {})
641    }
642}