#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/devm-helpers.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/mfd/macsmc.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/workqueue.h>
#define MAX_STRING_LENGTH 256
#define MACSMC_NOMINAL_CELL_VOLTAGE_MV 3800
#define CHNC_BATTERY_FULL BIT(0)
#define CHNC_NO_CHARGER BIT(7)
#define CHNC_NOCHG_CH0C BIT(14)
#define CHNC_NOCHG_CH0B_CH0K BIT(15)
#define CHNC_BATTERY_FULL_2 BIT(18)
#define CHNC_BMS_BUSY BIT(23)
#define CHNC_CHLS_LIMIT BIT(24)
#define CHNC_NOAC_CH0J BIT(53)
#define CHNC_NOAC_CH0I BIT(54)
#define CH0R_LOWER_FLAGS GENMASK(15, 0)
#define CH0R_NOAC_CH0I BIT(0)
#define CH0R_NOAC_DISCONNECTED BIT(4)
#define CH0R_NOAC_CH0J BIT(5)
#define CH0R_BMS_BUSY BIT(8)
#define CH0R_NOAC_CH0K BIT(9)
#define CH0R_NOAC_CHWA BIT(11)
#define CH0X_CH0C BIT(0)
#define CH0X_CH0B BIT(1)
#define ACSt_CAN_BOOT_AP BIT(2)
#define ACSt_CAN_BOOT_IBOOT BIT(1)
#define CHWA_CHLS_FIXED_START_OFFSET 5
#define CHLS_MIN_END_THRESHOLD 10
#define CHLS_FORCE_DISCHARGE 0x100
#define CHWA_FIXED_END_THRESHOLD 80
#define CHWA_PROP_WRITE_THRESHOLD 95
#define MACSMC_MAX_BATT_PROPS 50
#define MACSMC_MAX_AC_PROPS 10
struct macsmc_power {
struct device *dev;
struct apple_smc *smc;
struct power_supply_desc ac_desc;
struct power_supply_desc batt_desc;
struct power_supply *batt;
struct power_supply *ac;
char model_name[MAX_STRING_LENGTH];
char serial_number[MAX_STRING_LENGTH];
char mfg_date[MAX_STRING_LENGTH];
bool has_chwa;
bool has_chls;
bool has_ch0i;
bool has_ch0c;
bool has_chte;
bool fw_ge_27;
u8 num_cells;
int nominal_voltage_mv;
struct notifier_block nb;
struct work_struct critical_work;
bool emergency_shutdown_triggered;
bool orderly_shutdown_triggered;
};
static int macsmc_battery_get_status(struct macsmc_power *power)
{
u64 nocharge_flags;
u32 nopower_flags;
u16 ac_current;
int charge_limit = 0;
bool limited = false;
bool flag;
int ret;
ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags);
if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY))
return POWER_SUPPLY_STATUS_DISCHARGING;
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE), &flag);
if (ret < 0)
return ret;
if (!flag)
return POWER_SUPPLY_STATUS_DISCHARGING;
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC), &flag);
if (ret < 0)
return ret;
if (!flag)
return POWER_SUPPLY_STATUS_DISCHARGING;
ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current);
if (!ret && ac_current < 100)
return POWER_SUPPLY_STATUS_DISCHARGING;
ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag);
if (ret < 0)
return ret;
if (flag)
return POWER_SUPPLY_STATUS_FULL;
if (power->has_chls) {
u16 vu16;
ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16);
if (ret == 0 && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD)
charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET;
} else if (power->has_chwa) {
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag);
if (ret == 0 && flag)
charge_limit = CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET;
}
if (charge_limit > 0) {
u8 buic = 0;
if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
buic >= charge_limit)
limited = true;
}
ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags);
if (!ret) {
if (nocharge_flags & CHNC_BATTERY_FULL)
return POWER_SUPPLY_STATUS_FULL;
else if (nocharge_flags == CHNC_BMS_BUSY && !limited)
return POWER_SUPPLY_STATUS_CHARGING;
else if (nocharge_flags)
return POWER_SUPPLY_STATUS_NOT_CHARGING;
else
return POWER_SUPPLY_STATUS_CHARGING;
}
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC), &flag);
if (ret < 0)
return ret;
if (!flag)
return POWER_SUPPLY_STATUS_NOT_CHARGING;
return POWER_SUPPLY_STATUS_CHARGING;
}
static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power)
{
int ret;
u8 val8;
u8 chte_buf[4];
if (power->has_ch0i) {
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8);
if (ret)
return ret;
if (val8 & CH0R_NOAC_CH0I)
return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
}
if (power->has_chte) {
ret = apple_smc_read(power->smc, SMC_KEY(CHTE), chte_buf, 4);
if (ret < 0)
return ret;
if (chte_buf[0] == 0x01)
return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
} else if (power->has_ch0c) {
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8);
if (ret)
return ret;
if (val8 & CH0X_CH0C)
return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
}
return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
}
static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val)
{
int ret;
switch (val) {
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
if (power->has_ch0i) {
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
if (ret)
return ret;
}
if (power->has_chte) {
ret = apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 0);
if (ret)
return ret;
} else if (power->has_ch0c) {
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
if (ret)
return ret;
}
return 0;
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
if (power->has_chte)
return apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 1);
else if (power->has_ch0c)
return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 1);
else
return -EOPNOTSUPP;
case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
if (!power->has_ch0i)
return -EOPNOTSUPP;
return apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 1);
default:
return -EINVAL;
}
}
static int macsmc_battery_get_date(const char *s, int *out)
{
if (!isdigit(s[0]) || !isdigit(s[1]))
return -EOPNOTSUPP;
*out = (s[0] - '0') * 10 + s[1] - '0';
return 0;
}
static int macsmc_battery_read_bcf0(struct macsmc_power *power, u32 *val)
{
u8 tval = 0;
int ret;
if (power->fw_ge_27) {
ret = apple_smc_read_u8(power->smc, SMC_KEY(BCF0), &tval);
*val = tval;
return ret;
}
return apple_smc_read_u32(power->smc, SMC_KEY(BCF0), val);
}
static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
{
bool flag;
u32 val;
int ret;
if (macsmc_battery_read_bcf0(power, &val) >= 0 && val)
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) {
if (!(val & ACSt_CAN_BOOT_IBOOT))
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
if (!(val & ACSt_CAN_BOOT_AP))
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
}
ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC), &flag);
if (ret < 0)
return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
if (flag)
return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
else
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
}
static s16 macsmc_swap_b0rm(struct macsmc_power *power, s16 b0rm)
{
return power->fw_ge_27 ? b0rm : (s16)swab16(b0rm);
}
static int macsmc_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct macsmc_power *power = power_supply_get_drvdata(psy);
int ret = 0;
u8 vu8;
u16 vu16;
s16 vs16;
s32 vs32;
s64 vs64;
bool flag;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = macsmc_battery_get_status(power);
ret = val->intval < 0 ? val->intval : 0;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
val->intval = macsmc_battery_get_charge_behaviour(power);
ret = val->intval < 0 ? val->intval : 0;
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
break;
case POWER_SUPPLY_PROP_CAPACITY:
ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
val->intval = vu8;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
val->intval = macsmc_battery_get_capacity_level(power);
ret = val->intval < 0 ? val->intval : 0;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
val->intval = vs16 * 1000;
break;
case POWER_SUPPLY_PROP_POWER_NOW:
ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
val->intval = vs32 * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16);
val->intval = vu16 * 1000 * power->num_cells;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPM), &vs16);
val->intval = vs16 * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPX), &vs16);
val->intval = vs16 * 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
val->intval = macsmc_swap_b0rm(power, vu16) * 1000;
break;
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
val->intval = vu16 * power->nominal_voltage_mv;
break;
case POWER_SUPPLY_PROP_ENERGY_FULL:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
val->intval = vu16 * power->nominal_voltage_mv;
break;
case POWER_SUPPLY_PROP_ENERGY_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
val->intval = macsmc_swap_b0rm(power, vu16) * power->nominal_voltage_mv;
break;
case POWER_SUPPLY_PROP_TEMP:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
val->intval = vu16 - 2732;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
val->intval = vs64;
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16);
val->intval = vu16;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
break;
case POWER_SUPPLY_PROP_HEALTH:
flag = false;
ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD), &flag);
val->intval = flag ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = power->model_name;
break;
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
val->strval = power->serial_number;
break;
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval);
val->intval += 1992;
break;
case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval);
break;
case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval);
break;
default:
return -EINVAL;
}
return ret;
}
static int macsmc_battery_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct macsmc_power *power = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
return macsmc_battery_set_charge_behaviour(power, val->intval);
default:
return -EINVAL;
}
}
static int macsmc_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
return true;
default:
return false;
}
}
static const struct power_supply_desc macsmc_battery_desc_template = {
.name = "macsmc-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = macsmc_battery_get_property,
.set_property = macsmc_battery_set_property,
.property_is_writeable = macsmc_battery_property_is_writeable,
};
static int macsmc_ac_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct macsmc_power *power = power_supply_get_drvdata(psy);
int ret = 0;
u16 vu16;
u32 vu32;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32);
val->intval = !!vu32;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16);
val->intval = vu16 * 1000;
break;
case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT:
ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32);
val->intval = vu32 * 1000;
break;
default:
return -EINVAL;
}
return ret;
}
static const struct power_supply_desc macsmc_ac_desc_template = {
.name = "macsmc-ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.get_property = macsmc_ac_get_property,
};
static void macsmc_power_critical_work(struct work_struct *wrk)
{
struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work);
u16 bitv, b0av;
u32 bcf0;
if (!power->batt)
return;
if (power->emergency_shutdown_triggered || system_state > SYSTEM_RUNNING)
return;
if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 &&
apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 &&
b0av < bitv) {
power->emergency_shutdown_triggered = true;
dev_emerg(power->dev,
"Battery voltage (%d mV) below design minimum (%d mV)! Emergency shutdown.\n",
b0av, bitv);
hw_protection_trigger("Battery voltage below design minimum", 1500);
}
if (power->orderly_shutdown_triggered || system_state > SYSTEM_RUNNING)
return;
if (macsmc_battery_read_bcf0(power, &bcf0) == 0 && bcf0 != 0) {
power->orderly_shutdown_triggered = true;
dev_crit(power->dev, "Battery critical (empty flag set). Triggering orderly shutdown.\n");
orderly_poweroff(true);
}
}
static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
{
struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
if ((event & 0xffffff00) == 0x71010100 ||
(event & 0xffff0000) == 0x71060000 ||
(event & 0xffff0000) == 0x71130000) {
if (power->batt)
power_supply_changed(power->batt);
if (power->ac)
power_supply_changed(power->ac);
return NOTIFY_OK;
} else if (event == 0x71020000) {
if (power->batt)
schedule_work(&power->critical_work);
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static int macsmc_power_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config psy_cfg = {};
struct apple_smc_key_info info;
struct macsmc_power *power;
bool has_battery = false;
bool has_ac_adapter = false;
int ret = -ENODEV;
bool flag;
u16 vu16;
u32 val32;
enum power_supply_property *props;
size_t nprops;
if (!smc)
return -ENODEV;
power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL);
if (!power)
return -ENOMEM;
power->dev = dev;
power->smc = smc;
dev_set_drvdata(dev, power);
INIT_WORK(&power->critical_work, macsmc_power_critical_work);
ret = devm_work_autocancel(dev, &power->critical_work, macsmc_power_critical_work);
if (ret)
return ret;
if (apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16) == 0 &&
macsmc_battery_get_status(power) > POWER_SUPPLY_STATUS_UNKNOWN)
has_battery = true;
if (apple_smc_key_exists(smc, SMC_KEY(CHIS)))
has_ac_adapter = true;
if (!has_battery && !has_ac_adapter)
return -ENODEV;
if (has_battery) {
power->batt_desc = macsmc_battery_desc_template;
props = devm_kcalloc(dev, MACSMC_MAX_BATT_PROPS,
sizeof(enum power_supply_property),
GFP_KERNEL);
if (!props)
return -ENOMEM;
nprops = 0;
props[nprops++] = POWER_SUPPLY_PROP_STATUS;
props[nprops++] = POWER_SUPPLY_PROP_PRESENT;
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
props[nprops++] = POWER_SUPPLY_PROP_CURRENT_NOW;
props[nprops++] = POWER_SUPPLY_PROP_POWER_NOW;
props[nprops++] = POWER_SUPPLY_PROP_CAPACITY;
props[nprops++] = POWER_SUPPLY_PROP_CAPACITY_LEVEL;
props[nprops++] = POWER_SUPPLY_PROP_TEMP;
props[nprops++] = POWER_SUPPLY_PROP_CYCLE_COUNT;
props[nprops++] = POWER_SUPPLY_PROP_HEALTH;
props[nprops++] = POWER_SUPPLY_PROP_SCOPE;
props[nprops++] = POWER_SUPPLY_PROP_MODEL_NAME;
props[nprops++] = POWER_SUPPLY_PROP_SERIAL_NUMBER;
props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_YEAR;
props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_MONTH;
props[nprops++] = POWER_SUPPLY_PROP_MANUFACTURE_DAY;
props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW;
props[nprops++] = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW;
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT;
props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
props[nprops++] = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_FULL;
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_NOW;
props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
props[nprops++] = POWER_SUPPLY_PROP_ENERGY_FULL;
props[nprops++] = POWER_SUPPLY_PROP_ENERGY_NOW;
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_COUNTER;
if (apple_smc_key_exists(smc, SMC_KEY(CHTE)))
power->has_chte = true;
if (apple_smc_key_exists(smc, SMC_KEY(CH0C)))
power->has_ch0c = true;
if (apple_smc_key_exists(smc, SMC_KEY(CH0I)))
power->has_ch0i = true;
ret = apple_smc_get_key_info(power->smc, SMC_KEY(BCF0), &info);
if (ret) {
dev_err(&pdev->dev, "Failed to determine BCF0 key size\n");
return ret;
}
if (info.size == 1)
power->fw_ge_27 = true;
else if (info.size == 4)
power->fw_ge_27 = false;
else {
dev_err(&pdev->dev, "Unexpected BCF0 key size %d\n", info.size);
return -EIO;
}
if (power->has_chte)
apple_smc_write_u32(smc, SMC_KEY(CHTE), 0);
else if (power->has_ch0c)
apple_smc_write_u8(smc, SMC_KEY(CH0C), 0);
if (power->has_ch0i)
apple_smc_write_u8(smc, SMC_KEY(CH0I), 0);
apple_smc_write_u8(smc, SMC_KEY(CH0K), 0);
apple_smc_write_u8(smc, SMC_KEY(CH0B), 0);
if (power->has_ch0i || power->has_ch0c || power->has_chte) {
props[nprops++] = POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
power->batt_desc.charge_behaviours =
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
if (power->has_ch0i)
power->batt_desc.charge_behaviours |=
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
if (power->has_chte || power->has_ch0c)
power->batt_desc.charge_behaviours |=
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
}
if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag) == 0)
power->has_chwa = true;
else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0)
power->has_chls = true;
if (nprops > MACSMC_MAX_BATT_PROPS)
return -ENOMEM;
power->batt_desc.properties = props;
power->batt_desc.num_properties = nprops;
apple_smc_read(smc, SMC_KEY(BMDN), power->model_name,
sizeof(power->model_name) - 1);
apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number,
sizeof(power->serial_number) - 1);
apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date,
sizeof(power->mfg_date) - 1);
apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells);
power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
macsmc_battery_read_bcf0(power, &val32);
psy_cfg.drv_data = power;
power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg);
if (IS_ERR(power->batt)) {
dev_err_probe(dev, PTR_ERR(power->batt),
"Failed to register battery\n");
power->batt = NULL;
}
}
if (has_ac_adapter) {
power->ac_desc = macsmc_ac_desc_template;
props = devm_kcalloc(dev, MACSMC_MAX_AC_PROPS,
sizeof(enum power_supply_property),
GFP_KERNEL);
if (!props)
return -ENOMEM;
nprops = 0;
props[nprops++] = POWER_SUPPLY_PROP_ONLINE;
if (apple_smc_key_exists(power->smc, SMC_KEY(ACPW)))
props[nprops++] = POWER_SUPPLY_PROP_INPUT_POWER_LIMIT;
if (apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16) >= 0) {
props[nprops++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
props[nprops++] = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
}
if (nprops > MACSMC_MAX_AC_PROPS)
return -ENOMEM;
power->ac_desc.properties = props;
power->ac_desc.num_properties = nprops;
psy_cfg.drv_data = power;
power->ac = devm_power_supply_register(dev, &power->ac_desc, &psy_cfg);
if (IS_ERR(power->ac)) {
dev_err_probe(dev, PTR_ERR(power->ac),
"Failed to register AC adapter\n");
power->ac = NULL;
}
}
if (!power->batt && !power->ac)
return -ENODEV;
power->nb.notifier_call = macsmc_power_event;
blocking_notifier_chain_register(&smc->event_handlers, &power->nb);
return 0;
}
static void macsmc_power_remove(struct platform_device *pdev)
{
struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
blocking_notifier_chain_unregister(&power->smc->event_handlers, &power->nb);
}
static const struct platform_device_id macsmc_power_id[] = {
{ .name = "macsmc-power" },
{ }
};
MODULE_DEVICE_TABLE(platform, macsmc_power_id);
static struct platform_driver macsmc_power_driver = {
.driver = {
.name = "macsmc-power",
},
.id_table = macsmc_power_id,
.probe = macsmc_power_probe,
.remove = macsmc_power_remove,
};
module_platform_driver(macsmc_power_driver);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION("Apple SMC battery and power management driver");
MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>");