Logo Search packages:      
Sourcecode: pbbuttonsd version File versions

module_pmac.c

/* --------------------------------------------------------------------------
 * module_pmac.c
 * code for test module
 *
 * Copyright 2002 Matthias Grimm
 *
 * 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.
 * -------------------------------------------------------------------------*/

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

#ifdef WITH_MODULE_PMAC

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include "pbbinput.h"
#include "i2c-dev.h"
#include <sys/ioctl.h>
#include <linux/pmu.h>
#include <linux/adb.h>

#include <pbb.h>

#include "gettext_macros.h"
#include "input_manager.h"
#include "module_pmac.h"
#include "support.h"
#ifdef WITH_PMUD
#  include "tcp.h"
#endif
#include "debug.h"

struct moddata_pmac {
      char *pmudev;    /* pathname of the PMU device */
      int fd_pmu;      /* filehandle of the PMU device */
      char *adbdev;    /* pathname of the ADB device */
      int fd_adb;      /* filehandle of ADB device */

      int version;     /* PMU version */
      char *identity;  /* Identity string of this laptop */
      struct modflags_pmac flags;
      int flagschanged;        /* PMU flags that have changed recently */
      int oharevolbutton;      /* level of volume button on OHARE PBs scaled to 0..100 */
      int oharebrightbutton;   /* level of brightness button on OHARE PBs scaled to 0..15 */

      int charge[MAX_BATTERIES];     /* current battery charge */
      int chargemax[MAX_BATTERIES];  /* max battery charge */
      int current[MAX_BATTERIES];    /* electrical currrent out of the batteries */
      int voltage[MAX_BATTERIES];    /* current battery voltage */
      int timeleft;           /* time until battery runs out of fuel. This value is
                             low pass filtered to reduce fast signal fluctuation
                             This filter doesn't reduce accuracy. */

      unsigned short keytpmodeup;     /* trackpad control values */
      unsigned short modtpmodeup;
      unsigned short keytpmodedn;
      unsigned short modtpmodedn;
      int trackpad_mode;
      int keyboard_mode;             /* keyboard mode for recent keyboards (0xC[0..4]) */
      int batlog_mode;
      int batlog_cycle;
      int i2c_bus_lmu;
      int lcdfeedback;               /* correction factor for ambient light sensor */
#if defined(DEBUG) && SIMUAMBIENT
      int ambient;
#endif
} modbase_pmac;

int
pmac_init (struct tagitem *taglist)
{
      struct moddata_pmac *base = &modbase_pmac;
      static char devbuffer_pmu[STDBUFFERLEN];
      static char devbuffer_adb[STDBUFFERLEN];
      static char identitystring[STDBUFFERLEN];
      char *devname;
      int val, rc;

      base->pmudev            = devbuffer_pmu;
      base->adbdev            = devbuffer_adb;
      base->fd_pmu            = -1;
      base->fd_adb            = -1;
      base->flags.coveropen   = 1;           /* lid is open */
      base->flags.sleepsupported = 0;        /* sleep not supported by default */
      base->flags.set_tpmode  = 0;           /*  \  */
      base->flags.set_kbdmode = 0;           /*    reguest flags for delayed actions */
      base->flags.goto_sleep  = 0;           /*  /  */

      base->keytpmodeup = KEY_BRIGHTNESSUP;
      base->modtpmodeup = MOD_ALT;
      base->keytpmodedn = KEY_BRIGHTNESSDOWN;
      base->modtpmodedn = MOD_ALT;
      base->trackpad_mode     = -1;
      base->keyboard_mode     = -1;
      base->batlog_mode = BATLOG_NONE;  /* no batlog */
      base->batlog_cycle      = -1;         /* cycle invalid */
      base->i2c_bus_lmu       = -1;  /* not available by default */
      base->timeleft          = -1;
#if defined(DEBUG) && SIMUAMBIENT
      base->ambient           = 0;
#endif
#ifdef WITH_PMUD
      print_error(_("INFO: pmud support compiled in and active.\n"));
#endif

      devname = (char *) tagfind (taglist, TAG_PMUDEVICE, (long) DEFAULT_PMU);
      if ((rc = copy_path (devname, base->pmudev, TYPE_CHARDEV, CPFLG_NONE)) == 0) {
            if ((base->fd_pmu = open(base->pmudev, O_RDWR)) < 0) {
                  print_error(_("ERROR: Can't open PMU device %s: %s\n"), devname, strerror(errno));
                  return E_OPEN;
            }
      } else return rc;

      devname = (char *) tagfind (taglist, TAG_ADBDEVICE, (long) DEFAULT_ADB);
      if ((rc = copy_path (devname, base->adbdev, TYPE_CHARDEV, CPFLG_NONE)) == 0) {
            if ((base->fd_adb = open(base->adbdev, O_RDWR)) < 0) {
                  print_error(_("ERROR: Can't open ADB device %s: %s\n"), devname, strerror(errno));
                  return E_OPEN;
            }
      } else return rc;
      
      if ((val = lmu_find_bus()) >= 0) {
            base->i2c_bus_lmu = val;
            print_error(_("INFO: LMU found, ambient light sensor and keyboard illumination active.\n"));
      } else if (val == (-E_PERM))
            print_error(_("WARNING: Can't access i2c devices. Permission denied.\n"));
#ifdef DEBUG
      else
            print_error("DBG: No LMU present in this ancient PowerBook.\n");
#endif

      base->version  = get_pmu_version(base->fd_adb);
      pmac_identify(identitystring, base->version);
      base->identity = identitystring;
      ioctl(base->fd_pmu, PMU_IOC_CAN_SLEEP, &val);
      base->flags.sleepsupported = val == 1 ? 1 : 0; /* check if sleep is supported on this system */

      pmac_update_flags ();
      pmac_update_batteryinfo ();
      base->flagschanged = 0;

      /* Every frame buffer driver could register a backlight controller,
       * but only the right one is accepted. Verification is done by checking
       * OpenFirmware's property "backlight-control" in register_backlight_controller().
       * ioctl(base->fd_pmu, PMU_IOC_GET_BACKLIGHT, &val);  returns -ENODEV if no
       * backlight device is registered. This could be used to check if a backlight
       * controller is installed or if we have to call our fallback functions.
       */
      
      if (base->i2c_bus_lmu != -1) {  
            val = pmac_get_lcdbacklight();
            pmac_set_lcdbacklight (0);    /* measure LCD feedback */
            pmac_set_lcdbacklight (val);  /* restore LCD backlight level */
      }
            
      if ((rc = pmac_handle_tags (MODE_CONFIG, taglist))) /* interprete configfile */
            return rc;         /* critcal error occured, abort program */

      if (base->trackpad_mode == -1)                                          /* tpmode defined? */
            base->trackpad_mode = trackpad_get_config(base->fd_adb);    /* no, then get current mode */

      if (base->version != OHARE_PMU) {
            ioctl(base->fd_pmu, PMU_IOC_GRAB_BACKLIGHT, 0);
            if (base->keyboard_mode == -1)
                  base->keyboard_mode = keyboard_get_config(base->fd_adb);    /* get keyboard config*/
      }

      if ((register_inputhandler (dup(base->fd_pmu), pmac_pmu_handler)) < 0)
            print_error(_("WARNING: Can't install PMU input handler. Some functionality may be missing.\n"));

      register_function (KBDQUEUE, pmac_keyboard);
      register_function (T100QUEUE, pmac_timer);
      register_function (QUERYQUEUE, pmac_query);
      register_function (CONFIGQUEUE, pmac_configure);
      register_function (SECUREQUEUE, pmac_secure);
      return 0;
}

int
pmac_exit ()
{
      struct moddata_pmac *base = &modbase_pmac;

      batlog_save();            /* save batlog info to disc */
      if (base->fd_pmu != -1)
            close (base->fd_pmu); /* close PMU device */
      if (base->fd_adb != -1)
            close (base->fd_adb); /* close ADB device */

      return 0;
}

void
pmac_keyboard (struct tagitem *taglist)
{
      struct moddata_pmac *base = &modbase_pmac;
      int code, value, mod;

      code = (int) tagfind (taglist, TAG_KEYCODE, 0);
      value = (int) tagfind (taglist, TAG_KEYREPEAT, 0);
      mod = (int) tagfind (taglist, TAG_MODIFIER, 0);

      if (value == 1) {
            if ((code == base->keytpmodeup) && (mod == base->modtpmodeup)) {
                  if (++base->trackpad_mode > TRACKPAD_LAST)
                        base->trackpad_mode = 0;
            } else if ((code == base->keytpmodedn) && (mod == base->modtpmodedn)) {
                  if (--base->trackpad_mode < 0)
                        base->trackpad_mode = TRACKPAD_LAST;
            } else return;

            base->flags.set_tpmode = 1;     /* request a secure function call */
            singletag_to_clients (CHANGEVALUE, TAG_TPMODE, base->trackpad_mode);
      }
}

/* This function is called when the user is idle. That means no key is pressed */

void
pmac_secure (struct tagitem *taglist)
{
      struct moddata_pmac *base = &modbase_pmac;
      
      if (base->flags.set_tpmode == 1) {
            base->flags.set_tpmode = 0;
            trackpad_set_config(base->fd_adb, base->trackpad_mode);
      } else if (base->flags.set_kbdmode == 1) {
            base->flags.set_kbdmode = 0;
            keyboard_set_config(base->fd_adb, base->keyboard_mode);
      } else if (base->flags.goto_sleep == 1) {
            base->flags.goto_sleep = 0;
            if (base->flags.sleepsupported) {
                  batlog_save();
                  process_queue_single (CONFIGQUEUE, TAG_PREPAREFORSLEEP, 0);
#ifdef WITH_PMUD
                  activate_sleepmode ();
                  base->flagschanged = pmac_update_flags ();
#else
                  do {
                      activate_sleepmode ();
                      base->flagschanged = pmac_update_flags ();
                  } while (base->flags.coveropen == 0);
#endif
                  base->timeleft = 7200;       /* reset time value so that the */
                        /* filter approximate the real value from top. Otherwise */
                        /* an errornous battery warnlevel could be sent to       */
                        /* clients because the filtered value doesn't cross the */
                        /* thresholds quick enough. This problem occours only  */
                        /* after wakeup on battery after the battery had been */
                        /* recharged during sleep.                           */
                  pmac_update_batteryinfo ();
                  process_queue_single (CONFIGQUEUE, TAG_WAKEUPFROMSLEEP, 0);
                  base->batlog_cycle = batlog_setup();
            }
      }
}

void
pmac_query (struct tagitem *taglist)
{
      pmac_handle_tags (MODE_QUERY, taglist);
}

void
pmac_configure (struct tagitem *taglist)
{
      pmac_handle_tags (MODE_CONFIG, taglist);
}

int
pmac_handle_tags (int cfgure, struct tagitem *taglist)
{
      struct moddata_pmac *base = &modbase_pmac;
      int err, rc = 0;

      while (taglist->tag != TAG_END) {
            switch (taglist->tag) {
            case TAG_REINIT:      /* system tag */
                  base->flags.set_tpmode = 1;     /* reset trackpad mode */
                  base->flags.set_kbdmode = 1;     /* reset keyboard mode */
                  break;
            case TAG_PMUDEVICE:
                  if (cfgure) {
                        if ((err = copy_path ((char *) taglist->data, base->pmudev, TYPE_CHARDEV, CPFLG_NONE)))
                              rc = tagerror (taglist, err);
                  } else
                        taglist->data = (long) base->pmudev;
                  break;
            case TAG_ADBDEVICE:
                  if (cfgure) {
                        if ((err = copy_path ((char *) taglist->data, base->adbdev, TYPE_CHARDEV, CPFLG_NONE)))
                              rc = tagerror (taglist, err);
                  } else
                        taglist->data = (long) base->adbdev;
                  break;
            case TAG_BACKLIGHTMAX:     /* private tag */
                  if (cfgure) ;
                  else        taglist->data = BACKLIGHTMAX;
                  break;
            case TAG_BACKLIGHTLEVEL:   /* private tag */
                  if (cfgure) pmac_set_lcdbacklight (taglist->data);
                  else        taglist->data = pmac_get_lcdbacklight();
                  break;
            case TAG_TPMODEUPKEY:
                  if (cfgure) base->keytpmodeup = taglist->data;
                  else        taglist->data = base->keytpmodeup;
                  break;
            case TAG_TPMODEUPMOD:
                  if (cfgure) base->modtpmodeup = taglist->data;
                  else        taglist->data = base->modtpmodeup;
                  break;
            case TAG_TPMODEDOWNKEY:
                  if (cfgure) base->keytpmodedn = taglist->data;
                  else        taglist->data = base->keytpmodedn;
                  break;
            case TAG_TPMODEDOWNMOD:
                  if (cfgure) base->modtpmodedn = taglist->data;
                  else        taglist->data = base->modtpmodedn;
                  break;
            case TAG_TPMODE:
                  if (cfgure) {
                        base->trackpad_mode = taglist->data > TRACKPAD_LAST ? 0 : taglist->data;
                        base->flags.set_tpmode = 1;     /* request a secure function call */
                        singletag_to_clients (CHANGEVALUE, TAG_TPMODE, base->trackpad_mode);
                  } else
                        taglist->data = base->trackpad_mode;
                  break;
            case TAG_KBDMODE:
                  if (cfgure) {
                        if (base->version != OHARE_PMU) {
                              base->keyboard_mode = taglist->data > KEYBOARD_LAST ? 0 : taglist->data;
                              base->flags.set_kbdmode = 1;     /* request a secure function call */
                        }
                  } else
                        taglist->data = base->keyboard_mode;
                  break;
            case TAG_BATLOG:
                  if (cfgure) {
                        batlog_save();
                        base->batlog_mode = taglist->data > BATLOG_LAST ? 0 : taglist->data;
                        base->batlog_cycle = batlog_setup();            
                  } else
                        taglist->data = base->batlog_mode;
                  break;
            case TAG_BATCYCLE:
                  if (cfgure) tagerror(taglist, E_NOWRITE);
                  else        taglist->data = base->batlog_cycle;
                  break;
            case TAG_REQUESTSLEEP:    /* private tag */
                  if (cfgure) base->flags.goto_sleep = 1;   /* trigger delayed sleep */
                  break;
            case TAG_IDENTITY:
                  if (cfgure) tagerror (taglist, E_NOWRITE);
                  else        taglist->data = (long) base->identity;
                  break;
            case TAG_SLEEPSUPPORTED:
                  if (cfgure) tagerror (taglist, E_NOWRITE);
                  else        taglist->data = base->flags.sleepsupported;
                  break;
            case TAG_TIMEREMAINING:
                  if (cfgure) tagerror (taglist, E_NOWRITE);
                  else        taglist->data = base->timeleft;
                  break;
            case TAG_POWERSOURCE:
                  if (cfgure) tagerror (taglist, E_NOWRITE);
                  else        taglist->data = base->flags.ac_power;
                  break;
            case TAG_BATTERYPRESENT:
                  if (cfgure) tagerror (taglist, E_NOWRITE);
                  /* /proc/pmu is not reliable on machines with removable batteries.
                   * If no batteries were plugged in, /proc/pmu would not be updated
                   * anymore and contains the last battery data, which is not valid
                   * anymore.
                   * So pbbuttonsd ask the PMU for the existance of any battery and
                   * report the values from /proc/pmu only if the answer was yes.
                   */
                  else        taglist->data = base->flags.batpresent ? base->flags.bat_present : 0;
                  break;

            case TAG_AMBIENTLIGHT:
                  if (base->i2c_bus_lmu < 0)
                        tagerror (taglist, E_NOSUPPORT);
                  else if (cfgure)
#if defined(DEBUG) && SIMUAMBIENT
                        base->ambient = taglist->data;
#else
                        tagerror (taglist, E_NOWRITE);
#endif
                  else
                        taglist->data = lmu_get_ambient();
                  break;
            case TAG_KEYBLIGHTLEVEL:   /* private tag */
                  if (cfgure)
                        lmu_set_illumination (base->i2c_bus_lmu, taglist->data);
                  break;
            case TAG_KEYBLIGHTMAX:     /* private tag */
                  if (cfgure) ;
                  else        taglist->data = KEYBLIGHTMAX;
                  break;
            }
            taglist++;
      }
      return rc;
}

void
pmac_pmu_handler (int fd)
{
      unsigned char intr[16];
      int n;
#if defined(DEBUG) && PMUINTR
      int i;
#endif
      if ((n = read(fd, intr, sizeof(intr))) > 0) {
            switch(intr[0]) {
            case 4:
                  if (n < 3 || intr[2] < 1 || intr[2] > 2)
                        break;
                  call_script ("/sbin/cardctl %s %d", "eject", intr[2]-1);
            }
#if defined(DEBUG) && PMUINTR
            printf ("\nPMU interrupt: ");
            for (i=0; i < n; i++)
                  printf ("%.2x ", intr[i]);
            printf ("\n");
#endif
      }
}

void
pmac_timer (struct tagitem *taglist)
{
      struct moddata_pmac *base = &modbase_pmac;
      struct tagitem args[6];
      int val;

      taglist_init (args);

      val = base->timeleft;
      if (base->flagschanged == 0)     /* check if anybody else has already read the flags */
            base->flagschanged = pmac_update_flags (); /* if not, get ac power and cover status */
      pmac_update_batteryinfo ();

      if (base->flagschanged & PMU_ENV_AC_POWER) {
            taglist_add (args, TAG_POWERCHANGED, base->flags.ac_power);
            if (!base->flags.ac_power)      /* running on Battery */
                  base->batlog_cycle++;   /* then increase cycle */
      }
      if (val != base->timeleft && !base->flags.ac_power) {
            taglist_add (args, TAG_TIMECHANGED, base->timeleft);
            batlog_write(base->timeleft);
      }

#ifndef WITH_PMUD
      if (base->flagschanged & PMU_ENV_LID_CLOSED)
            taglist_add (args, TAG_COVERSTATUS, base->flags.coveropen);
#endif
      if (base->version == OHARE_PMU) {         /* PowerBook 3400, etc */
            if ((val = get_button(base->fd_adb, PMU_GET_VOLBUTTON)) >= 0) {
                  val = val > 64 ? 100 : val * 100 / 64;
                  if (base->oharevolbutton != val) {
                        base->oharevolbutton = val;
                        taglist_add (args, TAG_VOLUME, val);
                  }
            }
            if ((val = get_button(base->fd_adb, 0x49)) >= 0) {
                  val = 18 - (val >> 2);           /* This formula was derived for the usual pmac backlights */
                  if (val > 15) val = 15;          /* which uses 16 brightness steps. 0=min, 15=max */
                  if (val < 0) val = 0;
                  if (base->oharebrightbutton != val) {
                        base->oharebrightbutton = val;
                        taglist_add (args, TAG_LCDBRIGHTNESS, val);
                  }
            }
      }

      if (args[0].tag != TAG_END)
            process_queue (CONFIGQUEUE, args);  /* distribute changes to other modules */
      base->flagschanged = 0;
}


/* -------------------------------  MISCELLANEOUS FUNCTIONS ----------------------------------- */

/* This function put the machine into sleep mode. It tries pmud first. If it was not
   available, it would go directly over the PMU.
   Very importent is, that no modifier key is pressed, when this function is called,
   because this would confuse the input driver after wakeup. This case is not
   fully avoided, because this function is not atomic and with some fast actions
   the user could bypass our security gate. */

void
activate_sleepmode ()
{
      struct moddata_pmac *base = &modbase_pmac;
#ifdef WITH_PMUD
      char buf[35];
      int fd;
#endif

      base->trackpad_mode = trackpad_get_config (base->fd_adb);
      if (base->version != OHARE_PMU)
            base->keyboard_mode = keyboard_get_config (base->fd_adb);
      sync();

#ifndef WITH_PMUD
      /* PMU_IOC_SLEEP only succeeds if called as root.
       * It returns immediatly if not, without putting
       * the mashine to sleep
       */
      ioctl (base->fd_pmu, PMU_IOC_SLEEP, 0);    /* returns after next awake */
      trackpad_set_config(base->fd_adb, base->trackpad_mode);
#else
      if (((fd = tcp_open("localhost", "pmud", 879)) < 0)) {
            print_error(_("WARNING: Can't find pmud on port 879. Trigger PMU directly for sleep.\n"));
            ioctl (base->fd_pmu, PMU_IOC_SLEEP, 0);     /* returns after next awake */
            trackpad_set_config(base->fd_adb, base->trackpad_mode);
      } else {
            tcp_printf(fd, "sleep\n");

      /* read some bytes, in order to prevent pmud complaining about
       * dropped connections, before ever handling the 'sleep' command
       */
            tcp_printf(fd, "\n");
            sleep(6);                        /* give pmud time to snooze */
            tcp_read(fd, buf, sizeof(buf));
            tcp_close(fd);
      }
#endif
      if (base->version != OHARE_PMU)
            keyboard_set_config(base->fd_adb, base->keyboard_mode);
}

/* This function gets the battery status from the PMU kernel driver via
 * /proc/pmu and fill all battery slots of moddata struct, also if less
 * batteries are present.The following flags were set accordingly:
 * ac_power, bat_present and charging.
 */
void
pmac_update_batteryinfo ()
{
      struct moddata_pmac *base = &modbase_pmac;
      FILE *fd;
      char buffer[100], *token;
      int val, n, syscurrent, time_rem = 0;
      int charge, chargemax, current, voltage, timeleft[MAX_BATTERIES];

      syscurrent = 0;
      for (n=0; n < MAX_BATTERIES; n++) {
            charge = chargemax = current = voltage = timeleft[n] = 0;
            base->flags.bat_present &= ~(1<<n);
            sprintf (buffer, "/proc/pmu/battery_%i", n);
            if ((fd = fopen (buffer, "r"))) {
                  while (fgets (buffer,sizeof (buffer), fd)) {
                        if ((token = strtok (buffer, ":\n"))) {
                              if (!strncmp ("flags", token, 5)) {
                                    val = axtoi (strtok (0, ":\n"));
                                    base->flags.bat_present |= (val & 1) << n;
                                    base->flags.bat_charging = (base->flags.bat_charging & ~(1<<n)) | (((val & 2) >> 1) << n);
                              } else if (!strncmp ("charge", token, 6)) {
                                    charge = atoi(strtok(0, ":\n"));
                              } else if (!strncmp ("max_charge", token, 9)) {
                                    chargemax = atoi (strtok(0,":\n"));
                              } else if (!strncmp ("current", token, 7)) {
                                    current = atoi (strtok(0, ":\n"));
                              } else if (!strncmp ("voltage", token, 7)) {
                                    voltage = atoi (strtok(0,":\n"));
                              } else if (!strncmp ("time rem.", token, 8)) {
                                    timeleft[n] = atoi (strtok(0, ":\n"));
                              } else
                                    strtok (0,":\n");
                        }
                  }
                  fclose(fd);
            }
            base->charge[n] = charge;
            base->chargemax[n] = chargemax;
            base->voltage[n] = voltage;
            syscurrent += base->current[n] = current;  /* overall current out of the batteries */
      }

 /* work around for kernel bug: After booting without batteries the kernel doesn't
    recognise the ac plug and thought by mistake the computer runs on batteries
    but there were no batteries connected. So we check for batteries here and if
      none were found it would have to be AC. */

      if (!base->flags.ac_power && !base->flags.bat_present)
            base->flags.ac_power = 1;

 /*  During plug and unplug of the AC connector it could happen that the pmu time
     remaining temporarely is set to zero. In this case we freeze the timeleft and wait
     until the pmu time remaining value returns to a valid value */

      if (syscurrent < 0) {             /* computer runs on battery */
            for (n=0; n < MAX_BATTERIES; n++) {
                  if ((timeleft[n] > 0) || (base->charge[n] == 0))  /* is /proc/pmu reliable ? */
                        time_rem += timeleft[n];              /* then use it */
                  else if (base->version == OHARE_PMU)     /* otherwise calculate it by yourself */
                        time_rem += base->charge[n] * 274 / (-syscurrent);  /* formula for old Notebooks */
                  else
                        time_rem += base->charge[n] * 3600 / (-syscurrent);  /* formula for modern Notebools */
            }
      } else
            time_rem = -1;

      if (time_rem != -1) {
            if (base->timeleft >= 0)
                  time_rem  = base->timeleft + (time_rem - base->timeleft) / 8;   /* very simple low pass filter */
            base->timeleft = time_rem;
      }
}

/* use /proc/pmu/info as fallback, if PMU_GET_COVER did't work
 * As I know this has not happend yet and it is doubtable that
 * /proc/pmu/info will work correctly in this case. So this
 * routine may be removed in future versions.
 */   
int
pmac_get_procac ()
{
      FILE *fd;
      char buffer[100], *token;
      int ac = 0;  /* return battery if anything fails */

      if ((fd = fopen ("/proc/pmu/info","r"))) {
            while (fgets (buffer, sizeof (buffer), fd))
                  if ((token = strtok (buffer,":\n"))) {
                        if (!strncmp ("AC Power", token, 8))
                              ac = atoi (strtok(0,":\n"));
                        else
                              strtok (0,":\n");
                  }
            fclose(fd);
      }
      return ac;
}

/* This funtion gets some important flags from the PMU driver. If the
 * driver doesn't support the ioctl or reports an error, power source
 * will be read from /proc/pmu/info as fallback, so that power source
 * should always be set correctly.
 */
int
pmac_update_flags()
{
      struct moddata_pmac *base = &modbase_pmac;
      int env, envold, envnew;

      envold  = base->flags.ac_power ? PMU_ENV_AC_POWER : 0;
      envold |= base->flags.coveropen ? PMU_ENV_LID_CLOSED : 0;
      
      env = get_int_environment(base->fd_adb);
      if (env != -1) {
            base->flags.coveropen  = (env & PMU_ENV_LID_CLOSED) ? 0 : 1;
            base->flags.ac_power   = (env & PMU_ENV_AC_POWER) ? 1 : 0;
            base->flags.batpresent = (env & PMU_ENV_BATTERY_PRESENT) ? 1 : 0;
      } else
            base->flags.ac_power = pmac_get_procac();
      
      /* the PB G3 lombard PMU doesn't support the AC power flag in
       * environment flags. We use the /proc entry instead.
       */
      if (base->version < 12)
            base->flags.ac_power = pmac_get_procac();
            
      envnew  = base->flags.ac_power ? PMU_ENV_AC_POWER : 0;
      envnew |= base->flags.coveropen ? PMU_ENV_LID_CLOSED : 0;
      return (envold ^ envnew);
}

int
pmac_get_lcdbacklight ()
{
      struct moddata_pmac *base = &modbase_pmac;
      long val = 0;
            
      if ((ioctl(base->fd_pmu, PMU_IOC_GET_BACKLIGHT, &val)) == -1)
            return -1;

      return (int) val;
}

void
pmac_set_lcdbacklight (int val)
{
      struct moddata_pmac *base = &modbase_pmac;
      int ambient;
      
      if (val == -1)   /* no backlight controller available */
            return;
      
      ambient = lmu_get_raw_ambient(base->i2c_bus_lmu) - base->lcdfeedback;
      ioctl(base->fd_pmu, PMU_IOC_SET_BACKLIGHT, &val);
      base->lcdfeedback = val ? lmu_get_raw_ambient(base->i2c_bus_lmu) - ambient : 0;

#if defined(DEBUG) && (TESTAMBIENT || SIMUAMBIENT)
      ambient = lmu_get_raw_ambient(base->i2c_bus_lmu);
      printf("LCD Level: %2d, Raw (%4d) - LCD Feedback (%4d) = Ambient (%4d or %2d%%)\n",
            val, ambient, base->lcdfeedback, ambient - base->lcdfeedback,
            lmu_get_ambient());
      fflush(stdout);
#endif
      return;
}

/* ----------------------------  GENERIC FUNCTIONS ----------------------------- */

void
pmac_identify (char* buffer, int pmu)
{
  int idx;
  char *version[] = {
    N_("Unknown PowerBook"),
    "PowerBook 2400/3400/3500",        /*   9  */
    "G3 PowerBook Wallstreet",          /*  10  */
    "1999 G3 PowerBook Lombard",         /*  11  */
    "iBook/G3 PB Pismo/G4 PB Titanium",   /*  12  */
    N_("Wow, later than an Titanium! ;-)")
  };
  idx = pmu < 8 ? 8 : pmu - 8;
  if (idx > 5) idx = 5;
  snprintf(buffer, STDBUFFERLEN, "%s (PMU version: %d)", version[idx], pmu);
}

/* ------------------  POWER MAC SPECIFIC KEYBOARD FUNCTIONS ------------------- */

int
keyboard_get_config (int fd)
{
      unsigned char ADBBuffer[ADB_BUFSIZE];
      int n, dev, config = -1;

      if ((dev = keyboard_get_dev (fd)) > 0) {
            n = adb_read_reg (fd, ADBBuffer, dev, 1);
            if (n > 0) {
                  config = 0;
                  config |= ADBBuffer[3] & 0x01 ? KEYBOARD_FNTOP : 0;
            }
      }
      return config;
}

void
keyboard_set_config (int fd, int config)
{
      unsigned char ADBBuffer[ADB_BUFSIZE];
      int n, dev;

      if ((dev = keyboard_get_dev (fd)) > 0) {
            n = adb_read_reg (fd, ADBBuffer, dev, 1);
            if (n > 0) {
                  ADBBuffer[3] &= 0xfe;
                  ADBBuffer[3] |= (config & KEYBOARD_FNTOP) ? 0x01 : 0x00;
                  adb_write_reg (fd, ADBBuffer, n, dev, 1);
            }
      }
}

int
keyboard_get_dev (int fd)
{
      unsigned char ADBBuffer[ADB_BUFSIZE];
      int n;

      for (n=1; n<16; n++) {
            if ((adb_read_reg (fd, ADBBuffer, n, 3)) > 0) {
                  if (((ADBBuffer[2] & 0x0f)  == 2) &&            /* device id = keyboard */
                       ((ADBBuffer[3] & 0xc0) == 0xc0)) {        /* keyboard type */
                        return n;
                  }
            }
      }
      return -1;
}

/* -------------------- POWER MAC SPECIFIC TRACKPAD FUNCTIONS ------------------ */

int
trackpad_get_config (int fd)
{
      unsigned char ADBBuffer[ADB_BUFSIZE];
      int n, dev, config = -1;

      if ((dev = trackpad_get_dev (fd)) > 0) {
            trackpad_set_prgmode (fd, dev, 1);
            n = adb_read_reg (fd, ADBBuffer, dev, 2);
            if (n > 0) {
                  if (ADBBuffer[5] == 0xff)
                        config = TRACKPAD_LOCK;
                  else if (ADBBuffer[3] == 0x94)
                        config = TRACKPAD_DRAG;
                  else if (ADBBuffer[2] == 0x99)
                        config = TRACKPAD_TAP;
                  else
                        config = TRACKPAD_NOTAP;
            }
            trackpad_set_prgmode (fd, dev, 0);
      }
      return config;
}

void
trackpad_set_config (int fd, int config)
{
      unsigned char ADBBuffer[ADB_BUFSIZE];
      int n, dev;

      if ((dev = trackpad_get_dev (fd)) > 0) {
            trackpad_set_prgmode (fd, dev, 1);
            n = adb_read_reg (fd, ADBBuffer, dev, 2);
            if (n > 0) {
                  ADBBuffer[2] = (config < TRACKPAD_TAP) ? 0x19 : 0x99;
                  ADBBuffer[3] = (config < TRACKPAD_DRAG) ? 0x14 : 0x94;
                  ADBBuffer[5] = (config < TRACKPAD_LOCK) ? 0xb2 : 0xff;
                  adb_write_reg (fd, ADBBuffer, n, dev, 2);
            }
            trackpad_set_prgmode (fd, dev, 0);
      }
}

int
trackpad_get_dev (int fd)
{
      unsigned char ADBBuffer[ADB_BUFSIZE];
      int n;

      for (n=1; n<16; n++) {
            if ((adb_read_reg(fd, ADBBuffer, n, 1)) > 4) {
                  if ((ADBBuffer[2] == 0x74) &&
                       (ADBBuffer[3] == 0x70) &&            /* 'tpad' */
                       (ADBBuffer[4] == 0x61) &&
                       (ADBBuffer[5] == 0x64)) {
                        return n;
                  }
            }
      }
      return -1;
}

void
trackpad_set_prgmode (int fd, int dev, int mode)
{
      unsigned char ADBBuffer[ADB_BUFSIZE];
      int n;

      n = adb_read_reg(fd, ADBBuffer, dev, 1);
      if (n > 0) {
            ADBBuffer[8] = mode ? 0x0d : 0x03;
            adb_write_reg(fd, ADBBuffer, n, dev, 1);
            adb_read_reg(fd, ADBBuffer, dev, 2);             /* nessecary to activate program mode ! */
      }
}


/* ----------------------------- LOW LEVEL ADB FUNCTIONS ---------------------------- */

/* This function sends a request to the PMU via the ADB bus and reads
    the result. If successful the count of valid data bytes in the buffer will be
     returned, otherwise -1 */

int
send_pmu_request(int fd, unsigned char *buffer, int params, ...)
{
      va_list list;
      int n, x;

      if (params < 0 || params > 30)
            return -1;

      buffer[0] = PMU_PACKET;
      va_start(list, params);
      for (x = 0; x < params; ++x)
            buffer[x+1] = va_arg(list, int);
      va_end(list);

      n = write(fd, buffer, x+1);
      if ((n != x+1) || (n == -1))
            return -1;
      if ((n = read(fd, buffer, ADB_BUFSIZE)) < 0)
            return -1;
      return n;
}

/* This function reads a register from an ADB device into a Buffer. The data starts
   at byte 1 in buffer. Byte 0 contains the Packettype and could be ignored.
   The funtion returns the count of valid data bytes in the buffer (excluding byte 0) */

int
adb_read_reg(int fd, unsigned char *ADBBuffer, int dev, int reg)
{
      int n;

      ADBBuffer[0] = ADB_PACKET;
      ADBBuffer[1] = ADB_READREG(dev, reg);
      if ((write(fd, ADBBuffer, 2)) != 2)
            return -1;
      n = read(fd, ADBBuffer+1, ADB_BUFSIZE-1);
      return n;
}

/* This function writes a register into an ADB device. The first two bytes of the
   buffer are set by this funtion. */

int
adb_write_reg(int fd, unsigned char *ADBBuffer, int len, int dev, int reg)
{
      int n;
      len++;     /* take care about the command byte */
      ADBBuffer[0] = ADB_PACKET;
      ADBBuffer[1] = ADB_WRITEREG(dev, reg);
      if ((write(fd, ADBBuffer, len)) != len)
            return -1;
      n = read(fd, ADBBuffer+1, ADB_BUFSIZE-1);
      return 0;
}

/* ----------------------------- LOW LEVEL PMU FUNCTIONS ---------------------------- */

int
get_pmu_version(int fd)
{
      int count;
      unsigned char buffer[ADB_BUFSIZE];

      count = send_pmu_request(fd, buffer, 1, PMU_GET_VERSION);
      if (count == 2)
            return buffer[1];
      else
            return -1;
}

int
get_button(int fd, int button)
{
      int count;
      unsigned char buffer[ADB_BUFSIZE];

      count = send_pmu_request(fd, buffer, 1, button);
      if (count == 2)
            return buffer[1];
      else
            return -1;      /* not available or invalid */
}

int
get_int_environment(int fd)
{
      int count;
      unsigned char buffer[ADB_BUFSIZE];

      count = send_pmu_request(fd, buffer, 1, PMU_GET_COVER);
      if (count == 2)
            return buffer[1];
      else
            return -1;     /* not available or invalid */
}

/* ----------------------------- LOW LEVEL LMU FUNCTIONS ---------------------------- */

/* This function returns the a combined and compensated ambient
 * light sensor value in percent. The reduce flickering through
 * shadows or spot lights at one of the sensors each sensor is
 * weighted only half of full scale. In result spikes from one
 * sensor will be damped.
 */
int
lmu_get_ambient ()
{
      struct moddata_pmac *base = &modbase_pmac;
      int rc;

      rc = (int) (((lmu_get_raw_ambient(base->i2c_bus_lmu) - base->lcdfeedback) 
                        * 50) / AMBIENTMAX);
      if (rc < 0) rc = 0;
      if (rc > 100)     rc = 100;
      return rc;
}

/* This function tries to find the I2C device that controls the keyboard
 * illumination and the ambient light sensor used in alluminum PowerBooks
 * It returns the device number on success, otherwise a negative error code.
 * The loop starts with device 4 because that's the correct device on
 * 17" alluminum PowerBooks. Device 0 could be errornous detected because
 * the function uses only circumstantial evidence to detect the correct
 * device.
 */
int
lmu_find_bus ()
{
#if defined(DEBUG) && SIMUAMBIENT
      int rc = 99; /* dummy file handle for i2c bus, will hopefully never be used */
#else
      char i2cdevice[20], buf[4];
      int n, fd, rc = -E_NOCHAR;

      for(n=4; n < 260; n++) {
            snprintf(i2cdevice, 19, "/dev/i2c-%d", (n & 255) );
            if ((fd = open (i2cdevice, O_RDWR)) >= 0 ) {
                  if (ioctl (fd, I2C_SLAVE, LMU_ADDR) >= 0 ) {
                        if (read (fd, buf, 4) == 4 ) {
                              close (fd);
                              rc = n;
                              break;
                        }
                  }
                  close (fd);
            } else if (errno == ENODEV) {
                  rc = -E_NOCHAR;
                  break;
            } else if (errno == EACCES) {
                  rc = -E_PERM;
                  break;
            }
      }
#endif
      return rc;
}

void
lmu_set_illumination (int dev, unsigned short level)
{
#if !(defined(DEBUG) && SIMUAMBIENT)
      char i2cdevice[20];
      int fd;
      __u8 buf[3];

      if (dev >= 0) {
            if (level > 15) level = 15;   /* check bounds */
            buf[0] = 0x01;   /* i2c register */
            buf[1] = (__u8) (( ( level * 255 ) & 0xFF00 ) >> 8 );
            buf[2] = (__u8) (( ( level * 255 ) & 0x00FF ));

            snprintf(i2cdevice, 19, "/dev/i2c-%d", dev );
            if ((fd = open (i2cdevice, O_RDWR)) >= 0 ) {
                  if (ioctl (fd, I2C_SLAVE, LMU_ADDR) >= 0 )
                        write (fd, buf, 3);
                  close (fd);
            }
      }
#else
      printf("KBD Level: %2d at %2d%% Ambient\n",
            level, lmu_get_ambient());
      fflush(stdout);
#endif
}

/* This function reads the ambient light sensor. New aluminum
 * PowerBooks have two of them left and right below the speaker
 * covers.
 */
int
lmu_get_raw_ambient (int dev)
{
#if defined(DEBUG) && SIMUAMBIENT
      struct moddata_pmac *base = &modbase_pmac;
      int ambient, level;
      int asens[] = {2+2, 182+135, 217+167, 248+200, 309+281,
                     337+295, 381+341, 423+391, 461+426, 499+473,
                     538+515, 576+563, 620+669,
                   1072+1072, 1073+1076, 1077+1080};
                   
      level = pmac_get_lcdbacklight();
      ambient = asens[level == -1 ? 0 : level] + base->ambient * 2;
      if (ambient > 2*AMBIENTMAX)
            ambient = 2*AMBIENTMAX;
      return ambient;
#else
      char i2cdevice[20], buf[4];
      int fd, rc = -1;

      if (dev >= 0) {
            snprintf(i2cdevice, 19, "/dev/i2c-%d", dev );
            if ((fd = open (i2cdevice, O_RDWR)) >= 0 ) {
                  if (ioctl (fd, I2C_SLAVE, LMU_ADDR) >= 0 )
                        if (read (fd, buf, 4 ) == 4) {
                              rc  = (buf[0] << 8) | buf[1];
                              rc += (buf[2] << 8) | buf[3];
                        }
                  close (fd);
            }
      }
      return rc;
#endif
}

/* --------------------- BATTERY LOGFILE DEBUG FUNCTIONS --------------------- */
int
batlog_setup()
{
      struct moddata_pmac *base = &modbase_pmac;
      FILE *fd;
      char buffer[100], *token;
      char batlog[STDBUFFERLEN];
      int chargeold = 0, chargenew, cycle = -1;

      if (base->batlog_mode) {
            cycle = 1;
            if ((fd = fopen (DEFAULT_BATCYCLE,"r"))) {
                  while (fgets (buffer, sizeof (buffer), fd))
                        if ((token = strtok (buffer,":\n"))) {
                              if (!strncmp ("cycle", token, 5))
                                    cycle = atoi (strtok(0,":\n"));
                              else if (!strncmp ("charge", token, 6))
                                    chargeold = atoi(strtok(0, ":\n"));
                              else
                                    strtok (0,":\n");
                        }
                  fclose(fd);
                  if (!base->flags.ac_power) {
                        chargenew = base->charge[0] + base->charge[1];
                        if ((chargenew-chargeold) > 50)
                              cycle++;  /* start new cycle */
                  }
            }
            snprintf (batlog, STDBUFFERLEN, DEFAULT_BATLOG, cycle);
            print_error(_("INFO: Current battery cycle: %d, active logfile: %s.\n"),
                  cycle, base->batlog_mode == BATLOG_LOG ? batlog : "none");
      }
      return cycle;
}

void
batlog_save()
{
      struct moddata_pmac *base = &modbase_pmac;
      FILE *fd;

      if (base->batlog_mode)
            if ((fd = fopen (DEFAULT_BATCYCLE,"w"))) {
                  fprintf (fd,"cycle   : %d\n", base->batlog_cycle);
                  fprintf (fd,"charge  : %d\n", base->charge[0] + base->charge[1]);
                  fclose(fd);
            }
}

void
batlog_write(long timerem)
{
      struct moddata_pmac *base = &modbase_pmac;
      struct timeval tv;
      char batlog[STDBUFFERLEN];
      FILE *fd;
      static long curtime = 0;
      int rc;

      if (base->batlog_mode != BATLOG_LOG)
            return;
            
      snprintf (batlog, STDBUFFERLEN, DEFAULT_BATLOG, base->batlog_cycle);
      rc = check_devorfile(batlog, TYPE_FILE);
      if (rc == E_NOEXIST) {
            if ((fd = fopen(batlog, "w")) != NULL) { /* open new protocol file */
                  fprintf(fd, "# battery cycle %d\n# time\tvoltage[0]\tcharge[0]\tchargemax[0]\tvoltage[1]\tcharge[1]\tchargemax[1]\ttimerem.\n", base->batlog_cycle);
                  fclose(fd);
                  rc = 0;
            } else
                  rc = E_OPEN;
      }
            
      /* to evaluate battery degradation current battery values
         are written to a lofile for further investigation.
         Each battery charge/discharge cycle in a seperate file. */
      if (rc == 0 && timerem > -1) {
            gettimeofday(&tv, NULL);
            if (tv.tv_sec != curtime) {
                  curtime = tv.tv_sec;
                  if ((fd = fopen(batlog, "a")) != NULL) {    /* open protocol file */
                        fprintf(fd, "%ld\t%d\t%d\t%d\t%d\t%d\t%d\t%ld\n", tv.tv_sec,
                          base->voltage[0], base->charge[0], base->chargemax[0],
                          base->voltage[1], base->charge[1], base->chargemax[1],
                          timerem);
                        fclose(fd);
                  }
            }
      }
}
#endif /* WITH_MODULE_PMAC */

Generated by  Doxygen 1.6.0   Back to index