h2200_battery.c - fully functioning

From: Szabolcs Gyurko <szabolcs.gyurko_at_tlt.hu>
Date: Tue, 07 Sep 2004 17:52:05 +0200

Hi all,

I'm glad to introduce the fully functioning battery management for our
handsome little thing :)
So the driver implements all the interface to the drivers/misc/battery.c
but three.

Here are the issues:

H2200_BATTERY_(MIN|MAX)_(CURRENT|VOLTAGE) constants are must be defined to
the correct values.
Regarding to my measurements the max_voltage is 433 so I defined it to 440
to be on the safe side.
The min_voltage is defined to 0 but i'm not sure it is like that. Needs to
be measured.
The same with the min_current and max_current. Temperature info is also
working.

Regarding to include/linux/battery.h three functions are not implemented.
(get_charge, get_(min|max)_charge)
Actually I don't even know what do they supposed to mean...
My best guess is that the driver should calculate the charge in
percentages.
For that an internal conversion table must be implemented. For that I need
some voltage/percentage pairs
to be measured. As always testers are most welcome... :)

I found an issue in the underlying code (probably in
drivers/misc/battery.c) also. The "current" file
is appearing faulty in the sysfs. It is appearing as "(get_current())"
instead of "current". It must be some
macro or something which gives buggy result. I'll try to look it up. But
otherwise it gives back the correct
current value.

Every comments are most welcome otherwise have luck.

-- 
Szabolcs Gyurko
-------------------------------------------------- cut  
-----------------------------------------------------
/*
  * Battery driver lowlevel interface for iPAQ H2200 series
  *
  * Copyright (c) 2004 Matt Reimer
  *               2004 Szabolcs Gyurko
  *
  * Use consistent with the GNU GPL is permitted,
  * provided that this copyright notice is
  * preserved in its entirety in all copies and derived works.
  *
  * Author:  Matt Reimer <mreimer_at_vpop.net>
  *          April 2004
  *
  *          Szabolcs Gyurko <szabolcs.gyurko_at_tlt.hu>
  *          September 2004
  */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/soc-device.h>
#include <linux/battery.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/arch/hardware.h>
#include <asm/hardware/ipaq-hamcop.h>
#include <asm/arch-pxa/h2200-asic.h>
#include <asm/arch-pxa/h2200-gpio.h>
#include <asm/arch-pxa/h2200-init.h>
#define DEBUG if (h2200_battery_debug) printk
int owm_sample_delay = 200;             /* Sample delay in msec */
int owm_rw_delay = 100;         /* R/W delay in msec */
int h2200_battery_debug = 0;
MODULE_PARM(owm_sample_delay, "i");
MODULE_PARM_DESC(owm_sample_delay, "Set OWM sample delay (msec)");
MODULE_PARM(owm_rw_delay, "i");
MODULE_PARM_DESC(owm_rw_delay, "Set OWM R/W delay (msec)");
MODULE_PARM(h2200_battery_debug, "i");
MODULE_PARM_DESC(h2200_battery_debug, "If not zero debug messages will be  
printed");
#define H2200_BATTERY_MAX_VOLTAGE 440 /* My measurements */
#define H2200_BATTERY_MIN_VOLTAGE 0 /* Maybe incorrect */
#define H2200_BATTERY_MAX_CURRENT 0 /* MUST BE MEASURED */
#define H2200_BATTERY_MIN_CURRENT 0 /* Maybe incorrect */
#define OWM_STATE_NOTHING 0
#define OWM_STATE_RESET 1
#define OWM_STATE_READ 2
#define OWM_STATE_WRITE 3
static void *h2200_iomap = NULL;
static int owm_state = OWM_STATE_NOTHING;
static int owm_last;
#define OWM_Interrupt SHAMCOP_OWM_Interrupt(h2200_iomap  
+ _SHAMCOP_OWM_Base)
#define OWM_Command SHAMCOP_OWM_Command(h2200_iomap + _SHAMCOP_OWM_Base)
#define OWM_Data SHAMCOP_OWM_Data(h2200_iomap + _SHAMCOP_OWM_Base)
struct owm_net_address {
         char address[8];
};
static void
h2200_owm_down(void)
{
         unsigned long flags;
         local_irq_save(flags);
         DEBUG("%s: irq saved, disabling 1-wire\n", __FUNCTION__);
         SHAMCOP_CPM_ClockControl(h2200_iomap + _SHAMCOP_CPM_Base) &=  
~SHAMCOP_CPM_CLKCON_1WIRE_CLKEN;
         DEBUG("%s: 1-wire disabled, restoring irq\n", __FUNCTION__);
         local_irq_restore(flags);
}
static void
h2200_owm_up(void)
{
         unsigned long flags;
         local_irq_save(flags);
         DEBUG("%s: irq saved, enabling 1-wire\n", __FUNCTION__);
         SHAMCOP_CPM_ClockControl(h2200_iomap + _SHAMCOP_CPM_Base) |=  
SHAMCOP_CPM_CLKCON_1WIRE_CLKEN;
         DEBUG("%s: 1-wire enabled, restoring irq\n", __FUNCTION__);
         local_irq_restore(flags);
         DEBUG("%s: setting up clock\n", __FUNCTION__);
         SHAMCOP_OWM_ClockDivisor(h2200_iomap + _SHAMCOP_OWM_Base) = 0x13;
         DEBUG("%s: enabling interrupt\n", __FUNCTION__);
         SHAMCOP_OWM_InterruptEnable(h2200_iomap + _SHAMCOP_OWM_Base) =  
OWM_INTEN_ERBF | OWM_INTEN_IAS | OWM_INTEN_EPD;
}
static int
h2200_owm_sample(void)
{
         int value = OWM_Interrupt;
         if ((value & OWM_INT_PD) && owm_state == OWM_STATE_RESET) return 0;
         if ((value & OWM_INT_TBE) && (value & OWM_INT_RBF) && owm_state ==  
OWM_STATE_WRITE) {
                 owm_last = OWM_Data;
                 return 0;
         }
         if ((value & OWM_INT_RBF)) {
                 owm_last = OWM_Data;
                 if (owm_state == OWM_STATE_READ) return 0;
         }
         return 1;
}
static int
h2200_one_wire_wait_for_interrupt(int msec)
{
         signed long timeout;
         timeout = msec * HZ / 1000;
         while (timeout > 0) {
                 timeout = schedule_timeout(timeout);
                 if (timeout <= 0) {
                         if (h2200_owm_sample()) return 1;
                 }
         }
         return 0;
}
static int
h2200_one_wire_reset(void)
{
         int retval;
         owm_state = OWM_STATE_RESET;
         retval = OWM_Interrupt;
         OWM_Command = (OWM_Command | OWM_CMD_ONE_WIRE_RESET);
         retval = h2200_one_wire_wait_for_interrupt(owm_sample_delay);
         if (retval) {
                 DEBUG("%s: OWM Reset FAILED\n", __FUNCTION__);
                 return retval;
         }
         if (OWM_Interrupt & OWM_INT_PDR) {
                 DEBUG("%s: OWM Reset failed, no battery found\n",  
__FUNCTION__);
                 return 1;
         }
         udelay(owm_sample_delay);
         DEBUG("%s: Bus reset successfull\n", __FUNCTION__);
         return 0;
}
static int
h2200_one_wire_write(unsigned char data)
{
         int result;
         owm_state = OWM_STATE_WRITE;
         result = OWM_Interrupt;
         OWM_Data = data;
         result = h2200_one_wire_wait_for_interrupt(owm_sample_delay);
         if (result) DEBUG("%s: OWM write failed %d (0x%04x)\n",  
__FUNCTION__, result, OWM_Interrupt);
         udelay(owm_rw_delay);
         return result;
}
static int
h2200_one_wire_read( void )
{
         int result;
         owm_state = OWM_STATE_READ;
         result = OWM_Interrupt;
         OWM_Data = 0xff;
         result = h2200_one_wire_wait_for_interrupt(owm_sample_delay);
         if (result) {
                 DEBUG("%s: OWM read failed %d (0x%04x)\n", __FUNCTION__,  
result, OWM_Interrupt);
                 return result;
         }
         udelay(owm_rw_delay);
         return owm_last;
}
static int
h2200_owm_setup(struct owm_net_address *net) {
         int result, i;
         if ((result = h2200_one_wire_reset()) != 0) return result;
         if (net) {
                 if ((result = h2200_one_wire_write(0x55)) != 0) return  
result;
                 for ( i = 0 ; i < 8 ; i++ ) {
                         if ((result =  
h2200_one_wire_write(net->address[i])) != 0) return result;
                 }
         } else {
                 if ((result = h2200_one_wire_write(0xcc)) != 0) return  
result;
         }
         return 0;
}
static int
h2200_owm_read_bytes(struct owm_net_address *net, unsigned char address,  
unsigned char *data, unsigned short len)
{
         int result;
         int i;
         h2200_owm_up();
         if ((result = h2200_owm_setup(net)) != 0)          goto  
owm_read_bytes_fail;
         if ((result = h2200_one_wire_write(0x69)) != 0)    goto  
owm_read_bytes_fail;
         if ((result = h2200_one_wire_write(address)) != 0) goto  
owm_read_bytes_fail;
         for ( i = 0 ; i < len ; i++ ) {
                 if ((result = h2200_one_wire_read()) < 0) goto  
owm_read_bytes_fail;
                 data[i] = result;
         }
         result = 0;
  owm_read_bytes_fail:
         h2200_owm_down();
         return result;
}
static int
h2200_get_max_voltage(struct battery *bat)
{
         return H2200_BATTERY_MAX_VOLTAGE;
}
static int
h2200_get_min_voltage(struct battery *bat)
{
         return H2200_BATTERY_MIN_VOLTAGE;
}
static int
h2200_get_voltage(struct battery *bat)
{
         int result;
         unsigned char buf[32];
         int battery_voltage;
         result = h2200_owm_read_bytes(NULL, 0, buf, 32);
         if (result != 0) return result;
         battery_voltage = (buf[12] << 2) | (buf[13] >> 5);
         if (battery_voltage > 1023) return -EINVAL;
         return battery_voltage;
}
static int
h2200_get_max_current(struct battery *bat)
{
         return H2200_BATTERY_MAX_CURRENT;
}
static int
h2200_get_min_current(struct battery *bat)
{
         return H2200_BATTERY_MIN_CURRENT;
}
static int
h2200_get_current(struct battery *bat)
{
         int result;
         unsigned char buf[32];
         int battery_current;
         result = h2200_owm_read_bytes(NULL, 0, buf, 32);
         if (result != 0) return result;
         battery_current = (buf[14] << 5) | (buf[15] >> 3);
         if (battery_current > 4095) battery_current -= 8192;
         return battery_current;
}
static int
h2200_get_temp(struct battery *bat)
{
         int result;
         unsigned char buf[32];
         int battery_temp;
         result = h2200_owm_read_bytes(NULL, 0, buf, 32);
         if (result != 0) return result;
         battery_temp = (buf[24] << 3) | (buf[25] >> 5);
         if (battery_temp > 1023) battery_temp -= 2024;
         return battery_temp;
}
static int
h2200_get_battery_status(struct battery *bat)
{
         return (GET_H2200_GPIO(AC_IN_N)) ? BATTERY_STATUS_NOT_CHARGING :  
BATTERY_STATUS_CHARGING;
}
static struct battery h2200_battery = {
         .name = "h2200 battery",
         .id = "battery0",
         .get_voltage = h2200_get_voltage,
         .get_min_voltage = h2200_get_min_voltage,
         .get_max_voltage = h2200_get_max_voltage,
         .get_current = h2200_get_current,
         .get_min_current = h2200_get_min_current,
         .get_max_current = h2200_get_max_current,
         .get_temp = h2200_get_temp,
         .get_status = h2200_get_battery_status,
};
static int
h2200_battery_class_hotplug(struct class_device *dev, char **envp,
                             int num_envp, char *buffer, int buffer_size)
{
         return 0;
}
static void
h2200_battery_class_release(struct class_device *dev)
{
}
static void
h2200_battery_class_class_release(struct class *class)
{
}
static int __init
h2200_battery_init(void)
{
         int retval;
         printk("Battery inerface for iPAQ h2200 series - Szabolcs  
Gyurko\n");
         h2200_iomap = ioremap(H2200_HAMCOP_BASE, SHAMCOP_MAP_SIZE);
         if (!h2200_iomap) return -ENODEV;
         DEBUG("%s: hamcop io mapped to 0x%08X\n", __FUNCTION__, (unsigned  
int)h2200_iomap);
         retval = battery_class_register(&h2200_battery);
         h2200_battery.class_dev.class->release =  
h2200_battery_class_release;
         h2200_battery.class_dev.class->hotplug =  
h2200_battery_class_hotplug;
         h2200_battery.class_dev.class->class_release =  
h2200_battery_class_class_release;
         return retval;
}
static void __exit h2200_battery_exit(void)
{
         iounmap(h2200_iomap);
         battery_class_unregister(&h2200_battery);
}
module_init(h2200_battery_init);
module_exit(h2200_battery_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko_at_tlt.hu>");
MODULE_DESCRIPTION("Battery interface for iPAQ h2200 series PDA");
/*
  * Local Variables:
  *  mode:c
  *  c-style:"K&R"
  *  c-basic-offset:8
  * End:
  *
  */
------------------------------------------------------- cut  
--------------------------------------------------
Received on Tue Sep 07 2004 - 11:52:21 EDT

This archive was generated by hypermail 2.2.0 : Mon Jul 25 2005 - 17:19:29 EDT