Subject: pcf50606_rebase_changes,patch
authorBalaji Rao <balajirrao@openmoko.org>
Wed, 28 Jan 2009 19:30:42 +0000 (19:30 +0000)
committerAndy Green <agreen@octopus.localdomain>
Wed, 28 Jan 2009 19:30:42 +0000 (19:30 +0000)
X-Git-Url: http://git.openmoko.org/?p=kernel.git;a=commitdiff_plain;h=938eddf17625cce0307f7612a3ea2560384e2384

pcf50606_rebase_changes,patch

This patch brings into andy-tracking all changes related to pcf50606 from old
balaji-tracking.

17 files changed:
drivers/mfd/pcf50606-adc.c [new file with mode: 0644]
drivers/mfd/pcf50606-core.c [new file with mode: 0644]
drivers/mfd/pcf50606-gpo.c [new file with mode: 0644]
drivers/mmc/host/s3cmci.c
drivers/mmc/host/s3cmci.h
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/gta01_battery.c [new file with mode: 0644]
drivers/power/pcf50606-charger.c [new file with mode: 0644]
drivers/regulator/Kconfig
drivers/regulator/Makefile
drivers/regulator/pcf50606-regulator.c [new file with mode: 0644]
drivers/rtc/Kconfig
drivers/rtc/rtc-pcf50606.c
drivers/watchdog/pcf50606_wdt.c [new file with mode: 0644]
include/linux/gta01_battery.h [new file with mode: 0644]
include/linux/mfd/pcf50606/input.h [deleted file]

diff --git a/drivers/mfd/pcf50606-adc.c b/drivers/mfd/pcf50606-adc.c
new file mode 100644 (file)
index 0000000..fe0336b
--- /dev/null
@@ -0,0 +1,279 @@
+/* Philips PCF50606 ADC Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50606 driver mainly by
+ * Harald Welte, Andy Green, Werner Almesberger and Matt Hsu
+ *
+ *  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.
+ *
+ *  NOTE: This driver does not yet support subtractive ADC mode, which means
+ *  you can do only one measurement per read request.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+
+#include <linux/mfd/pcf50606/core.h>
+#include <linux/mfd/pcf50606/adc.h>
+
+struct pcf50606_adc_request {
+       int mux;
+       int result;
+       void (*callback)(struct pcf50606 *, void *, int);
+       void *callback_param;
+
+       /* Used in case of sync requests */
+       struct completion completion;
+
+};
+
+#define PCF50606_MAX_ADC_FIFO_DEPTH 8
+
+struct pcf50606_adc {
+       struct pcf50606 *pcf;
+
+       /* Private stuff */
+       struct pcf50606_adc_request *queue[PCF50606_MAX_ADC_FIFO_DEPTH];
+       int queue_head;
+       int queue_tail;
+       struct mutex queue_mutex;
+};
+
+static inline struct pcf50606_adc *__to_adc(struct pcf50606 *pcf)
+{
+       return platform_get_drvdata(pcf->adc_pdev);
+}
+
+static void adc_setup(struct pcf50606 *pcf, int channel)
+{
+       channel &= PCF50606_ADCC2_ADCMUX_MASK;
+
+       /* start ADC conversion of selected channel */
+       pcf50606_reg_write(pcf, PCF50606_REG_ADCC2, channel |
+                   PCF50606_ADCC2_ADCSTART | PCF50606_ADCC2_RES_10BIT);
+
+}
+
+static void trigger_next_adc_job_if_any(struct pcf50606 *pcf)
+{
+       struct pcf50606_adc *adc = __to_adc(pcf);
+       int head, tail;
+
+       mutex_lock(&adc->queue_mutex);
+
+       head = adc->queue_head;
+       tail = adc->queue_tail;
+
+       if (!adc->queue[head])
+               goto out;
+
+       adc_setup(pcf, adc->queue[head]->mux);
+out:
+       mutex_unlock(&adc->queue_mutex);
+}
+
+static int
+adc_enqueue_request(struct pcf50606 *pcf, struct pcf50606_adc_request *req)
+{
+       struct pcf50606_adc *adc = __to_adc(pcf);
+       int head, tail;
+
+       mutex_lock(&adc->queue_mutex);
+       head = adc->queue_head;
+       tail = adc->queue_tail;
+
+       if (adc->queue[tail]) {
+               mutex_unlock(&adc->queue_mutex);
+               return -EBUSY;
+       }
+
+       adc->queue[tail] = req;
+
+       adc->queue_tail =
+               (tail + 1) & (PCF50606_MAX_ADC_FIFO_DEPTH - 1);
+
+       mutex_unlock(&adc->queue_mutex);
+
+       trigger_next_adc_job_if_any(pcf);
+
+       return 0;
+}
+
+static void
+pcf50606_adc_sync_read_callback(struct pcf50606 *pcf, void *param, int result)
+{
+       struct pcf50606_adc_request *req;
+
+       /*We know here that the passed param is an adc_request object */
+       req = (struct pcf50606_adc_request *)param;
+
+       req->result = result;
+       complete(&req->completion);
+}
+
+int pcf50606_adc_sync_read(struct pcf50606 *pcf, int mux)
+{
+
+       struct pcf50606_adc_request *req;
+       int result;
+
+       /* req is freed when the result is ready, in irq handler*/
+       req = kzalloc(sizeof(*req), GFP_KERNEL);
+       if (!req)
+               return -ENOMEM;
+
+       req->mux = mux;
+       req->callback =  pcf50606_adc_sync_read_callback;
+       req->callback_param = req;
+       init_completion(&req->completion);
+
+       adc_enqueue_request(pcf, req);
+
+       if (wait_for_completion_timeout(&req->completion, 5 * HZ) == 5 * HZ) {
+               dev_err(pcf->dev, "ADC read timed out \n");
+       }
+
+       result = req->result;
+
+       return result;
+}
+EXPORT_SYMBOL_GPL(pcf50606_adc_sync_read);
+
+int pcf50606_adc_async_read(struct pcf50606 *pcf, int mux,
+                            void (*callback)(struct pcf50606 *, void *, int),
+                            void *callback_param)
+{
+       struct pcf50606_adc_request *req;
+
+       /* req is freed when the result is ready, in pcf50606_work*/
+       req = kmalloc(sizeof(*req), GFP_KERNEL);
+       if (!req)
+               return -ENOMEM;
+
+       req->mux = mux;
+       req->callback = callback;
+       req->callback_param = callback_param;
+
+       adc_enqueue_request(pcf, req);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50606_adc_async_read);
+
+static int adc_result(struct pcf50606 *pcf)
+{
+       u16 ret = (pcf50606_reg_read(pcf, PCF50606_REG_ADCS1) << 2) |
+                       (pcf50606_reg_read(pcf, PCF50606_REG_ADCS2) & 0x03);
+
+       dev_info(pcf->dev, "adc result = %d\n", ret);
+
+       return ret;
+}
+
+static void pcf50606_adc_irq(int irq, void *data)
+{
+       struct pcf50606_adc *adc = data;
+       struct pcf50606 *pcf = adc->pcf;
+       struct pcf50606_adc_request *req;
+       int head;
+
+       mutex_lock(&adc->queue_mutex);
+       head = adc->queue_head;
+
+       req = adc->queue[head];
+       if (WARN_ON(!req)) {
+               dev_err(pcf->dev, "pcf50606-adc irq: ADC queue empty!\n");
+               mutex_unlock(&adc->queue_mutex);
+               return;
+       }
+
+       adc->queue[head] = NULL;
+       adc->queue_head = (head + 1) &
+                                     (PCF50606_MAX_ADC_FIFO_DEPTH - 1);
+
+       mutex_unlock(&adc->queue_mutex);
+
+       req->callback(pcf, req->callback_param, adc_result(pcf));
+       kfree(req);
+
+       trigger_next_adc_job_if_any(pcf);
+}
+
+static int __devinit pcf50606_adc_probe(struct platform_device *pdev)
+{
+       struct pcf50606_subdev_pdata *pdata = pdev->dev.platform_data;
+       struct pcf50606_adc *adc;
+
+       adc = kzalloc(sizeof(*adc), GFP_KERNEL);
+       if (!adc)
+               return -ENOMEM;
+
+       adc->pcf = pdata->pcf;
+       platform_set_drvdata(pdev, adc);
+
+       pcf50606_register_irq(pdata->pcf, PCF50606_IRQ_ADCRDY,
+                                       pcf50606_adc_irq, adc);
+
+       mutex_init(&adc->queue_mutex);
+
+       return 0;
+}
+
+static int __devexit pcf50606_adc_remove(struct platform_device *pdev)
+{
+       struct pcf50606_adc *adc = platform_get_drvdata(pdev);
+       int i, head;
+
+       pcf50606_free_irq(adc->pcf, PCF50606_IRQ_ADCRDY);
+
+       mutex_lock(&adc->queue_mutex);
+       head = adc->queue_head;
+
+       if (WARN_ON(adc->queue[head]))
+               dev_err(adc->pcf->dev,
+                       "adc driver removed with request pending\n");
+
+       for (i = 0; i < PCF50606_MAX_ADC_FIFO_DEPTH; i++)
+               kfree(adc->queue[i]);
+
+       mutex_unlock(&adc->queue_mutex);
+       kfree(adc);
+
+       return 0;
+}
+
+struct platform_driver pcf50606_adc_driver = {
+       .driver = {
+               .name = "pcf50606-adc",
+       },
+       .probe = pcf50606_adc_probe,
+       .remove = __devexit_p(pcf50606_adc_remove),
+};
+
+static int __init pcf50606_adc_init(void)
+{
+               return platform_driver_register(&pcf50606_adc_driver);
+}
+module_init(pcf50606_adc_init);
+
+static void __exit pcf50606_adc_exit(void)
+{
+               platform_driver_unregister(&pcf50606_adc_driver);
+}
+module_exit(pcf50606_adc_exit);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50606 adc driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50606-adc");
+
diff --git a/drivers/mfd/pcf50606-core.c b/drivers/mfd/pcf50606-core.c
new file mode 100644 (file)
index 0000000..994d6f4
--- /dev/null
@@ -0,0 +1,686 @@
+/* Philips PCF50606 Power Management Unit (PMU) driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ *         Matt Hsu <matt@openmoko.org>
+ * All rights reserved.
+ *
+ * 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <linux/mfd/pcf50606/core.h>
+
+static int __pcf50606_read(struct pcf50606 *pcf, u8 reg, int num, u8 *data)
+{
+       int ret;
+
+       ret = i2c_smbus_read_i2c_block_data(pcf->i2c_client, reg,
+                               num, data);
+       if (ret < 0)
+               dev_err(pcf->dev, "Error reading %d regs at %d\n", num, reg);
+
+       return ret;
+}
+
+static int __pcf50606_write(struct pcf50606 *pcf, u8 reg, int num, u8 *data)
+{
+       int ret;
+
+       ret = i2c_smbus_write_i2c_block_data(pcf->i2c_client, reg,
+                               num, data);
+       if (ret < 0)
+               dev_err(pcf->dev, "Error writing %d regs at %d\n", num, reg);
+
+       return ret;
+
+}
+
+/* Read a block of upto 32 regs  */
+int pcf50606_read_block(struct pcf50606 *pcf, u8 reg,
+                                       int nr_regs, u8 *data)
+{
+       int ret;
+
+       mutex_lock(&pcf->lock);
+       ret = __pcf50606_read(pcf, reg, nr_regs, data);
+       mutex_unlock(&pcf->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50606_read_block);
+
+/* Write a block of upto 32 regs  */
+int pcf50606_write_block(struct pcf50606 *pcf , u8 reg,
+                                       int nr_regs, u8 *data)
+{
+       int ret;
+
+       mutex_lock(&pcf->lock);
+       ret = __pcf50606_write(pcf, reg, nr_regs, data);
+       mutex_unlock(&pcf->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50606_write_block);
+
+u8 pcf50606_reg_read(struct pcf50606 *pcf, u8 reg)
+{
+       u8 val;
+
+       mutex_lock(&pcf->lock);
+       __pcf50606_read(pcf, reg, 1, &val);
+       mutex_unlock(&pcf->lock);
+
+       return val;
+}
+EXPORT_SYMBOL_GPL(pcf50606_reg_read);
+
+int pcf50606_reg_write(struct pcf50606 *pcf, u8 reg, u8 val)
+{
+       int ret;
+
+       mutex_lock(&pcf->lock);
+       ret = __pcf50606_write(pcf, reg, 1, &val);
+       mutex_unlock(&pcf->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50606_reg_write);
+
+int pcf50606_reg_set_bit_mask(struct pcf50606 *pcf, u8 reg, u8 mask, u8 val)
+{
+       int ret;
+       u8 tmp;
+
+       val &= mask;
+
+       mutex_lock(&pcf->lock);
+       ret = __pcf50606_read(pcf, reg, 1, &tmp);
+       if (ret < 0)
+               goto out;
+
+       tmp &= ~mask;
+       tmp |= val;
+       ret = __pcf50606_write(pcf, reg, 1, &tmp);
+
+out:
+       mutex_unlock(&pcf->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50606_reg_set_bit_mask);
+
+int pcf50606_reg_clear_bits(struct pcf50606 *pcf, u8 reg, u8 val)
+{
+       int ret;
+       u8 tmp;
+
+       mutex_lock(&pcf->lock);
+       ret = __pcf50606_read(pcf, reg, 1, &tmp);
+       if (ret < 0)
+               goto out;
+
+       tmp &= ~val;
+       ret = __pcf50606_write(pcf, reg, 1, &tmp);
+
+out:
+       mutex_unlock(&pcf->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50606_reg_clear_bits);
+
+/* sysfs attributes */
+static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr,
+                           char *buf)
+{
+       struct pcf50606 *pcf = dev_get_drvdata(dev);
+       u8 dump[16];
+       int n, n1, idx = 0;
+       char *buf1 = buf;
+       static u8 address_no_read[] = { /* must be ascending */
+               PCF50606_REG_INT1,
+               PCF50606_REG_INT2,
+               PCF50606_REG_INT3,
+               0 /* terminator */
+       };
+
+       for (n = 0; n < 256; n += sizeof(dump)) {
+               for (n1 = 0; n1 < sizeof(dump); n1++)
+                       if (n == address_no_read[idx]) {
+                               idx++;
+                               dump[n1] = 0x00;
+                       } else
+                               dump[n1] = pcf50606_reg_read(pcf, n + n1);
+
+               hex_dump_to_buffer(dump, sizeof(dump), 16, 1, buf1, 128, 0);
+               buf1 += strlen(buf1);
+               *buf1++ = '\n';
+               *buf1 = '\0';
+       }
+
+       return buf1 - buf;
+}
+static DEVICE_ATTR(dump_regs, 0400, show_dump_regs, NULL);
+
+static ssize_t show_resume_reason(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       struct pcf50606 *pcf = dev_get_drvdata(dev);
+       int n;
+
+       n = sprintf(buf, "%02x%02x%02x\n",
+                               pcf->resume_reason[0],
+                               pcf->resume_reason[1],
+                               pcf->resume_reason[2]);
+
+       return n;
+}
+static DEVICE_ATTR(resume_reason, 0400, show_resume_reason, NULL);
+
+static struct attribute *pcf_sysfs_entries[] = {
+       &dev_attr_dump_regs.attr,
+       &dev_attr_resume_reason.attr,
+       NULL,
+};
+
+static struct attribute_group pcf_attr_group = {
+       .name   = NULL,                 /* put in device directory */
+       .attrs  = pcf_sysfs_entries,
+};
+
+int pcf50606_register_irq(struct pcf50606 *pcf, int irq,
+                       void (*handler) (int, void *), void *data)
+{
+       if (irq < 0 || irq > PCF50606_NUM_IRQ || !handler)
+               return -EINVAL;
+
+       if (WARN_ON(pcf->irq_handler[irq].handler))
+               return -EBUSY;
+
+       mutex_lock(&pcf->lock);
+       pcf->irq_handler[irq].handler = handler;
+       pcf->irq_handler[irq].data = data;
+       mutex_unlock(&pcf->lock);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50606_register_irq);
+
+int pcf50606_free_irq(struct pcf50606 *pcf, int irq)
+{
+       if (irq < 0 || irq > PCF50606_NUM_IRQ)
+               return -EINVAL;
+
+       mutex_lock(&pcf->lock);
+       pcf->irq_handler[irq].handler = NULL;
+       mutex_unlock(&pcf->lock);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50606_free_irq);
+
+static int __pcf50606_irq_mask_set(struct pcf50606 *pcf, int irq, u8 mask)
+{
+       u8 reg, bits, tmp;
+       int ret = 0, idx;
+
+       idx = irq >> 3;
+       reg =  PCF50606_REG_INT1M + idx;
+       bits = 1 << (irq & 0x07);
+
+       mutex_lock(&pcf->lock);
+
+       if (mask) {
+               ret = __pcf50606_read(pcf, reg, 1, &tmp);
+               if (ret < 0)
+                       goto out;
+
+               tmp |= bits;
+
+               ret = __pcf50606_write(pcf, reg, 1, &tmp);
+               if (ret < 0)
+                       goto out;
+
+               pcf->mask_regs[idx] &= ~bits;
+               pcf->mask_regs[idx] |= bits;
+       } else {
+               ret = __pcf50606_read(pcf, reg, 1, &tmp);
+               if (ret < 0)
+                       goto out;
+
+               tmp &= ~bits;
+
+               ret = __pcf50606_write(pcf, reg, 1, &tmp);
+               if (ret < 0)
+                       goto out;
+
+               pcf->mask_regs[idx] &= ~bits;
+       }
+out:
+       mutex_unlock(&pcf->lock);
+
+       return ret;
+}
+
+int pcf50606_irq_mask(struct pcf50606 *pcf, int irq)
+{
+       dev_info(pcf->dev, "Masking IRQ %d\n", irq);
+
+       return __pcf50606_irq_mask_set(pcf, irq, 1);
+}
+EXPORT_SYMBOL_GPL(pcf50606_irq_mask);
+
+int pcf50606_irq_unmask(struct pcf50606 *pcf, int irq)
+{
+       dev_info(pcf->dev, "Unmasking IRQ %d\n", irq);
+
+       return __pcf50606_irq_mask_set(pcf, irq, 0);
+}
+EXPORT_SYMBOL_GPL(pcf50606_irq_unmask);
+
+int pcf50606_irq_mask_get(struct pcf50606 *pcf, int irq)
+{
+       u8 reg, bits;
+
+       reg =  (irq / 8);
+       bits = (1 << (irq % 8));
+
+       return pcf->mask_regs[reg] & bits;
+}
+EXPORT_SYMBOL_GPL(pcf50606_irq_mask_get);
+
+static void pcf50606_irq_call_handler(struct pcf50606 *pcf,
+                                       int irq)
+{
+       if (pcf->irq_handler[irq].handler)
+               pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data);
+}
+
+#define PCF50606_ONKEY1S_TIMEOUT       8
+
+#define PCF50606_REG_MBCS1             0x2c
+
+static void pcf50606_irq_worker(struct work_struct *work)
+{
+       struct pcf50606 *pcf;
+       int ret, i, j;
+       u8 pcf_int[3], chgstat;
+
+       pcf = container_of(work, struct pcf50606, irq_work);
+
+       /* Read the 3 INT regs in one transaction */
+       ret = pcf50606_read_block(pcf, PCF50606_REG_INT1,
+                                               ARRAY_SIZE(pcf_int), pcf_int);
+       if (ret != ARRAY_SIZE(pcf_int)) {
+               dev_info(pcf->dev, "Error reading INT registers\n");
+               
+               /*
+                * If this doesn't ACK the interrupt to the chip, we'll be
+                * called once again as we're level triggered.
+                */
+               goto out;
+       }
+
+       /* We immediately read the charger status. We thus make sure
+        * only of CHGINS/CHGRM interrupt handlers are called */
+       if (pcf_int[1] & (PCF50606_INT2_CHGINS | PCF50606_INT2_CHGRM)) {
+               chgstat = pcf50606_reg_read(pcf, PCF50606_REG_MBCS1);
+               if (chgstat & (0x1 << 4))
+                       pcf_int[1] &= ~(1 << PCF50606_INT2_CHGRM);
+               else
+                       pcf_int[1] &= ~(1 << PCF50606_INT2_CHGINS);
+       }
+       
+       dev_info(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x\n",
+                               pcf_int[0], pcf_int[1], pcf_int[2]);
+
+       /* Some revisions of the chip don't have a 8s standby mode on
+        * ONKEY1S press. We try to manually do it in such cases. */
+       if (pcf_int[0] & PCF50606_INT1_SECOND && pcf->onkey1s_held) {
+               dev_info(pcf->dev, "ONKEY1S held for %d secs\n",
+                                                       pcf->onkey1s_held);
+               if (pcf->onkey1s_held++ == PCF50606_ONKEY1S_TIMEOUT)
+                       if (pcf->pdata->force_shutdown)
+                               pcf->pdata->force_shutdown(pcf);
+       }
+
+       if (pcf_int[0] & PCF50606_INT1_ONKEY1S) {
+               dev_info(pcf->dev, "ONKEY1S held\n");
+               pcf->onkey1s_held = 1 ;
+
+               /* Unmask IRQ_SECOND */
+               pcf50606_reg_clear_bits(pcf, PCF50606_REG_INT1M,
+                                               PCF50606_INT1_SECOND);
+
+               /* Unmask IRQ_ONKEYF */
+               pcf50606_reg_clear_bits(pcf, PCF50606_REG_INT1M,
+                                               PCF50606_INT1_ONKEYF);
+       }
+
+       if ((pcf_int[0] & PCF50606_INT1_ONKEYR) && pcf->onkey1s_held) {
+               pcf->onkey1s_held = 0;
+
+               /* Mask SECOND and ONKEYF interrupts */
+               if (pcf->mask_regs[0] & PCF50606_INT1_SECOND)
+                       pcf50606_reg_set_bit_mask(pcf,
+                                       PCF50606_REG_INT1M,
+                                       PCF50606_INT1_SECOND,
+                                       PCF50606_INT1_SECOND);
+
+               if (pcf->mask_regs[0] & PCF50606_INT1_ONKEYF)
+                       pcf50606_reg_set_bit_mask(pcf,
+                                       PCF50606_REG_INT1M,
+                                       PCF50606_INT1_ONKEYF,
+                                       PCF50606_INT1_ONKEYF);
+       }
+
+       /* Have we just resumed ? */
+       if (pcf->is_suspended) {
+
+               pcf->is_suspended = 0;
+
+               /* Set the resume reason filtering out non resumers */
+               for (i = 0; i < ARRAY_SIZE(pcf_int); i++)
+                       pcf->resume_reason[i] = pcf_int[i] &
+                                               pcf->pdata->resumers[i];
+
+               /* Make sure we don't pass on ONKEY events to
+                * userspace now */
+               pcf_int[1] &= ~(PCF50606_INT1_ONKEYR | PCF50606_INT1_ONKEYF);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(pcf_int); i++) {
+               /* Unset masked interrupts */
+               pcf_int[i] &= ~pcf->mask_regs[i];
+
+               for (j = 0; j < 8 ; j++)
+                       if (pcf_int[i] & (1 << j))
+                               pcf50606_irq_call_handler(pcf, (i * 8) + j);
+       }
+
+out:
+       put_device(pcf->dev);
+       enable_irq(pcf->irq);
+}
+
+static irqreturn_t pcf50606_irq(int irq, void *data)
+{
+       struct pcf50606 *pcf = data;
+
+       get_device(pcf->dev);
+       disable_irq(pcf->irq);
+       schedule_work(&pcf->irq_work);
+
+       return IRQ_HANDLED;
+}
+
+static void
+pcf50606_client_dev_register(struct pcf50606 *pcf, const char *name,
+                                               struct platform_device **pdev)
+{
+       struct pcf50606_subdev_pdata *subdev_pdata;
+       int ret;
+
+       *pdev = platform_device_alloc(name, -1);
+       if (!*pdev) {
+               dev_err(pcf->dev, "Falied to allocate %s\n", name);
+               return;
+       }
+
+       subdev_pdata = kmalloc(sizeof(*subdev_pdata), GFP_KERNEL);
+       if (!subdev_pdata) {
+               dev_err(pcf->dev, "Error allocating subdev pdata\n");
+               platform_device_put(*pdev);
+       }
+
+       subdev_pdata->pcf = pcf;
+       platform_device_add_data(*pdev, subdev_pdata, sizeof(*subdev_pdata));
+
+       (*pdev)->dev.parent = pcf->dev;
+
+       ret = platform_device_add(*pdev);
+       if (ret) {
+               dev_err(pcf->dev, "Failed to register %s: %d\n", name, ret);
+               platform_device_put(*pdev);
+               *pdev = NULL;
+       }
+}
+
+#ifdef CONFIG_PM
+static int pcf50606_suspend(struct device *dev, pm_message_t state)
+{
+       struct pcf50606 *pcf;
+       int ret, i;
+       u8 res[3];
+
+       pcf = dev_get_drvdata(dev);
+
+       /* Make sure our interrupt handlers are not called
+        * henceforth */
+       disable_irq(pcf->irq);
+
+       /* Make sure that any running IRQ worker has quit */
+       cancel_work_sync(&pcf->irq_work);
+
+       /* Save the masks */
+       ret = pcf50606_read_block(pcf, PCF50606_REG_INT1M,
+                               ARRAY_SIZE(pcf->suspend_irq_masks),
+                                               pcf->suspend_irq_masks);
+       if (ret < 0) {
+               dev_err(pcf->dev, "error saving irq masks\n");
+               goto out;
+       }
+       
+       /* Write wakeup irq masks */
+       for (i = 0; i < ARRAY_SIZE(res); i++)
+               res[i] = ~pcf->pdata->resumers[i];
+
+       ret = pcf50606_write_block(pcf, PCF50606_REG_INT1M,
+                                       ARRAY_SIZE(res), &res[0]);
+       if (ret < 0) {
+               dev_err(pcf->dev, "error writing wakeup irq masks\n");
+               goto out;
+       }
+
+       pcf->is_suspended = 1;
+
+out:
+       return ret;
+}
+
+static int pcf50606_resume(struct device *dev)
+{
+       struct pcf50606 *pcf;
+       int ret;
+
+       pcf = dev_get_drvdata(dev);
+
+       /* Write the saved mask registers */
+       ret = pcf50606_write_block(pcf, PCF50606_REG_INT1M,
+                               ARRAY_SIZE(pcf->suspend_irq_masks),
+                                       pcf->suspend_irq_masks);
+       if (ret < 0)
+               dev_err(pcf->dev, "Error restoring saved suspend masks\n");
+
+       get_device(pcf->dev);
+
+       /*
+        * Clear any pending interrupts and set resume reason if any.
+        * This will leave with enable_irq()
+        */
+       pcf50606_irq_worker(&pcf->irq_work);
+
+       return 0;
+}
+#else
+#define pcf50606_suspend NULL
+#define pcf50606_resume NULL
+#endif
+
+static int pcf50606_probe(struct i2c_client *client,
+                               const struct i2c_device_id *ids)
+{
+       struct pcf50606 *pcf;
+       struct pcf50606_platform_data *pdata = client->dev.platform_data;
+       int i, ret = 0;
+       int version, variant;
+
+       pcf = kzalloc(sizeof(*pcf), GFP_KERNEL);
+       if (!pcf)
+               return -ENOMEM;
+
+       pcf->pdata = pdata;
+
+       mutex_init(&pcf->lock);
+
+       i2c_set_clientdata(client, pcf);
+       pcf->dev = &client->dev;
+       pcf->i2c_client = client;
+       pcf->irq = client->irq;
+
+       INIT_WORK(&pcf->irq_work, pcf50606_irq_worker);
+
+       version = pcf50606_reg_read(pcf, 0);
+       variant = pcf50606_reg_read(pcf, 1);
+       if (version < 0 || variant < 0) {
+               dev_err(pcf->dev, "Unable to probe pcf50606\n");
+               ret = -ENODEV;
+               goto err;
+       }
+
+       dev_info(pcf->dev, "Probed device version %d variant %d\n",
+                                                       version, variant);
+       /* Enable all inteerupts except RTC SECOND */
+       pcf->mask_regs[0] = 0x40;
+       pcf50606_reg_write(pcf, PCF50606_REG_INT1M, pcf->mask_regs[0]);
+       pcf50606_reg_write(pcf, PCF50606_REG_INT2M, 0x00);
+       pcf50606_reg_write(pcf, PCF50606_REG_INT3M, 0x00);
+
+       pcf50606_client_dev_register(pcf, "pcf50606-input",
+                                               &pcf->input_pdev);
+       pcf50606_client_dev_register(pcf, "pcf50606-rtc",
+                                               &pcf->rtc_pdev);
+       pcf50606_client_dev_register(pcf, "pcf50606-mbc",
+                                               &pcf->mbc_pdev);
+       pcf50606_client_dev_register(pcf, "pcf50606-adc",
+                                               &pcf->adc_pdev);
+       pcf50606_client_dev_register(pcf, "pcf50606-wdt",
+                                               &pcf->wdt_pdev);
+       for (i = 0; i < PCF50606_NUM_REGULATORS; i++) {
+               struct platform_device *pdev;
+
+               pdev = platform_device_alloc("pcf50606-regltr", i);
+               if (!pdev) {
+                       dev_err(pcf->dev, "Cannot create regulator\n");
+                       continue;
+               }
+       
+               pdev->dev.parent = pcf->dev;
+               pdev->dev.platform_data = &pdata->reg_init_data[i];
+               pdev->dev.driver_data = pcf;
+               pcf->regulator_pdev[i] = pdev;
+
+               platform_device_add(pdev);
+       }
+
+       if (client->irq) {
+               set_irq_handler(client->irq, handle_level_irq);
+               ret = request_irq(client->irq, pcf50606_irq,
+                               IRQF_TRIGGER_LOW, "pcf50606", pcf);
+
+               if (ret) {
+                       dev_err(pcf->dev, "Failed to request IRQ %d\n", ret);
+                       goto err;
+               }
+       } else {
+               dev_err(pcf->dev, "No IRQ configured\n");
+               goto err;
+       }
+
+       if (enable_irq_wake(client->irq) < 0)
+               dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source"
+                       "in this hardware revision", client->irq);
+
+       ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group);
+       if (ret)
+               dev_err(pcf->dev, "error creating sysfs entries\n");
+
+       if (pdata->probe_done)
+               pdata->probe_done(pcf);
+
+       return 0;
+
+err:
+       kfree(pcf);
+       return ret;
+}
+
+static int pcf50606_remove(struct i2c_client *client)
+{
+       struct pcf50606 *pcf = i2c_get_clientdata(client);
+       int i;
+
+       free_irq(pcf->irq, pcf);
+
+       platform_device_unregister(pcf->input_pdev);
+       platform_device_unregister(pcf->rtc_pdev);
+       platform_device_unregister(pcf->mbc_pdev);
+       platform_device_unregister(pcf->adc_pdev);
+
+       for (i = 0; i < PCF50606_NUM_REGULATORS; i++)
+               platform_device_unregister(pcf->regulator_pdev[i]);
+
+       kfree(pcf);
+
+       return 0;
+}
+
+static struct i2c_device_id pcf50606_id_table[] = {
+       {"pcf50606", 0x08},
+};
+
+static struct i2c_driver pcf50606_driver = {
+       .driver = {
+               .name   = "pcf50606",
+               .suspend = pcf50606_suspend,
+               .resume = pcf50606_resume,
+       },
+       .id_table = pcf50606_id_table,
+       .probe = pcf50606_probe,
+       .remove = pcf50606_remove,
+};
+
+static int __init pcf50606_init(void)
+{
+       return i2c_add_driver(&pcf50606_driver);
+}
+
+static void pcf50606_exit(void)
+{
+       i2c_del_driver(&pcf50606_driver);
+}
+
+MODULE_DESCRIPTION("I2C chip driver for NXP PCF50606 PMU");
+MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>");
+MODULE_LICENSE("GPL");
+
+module_init(pcf50606_init);
+module_exit(pcf50606_exit);
diff --git a/drivers/mfd/pcf50606-gpo.c b/drivers/mfd/pcf50606-gpo.c
new file mode 100644 (file)
index 0000000..3510f8b
--- /dev/null
@@ -0,0 +1,119 @@
+/* Philips PCF50606 GPO Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50606 driver mainly by
+ * Harald Welte, Andy Green Werner Almesberger and Matt Hsu
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+
+#include <linux/mfd/pcf50606/core.h>
+#include <linux/mfd/pcf50606/gpo.h>
+
+void pcf50606_gpo_set_active(struct pcf50606 *pcf, int gpo, int val)
+{
+       u8 reg, value, mask;
+
+       reg = gpo;
+       value = val;
+       mask = 0x07;
+
+       if (gpo == PCF50606_GPO2) {
+               value = val << 4;
+               mask = 0x07 << 4;
+       }
+       pcf50606_reg_set_bit_mask(pcf, reg, mask, value);
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo_set_active);
+
+int pcf50606_gpo_get_active(struct pcf50606 *pcf, int gpo)
+{
+       u8 reg, value, shift = 0;
+
+       reg = gpo;
+       if (gpo == PCF50606_GPO2)
+               shift = 4;
+       
+       value = pcf50606_reg_read(pcf, reg);
+
+       return (value >> shift) & 0x07;
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo_get_active);
+
+void pcf50606_gpo_set_standby(struct pcf50606 *pcf, int gpo, int val)
+{
+       u8 reg;
+
+       if (gpo == PCF50606_GPO1 || gpo == PCF50606_GPO2) {
+               dev_err(pcf->dev, "Can't set standby settings for GPO[12]n");
+               return;
+       }
+
+       reg = gpo;
+
+       pcf50606_reg_set_bit_mask(pcf, gpo, 0x07 << 3, val);
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo_set_standby);
+
+int pcf50606_gpo_get_standby(struct pcf50606 *pcf, int gpo)
+{
+       u8 reg, value;
+
+       if (gpo == PCF50606_GPO1 || gpo == PCF50606_GPO2) {
+               dev_err(pcf->dev, "Can't get standby settings for GPO[12]n");
+               return -EINVAL;
+       }
+
+       reg = gpo;
+       value = pcf50606_reg_read(pcf, reg);
+
+       return (value >> 3) & 0x07;
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo_get_standby);
+
+void pcf50606_gpo_invert_set(struct pcf50606 *pcf, int gpo, int invert)
+{
+       u8 reg, value, mask;
+
+       reg = gpo;
+       value = !!invert << 6;
+       mask = 0x01 << 6;
+
+       if (gpo == PCF50606_GPO1) {
+               mask = 0x01 << 4;
+               value = !!invert << 4;
+       }
+       else if (gpo == PCF50606_GPO2) {
+               mask = 0x01 << 7;
+               value = !!invert << 7;
+       }
+
+       pcf50606_reg_set_bit_mask(pcf, reg, mask, value);
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo_invert_set);
+
+int pcf50606_gpo_invert_get(struct pcf50606 *pcf, int gpo)
+{
+       u8 reg, value, shift;
+
+       reg = gpo;
+       shift = 6;
+
+       if (gpo == PCF50606_GPO1)
+               shift = 4;
+       else if (gpo == PCF50606_GPO2)
+               shift = 7;
+
+       value =  pcf50606_reg_read(pcf, reg);
+
+       return (value >> shift) & 0x01;
+}
+EXPORT_SYMBOL_GPL(pcf50606_gpo_invert_get);
index 45c5192..a014b66 100644 (file)
@@ -1109,18 +1109,42 @@ static void s3cmci_set_clk(struct s3cmci_host *host, struct mmc_ios *ios)
                host->real_rate = 0;
 }
 
+static int s3cmci_get_mv(int vdd)
+{
+       int mv = 1700; /* 1.7V for MMC_VDD_165_195 */
+       int bit;
+
+       for (bit = 0; bit != 24; bit++)
+               if (vdd == (1 << bit))
+                       mv += 100 * (bit - 4);
+
+       return mv;
+}      
+
 static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
        struct s3cmci_host *host = mmc_priv(mmc);
        u32 mci_con;
+       struct regulator *regulator;
+       int mv;
 
        /* Set the power state */
 
        mci_con = readl(host->base + S3C2410_SDICON);
 
+       regulator = host->regulator;
+
        switch (ios->power_mode) {
-       case MMC_POWER_ON:
        case MMC_POWER_UP:
+               if (regulator) {
+                       mv = s3cmci_get_mv(ios->vdd);
+                       regulator_set_voltage(regulator, mv * 1000, mv * 1000);
+                       regulator_enable(regulator);
+               } else if (host->pdata->set_power) {
+                       host->pdata->set_power(ios->power_mode, ios->vdd);
+               }  else
+                       dev_err(&host->pdev->dev, "Can't MMC_POWERUP\n");
+       case MMC_POWER_ON:
                s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);
                s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);
                s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);
@@ -1128,26 +1152,37 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
                s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
 
-               if (host->pdata->set_power)
+               if (regulator) {
+                       mv = s3cmci_get_mv(ios->vdd);
+                       regulator_set_voltage(regulator, mv * 1000, mv * 1000);
+               } else if (host->pdata->set_power) {
                        host->pdata->set_power(ios->power_mode, ios->vdd);
-
+               }  else
+                       dev_err(&host->pdev->dev, "Can't MMC_POWERON\n");
+       
                if (!host->is2440)
                        mci_con |= S3C2410_SDICON_FIFORESET;
 
                break;
 
        case MMC_POWER_OFF:
-       default:
                s3c2410_gpio_setpin(S3C2410_GPE5, 0);
                s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP);
 
                if (host->is2440)
                        mci_con |= S3C2440_SDICON_SDRESET;
 
-               if (host->pdata->set_power)
+               if (regulator) {
+                       if (regulator_is_enabled(regulator))
+                               regulator_disable(regulator);
+               } else if (host->pdata->set_power) {
                        host->pdata->set_power(ios->power_mode, ios->vdd);
-
+               }  else
+                       dev_err(&host->pdev->dev, "Can't MMC_POWEROFF\n");
+       
                break;
+       default:
+               printk(KERN_ERR "No such power mode %d\n", ios->power_mode);
        }
 
        s3cmci_set_clk(host, ios);
@@ -1429,6 +1464,12 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
                goto free_dmabuf;
        }
 
+       host->regulator = regulator_get(&pdev->dev, "SD_3V3");
+       if (IS_ERR(host->regulator)) {
+               dev_err(&pdev->dev, "Regulator for SD_3V3 unavilable \n");
+               host->regulator = NULL;
+       }
+
        ret = mmc_add_host(mmc);
        if (ret) {
                dev_err(&pdev->dev, "failed to add mmc host.\n");
index c2b65b3..7f80047 100644 (file)
@@ -10,6 +10,7 @@
 
 
 #include <mach/regs-sdi.h>
+#include <linux/regulator/consumer.h>
 
 /* FIXME: DMA Resource management ?! */
 #define S3CMCI_DMA 0
@@ -81,4 +82,6 @@ struct s3cmci_host {
 #ifdef CONFIG_CPU_FREQ
        struct notifier_block   freq_transition;
 #endif
+
+       struct regulator *regulator;
 };
index 64fbc6c..ce5364a 100644 (file)
@@ -101,5 +101,16 @@ config GTA02_HDQ
          on the Neo Freerunner.  You probably want to select
          at least BATTERY_BQ27000_HDQ as well
 
+config CHARGER_PCF50606
+       tristate "Support for NXP PCF50606 MBC"
+       depends on MFD_PCF50606
+       help
+        Say Y to include support for NXP PCF50606 Battery Charger.
+
+config BATTERY_GTA01
+       tristate "GTA01 battery driver"
+       help
+         Say Y here to enable this dumb driver for dumb gta01 batteries
+
 endif # POWER_SUPPLY
 
index 5c58fea..408b9fc 100644 (file)
@@ -26,8 +26,9 @@ obj-$(CONFIG_BATTERY_WM97XX)  += wm97xx_battery.o
 obj-$(CONFIG_BATTERY_BQ27x00)  += bq27x00_battery.o
 obj-$(CONFIG_BATTERY_DA9030)   += da9030_battery.o
 obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
+obj-$(CONFIG_CHARGER_PCF50606) += pcf50606-charger.o
 obj-$(CONFIG_BATTERY_PALMTX)   += palmtx_battery.o
 obj-$(CONFIG_BATTERY_BQ27000_HDQ)      += bq27000_battery.o
+obj-$(CONFIG_BATTERY_GTA01)    += gta01_battery.o
 
 obj-$(CONFIG_GTA02_HDQ)                += gta02_hdq.o
-
diff --git a/drivers/power/gta01_battery.c b/drivers/power/gta01_battery.c
new file mode 100644 (file)
index 0000000..126b172
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Dumb driver for gta01 battery
+ *
+ * Copyright 2009 Openmoko, Inc
+ * Balaji Rao <balajirrao@openmoko.org>
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/gta01_battery.h>
+
+struct gta01_battery {
+       struct power_supply psy;
+       struct gta01_bat_platform_data *pdata;
+};
+
+static enum power_supply_property gta01_bat_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static int gta01_bat_get_property(struct power_supply *psy,
+                                      enum power_supply_property psp,
+                                      union power_supply_propval *val)
+{
+       struct gta01_battery *bat = container_of(psy, struct gta01_battery, psy);
+       
+       switch(psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (bat->pdata->get_charging_status())
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = bat->pdata->get_voltage();
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = bat->pdata->get_current();
+               break;
+       default:
+               printk(KERN_ERR "Unknown property benig asked for\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void gta01_bat_ext_changed(struct power_supply *psy)
+{
+       struct gta01_battery *bat = container_of(psy, struct gta01_battery, psy);
+       power_supply_changed(&bat->psy);
+}
+
+static int gta01_battery_probe(struct platform_device *pdev)
+{
+       struct gta01_battery *gta01_bat;
+
+       gta01_bat = kzalloc(sizeof(*gta01_bat), GFP_KERNEL);
+       if (!gta01_bat)
+               return -ENOMEM;
+
+       gta01_bat->psy.name = "battery";
+       gta01_bat->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+       gta01_bat->psy.properties = gta01_bat_props;
+       gta01_bat->psy.num_properties = ARRAY_SIZE(gta01_bat_props);
+       gta01_bat->psy.get_property = gta01_bat_get_property;
+       gta01_bat->psy.external_power_changed = gta01_bat_ext_changed;
+
+       gta01_bat->pdata = pdev->dev.platform_data;
+       power_supply_register(&pdev->dev, &gta01_bat->psy);
+
+       return 0;
+}
+
+static int gta01_battery_remove(struct platform_device *pdev)
+{
+       struct gta01_battery *bat = platform_get_drvdata(pdev);
+
+       power_supply_unregister(&bat->psy);
+       kfree(bat);
+
+       return 0;
+}
+
+static struct platform_driver gta01_battery_driver = {
+       .driver = {
+               .name = "gta01_battery",
+       },
+       .probe    = gta01_battery_probe,
+       .remove   = gta01_battery_remove,
+};
+
+static int __init gta01_battery_init(void)
+{
+       return platform_driver_register(&gta01_battery_driver);
+}
+
+static void __exit gta01_battery_exit(void)
+{
+       platform_driver_unregister(&gta01_battery_driver);
+}
+
+module_init(gta01_battery_init);
+module_exit(gta01_battery_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("gta01 battery driver");
diff --git a/drivers/power/pcf50606-charger.c b/drivers/power/pcf50606-charger.c
new file mode 100644 (file)
index 0000000..0f2aeda
--- /dev/null
@@ -0,0 +1,236 @@
+/* NXP PCF50606 Main Battery Charger Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50606 driver mainly by
+ * Harald Welte, Andy Green and Werner Almesberger
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/pcf50606/core.h>
+#include <linux/mfd/pcf50606/mbc.h>
+
+struct pcf50606_mbc {
+       struct pcf50606 *pcf;
+
+       int charger_online;
+       struct power_supply charger;
+};
+
+void pcf50606_charge_fast(struct pcf50606 *pcf, int on)
+{
+       struct pcf50606_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
+
+       if (on) {
+               pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_MBCC1,
+                                PCF50606_MBCC1_AUTOFST,
+                                PCF50606_MBCC1_AUTOFST);\
+                       mbc->charger_online = 1;
+       } else {
+               /* disable automatic fast-charge */
+               pcf50606_reg_clear_bits(pcf, PCF50606_REG_MBCC1,
+                                       PCF50606_MBCC1_AUTOFST);
+               /* switch to idle mode to abort existing charge process */
+               pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_MBCC1,
+                               PCF50606_MBCC1_CHGMOD_MASK,
+                               PCF50606_MBCC1_CHGMOD_IDLE);
+                       mbc->charger_online = 0;
+       }
+}
+EXPORT_SYMBOL_GPL(pcf50606_charge_fast);
+
+static ssize_t
+show_chgmode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct pcf50606_mbc *mbc = dev_get_drvdata(dev);
+
+       u8 mbcc1 = pcf50606_reg_read(mbc->pcf, PCF50606_REG_MBCC1);
+       u8 chgmod = (mbcc1 & PCF50606_MBCC1_CHGMOD_MASK);
+
+       return sprintf(buf, "%d\n", chgmod);
+}
+
+static ssize_t set_chgmode(struct device *dev, struct device_attribute *attr,
+                          const char *buf, size_t count)
+{
+       struct pcf50606_mbc *mbc = dev_get_drvdata(dev);
+       u_int8_t mbcc1 = pcf50606_reg_read(mbc->pcf, PCF50606_REG_MBCC1);
+
+       mbcc1 &= ~PCF50606_MBCC1_CHGMOD_MASK;
+
+       if (!strcmp(buf, "qualification"))
+               mbcc1 |= PCF50606_MBCC1_CHGMOD_QUAL;
+       else if (!strcmp(buf, "pre"))
+               mbcc1 |= PCF50606_MBCC1_CHGMOD_PRE;
+       else if (!strcmp(buf, "trickle"))
+               mbcc1 |= PCF50606_MBCC1_CHGMOD_TRICKLE;
+       else if (!strcmp(buf, "fast_cccv"))
+               mbcc1 |= PCF50606_MBCC1_CHGMOD_FAST_CCCV;
+       /* We don't allow the other fast modes for security reasons */
+       else if (!strcmp(buf, "idle"))
+               mbcc1 |= PCF50606_MBCC1_CHGMOD_IDLE;
+       else
+               return -EINVAL;
+
+       pcf50606_reg_write(mbc->pcf, PCF50606_REG_MBCC1, mbcc1);
+
+       return count;
+}
+
+static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, set_chgmode);
+
+
+static struct attribute *pcf50606_mbc_sysfs_entries[] = {
+       &dev_attr_chgmode.attr,
+       NULL,
+};
+
+static struct attribute_group mbc_attr_group = {
+       .name   = NULL,                 /* put in device directory */
+       .attrs  = pcf50606_mbc_sysfs_entries,
+};
+
+static void
+pcf50606_mbc_irq_handler(int irq, void *data)
+{
+       struct pcf50606_mbc *mbc = data;
+
+       power_supply_changed(&mbc->charger);
+
+       if (mbc->pcf->pdata->mbc_event_callback)
+               mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq);
+}
+
+static int charger_get_property(struct power_supply *psy,
+                       enum power_supply_property psp,
+                       union power_supply_propval *val)
+{
+       struct pcf50606_mbc *mbc = container_of(psy, struct pcf50606_mbc, charger);
+       int ret = 0;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval =  mbc->charger_online;
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static enum power_supply_property power_props[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const u8 mbc_irq_handlers[] = {
+       PCF50606_IRQ_CHGINS,
+       PCF50606_IRQ_CHGRM,
+       PCF50606_IRQ_CHGFOK,
+       PCF50606_IRQ_CHGERR,
+       PCF50606_IRQ_CHGFRDY,
+       PCF50606_IRQ_CHGPROT,
+};
+
+static int __devinit pcf50606_mbc_probe(struct platform_device *pdev)
+{
+       struct pcf50606_mbc *mbc;
+       struct pcf50606_subdev_pdata *pdata = pdev->dev.platform_data;
+       int ret;
+       int i;
+       u8 oocs;
+
+       mbc = kzalloc(sizeof(*mbc), GFP_KERNEL);
+       if (!mbc)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, mbc);
+       mbc->pcf = pdata->pcf;
+
+       /* Set up IRQ handlers */
+       for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
+               pcf50606_register_irq(mbc->pcf, mbc_irq_handlers[i],
+                                       pcf50606_mbc_irq_handler, mbc);
+
+       mbc->charger.name               = "charger";
+       mbc->charger.type               = POWER_SUPPLY_TYPE_MAINS;
+       mbc->charger.properties         = power_props;
+       mbc->charger.num_properties     = ARRAY_SIZE(power_props);
+       mbc->charger.get_property       = &charger_get_property;
+       mbc->charger.supplied_to        = mbc->pcf->pdata->batteries;
+       mbc->charger.num_supplicants    = mbc->pcf->pdata->num_batteries;
+
+       ret = power_supply_register(&pdev->dev, &mbc->charger);
+       if (ret) {
+               dev_err(mbc->pcf->dev, "failed to register charger\n");
+               kfree(mbc);
+               return ret;
+       }
+
+       ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group);
+       if (ret)
+               dev_err(mbc->pcf->dev, "failed to create sysfs entries\n");
+
+       oocs = pcf50606_reg_read(mbc->pcf, PCF50606_REG_OOCS);
+       if (oocs & PCF50606_OOCS_CHGOK)
+               pcf50606_mbc_irq_handler(PCF50606_IRQ_CHGINS, mbc);
+
+       return 0;
+}
+
+static int __devexit pcf50606_mbc_remove(struct platform_device *pdev)
+{
+       struct pcf50606_mbc *mbc = platform_get_drvdata(pdev);
+       int i;
+
+       /* Remove IRQ handlers */
+       for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
+               pcf50606_free_irq(mbc->pcf, mbc_irq_handlers[i]);
+
+       power_supply_unregister(&mbc->charger);
+
+       kfree(mbc);
+
+       return 0;
+}
+
+static struct platform_driver pcf50606_mbc_driver = {
+       .driver = {
+               .name = "pcf50606-mbc",
+       },
+       .probe = pcf50606_mbc_probe,
+       .remove = __devexit_p(pcf50606_mbc_remove),
+};
+
+static int __init pcf50606_mbc_init(void)
+{
+       return platform_driver_register(&pcf50606_mbc_driver);
+}
+module_init(pcf50606_mbc_init);
+
+static void __exit pcf50606_mbc_exit(void)
+{
+       platform_driver_unregister(&pcf50606_mbc_driver);
+}
+module_exit(pcf50606_mbc_exit);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50606 mbc driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50606-mbc");
index e7e0cf1..b97da4a 100644 (file)
@@ -80,4 +80,10 @@ config REGULATOR_PCF50633
         Say Y here to support the voltage regulators and convertors
         on PCF50633
 
+config REGULATOR_PCF50606
+       bool "PCF50606 regulator driver"
+        depends on MFD_PCF50606
+       help
+        Say Y here to support the voltage regulators and convertors
+        on PCF50606
 endif
index 61b30c6..ec18622 100644 (file)
@@ -12,5 +12,6 @@ obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
 obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
 obj-$(CONFIG_REGULATOR_DA903X) += da903x.o
 obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
+obj-$(CONFIG_REGULATOR_PCF50606) += pcf50606-regulator.o
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/pcf50606-regulator.c b/drivers/regulator/pcf50606-regulator.c
new file mode 100644 (file)
index 0000000..5d0704c
--- /dev/null
@@ -0,0 +1,380 @@
+/* NXP PCF50606 PMIC Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50606 driver mainly by
+ * Harald Welte and Andy Green and Werner Almesberger
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/pcf50606/core.h>
+#include <linux/mfd/pcf50606/pmic.h>
+
+#define PCF50606_REGULATOR(_name, _id)                 \
+       {                                       \
+               .name = _name,                  \
+               .id = _id,                      \
+               .ops = &pcf50606_regulator_ops, \
+               .type = REGULATOR_VOLTAGE,      \
+               .owner = THIS_MODULE,           \
+       }
+
+static const u8 pcf50606_regulator_registers[PCF50606_NUM_REGULATORS] = {
+       [PCF50606_REGULATOR_DCD]        = PCF50606_REG_DCDC1,
+       [PCF50606_REGULATOR_DCDE]       = PCF50606_REG_DCDEC1,
+       [PCF50606_REGULATOR_DCUD]       = PCF50606_REG_DCUDC1,
+       [PCF50606_REGULATOR_D1REG]      = PCF50606_REG_D1REGC1,
+       [PCF50606_REGULATOR_D2REG]      = PCF50606_REG_D2REGC1,
+       [PCF50606_REGULATOR_D3REG]      = PCF50606_REG_D3REGC1,
+       [PCF50606_REGULATOR_LPREG]      = PCF50606_REG_LPREGC1,
+       [PCF50606_REGULATOR_IOREG]      = PCF50606_REG_IOREGC,
+};
+
+static u8 dcudc_voltage(unsigned int millivolts)
+{
+       if (millivolts < 900)
+               return 0;
+       if (millivolts > 5500)
+               return 0x1f;
+       if (millivolts <= 3300) {
+               millivolts -= 900;
+               return millivolts/300;
+       }
+       if (millivolts < 4000)
+               return 0x0f;
+       else {
+               millivolts -= 4000;
+               return millivolts/100;
+       }
+}
+
+static unsigned int dcudc_2voltage(u8 bits)
+{
+       bits &= 0x1f;
+       if (bits < 0x08)
+               return 900 + bits * 300;
+       else if (bits < 0x10)
+               return 3300;
+       else
+               return 4000 + bits * 100;
+}
+
+static u8 dcdec_voltage(unsigned int millivolts)
+{
+       if (millivolts < 900)
+               return 0;
+       else if (millivolts > 3300)
+               return 0x0f;
+
+       millivolts -= 900;
+       return millivolts/300;
+}
+
+static unsigned int dcdec_2voltage(u8 bits)
+{
+       bits &= 0x0f;
+       return 900 + bits*300;
+}
+
+static u8 dcdc_voltage(unsigned int millivolts)
+{
+       if (millivolts < 900)
+               return 0;
+       else if (millivolts > 3600)
+               return 0x1f;
+
+       if (millivolts < 1500) {
+               millivolts -= 900;
+               return millivolts/25;
+       } else {
+               millivolts -= 1500;
+               return 0x18 + millivolts/300;
+       }
+}
+
+static unsigned int dcdc_2voltage(u8 bits)
+{
+       bits &= 0x1f;
+       if ((bits & 0x18) == 0x18)
+               return 1500 + ((bits & 0x7) * 300);
+       else
+               return 900 + (bits * 25);
+}
+
+static u8 dx_voltage(unsigned int millivolts)
+{
+       if (millivolts < 900)
+               return 0;
+       else if (millivolts > 3300)
+               return 0x18;
+
+       millivolts -= 900;
+       return millivolts/100;
+}
+
+static unsigned int dx_2voltage(u8 bits)
+{
+       bits &= 0x1f;
+       return 900 + (bits * 100);
+}
+
+static int pcf50606_regulator_set_voltage(struct regulator_dev *rdev,
+                                               int min_uV, int max_uV)
+{
+       struct pcf50606 *pcf;
+       int regulator_id, millivolts, rc;
+       u8 volt_bits, regnr;
+
+       pcf = rdev_get_drvdata(rdev);
+
+       regulator_id = rdev_get_id(rdev);
+       if (regulator_id >= PCF50606_NUM_REGULATORS)
+               return -EINVAL;
+
+       millivolts = min_uV / 1000;
+
+       switch (regulator_id) {
+       case PCF50606_REGULATOR_DCD:
+               volt_bits = dcdc_voltage(millivolts);
+               rc = pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_DCDC1, 0x1f,
+                                     volt_bits);
+               break;
+       case PCF50606_REGULATOR_DCDE:
+               volt_bits = dcdec_voltage(millivolts);
+               rc = pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_DCDEC1, 0x0f,
+                                     volt_bits);
+               break;
+       case PCF50606_REGULATOR_DCUD:
+               volt_bits = dcudc_voltage(millivolts);
+               rc = pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_DCUDC1, 0x1f,
+                                     volt_bits);
+               break;
+       case PCF50606_REGULATOR_D1REG:
+       case PCF50606_REGULATOR_D2REG:
+       case PCF50606_REGULATOR_D3REG:
+               regnr = PCF50606_REG_D1REGC1 +
+                               (regulator_id - PCF50606_REGULATOR_D1REG);
+               volt_bits = dx_voltage(millivolts);
+               rc = pcf50606_reg_set_bit_mask(pcf, regnr, 0x1f, volt_bits);
+               break;
+       case PCF50606_REGULATOR_LPREG:
+               volt_bits = dx_voltage(millivolts);
+               rc = pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_LPREGC1, 0x1f,
+                                             volt_bits);
+               break;
+       case PCF50606_REGULATOR_IOREG:
+               if (millivolts < 1800)
+                       return -EINVAL;
+               volt_bits = dx_voltage(millivolts);
+               rc = pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_IOREGC, 0x1f,
+                                             volt_bits);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return rc;
+}
+
+static int pcf50606_regulator_get_voltage(struct regulator_dev *rdev)
+{
+       struct pcf50606 *pcf;
+       u8 volt_bits, regnr;
+       int rc = 0, regulator_id;
+
+
+       pcf = rdev_get_drvdata(rdev);
+
+       regulator_id = rdev_get_id(rdev);
+       if (regulator_id >= PCF50606_NUM_REGULATORS)
+               return -EINVAL;
+
+       switch (regulator_id) {
+       case PCF50606_REGULATOR_DCD:
+               volt_bits = pcf50606_reg_read(pcf, PCF50606_REG_DCDC1) & 0x1f;
+               rc = dcdc_2voltage(volt_bits);
+               break;
+       case PCF50606_REGULATOR_DCDE:
+               volt_bits = pcf50606_reg_read(pcf, PCF50606_REG_DCDEC1) & 0x0f;
+               rc = dcdec_2voltage(volt_bits);
+               break;
+       case PCF50606_REGULATOR_DCUD:
+               volt_bits = pcf50606_reg_read(pcf, PCF50606_REG_DCUDC1) & 0x1f;
+               rc = dcudc_2voltage(volt_bits);
+               break;
+       case PCF50606_REGULATOR_D1REG:
+       case PCF50606_REGULATOR_D2REG:
+       case PCF50606_REGULATOR_D3REG:
+               regnr = PCF50606_REG_D1REGC1 + (regulator_id - PCF50606_REGULATOR_D1REG);
+               volt_bits = pcf50606_reg_read(pcf, regnr) & 0x1f;
+               if (volt_bits > 0x18)
+                       volt_bits = 0x18;
+               rc = dx_2voltage(volt_bits);
+               break;
+       case PCF50606_REGULATOR_LPREG:
+               volt_bits = pcf50606_reg_read(pcf, PCF50606_REG_LPREGC1) & 0x1f;
+               if (volt_bits > 0x18)
+                       volt_bits = 0x18;
+               rc = dx_2voltage(volt_bits);
+               break;
+       case PCF50606_REGULATOR_IOREG:
+               volt_bits = pcf50606_reg_read(pcf, PCF50606_REG_IOREGC) & 0x1f;
+               if (volt_bits > 0x18)
+                       volt_bits = 0x18;
+               rc = dx_2voltage(volt_bits);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return rc * 1000;
+
+}
+
+static int pcf50606_regulator_enable(struct regulator_dev *rdev)
+{
+       struct pcf50606 *pcf = rdev_get_drvdata(rdev);
+       int regulator_id;
+       u8 regnr;
+
+       regulator_id = rdev_get_id(rdev);
+       if (regulator_id >= PCF50606_NUM_REGULATORS)
+               return -EINVAL;
+       
+       regnr = pcf50606_regulator_registers[regulator_id];
+
+       return pcf50606_reg_set_bit_mask(pcf, regnr, 0xe0, 0xe0);
+}
+
+static int pcf50606_regulator_disable(struct regulator_dev *rdev)
+{
+       struct pcf50606 *pcf = rdev_get_drvdata(rdev);
+       int regulator_id;
+       u8 regnr;
+
+       regulator_id = rdev_get_id(rdev);
+       if (regulator_id >= PCF50606_NUM_REGULATORS)
+               return -EINVAL;
+
+       /* IOREG cannot be powered off since it powers the PMU I2C */
+       if (regulator_id == PCF50606_REGULATOR_IOREG)
+               return -EINVAL;
+       
+       regnr = pcf50606_regulator_registers[regulator_id];
+
+       return pcf50606_reg_set_bit_mask(pcf, regnr, 0xe0, 0);
+}
+
+static int pcf50606_regulator_is_enabled(struct regulator_dev *rdev)
+{
+       struct pcf50606 *pcf = rdev_get_drvdata(rdev);
+       int regulator_id = rdev_get_id(rdev);
+       u8 regnr, val;
+
+       regulator_id = rdev_get_id(rdev);
+       if (regulator_id >= PCF50606_NUM_REGULATORS)
+               return -EINVAL;
+
+       /* the *ENA register is always one after the *OUT register */
+       regnr = pcf50606_regulator_registers[regulator_id];
+       val = (pcf50606_reg_read(pcf, regnr) & 0xe0) >> 5;
+
+       /* PWREN1 = 1, PWREN2 = 1, see table 16 of datasheet */
+       if (val == 0 || val == 5)
+               return 0;
+
+       return 1;
+}
+
+static struct regulator_ops pcf50606_regulator_ops = {
+       .set_voltage = pcf50606_regulator_set_voltage,
+       .get_voltage = pcf50606_regulator_get_voltage,
+       .enable = pcf50606_regulator_enable,
+       .disable = pcf50606_regulator_disable,
+       .is_enabled = pcf50606_regulator_is_enabled,
+};
+
+static struct regulator_desc regulators[] = {
+       [PCF50606_REGULATOR_DCD] =
+               PCF50606_REGULATOR("dcd", PCF50606_REGULATOR_DCD),
+       [PCF50606_REGULATOR_DCDE] =
+               PCF50606_REGULATOR("dcde", PCF50606_REGULATOR_DCDE),
+       [PCF50606_REGULATOR_DCUD] =
+               PCF50606_REGULATOR("dcud", PCF50606_REGULATOR_DCUD),
+       [PCF50606_REGULATOR_D1REG] =
+               PCF50606_REGULATOR("d1reg", PCF50606_REGULATOR_D1REG),
+       [PCF50606_REGULATOR_D2REG] =
+               PCF50606_REGULATOR("d2reg", PCF50606_REGULATOR_D2REG),
+       [PCF50606_REGULATOR_D3REG] =
+               PCF50606_REGULATOR("d3reg", PCF50606_REGULATOR_D3REG),
+       [PCF50606_REGULATOR_LPREG] =
+               PCF50606_REGULATOR("lpreg", PCF50606_REGULATOR_LPREG),
+       [PCF50606_REGULATOR_IOREG] =
+               PCF50606_REGULATOR("ioreg", PCF50606_REGULATOR_IOREG),
+};
+
+static int __devinit pcf50606_regulator_probe(struct platform_device *pdev)
+{
+       struct regulator_dev *rdev;
+       struct pcf50606 *pcf;
+
+       /* Already set by core driver */
+       pcf = platform_get_drvdata(pdev);
+
+       rdev = regulator_register(&regulators[pdev->id], &pdev->dev, pcf);
+       if (IS_ERR(rdev))
+               return PTR_ERR(rdev);
+
+       if (pcf->pdata->regulator_registered)
+               pcf->pdata->regulator_registered(pcf, pdev->id);
+
+       return 0;
+}
+
+static int __devexit pcf50606_regulator_remove(struct platform_device *pdev)
+{
+       struct regulator_dev *rdev = platform_get_drvdata(pdev);
+
+       regulator_unregister(rdev);
+
+       return 0;
+}
+
+static struct platform_driver pcf50606_regulator_driver = {
+       .driver = {
+               .name = "pcf50606-regltr",
+       },
+       .probe = pcf50606_regulator_probe,
+       .remove = __devexit_p(pcf50606_regulator_remove),
+};
+
+static int __init pcf50606_regulator_init(void)
+{
+       return platform_driver_register(&pcf50606_regulator_driver);
+}
+module_init(pcf50606_regulator_init);
+
+static void __exit pcf50606_regulator_exit(void)
+{
+       platform_driver_unregister(&pcf50606_regulator_driver);
+}
+module_exit(pcf50606_regulator_exit);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50606 regulator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50606-regulator");
index cced4d1..c13b40d 100644 (file)
@@ -223,6 +223,13 @@ config RTC_DRV_PCF8583
          This driver can also be built as a module. If so, the module
          will be called rtc-pcf8583.
 
+config RTC_DRV_PCF50606
+       depends on MFD_PCF50606
+       tristate "Philips PCF50606"
+       help
+         If you say yes here you get support for the Philips PCF50606
+         PMU's RTC.
+
 config RTC_DRV_M41T80
        tristate "ST M41T65/M41T80/81/82/83/84/85/87"
        help
index 841ea28..e059093 100644 (file)
@@ -1,4 +1,4 @@
-/* Philips PCF50606 RTC Driver
+/* NXP PCF50606 RTC Driver
  *
  * (C) 2006-2008 by Openmoko, Inc.
  * Author: Balaji Rao <balajirrao@openmoko.org>
@@ -7,31 +7,41 @@
  * Broken down from monstrous PCF50606 driver mainly by
  * Harald Welte, Andy Green and Werner Almesberger
  *
- * 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 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 Place, Suite 060, Boston,
- * MA 02111-1307 USA
  */
 
-#include <linux/rtc.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
 #include <linux/platform_device.h>
+#include <linux/rtc.h>
 #include <linux/bcd.h>
+#include <linux/err.h>
 
 #include <linux/mfd/pcf50606/core.h>
-#include <linux/mfd/pcf50606/rtc.h>
+
+#define PCF50606_REG_RTCSC     0x0a /* Second */
+#define PCF50606_REG_RTCMN     0x0b /* Minute */
+#define PCF50606_REG_RTCHR     0x0c /* Hour */
+#define PCF50606_REG_RTCWD     0x0d /* Weekday */
+#define PCF50606_REG_RTCDT     0x0e /* Day */
+#define PCF50606_REG_RTCMT     0x0f /* Month */
+#define PCF50606_REG_RTCYR     0x10 /* Year */
+#define PCF50606_REG_RTCSCA    0x11 /* Alarm Second */
+#define PCF50606_REG_RTCMNA    0x12 /* Alarm Minute */
+#define PCF50606_REG_RTCHRA    0x13 /* Alarm Hour */
+#define PCF50606_REG_RTCWDA    0x14 /* Alarm Weekday */
+#define PCF50606_REG_RTCDTA    0x15 /* Alarm Day */
+#define PCF50606_REG_RTCMTA    0x16 /* Alarm Month */
+#define PCF50606_REG_RTCYRA    0x17 /* Alarm Year */
 
 enum pcf50606_time_indexes {
-       PCF50606_TI_SEC = 0,
+       PCF50606_TI_SEC,
        PCF50606_TI_MIN,
        PCF50606_TI_HOUR,
        PCF50606_TI_WKDAY,
@@ -41,11 +51,18 @@ enum pcf50606_time_indexes {
        PCF50606_TI_EXTENT /* always last */
 };
 
-
 struct pcf50606_time {
        u_int8_t time[PCF50606_TI_EXTENT];
 };
 
+struct pcf50606_rtc {
+       int alarm_enabled;
+       int second_enabled;
+
+       struct pcf50606 *pcf;
+       struct rtc_device *rtc_dev;
+};
+
 static void pcf2rtc_time(struct rtc_time *rtc, struct pcf50606_time *pcf)
 {
        rtc->tm_sec = bcd2bin(pcf->time[PCF50606_TI_SEC]);
@@ -65,54 +82,51 @@ static void rtc2pcf_time(struct pcf50606_time *pcf, struct rtc_time *rtc)
        pcf->time[PCF50606_TI_WKDAY] = bin2bcd(rtc->tm_wday);
        pcf->time[PCF50606_TI_DAY] = bin2bcd(rtc->tm_mday);
        pcf->time[PCF50606_TI_MONTH] = bin2bcd(rtc->tm_mon);
-       pcf->time[PCF50606_TI_YEAR] = bin2bcd(rtc->tm_year - 100);
+       pcf->time[PCF50606_TI_YEAR] = bin2bcd(rtc->tm_year % 100);
 }
 
-static int pcf50606_rtc_ioctl(struct device *dev, unsigned int cmd,
-                             unsigned long arg)
+static int
+pcf50606_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
 {
-       struct pcf50606 *pcf;
-
-       pcf = dev_get_drvdata(dev);
+       struct pcf50606_rtc *rtc = dev_get_drvdata(dev);
 
        switch (cmd) {
        case RTC_AIE_OFF:
-               /* disable the alarm interrupt */
-               pcf->rtc.alarm_enabled = 0;
-               pcf50606_irq_mask(pcf, PCF50606_IRQ_ALARM);
+               rtc->alarm_enabled = 0;
+               pcf50606_irq_mask(rtc->pcf, PCF50606_IRQ_ALARM);
                return 0;
        case RTC_AIE_ON:
-               /* enable the alarm interrupt */
-               pcf->rtc.alarm_enabled = 1;
-               pcf50606_irq_unmask(pcf, PCF50606_IRQ_ALARM);
+               rtc->alarm_enabled = 1;
+               pcf50606_irq_unmask(rtc->pcf, PCF50606_IRQ_ALARM);
                return 0;
-       case RTC_PIE_OFF:
-               /* disable periodic interrupt (hz tick) */
-               pcf->rtc.second_enabled = 0;
-               pcf50606_irq_mask(pcf, PCF50606_IRQ_SECOND);
+       case RTC_UIE_OFF:
+               rtc->second_enabled = 0;
+               pcf50606_irq_mask(rtc->pcf, PCF50606_IRQ_SECOND);
                return 0;
-       case RTC_PIE_ON:
-               /* ensable periodic interrupt (hz tick) */
-               pcf->rtc.second_enabled = 1;
-               pcf50606_irq_unmask(pcf, PCF50606_IRQ_SECOND);
+       case RTC_UIE_ON:
+               rtc->second_enabled = 1;
+               pcf50606_irq_unmask(rtc->pcf, PCF50606_IRQ_SECOND);
                return 0;
        }
+
        return -ENOIOCTLCMD;
 }
 
 static int pcf50606_rtc_read_time(struct device *dev, struct rtc_time *tm)
 {
-       struct pcf50606 *pcf;
+       struct pcf50606_rtc *rtc;
        struct pcf50606_time pcf_tm;
        int ret;
 
-       pcf = dev_get_drvdata(dev);
+       rtc = dev_get_drvdata(dev);
 
-       ret = pcf50606_read_block(pcf, PCF50606_REG_RTCSC,
+       ret = pcf50606_read_block(rtc->pcf, PCF50606_REG_RTCSC,
                                            PCF50606_TI_EXTENT,
                                            &pcf_tm.time[0]);
-       if (ret != PCF50606_TI_EXTENT)
+       if (ret != PCF50606_TI_EXTENT) {
                dev_err(dev, "Failed to read time\n");
+               return -EIO;
+       }
 
        dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
                pcf_tm.time[PCF50606_TI_DAY],
@@ -128,22 +142,23 @@ static int pcf50606_rtc_read_time(struct device *dev, struct rtc_time *tm)
                tm->tm_mday, tm->tm_mon, tm->tm_year,
                tm->tm_hour, tm->tm_min, tm->tm_sec);
 
-       return 0;
+       return rtc_valid_tm(tm);
 }
 
 static int pcf50606_rtc_set_time(struct device *dev, struct rtc_time *tm)
 {
-       struct pcf50606 *pcf;
+       struct pcf50606_rtc *rtc;
        struct pcf50606_time pcf_tm;
-       int ret;
-       int second_masked, alarm_masked;
+       int second_masked, alarm_masked, ret = 0;
 
-       pcf = dev_get_drvdata(dev);
+       rtc = dev_get_drvdata(dev);
 
        dev_dbg(dev, "RTC_TIME: %u.%u.%u %u:%u:%u\n",
                tm->tm_mday, tm->tm_mon, tm->tm_year,
                tm->tm_hour, tm->tm_min, tm->tm_sec);
+
        rtc2pcf_time(&pcf_tm, tm);
+
        dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
                pcf_tm.time[PCF50606_TI_DAY],
                pcf_tm.time[PCF50606_TI_MONTH],
@@ -153,79 +168,78 @@ static int pcf50606_rtc_set_time(struct device *dev, struct rtc_time *tm)
                pcf_tm.time[PCF50606_TI_SEC]);
 
 
-       second_masked = pcf50606_irq_mask_get(pcf, PCF50606_IRQ_SECOND);
-       alarm_masked = pcf50606_irq_mask_get(pcf, PCF50606_IRQ_ALARM);
+       second_masked = pcf50606_irq_mask_get(rtc->pcf, PCF50606_IRQ_SECOND);
+       alarm_masked = pcf50606_irq_mask_get(rtc->pcf, PCF50606_IRQ_ALARM);
 
        if (!second_masked)
-               pcf50606_irq_mask(pcf, PCF50606_IRQ_SECOND);
+               pcf50606_irq_mask(rtc->pcf, PCF50606_IRQ_SECOND);
        if (!alarm_masked)
-               pcf50606_irq_mask(pcf, PCF50606_IRQ_ALARM);
+               pcf50606_irq_mask(rtc->pcf, PCF50606_IRQ_ALARM);
 
-       ret = pcf50606_write_block(pcf, PCF50606_REG_RTCSC,
+       /* Returns 0 on success */
+       ret = pcf50606_write_block(rtc->pcf, PCF50606_REG_RTCSC,
                                             PCF50606_TI_EXTENT,
                                             &pcf_tm.time[0]);
-       if (ret)
-               dev_err(dev, "Failed to set time %d\n", ret);
 
        if (!second_masked)
-               pcf50606_irq_unmask(pcf, PCF50606_IRQ_SECOND);
+               pcf50606_irq_unmask(rtc->pcf, PCF50606_IRQ_SECOND);
        if (!alarm_masked)
-               pcf50606_irq_unmask(pcf, PCF50606_IRQ_ALARM);
+               pcf50606_irq_unmask(rtc->pcf, PCF50606_IRQ_ALARM);
 
-
-       return 0;
+       return ret;
 }
 
 static int pcf50606_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
 {
-       struct pcf50606 *pcf;
+       struct pcf50606_rtc *rtc;
        struct pcf50606_time pcf_tm;
-       int ret;
+       int ret = 0;
 
-       pcf = dev_get_drvdata(dev);
+       rtc = dev_get_drvdata(dev);
 
-       alrm->enabled = pcf->rtc.alarm_enabled;
+       alrm->enabled = rtc->alarm_enabled;
 
-       ret = pcf50606_read_block(pcf, PCF50606_REG_RTCSCA,
+       ret = pcf50606_read_block(rtc->pcf, PCF50606_REG_RTCSCA,
                                PCF50606_TI_EXTENT, &pcf_tm.time[0]);
-
-       if (ret != PCF50606_TI_EXTENT)
-               dev_err(dev, "Failed to read Alarm time :-(\n");
+       if (ret != PCF50606_TI_EXTENT) {
+               dev_err(dev, "Failed to read time\n");
+               return -EIO;
+       }
 
        pcf2rtc_time(&alrm->time, &pcf_tm);
 
-       return 0;
+       return rtc_valid_tm(&alrm->time);
 }
 
 static int pcf50606_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
 {
-       struct pcf50606 *pcf;
+       struct pcf50606_rtc *rtc;
        struct pcf50606_time pcf_tm;
-       int ret, alarm_masked;
+       int alarm_masked, ret = 0;
 
-       pcf = dev_get_drvdata(dev);
+       rtc = dev_get_drvdata(dev);
 
        rtc2pcf_time(&pcf_tm, &alrm->time);
 
        /* do like mktime does and ignore tm_wday */
        pcf_tm.time[PCF50606_TI_WKDAY] = 7;
 
-       alarm_masked = pcf50606_irq_mask_get(pcf, PCF50606_IRQ_ALARM);
+       alarm_masked = pcf50606_irq_mask_get(rtc->pcf, PCF50606_IRQ_ALARM);
 
        /* disable alarm interrupt */
        if (!alarm_masked)
-               pcf50606_irq_mask(pcf, PCF50606_IRQ_ALARM);
+               pcf50606_irq_mask(rtc->pcf, PCF50606_IRQ_ALARM);
 
-       ret = pcf50606_write_block(pcf, PCF50606_REG_RTCSCA,
-                                       PCF50606_TI_EXTENT, &pcf_tm.time[0]);
-       if (ret)
-               dev_err(dev, "Failed to write alarm time  %d\n", ret);
+       /* Returns 0 on success */
+       ret = pcf50606_write_block(rtc->pcf, PCF50606_REG_RTCSCA,
+                               PCF50606_TI_EXTENT, &pcf_tm.time[0]);
 
        if (!alarm_masked)
-               pcf50606_irq_unmask(pcf, PCF50606_IRQ_ALARM);
+               pcf50606_irq_unmask(rtc->pcf, PCF50606_IRQ_ALARM);
 
-       return 0;
+       return ret;
 }
+
 static struct rtc_class_ops pcf50606_rtc_ops = {
        .ioctl          = pcf50606_rtc_ioctl,
        .read_time      = pcf50606_rtc_read_time,
@@ -234,41 +248,63 @@ static struct rtc_class_ops pcf50606_rtc_ops = {
        .set_alarm      = pcf50606_rtc_set_alarm,
 };
 
-static void pcf50606_rtc_irq(struct pcf50606 *pcf, int irq, void *unused)
+static void pcf50606_rtc_irq(int irq, void *data)
 {
+       struct pcf50606_rtc *rtc = data;
+
        switch (irq) {
        case PCF50606_IRQ_ALARM:
-               rtc_update_irq(pcf->rtc.rtc_dev, 1, RTC_AF | RTC_IRQF);
+               rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF);
                break;
        case PCF50606_IRQ_SECOND:
-               rtc_update_irq(pcf->rtc.rtc_dev, 1, RTC_PF | RTC_IRQF);
+               rtc_update_irq(rtc->rtc_dev, 1, RTC_UF | RTC_IRQF);
                break;
        }
 }
 
-static int pcf50606_rtc_probe(struct platform_device *pdev)
+static int __devinit pcf50606_rtc_probe(struct platform_device *pdev)
 {
-       struct rtc_device *rtc;
-       struct pcf50606 *pcf;
-
-       rtc = rtc_device_register("pcf50606", &pdev->dev,
-                                       &pcf50606_rtc_ops, THIS_MODULE);
-       if (IS_ERR(rtc))
-               return -ENODEV;
-
-       pcf = platform_get_drvdata(pdev);
-
-       /* Set up IRQ handlers */
-       pcf->irq_handler[PCF50606_IRQ_ALARM].handler = pcf50606_rtc_irq;
-       pcf->irq_handler[PCF50606_IRQ_SECOND].handler = pcf50606_rtc_irq;
+       struct pcf50606_subdev_pdata *pdata;
+       struct pcf50606_rtc *rtc;
+
+       
+       rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
+       if (!rtc)
+               return -ENOMEM;
+
+       pdata = pdev->dev.platform_data;
+       rtc->pcf = pdata->pcf;
+       platform_set_drvdata(pdev, rtc);
+       rtc->rtc_dev = rtc_device_register("pcf50606-rtc", &pdev->dev,
+                               &pcf50606_rtc_ops, THIS_MODULE);
+
+       if (IS_ERR(rtc->rtc_dev)) {
+               kfree(rtc);
+               return PTR_ERR(rtc->rtc_dev);
+       }
 
-       pcf->rtc.rtc_dev = rtc;
+       pcf50606_register_irq(rtc->pcf, PCF50606_IRQ_ALARM,
+                                       pcf50606_rtc_irq, rtc);
+       pcf50606_register_irq(rtc->pcf, PCF50606_IRQ_SECOND,
+                                       pcf50606_rtc_irq, rtc);
 
        return 0;
 }
 
-static int pcf50606_rtc_remove(struct platform_device *pdev)
+
+static int __devexit pcf50606_rtc_remove(struct platform_device *pdev)
 {
+       struct pcf50606_rtc *rtc;
+
+       rtc = platform_get_drvdata(pdev);
+       
+       pcf50606_free_irq(rtc->pcf, PCF50606_IRQ_ALARM);
+       pcf50606_free_irq(rtc->pcf, PCF50606_IRQ_SECOND);
+       
+       rtc_device_unregister(rtc->rtc_dev);
+
+       kfree(rtc);
+
        return 0;
 }
 
@@ -293,8 +329,7 @@ static void __exit pcf50606_rtc_exit(void)
 }
 module_exit(pcf50606_rtc_exit);
 
-
-MODULE_DESCRIPTION("RTC driver for NXP PCF50606 power management unit");
+MODULE_DESCRIPTION("PCF50606 RTC driver");
 MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
 MODULE_LICENSE("GPL");
 
diff --git a/drivers/watchdog/pcf50606_wdt.c b/drivers/watchdog/pcf50606_wdt.c
new file mode 100644 (file)
index 0000000..f085874
--- /dev/null
@@ -0,0 +1,223 @@
+/* Philips PCF50606 Watchdog Timer Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50606 driver mainly by
+ * Harald Welte, Matt Hsu, Andy Green and Werner Almesberger
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/err.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/pcf50606/core.h>
+
+static struct pcf50606 *pcf = NULL;
+static unsigned long wdt_status;
+
+#define WDT_IN_USE        0
+#define WDT_OK_TO_CLOSE   1
+#define WDT_REGION_INITED 2
+#define WDT_DEVICE_INITED 3
+
+static int allow_close;
+#define CLOSE_STATE_NOT                0x0000
+#define CLOSE_STATE_ALLOW      0x2342
+
+#define PCF50606_REG_OOCC1     0x08
+#define PCF50606_REG_OOCS      0x01
+
+#define PCF50606_OOCS_WDTEXP   0x80
+#define PCF50606_OOCC1_WDTRST  0x08
+
+static void pcf50606_wdt_start(void)
+{
+       pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_OOCC1, PCF50606_OOCC1_WDTRST,
+                        PCF50606_OOCC1_WDTRST);
+}
+
+static void pcf50606_wdt_stop(void)
+{
+       pcf50606_reg_clear_bits(pcf, PCF50606_REG_OOCS, PCF50606_OOCS_WDTEXP);
+}
+
+static void pcf50606_wdt_keepalive(void)
+{
+       pcf50606_wdt_start();
+}
+
+static int pcf50606_wdt_open(struct inode *inode, struct file *file)
+{
+       if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+               return -EBUSY;
+
+       pcf50606_wdt_start();
+
+       return nonseekable_open(inode, file);
+}
+
+static int pcf50606_wdt_release(struct inode *inode, struct file *file)
+{
+       if (allow_close == CLOSE_STATE_ALLOW)
+               pcf50606_wdt_stop();
+       else {
+               printk(KERN_CRIT "Unexpected close, not stopping watchdog!\n");
+               pcf50606_wdt_keepalive();
+       }
+
+       allow_close = CLOSE_STATE_NOT;
+       clear_bit(WDT_IN_USE, &wdt_status);
+
+       return 0;
+}
+
+static ssize_t pcf50606_wdt_write(struct file *file, const char __user *data,
+                                 size_t len, loff_t *ppos)
+{
+       if (len) {
+               size_t i;
+
+               for (i = 0; i != len; i++) {
+                       char c;
+                       if (get_user(c, data + i))
+                               return -EFAULT;
+                       if (c == 'V')
+                               allow_close = CLOSE_STATE_ALLOW;
+               }
+               pcf50606_wdt_keepalive();
+       }
+
+       return len;
+}
+
+static struct watchdog_info pcf50606_wdt_ident = {
+       .options        = WDIOF_MAGICCLOSE,
+       .firmware_version = 0,
+       .identity       = "PCF50606 Watchdog",
+};
+
+static int pcf50606_wdt_ioctl(struct inode *inode, struct file *file,
+                             unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       int __user *p = argp;
+
+       switch (cmd) {
+       case WDIOC_GETSUPPORT:
+               return copy_to_user(argp, &pcf50606_wdt_ident,
+                                   sizeof(pcf50606_wdt_ident)) ? -EFAULT : 0;
+               break;
+       case WDIOC_GETSTATUS:
+       case WDIOC_GETBOOTSTATUS:
+               return put_user(0, p);
+       case WDIOC_KEEPALIVE:
+               pcf50606_wdt_keepalive();
+               return 0;
+       case WDIOC_GETTIMEOUT:
+               return put_user(8, p);
+       default:
+               return -ENOIOCTLCMD;
+       }
+}
+
+static struct file_operations pcf50606_wdt_fops = {
+       .owner          = THIS_MODULE,
+       .llseek         = no_llseek,
+       .write          = &pcf50606_wdt_write,
+       .ioctl          = &pcf50606_wdt_ioctl,
+       .open           = &pcf50606_wdt_open,
+       .release        = &pcf50606_wdt_release,
+};
+
+static struct miscdevice pcf50606_wdt_miscdev = {
+       .minor          = WATCHDOG_MINOR,
+       .name           = "watchdog",
+       .fops           = &pcf50606_wdt_fops,
+};
+
+static void pcf50606_wdt_irq(int irq, void *unused)
+{
+       pcf50606_reg_set_bit_mask(pcf, PCF50606_REG_OOCC1,
+                                PCF50606_OOCC1_WDTRST,
+                                PCF50606_OOCC1_WDTRST);
+}
+
+int __init pcf50606_wdt_probe(struct platform_device *pdev)
+{
+       struct pcf50606_subdev_pdata *pdata;
+       int err;
+
+       if (pcf) {
+               dev_err(pcf->dev, "Only one instance of WDT supported\n");
+               return -ENODEV;
+       }
+
+       pdata = pdev->dev.platform_data;
+       if (!pdata) {
+               dev_err(&pdev->dev, "No platform data available\n");
+               return -EINVAL;
+       }
+
+       pcf = pdata->pcf;
+
+       err = misc_register(&pcf50606_wdt_miscdev);
+       if (err) {
+               dev_err(&pdev->dev, "cannot register miscdev on "
+                      "minor=%d (%d)\n", WATCHDOG_MINOR, err);
+               return err;
+       }
+       set_bit(WDT_DEVICE_INITED, &wdt_status);
+
+       pcf50606_register_irq(pcf, PCF50606_IRQ_CHGWD10S, pcf50606_wdt_irq, NULL);
+
+       return 0;
+}
+
+static int __devexit pcf50606_wdt_remove(struct platform_device *pdev)
+{
+       pcf50606_free_irq(pcf, PCF50606_IRQ_CHGWD10S);
+       misc_deregister(&pcf50606_wdt_miscdev);
+       pcf = NULL;
+
+       return 0;
+}
+
+struct platform_driver pcf50606_wdt_driver = {
+       .driver = {
+               .name = "pcf50606-wdt",
+       },
+       .probe = pcf50606_wdt_probe,
+       .remove = __devexit_p(pcf50606_wdt_remove),
+};
+
+static int __init pcf50606_wdt_init(void)
+{
+               return platform_driver_register(&pcf50606_wdt_driver);
+}
+module_init(pcf50606_wdt_init);
+
+static void __exit pcf50606_wdt_exit(void)
+{
+               platform_driver_unregister(&pcf50606_wdt_driver);
+}
+module_exit(pcf50606_wdt_exit);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50606 wdt driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50606-wdt");
+
diff --git a/include/linux/gta01_battery.h b/include/linux/gta01_battery.h
new file mode 100644 (file)
index 0000000..d670ad4
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __GTA01_BATTERY_H__
+#define __GTA01_BATTERY_H__
+
+struct gta01_bat_platform_data {
+       int (*get_charging_status)(void);
+       int (*get_voltage)(void);
+       int (*get_current)(void);
+};
+
+#endif
diff --git a/include/linux/mfd/pcf50606/input.h b/include/linux/mfd/pcf50606/input.h
deleted file mode 100644 (file)
index 3709fb9..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * input.h  -- Input driver for NXP PCF50606
- *
- * (C) 2006-2008 by Openmoko, Inc.
- * All rights reserved.
- *
- * 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.
- */
-
-#ifndef __LINUX_MFD_PCF50606_INPUT_H
-#define __LINUX_MFD_PCF50606_INPUT_H
-
-#include <linux/platform_device.h>
-#include <linux/input.h>
-
-#define PCF50606_OOCS_ONKEY     0x01
-#define PCF50606_OOCS_EXTON     0x02
-
-#define PCF50606_OOCC2_ONKEYDB_NONE     0x00
-#define PCF50606_OOCC2_ONKEYDB_14ms     0x01
-#define PCF50606_OOCC2_ONKEYDB_62ms     0x02
-#define PCF50606_OOCC2_ONKEYDB_500ms    0x03
-#define PCF50606_OOCC2_EXTONDB_NONE     0x00
-#define PCF50606_OOCC2_EXTONDB_14ms     0x04
-#define PCF50606_OOCC2_EXTONDB_62ms     0x08
-#define PCF50606_OOCC2_EXTONDB_500ms    0x0c
-
-struct pcf50606_input {
-       struct input_dev *input_dev;
-       struct platform_device *pdev;
-};
-
-#endif
-