cb466960004e47212a23e474562e66e4aa48123e diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7871f05..8c9ae6d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_HP_ILO) += hpilo.o obj-$(CONFIG_ISL29003) += isl29003.o obj-$(CONFIG_C2PORT) += c2port/ obj-y += eeprom/ +obj-$(CONFIG_MACH_NETBOOKPRO) += lx/ diff --git a/drivers/misc/lx/Makefile b/drivers/misc/lx/Makefile new file mode 100644 index 0000000..c53c032 --- /dev/null +++ b/drivers/misc/lx/Makefile @@ -0,0 +1,6 @@ + +lx-battery-y := lx-battery-core.o +lx-battery-$(CONFIG_APM_EMULATION) += lx-battery-apm.o +lx-battery-$(CONFIG_PROC_FS) += lx-battery-procfs.o + +obj-y := lx-battery.o diff --git a/drivers/misc/lx/lx-battery-apm.c b/drivers/misc/lx/lx-battery-apm.c new file mode 100644 index 0000000..3ab7f91 --- /dev/null +++ b/drivers/misc/lx/lx-battery-apm.c @@ -0,0 +1,123 @@ +/* + * drivers/misc/lx/lx-battery-apm.c + * + * (c) 2004 Simtec Electronics + * Written by Ben Dooks + */ +#include +#include +#include +#include +#include +#include + +#include + +#include "lx-battery.h" + +static void lx_apm_get_powerstatus(struct apm_power_info *pwr) +{ + struct pcon_battinfo batt; + + if (lx_battmon_getstate(&batt)) { + pr_debug("apm: failed to read power info\n"); + return; + } + + if (batt.flags & NBP_PCON_PWRF_ACPresent) + pwr->ac_line_status = APM_AC_ONLINE; + else + pwr->ac_line_status = APM_AC_OFFLINE; + + if (batt.flags & NBP_PCON_PWRF_BatteryPreset) { + if (batt.charge_state == NBP_PCON_CS_Charging) { + pwr->battery_status = APM_BATTERY_STATUS_CHARGING; + pwr->battery_flag = APM_BATTERY_FLAG_CHARGING; + } else { + pwr->battery_flag = 0; + } + + if (batt.main_batt_capacity_percent >= 20) { + pwr->battery_status = APM_BATTERY_STATUS_HIGH; + pwr->battery_flag |= APM_BATTERY_FLAG_HIGH; + } else if (batt.main_batt_capacity_percent >= 5) { + pwr->battery_status = APM_BATTERY_STATUS_LOW; + pwr->battery_flag |= APM_BATTERY_FLAG_LOW; + } else { + pwr->battery_status = APM_BATTERY_STATUS_CRITICAL; + pwr->battery_flag |= APM_BATTERY_FLAG_CRITICAL; + } + + if (batt.discharge_current) { + /* + * max_capacity is in mAh, discharge_current is mA, + * so mAh/mA = hours not minutes. --rmk + */ + pwr->units = APM_UNITS_SECS; + pwr->time = batt.max_capacity * + batt.main_batt_capacity_percent * 36 / + batt.discharge_current; + + if (pwr->time >= 32767) { + pwr->time /= 60; + pwr->units = APM_UNITS_MINS; + } + } + + pwr->battery_life = batt.main_batt_capacity_percent; + } else { + pwr->battery_status = APM_BATTERY_STATUS_NOT_PRESENT; + pwr->battery_flag = APM_BATTERY_FLAG_NOT_PRESENT; + } +} + +/* + * irq 1 is "power failing" + * irq 2 is "please suspend" + */ +static irqreturn_t power_irq(int irqno, void *arg) +{ + apm_queue_event((apm_event_t)(int)arg); + return IRQ_HANDLED; +} + +static int __init lx_apm_init(void) +{ + int ret; + + ret = request_irq(IRQ_GPIO(0), power_irq, + IRQF_DISABLED|IRQF_TRIGGER_RISING, + "Battery Warning", (void *)APM_CRITICAL_SUSPEND); + if (ret) { + pr_err("LX: Failed to get irq for battery warning\n"); + return ret; + } + + ret = request_irq(IRQ_GPIO(1), power_irq, + IRQF_DISABLED|IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, + "Suspend Request", (void *)APM_SYS_SUSPEND); + if (ret) { + pr_err("LX: Failed to get irq for suspend request\n"); + free_irq(IRQ_GPIO(0), (void *)APM_CRITICAL_SUSPEND); + return ret; + } + + apm_get_power_status = lx_apm_get_powerstatus; + + return 0; +} + +static void __exit lx_apm_exit(void) +{ + free_irq(IRQ_GPIO(1), (void *)APM_SYS_SUSPEND); + free_irq(IRQ_GPIO(0), (void *)APM_CRITICAL_SUSPEND); + + apm_get_power_status = NULL; +} + +module_init(lx_apm_init); +module_exit(lx_apm_exit); + +MODULE_AUTHOR("Ben Dooks, Simtec Electronics "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("LX APM Emulation interface"); diff --git a/drivers/misc/lx/lx-battery-core.c b/drivers/misc/lx/lx-battery-core.c new file mode 100644 index 0000000..563147a --- /dev/null +++ b/drivers/misc/lx/lx-battery-core.c @@ -0,0 +1,432 @@ +/* + * LX battery monitoring + * + * Copyright (c) 2004 Simtec Electronics + * + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * PCON sends 32-byte data frames to the PXA over the SSP bus. When + * enabled, it will attempt a transfer once every 10 seconds by raising + * DREQ1. This causes the SSP tx DMA to start, thereby causing the SSP + * clock to run. This allows the SSP rx DMA to receive the battery state + * information. + * + * If no battery is fitted, PCON will not send battery state. This + * causes us to time out and re-send the request to enable battery state + * from PCON. We could avoid this by checking whether PCON says the + * battery is fitted, but for safety we leave the timer running. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "lx-battery.h" + +#define SPI_DMASIZE (32) +#define SPI_RXBUFFERS (12) + +#define SPI_DMA_CMD \ + (DCMD_FLOWSRC | DCMD_BURST32 | DCMD_WIDTH2 | SPI_DMASIZE) + +#define SPI_RXDMA_CMD (DCMD_INCTRGADDR | SPI_DMA_CMD | DCMD_ENDIRQEN) +#define SPI_TXDMA_CMD (DCMD_INCSRCADDR | SPI_DMA_CMD) + +#define BAT_TIMEOUT (30 * HZ) + +/* size of this structure must be aligned to 4 words */ +struct spi_buffer { + pxa_dma_desc dma_desc; + u32 data[SPI_DMASIZE / sizeof(u32)]; +}; + +/* + * This describes the DMA data buffer. + */ +struct spi_dma_data { + struct spi_buffer rx[SPI_RXBUFFERS] __attribute__((aligned(16))); + struct spi_buffer tx __attribute__((aligned(16))); +}; + +struct bat_priv { + struct device *dev; + struct ssp_dev ssp; + struct ssp_state state; + struct spi_dma_data *cpu_ring; + dma_addr_t dma_ring; + int rx_chan; + int tx_chan; + unsigned int rx_last; + dma_addr_t rx_phys[SPI_RXBUFFERS]; + struct timer_list timer; + struct work_struct work; + + struct pcon_battinfo battinfo; + struct power_stats stats; +}; + +/* + * This macro returns the DMA address of element 'x' from the above + * structure. + */ +#define SPI_DMAPTR(bat,x) \ + (bat->dma_ring + offsetof(struct spi_dma_data, x)) + +static struct bat_priv *global_bat; + +int lx_battmon_getstate(struct pcon_battinfo *info) +{ + unsigned long flags; + int ret = -ENOENT; + + local_irq_save(flags); + if (global_bat && global_bat->battinfo.magic == PCON_BATTINFO_MAGIC) { + memcpy(info, &global_bat->battinfo, sizeof(*info)); + ret = 0; + } + local_irq_restore(flags); + return ret; +} + +int lx_battmon_getstats(struct power_stats *stats) +{ + unsigned long flags; + int ret = -ENOENT; + + local_irq_save(flags); + if (global_bat) { + memcpy(stats, &global_bat->stats, sizeof(*stats)); + ret = 0; + } + local_irq_restore(flags); + return ret; +} + +static struct pcon_battinfo *lx_to_battinfo(u32 *data) +{ + u32 *ptr = data; + unsigned int i; + + /* + * 16-bit swap the data. + */ + for (i = 0; i < SPI_DMASIZE / sizeof(u32); i++, ptr++) { + u32 t1, t2; + t1 = *ptr; + t2 = (t1 >> 8) & ~0x0000ff00; + t2 |= (t1 << 8) & ~0x00ff0000; + *ptr = t2; + } + return (struct pcon_battinfo *)data; +} + +static void lx_bat_rx(struct bat_priv *bat, u32 *data) +{ + struct pcon_battinfo *battinfo = lx_to_battinfo(data); + + if (battinfo->magic == PCON_BATTINFO_MAGIC) { + bat->battinfo = *battinfo; + bat->stats.last_good = jiffies; + bat->stats.num_good++; + + /* If successfully received, restart the timeout */ + mod_timer(&bat->timer, jiffies + BAT_TIMEOUT); + } else { + bat->stats.last_bad = jiffies; + bat->stats.num_bad++; + } + + /* zero the buffer we where given */ + memset(data, 0, SPI_DMASIZE); +} + +static void lx_bat_rxirq(int irq, void *param) +{ + struct bat_priv *bat = param; + unsigned int dcsr, dtadr; + dma_addr_t ddadr; + + dcsr = DCSR(bat->rx_chan); + ddadr = DDADR(bat->rx_chan); + dtadr = DTADR(bat->rx_chan); + + /* clear all pending interrupts */ + DCSR(bat->rx_chan) = dcsr; + + dev_dbg(bat->dev, "rx irq: dcsr=%08x, ddadr=%08x, dtadr=%08x\n", + dcsr, ddadr, dtadr); + + if (dcsr & DCSR_BUSERR) + dev_err(bat->dev, "rx: dma error (%08x)\n", dcsr); + + if (dcsr & DCSR_ENDINTR) { + unsigned int buff = bat->rx_last; + + dev_dbg(bat->dev, "rx: dma channel end\n"); + + while (1) { + struct spi_buffer *rx = &bat->cpu_ring->rx[buff]; + + buff = (buff + 1) % SPI_RXBUFFERS; + + /* check: see if this is the last buffer... */ + if (bat->rx_phys[buff] == (ddadr & ~15)) + break; + + bat->rx_last = buff; + lx_bat_rx(bat, rx->data); + } + } +} + +static void lx_bat_txirq(int irq, void *param) +{ + struct bat_priv *bat = param; + unsigned int dcsr = DCSR(bat->tx_chan); + + DCSR(bat->tx_chan) = dcsr | DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR; + + dev_dbg(bat->dev, "tx: dcsr=%08x\n", dcsr); +} + +/* + * Disable the SPI engine and its associated DMA channels. + */ +static void lx_bat_disable(struct bat_priv *bat) +{ + ssp_disable(&bat->ssp); + + DCSR(bat->rx_chan) = 0; + DCSR(bat->tx_chan) = 0; + + CKEN &= ~(1 << 3); //pxa_set_cken(CKEN3_SSP, 0); +} + +/* + * Enable the SPI engine and its associated DMA channels. + */ +static void lx_bat_enable(struct bat_priv *bat) +{ + CKEN |= (1 << 3); //pxa_set_cken(CKEN3_SSP, 1); + + DRCMR(1) = bat->tx_chan | DRCMR_MAPVLD; + DRCMR(bat->ssp.ssp->drcmr_rx) = bat->rx_chan | DRCMR_MAPVLD; + + bat->rx_last = 0; + DDADR(bat->rx_chan) = SPI_DMAPTR(bat, rx[0].dma_desc); + DDADR(bat->tx_chan) = SPI_DMAPTR(bat, tx.dma_desc); + + ssp_enable(&bat->ssp); + + DCSR(bat->rx_chan) |= DCSR_RUN; + DCSR(bat->tx_chan) |= DCSR_RUN; +} + +static void lx_bat_work(struct work_struct *work) +{ + struct bat_priv *bat = container_of(work, struct bat_priv, work); + int ret; + + lx_bat_disable(bat); + lx_bat_enable(bat); + + ret = pcon_monitorbattery(1, 0); + if (ret < 0) + dev_err(bat->dev, "LX: battery monitor setstate failed\n"); + + mod_timer(&bat->timer, jiffies + BAT_TIMEOUT); +} + +static void lx_bat_timeout(unsigned long data) +{ + struct work_struct *work = (struct work_struct *)data; + pr_debug("LX: battery monitor timeout, restarting\n"); + schedule_work(work); +} + +static int __init lx_bat_probe(struct platform_device *dev) +{ + struct bat_priv *bat; + int ret, i; + + if (global_bat) + return -EBUSY; + + bat = kzalloc(sizeof(*bat), GFP_KERNEL); + if (!bat) { + ret = -ENOMEM; + goto err_out; + } + + bat->dev = &dev->dev; + platform_set_drvdata(dev, bat); + + setup_timer(&bat->timer, lx_bat_timeout, (unsigned long)&bat->work); + INIT_WORK(&bat->work, lx_bat_work); + + ret = ssp_init(&bat->ssp, PXA25x_SSP, SSP_NO_IRQ); + if (ret) + goto err_ssp; + + /* + * SSCR0 -> 0xF | (1<<7) (0x8F) + * SSCR1 -> (11<<6) | (15<<10) = (0x3EC0) + * + * SSCR0 = SSCR0_Motorola | SSCR0_DataSize(16) | SSCR0_SerClkDiv(2); + * SSCR1 = SSCR1_TxTresh(12) | SSCR1_RxTresh(16); + */ + ssp_config(&bat->ssp, SSCR0_Motorola | SSCR0_DataSize(16), + SSCR1_TxTresh(12) | SSCR1_RxTresh(16), 0, SSCR0_SerClkDiv(4)); + + bat->cpu_ring = dma_alloc_coherent(bat->dev, + sizeof(struct spi_dma_data), + &bat->dma_ring, GFP_KERNEL); + + if (bat->cpu_ring == NULL) { + dev_err(bat->dev, "no memory for DMA buffers\n"); + ret = -ENOMEM; + goto err_dmabuf; + } + + memset(bat->cpu_ring, 0, sizeof(struct spi_dma_data)); + + for (i = 0; i < SPI_RXBUFFERS; i++) { + struct spi_buffer *rx = &bat->cpu_ring->rx[i]; + dma_addr_t ddadr = SPI_DMAPTR(bat, rx[i + 1].dma_desc); + + if (i == SPI_RXBUFFERS - 1) + ddadr = bat->dma_ring; + + rx->dma_desc.ddadr = ddadr; + rx->dma_desc.dsadr = bat->ssp.ssp->phys_base + SSDR; + rx->dma_desc.dtadr = SPI_DMAPTR(bat, rx[i].data); + rx->dma_desc.dcmd = SPI_RXDMA_CMD; + + bat->rx_phys[i] = SPI_DMAPTR(bat, rx[i].dma_desc); + } + + bat->cpu_ring->tx.dma_desc.ddadr = SPI_DMAPTR(bat, tx.dma_desc); + bat->cpu_ring->tx.dma_desc.dsadr = SPI_DMAPTR(bat, tx.data); + bat->cpu_ring->tx.dma_desc.dtadr = bat->ssp.ssp->phys_base + SSDR; + bat->cpu_ring->tx.dma_desc.dcmd = SPI_TXDMA_CMD; + + bat->rx_chan = pxa_request_dma("SPI RX", DMA_PRIO_LOW, + lx_bat_rxirq, bat); + if (bat->rx_chan < 0) { + dev_err(bat->dev, "failed to allocate RX DMA channel (%d)\n", + bat->rx_chan); + ret = bat->rx_chan; + goto err_rxdma; + } + + bat->tx_chan = pxa_request_dma("SPI TX", DMA_PRIO_LOW, + lx_bat_txirq, bat); + if (bat->tx_chan < 0) { + dev_err(bat->dev, "failed to allocate TX DMA channel (%d)\n", + bat->tx_chan); + ret = bat->tx_chan; + goto err_txdma; + } + + lx_bat_enable(bat); + pcon_monitorbattery(1, PCON_FLG_ASYNC); + mod_timer(&bat->timer, jiffies + BAT_TIMEOUT); + + global_bat = bat; + + return 0; + + err_txdma: + pxa_free_dma(bat->rx_chan); + err_rxdma: + dma_free_coherent(bat->dev, sizeof(struct spi_dma_data), bat->cpu_ring, + bat->dma_ring); + err_dmabuf: + ssp_exit(&bat->ssp); + err_ssp: + kfree(bat); + err_out: + return ret; +} + +static int lx_bat_remove(struct platform_device *dev) +{ + struct bat_priv *bat = platform_get_drvdata(dev); + + global_bat = NULL; + + pcon_monitorbattery(0, 0); + lx_bat_disable(bat); + + del_timer_sync(&bat->timer); + cancel_work_sync(&bat->work); + pxa_free_dma(bat->tx_chan); + pxa_free_dma(bat->rx_chan); + dma_free_coherent(bat->dev, sizeof(struct spi_dma_data), bat->cpu_ring, + bat->dma_ring); + ssp_exit(&bat->ssp); + kfree(bat); + return 0; +} + +static int lx_bat_suspend(struct platform_device *dev, pm_message_t state) +{ + struct bat_priv *bat = platform_get_drvdata(dev); + pcon_monitorbattery(0, 0); + lx_bat_disable(bat); + del_timer_sync(&bat->timer); + ssp_save_state(&bat->ssp, &bat->state); + return 0; +} + +static int lx_bat_resume(struct platform_device *dev) +{ + struct bat_priv *bat = platform_get_drvdata(dev); + ssp_restore_state(&bat->ssp, &bat->state); + lx_bat_enable(bat); + pcon_monitorbattery(1, PCON_FLG_ASYNC); + mod_timer(&bat->timer, jiffies + BAT_TIMEOUT); + return 0; +} + +static struct platform_driver bat_driver = { + .probe = lx_bat_probe, + .remove = lx_bat_remove, + .suspend = lx_bat_suspend, + .resume = lx_bat_resume, + .driver = { + .name = "lx-spi", + }, +}; + +static int __init lx_bat_init(void) +{ + return platform_driver_register(&bat_driver); +} + +static void __exit lx_bat_exit(void) +{ + platform_driver_unregister(&bat_driver); +} + +module_init(lx_bat_init); +module_exit(lx_bat_exit); + +MODULE_AUTHOR("Ben Dooks, Simtec Electronics "); +MODULE_DESCRIPTION("LX SPI Battery Monitor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/lx/lx-battery-procfs.c b/drivers/misc/lx/lx-battery-procfs.c new file mode 100644 index 0000000..892ea2a --- /dev/null +++ b/drivers/misc/lx/lx-battery-procfs.c @@ -0,0 +1,140 @@ +/* + * drivers/misc/lx/lx-battery-procfs.c + * + * Copyright (c) 2004 Simtec Electronics + * Written by Ben Dooks + * + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include "lx-battery.h" + +static int lx_batproc_proc_rd_stats(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct power_stats stats; + unsigned long timediff; + int i; + + lx_battmon_getstats(&stats); + + /* work out how long since last management frame received */ + timediff = (jiffies - stats.last_good) / (HZ / 10); + i = sprintf(page, "Good frames: %ld (%ld.%ld secs)\n", + stats.num_good, timediff / 10, timediff % 10); + + if (stats.num_bad) { + timediff = (jiffies - stats.last_bad) / (HZ / 10); + i += sprintf(page+i, "Bad frames: %ld (%ld.%ld secs)\n", + stats.num_bad, timediff / 10, timediff % 10); + } + + return i; +} + +static const char *charge_state[] = { + [NBP_PCON_CS_NotCharging] = "not charging", + [NBP_PCON_CS_Charging] = "charging", + [NBP_PCON_CS_QuickChargeComplete] = "quick charging", + [NBP_PCON_CS_ChargeComplete] = "charging complete", +}; + +static int lx_batproc_proc_rd_drv(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct pcon_battinfo bi; + int i = lx_battmon_getstate(&bi); + + if (i) + return sprintf(page, "Invalid battery state\n"); + + i += sprintf(page+i, "Main Battery: %spresent\n", + bi.flags & NBP_PCON_PWRF_BatteryPreset ? "" : "not "); + i += sprintf(page+i, " Temperature : %d.%d C\n", + bi.temperature / 10, bi.temperature % 10); + i += sprintf(page+i, " Maximum Capacity : %d mAh\n", + bi.max_capacity); + i += sprintf(page+i, " Voltage : %d mV\n", + bi.main_batt_voltage); + i += sprintf(page+i, " Capacity : %d %%\n", + bi.main_batt_capacity_percent); + i += sprintf(page+i, " Charge Current : %d mA\n", + bi.charge_current); + i += sprintf(page+i, " Discharge Current: %d mA\n", + bi.discharge_current); + i += sprintf(page+i, " Status : %scalibrated%s\n\n", + bi.flags & NBP_PCON_PWRF_Calibrated ? "" : "not ", + bi.flags & NBP_PCON_PWRF_Calibrating ? ", calibrating" : ""); + + i += sprintf(page+i, "Backup Battery: %spresent\n", + bi.flags & NBP_PCON_PWRF_BackupBatteryPreset ? "" : "not "); + i += sprintf(page+i, " Battery : %d mV\n", + bi.backup_batt_voltage); + i += sprintf(page+i, " Capacity : %d %%\n\n", + bi.backup_batt_capacity_percent); + + i += sprintf(page+i, "Charger State:\n"); + i += sprintf(page+i, " AC %splugged in\n", + bi.flags & NBP_PCON_PWRF_ACPresent ? "" : "not " ); + i += sprintf(page+i, " Charging %s\n", + bi.flags & NBP_PCON_PWRF_ChargeEnabled ? "enabled" : "disabled"); + i += sprintf(page+i, " Status: "); + + if (bi.charge_state < ARRAY_SIZE(charge_state) && + charge_state[bi.charge_state]) + i += sprintf(page+i, "%s\n", charge_state[bi.charge_state]); + else + i += sprintf(page+i, "unknown state (%d)\n", bi.charge_state); + + return i; +} + +static int __init lx_batproc_init(void) +{ + struct proc_dir_entry *pde; + int ret = -ENOMEM; + + pde = create_proc_read_entry("driver/battery", 0, NULL, + lx_batproc_proc_rd_drv, NULL); + if (pde == NULL) { + pr_err("LX: failed to create /proc/driver/battery entry\n"); + goto err_out; + } + + pde = create_proc_read_entry("driver/battery-stats", 0, NULL, + lx_batproc_proc_rd_stats, NULL); + if (pde == NULL) { + pr_err("LX: failed to create /proc/driver/battery-stats entry\n"); + goto err_drv; + } + + return 0; + + err_drv: + remove_proc_entry("driver/battery", NULL); + err_out: + return ret; +} + +static void __exit lx_batproc_exit(void) +{ + remove_proc_entry("driver/battery-stats", NULL); + remove_proc_entry("driver/battery", NULL); +} + +module_init(lx_batproc_init); +module_exit(lx_batproc_exit); + +MODULE_AUTHOR("Ben Dooks, Simtec Electronics "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("LX Battery Monitor procfs support"); diff --git a/drivers/misc/lx/lx-battery.h b/drivers/misc/lx/lx-battery.h new file mode 100644 index 0000000..e57fb9d --- /dev/null +++ b/drivers/misc/lx/lx-battery.h @@ -0,0 +1,37 @@ +/* struct pcon_battinfo should be 32bytes long */ +struct pcon_battinfo { + u16 flags; +#define NBP_PCON_PWRF_BatteryPreset 0x0001 +#define NBP_PCON_PWRF_ACPresent 0x0002 +#define NBP_PCON_PWRF_ChargeEnabled 0x0004 +#define NBP_PCON_PWRF_BackupBatteryPreset 0x0008 +#define NBP_PCON_PWRF_Calibrated 0x0010 +#define NBP_PCON_PWRF_Calibrating 0x0020 + u16 unused[4]; + u16 max_capacity; + u16 temperature; + u16 charge_state; +#define NBP_PCON_CS_NotCharging 0 +#define NBP_PCON_CS_Charging 1 +#define NBP_PCON_CS_QuickChargeComplete 2 +#define NBP_PCON_CS_ChargeComplete 3 + u16 main_batt_voltage; + u16 backup_batt_voltage; + u8 main_batt_capacity_percent; + u8 backup_batt_capacity_percent; + u16 charge_current; + u16 discharge_current; + u16 pad[2]; + u16 magic; +#define PCON_BATTINFO_MAGIC (0xADB0) +} __attribute__((__packed__)); + +struct power_stats { + unsigned long last_good; + unsigned long num_good; + unsigned long last_bad; + unsigned long num_bad; +}; + +extern int lx_battmon_getstate(struct pcon_battinfo *info); +extern int lx_battmon_getstats(struct power_stats *stats);