/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>

#include "os/mynewt.h"
#include "charge-control/charge_control.h"
#include "charge_control_priv.h"

/* =================================================================
 * ====================== LOCAL FUNCTION DECLARATIONS ==============
 * =================================================================
 */

/* ---------------------------- OS -------------------------------- */

static void charge_control_read_ev_cb(struct os_event *ev);

static void charge_control_mgr_wakeup_event(struct os_event *);
static void charge_control_base_ts_update_event(struct os_event *);

/* ---------------------- CHARGE CONTROL --------------------------- */

/**
 * Prevents changes to a charge control device during an operation
 *
 * @param Charge control device to lock
 *
 * @return 0 on success, non-zero error code on failure
 */
static int charge_control_lock(struct charge_control *);

/**
 * Releases mutex lock on charge control device
 *
 * @param Charge control device to release
 */
static void charge_control_unlock(struct charge_control *);

/**
 * Thread-safe operation to change poll rate on charge control device
 */
static void
charge_control_update_poll_rate(struct charge_control *, uint32_t);

static os_time_t
charge_control_calc_nextrun_delta(struct charge_control *, os_time_t);

static struct charge_control *
charge_control_find_min_nextrun(os_time_t, os_time_t *);

static void
charge_control_update_nextrun(struct charge_control *, os_time_t);

static int
charge_control_read_data_func(struct charge_control *, void *, void *,
        charge_control_type_t);

static void
charge_control_up_timestamp(struct charge_control *);

static int
charge_control_insert_type_trait(struct charge_control *,
        struct charge_control_type_traits *);

static int
charge_control_remove_type_trait(struct charge_control *,
        struct charge_control_type_traits *);

/**
 * Search the charge control type traits list for a specific type of charge
 * controller.  Set the charge control pointer to NULL to start from the
 * beginning of the list or to a specific charge control object to begin from
 * that point in the list.
 *
 * @param The charge controller type to search for
 * @param Pointer to a charge controller
 *
 * @return NULL when no charge control type is found, pointer to a
 * charge_control_type_traits structure when found.
 */
static struct charge_control_type_traits *
charge_control_get_type_traits_bytype(charge_control_type_t,
        struct charge_control *);

static struct charge_control *
charge_control_get_type_traits_byname(char *,
        struct charge_control_type_traits **, charge_control_type_t);

static uint8_t
charge_control_type_traits_empty(struct charge_control *);

static void
charge_control_poll_per_type_trait(struct charge_control *, os_time_t,
        os_time_t);

/* ---------------------- CHARGE CONTROL MANAGER ------------------- */

/**
 * Pends on the charge control manager mutex indefinitely.  Must be used prior
 * to operations which iterate through the list of charge controllers.
 *
 * @return 0 on success, non-zero error code on failure.
 */
static int
charge_control_mgr_lock(void);

/** Releases the charge control manager mutex. */
static void
charge_control_mgr_unlock(void);

static int
charge_control_mgr_match_bydevname(struct charge_control *, void *arg);

static void
charge_control_mgr_remove(struct charge_control *);

static void
charge_control_mgr_insert(struct charge_control *);

static void
charge_control_mgr_poll_bytype(struct charge_control *, charge_control_type_t,
        struct charge_control_type_traits *, os_time_t);

static void
charge_control_mgr_evq_set(struct os_eventq *);

static void
charge_control_mgr_init(void);

/* =================================================================
 * ====================== LOCAL STRUCTS/VARIABLES ==================
 * =================================================================
 */

/**
 * Declaration and definition of manager for the list of charge control devices
 */
struct {
    /** Locks list for manipulation/search operations */
    struct os_mutex mgr_lock;

    /** Manages poll rates of the charge control devices */
    struct os_callout mgr_wakeup_callout;

    /** Event queue to which wakeup events are added */
    struct os_eventq *mgr_eventq;

    /** Charge control device list */
    SLIST_HEAD(, charge_control) mgr_charge_control_list;
} charge_control_mgr;

struct charge_control_read_ctx {
    charge_control_data_func_t user_func;
    void *user_arg;
};

struct charge_control_timestamp charge_control_base_ts;

/** OS Callout to update charge control timestamp */
struct os_callout cct_up_osco;

/** Event for performing a charge control read */
static struct os_event charge_control_read_event = {
    .ev_cb = charge_control_read_ev_cb,
};

#ifdef MYNEWT_VAL_CHARGE_CONTROL_MGR_EVQ
extern struct os_eventq MYNEWT_VAL(CHARGE_CONTROL_MGR_EVQ);
#endif

/* =================================================================
 * ====================== PKG ======================================
 * =================================================================
 */

void charge_control_pkg_init(void)
{
    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

    charge_control_mgr_init();

#if MYNEWT_VAL(CHARGE_CONTROL_CLI)
    charge_control_shell_register();
#endif
}

/* =================================================================
 * ====================== OS =======================================
 * =================================================================
 */

static void
charge_control_read_ev_cb(struct os_event *ev)
{
    int rc;
    struct charge_control_read_ev_ctx *ccrec;

    ccrec = ev->ev_arg;
    rc = charge_control_read(ccrec->ccrec_charge_control, ccrec->ccrec_type,
            NULL, NULL, OS_TIMEOUT_NEVER);
    assert(rc == 0);
}

static void
charge_control_mgr_wakeup_event(struct os_event *ev)
{
    struct charge_control *cursor;
    os_time_t now;
    os_time_t next_wakeup;

    now = os_time_get();

#if MYNEWT_VAL(SENSOR_POLL_TEST_LOG)
    ccmgr_wakeup[ccmgr_wakeup_idx++%500] = now;
#endif

    charge_control_mgr_lock();

    cursor = NULL;
    while (1) {

        cursor = charge_control_find_min_nextrun(now, &next_wakeup);

        charge_control_lock(cursor);
        /* Charge controllers that are not periodic are inserted at the end of
         * the charge_control list.
         */
        if (!cursor->cc_poll_rate) {
            charge_control_unlock(cursor);
            charge_control_mgr_unlock();
            return;
        }

        /* List is sorted by what runs first.  If we reached the first element
         * that doesn't run, break out.
         */
        if (next_wakeup > 0) {
            break;
        }

        if (charge_control_type_traits_empty(cursor)) {

            charge_control_mgr_poll_bytype(cursor, cursor->cc_mask, NULL, now);
        } else {
            charge_control_poll_per_type_trait(cursor, now, next_wakeup);
        }

        charge_control_update_nextrun(cursor, now);

        charge_control_unlock(cursor);
    }

    charge_control_mgr_unlock();

    os_callout_reset(&charge_control_mgr.mgr_wakeup_callout, next_wakeup);
}

static void
charge_control_base_ts_update_event(struct os_event *ev)
{
    os_time_t ticks;
    int rc;
    struct os_timeval ostv;
    struct os_timezone ostz;

    ticks = os_time_get();

    rc = os_gettimeofday(&ostv, &ostz);
    if (rc) {
        /* There is nothing we can do here
         * just reset the timer frequently if we
         * fail to get time, till then we will keep using
         * old timestamp values.
         */
        ticks += OS_TICKS_PER_SEC * 600;
        goto done;
    }

    /* CPU time gets wrapped in 4295 seconds since it is uint32_t,
     * hence the hardcoded value of 3600 seconds, We want to make
     * sure that the cputime never gets wrapped more than once.
     * os_timeval usecs value gets wrapped in 2147 secs since it is int32_t.
     * Hence, we take 2000 secs so that we update before it gets wrapped
     * without cutting it too close.
     */
    ticks += OS_TICKS_PER_SEC * 2000;

    charge_control_base_ts.cct_ostv = ostv;
    charge_control_base_ts.cct_ostz = ostz;
    charge_control_base_ts.cct_cputime = os_cputime_get32();

done:
    os_callout_reset(&cct_up_osco, ticks);
}

/* =================================================================
 * ====================== CHARGE CONTROL ===========================
 * =================================================================
 */

static int
charge_control_lock(struct charge_control *cc)
{
    int rc;

    rc = os_mutex_pend(&cc->cc_lock, OS_TIMEOUT_NEVER);
    if (rc == 0 || rc == OS_NOT_STARTED) {
        return (0);
    }
    return (rc);
}

static void
charge_control_unlock(struct charge_control *cc)
{
    os_mutex_release(&cc->cc_lock);
}

static void
charge_control_update_poll_rate(struct charge_control * cc, uint32_t poll_rate)
{
    charge_control_lock(cc);

    cc->cc_poll_rate = poll_rate;

    charge_control_unlock(cc);
}

static os_time_t
charge_control_calc_nextrun_delta(struct charge_control * cc, os_time_t now)
{
    os_time_t charge_control_ticks;
    int delta;

    charge_control_lock(cc);

    delta = (int32_t)(cc->cc_next_run - now);
    if (delta < 0) {
        /* This fires the callout right away */
        charge_control_ticks = 0;
    } else {
        charge_control_ticks = delta;
    }

    charge_control_unlock(cc);

    return charge_control_ticks;
}

static struct charge_control *
charge_control_find_min_nextrun(os_time_t now, os_time_t *min_nextrun)
{
    struct charge_control *head;

    head = NULL;

    charge_control_mgr_lock();

    head = SLIST_FIRST(&charge_control_mgr.mgr_charge_control_list);

    *min_nextrun = charge_control_calc_nextrun_delta(head, now);

    charge_control_mgr_unlock();

    return head;
}

static void
charge_control_update_nextrun(struct charge_control *cc, os_time_t now)
{
    os_time_t charge_control_ticks;

    os_time_ms_to_ticks(cc->cc_poll_rate, &charge_control_ticks);

    charge_control_lock(cc);

    /* Remove the charge_control from the charge_control list for insert. */
    charge_control_mgr_remove(cc);

    /* Set next wakeup, and insert the charge_control back into the
     * list.
     */
    cc->cc_next_run = charge_control_ticks + now;

    /* Re-insert the charge_control manager, with the new wakeup time. */
    charge_control_mgr_insert(cc);

    charge_control_unlock(cc);
}

static int
charge_control_read_data_func(struct charge_control *cc, void *arg,
        void *data, charge_control_type_t type)
{
    struct charge_control_listener *listener;
    struct charge_control_read_ctx *ctx;

    ctx = (struct charge_control_read_ctx *) arg;

    if ((uint8_t)(uintptr_t)(ctx->user_arg) != CHARGE_CONTROL_IGN_LISTENER) {
        /* Notify all listeners first */
        SLIST_FOREACH(listener, &cc->cc_listener_list, ccl_next) {
            if (listener->ccl_type & type) {
                listener->ccl_func(cc, listener->ccl_arg, data, type);
            }
        }
    }

    /* Call data function */
    if (ctx->user_func != NULL) {
        return (ctx->user_func(cc, ctx->user_arg, data, type));
    } else {
        return (0);
    }
}

static void charge_control_up_timestamp(struct charge_control *cc)
{
    uint32_t curr_ts_ticks;
    uint32_t ts;

    curr_ts_ticks = os_cputime_get32();

    ts = os_cputime_ticks_to_usecs(curr_ts_ticks -
             charge_control_base_ts.cct_cputime);

    /* Updating cputime */
    charge_control_base_ts.cct_cputime = cc->cc_sts.cct_cputime = curr_ts_ticks;

    /* Updating seconds */
    charge_control_base_ts.cct_ostv.tv_sec  =
        charge_control_base_ts.cct_ostv.tv_sec
        + (ts + charge_control_base_ts.cct_ostv.tv_usec)/1000000;
    cc->cc_sts.cct_ostv.tv_sec = charge_control_base_ts.cct_ostv.tv_sec;

    /* Updating Micro seconds */
    charge_control_base_ts.cct_ostv.tv_usec  =
        (charge_control_base_ts.cct_ostv.tv_usec + ts)%1000000;
    cc->cc_sts.cct_ostv.tv_usec = charge_control_base_ts.cct_ostv.tv_usec;
}

static int
charge_control_insert_type_trait(struct charge_control *cc,
        struct charge_control_type_traits *cctt)
{
    struct charge_control_type_traits *cursor, *prev;
    int rc;

    rc = charge_control_lock(cc);
    if (rc != 0) {
        goto err;
    }

    prev = cursor = NULL;
    SLIST_FOREACH(cursor, &cc->cc_type_traits_list, cctt_next) {
        if (cursor->cctt_poll_n == 0) {
            break;
        }

        if (OS_TIME_TICK_LT(cctt->cctt_poll_n, cursor->cctt_poll_n)) {
            break;
        }

        prev = cursor;
    }

    if (prev == NULL) {
        SLIST_INSERT_HEAD(&cc->cc_type_traits_list, cctt, cctt_next);
    }
    else {
        SLIST_INSERT_AFTER(prev, cctt, cctt_next);
    }

    charge_control_unlock(cc);

    return 0;
err:
    return rc;
}

static int
charge_control_remove_type_trait(struct charge_control *cc,
        struct charge_control_type_traits *cctt)
{
    int rc;

    rc = charge_control_lock(cc);
    if (rc != 0) {
        goto err;
    }

    /* Remove this entry from the list */
    SLIST_REMOVE(&cc->cc_type_traits_list, cctt, charge_control_type_traits,
            cctt_next);

    charge_control_unlock(cc);

    return (0);
err:
    return (rc);
}

static struct charge_control_type_traits *
charge_control_get_type_traits_bytype(charge_control_type_t type,
        struct charge_control * cc)
{
    struct charge_control_type_traits *cctt;

    cctt = NULL;

    charge_control_lock(cc);

    SLIST_FOREACH(cctt, &cc->cc_type_traits_list, cctt_next) {
        if (cctt->cctt_charge_control_type == type) {
            break;
        }
    }

    charge_control_unlock(cc);

    return cctt;
}

static struct charge_control *
charge_control_get_type_traits_byname(char *devname,
        struct charge_control_type_traits **cctt, charge_control_type_t type)
{
    struct charge_control *cc;

    cc = charge_control_mgr_find_next_bydevname(devname, NULL);
    if (!cc) {
        goto err;
    }

    *cctt = charge_control_get_type_traits_bytype(type, cc);

err:
    return cc;
}

static uint8_t
charge_control_type_traits_empty(struct charge_control * cc)
{
    return SLIST_EMPTY(&cc->cc_type_traits_list);
}

static void
charge_control_poll_per_type_trait(struct charge_control *cc, os_time_t now,
        os_time_t next_wakeup)
{
    struct charge_control_type_traits *cctt;

    /* Lock the charge controller */
    charge_control_lock(cc);

    SLIST_FOREACH(cctt, &cc->cc_type_traits_list, cctt_next) {

        /* poll multiple is one if no multiple is specified,
         * as a result, the charge controller would get polled at the
         * poll rate if no multiple is specified
         *
         * If a multiple is specified, the charge controller would get polled
         * at the poll multiple
         */

        charge_control_mgr_poll_bytype(cc, cctt->cctt_charge_control_type, cctt,
                               now);
    }

    /* Unlock the charge controller to allow other access */
    charge_control_unlock(cc);
}

int
charge_control_init(struct charge_control *cc, struct os_dev *dev)
{
    int rc;

    memset(cc, 0, sizeof(*cc));

    rc = os_mutex_init(&cc->cc_lock);
    if (rc != 0) {
        goto err;
    }
    cc->cc_dev = dev;

    return (0);
err:
    return (rc);
}

int
charge_control_register_listener(struct charge_control *cc,
        struct charge_control_listener *listener)
{
    int rc;

    rc = charge_control_lock(cc);
    if (rc != 0) {
        goto err;
    }

    SLIST_INSERT_HEAD(&cc->cc_listener_list, listener, ccl_next);

    charge_control_unlock(cc);

    return (0);
err:
    return (rc);
}

int
charge_control_unregister_listener(struct charge_control *cc,
        struct charge_control_listener *listener)
{
    int rc;

    rc = charge_control_lock(cc);
    if (rc != 0) {
        goto err;
    }

    /* Remove this entry from the list */
    SLIST_REMOVE(&cc->cc_listener_list, listener, charge_control_listener,
            ccl_next);

    charge_control_unlock(cc);

    return (0);
err:
    return (rc);
}

int
charge_control_read(struct charge_control *cc, charge_control_type_t type,
        charge_control_data_func_t data_func, void *arg, uint32_t timeout)
{
    struct charge_control_read_ctx ccrc;
    int rc;

    rc = charge_control_lock(cc);
    if (rc) {
        goto err;
    }

    ccrc.user_func = data_func;
    ccrc.user_arg = arg;

    if (!charge_control_mgr_match_bytype(cc, (void *)&type)) {
        rc = SYS_ENOENT;
        goto err;
    }

    charge_control_up_timestamp(cc);

    rc = cc->cc_funcs->ccd_read(cc, type, charge_control_read_data_func, &ccrc,
            timeout);
    if (rc) {
        goto err;
    }

err:
    charge_control_unlock(cc);
    return (rc);
}

/* =================================================================
 * ====================== CHARGE CONTROL MANAGER ===================
 * =================================================================
 */

static int
charge_control_mgr_lock(void)
{
    int rc;

    rc = os_mutex_pend(&charge_control_mgr.mgr_lock, OS_TIMEOUT_NEVER);
    if (rc == 0 || rc == OS_NOT_STARTED) {
        return (0);
    }
    return rc;
}

static void
charge_control_mgr_unlock(void)
{
    (void) os_mutex_release(&charge_control_mgr.mgr_lock);
}

static int
charge_control_mgr_match_bydevname(struct charge_control *cc, void *arg)
{
    char *devname;

    devname = (char *) arg;

    if (!strcmp(cc->cc_dev->od_name, devname)) {
        return (1);
    }

    return (0);
}

static void
charge_control_mgr_remove(struct charge_control *cc)
{
    SLIST_REMOVE(&charge_control_mgr.mgr_charge_control_list, cc,
            charge_control, cc_next);
}

static void
charge_control_mgr_insert(struct charge_control *cc)
{
    struct charge_control *cursor, *prev;

    prev = cursor = NULL;
    if (!cc->cc_poll_rate) {
        SLIST_FOREACH(cursor, &charge_control_mgr.mgr_charge_control_list, cc_next) {
            prev = cursor;
        }
        goto insert;
    }

    prev = cursor = NULL;
    SLIST_FOREACH(cursor, &charge_control_mgr.mgr_charge_control_list, cc_next) {
        if (!cursor->cc_poll_rate) {
            break;
        }

        if (OS_TIME_TICK_LT(cc->cc_next_run, cursor->cc_next_run)) {
            break;
        }

        prev = cursor;
    }

insert:
    if (prev == NULL) {
        SLIST_INSERT_HEAD(&charge_control_mgr.mgr_charge_control_list, cc,
                cc_next);
    } else {
        SLIST_INSERT_AFTER(prev, cc, cc_next);
    }
}

static void
charge_control_mgr_poll_bytype(struct charge_control *cc,
        charge_control_type_t type, struct charge_control_type_traits *cctt,
        os_time_t now)
{
    if (!cctt || !cctt->cctt_polls_left) {
        /* Charge control read results. Every time a charge controller is read,
         * all of its listeners are called by default. Specify NULL as a
         * callback, because we just want to run all the listeners.
         */

        charge_control_read(cc, type, NULL, NULL, OS_TIMEOUT_NEVER);

        charge_control_lock(cc);

        if (cctt) {
            if (!cctt->cctt_polls_left && cctt->cctt_poll_n) {
                cctt->cctt_polls_left = cctt->cctt_poll_n;
                cctt->cctt_polls_left--;
            }
        }

        /* Unlock the sensor to allow other access */
        charge_control_unlock(cc);

    } else {
        cctt->cctt_polls_left--;
    }
}

static void
charge_control_mgr_evq_set(struct os_eventq *evq)
{
    assert(evq != NULL);
    charge_control_mgr.mgr_eventq = evq;
}

static void
charge_control_mgr_init(void)
{
    struct os_timeval ostv;
    struct os_timezone ostz;

#ifdef MYNEWT_VAL_CHARGE_CONTROL_MGR_EVQ
    charge_control_mgr_evq_set(&MYNEWT_VAL(CHARGE_CONTROL_MGR_EVQ));
#else
    charge_control_mgr_evq_set(os_eventq_dflt_get());
#endif

    /**
     * Initialize charge control polling callout and set it to fire on boot.
     */
    os_callout_init(&charge_control_mgr.mgr_wakeup_callout,
            charge_control_mgr_evq_get(), charge_control_mgr_wakeup_event,
            NULL);

    /* Initialize sensor cputime update callout and set it to fire after an
     * hour, CPU time gets wrapped in 4295 seconds,
     * hence the hardcoded value of 3600 seconds, We make sure that the
     * cputime never gets wrapped more than once.
     */
    os_gettimeofday(&ostv, &ostz);

    charge_control_base_ts.cct_ostv = ostv;
    charge_control_base_ts.cct_ostz = ostz;
    charge_control_base_ts.cct_cputime = os_cputime_get32();

    os_callout_init(&cct_up_osco, charge_control_mgr_evq_get(),
            charge_control_base_ts_update_event, NULL);
    os_callout_reset(&cct_up_osco, OS_TICKS_PER_SEC);

    os_mutex_init(&charge_control_mgr.mgr_lock);
}

int
charge_control_mgr_register(struct charge_control *cc)
{
    int rc;

    rc = charge_control_mgr_lock();
    if (rc != 0) {
        goto err;
    }

    rc = charge_control_lock(cc);
    if (rc != 0) {
        goto err;
    }

    charge_control_mgr_insert(cc);

    charge_control_unlock(cc);

    charge_control_mgr_unlock();

    return (0);
err:
    return (rc);
}

struct os_eventq *
charge_control_mgr_evq_get(void)
{
    return (charge_control_mgr.mgr_eventq);
}

struct charge_control *
charge_control_mgr_find_next(charge_control_mgr_compare_func_t compare_func,
        void *arg, struct charge_control *prev_cursor)
{
    struct charge_control *cursor;
    int rc;

    cursor = NULL;

    /* Couldn't acquire lock of charge control list, exit */
    rc = charge_control_mgr_lock();
    if (rc != 0) {
        goto done;
    }

    cursor = prev_cursor;
    if (cursor == NULL) {
        cursor = SLIST_FIRST(&charge_control_mgr.mgr_charge_control_list);
    } else {
        cursor = SLIST_NEXT(prev_cursor, cc_next);
    }

    while (cursor != NULL) {
        if (compare_func(cursor, arg)) {
            break;
        }
        cursor = SLIST_NEXT(cursor, cc_next);
    }

    charge_control_mgr_unlock();

done:
    return (cursor);
}

struct charge_control *
charge_control_mgr_find_next_bytype(charge_control_type_t type,
        struct charge_control *prev_cursor)
{
    return (charge_control_mgr_find_next(charge_control_mgr_match_bytype,
            (void *) &type, prev_cursor));
}

struct charge_control *
charge_control_mgr_find_next_bydevname(char *devname,
        struct charge_control *prev_cursor)
{
    return (charge_control_mgr_find_next(charge_control_mgr_match_bydevname,
            devname, prev_cursor));
}

int
charge_control_mgr_match_bytype(struct charge_control *cc, void *arg)
{
    charge_control_type_t *type;

    type = (charge_control_type_t *) arg;

    /* cc_types is a bitmask that contains the supported charge control types
     * for this charge controller, and type is the bitmask we're searching for
     * We also look at the mask as the driver might be configured to work in a
     * mode where only some of the charge control types are supported but not
     * all. Compare the three, and if there is a match, return 1. If it is not
     * supported, return 0.
     */
    return (*type & cc->cc_types & cc->cc_mask) ? 1 : 0;
}

int
charge_control_set_poll_rate_ms(char *devname, uint32_t poll_rate)
{
    struct charge_control *charge_control;
    os_time_t next_wakeup;
    os_time_t now;
    int rc;

    os_callout_stop(&charge_control_mgr.mgr_wakeup_callout);

    charge_control = charge_control_mgr_find_next_bydevname(devname, NULL);
    if (!charge_control) {
        rc = SYS_EINVAL;
        goto err;
    }

    charge_control_lock(charge_control);

    now = os_time_get();

    os_time_ms_to_ticks(poll_rate, &next_wakeup);

    charge_control_update_poll_rate(charge_control, poll_rate);

    charge_control_update_nextrun(charge_control, now);

    charge_control_unlock(charge_control);

    charge_control = charge_control_find_min_nextrun(now, &next_wakeup);

    os_callout_reset(&charge_control_mgr.mgr_wakeup_callout, next_wakeup);

    return 0;
err:
    return rc;
}

int
charge_control_set_n_poll_rate(char * devname,
        struct charge_control_type_traits *cctt)
{
    struct charge_control *cc;
    struct charge_control_type_traits *cctt_tmp;
    int rc;

    if (!cctt) {
        rc = SYS_EINVAL;
        goto err;
    }

    cctt_tmp = NULL;

    cc = charge_control_get_type_traits_byname(devname, &cctt_tmp,
                                           cctt->cctt_charge_control_type);
    if (!cc) {
        rc = SYS_EINVAL;
        goto err;
    }

    if (!cctt_tmp && cctt) {
        rc = charge_control_insert_type_trait(cc, cctt);
        rc |= charge_control_lock(cc);
        if (rc) {
            goto err;
        }
        cctt_tmp = cctt;
        cctt_tmp->cctt_polls_left = cctt->cctt_poll_n;
        charge_control_unlock(cc);
    } else if (cctt_tmp) {
        rc = charge_control_remove_type_trait(cc, cctt_tmp);
        if (rc) {
            goto err;
        }

        charge_control_lock(cc);

        cctt_tmp->cctt_poll_n = cctt->cctt_poll_n;
        cctt_tmp->cctt_polls_left = cctt->cctt_poll_n;

        charge_control_unlock(cc);

        rc = charge_control_insert_type_trait(cc, cctt_tmp);
        if (rc) {
            goto err;
        }

    } else {
        rc = SYS_EINVAL;
        goto err;
    }

    return 0;
err:
    return rc;
}

void
charge_control_mgr_put_read_evt(void *arg)
{
    charge_control_read_event.ev_arg = arg;
    os_eventq_put(charge_control_mgr_evq_get(), &charge_control_read_event);
}
