1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Driver for the PCA9544A I2C Selector.
//!
//! This chip allows for multiple I2C devices with the same addresses to
//! sit on the same I2C bus.
//!
//! <http://www.ti.com/product/PCA9544A>
//!
//! > The PCA9544A is a quad bidirectional translating switch controlled via the
//! > I2C bus. The SCL/SDA upstream pair fans out to four downstream pairs, or
//! > channels. One SCL/SDA pair can be selected at a time, and this is
//! > determined by the contents of the programmable control register. Four
//! > interrupt inputs (INT3–INT0), one for each of the downstream pairs, are
//! > provided. One interrupt output (INT) acts as an AND of the four interrupt
//! > inputs.
//!
//! Usage
//! -----
//!
//! ```rust
//! let pca9544a_i2c = static_init!(
//!     capsules::virtual_i2c::I2CDevice,
//!     capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x70));
//! let pca9544a = static_init!(
//!     capsules::pca9544a::PCA9544A<'static>,
//!     capsules::pca9544a::PCA9544A::new(pca9544a_i2c, &mut capsules::pca9544a::BUFFER));
//! pca9544a_i2c.set_client(pca9544a);
//! ```

use core::cell::Cell;
use kernel::{AppId, Callback, Driver, ReturnCode};
use kernel::common::take_cell::TakeCell;
use kernel::hil::i2c;

pub static mut BUFFER: [u8; 5] = [0; 5];

#[derive(Clone, Copy, PartialEq)]
enum State {
    Idle,

    /// Read the control register and return the specified data field.
    ReadControl(ControlField),

    Done,
}

#[derive(Clone, Copy, PartialEq)]
enum ControlField {
    InterruptMask,
    SelectedChannels,
}

pub struct PCA9544A<'a> {
    i2c: &'a i2c::I2CDevice,
    state: Cell<State>,
    buffer: TakeCell<'static, [u8]>,
    callback: Cell<Option<Callback>>,
}

impl<'a> PCA9544A<'a> {
    pub fn new(i2c: &'a i2c::I2CDevice, buffer: &'static mut [u8]) -> PCA9544A<'a> {
        PCA9544A {
            i2c: i2c,
            state: Cell::new(State::Idle),
            buffer: TakeCell::new(buffer),
            callback: Cell::new(None),
        }
    }

    /// Choose which channel(s) are active. Channels are encoded with a bitwise
    /// mask (0x01 means enable channel 0, 0x0F means enable all channels).
    /// Send 0 to disable all channels.
    fn select_channels(&self, channel_bitmask: u8) -> ReturnCode {
        self.buffer.take().map_or(ReturnCode::ENOMEM, |buffer| {
            self.i2c.enable();

            // Always clear the settings so we get to a known state
            buffer[0] = 0;

            // Iterate the bit array to send the correct channel enables
            let mut index = 1;
            for i in 0..4 {
                if channel_bitmask & (0x01 << i) != 0 {
                    // B2 B1 B0 are set starting at 0x04
                    buffer[index] = i + 4;
                    index += 1;
                }
            }

            self.i2c.write(buffer, index as u8);
            self.state.set(State::Done);

            ReturnCode::SUCCESS
        })
    }

    fn read_interrupts(&self) -> ReturnCode {
        self.read_control(ControlField::InterruptMask)
    }

    fn read_selected_channels(&self) -> ReturnCode {
        self.read_control(ControlField::SelectedChannels)
    }

    fn read_control(&self, field: ControlField) -> ReturnCode {
        self.buffer.take().map_or(ReturnCode::ENOMEM, |buffer| {
            self.i2c.enable();

            // Just issuing a read to the selector reads its control register.
            self.i2c.read(buffer, 1);
            self.state.set(State::ReadControl(field));

            ReturnCode::SUCCESS
        })
    }
}

impl<'a> i2c::I2CClient for PCA9544A<'a> {
    fn command_complete(&self, buffer: &'static mut [u8], _error: i2c::Error) {
        match self.state.get() {
            State::ReadControl(field) => {
                let ret = match field {
                    ControlField::InterruptMask => (buffer[0] >> 4) & 0x0F,
                    ControlField::SelectedChannels => buffer[0] & 0x07,
                };

                self.callback
                    .get()
                    .map(|mut cb| cb.schedule((field as usize) + 1, ret as usize, 0));

                self.buffer.replace(buffer);
                self.i2c.disable();
                self.state.set(State::Idle);
            }
            State::Done => {
                self.callback.get().map(|mut cb| cb.schedule(0, 0, 0));

                self.buffer.replace(buffer);
                self.i2c.disable();
                self.state.set(State::Idle);
            }
            _ => {}
        }
    }
}

impl<'a> Driver for PCA9544A<'a> {
    /// Setup callback for event done.
    ///
    /// ### `subscribe_num`
    ///
    /// - `0`: Callback is triggered when a channel is finished being selected
    ///   or when the current channel setup is returned.
    fn subscribe(&self, subscribe_num: usize, callback: Callback) -> ReturnCode {
        match subscribe_num {
            0 => {
                self.callback.set(Some(callback));
                ReturnCode::SUCCESS
            }

            // default
            _ => ReturnCode::ENOSUPPORT,
        }
    }

    /// Control the I2C selector.
    ///
    /// ### `command_num`
    ///
    /// - `0`: Driver check.
    /// - `1`: Choose which channels are active.
    /// - `2`: Disable all channels.
    /// - `3`: Read the list of fired interrupts.
    /// - `4`: Read which channels are selected.
    fn command(&self, command_num: usize, data: usize, _: usize, _: AppId) -> ReturnCode {
        match command_num {
            // Check if present.
            0 => ReturnCode::SUCCESS,

            // Select channels.
            1 => self.select_channels(data as u8),

            // Disable all channels.
            2 => self.select_channels(0),

            // Read the current interrupt fired mask.
            3 => self.read_interrupts(),

            // Read the current selected channels.
            4 => self.read_selected_channels(),

            // default
            _ => ReturnCode::ENOSUPPORT,
        }
    }
}