Skip to content

Commit

Permalink
supply: allow USB data disconnection
Browse files Browse the repository at this point in the history
Allow USB data disconnection via sysfs
openemc_supply_connect_data file.
  • Loading branch information
surban committed Jun 25, 2024
1 parent 256309a commit 4118804
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 71 deletions.
1 change: 1 addition & 0 deletions openemc-driver/openemc.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
#define OPENEMC_SUPPLY_USB_COMMUNICATION 0x93
#define OPENEMC_SUPPLY_VOLTAGE 0x94
#define OPENEMC_SUPPLY_CURRENT 0x95
#define OPENEMC_SUPPLY_CONNECT_DATA 0x96

/* Board IO register definitions */
#define OPENEMC_BOARD_IO 0xe0
Expand Down
52 changes: 52 additions & 0 deletions openemc-driver/openemc_supply.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,54 @@ static const struct power_supply_desc openemc_supply_desc = {
.get_property = openemc_supply_get_property,
};

static ssize_t openemc_supply_connect_data_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct openemc_supply *sup = dev_get_drvdata(dev);
int ret;
u8 connect;

ret = openemc_read_u8(sup->emc, OPENEMC_SUPPLY_CONNECT_DATA, &connect);
if (ret < 0)
return ret;

if (connect == 0)
return sprintf(buf, "disconnect");
else
return sprintf(buf, "connect");
}
static ssize_t openemc_supply_connect_data_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct openemc_supply *sup = dev_get_drvdata(dev);
u8 connect;
int ret;

if (sysfs_streq(buf, "disconnect"))
connect = 0;
else if (sysfs_streq(buf, "connect"))
connect = 1;
else
return -EINVAL;

ret = openemc_write_u8(sup->emc, OPENEMC_SUPPLY_CONNECT_DATA, connect);
if (ret != 0)
return ret;

return count;
}
static DEVICE_ATTR_RW(openemc_supply_connect_data);

static struct attribute *openemc_supply_attrs[] = {
&dev_attr_openemc_supply_connect_data.attr, NULL
};

static const struct attribute_group openemc_supply_group = {
.attrs = openemc_supply_attrs,
};

static int openemc_supply_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
Expand Down Expand Up @@ -248,6 +296,10 @@ static int openemc_supply_probe(struct platform_device *pdev)
}
}

ret = devm_device_add_group(&pdev->dev, &openemc_supply_group);
if (ret < 0)
return ret;

dev_info(sup->dev, "OpenEMC power supply %s registered\n",
psy_desc->name);

Expand Down
152 changes: 86 additions & 66 deletions openemc-firmware/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ mod app {
bq25713: Option<Bq25713<I2c2Master>>,
/// Latest power supply report.
power_supply: Option<PowerSupply>,
/// Connect USB data lines of supply?
power_supply_connect_data: bool,
/// Latest battery report.
battery: Option<Battery>,
/// Undervoltage power off in progress?
Expand Down Expand Up @@ -748,6 +750,7 @@ mod app {
max14636,
bq25713,
power_supply: None,
power_supply_connect_data: false,
battery: None,
undervoltage_power_off: false,
bootloader_crc32,
Expand Down Expand Up @@ -955,7 +958,7 @@ mod app {

/// Updates the power supply status.
#[task(
shared = [i2c2, stusb4500, max14636, bq25713, power_supply, irq, board, &power_mode],
shared = [i2c2, stusb4500, max14636, bq25713, power_supply, power_supply_connect_data, irq, board, &power_mode],
local = [
first: Option<Instant> = None,
last_change: Option<Instant> = None,
Expand All @@ -974,84 +977,94 @@ mod app {
cx.shared.max14636,
cx.shared.bq25713,
cx.shared.power_supply,
cx.shared.power_supply_connect_data,
cx.shared.irq,
cx.shared.board,
)
.lock(|i2c2, stusb4500, max14636, bq25713, power_supply, irq, board| {
// Merge power supply reports.
let mut report = PowerSupply::default();
if let Some(stusb4500) = stusb4500 {
let stusb4500_report = stusb4500.report();
defmt::trace!("STUSB4500 power supply report: {:?}", &stusb4500_report);
report = report.merge(stusb4500.report());
}
if let Some(max14636) = max14636 {
let max14636_report = max14636.report();
defmt::trace!("MAX14636 power supply report: {:?}", &max14636_report);
report = report.merge(&max14636_report);
}
.lock(
|i2c2, stusb4500, max14636, bq25713, power_supply, power_supply_connect_data, irq, board| {
// Set USB data connection.
if let Some(max14636) = max14636 {
if max14636.good_battery() != *power_supply_connect_data {
max14636.set_good_battery(*power_supply_connect_data);
}
}

// Update power supply status.
if Some(&report) != power_supply.as_ref() {
defmt::info!("Power supply: {:?}", report);
// Merge power supply reports.
let mut report = PowerSupply::default();
if let Some(stusb4500) = stusb4500 {
let stusb4500_report = stusb4500.report();
defmt::trace!("STUSB4500 power supply report: {:?}", &stusb4500_report);
report = report.merge(stusb4500.report());
}
if let Some(max14636) = max14636 {
let max14636_report = max14636.report();
defmt::trace!("MAX14636 power supply report: {:?}", &max14636_report);
report = report.merge(&max14636_report);
}

// Store report and notify host.
irq.pend_soft(IrqState::SUPPLY | IrqState::BATTERY);
*power_supply = Some(report.clone());
*last_change = monotonics::now();
}
// Update power supply status.
if Some(&report) != power_supply.as_ref() {
defmt::info!("Power supply: {:?}", report);

// Check for power on and shutdown in charging mode.
if *cx.shared.power_mode == PowerMode::Charging {
if board.check_power_on_requested() {
defmt::info!("Power on requested");
let _ = power_restart::spawn(BootReason::Restart);
// Store report and notify host.
irq.pend_soft(IrqState::SUPPLY | IrqState::BATTERY);
*power_supply = Some(report.clone());
*last_change = monotonics::now();
}

if !report.is_connected() && monotonics::now() - *first > grace_period {
defmt::info!("Shutdown because power supply disconnected");
let _ = power_off::spawn();
// Check for power on and shutdown in charging mode.
if *cx.shared.power_mode == PowerMode::Charging {
if board.check_power_on_requested() {
defmt::info!("Power on requested");
let _ = power_restart::spawn(BootReason::Restart);
}

if !report.is_connected() && monotonics::now() - *first > grace_period {
defmt::info!("Shutdown because power supply disconnected");
let _ = power_off::spawn();
}
}
}

// Calculate input current limit.
let limit_opt = if report.is_unknown() {
None
} else {
Some(board.input_current_limit(&report, monotonics::now() - *last_change))
};
// Calculate input current limit.
let limit_opt = if report.is_unknown() {
None
} else {
Some(board.input_current_limit(&report, monotonics::now() - *last_change))
};

// Configure battery charger.
match &limit_opt {
_ if limit_opt == *cx.local.limit_opt => (),
Some(limit) => match (i2c2, bq25713) {
(Some(i2c2), Some(bq25713)) => {
defmt::info!(
"Setting BQ25713 maximum input current to {} mA and ICO to {}",
limit.max_input_current_ma,
limit.ico
);

// Configure battery charger.
match &limit_opt {
_ if limit_opt == *cx.local.limit_opt => (),
Some(limit) => match (i2c2, bq25713) {
(Some(i2c2), Some(bq25713)) => {
defmt::info!(
"Setting BQ25713 maximum input current to {} mA and ICO to {}",
limit.max_input_current_ma,
limit.ico
);

let res = bq25713.set_input_current_limit(i2c2, limit).and_then(|_| {
if limit.max_input_current_ma > 0 && !bq25713.is_charge_enabled() {
bq25713.set_charge_enable(i2c2, true)?;
} else if limit.max_input_current_ma == 0 && bq25713.is_charge_enabled() {
bq25713.set_charge_enable(i2c2, false)?;
let res = bq25713.set_input_current_limit(i2c2, limit).and_then(|_| {
if limit.max_input_current_ma > 0 && !bq25713.is_charge_enabled() {
bq25713.set_charge_enable(i2c2, true)?;
} else if limit.max_input_current_ma == 0 && bq25713.is_charge_enabled() {
bq25713.set_charge_enable(i2c2, false)?;
}
Ok(())
});
match res {
Ok(()) => *cx.local.limit_opt = limit_opt,
Err(err) => defmt::error!("Cannot configure BQ25713 charging: {}", err),
}
Ok(())
});
match res {
Ok(()) => *cx.local.limit_opt = limit_opt,
Err(err) => defmt::error!("Cannot configure BQ25713 charging: {}", err),
}
}
_ => {
*cx.local.limit_opt = limit_opt;
}
},
None => *cx.local.limit_opt = limit_opt,
}
});
_ => {
*cx.local.limit_opt = limit_opt;
}
},
None => *cx.local.limit_opt = limit_opt,
}
},
);

defmt::unwrap!(power_supply_update::spawn_after(500u64.millis()));
}
Expand Down Expand Up @@ -1475,6 +1488,7 @@ mod app {
adc_buf,
board,
power_supply,
power_supply_connect_data,
battery,
flash,
crc,
Expand Down Expand Up @@ -1886,6 +1900,12 @@ mod app {
}
});
}
Event::Read { reg: reg::SUPPLY_CONNECT_DATA } => {
cx.shared.power_supply_connect_data.lock(|&mut connect| respond_u8(connect.into()));
}
Event::Write { reg: reg::SUPPLY_CONNECT_DATA, value } => {
cx.shared.power_supply_connect_data.lock(|connect| *connect = value.as_u8() != 0);
}
Event::Read { reg: reg::BOARD_IO } => {
cx.shared.board.lock(|board| {
let data = board.io_read();
Expand Down
4 changes: 4 additions & 0 deletions openemc-firmware/src/reg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ pub const SUPPLY_VOLTAGE: u8 = 0x94;
/// External power supply current in mA. (read-only, u32)
pub const SUPPLY_CURRENT: u8 = 0x95;

/// External power supply USB communication connection. (read/write, u8)
/// 0=disconnected, 1=connected.
pub const SUPPLY_CONNECT_DATA: u8 = 0x96;

/// Read or write to board handler. (read/write)
/// First byte of read response is length.
pub const BOARD_IO: u8 = 0xe0;
Expand Down
23 changes: 18 additions & 5 deletions openemc-firmware/src/supply/max14636.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

//! MAX14636 USB charger detector driver.
use stm32f1xx_hal::gpio::{ErasedPin, Floating, Input, Output, PullUp};
use stm32f1xx_hal::gpio::{ErasedPin, Floating, Input, Output, PinState, PullUp};

use super::PowerSupply;

/// MAX14636 USB charger detector driver.
pub struct Max14636 {
_good_bat: ErasedPin<Output>,
good_bat: ErasedPin<Output>,
sw_open: ErasedPin<Input<PullUp>>,
chg_al_n: ErasedPin<Input<PullUp>>,
chg_det: ErasedPin<Input<Floating>>,
Expand All @@ -33,11 +33,24 @@ pub struct Max14636 {
impl Max14636 {
/// Creates a new driver instance.
pub fn new(
mut good_bat: ErasedPin<Output>, sw_open: ErasedPin<Input<PullUp>>, chg_al_n: ErasedPin<Input<PullUp>>,
good_bat: ErasedPin<Output>, sw_open: ErasedPin<Input<PullUp>>, chg_al_n: ErasedPin<Input<PullUp>>,
chg_det: ErasedPin<Input<Floating>>,
) -> Self {
good_bat.set_high();
Self { _good_bat: good_bat, sw_open, chg_al_n, chg_det }
Self { good_bat, sw_open, chg_al_n, chg_det }
}

/// Sets the good battery state.
///
/// The USB data connection is established when `good_battery` is true.
/// Otherwise the USB data lines are disconnected.
pub fn set_good_battery(&mut self, good_battery: bool) {
defmt::info!("Setting good battery to {:?}", good_battery);
self.good_bat.set_state(if good_battery { PinState::High } else { PinState::Low });
}

/// Whether the good battery state is set.
pub fn good_battery(&self) -> bool {
self.good_bat.is_set_high()
}

/// Power supply report.
Expand Down

0 comments on commit 4118804

Please sign in to comment.