blob: e77731903f0fe7f7b508523bf566b53425effd45 [file] [edit]
//! Provides userspace control of buttons on a board.
//!
//! This allows for much more cross platform controlling of buttons without
//! having to know which of the GPIO pins exposed across the syscall interface
//! are buttons.
//!
//! Usage
//! -----
//!
//! ```rust
//! # use kernel::static_init;
//!
//! let button_pins = static_init!(
//! [&'static sam4l::gpio::GPIOPin; 1],
//! [&sam4l::gpio::PA[16]]);
//! let button = static_init!(
//! capsules::button::Button<'static>,
//! capsules::button::Button::new(button_pins, board_kernel.create_grant(&grant_cap)));
//! for btn in button_pins.iter() {
//! btn.set_client(button);
//! }
//! ```
//!
//! Syscall Interface
//! -----------------
//!
//! - Stability: 2 - Stable
//!
//! ### Command
//!
//! Enable or disable button interrupts and read the current button state.
//!
//! #### `command_num`
//!
//! - `0`: Driver check and get number of buttons on the board.
//! - `1`: Enable interrupts for a given button. This will enable both press
//! and depress events.
//! - `2`: Disable interrupts for a button. No affect or reliance on
//! registered callback.
//! - `3`: Read the current state of the button.
//!
//! ### Subscribe
//!
//! Setup a callback for button presses.
//!
//! #### `subscribe_num`
//!
//! - `0`: Set callback for pin interrupts. Note setting this callback has
//! no reliance on individual pins being configured as interrupts. The
//! interrupt will be called with two parameters: the index of the button
//! that triggered the interrupt and the pressed (1) or not pressed (0) state
//! of the button.
use core::cell::Cell;
use kernel::hil::gpio;
use kernel::hil::gpio::{Configure, Input, InterruptWithValue};
use kernel::{AppId, Callback, Driver, Grant, ReturnCode};
/// Syscall driver number.
use crate::driver;
pub const DRIVER_NUM: usize = driver::NUM::Button as usize;
/// This capsule keeps track for each app of which buttons it has a registered
/// interrupt for. `SubscribeMap` is a bit array where bits are set to one if
/// that app has an interrupt registered for that button.
pub type SubscribeMap = u32;
/// Manages the list of GPIO pins that are connected to buttons and which apps
/// are listening for interrupts from which buttons.
pub struct Button<'a, P: gpio::InterruptPin<'a>> {
pins: &'a [(
&'a gpio::InterruptValueWrapper<'a, P>,
gpio::ActivationMode,
gpio::FloatingState,
)],
apps: Grant<(Option<Callback>, SubscribeMap)>,
}
impl<'a, P: gpio::InterruptPin<'a>> Button<'a, P> {
pub fn new(
pins: &'a [(
&'a gpio::InterruptValueWrapper<'a, P>,
gpio::ActivationMode,
gpio::FloatingState,
)],
grant: Grant<(Option<Callback>, SubscribeMap)>,
) -> Self {
for (i, &(pin, _, floating_state)) in pins.iter().enumerate() {
pin.make_input();
pin.set_value(i as u32);
pin.set_floating_state(floating_state);
}
Self {
pins: pins,
apps: grant,
}
}
fn get_button_state(&self, pin_num: u32) -> gpio::ActivationState {
let pin = &self.pins[pin_num as usize];
pin.0.read_activation(pin.1)
}
}
impl<'a, P: gpio::InterruptPin<'a>> Driver for Button<'a, P> {
/// Set callbacks.
///
/// ### `subscribe_num`
///
/// - `0`: Set callback for pin interrupts. Note setting this callback has
/// no reliance on individual pins being configured as interrupts. The
/// interrupt will be called with two parameters: the index of the button
/// that triggered the interrupt and the pressed/not pressed state of the
/// button.
fn subscribe(
&self,
subscribe_num: usize,
callback: Option<Callback>,
app_id: AppId,
) -> ReturnCode {
match subscribe_num {
0 => self
.apps
.enter(app_id, |cntr, _| {
cntr.0 = callback;
ReturnCode::SUCCESS
})
.unwrap_or_else(|err| err.into()),
// default
_ => ReturnCode::ENOSUPPORT,
}
}
/// Configure interrupts and read state for buttons.
///
/// `data` is the index of the button in the button array as passed to
/// `Button::new()`.
///
/// All commands greater than zero return `EINVAL` if an invalid button
/// number is passed in.
///
/// ### `command_num`
///
/// - `0`: Driver check and get number of buttons on the board.
/// - `1`: Enable interrupts for a given button. This will enable both press
/// and depress events.
/// - `2`: Disable interrupts for a button. No affect or reliance on
/// registered callback.
/// - `3`: Read the current state of the button.
fn command(&self, command_num: usize, data: usize, _: usize, appid: AppId) -> ReturnCode {
let pins = self.pins;
match command_num {
// return button count
0 => ReturnCode::SuccessWithValue {
value: pins.len() as usize,
},
// enable interrupts for a button
1 => {
if data < pins.len() {
self.apps
.enter(appid, |cntr, _| {
cntr.1 |= 1 << data;
pins[data]
.0
.enable_interrupts(gpio::InterruptEdge::EitherEdge);
ReturnCode::SUCCESS
})
.unwrap_or_else(|err| err.into())
} else {
ReturnCode::EINVAL /* impossible button */
}
}
// disable interrupts for a button
2 => {
if data >= pins.len() {
ReturnCode::EINVAL /* impossible button */
} else {
let res = self
.apps
.enter(appid, |cntr, _| {
cntr.1 &= !(1 << data);
ReturnCode::SUCCESS
})
.unwrap_or_else(|err| err.into());
// are any processes waiting for this button?
let interrupt_count = Cell::new(0);
self.apps.each(|cntr| {
cntr.0.map(|_| {
if cntr.1 & (1 << data) != 0 {
interrupt_count.set(interrupt_count.get() + 1);
}
});
});
// if not, disable the interrupt
if interrupt_count.get() == 0 {
self.pins[data].0.disable_interrupts();
}
res
}
}
// read input
3 => {
if data >= pins.len() {
ReturnCode::EINVAL /* impossible button */
} else {
let button_state = self.get_button_state(data as u32);
ReturnCode::SuccessWithValue {
value: button_state as usize,
}
}
}
// default
_ => ReturnCode::ENOSUPPORT,
}
}
}
impl<'a, P: gpio::InterruptPin<'a>> gpio::ClientWithValue for Button<'a, P> {
fn fired(&self, pin_num: u32) {
// Read the value of the pin and get the button state.
let button_state = self.get_button_state(pin_num);
let interrupt_count = Cell::new(0);
// schedule callback with the pin number and value
self.apps.each(|cntr| {
cntr.0.map(|mut callback| {
if cntr.1 & (1 << pin_num) != 0 {
interrupt_count.set(interrupt_count.get() + 1);
callback.schedule(pin_num as usize, button_state as usize, 0);
}
});
});
// It's possible we got an interrupt for a process that has since died
// (and didn't unregister the interrupt). Lazily disable interrupts for
// this button if so.
if interrupt_count.get() == 0 {
self.pins[pin_num as usize].0.disable_interrupts();
}
}
}