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                let _ = upcalls.schedule_upcall(upcall::EVENT_FINISHED, (0, 0, 0));
468            });
469        });
470    }
471
472    fn status(
473        &self,
474        undervolt_lockout: bool,
475        vbat_alert: bool,
476        charge_alert_low: bool,
477        charge_alert_high: bool,
478        accumulated_charge_overflow: bool,
479    ) {
480        let ret = (undervolt_lockout as usize)
481            | ((vbat_alert as usize) << 1)
482            | ((charge_alert_low as usize) << 2)
483            | ((charge_alert_high as usize) << 3)
484            | ((accumulated_charge_overflow as usize) << 4);
485        self.owning_process.map(|pid| {
486            let _res = self.grants.enter(pid, |_app, upcalls| {
487                let _ = upcalls.schedule_upcall(
488                    upcall::EVENT_FINISHED,
489                    (1, ret, self.ltc294x.model.get() as usize),
490                );
491            });
492        });
493    }
494
495    fn charge(&self, charge: u16) {
496        self.owning_process.map(|pid| {
497            let _res = self.grants.enter(pid, |_app, upcalls| {
498                let _ = upcalls.schedule_upcall(upcall::EVENT_FINISHED, (2, charge as usize, 0));
499            });
500        });
501    }
502
503    fn done(&self) {
504        self.owning_process.map(|pid| {
505            let _res = self.grants.enter(pid, |_app, upcalls| {
506                let _ = upcalls.schedule_upcall(upcall::EVENT_FINISHED, (3, 0, 0));
507            });
508        });
509    }
510
511    fn voltage(&self, voltage: u16) {
512        self.owning_process.map(|pid| {
513            let _res = self.grants.enter(pid, |_app, upcalls| {
514                let _ = upcalls.schedule_upcall(upcall::EVENT_FINISHED, (4, voltage as usize, 0));
515            });
516        });
517    }
518
519    fn current(&self, current: u16) {
520        self.owning_process.map(|pid| {
521            let _res = self.grants.enter(pid, |_app, upcalls| {
522                let _ = upcalls.schedule_upcall(upcall::EVENT_FINISHED, (5, current as usize, 0));
523            });
524        });
525    }
526}
527
528impl<I: i2c::I2CDevice> SyscallDriver for LTC294XDriver<'_, I> {
529    /// Request operations for the LTC294X chip.
530    ///
531    /// ### `command_num`
532    ///
533    /// - `0`: Driver existence check.
534    /// - `1`: Get status of the chip.
535    /// - `2`: Configure settings of the chip.
536    /// - `3`: Reset accumulated charge measurement to zero.
537    /// - `4`: Set the upper threshold for charge.
538    /// - `5`: Set the lower threshold for charge.
539    /// - `6`: Get the current charge accumulated.
540    /// - `7`: Shutdown the chip.
541    /// - `8`: Get the voltage reading. Only supported on the LTC2942 and
542    ///   LTC2943.
543    /// - `9`: Get the current reading. Only supported on the LTC2943.
544    /// - `10`: Set the model of the LTC294X actually being used. `data` is the
545    ///   value of the X.
546    fn command(
547        &self,
548        command_num: usize,
549        data: usize,
550        _: usize,
551        process_id: ProcessId,
552    ) -> CommandReturn {
553        if command_num == 0 {
554            // Handle this first as it should be returned
555            // unconditionally
556            return CommandReturn::success();
557        }
558
559        let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| {
560            self.grants
561                .enter(current_process, |_, _| current_process == process_id)
562                .unwrap_or(true)
563        });
564        if match_or_empty_or_nonexistant {
565            self.owning_process.set(process_id);
566        } else {
567            return CommandReturn::failure(ErrorCode::NOMEM);
568        }
569
570        match command_num {
571            // Get status.
572            1 => self.ltc294x.read_status().into(),
573
574            // Configure.
575            2 => {
576                let int_pin_raw = data & 0x03;
577                let prescaler = (data >> 2) & 0x07;
578                let vbat_raw = (data >> 5) & 0x03;
579                let int_pin_conf = match int_pin_raw {
580                    0 => InterruptPinConf::Disabled,
581                    1 => InterruptPinConf::ChargeCompleteMode,
582                    2 => InterruptPinConf::AlertMode,
583                    _ => InterruptPinConf::Disabled,
584                };
585                let vbat_alert = match vbat_raw {
586                    0 => VBatAlert::Off,
587                    1 => VBatAlert::Threshold2V8,
588                    2 => VBatAlert::Threshold2V9,
589                    3 => VBatAlert::Threshold3V0,
590                    _ => VBatAlert::Off,
591                };
592
593                self.ltc294x
594                    .configure(int_pin_conf, prescaler as u8, vbat_alert)
595                    .into()
596            }
597
598            // Reset charge.
599            3 => self.ltc294x.reset_charge().into(),
600
601            // Set high threshold
602            4 => self.ltc294x.set_high_threshold(data as u16).into(),
603
604            // Set low threshold
605            5 => self.ltc294x.set_low_threshold(data as u16).into(),
606
607            // Get charge
608            6 => self.ltc294x.get_charge().into(),
609
610            // Shutdown
611            7 => self.ltc294x.shutdown().into(),
612
613            // Get voltage
614            8 => self.ltc294x.get_voltage().into(),
615
616            // Get current
617            9 => self.ltc294x.get_current().into(),
618
619            // Set the current chip model
620            10 => self.ltc294x.set_model(data).into(),
621
622            // default
623            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
624        }
625    }
626
627    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
628        self.grants.enter(processid, |_, _| {})
629    }
630}