#!/bin/sh
# 55-cros-ec - Battery Plugin for laptops with ChromeOS EC:
# 1. Framework 13/16 AMD or Intel
# 2. Chromebooks modded with chrultrabook/coreboot custom UEFI firmware
#
# Requires the cros_charge-control module as of Kernel 6.12.8/6.13 providing the following sysfs nodes:
# * /sys/class/power_supply/BAT[01]/charge_control_start_threshold: 0..100
# * /sys/class/power_supply/BAT[01]/charge_control_end_threshold: 0..100
# * /sys/class/power_supply/BAT[01]/charge_behaviour: auto|inhibit-charge|force-discharge
#
# Copyright (c) 2025 Thomas Koch <linrunner at gmx.net> and others.
# SPDX-License-Identifier: GPL-2.0-or-later

# Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat

# --- Hardware Detection

readonly BATDRV_CROSCC_DL='/sys/bus/platform/devices/cros-charge-control.*.auto/driver'
readonly BATDRV_CROSCC_MIN_KVER="6.12"

batdrv_is_cros_ec () {
    # check if kernel module owns a device
    # rc: 0=laptop w/ ChromeOS EC and cros_charge-control driver active
    #     1=other hardware - or overriden by framework_laptop driver
    local drvl

    # shellcheck disable=SC2086
    drvl="$(readlink $BATDRV_CROSCC_DL)"
    [ "${drvl##*/}" = "cros-charge-control" ]
}

# --- Plugin API functions

batdrv_init () {
    # detect hardware and initialize driver
    # rc: 0=matching hardware detected/1=not detected/2=no batteries detected
    # retval: $_batdrv_plugin, $_batdrv_kmod, $_batdrv_sim
    #
    # 1. check for native kernel acpi (Linux 6.12.8/6.13 or higher required)
    #    --> retval $_natacpi:
    #       0=start and stop threshold/
    #       1=stop threshold only/
    #       32=disabled/
    #       128=no kernel support/
    #       254=laptop not supported (incompatible hard- or firmware)
    #
    # 2. determine method for
    #    reading battery data                   --> retval $_bm_read,
    #    reading/writing charging thresholds    --> retval $_bm_thresh,
    #    reading/writing force discharge        --> retval $_bm_dischg:
    #       none/natacpi
    #
    # 3. define sysfile basenames for natacpi
    #    start threshold                        --> retval $_bn_start,
    #    stop threshold                         --> retval $_bn_stop,
    #    discharge                              --> retval $_bn_discharge
    #
    # 4. determine present batteries
    #    list of batteries (space separated)    --> retval $_batteries;
    #
    # 5. define charge threshold defaults
    #    start threshold                        --> retval $_bt_def_start,
    #    stop threshold                         --> retval $_bt_def_stop;

    _batdrv_plugin="cros-ec"
    _batdrv_kmod="cros_charge-control" # kernel module for natacpi
    _batdrv_sim=0

    # check plugin simulation override and denylist
    if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then
        if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then
            echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate"
            _batdrv_sim=1
        else
            echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip"
            return 1
        fi
    elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then
        echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist"
        return 1
    else
        # check if driver loaded
        if ! batdrv_is_cros_ec; then
            # other hardware or overriden by framework_laptop driver -> try next plugin
            echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match"
            return 1
        fi
    fi

    # presume no features at all
    _natacpi=128
    _bm_read="natacpi"
    _bm_thresh="none"
    _bm_dischg="none"
    _bn_start=""
    _bn_stop=""
    _bn_dischg=""
    _batteries=""
    _bt_def_start=0
    _bt_def_stop=100

    # iterate batteries and check for native kernel ACPI
    local bd bs
    local done=0
    for bd in "$ACPIBATDIR"/BAT[01]; do
        if [ "$(read_sysf "$bd/present")" = "1" ]; then
            # record detected batteries
            bs=${bd##/*/}
            if [ -n "$_batteries" ]; then
                _batteries="$_batteries $bs"
            else
                _batteries="$bs"
            fi
            # skip natacpi detection for 2nd and subsequent batteries
            [ $done -eq 1 ] && continue

            done=1
            if [ "$NATACPI_ENABLE" = "0" ]; then
                # natacpi disabled in configuration --> skip actual detection
                _natacpi=32
                continue
            fi

            if readable_sysf "$bd/charge_control_start_threshold" \
                && readable_sysf "$bd/charge_control_end_threshold" \
                && readable_sysf "$bd/charge_behaviour" \
                && [ "$X_BAT_CROSCC_SIMULATE_ECVER" != "2" ] \
                && [ "$X_BAT_CROSCC_SIMULATE_ECVER" != "1" ]; then
                # all sysfiles exist and are actually readable
                if kernel_version_ge "$BATDRV_CROSCC_MIN_KVER"; then
                    # patched kernel -> EC cmd v3 w/ start and stop threshold
                    _bn_start="charge_control_start_threshold"
                    _bn_stop="charge_control_end_threshold"
                    _bm_thresh="natacpi"
                    _bn_dischg="charge_behaviour"
                    _bm_dischg="natacpi"
                    _natacpi=0
                else
                    # unpatched kernel:
                    # - No distinction can be made between EC cmd v2 and v3
                    # - start == stop cannot be set for v2
                    # -> insufficient kernel support
                    _natacpi=128
                fi
            elif [ "$X_BAT_CROSCC_SIMULATE_ECVER" = "2" ] \
                || [ ! -f "$bd/charge_control_start_threshold" ] \
                && readable_sysf "$bd/charge_control_end_threshold" \
                && readable_sysf "$bd/charge_behaviour" \
                && [ "$X_BAT_CROSCC_SIMULATE_ECVER" != "1" ]; then
                # patched kernel -> EC cmd v2 w/ stop threshold
                _bn_stop="charge_control_end_threshold"
                _bm_thresh="natacpi"
                _bn_dischg="charge_behaviour"
                _bm_dischg="natacpi"
                _natacpi=1
            else
                # EC cmd v1 w/o thresholds i.e unsupported firmware
                _natacpi=254
            fi
        fi
    done

    # quit if no battery detected, there is no point in activating the plugin
    if [ -z "$_batteries" ]; then
        echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries"
        return 2
    fi

    # shellcheck disable=SC2034
    _batdrv_selected=$_batdrv_plugin
    echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg; bn_dischg=$_bn_dischg"
    return 0
}

batdrv_select_battery () {
    # determine battery acpidir and sysfiles
    # $1: BAT0/BAT1/DEF
    # global params: $_batdrv_plugin, $_natacpi, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg
    # rc: 0=bat exists/1=bat non-existent
    # retval: $_bat_str:    BAT0/BAT1/<other>;
    #         $_bt_cfg_bat: config suffix (BAT0/BAT1);
    #         $_bd_read:    directory with battery data sysfiles;
    #         $_bf_start:   sysfile for start threshold;
    #         $_bf_stop:    sysfile for stop threshold;
    #         $_bf_status:  sysfile for status read
    # prerequisite: batdrv_init()

    # defaults
    _bat_str=""    # no bat
    _bt_cfg_bat=""
    _bd_read=""    # no directory
    _bf_start=""
    _bf_stop=""

    local bat="$1"

    # convert battery param to uppercase
    bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")"

    # validate battery param
    case "$bat" in
        DEF) # 1st battery is default
            _bat_str="${_batteries%% *}"
            ;;

        *)
            if wordinlist "$bat" "$_batteries"; then
                _bat_str="$bat"
            else
                # battery not present --> quit
                echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present"
                return 1
            fi
            ;;
    esac

    # config suffix equals battery name
    _bt_cfg_bat="$_bat_str"

    # determine natacpi sysfiles
    _bd_read="$ACPIBATDIR/$_bat_str"
    if [ "$_bm_thresh" = "natacpi" ]; then
        [ "$_natacpi" = "0" ] && _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start"
        _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop"
    fi

    if [ "$_bm_dischg" = "natacpi" ]; then
        _bf_dischg="$ACPIBATDIR/$_bat_str/$_bn_dischg"
        _bf_status="$ACPIBATDIR/$_bat_str/status"
    fi

    echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop; bf_dischg=$_bf_dischg"
    return 0
}

batdrv_read_threshold () {
    # read and print charge threshold
    # $1: start/stop
    # $2: 0=api/1=tlp-stat output
    # global params: $_batdrv_plugin, $_natapci, $_bm_thresh, $_bf_start, $_bf_stop
    # out:
    # - api: 0..100/"" on error
    # - tlp-stat: 0..100/"(not available)" on error
    # rc: 0=ok/4=read error/255=no api
    # prerequisite: batdrv_init(), batdrv_select_battery()

    local bf out="" rc=0

    if  [ "$1" = "start" ] && [ "$_natacpi" = "1" ]; then
        # EC cmd v2 w/o start
        [ "$2" = "1" ] && printf "(not available)"
        return 0
    fi

    case "$1" in
        start) out="$X_THRESH_SIMULATE_START" ;;
        stop)  out="$X_THRESH_SIMULATE_STOP"  ;;
    esac
    if [ -n "$out" ]; then
        printf "%s" "$out"
        echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc"
        return 0
    fi

    if [ "$_bm_thresh" = "natacpi" ]; then
        # read threshold from sysfile
        case "$1" in
            start) bf=$_bf_start ;;
            stop)  bf=$_bf_stop  ;;
        esac
        if ! out=$(read_sysf "$bf"); then
            # not readable/non-existent
            [ "$2" = "1" ] && out="(not available)"
            rc=4
        fi
    else
        # no threshold api
        rc=255
    fi

    # "return" threshold
    if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then
        printf "%s" "$out"
    else
        [ "$2" = "1" ] && printf "(not available)"
        rc=4
    fi

    echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc"
    return $rc
}

batdrv_write_thresholds () {
    # write both charge thresholds for a battery
    # use pre-determined method and sysfiles from global parms
    # $1: new start threshold 0(disabled)..99/DEF(disabled)
    # $2: new stop threshold 1..100/DEF(default)
    # $3: 0=quiet/1=output parameter errors/2=output progress and errors
    # $4: non-empty string indicates thresholds stem from configuration
    # global params: $_batdrv_plugin, $_natacpi, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop
    # rc: 0=ok/
    #     1=not configured/
    #     2=threshold(s) out of range or non-numeric/
    #     3=minimum start stop diff violated/
    #     4=threshold read error/
    #     5=threshold write error
    # prerequisite: batdrv_init(), batdrv_select_battery()
    local new_start="${1:-}"
    local new_stop="${2:-}"
    local verb="${3:-0}"
    local old_start old_stop

    # insert defaults
    [ "$new_start" = "DEF" ] && new_start=$_bt_def_start
    [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop

    # --- validate thresholds
    local rc

    if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then
        # do nothing if unconfigured
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat"
        return 1
    fi

    # start: check for 3 digits max, ensure min 0 / max 99 -- EC cmd v3 only
    if  [ "$_natacpi" = "0" ] && \
       { ! is_uint "$new_start" 3 || ! is_within_bounds "$new_start" 0 99; }; then
        # threshold out of range
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat"
        case $verb in
            1)
                if [ -n "$4" ]; then
                    echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (0..99). Battery skipped."
                fi
                ;;

            2)
                if [ -n "$4" ]; then
                    cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (0..99). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2
                else
                    cprintf "" "Error: start charge threshold (%s) for %s is not specified, invalid or out of range (0..99). Aborted.\n" "$new_start" "$_bat_str" 1>&2
                fi
                ;;
        esac
        return 2
    fi

    # stop: check for 3 digits max, ensure min 1 / max 100
    if ! is_uint "$new_stop" 3 || \
       ! is_within_bounds "$new_stop" 1 100; then
        # threshold out of range
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat"
        case $verb in
            1)
                if [ -n "$4" ]; then
                    echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (1..100). Battery skipped."
                fi
                ;;

            2)
                if [ -n "$4" ]; then
                    cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (1..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2
                else
                    cprintf "" "Error: stop charge threshold (%s) for %s is not specified, invalid or out of range (1..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2
                fi
                ;;
        esac
        return 2
    fi

    # check start < stop   -- EC cmd v3 only
    if  [ "$_natacpi" = "0" ] && [ "$new_start" -ge "$new_stop" ]; then
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat"
        case $verb in
            1)
                if [ -n "$4" ]; then
                    echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} >= STOP_CHARGE_THRESH_$_bt_cfg_bat. Battery skipped."
                fi
                ;;

            2)
                if [ -n "$4" ]; then
                    cprintf "" "Error in configuration: START_CHARGE_THRESH_%s >= STOP_CHARGE_THRESH_%s. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2
                else
                    cprintf "" "Error: start threshold >= stop threshold for %s. Aborted.\n" "$_bat_str" 1>&2
                fi
                ;;
        esac
        return 3
    fi

    # read active threshold values
    if ! old_start=$(batdrv_read_threshold start 0) || \
       ! old_stop=$(batdrv_read_threshold stop 0); then
        echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat"
        case $verb in
            1) echo_message "Error: could not read current charge threshold(s) for $_bat_str. Battery skipped." ;;
            2) cprintf "" "Error: could not read current charge threshold(s) for %s. Aborted.\n" "$_bat_str" 1>&2 ;;
        esac
        return 4
    fi

    # determine write sequence too meet boundary condition start < stop
    # disclaimer: the driver doesn't enforce it but we don't know about the
    # firmware and it seems reasonable anyway
    local rc=0 steprc tseq

    if [ "$_natacpi" = "0" ]; then
        # EC cmd v3
        if [ "$new_start" -ge "$old_stop" ]; then
            tseq="stop start"
        else
            tseq="start stop"
        fi
    else
        # EC cmd v2
        tseq="stop"
    fi

    # write new thresholds in determined sequence
    if [ "$verb" = "2" ]; then
        printf "Setting temporary charge threshold(s) for battery %s:\n" "$_bat_str" 1>&2
    fi

    for step in $tseq; do
        local old_thresh new_thresh steprc

        case $step in
            start)
                old_thresh=$old_start
                new_thresh=$new_start
                ;;

            stop)
                old_thresh=$old_stop
                new_thresh=$new_stop
                ;;
        esac

        if [ "$old_thresh" != "$new_thresh" ]; then
            # new threshold differs from effective one --> write it
            case $step in
                start) write_sysf "$new_thresh" "$_bf_start" ;;
                stop)  write_sysf "$new_thresh" "$_bf_stop"  ;;
            esac
            steprc=$?; [ $steprc -ne 0 ] && rc=5
            echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc"

            case $verb in
                2)
                    if [ $steprc -eq 0 ]; then
                        printf        "  %-5s = %3d\n" "$step" "$new_thresh" 1>&2
                    else
                        cprintf "err" "  %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2
                    fi
                    ;;
                1)
                    if [ $steprc -gt 0 ]; then
                        echo_message "Error: writing $step charge threshold for $_bat_str failed."
                    fi
                    ;;
            esac
        else
            echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh"

            if [ "$verb" = "2" ]; then
                printf "  %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2
            fi
        fi
    done # for step

    echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc"
    return $rc
}

batdrv_calc_soc () {
    # calc and print battery charge level (rounded)
    # $1: format (optional)
    # global param: $_bd_read
    # prerequisite: batdrv_init(), batdrv_select_battery()
    # rc: 0=ok/1=charge level read error
    local cf cn

    if [ -f "$_bd_read/charge_full" ]; then
        # real hardware
        cf=$(read_sysval "$_bd_read/charge_full")
        cn=$(read_sysval "$_bd_read/charge_now")
    elif [ -f "$_bd_read/energy_full" ]; then
        # simulation - on ThinkPad only
        cf=$(read_sysval "$_bd_read/energy_full")
        cn=$(read_sysval "$_bd_read/energy_now")
    else
        cf=0
        cn=0
    fi

    if [ "$cf" != "0" ]; then
        if [ -n "$1" ]; then
            perl -e 'printf ("'"$1"'", 100.0 * '"$cn"' / '"$cf"')'
        else
            perl -e 'printf ("%d", int(100.0 * '"$cn"' / '"$cf"' + 0.5))'
        fi
        return 0
    else
        printf "255"
        return 1
    fi
}

batdrv_chargeonce () {
    # charge battery to stop threshold once
    # use pre-determined method and sysfiles from global parms
    # global params: $_batdrv_plugin, $_natacpi, $_bm_thresh, $_bat_str, $_bf_start, $_bf_stop
    # rc: 0=ok/
    #     2=charge level read error/
    #     3=charge level too high/
    #     4=threshold read error/
    #     5=threshold write error/
    #     255=not implemented (EC cmd v2)
    # prerequisite: batdrv_init(), batdrv_select_battery()

    local soc cur_stop temp_start
    local rc=0

    if [ "$_natacpi" = "1" ]; then
        # EC cmd v2
        echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented"
        return 255
    fi

    if ! cur_stop=$(batdrv_read_threshold stop 0); then
        cprintf "" "Error: reading stop charge threshold of battery %s failed. Aborted.\n" "$_bat_str" 1>&2
        echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).thresh_unknown: stop=$cur_stop; rc=4"
        return 4
    fi

    # get current charge level (in %)
    if ! soc="$(batdrv_calc_soc)"; then
        cprintf "" "Error: cannot determine charge level of battery %s.\n" "$_bat_str" 1>&2
        echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_unknown; rc=2"
        return 2
    fi

    temp_start=$(( cur_stop - 1 ))
    if [ "$soc" -gt "$temp_start" ]; then
        cprintf "" "Error: the charge level of battery %s is %s%%. " "$_bat_str" "$soc"  1>&2
        cprintf "err" "For this command to work, it must not be higher than %s%% (stop threshold - 1).\n" "$temp_start" 1>&2
        echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_too_high: soc=$soc; stop=$cur_stop; rc=3"
        return 3
    fi

    printf "Current charge level of battery %s is %s%%, we are about to charge to %s%%.\n" "$_bat_str" "$soc" "$cur_stop" 1>&2
    printf "Setting temporary charge threshold for battery %s:\n" "$_bat_str" 1>&2
    write_sysf "$temp_start" "$_bf_start" || rc=5
    if [ $rc -eq 0 ]; then
        printf "  start = %3d\n" "$temp_start" 1>&2
        cprintf "notice" "Charging starts now, keep the charger connected.\n"  1>&2
    else
        cprintf "err" "  start = %3d (Error: write failed)\n" "$temp_start" 1>&2
    fi
    echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str): soc=$soc; start=$temp_start; stop=$cur_stop; rc=$rc"

    return $rc
}

batdrv_apply_configured_thresholds () {
    # apply configured stop thresholds from configuration to all batteries
    # - called for bg tasks tlp init [re]start/auto and tlp start
    # output parameter errors only
    # global params: $_bt_cfg_bat
    # prerequisite: batdrv_init()

    local bat start_thresh stop_thresh

    for bat in BAT0 BAT1; do
        if batdrv_select_battery "$bat"; then
            eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}"
            eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}"
            batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1
        fi
    done

    return 0
}

batdrv_read_force_discharge () {
    # read and print force-discharge state
    # $1: 0=api/1=tlp-stat output
    # global params: $_batdrv_plugin, $_bm_dischg, $_bat_str, $_bf_dischg
    # out:
    # - api: 0=off/1=on/"" on error
    # - tlp-stat: status text/"(not available)" on error
    # rc: 0=ok/4=read error/255=no api
    # prerequisite: batdrv_init(), batdrv_select_battery()

    local rc=0 out=""

    case $_bm_dischg in
        natacpi)
            # read state from sysfile
            if out=$(read_sysf "$_bf_dischg"); then
                if [ "$1" != "1" ]; then
                    # api output
                    if echo "$out" | grep -q "\[force-discharge\]"; then
                        out=1
                    else
                        out=0
                    fi
                fi
            else
                # not readable/non-existent
                [ "$1" = "1" ] && out="(not available)"
                rc=4
            fi
            ;;

        *) # no discharge api
            [ "$1" = "1" ] && out="(not available)"
            rc=255
            ;;
    esac

    # "return" force-discharge
    if [ "$X_DISCHG_SIMULATE_READERR" != "1" ]; then
        printf "%s" "$out"
    else
        [ "$1" = "1" ] && printf "(not available)\n"
        rc=4
    fi

    if [ "$rc" -gt 0 ]; then
        # log output in the error case only
        echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge($_bat_str): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; out=$out; rc=$rc"
    fi
    return $rc
}

batdrv_write_force_discharge () {
    # write force discharge state
    # $1: 0=off/1=on
    # global params: $_batdrv_plugin, $_bat_str, $_bm_dischg, $_bf_dischg
    # rc: 0=done/5=write error/255=no api
    # prerequisite: batdrv_init(), batdrv_select_battery()

    local rc=0

    case $_bm_dischg in
        natacpi)
            # write force_discharge
            case "$1" in
                0) write_sysf "auto" "$_bf_dischg" || rc=5 ;;
                1) write_sysf "force-discharge" "$_bf_dischg" || rc=5 ;;
            esac
            ;; # natacpi

        *) # no discharge api
            rc=255
            ;;
    esac

    echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge($_bat_str, $1): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; rc=$rc"
    return $rc
}

batdrv_cancel_force_discharge () {
    # trap: called from batdrv_discharge
    # global params: $_batdrv_plugin, $_bat_str
    # prerequisite: batdrv_discharge()

    batdrv_write_force_discharge 0
    unlock_tlp tlp_discharge
    echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.cancelled($_bat_str)"
    printf " Cancelled.\n"

    do_exit 5
}

batdrv_force_discharge_active () {
    # check battery soc and terminate 'force-discharge' in time *before* the battery is empty
    # DANGER: Framework EC firmware does *not* automatically terminate discharge, the laptop switches off hard instead
    # global params: $_batdrv_plugin, $_batdrv_sim, $_bat_str, $_bm_read, $_bd_read, $_bat_target_ch
    # rc: 0=discharging/1=not discharging/2=AC detached/4=terminated/6=target soc reached/255=internal error
    # retval: $_bat_cn, $_bat_fd, $_bat_cur, $_bat_limit, $_bat_soc, $_bat_st, $_bat_volt, $_bat_dischg_rc
    # prerequisite: batdrv_init(), batdrv_select_battery()

    # battery readings to be returned to caller as retvals (and for trace output)
    case "$_batdrv_sim" in
        0) # runs on native hardware
            _bat_cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')"
            _bat_soc="$(batdrv_calc_soc)"
            _bat_cur="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/current_now")"' / 1000.0 );')"
            _bat_volt="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/voltage_now")"' / 1000.0 );')"

            # Calculate minimum charge [mAh] where force-discharge must be terminated:
            # Momentary discharge current [mA] multiplied by the 5 sec i.e. 1h / 720 test interval,
            # plus a 50% (1.5x) safety margin
            _bat_limit=$(perl -e 'printf ("%d", 2.0 * '"$_bat_cur"' / 720.0 + '"$_bat_target_ch"' );')
            ;;

        1) # simulation on ThinkPad
            _bat_cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')"
            _bat_soc="$(batdrv_calc_soc)"
            _bat_cur="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/power_now")"' / 1000.0 );')"
            _bat_volt="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/voltage_now")"' / 1000.0 );')"

            # Calculate minimum charge [mWh] where force-discharge must be terminated:
            # Momentary discharge power [mW] multiplied by the 5 sec i.e. 1h / 720 test interval,
            # plus a 50% (1.5x) safety margin
            _bat_limit=$(perl -e 'printf ("%d", 2.0 * '"$_bat_cur"' / 720.0 + '"$_bat_target_ch"');')
            ;;
    esac

    _bat_st="$(read_sysf "$_bd_read/status")"
    _bat_fd="$(batdrv_read_force_discharge 0)"

    if [ "$_bat_cn" -lt "$_bat_limit" ]; then
       # limit is reached -> cancel force-discharge immediately to prevent sudden poweroff
       batdrv_write_force_discharge 0
       _bat_dischg_rc=6
    elif ! get_sys_power_supply; then
        # AC detached --> cancel discharge immediately
        _bat_dischg_rc=2
    elif [ "$_bat_fd" = "0" ]; then
        # force_discharge was reset (by some other process, not by the ec) --> quit
        _bat_dischg_rc=4
    else
        # check battery status
        case "$_bat_st" in
            Discharging)
                # battery is discharging
                _bat_dischg_rc=0
                ;;

            *)  # battery is not discharging
                _bat_dischg_rc=1
                ;;
        esac
    fi

    case "$_bat_dischg_rc" in
        0) echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.running($_bat_str): fd=$_bat_fd; st=$_bat_st; target=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; cur=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" ;;
        1) echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.not_discharging($_bat_str): fd=$_bat_fd; st=$_bat_st; target=$_bat_target_ch; soc=$_bat_soc; cn=$_bat_cn; cur=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" ;;
        2) echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.ac_detached($_bat_str): fd=$_bat_fd; st=$_bat_st; target=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; cur=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" ;;
        4) echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.terminated($_bat_str): fd=$_bat_fd; st=$_bat_st; target=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; curr=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" ;;
        6) echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.target_soc($_bat_str): fd=$_bat_fd; st=$_bat_st; target=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; cur=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" ;;
        *) echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.int_err($_bat_str): fd=$_bat_fd; st=$_bat_st; target=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; cur=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" ;;
    esac

    return $_bat_dischg_rc
}

batdrv_discharge_safetylock () {
    # check safety lock
    # $1: discharge/recalibrate
    # rc: 0=engaged/1=disengaged

    local mode
    mode="${1:-discharge}"

    if [ "$CROS_EC_UNBLOCK_DISCHARGE" != "1" ]; then
        # safety lock engaged
        echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.safetylock"
        cecho "Caution: ${mode} operation is blocked for safety reasons." "err" 1>&2
        echo 1>&2
        cecho "Your laptop's EC firmware will not stop until the battery is completely discharged." "err" 1>&2
        cecho "This will cause the laptop to suddenly power off, which can lead to data loss and data corruption." "err" 1>&2
        cecho "TLP will stop discharging *before* this happens, but it can't respond to unforeseen battery behavior." "err" 1>&2
        echo 1>&2
        cecho "Add CROS_EC_UNBLOCK_DISCHARGE=1 to your configuration to accept the risk and unblock ${mode}." "notice" 1>&2
        cecho "Make sure all data is saved and backups are in place." "notice" 1>&2
        echo 1>&2
        return 0
    else
        # safety lock disengaged by user configuration
        return 1
    fi
}

batdrv_discharge () {
    # discharge battery
    # global params: $_batdrv_plugin, $_batdrv_sim, $_bm_dischg, $_bat_str, $_bat_idx, $_bd_read, $_bf_dischg
    # rc: 0=done/1=malfunction/2=AC detached/3=not emptied/4=already empty/5=cancelled by ^C/6=target soc reached/7=target soc out of bounds/8=force-discharge malfunction/9=charge level read error/16=blocked

    # prerequisite: batdrv_init(), batdrv_select_battery()
    local cf cn soc target_soc wt

    if ! soc="$(batdrv_calc_soc)"; then
        _bat_dischg_rc=9
        echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge($_bat_str).charge_level_unknown; rc=$_bat_dischg_rc"
        cecho "Error: cannot read charge level of battery ${_bat_str}."  1>&2
        return $_bat_dischg_rc
    fi

    target_soc="${1:-0}"
    if is_uint "$target_soc"; then
        if ! is_within_bounds "$target_soc" 0 99; then
            echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_out_of_bounds($_bat_str): target=$target_soc"
            cprintf "" "Error: target charge level (%s) for battery %s is out of range (0..99).\n" "$target_soc" "$_bat_str" 1>&2
            _bat_dischg_rc=7
            return $_bat_dischg_rc
        fi
    fi
    case "$_batdrv_sim" in
        0) # runs on native hardware
            cf="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_full")"' / 1000.0 );')"
            _bat_target_ch="$(perl -e 'printf ("%d", '"$cf"' * '"$target_soc"' / 100.0)')"
            cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')"
            ;;

        1) # simulation on ThinkPad
            cf="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_full")"' / 1000.0 );')"
            _bat_target_ch="$(perl -e 'printf ("%d", '"$cf"' * '"$target_soc"' / 100.0)')"
            cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')"
            ;;
    esac

    if [ "$_bat_target_ch" -ge "$cn" ]; then
        echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_below_soc($_bat_str): target=$target_soc; soc=$soc"
        cprintf "" "Error: target level (%s%%) is too high compared to the actual charge level (%s%%) of battery %s.\n" "$target_soc" "$soc" "$_bat_str" 1>&2
        _bat_dischg_rc=7
        return $_bat_dischg_rc
    fi

    ########
    ## local rtime
    ## _bat_dischg_rc=255

    # start discharge
    if ! batdrv_write_force_discharge 1; then
        echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.force_discharge_write_error($_bat_str)"
        cecho "Error: discharge $_bat_str malfunction -- check your hardware (battery, charger)." 1>&2
        unlock_tlp tlp_discharge
        return 8
    fi

    trap batdrv_cancel_force_discharge INT TERM # enable ^C and error hook

    # wait for start == while status not "discharging" -- 15.0 sec timeout
    printf "Initiating discharge of battery %s " "$_bat_str" 1>&2
    wt=15
    while ! batdrv_force_discharge_active && [ $wt -gt 0 ] ; do
        if [ "$_bat_dischg_rc" = "6" ]; then
            # limit already undercut - do not continue
            _bat_dischg_rc=4
            echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_soc($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur;  volt=$_bat_volt; rc=$_bat_dischg_rc"
            echo 1>&2
            cecho "Error: actual charge level of battery $_bat_str is too close to the target level." "err" 1>&2
            return $_bat_dischg_rc
        fi
        sleep 1
        printf "." 1>&2
        wt=$((wt - 1))
    done
    printf "\n" 1>&2

    if batdrv_force_discharge_active; then
        # discharge initiated successfully --> wait for completion
        while batdrv_force_discharge_active; do
            clear
            printf "Currently discharging battery %s to %s%% (%s mAh):\n" "$_bat_str" "$target_soc" "$_bat_target_ch" 1>&2

            # show current battery state
            if [ -n "$_bat_volt" ]; then
                printf "voltage            = %6s [mV]\n"  "$_bat_volt" 1>&2
            else
                printf "voltage            = not available [mV]\n" 1>&2
            fi

            case "$_batdrv_sim" in
                0) # runs on native hardware
                    printf "remaining charge   = %6d [mAh]\n" "$_bat_cn" 1>&2
                    printf "safety cut-off     = %6d [mAh]\n" "$_bat_limit" 1>&2
                    ;;

                1) # simulation on ThinkPad
                    printf "remaining charge   = %6d [mWh]\n" "$_bat_cn" 1>&2
                    printf "safety cut-off     = %6d [mWh]\n" "$_bat_limit" 1>&2
                    ;;
            esac

            if soc="$(batdrv_calc_soc "%6.1f")"; then
                printf "remaining percent  = %6s [%%]\n" "$soc"  1>&2
            else
                printf "remaining percent  = n/a [%%]\n" 1>&2
            fi

            if [ "$_bat_cur" != "0" ]; then
                perl -e 'printf ("remaining time     = %6d [min]\n", 60.0 * ('"$_bat_cn"' - '"$_bat_target_ch"') / '"$_bat_cur"');' 1>&2
            else
                printf "remaining time     = not discharging [min]\n" 1>&2
            fi
            case "$_batdrv_sim" in
                0) printf "current            = %6s [mA]\n"  "$_bat_cur" 1>&2 ;;
                1) printf "current            = %6s [mW]\n"  "$_bat_cur" 1>&2 ;;
            esac

            printf "status             = %s\n" "$_bat_st" 1>&2
            printf "force-discharge    = %s\n" "$_bat_fd" 1>&2
            cecho "Caution: make sure all data is saved and backups are in place." "err" 1>&2
            echo "Press Ctrl+C to cancel." 1>&2
            sleep 5
        done

        # cancel force-discharge because EC cannot do it on its own
        batdrv_write_force_discharge 0

        # check exit cause
        case "$_bat_dischg_rc" in
            2) # system on battery, AC power removed
                echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.ac_detached($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc"
                cecho "Warning: battery $_bat_str discharge has stopped early -- AC/charger removed." 1>&2
                ;;

            1|4) # discharging stopped(1) or force-discharge reset (3) before limit was reached
                if [ "$target_soc" -eq 0 ] && [ "$_bat_soc" -gt 1 ]; then
                    # system on AC, target soc = 0, SOC > 1%, force-discharge ended prematurely
                    _bat_dischg_rc=3
                    echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_completed($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc"
                    cecho "Error: battery $_bat_str discharge was aborted early -- check your hardware (battery, charger)." 1>&2
                elif [ "$target_soc" -gt 0 ]; then
                    # system on AC, target soc > 0, force-discharge ended prematurely
                    _bat_dischg_rc=3
                    echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_completed($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc"
                    cecho "Error: battery $_bat_str discharge was aborted early -- check your hardware (battery, charger)." 1>&2
                else
                    # battery discharged completely
                    _bat_dischg_rc=0
                    echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.complete($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur;  volt=$_bat_volt; rc=$_bat_dischg_rc"
                    cecho "Done: battery $_bat_str discharge complete." "success"  1>&2
                fi
                ;;

            6) # target soc reached resp. limit undercut
                _bat_dischg_rc=0
                echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_soc($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur;  volt=$_bat_volt; rc=$_bat_dischg_rc"
                cecho "Done: battery $_bat_str discharge complete." "success" 1>&2
                ;;
        esac
    else
        # force-discharge init malfunction
        _bat_dischg_rc=8
        echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.init_error($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc"
        cecho "Error: discharge $_bat_str malfunction -- check your hardware (battery, charger)." 1>&2
    fi

    trap - INT TERM # remove ^C and error hook

    return $_bat_dischg_rc
}

batdrv_show_battery_data () {
    # output battery status
    # $1: 1=verbose
    # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg
    # prerequisite: batdrv_init()

    local verbose="${1:-0}"

    printf "+++ Battery Care\n"
    printf "Plugin: %s\n" "$_batdrv_plugin"

    if [ "$_bm_thresh" != "none" ]; then
        case $_natacpi in
            0) cecho "Supported features: charge thresholds, recalibration" "success" ;;
            1) cecho "Supported features: charge threshold, recalibration" "success" ;;
        esac
    else
        cprintf "warning" "Supported features: none available\n"
    fi

    printf "Driver usage:\n"
    # native kernel ACPI battery API
    case $_natacpi in
        0)   cprintf "success" "* natacpi (%s) = active (charge thresholds, recalibration) - EC cmd v3\n" "$_batdrv_kmod" ;;
        1)   cprintf "success" "* natacpi (%s) = active (charge threshold, recalibration) - EC cmd v2\n" "$_batdrv_kmod" ;;
        32)  cprintf "notice"  "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;;
        128) cprintf "warning" "* natacpi (%s) = inactive (insufficient kernel support)\n" "$_batdrv_kmod" ;;
        254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported) - EC cmd v1\n" "$_batdrv_kmod" ;;
        *)   cprintf "err"     "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;;
    esac
    if [ "$_bm_thresh" != "none" ]; then
        printf "Parameter value ranges:\n"
        [ "$_natacpi" = "0" ] &&  printf "* START_CHARGE_THRESH_BAT0/1:  0(off)..99\n"
        printf "* STOP_CHARGE_THRESH_BAT0/1:   1..100(default)\n"
    fi
    printf "\n"

    # -- show battery data
    local bat
    local bcnt=0
    local ed ef en
    local efsum=0
    local ensum=0

    for bat in $_batteries; do # iterate batteries
        batdrv_select_battery "$bat"

        printf "+++ Battery Status: %s\n" "$bat"

        printparm "%-59s = ##%s##" "$_bd_read/manufacturer"
        printparm "%-59s = ##%s##" "$_bd_read/model_name"

        print_battery_cycle_count "$_bd_read/cycle_count" "$(read_sysf "$_bd_read/cycle_count")"

        if [ -f "$_bd_read/energy_full" ]; then
            printparm "%-59s = ##%6d## [mWh]" "$_bd_read/energy_full_design" "" 000
            printparm "%-59s = ##%6d## [mWh]" "$_bd_read/energy_full" "" 000
            printparm "%-59s = ##%6d## [mWh]" "$_bd_read/energy_now" "" 000
            printparm "%-59s = ##%6d## [mW]"  "$_bd_read/power_now" "" 000

            # store values for charge / capacity calculation below
            ed=$(read_sysval "$_bd_read/energy_full_design")
            ef=$(read_sysval "$_bd_read/energy_full")
            en=$(read_sysval "$_bd_read/energy_now")
            efsum=$((efsum + ef))
            ensum=$((ensum + en))

        elif [ -f "$_bd_read/charge_full" ]; then
            printparm "%-59s = ##%6d## [mAh]" "$_bd_read/charge_full_design" "" 000
            printparm "%-59s = ##%6d## [mAh]" "$_bd_read/charge_full" "" 000
            printparm "%-59s = ##%6d## [mAh]" "$_bd_read/charge_now" "" 000
            printparm "%-59s = ##%6d## [mA]" "$_bd_read/current_now" "" 000

            # store values for charge / capacity calculation below
            ed=$(read_sysval "$_bd_read/charge_full_design")
            ef=$(read_sysval "$_bd_read/charge_full")
            en=$(read_sysval "$_bd_read/charge_now")
            efsum=$((efsum + ef))
            ensum=$((ensum + en))

        else
            ed=0
            ef=0
            en=0
        fi

        print_batstate "$_bd_read/status"
        printf "\n"

        if [ "$verbose" -eq 1 ]; then
            printparm "%-59s = ##%6s## [mV]" "$_bd_read/voltage_min_design" "" 000
            printparm "%-59s = ##%6s## [mV]" "$_bd_read/voltage_now" "" 000
            printf "\n"
        fi

        # --- show battery features: thresholds, force_discharge
        local lf=0
        if [ "$_bm_thresh" = "natacpi" ]; then
            [ "$_natacpi" = "1" ] || printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start "1")"
            printf "%-59s = %6s [%%]\n" "$_bf_stop"  "$(batdrv_read_threshold stop "1")"
            lf=1
        fi
        if [ "$_bm_dischg" = "natacpi" ]; then
            printf "%-59s = %6s\n" "$_bf_dischg" "$(batdrv_read_force_discharge 1)"
            lf=1
        fi
        [ $lf -gt 0 ] && printf "\n"

        # --- show charge level (SOC) and capacity
        lf=0
        if [ "$ef" -ne 0 ]; then
            perl -e 'printf ("%-59s = %6.1f [%%]\n", "Charge",   100.0 * '"$en"' / '"$ef"');'
            lf=1
        fi
        if [ "$ed" -ne 0 ]; then
            perl -e 'printf ("%-59s = %6.1f [%%]\n", "Capacity", 100.0 * '"$ef"' / '"$ed"');'
            lf=1
        fi
        [ "$lf" -gt 0 ] && printf "\n"

        bcnt=$((bcnt+1))

    done # for bat

    if [ $bcnt -gt 1 ] && [ $efsum -ne 0 ]; then
        # more than one battery detected --> show charge total
        perl -e 'printf ("%-59s = %6.1f [%%]\n", "+++ Charge total",   100.0 * '"$ensum"' / '"$efsum"');'
        printf "\n"
    fi

    return 0
}

batdrv_check_soc_gt_stop () {
    # check if battery charge level (SOC) is greater than the stop threshold
    # rc: 0=greater/1=less or equal (or thresholds not supported)
    # global params: $_bm_thresh, $_bat_str
    # prerequisite: batdrv_init(), batdrv_select_battery()

    local soc stop

    if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then
        stop="$(batdrv_read_threshold stop 0)"
        if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then
             return 0
        fi
    fi

    return 1
}

batdrv_recommendations () {
    # output specific recommendations
    # prerequisite: batdrv_init()

    soc_gt_stop_recommendation

    return 0
}
