Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/power/supply/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 16 kB image not shown  

Quelle  huawei-gaokun-battery.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * huawei-gaokun-battery - A power supply driver for HUAWEI Matebook E Go
 *
 * Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
 */


#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_data/huawei-gaokun-ec.h>
#include <linux/power_supply.h>
#include <linux/sprintf.h>

/* -------------------------------------------------------------------------- */
/* String Data Reg */

#define EC_BAT_VENDOR  0x01 /* from 0x01 to 0x0F, SUNWODA */
#define EC_BAT_MODEL  0x11 /* from 0x11 to 0x1F, HB30A8P9ECW-22T */

#define EC_ADP_STATUS  0x81
#define EC_AC_STATUS  BIT(0)
#define EC_BAT_PRESENT  BIT(1) /* BATC._STA */

#define EC_BAT_STATUS  0x82 /* _BST */
#define EC_BAT_DISCHARGING BIT(0)
#define EC_BAT_CHARGING  BIT(1)
#define EC_BAT_CRITICAL  BIT(2) /* Low Battery Level */
#define EC_BAT_FULL  BIT(3)

/* -------------------------------------------------------------------------- */
/* Word Data Reg */

/* 0x5A: ?
 * 0x5C: ?
 * 0x5E: ?
 * 0X60: ?
 * 0x84: ?
 */


#define EC_BAT_STATUS_START 0x90
#define EC_BAT_PERCENTAGE 0x90
#define EC_BAT_VOLTAGE  0x92
#define EC_BAT_CAPACITY  0x94
#define EC_BAT_FULL_CAPACITY 0x96
/* 0x98: ? */
#define EC_BAT_CURRENT  0x9A
/* 0x9C: ? */

#define EC_BAT_INFO_START 0xA0
/* 0xA0: POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT? */
#define EC_BAT_DESIGN_CAPACITY 0xA2
#define EC_BAT_DESIGN_VOLTAGE 0xA4
#define EC_BAT_SERIAL_NUMBER 0xA6
#define EC_BAT_CYCLE_COUNT 0xAA

/* -------------------------------------------------------------------------- */
/* Battery Event ID */

#define EC_EVENT_BAT_A0  0xA0
#define EC_EVENT_BAT_A1  0xA1
#define EC_EVENT_BAT_A2  0xA2
#define EC_EVENT_BAT_A3  0xA3
#define EC_EVENT_BAT_B1  0xB1
/* EVENT B1 A0 A1 repeat about every 1s 2s 3s respectively */

/* ACPI _BIX field, Min sampling time, the duration between two _BST */
#define CACHE_TIME  2000 /* cache time in milliseconds */

#define MILLI_TO_MICRO  1000

#define SMART_CHARGE_MODE 0
#define SMART_CHARGE_DELAY 1
#define SMART_CHARGE_START 2
#define SMART_CHARGE_END 3

#define NO_DELAY_MODE 1
#define DELAY_MODE 4

struct gaokun_psy_bat_status {
 __le16 percentage_now; /* 0x90 */
 __le16 voltage_now;
 __le16 capacity_now;
 __le16 full_capacity;
 __le16 unknown1;
 __le16 rate_now;
 __le16 unknown2; /* 0x9C */
} __packed;

struct gaokun_psy_bat_info {
 __le16 unknown3; /* 0xA0 */
 __le16 design_capacity;
 __le16 design_voltage;
 __le16 serial_number;
 __le16 padding2;
 __le16 cycle_count; /* 0xAA */
} __packed;

struct gaokun_psy {
 struct gaokun_ec *ec;
 struct device *dev;
 struct notifier_block nb;

 struct power_supply *bat_psy;
 struct power_supply *adp_psy;

 unsigned long update_time;
 struct gaokun_psy_bat_status status;
 struct gaokun_psy_bat_info info;

 char battery_model[0x10]; /* HB30A8P9ECW-22T, the real one is XXX-22A */
 char battery_serial[0x10];
 char battery_vendor[0x10];

 int charge_now;
 int online;
 int bat_present;
};

/* -------------------------------------------------------------------------- */
/* Adapter */

static int gaokun_psy_get_adp_status(struct gaokun_psy *ecbat)
{
 /* _PSR */
 int ret;
 u8 online;

 ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &online);
 if (ret)
  return ret;

 ecbat->online = !!(online & EC_AC_STATUS);

 return 0;
}

static int gaokun_psy_get_adp_property(struct power_supply *psy,
           enum power_supply_property psp,
           union power_supply_propval *val)
{
 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
 int ret;

 ret = gaokun_psy_get_adp_status(ecbat);
 if (ret)
  return ret;

 switch (psp) {
 case POWER_SUPPLY_PROP_ONLINE:
  val->intval = ecbat->online;
  break;
 case POWER_SUPPLY_PROP_USB_TYPE:
  val->intval = POWER_SUPPLY_USB_TYPE_C;
  break;
 default:
  return -EINVAL;
 }

 return 0;
}

static enum power_supply_property gaokun_psy_adp_props[] = {
 POWER_SUPPLY_PROP_ONLINE,
 POWER_SUPPLY_PROP_USB_TYPE,
};

static const struct power_supply_desc gaokun_psy_adp_desc = {
 .name  = "gaokun-ec-adapter",
 .type  = POWER_SUPPLY_TYPE_USB,
 .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C),
 .get_property = gaokun_psy_get_adp_property,
 .properties = gaokun_psy_adp_props,
 .num_properties = ARRAY_SIZE(gaokun_psy_adp_props),
};

/* -------------------------------------------------------------------------- */
/* Battery */

static inline void gaokun_psy_get_bat_present(struct gaokun_psy *ecbat)
{
 int ret;
 u8 present;

 /* Some kind of initialization */
 gaokun_ec_write(ecbat->ec, (u8 []){0x02, 0xB2, 1, 0x90});

 ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &present);

 ecbat->bat_present = ret ? false : !!(present & EC_BAT_PRESENT);
}

static inline int gaokun_psy_bat_present(struct gaokun_psy *ecbat)
{
 return ecbat->bat_present;
}

static int gaokun_psy_get_bat_info(struct gaokun_psy *ecbat)
{
 /* _BIX */
 if (!gaokun_psy_bat_present(ecbat))
  return 0;

 return gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_INFO_START,
     sizeof(ecbat->info), (u8 *)&ecbat->info);
}

static void gaokun_psy_update_bat_charge(struct gaokun_psy *ecbat)
{
 u8 charge;

 gaokun_ec_psy_read_byte(ecbat->ec, EC_BAT_STATUS, &charge);

 switch (charge) {
 case EC_BAT_CHARGING:
  ecbat->charge_now = POWER_SUPPLY_STATUS_CHARGING;
  break;
 case EC_BAT_DISCHARGING:
  ecbat->charge_now = POWER_SUPPLY_STATUS_DISCHARGING;
  break;
 case EC_BAT_FULL:
  ecbat->charge_now = POWER_SUPPLY_STATUS_FULL;
  break;
 default:
  dev_warn(ecbat->dev, "unknown charge state %d\n", charge);
 }
}

static int gaokun_psy_get_bat_status(struct gaokun_psy *ecbat)
{
 /* _BST */
 int ret;

 if (time_before(jiffies, ecbat->update_time +
   msecs_to_jiffies(CACHE_TIME)))
  return 0;

 gaokun_psy_update_bat_charge(ecbat);
 ret = gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_STATUS_START,
           sizeof(ecbat->status), (u8 *)&ecbat->status);

 ecbat->update_time = jiffies;

 return ret;
}

static void gaokun_psy_init(struct gaokun_psy *ecbat)
{
 gaokun_psy_get_bat_present(ecbat);
 if (!gaokun_psy_bat_present(ecbat))
  return;

 gaokun_psy_get_bat_info(ecbat);

 snprintf(ecbat->battery_serial, sizeof(ecbat->battery_serial),
   "%d", le16_to_cpu(ecbat->info.serial_number));

 gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_VENDOR,
     sizeof(ecbat->battery_vendor) - 1,
     ecbat->battery_vendor);

 gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_MODEL,
     sizeof(ecbat->battery_model) - 1,
     ecbat->battery_model);

 ecbat->battery_model[14] = 'A'/* FIX UP */
}

static int gaokun_psy_get_bat_property(struct power_supply *psy,
           enum power_supply_property psp,
           union power_supply_propval *val)
{
 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
 u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE];
 int ret;

 if (gaokun_psy_bat_present(ecbat))
  gaokun_psy_get_bat_status(ecbat);
 else if (psp != POWER_SUPPLY_PROP_PRESENT)
  return -ENODEV;

 switch (psp) {
 case POWER_SUPPLY_PROP_STATUS:
  val->intval = ecbat->charge_now;
  break;

 case POWER_SUPPLY_PROP_PRESENT:
  val->intval = ecbat->bat_present;
  break;

 case POWER_SUPPLY_PROP_TECHNOLOGY:
  val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
  break;

 case POWER_SUPPLY_PROP_CYCLE_COUNT:
  val->intval = le16_to_cpu(ecbat->info.cycle_count);
  break;

 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
  val->intval = le16_to_cpu(ecbat->info.design_voltage) * MILLI_TO_MICRO;
  break;

 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
  val->intval = le16_to_cpu(ecbat->status.voltage_now) * MILLI_TO_MICRO;
  break;

 case POWER_SUPPLY_PROP_CURRENT_NOW:
  val->intval = (s16)le16_to_cpu(ecbat->status.rate_now) * MILLI_TO_MICRO;
  break;

 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
  val->intval = le16_to_cpu(ecbat->info.design_capacity) * MILLI_TO_MICRO;
  break;

 case POWER_SUPPLY_PROP_CHARGE_FULL:
  val->intval = le16_to_cpu(ecbat->status.full_capacity) * MILLI_TO_MICRO;
  break;

 case POWER_SUPPLY_PROP_CHARGE_NOW:
  val->intval = le16_to_cpu(ecbat->status.capacity_now) * MILLI_TO_MICRO;
  break;

 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
  ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf);
  if (ret)
   return ret;

  if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD)
   val->intval = buf[SMART_CHARGE_START];
  else
   val->intval = buf[SMART_CHARGE_END];
  break;

 case POWER_SUPPLY_PROP_CAPACITY:
  val->intval = le16_to_cpu(ecbat->status.percentage_now);
  break;

 case POWER_SUPPLY_PROP_MODEL_NAME:
  val->strval = ecbat->battery_model;
  break;

 case POWER_SUPPLY_PROP_MANUFACTURER:
  val->strval = ecbat->battery_vendor;
  break;

 case POWER_SUPPLY_PROP_SERIAL_NUMBER:
  val->strval = ecbat->battery_serial;
  break;

 default:
  return -EINVAL;
 }

 return 0;
}

static int gaokun_psy_set_bat_property(struct power_supply *psy,
           enum power_supply_property psp,
           const union power_supply_propval *val)
{
 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
 u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE];
 int ret;

 if (!gaokun_psy_bat_present(ecbat))
  return -ENODEV;

 ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf);
 if (ret)
  return ret;

 switch (psp) {
 /*
 * Resetting another thershold makes single thersold setting more likely
 * to succeed. But setting start = end makes thing strange(failure).
 */

 case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
  buf[SMART_CHARGE_START] = val->intval;
  if (buf[SMART_CHARGE_START] > buf[SMART_CHARGE_END])
   buf[SMART_CHARGE_END] = buf[SMART_CHARGE_START] + 1;
  return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf);

 case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
  buf[SMART_CHARGE_END] = val->intval;
  if (buf[SMART_CHARGE_END] < buf[SMART_CHARGE_START])
   buf[SMART_CHARGE_START] = buf[SMART_CHARGE_END] - 1;
  return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf);

 default:
  return -EINVAL;
 }

 return 0;
}

static int gaokun_psy_is_bat_property_writeable(struct power_supply *psy,
      enum power_supply_property psp)
{
 return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD ||
        psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD;
}

static enum power_supply_property gaokun_psy_bat_props[] = {
 POWER_SUPPLY_PROP_STATUS,
 POWER_SUPPLY_PROP_PRESENT,
 POWER_SUPPLY_PROP_TECHNOLOGY,
 POWER_SUPPLY_PROP_CYCLE_COUNT,
 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
 POWER_SUPPLY_PROP_VOLTAGE_NOW,
 POWER_SUPPLY_PROP_CURRENT_NOW,
 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
 POWER_SUPPLY_PROP_CHARGE_FULL,
 POWER_SUPPLY_PROP_CHARGE_NOW,
 POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
 POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
 POWER_SUPPLY_PROP_CAPACITY,
 POWER_SUPPLY_PROP_MODEL_NAME,
 POWER_SUPPLY_PROP_MANUFACTURER,
 POWER_SUPPLY_PROP_SERIAL_NUMBER,
};

static const struct power_supply_desc gaokun_psy_bat_desc = {
 .name   = "gaokun-ec-battery",
 .type   = POWER_SUPPLY_TYPE_BATTERY,
 .get_property  = gaokun_psy_get_bat_property,
 .set_property  = gaokun_psy_set_bat_property,
 .property_is_writeable = gaokun_psy_is_bat_property_writeable,
 .properties  = gaokun_psy_bat_props,
 .num_properties  = ARRAY_SIZE(gaokun_psy_bat_props),
};

/* -------------------------------------------------------------------------- */
/* Sysfs */

/*
 * Note that, HUAWEI calls them SBAC/GBAC and SBCM/GBCM in DSDT, they are likely
 * Set/Get Battery Adaptive Charging and Set/Get Battery Charging Mode.
 */


/* battery adaptive charge */
static ssize_t battery_adaptive_charge_show(struct device *dev,
         struct device_attribute *attr,
         char *buf)
{
 struct power_supply *psy = to_power_supply(dev);
 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
 int ret;
 bool on;

 ret = gaokun_ec_psy_get_smart_charge_enable(ecbat->ec, &on);
 if (ret)
  return ret;

 return sysfs_emit(buf, "%d\n", on);
}

static ssize_t battery_adaptive_charge_store(struct device *dev,
          struct device_attribute *attr,
          const char *buf, size_t size)
{
 struct power_supply *psy = to_power_supply(dev);
 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
 int ret;
 bool on;

 if (kstrtobool(buf, &on))
  return -EINVAL;

 ret = gaokun_ec_psy_set_smart_charge_enable(ecbat->ec, on);
 if (ret)
  return ret;

 return size;
}

static DEVICE_ATTR_RW(battery_adaptive_charge);

static inline int get_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE])
{
 return buf[SMART_CHARGE_MODE] == NO_DELAY_MODE ? 0 : buf[SMART_CHARGE_DELAY];
}

static inline void
set_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE], u8 delay)
{
 if (delay) {
  buf[SMART_CHARGE_DELAY] = delay;
  buf[SMART_CHARGE_MODE] = DELAY_MODE;
 } else {
   /* No writing zero, there is a specific mode for it. */
  buf[SMART_CHARGE_MODE] = NO_DELAY_MODE;
 }
}

/* Smart charge */
static ssize_t smart_charge_delay_show(struct device *dev,
           struct device_attribute *attr,
           char *buf)
{
 struct power_supply *psy = to_power_supply(dev);
 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
 u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
 int ret;

 ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf);
 if (ret)
  return ret;

 return sysfs_emit(buf, "%d\n", get_charge_delay(bf));
}

static ssize_t smart_charge_delay_store(struct device *dev,
     struct device_attribute *attr,
     const char *buf, size_t size)
{
 struct power_supply *psy = to_power_supply(dev);
 struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
 u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
 u8 delay;
 int ret;

 if (kstrtou8(buf, 10, &delay))
  return -EINVAL;

 ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf);
 if (ret)
  return ret;

 set_charge_delay(bf, delay);

 ret = gaokun_ec_psy_set_smart_charge(ecbat->ec, bf);
 if (ret)
  return ret;

 return size;
}

static DEVICE_ATTR_RW(smart_charge_delay);

static struct attribute *gaokun_psy_features_attrs[] = {
 &dev_attr_battery_adaptive_charge.attr,
 &dev_attr_smart_charge_delay.attr,
 NULL,
};
ATTRIBUTE_GROUPS(gaokun_psy_features);

static int gaokun_psy_notify(struct notifier_block *nb,
        unsigned long action, void *data)
{
 struct gaokun_psy *ecbat = container_of(nb, struct gaokun_psy, nb);

 switch (action) {
 case EC_EVENT_BAT_A2:
 case EC_EVENT_BAT_B1:
  gaokun_psy_get_bat_info(ecbat);
  return NOTIFY_OK;

 case EC_EVENT_BAT_A0:
  gaokun_psy_get_adp_status(ecbat);
  power_supply_changed(ecbat->adp_psy);
  msleep(10);
  fallthrough;

 case EC_EVENT_BAT_A1:
 case EC_EVENT_BAT_A3:
  if (action == EC_EVENT_BAT_A3) {
   gaokun_psy_get_bat_info(ecbat);
   msleep(100);
  }
  gaokun_psy_get_bat_status(ecbat);
  power_supply_changed(ecbat->bat_psy);
  return NOTIFY_OK;

 default:
  return NOTIFY_DONE;
 }
}

static int gaokun_psy_probe(struct auxiliary_device *adev,
       const struct auxiliary_device_id *id)
{
 struct gaokun_ec *ec = adev->dev.platform_data;
 struct power_supply_config psy_cfg = {};
 struct device *dev = &adev->dev;
 struct gaokun_psy *ecbat;

 ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
 if (!ecbat)
  return -ENOMEM;

 ecbat->ec = ec;
 ecbat->dev = dev;
 ecbat->nb.notifier_call = gaokun_psy_notify;

 auxiliary_set_drvdata(adev, ecbat);

 psy_cfg.drv_data = ecbat;
 ecbat->adp_psy = devm_power_supply_register(dev, &gaokun_psy_adp_desc,
          &psy_cfg);
 if (IS_ERR(ecbat->adp_psy))
  return dev_err_probe(dev, PTR_ERR(ecbat->adp_psy),
         "Failed to register AC power supply\n");

 psy_cfg.supplied_to = (char **)&gaokun_psy_bat_desc.name;
 psy_cfg.num_supplicants = 1;
 psy_cfg.no_wakeup_source = true;
 psy_cfg.attr_grp = gaokun_psy_features_groups;
 ecbat->bat_psy = devm_power_supply_register(dev, &gaokun_psy_bat_desc,
          &psy_cfg);
 if (IS_ERR(ecbat->bat_psy))
  return dev_err_probe(dev, PTR_ERR(ecbat->bat_psy),
         "Failed to register battery power supply\n");
 gaokun_psy_init(ecbat);

 return gaokun_ec_register_notify(ec, &ecbat->nb);
}

static void gaokun_psy_remove(struct auxiliary_device *adev)
{
 struct gaokun_psy *ecbat = auxiliary_get_drvdata(adev);

 gaokun_ec_unregister_notify(ecbat->ec, &ecbat->nb);
}

static const struct auxiliary_device_id gaokun_psy_id_table[] = {
 { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_PSY, },
 {}
};
MODULE_DEVICE_TABLE(auxiliary, gaokun_psy_id_table);

static struct auxiliary_driver gaokun_psy_driver = {
 .name = GAOKUN_DEV_PSY,
 .id_table = gaokun_psy_id_table,
 .probe = gaokun_psy_probe,
 .remove = gaokun_psy_remove,
};

module_auxiliary_driver(gaokun_psy_driver);

MODULE_DESCRIPTION("HUAWEI Matebook E Go psy driver");
MODULE_LICENSE("GPL");

Messung V0.5
C=94 H=95 G=94

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.