/* battstat        A MATE battery meter for laptops.
 * Copyright (C) 2000 by Jörgen Pehrson <jp@spektr.eu.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02110-1301, USA.
 *
 $Id$
 */

/*
 * ACPI battery functions for FreeBSD >= 5.2.
 * September 2004 by Joe Marcus Clarke <marcus@FreeBSD.org>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)

#include <stdio.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/ioctl.h>
#include <machine/apm_bios.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <glib.h>

#include "acpi-freebsd.h"

#ifdef HAVE_ACPIIO

#include <dev/acpica/acpiio.h>

static gboolean
update_ac_info(struct acpi_info * acpiinfo)
{
  int acline;
  size_t len = sizeof(acline);

  acpiinfo->ac_online = FALSE;

  if (sysctlbyname(ACPI_ACLINE, &acline, &len, NULL, 0) == -1) {
    return FALSE;
  }

  acpiinfo->ac_online = acline ? TRUE : FALSE;

  return TRUE;
}

static gboolean
update_battery_info(struct acpi_info * acpiinfo)
{
  union acpi_battery_ioctl_arg battio;
  int i;

  /* We really don't have to do this here.  All of the relevant battery
   * info can be obtained through sysctl.  However, one day, the rate
   * may be useful to get time left to full charge.
   */

  for(i = BATT_MIN; i < BATT_MAX; i++) {
    battio.unit = i;
    if (ioctl(acpiinfo->acpifd, ACPIIO_CMBAT_GET_BIF, &battio) == -1) {
      continue;
    }

    acpiinfo->max_capacity += battio.bif.lfcap;
    acpiinfo->low_capacity += battio.bif.wcap;
    acpiinfo->critical_capacity += battio.bif.lcap;
  }

  return TRUE;
}

gboolean
acpi_freebsd_init(struct acpi_info * acpiinfo)
{
  int acpi_fd;

  g_assert(acpiinfo);

  acpi_fd = open(ACPIDEV, O_RDONLY);
  if (acpi_fd >= 0) {
    acpiinfo->acpifd = acpi_fd;
  }
  else {
    acpiinfo->acpifd = -1;
    return FALSE;
  }

  update_battery_info(acpiinfo);
  update_ac_info(acpiinfo);

  return TRUE;
}

void
acpi_freebsd_cleanup(struct acpi_info * acpiinfo)
{
  g_assert(acpiinfo);

  if (acpiinfo->acpifd >= 0) {
    close(acpiinfo->acpifd);
    acpiinfo->acpifd = -1;
  }
}

/* XXX This is a hack since user-land applications can't get ACPI events yet.
 * Devd provides this (or supposedly provides this), but you need to be
 * root to access devd.
 */
gboolean
acpi_process_event(struct acpi_info * acpiinfo)
{
  g_assert(acpiinfo);

  update_ac_info(acpiinfo);
  update_battery_info(acpiinfo);

  return TRUE;
}

gboolean
acpi_freebsd_read(struct apm_info *apminfo, struct acpi_info * acpiinfo)
{
  int time;
  int life;
  int acline;
  int state;
  size_t len;
  int rate;
  int remain;
  union acpi_battery_ioctl_arg battio;
  gboolean charging;
  int i;

  g_assert(acpiinfo);

  charging = FALSE;

  for(i = BATT_MIN; i < BATT_MAX; i++) {
    battio.unit = i;
    if (ioctl(acpiinfo->acpifd, ACPIIO_CMBAT_GET_BST, &battio) == -1) {
      continue;
    }

    remain += battio.bst.cap;
    rate += battio.bst.rate;
  }

  len = sizeof(time);
  if (sysctlbyname(ACPI_TIME, &time, &len, NULL, 0) == -1) {
    return FALSE;
  }

  len = sizeof(life);
  if (sysctlbyname(ACPI_LIFE, &life, &len, NULL, 0) == -1) {
    return FALSE;
  }

  len = sizeof(state);
  if (sysctlbyname(ACPI_STATE, &state, &len, NULL, 0) == -1) {
    return FALSE;
  }

  apminfo->ai_acline = acpiinfo->ac_online ? 1 : 0;
  if (state & ACPI_BATT_STAT_CHARGING) {
    apminfo->ai_batt_stat = 3;
    charging = TRUE;
  }
  else if (state & ACPI_BATT_STAT_CRITICAL) {
    /* Add a special check here since FreeBSD's ACPI interface will tell us
     * when the battery is critical.
     */
    apminfo->ai_batt_stat = 2;
  }
  else {
    apminfo->ai_batt_stat = remain < acpiinfo->low_capacity ? 1 : remain < acpiinfo->critical_capacity ? 2 : 0;
  }
  apminfo->ai_batt_life = life;
  if (!charging) {
    apminfo->ai_batt_time = time;
  }
  else if (charging && rate > 0) {
    apminfo->ai_batt_time = (int) ((acpiinfo->max_capacity-remain)/(float)rate);
  }
  else
    apminfo->ai_batt_time = -1;

  return TRUE;
}

#else /* !defined(HAVE_ACPIIO) */

gboolean
acpi_freebsd_init(struct acpi_info * acpiinfo)
{
  return FALSE;
}

gboolean
acpi_process_event(struct acpi_info * acpiinfo)
{
  return FALSE;
}

gboolean
acpi_freebsd_read(struct apm_info *apminfo, struct acpi_info * acpiinfo)
{
  return FALSE;
}

void
acpi_freebsd_cleanup(struct acpi_info * acpiinfo)
{
}

#endif /* defined(HAVE_ACPIIO) */

#endif /* defined(__FreeBSD__) || defined(__FreeBSD_kernel__) */