2008-12-14 11:36:47

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 0/7] PCF50633 support

[Dropped LKML accidently - resending]

The following series implements support for NXP PCF50633. It's basically
an I2C device with 9 regulators, an ADC, a PMIC, a Battery Charger
and a RTC.

This chip is used in Openmoko Neo Freerunner mobile phone.

Driver for a similar chip PCF50606, used in Openmoko Neo 1973 device
will follow soon.

The specs are open and are available at
http://people.openmoko.org/tony_tu/GTA02/datasheet/PMU/PCF50633DS_02.pdf

---
Balaji Rao (7):
mfd: PCF50633 core driver
mfd: PCF50633 adc driver
mfd: PCF50633 gpio support
rtc: PCF50633 rtc driver
power_supply: PCF50633 battery charger driver
input: PCF50633 input driver
regulator: PCF50633 pmic driver

drivers/input/misc/Kconfig | 6
drivers/input/misc/Makefile | 1
drivers/input/misc/pcf50633-input.c | 119 ++++++
drivers/mfd/Kconfig | 23 +
drivers/mfd/Makefile | 6
drivers/mfd/pcf50633-adc.c | 263 ++++++++++++
drivers/mfd/pcf50633-core.c | 681 ++++++++++++++++++++++++++++++++
drivers/mfd/pcf50633-gpio.c | 86 ++++
drivers/power/Kconfig | 6
drivers/power/Makefile | 2
drivers/power/pcf50633-charger.c | 285 +++++++++++++
drivers/regulator/Kconfig | 7
drivers/regulator/Makefile | 1
drivers/regulator/pcf50633-regulator.c | 338 ++++++++++++++++
drivers/rtc/Kconfig | 6
drivers/rtc/Makefile | 1
drivers/rtc/rtc-pcf50633.c | 302 ++++++++++++++
include/linux/mfd/pcf50633/adc.h | 88 ++++
include/linux/mfd/pcf50633/core.h | 212 ++++++++++
include/linux/mfd/pcf50633/gpio.h | 52 ++
include/linux/mfd/pcf50633/input.h | 29 +
include/linux/mfd/pcf50633/led.h | 24 +
include/linux/mfd/pcf50633/mbc.h | 137 ++++++
include/linux/mfd/pcf50633/pmic.h | 75 ++++
include/linux/mfd/pcf50633/rtc.h | 43 ++
25 files changed, 2792 insertions(+), 1 deletions(-)
create mode 100644 drivers/input/misc/pcf50633-input.c
create mode 100644 drivers/mfd/pcf50633-adc.c
create mode 100644 drivers/mfd/pcf50633-core.c
create mode 100644 drivers/mfd/pcf50633-gpio.c
create mode 100644 drivers/power/pcf50633-charger.c
create mode 100644 drivers/regulator/pcf50633-regulator.c
create mode 100644 drivers/rtc/rtc-pcf50633.c
create mode 100644 include/linux/mfd/pcf50633/adc.h
create mode 100644 include/linux/mfd/pcf50633/core.h
create mode 100644 include/linux/mfd/pcf50633/gpio.h
create mode 100644 include/linux/mfd/pcf50633/input.h
create mode 100644 include/linux/mfd/pcf50633/led.h
create mode 100644 include/linux/mfd/pcf50633/mbc.h
create mode 100644 include/linux/mfd/pcf50633/pmic.h
create mode 100644 include/linux/mfd/pcf50633/rtc.h

--
Balaji Rao


2008-12-14 11:35:34

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 6/7] input: PCF50633 input driver

Signed-off-by: Balaji Rao <[email protected]>
Cc: Andy Green <[email protected]>
Cc: Dmitry Torokhov <[email protected]>
---
drivers/input/misc/Kconfig | 6 ++
drivers/input/misc/Makefile | 1
drivers/input/misc/pcf50633-input.c | 119 +++++++++++++++++++++++++++++++++++
3 files changed, 126 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/misc/pcf50633-input.c

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 199055d..3bc7c93 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -220,4 +220,10 @@ config HP_SDC_RTC
Say Y here if you want to support the built-in real time clock
of the HP SDC controller.

+config INPUT_PCF50633_PMU
+ tristate "PCF50633 PMU events"
+ depends on MFD_PCF50633
+ help
+ Say Y to include support for input events on NXP PCF50633.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index d7db2ae..bb62e6e 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -21,3 +21,4 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_UINPUT) += uinput.o
obj-$(CONFIG_INPUT_APANEL) += apanel.o
obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
+obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o
diff --git a/drivers/input/misc/pcf50633-input.c b/drivers/input/misc/pcf50633-input.c
new file mode 100644
index 0000000..36ea8b2
--- /dev/null
+++ b/drivers/input/misc/pcf50633-input.c
@@ -0,0 +1,119 @@
+/* NXP PCF50633 Input Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <[email protected]>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 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 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 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/input.h>
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/input.h>
+
+static void
+pcf50633_input_irq(struct pcf50633 *pcf, int irq, void *unused)
+{
+ struct input_dev *input_dev = pcf->input.input_dev;
+ int onkey_released;
+
+ /* We report only one event depending on the key press status */
+ onkey_released = pcf50633_reg_read(pcf, PCF50633_REG_OOCSTAT)
+ & PCF50633_OOCSTAT_ONKEY;
+
+ if (irq == PCF50633_IRQ_ONKEYF && !onkey_released)
+ input_report_key(input_dev, KEY_POWER, 1);
+ else if (irq == PCF50633_IRQ_ONKEYR && onkey_released)
+ input_report_key(input_dev, KEY_POWER, 0);
+
+ input_sync(input_dev);
+}
+
+int __init pcf50633_input_probe(struct platform_device *pdev)
+{
+ struct pcf50633 *pcf;
+ struct input_dev *input_dev;
+ int ret;
+
+ pcf = platform_get_drvdata(pdev);
+
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "PCF50633 PMU events";
+ input_dev->id.bustype = BUS_I2C;
+
+ input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_PWR);
+ set_bit(KEY_POWER, input_dev->keybit);
+
+ ret = input_register_device(input_dev);
+ if (ret)
+ goto out;
+
+ pcf->input.input_dev = input_dev;
+
+ /* Set up interrupt handlers */
+ pcf->irq_handler[PCF50633_IRQ_ONKEYR].handler = pcf50633_input_irq;
+ pcf->irq_handler[PCF50633_IRQ_ONKEYF].handler = pcf50633_input_irq;
+
+ return 0;
+
+out:
+ input_free_device(input_dev);
+ return ret;
+}
+
+static int __devexit pcf50633_input_remove(struct platform_device *pdev)
+{
+ struct pcf50633 *pcf;
+
+ pcf = platform_get_drvdata(pdev);
+
+ input_unregister_device(pcf->input.input_dev);
+ input_free_device(pcf->input.input_dev);
+
+ return 0;
+}
+
+struct platform_driver pcf50633_input_driver = {
+ .driver = {
+ .name = "pcf50633-input",
+ },
+ .probe = pcf50633_input_probe,
+ .remove = __devexit_p(pcf50633_input_remove),
+};
+
+static int __init pcf50633_input_init(void)
+{
+ return platform_driver_register(&pcf50633_input_driver);
+}
+module_init(pcf50633_input_init);
+
+static void __exit pcf50633_input_exit(void)
+{
+ platform_driver_unregister(&pcf50633_input_driver);
+}
+module_exit(pcf50633_input_exit);
+
+MODULE_AUTHOR("Balaji Rao <[email protected]>");
+MODULE_DESCRIPTION("PCF50633 input driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50633-input");

2008-12-14 11:35:51

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 4/7] rtc: PCF50633 rtc driver

Signed-off-by: Balaji Rao <[email protected]>
Cc: Andy Green <[email protected]>
Cc: Alessandro Zummo <[email protected]>
Cc: Paul Gortmaker <[email protected]>
---
drivers/rtc/Kconfig | 6 +
drivers/rtc/Makefile | 1
drivers/rtc/rtc-pcf50633.c | 302 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 309 insertions(+), 0 deletions(-)
create mode 100644 drivers/rtc/rtc-pcf50633.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 123092d..68e68d2 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -497,6 +497,12 @@ config RTC_DRV_WM8350
This driver can also be built as a module. If so, the module
will be called "rtc-wm8350".

+config RTC_DRV_PCF50633
+ depends on MFD_PCF50633
+ tristate "NXP PCF50633 RTC"
+ help
+ If you say yes here you get support for the NXP PCF50633 RTC.
+
comment "on-CPU RTC drivers"

config RTC_DRV_OMAP
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 6e79c91..a717fec 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -70,3 +70,4 @@ obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
+obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
diff --git a/drivers/rtc/rtc-pcf50633.c b/drivers/rtc/rtc-pcf50633.c
new file mode 100644
index 0000000..f314810
--- /dev/null
+++ b/drivers/rtc/rtc-pcf50633.c
@@ -0,0 +1,302 @@
+/* NXP PCF50633 RTC Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <[email protected]>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 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 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 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+#include <linux/bcd.h>
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/rtc.h>
+
+enum pcf50633_time_indexes {
+ PCF50633_TI_SEC,
+ PCF50633_TI_MIN,
+ PCF50633_TI_HOUR,
+ PCF50633_TI_WKDAY,
+ PCF50633_TI_DAY,
+ PCF50633_TI_MONTH,
+ PCF50633_TI_YEAR,
+ PCF50633_TI_EXTENT /* always last */
+};
+
+
+struct pcf50633_time {
+ u_int8_t time[PCF50633_TI_EXTENT];
+};
+
+static void pcf2rtc_time(struct rtc_time *rtc, struct pcf50633_time *pcf)
+{
+ rtc->tm_sec = bcd2bin(pcf->time[PCF50633_TI_SEC]);
+ rtc->tm_min = bcd2bin(pcf->time[PCF50633_TI_MIN]);
+ rtc->tm_hour = bcd2bin(pcf->time[PCF50633_TI_HOUR]);
+ rtc->tm_wday = bcd2bin(pcf->time[PCF50633_TI_WKDAY]);
+ rtc->tm_mday = bcd2bin(pcf->time[PCF50633_TI_DAY]);
+ rtc->tm_mon = bcd2bin(pcf->time[PCF50633_TI_MONTH]);
+ rtc->tm_year = bcd2bin(pcf->time[PCF50633_TI_YEAR]) + 100;
+}
+
+static void rtc2pcf_time(struct pcf50633_time *pcf, struct rtc_time *rtc)
+{
+ pcf->time[PCF50633_TI_SEC] = bin2bcd(rtc->tm_sec);
+ pcf->time[PCF50633_TI_MIN] = bin2bcd(rtc->tm_min);
+ pcf->time[PCF50633_TI_HOUR] = bin2bcd(rtc->tm_hour);
+ pcf->time[PCF50633_TI_WKDAY] = bin2bcd(rtc->tm_wday);
+ pcf->time[PCF50633_TI_DAY] = bin2bcd(rtc->tm_mday);
+ pcf->time[PCF50633_TI_MONTH] = bin2bcd(rtc->tm_mon);
+ pcf->time[PCF50633_TI_YEAR] = bin2bcd(rtc->tm_year - 100);
+}
+
+static int
+pcf50633_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ struct pcf50633 *pcf;
+
+ pcf = dev_get_drvdata(dev);
+
+ switch (cmd) {
+ case RTC_AIE_OFF:
+ pcf->rtc.alarm_enabled = 0;
+ pcf50633_irq_mask(pcf, PCF50633_IRQ_ALARM);
+ return 0;
+ case RTC_AIE_ON:
+ pcf->rtc.alarm_enabled = 1;
+ pcf50633_irq_unmask(pcf, PCF50633_IRQ_ALARM);
+ return 0;
+ case RTC_PIE_OFF:
+ pcf->rtc.second_enabled = 0;
+ pcf50633_irq_mask(pcf, PCF50633_IRQ_SECOND);
+ return 0;
+ case RTC_PIE_ON:
+ pcf->rtc.second_enabled = 1;
+ pcf50633_irq_unmask(pcf, PCF50633_IRQ_SECOND);
+ return 0;
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static int pcf50633_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf50633 *pcf;
+ struct pcf50633_time pcf_tm;
+ int ret;
+
+ pcf = dev_get_drvdata(dev);
+
+ ret = pcf50633_read_block(pcf, PCF50633_REG_RTCSC,
+ PCF50633_TI_EXTENT,
+ &pcf_tm.time[0]);
+ if (ret != PCF50633_TI_EXTENT)
+ dev_err(dev, "Failed to read time\n");
+
+ dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
+ pcf_tm.time[PCF50633_TI_DAY],
+ pcf_tm.time[PCF50633_TI_MONTH],
+ pcf_tm.time[PCF50633_TI_YEAR],
+ pcf_tm.time[PCF50633_TI_HOUR],
+ pcf_tm.time[PCF50633_TI_MIN],
+ pcf_tm.time[PCF50633_TI_SEC]);
+
+ pcf2rtc_time(tm, &pcf_tm);
+
+ 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);
+
+ return 0;
+}
+
+static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf50633 *pcf;
+ struct pcf50633_time pcf_tm;
+ int second_masked, alarm_masked, ret = 0;
+
+ pcf = 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[PCF50633_TI_DAY],
+ pcf_tm.time[PCF50633_TI_MONTH],
+ pcf_tm.time[PCF50633_TI_YEAR],
+ pcf_tm.time[PCF50633_TI_HOUR],
+ pcf_tm.time[PCF50633_TI_MIN],
+ pcf_tm.time[PCF50633_TI_SEC]);
+
+
+ second_masked = pcf50633_irq_mask_get(pcf, PCF50633_IRQ_SECOND);
+ alarm_masked = pcf50633_irq_mask_get(pcf, PCF50633_IRQ_ALARM);
+
+ if (!second_masked)
+ pcf50633_irq_mask(pcf, PCF50633_IRQ_SECOND);
+ if (!alarm_masked)
+ pcf50633_irq_mask(pcf, PCF50633_IRQ_ALARM);
+
+ ret = pcf50633_write_block(pcf, PCF50633_REG_RTCSC,
+ PCF50633_TI_EXTENT,
+ &pcf_tm.time[0]);
+ if (ret)
+ dev_err(dev, "Failed to set time %d\n", ret);
+
+ if (!second_masked)
+ pcf50633_irq_unmask(pcf, PCF50633_IRQ_SECOND);
+ if (!alarm_masked)
+ pcf50633_irq_unmask(pcf, PCF50633_IRQ_ALARM);
+
+ return ret;
+}
+
+static int pcf50633_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct pcf50633 *pcf;
+ struct pcf50633_time pcf_tm;
+ int ret = 0;
+
+ pcf = dev_get_drvdata(dev);
+
+ alrm->enabled = pcf->rtc.alarm_enabled;
+
+ ret = pcf50633_read_block(pcf, PCF50633_REG_RTCSCA,
+ PCF50633_TI_EXTENT, &pcf_tm.time[0]);
+
+ if (ret != PCF50633_TI_EXTENT)
+ dev_err(dev, "Failed to read Alarm time %d\n", ret);
+
+ pcf2rtc_time(&alrm->time, &pcf_tm);
+
+ return ret;
+}
+
+static int pcf50633_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct pcf50633 *pcf;
+ struct pcf50633_time pcf_tm;
+ int alarm_masked, ret = 0;
+
+ pcf = dev_get_drvdata(dev);
+
+ rtc2pcf_time(&pcf_tm, &alrm->time);
+
+ /* do like mktime does and ignore tm_wday */
+ pcf_tm.time[PCF50633_TI_WKDAY] = 7;
+
+ alarm_masked = pcf50633_irq_mask_get(pcf, PCF50633_IRQ_ALARM);
+
+ /* disable alarm interrupt */
+ if (!alarm_masked)
+ pcf50633_irq_mask(pcf, PCF50633_IRQ_ALARM);
+
+ ret = pcf50633_write_block(pcf, PCF50633_REG_RTCSCA,
+ PCF50633_TI_EXTENT, &pcf_tm.time[0]);
+ if (ret)
+ dev_err(dev, "Failed to write alarm time %d\n", ret);
+
+ if (!alarm_masked)
+ pcf50633_irq_unmask(pcf, PCF50633_IRQ_ALARM);
+
+ return ret;
+}
+static struct rtc_class_ops pcf50633_rtc_ops = {
+ .ioctl = pcf50633_rtc_ioctl,
+ .read_time = pcf50633_rtc_read_time,
+ .set_time = pcf50633_rtc_set_time,
+ .read_alarm = pcf50633_rtc_read_alarm,
+ .set_alarm = pcf50633_rtc_set_alarm,
+};
+
+static void pcf50633_rtc_irq(struct pcf50633 *pcf, int irq, void *unused)
+{
+ switch (irq) {
+ case PCF50633_IRQ_ALARM:
+ rtc_update_irq(pcf->rtc.rtc_dev, 1, RTC_AF | RTC_IRQF);
+ break;
+ case PCF50633_IRQ_SECOND:
+ rtc_update_irq(pcf->rtc.rtc_dev, 1, RTC_PF | RTC_IRQF);
+ break;
+ }
+}
+
+static int pcf50633_rtc_probe(struct platform_device *pdev)
+{
+ struct rtc_device *rtc;
+ struct pcf50633 *pcf;
+
+ rtc = rtc_device_register("pcf50633-rtc", &pdev->dev,
+ &pcf50633_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc))
+ return -ENODEV;
+
+ pcf = platform_get_drvdata(pdev);
+
+ /* Set up IRQ handlers */
+ pcf->irq_handler[PCF50633_IRQ_ALARM].handler = pcf50633_rtc_irq;
+ pcf->irq_handler[PCF50633_IRQ_SECOND].handler = pcf50633_rtc_irq;
+
+ pcf->rtc.rtc_dev = rtc;
+
+ return 0;
+}
+
+static int pcf50633_rtc_remove(struct platform_device *pdev)
+{
+ struct pcf50633 *pcf;
+
+ pcf = platform_get_drvdata(pdev);
+ rtc_device_unregister(pcf->rtc.rtc_dev);
+
+ return 0;
+}
+
+
+static struct platform_driver pcf50633_rtc_driver = {
+ .driver = {
+ .name = "pcf50633-rtc",
+ },
+ .probe = pcf50633_rtc_probe,
+ .remove = __devexit_p(pcf50633_rtc_remove),
+};
+
+static int __init pcf50633_rtc_init(void)
+{
+ return platform_driver_register(&pcf50633_rtc_driver);
+}
+module_init(pcf50633_rtc_init);
+
+static void __exit pcf50633_rtc_exit(void)
+{
+ platform_driver_unregister(&pcf50633_rtc_driver);
+}
+module_exit(pcf50633_rtc_exit);
+
+
+MODULE_DESCRIPTION("PCF50633 RTC driver");
+MODULE_AUTHOR("Balaji Rao <[email protected]>");
+MODULE_LICENSE("GPL");
+

2008-12-14 11:36:10

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 2/7] mfd: PCF50633 adc driver

This patch adds basic support for the PCF50633 ADC. The subtractive mode
is not supported yet.

Since we don't have adc subsystem, it currently lives in drivers/mfd.

Signed-off-by: Balaji Rao <[email protected]>
Cc: Andy Green <[email protected]>
Cc: Samuel Ortiz <[email protected]>
---
drivers/mfd/Kconfig | 7 +
drivers/mfd/Makefile | 1
drivers/mfd/pcf50633-adc.c | 263 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 271 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/pcf50633-adc.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index fb1f0f7..02e3d73 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -162,6 +162,13 @@ config MFD_PCF50633
facilities, and registers devices for the various functions
so that function-specific drivers can bind to them.

+config PCF50633_ADC
+ tristate "Support for NXP PCF50633 ADC"
+ depends on MFD_PCF50633
+ help
+ Say yes here if you want to include support for ADC in the
+ NXP PCF50633 chip.
+
endmenu

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 6a4add8..7a12b09 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -34,3 +34,4 @@ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
obj-$(CONFIG_PMIC_DA903X) += da903x.o

obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o
+obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o
diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c
new file mode 100644
index 0000000..80e40c6
--- /dev/null
+++ b/drivers/mfd/pcf50633-adc.c
@@ -0,0 +1,263 @@
+/* NXP PCF50633 ADC Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <[email protected]>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 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 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 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+/*
+ * NOTE: This driver does not yet support subtractive ADC mode, which means
+ * you can do only one measurement per read request.
+ */
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/adc.h>
+
+struct pcf50633_adc_request {
+ int mux;
+ int avg;
+ int result;
+ void (*callback)(struct pcf50633 *, void *, int);
+ void *callback_param;
+
+ /* Used in case of sync requests */
+ struct completion completion;
+
+};
+
+static void adc_read_setup(struct pcf50633 *pcf,
+ int channel, int avg)
+{
+ channel &= PCF50633_ADCC1_ADCMUX_MASK;
+
+ /* kill ratiometric, but enable ACCSW biasing */
+ pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
+ pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
+
+ /* start ADC conversion on selected channel */
+ pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
+ PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
+
+}
+
+static void trigger_next_adc_job_if_any(struct pcf50633 *pcf)
+{
+ int head, tail;
+
+ mutex_lock(&pcf->adc.queue_mutex);
+
+ head = pcf->adc.queue_head;
+ tail = pcf->adc.queue_tail;
+
+ if (!pcf->adc.queue[head])
+ goto out;
+
+ adc_read_setup(pcf, pcf->adc.queue[head]->mux,
+ pcf->adc.queue[head]->avg);
+out:
+ mutex_unlock(&pcf->adc.queue_mutex);
+}
+
+static int
+adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req)
+{
+ int head, tail;
+
+ mutex_lock(&pcf->adc.queue_mutex);
+
+ head = pcf->adc.queue_head;
+ tail = pcf->adc.queue_tail;
+
+ if (pcf->adc.queue[tail]) {
+ mutex_unlock(&pcf->adc.queue_mutex);
+ return -EBUSY;
+ }
+
+ pcf->adc.queue[tail] = req;
+ pcf->adc.queue_tail =
+ (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
+
+ mutex_unlock(&pcf->adc.queue_mutex);
+
+ trigger_next_adc_job_if_any(pcf);
+
+ return 0;
+}
+
+static void
+pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result)
+{
+ struct pcf50633_adc_request *req;
+
+ /*We know here that the passed param is an adc_request object */
+ req = param;
+
+ req->result = result;
+ complete(&req->completion);
+}
+
+int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
+{
+
+ struct pcf50633_adc_request *req;
+
+ /* req is freed when the result is ready, in interrupt handler */
+ req = kzalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req->mux = mux;
+ req->avg = avg;
+ req->callback = pcf50633_adc_sync_read_callback;
+ req->callback_param = req;
+
+ init_completion(&req->completion);
+ adc_enqueue_request(pcf, req);
+ wait_for_completion(&req->completion);
+
+ return req->result;
+}
+EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read);
+
+int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
+ void (*callback)(struct pcf50633 *, void *, int),
+ void *callback_param)
+{
+ struct pcf50633_adc_request *req;
+
+ /* req is freed when the result is ready, in interrupt handler */
+ req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req->mux = mux;
+ req->avg = avg;
+ req->callback = callback;
+ req->callback_param = callback_param;
+
+ adc_enqueue_request(pcf, req);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50633_adc_async_read);
+
+static int adc_result(struct pcf50633 *pcf)
+{
+ u8 adcs1, adcs3;
+ u16 result;
+
+ adcs1 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS1);
+ adcs3 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS3);
+ result = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK);
+
+ dev_dbg(pcf->dev, "adc result = %d\n", result);
+
+ return result;
+}
+
+static void pcf50633_adc_irq(struct pcf50633 *pcf, int irq, void *unused)
+{
+ struct pcf50633_adc_request *req;
+ int head;
+
+ mutex_lock(&pcf->adc.queue_mutex);
+ head = pcf->adc.queue_head;
+
+ req = pcf->adc.queue[head];
+ if (!req) {
+ dev_err(pcf->dev, "pcf50633-adc irq: ADC queue empty\n");
+ mutex_unlock(&pcf->adc.queue_mutex);
+ return;
+ }
+ pcf->adc.queue[head] = NULL;
+ pcf->adc.queue_head = (head + 1) &
+ (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
+
+ mutex_unlock(&pcf->adc.queue_mutex);
+ req->callback(pcf, req->callback_param, adc_result(pcf));
+
+ kfree(req);
+
+ trigger_next_adc_job_if_any(pcf);
+}
+
+int __init pcf50633_adc_probe(struct platform_device *pdev)
+{
+ struct pcf50633 *pcf;
+
+ pcf = platform_get_drvdata(pdev);
+
+ /* Set up IRQ handlers */
+ pcf->irq_handler[PCF50633_IRQ_ADCRDY].handler = pcf50633_adc_irq;
+
+ mutex_init(&pcf->adc.queue_mutex);
+
+ return 0;
+}
+
+static int __devexit pcf50633_adc_remove(struct platform_device *pdev)
+{
+ struct pcf50633 *pcf;
+ int i, head;
+
+ pcf = platform_get_drvdata(pdev);
+ pcf->irq_handler[PCF50633_IRQ_ADCRDY].handler = NULL;
+
+ mutex_lock(&pcf->adc.queue_mutex);
+
+ head = pcf->adc.queue_head;
+
+ if (pcf->adc.queue[head])
+ dev_err(pcf->dev, "adc driver removed with request pending\n");
+
+ for (i = 0; i < PCF50633_MAX_ADC_FIFO_DEPTH; i++)
+ kfree(pcf->adc.queue[i]);
+
+ mutex_unlock(&pcf->adc.queue_mutex);
+
+ return 0;
+}
+
+struct platform_driver pcf50633_adc_driver = {
+ .driver = {
+ .name = "pcf50633-adc",
+ },
+ .probe = pcf50633_adc_probe,
+ .remove = __devexit_p(pcf50633_adc_remove),
+};
+
+static int __init pcf50633_adc_init(void)
+{
+ return platform_driver_register(&pcf50633_adc_driver);
+}
+module_init(pcf50633_adc_init);
+
+static void __exit pcf50633_adc_exit(void)
+{
+ platform_driver_unregister(&pcf50633_adc_driver);
+}
+module_exit(pcf50633_adc_exit);
+
+MODULE_AUTHOR("Balaji Rao <[email protected]>");
+MODULE_DESCRIPTION("PCF50633 adc driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50633-adc");
+

2008-12-14 11:36:32

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 1/7] mfd: PCF50633 core driver

This patch implements the core of the PCF50633 driver. This core driver has
generic register read/write functions and does interrupt management for its
sub devices.

This patch is against linux-2.6.git

Signed-off-by: Balaji Rao <[email protected]>
Cc: Andy Green <[email protected]>
Cc: Samuel Ortiz <[email protected]>
---
drivers/mfd/Kconfig | 9
drivers/mfd/Makefile | 4
drivers/mfd/pcf50633-core.c | 681 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/pcf50633/adc.h | 88 +++++
include/linux/mfd/pcf50633/core.h | 212 +++++++++++
include/linux/mfd/pcf50633/gpio.h | 52 +++
include/linux/mfd/pcf50633/input.h | 29 ++
include/linux/mfd/pcf50633/led.h | 24 +
include/linux/mfd/pcf50633/mbc.h | 137 +++++++
include/linux/mfd/pcf50633/pmic.h | 75 ++++
include/linux/mfd/pcf50633/rtc.h | 43 ++
11 files changed, 1353 insertions(+), 1 deletions(-)
create mode 100644 drivers/mfd/pcf50633-core.c
create mode 100644 include/linux/mfd/pcf50633/adc.h
create mode 100644 include/linux/mfd/pcf50633/core.h
create mode 100644 include/linux/mfd/pcf50633/gpio.h
create mode 100644 include/linux/mfd/pcf50633/input.h
create mode 100644 include/linux/mfd/pcf50633/led.h
create mode 100644 include/linux/mfd/pcf50633/mbc.h
create mode 100644 include/linux/mfd/pcf50633/pmic.h
create mode 100644 include/linux/mfd/pcf50633/rtc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 8cd3dd9..fb1f0f7 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -153,6 +153,15 @@ config MFD_WM8350_I2C
I2C as the control interface. Additional options must be
selected to enable support for the functionality of the chip.

+config MFD_PCF50633
+ tristate "Support for NXP PCF50633"
+ depends on I2C
+ help
+ Say yes here if you have NXP PCF50633 chip on your board.
+ This core driver provides register access and IRQ handling
+ facilities, and registers devices for the various functions
+ so that function-specific drivers can bind to them.
+
endmenu

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 9a5ad8a..6a4add8 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -31,4 +31,6 @@ obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o
endif
obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o

-obj-$(CONFIG_PMIC_DA903X) += da903x.o
\ No newline at end of file
+obj-$(CONFIG_PMIC_DA903X) += da903x.o
+
+obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o
diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c
new file mode 100644
index 0000000..5274921
--- /dev/null
+++ b/drivers/mfd/pcf50633-core.c
@@ -0,0 +1,681 @@
+/* NXP PCF50633 Power Management Unit (PMU) driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Harald Welte <[email protected]>
+ * Balaji Rao <[email protected]>
+ * 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.
+ *
+ * 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 330, Boston,
+ * MA 02111-1307 USA
+ *
+ */
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/pcf50633/core.h>
+
+static int __pcf50633_read(struct pcf50633 *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 __pcf50633_write(struct pcf50633 *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 pcf50633_read_block(struct pcf50633 *pcf, u8 reg,
+ int nr_regs, u8 *data)
+{
+ int ret;
+
+ mutex_lock(&pcf->lock);
+
+ ret = __pcf50633_read(pcf, reg, nr_regs, data);
+
+ mutex_unlock(&pcf->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50633_read_block);
+
+/* Write a block of upto 32 regs */
+int pcf50633_write_block(struct pcf50633 *pcf , u8 reg,
+ int nr_regs, u8 *data)
+{
+ int ret;
+
+ mutex_lock(&pcf->lock);
+
+ ret = __pcf50633_write(pcf, reg, nr_regs, data);
+
+ mutex_unlock(&pcf->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50633_write_block);
+
+u8 pcf50633_reg_read(struct pcf50633 *pcf, u8 reg)
+{
+ u8 val;
+
+ mutex_lock(&pcf->lock);
+
+ __pcf50633_read(pcf, reg, 1, &val);
+
+ mutex_unlock(&pcf->lock);
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(pcf50633_reg_read);
+
+int pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val)
+{
+ int ret;
+
+ mutex_lock(&pcf->lock);
+
+ ret = __pcf50633_write(pcf, reg, 1, &val);
+
+ mutex_unlock(&pcf->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50633_reg_write);
+
+int pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ val &= mask;
+
+ mutex_lock(&pcf->lock);
+
+ ret = __pcf50633_read(pcf, reg, 1, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp &= ~mask;
+ tmp |= val;
+ ret = __pcf50633_write(pcf, reg, 1, &tmp);
+
+out:
+ mutex_unlock(&pcf->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50633_reg_set_bit_mask);
+
+int pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ mutex_lock(&pcf->lock);
+
+ ret = __pcf50633_read(pcf, reg, 1, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp &= ~val;
+ ret = __pcf50633_write(pcf, reg, 1, &tmp);
+
+out:
+ mutex_unlock(&pcf->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50633_reg_clear_bits);
+
+/* sysfs attributes */
+static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct pcf50633 *pcf = dev_get_drvdata(dev);
+ u8 dump[16];
+ int n, n1, idx = 0;
+ char *buf1 = buf;
+ static u8 address_no_read[] = { /* must be ascending */
+ PCF50633_REG_INT1,
+ PCF50633_REG_INT2,
+ PCF50633_REG_INT3,
+ PCF50633_REG_INT4,
+ PCF50633_REG_INT5,
+ 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] = pcf50633_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 pcf50633 *pcf = dev_get_drvdata(dev);
+ int n;
+
+ n = sprintf(buf, "%02x%02x%02x%02x%02x\n",
+ pcf->resume_reason[0],
+ pcf->resume_reason[1],
+ pcf->resume_reason[2],
+ pcf->resume_reason[3],
+ pcf->resume_reason[4]);
+
+ 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,
+};
+
+
+static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask)
+{
+ u8 reg, bits, tmp;
+ int ret = 0, idx;
+
+ idx = irq >> 3;
+ reg = PCF50633_REG_INT1M + idx;
+ bits = 1 << (irq & 0x07);
+
+ mutex_lock(&pcf->lock);
+
+ if (mask) {
+ ret = __pcf50633_read(pcf, reg, 1, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp |= bits;
+
+ ret = __pcf50633_write(pcf, reg, 1, &tmp);
+ if (ret < 0)
+ goto out;
+
+ pcf->mask_regs[idx] &= ~bits;
+ pcf->mask_regs[idx] |= bits;
+ } else {
+ ret = __pcf50633_read(pcf, reg, 1, &tmp);
+ if (ret < 0)
+ goto out;
+
+ tmp &= ~bits;
+
+ ret = __pcf50633_write(pcf, reg, 1, &tmp);
+ if (ret < 0)
+ goto out;
+
+ pcf->mask_regs[idx] &= ~bits;
+ }
+out:
+ mutex_unlock(&pcf->lock);
+
+ return ret;
+}
+
+int pcf50633_irq_mask(struct pcf50633 *pcf, int irq)
+{
+ dev_info(pcf->dev, "Masking IRQ %d\n", irq);
+
+ return __pcf50633_irq_mask_set(pcf, irq, 1);
+}
+EXPORT_SYMBOL_GPL(pcf50633_irq_mask);
+
+int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq)
+{
+ dev_info(pcf->dev, "Unmasking IRQ %d\n", irq);
+
+ return __pcf50633_irq_mask_set(pcf, irq, 0);
+}
+EXPORT_SYMBOL_GPL(pcf50633_irq_unmask);
+
+int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq)
+{
+ u8 reg, bits;
+
+ reg = irq >> 3;
+ bits = 1 << (irq & 0x07);
+
+ return pcf->mask_regs[reg] & bits;
+}
+EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get);
+
+static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq)
+{
+ if (pcf->irq_handler[irq].handler)
+ pcf->irq_handler[irq].handler(pcf, irq,
+ pcf->irq_handler[irq].data);
+}
+
+/* Maximum amount of time ONKEY is held before emergency action is taken */
+#define PCF50633_ONKEY1S_TIMEOUT 8
+
+static void pcf50633_irq_worker(struct work_struct *work)
+{
+ struct pcf50633 *pcf;
+ int ret, i, j;
+ u8 pcf_int[5], chgstat;
+
+ pcf = container_of(work, struct pcf50633, irq_work);
+
+ /* Read the 5 INT regs in one transaction */
+ ret = pcf50633_read_block(pcf, PCF50633_REG_INT1,
+ ARRAY_SIZE(pcf_int), pcf_int);
+ if (ret != ARRAY_SIZE(pcf_int)) {
+ dev_err(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 usb and adapter status. We thus make sure
+ * only of USBINS/USBREM IRQ handlers are called */
+ if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) {
+ chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
+ if (chgstat & (0x3 << 4))
+ pcf_int[0] &= ~(1 << PCF50633_INT1_USBREM);
+ else
+ pcf_int[0] &= ~(1 << PCF50633_INT1_USBINS);
+ }
+
+ /* Make sure only one of ADPINS or ADPREM is set */
+ if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) {
+ chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
+ if (chgstat & (0x3 << 4))
+ pcf_int[0] &= ~(1 << PCF50633_INT1_ADPREM);
+ else
+ pcf_int[0] &= ~(1 << PCF50633_INT1_ADPINS);
+ }
+
+ dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x "
+ "INT4=0x%02x INT5=0x%02x\n", pcf_int[0],
+ pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]);
+
+ /* 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] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) {
+ dev_info(pcf->dev, "ONKEY1S held for %d secs\n",
+ pcf->onkey1s_held);
+ if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT)
+ if (pcf->pdata->force_shutdown)
+ pcf->pdata->force_shutdown(pcf);
+ }
+
+ if (pcf_int[2] & PCF50633_INT3_ONKEY1S) {
+ dev_info(pcf->dev, "ONKEY1S held\n");
+ pcf->onkey1s_held = 1 ;
+
+ /* Unmask IRQ_SECOND */
+ pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M,
+ PCF50633_INT1_SECOND);
+
+ /* Unmask IRQ_ONKEYR */
+ pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M,
+ PCF50633_INT2_ONKEYR);
+ }
+
+ if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) {
+ pcf->onkey1s_held = 0;
+
+ /* Mask SECOND and ONKEYR interrupts */
+ if (pcf->mask_regs[0] & PCF50633_INT1_SECOND)
+ pcf50633_reg_set_bit_mask(pcf,
+ PCF50633_REG_INT1M,
+ PCF50633_INT1_SECOND,
+ PCF50633_INT1_SECOND);
+
+ if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR)
+ pcf50633_reg_set_bit_mask(pcf,
+ PCF50633_REG_INT2M,
+ PCF50633_INT2_ONKEYR,
+ PCF50633_INT2_ONKEYR);
+ }
+
+ /* 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 any ONKEY events to
+ * userspace now */
+ pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_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))
+ pcf50633_irq_call_handler(pcf, (i * 8) + j);
+ }
+
+out:
+ put_device(pcf->dev);
+ enable_irq(pcf->irq);
+}
+
+static irqreturn_t pcf50633_irq(int irq, void *data)
+{
+ struct pcf50633 *pcf = data;
+
+ dev_dbg(pcf->dev, "pcf50633_irq\n");
+
+ get_device(pcf->dev);
+ disable_irq(pcf->irq);
+ schedule_work(&pcf->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+static void
+pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name,
+ struct platform_device **pdev)
+{
+ int ret;
+
+ *pdev = platform_device_alloc(name, -1);
+
+ if (!pdev) {
+ dev_err(pcf->dev, "Falied to allocate %s\n", name);
+ return;
+ }
+
+ (*pdev)->dev.parent = pcf->dev;
+ platform_set_drvdata(*pdev, pcf);
+
+ 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 pcf50633_suspend(struct device *dev, pm_message_t state)
+{
+ struct pcf50633 *pcf;
+ int ret = 0, i;
+ u8 res[5];
+
+ 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 = pcf50633_read_block(pcf, PCF50633_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 = pcf50633_write_block(pcf, PCF50633_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 pcf50633_resume(struct device *dev)
+{
+ struct pcf50633 *pcf;
+ int ret;
+
+ pcf = dev_get_drvdata(dev);
+
+ /* Write the saved mask registers */
+ ret = pcf50633_write_block(pcf, PCF50633_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()
+ */
+ pcf50633_irq_worker(&pcf->irq_work);
+
+ return 0;
+}
+#else
+#define pcf50633_suspend NULL
+#define pcf50633_resume NULL
+#endif
+
+static int pcf50633_probe(struct i2c_client *client,
+ const struct i2c_device_id *ids)
+{
+ struct pcf50633 *pcf;
+ struct pcf50633_platform_data *pdata;
+ int i, ret = 0;
+ int version, variant;
+ u8 mbcs1;
+
+ pdata = client->dev.platform_data;
+
+ pcf = kzalloc(sizeof(*pcf), GFP_KERNEL);
+ if (!pcf)
+ return -ENOMEM;
+
+ pcf->pdata = pdata;
+ pdata->pcf = pcf;
+
+ mutex_init(&pcf->lock);
+
+ i2c_set_clientdata(client, pcf);
+ pcf->dev = &client->dev;
+ pcf->i2c_client = client;
+
+ INIT_WORK(&pcf->irq_work, pcf50633_irq_worker);
+
+ version = pcf50633_reg_read(pcf, 0);
+ variant = pcf50633_reg_read(pcf, 1);
+ if (version < 0 || variant < 0) {
+ dev_err(pcf->dev, "Unable to probe pcf50633\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] = 0x80;
+ pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]);
+ pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00);
+ pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00);
+ pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00);
+ pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00);
+
+ pcf50633_client_dev_register(pcf, "pcf50633-input",
+ &pcf->input.pdev);
+ pcf50633_client_dev_register(pcf, "pcf50633-rtc",
+ &pcf->rtc.pdev);
+ pcf50633_client_dev_register(pcf, "pcf50633-mbc",
+ &pcf->mbc.pdev);
+ pcf50633_client_dev_register(pcf, "pcf50633-adc",
+ &pcf->adc.pdev);
+
+ for (i = 0; i < PCF50633_NUM_REGULATORS; i++) {
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("pcf50633-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->pmic.pdev[i] = pdev;
+
+ platform_device_add(pdev);
+ }
+
+ pcf->irq = client->irq;
+
+ if (client->irq) {
+ set_irq_handler(client->irq, handle_level_irq);
+ ret = request_irq(client->irq, pcf50633_irq,
+ IRQF_TRIGGER_LOW, "pcf50633", 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);
+
+ mbcs1 = pcf50633_reg_read(pcf, PCF50633_REG_MBCS1);
+
+ if (mbcs1 & PCF50633_MBCS1_USBPRES)
+ pcf50633_irq_call_handler(pcf, PCF50633_IRQ_USBINS);
+ if (mbcs1 & PCF50633_MBCS1_ADAPTPRES)
+ pcf50633_irq_call_handler(pcf, PCF50633_IRQ_ADPINS);
+
+ 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 pcf50633_remove(struct i2c_client *client)
+{
+ struct pcf50633 *pcf = i2c_get_clientdata(client);
+
+ free_irq(pcf->irq, pcf);
+ kfree(pcf);
+
+ return 0;
+}
+
+static struct i2c_device_id pcf50633_id_table[] = {
+ {"pcf50633", 0x73},
+};
+
+static struct i2c_driver pcf50633_driver = {
+ .driver = {
+ .name = "pcf50633",
+ .suspend = pcf50633_suspend,
+ .resume = pcf50633_resume,
+ },
+ .id_table = pcf50633_id_table,
+ .probe = pcf50633_probe,
+ .remove = pcf50633_remove,
+};
+
+static int __init pcf50633_init(void)
+{
+ return i2c_add_driver(&pcf50633_driver);
+}
+
+static void pcf50633_exit(void)
+{
+ i2c_del_driver(&pcf50633_driver);
+}
+
+MODULE_DESCRIPTION("I2C chip driver for NXP PCF50633 PMU");
+MODULE_AUTHOR("Harald Welte <[email protected]>");
+MODULE_LICENSE("GPL");
+
+module_init(pcf50633_init);
+module_exit(pcf50633_exit);
diff --git a/include/linux/mfd/pcf50633/adc.h b/include/linux/mfd/pcf50633/adc.h
new file mode 100644
index 0000000..13862bf
--- /dev/null
+++ b/include/linux/mfd/pcf50633/adc.h
@@ -0,0 +1,88 @@
+/*
+ * adc.h -- Driver for NXP PCF50633 ADC
+ *
+ * (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_PCF50633_ADC_H
+#define __LINUX_MFD_PCF50633_ADC_H
+
+#include <linux/platform_device.h>
+
+/* ADC Registers */
+#define PCF50633_REG_ADCC3 0x52
+#define PCF50633_REG_ADCC2 0x53
+#define PCF50633_REG_ADCC1 0x54
+#define PCF50633_REG_ADCS1 0x55
+#define PCF50633_REG_ADCS2 0x56
+#define PCF50633_REG_ADCS3 0x57
+
+#define PCF50633_ADCC1_ADCSTART 0x01
+#define PCF50633_ADCC1_RES_10BIT 0x02
+#define PCF50633_ADCC1_AVERAGE_NO 0x00
+#define PCF50633_ADCC1_AVERAGE_4 0x04
+#define PCF50633_ADCC1_AVERAGE_8 0x08
+#define PCF50633_ADCC1_AVERAGE_16 0x0c
+#define PCF50633_ADCC1_MUX_BATSNS_RES 0x00
+#define PCF50633_ADCC1_MUX_BATSNS_SUBTR 0x10
+#define PCF50633_ADCC1_MUX_ADCIN2_RES 0x20
+#define PCF50633_ADCC1_MUX_ADCIN2_SUBTR 0x30
+#define PCF50633_ADCC1_MUX_BATTEMP 0x60
+#define PCF50633_ADCC1_MUX_ADCIN1 0x70
+#define PCF50633_ADCC1_AVERAGE_MASK 0x0c
+#define PCF50633_ADCC1_ADCMUX_MASK 0xf0
+
+#define PCF50633_ADCC2_RATIO_NONE 0x00
+#define PCF50633_ADCC2_RATIO_BATTEMP 0x01
+#define PCF50633_ADCC2_RATIO_ADCIN1 0x02
+#define PCF50633_ADCC2_RATIO_BOTH 0x03
+#define PCF50633_ADCC2_RATIOSETTL_100US 0x04
+
+#define PCF50633_ADCC3_ACCSW_EN 0x01
+#define PCF50633_ADCC3_NTCSW_EN 0x04
+#define PCF50633_ADCC3_RES_DIV_TWO 0x10
+#define PCF50633_ADCC3_RES_DIV_THREE 0x00
+
+#define PCF50633_ADCS3_REF_NTCSW 0x00
+#define PCF50633_ADCS3_REF_ACCSW 0x10
+#define PCF50633_ADCS3_REF_2V0 0x20
+#define PCF50633_ADCS3_REF_VISA 0x30
+#define PCF50633_ADCS3_REF_2V0_2 0x70
+#define PCF50633_ADCS3_ADCRDY 0x80
+
+#define PCF50633_ADCS3_ADCDAT1L_MASK 0x03
+#define PCF50633_ADCS3_ADCDAT2L_MASK 0x0c
+#define PCF50633_ADCS3_ADCDAT2L_SHIFT 2
+#define PCF50633_ASCS3_REF_MASK 0x70
+
+
+struct pcf50633;
+
+#define PCF50633_MAX_ADC_FIFO_DEPTH 8
+
+struct pcf50633_adc_request;
+
+struct pcf50633_adc {
+ struct platform_device *pdev;
+
+ /* Private stuff */
+ struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH];
+ int queue_head;
+ int queue_tail;
+ struct mutex queue_mutex;
+};
+
+extern int
+pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
+ void (*callback)(struct pcf50633 *, void *, int),
+ void *callback_param);
+extern int
+pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg);
+
+#endif /* __LINUX_PCF50633_ADC_H */
diff --git a/include/linux/mfd/pcf50633/core.h b/include/linux/mfd/pcf50633/core.h
new file mode 100644
index 0000000..b69ff9c
--- /dev/null
+++ b/include/linux/mfd/pcf50633/core.h
@@ -0,0 +1,212 @@
+/*
+ * core.h -- Core driver for NXP PCF50633
+ *
+ * (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_PCF50633_CORE_H
+#define __LINUX_MFD_PCF50633_CORE_H
+
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/pcf50633/pmic.h>
+#include <linux/mfd/pcf50633/input.h>
+#include <linux/mfd/pcf50633/mbc.h>
+#include <linux/mfd/pcf50633/rtc.h>
+#include <linux/mfd/pcf50633/adc.h>
+#include <linux/mfd/pcf50633/gpio.h>
+
+struct pcf50633;
+
+struct pcf50633_platform_data {
+ struct regulator_init_data reg_init_data[PCF50633_NUM_REGULATORS];
+
+ char **batteries;
+ int num_batteries;
+
+ /* Callbacks */
+ void (*probe_done)(struct pcf50633 *);
+ void (*mbc_event_callback)(struct pcf50633 *, int);
+ void (*regulator_registered)(struct pcf50633 *, int);
+ void (*force_shutdown)(struct pcf50633 *);
+
+ u8 resumers[5];
+
+ /* Runtime data - filled by driver afer probe */
+ struct pcf50633 *pcf;
+};
+
+struct pcf50633_irq {
+ void (*handler)(struct pcf50633 *, int, void *);
+ void *data;
+};
+
+int pcf50633_irq_mask(struct pcf50633 *pcf, int irq);
+int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq);
+int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq);
+
+int pcf50633_read_block(struct pcf50633 *, u8 reg,
+ int nr_regs, u8 *data);
+int pcf50633_write_block(struct pcf50633 *pcf, u8 reg,
+ int nr_regs, u8 *data);
+u8 pcf50633_reg_read(struct pcf50633 *, u8 reg);
+int pcf50633_reg_write(struct pcf50633 *pcf, u8 reg, u8 val);
+
+int pcf50633_reg_set_bit_mask(struct pcf50633 *pcf, u8 reg, u8 mask, u8 val);
+int pcf50633_reg_clear_bits(struct pcf50633 *pcf, u8 reg, u8 bits);
+
+/* Interrupt registers */
+
+#define PCF50633_REG_INT1 0x02
+#define PCF50633_REG_INT2 0x03
+#define PCF50633_REG_INT3 0x04
+#define PCF50633_REG_INT4 0x05
+#define PCF50633_REG_INT5 0x06
+
+#define PCF50633_REG_INT1M 0x07
+#define PCF50633_REG_INT2M 0x08
+#define PCF50633_REG_INT3M 0x09
+#define PCF50633_REG_INT4M 0x0a
+#define PCF50633_REG_INT5M 0x0b
+
+enum {
+ /* Chip IRQs */
+ PCF50633_IRQ_ADPINS,
+ PCF50633_IRQ_ADPREM,
+ PCF50633_IRQ_USBINS,
+ PCF50633_IRQ_USBREM,
+ PCF50633_IRQ_RESERVED1,
+ PCF50633_IRQ_RESERVED2,
+ PCF50633_IRQ_ALARM,
+ PCF50633_IRQ_SECOND,
+ PCF50633_IRQ_ONKEYR,
+ PCF50633_IRQ_ONKEYF,
+ PCF50633_IRQ_EXTON1R,
+ PCF50633_IRQ_EXTON1F,
+ PCF50633_IRQ_EXTON2R,
+ PCF50633_IRQ_EXTON2F,
+ PCF50633_IRQ_EXTON3R,
+ PCF50633_IRQ_EXTON3F,
+ PCF50633_IRQ_BATFULL,
+ PCF50633_IRQ_CHGHALT,
+ PCF50633_IRQ_THLIMON,
+ PCF50633_IRQ_THLIMOFF,
+ PCF50633_IRQ_USBLIMON,
+ PCF50633_IRQ_USBLIMOFF,
+ PCF50633_IRQ_ADCRDY,
+ PCF50633_IRQ_ONKEY1S,
+ PCF50633_IRQ_LOWSYS,
+ PCF50633_IRQ_LOWBAT,
+ PCF50633_IRQ_HIGHTMP,
+ PCF50633_IRQ_AUTOPWRFAIL,
+ PCF50633_IRQ_DWN1PWRFAIL,
+ PCF50633_IRQ_DWN2PWRFAIL,
+ PCF50633_IRQ_LEDPWRFAIL,
+ PCF50633_IRQ_LEDOVP,
+ PCF50633_IRQ_LDO1PWRFAIL,
+ PCF50633_IRQ_LDO2PWRFAIL,
+ PCF50633_IRQ_LDO3PWRFAIL,
+ PCF50633_IRQ_LDO4PWRFAIL,
+ PCF50633_IRQ_LDO5PWRFAIL,
+ PCF50633_IRQ_LDO6PWRFAIL,
+ PCF50633_IRQ_HCLDOPWRFAIL,
+ PCF50633_IRQ_HCLDOOVL,
+
+ /* Always last */
+ PCF50633_NUM_IRQ,
+};
+
+struct pcf50633 {
+ struct device *dev;
+ struct i2c_client *i2c_client;
+
+ struct pcf50633_platform_data *pdata;
+ int irq;
+ struct pcf50633_irq irq_handler[PCF50633_NUM_IRQ];
+ struct work_struct irq_work;
+ struct mutex lock;
+
+ u8 mask_regs[5];
+
+ u8 suspend_irq_masks[5];
+ u8 resume_reason[5];
+ int is_suspended;
+
+ int onkey1s_held;
+
+ struct pcf50633_pmic pmic;
+ struct pcf50633_input input;
+ struct pcf50633_mbc mbc;
+ struct pcf50633_rtc rtc;
+ struct pcf50633_adc adc;
+};
+
+enum pcf50633_reg_int1 {
+ PCF50633_INT1_ADPINS = 0x01, /* Adapter inserted */
+ PCF50633_INT1_ADPREM = 0x02, /* Adapter removed */
+ PCF50633_INT1_USBINS = 0x04, /* USB inserted */
+ PCF50633_INT1_USBREM = 0x08, /* USB removed */
+ /* reserved */
+ PCF50633_INT1_ALARM = 0x40, /* RTC alarm time is reached */
+ PCF50633_INT1_SECOND = 0x80, /* RTC periodic second interrupt */
+};
+
+enum pcf50633_reg_int2 {
+ PCF50633_INT2_ONKEYR = 0x01, /* ONKEY rising edge */
+ PCF50633_INT2_ONKEYF = 0x02, /* ONKEY falling edge */
+ PCF50633_INT2_EXTON1R = 0x04, /* EXTON1 rising edge */
+ PCF50633_INT2_EXTON1F = 0x08, /* EXTON1 falling edge */
+ PCF50633_INT2_EXTON2R = 0x10, /* EXTON2 rising edge */
+ PCF50633_INT2_EXTON2F = 0x20, /* EXTON2 falling edge */
+ PCF50633_INT2_EXTON3R = 0x40, /* EXTON3 rising edge */
+ PCF50633_INT2_EXTON3F = 0x80, /* EXTON3 falling edge */
+};
+
+enum pcf50633_reg_int3 {
+ PCF50633_INT3_BATFULL = 0x01, /* Battery full */
+ PCF50633_INT3_CHGHALT = 0x02, /* Charger halt */
+ PCF50633_INT3_THLIMON = 0x04,
+ PCF50633_INT3_THLIMOFF = 0x08,
+ PCF50633_INT3_USBLIMON = 0x10,
+ PCF50633_INT3_USBLIMOFF = 0x20,
+ PCF50633_INT3_ADCRDY = 0x40, /* ADC result ready */
+ PCF50633_INT3_ONKEY1S = 0x80, /* ONKEY pressed 1 second */
+};
+
+enum pcf50633_reg_int4 {
+ PCF50633_INT4_LOWSYS = 0x01,
+ PCF50633_INT4_LOWBAT = 0x02,
+ PCF50633_INT4_HIGHTMP = 0x04,
+ PCF50633_INT4_AUTOPWRFAIL = 0x08,
+ PCF50633_INT4_DWN1PWRFAIL = 0x10,
+ PCF50633_INT4_DWN2PWRFAIL = 0x20,
+ PCF50633_INT4_LEDPWRFAIL = 0x40,
+ PCF50633_INT4_LEDOVP = 0x80,
+};
+
+enum pcf50633_reg_int5 {
+ PCF50633_INT5_LDO1PWRFAIL = 0x01,
+ PCF50633_INT5_LDO2PWRFAIL = 0x02,
+ PCF50633_INT5_LDO3PWRFAIL = 0x04,
+ PCF50633_INT5_LDO4PWRFAIL = 0x08,
+ PCF50633_INT5_LDO5PWRFAIL = 0x10,
+ PCF50633_INT5_LDO6PWRFAIL = 0x20,
+ PCF50633_INT5_HCLDOPWRFAIL = 0x40,
+ PCF50633_INT5_HCLDOOVL = 0x80,
+};
+
+/* misc. registers */
+#define PCF50633_REG_OOCSHDWN 0x0c
+
+#endif
+
diff --git a/include/linux/mfd/pcf50633/gpio.h b/include/linux/mfd/pcf50633/gpio.h
new file mode 100644
index 0000000..33bf7c7
--- /dev/null
+++ b/include/linux/mfd/pcf50633/gpio.h
@@ -0,0 +1,52 @@
+/*
+ * gpio.h -- GPIO driver for NXP PCF50633
+ *
+ * (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_PCF50633_GPIO_H
+#define __LINUX_MFD_PCF50633_GPIO_H
+
+#define PCF50633_GPIO1 1
+#define PCF50633_GPIO2 2
+#define PCF50633_GPIO3 3
+#define PCF50633_GPO 4
+
+#define PCF50633_REG_GPIO1CFG 0x14
+#define PCF50633_REG_GPIO2CFG 0x15
+#define PCF50633_REG_GPIO3CFG 0x16
+#define PCF50633_REG_GPOCFG 0x17
+
+#define PCF50633_GPOCFG_GPOSEL_MASK 0x07
+
+enum pcf50633_reg_gpocfg {
+ PCF50633_GPOCFG_GPOSEL_0 = 0x00,
+ PCF50633_GPOCFG_GPOSEL_LED_NFET = 0x01,
+ PCF50633_GPOCFG_GPOSEL_SYSxOK = 0x02,
+ PCF50633_GPOCFG_GPOSEL_CLK32K = 0x03,
+ PCF50633_GPOCFG_GPOSEL_ADAPUSB = 0x04,
+ PCF50633_GPOCFG_GPOSEL_USBxOK = 0x05,
+ PCF50633_GPOCFG_GPOSEL_ACTPH4 = 0x06,
+ PCF50633_GPOCFG_GPOSEL_1 = 0x07,
+ PCF50633_GPOCFG_GPOSEL_INVERSE = 0x08,
+};
+
+struct pcf50633;
+
+int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val);
+u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio);
+
+int pcf50633_gpio_invert_set(struct pcf50633 *, int gpio, int invert);
+int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio);
+
+int pcf50633_gpio_power_supply_set(struct pcf50633 *,
+ int gpio, int regulator, int on);
+#endif /* __LINUX_MFD_PCF50633_GPIO_H */
+
+
diff --git a/include/linux/mfd/pcf50633/input.h b/include/linux/mfd/pcf50633/input.h
new file mode 100644
index 0000000..fd10302
--- /dev/null
+++ b/include/linux/mfd/pcf50633/input.h
@@ -0,0 +1,29 @@
+/*
+ * input.h -- Input driver for NXP PCF50633
+ *
+ * (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_PCF50633_INPUT_H
+#define __LINUX_MFD_PCF50633_INPUT_H
+
+#include <linux/platform_device.h>
+#include <linux/input.h>
+
+#define PCF50633_OOCSTAT_ONKEY 0x01
+#define PCF50633_REG_OOCSTAT 0x12
+#define PCF50633_REG_OOCMODE 0x10
+
+struct pcf50633_input {
+ struct input_dev *input_dev;
+ struct platform_device *pdev;
+};
+
+#endif
+
diff --git a/include/linux/mfd/pcf50633/led.h b/include/linux/mfd/pcf50633/led.h
new file mode 100644
index 0000000..c84a97e
--- /dev/null
+++ b/include/linux/mfd/pcf50633/led.h
@@ -0,0 +1,24 @@
+/*
+ * led.h -- LED driver for NXP PCF50633
+ *
+ * (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_PCF50633_LED_H
+#define __LINUX_MFD_PCF50633_LED_H
+
+#include <linux/platform_device.h>
+
+#define PCF50633_REG_LEDOUT 0x28
+#define PCF50633_REG_LEDENA 0x29
+#define PCF50633_REG_LEDCTL 0x2a
+#define PCF50633_REG_LEDDIM 0x2b
+
+#endif
+
diff --git a/include/linux/mfd/pcf50633/mbc.h b/include/linux/mfd/pcf50633/mbc.h
new file mode 100644
index 0000000..7843934
--- /dev/null
+++ b/include/linux/mfd/pcf50633/mbc.h
@@ -0,0 +1,137 @@
+/*
+ * mbc.h -- Driver for NXP PCF50633 Main Battery Charger
+ *
+ * (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_PCF50633_MBC_H
+#define __LINUX_MFD_PCF50633_MBC_H
+
+#include <linux/platform_device.h>
+
+#define PCF50633_REG_MBCC1 0x43
+#define PCF50633_REG_MBCC2 0x44
+#define PCF50633_REG_MBCC3 0x45
+#define PCF50633_REG_MBCC4 0x46
+#define PCF50633_REG_MBCC5 0x47
+#define PCF50633_REG_MBCC6 0x48
+#define PCF50633_REG_MBCC7 0x49
+#define PCF50633_REG_MBCC8 0x4a
+#define PCF50633_REG_MBCS1 0x4b
+#define PCF50633_REG_MBCS2 0x4c
+#define PCF50633_REG_MBCS3 0x4d
+
+enum pcf50633_reg_mbcc1 {
+ PCF50633_MBCC1_CHGENA = 0x01, /* Charger enable */
+ PCF50633_MBCC1_AUTOSTOP = 0x02,
+ PCF50633_MBCC1_AUTORES = 0x04, /* automatic resume */
+ PCF50633_MBCC1_RESUME = 0x08, /* explicit resume cmd */
+ PCF50633_MBCC1_RESTART = 0x10, /* restart charging */
+ PCF50633_MBCC1_PREWDTIME_60M = 0x20, /* max. precharging time */
+ PCF50633_MBCC1_WDTIME_1H = 0x00,
+ PCF50633_MBCC1_WDTIME_2H = 0x40,
+ PCF50633_MBCC1_WDTIME_4H = 0x80,
+ PCF50633_MBCC1_WDTIME_6H = 0xc0,
+};
+#define PCF50633_MBCC1_WDTIME_MASK 0xc0
+
+enum pcf50633_reg_mbcc2 {
+ PCF50633_MBCC2_VBATCOND_2V7 = 0x00,
+ PCF50633_MBCC2_VBATCOND_2V85 = 0x01,
+ PCF50633_MBCC2_VBATCOND_3V0 = 0x02,
+ PCF50633_MBCC2_VBATCOND_3V15 = 0x03,
+ PCF50633_MBCC2_VMAX_4V = 0x00,
+ PCF50633_MBCC2_VMAX_4V20 = 0x28,
+ PCF50633_MBCC2_VRESDEBTIME_64S = 0x80, /* debounce time (32/64sec) */
+};
+
+enum pcf50633_reg_mbcc7 {
+ PCF50633_MBCC7_USB_100mA = 0x00,
+ PCF50633_MBCC7_USB_500mA = 0x01,
+ PCF50633_MBCC7_USB_1000mA = 0x02,
+ PCF50633_MBCC7_USB_SUSPEND = 0x03,
+ PCF50633_MBCC7_BATTEMP_EN = 0x04,
+ PCF50633_MBCC7_BATSYSIMAX_1A6 = 0x00,
+ PCF50633_MBCC7_BATSYSIMAX_1A8 = 0x40,
+ PCF50633_MBCC7_BATSYSIMAX_2A0 = 0x80,
+ PCF50633_MBCC7_BATSYSIMAX_2A2 = 0xc0,
+};
+#define PCF50633_MBCC7_USB_MASK 0x03
+
+enum pcf50633_reg_mbcc8 {
+ PCF50633_MBCC8_USBENASUS = 0x10,
+};
+
+enum pcf50633_reg_mbcs1 {
+ PCF50633_MBCS1_USBPRES = 0x01,
+ PCF50633_MBCS1_USBOK = 0x02,
+ PCF50633_MBCS1_ADAPTPRES = 0x04,
+ PCF50633_MBCS1_ADAPTOK = 0x08,
+ PCF50633_MBCS1_TBAT_OK = 0x00,
+ PCF50633_MBCS1_TBAT_ABOVE = 0x10,
+ PCF50633_MBCS1_TBAT_BELOW = 0x20,
+ PCF50633_MBCS1_TBAT_UNDEF = 0x30,
+ PCF50633_MBCS1_PREWDTEXP = 0x40,
+ PCF50633_MBCS1_WDTEXP = 0x80,
+};
+
+enum pcf50633_reg_mbcs2_mbcmod {
+ PCF50633_MBCS2_MBC_PLAY = 0x00,
+ PCF50633_MBCS2_MBC_USB_PRE = 0x01,
+ PCF50633_MBCS2_MBC_USB_PRE_WAIT = 0x02,
+ PCF50633_MBCS2_MBC_USB_FAST = 0x03,
+ PCF50633_MBCS2_MBC_USB_FAST_WAIT = 0x04,
+ PCF50633_MBCS2_MBC_USB_SUSPEND = 0x05,
+ PCF50633_MBCS2_MBC_ADP_PRE = 0x06,
+ PCF50633_MBCS2_MBC_ADP_PRE_WAIT = 0x07,
+ PCF50633_MBCS2_MBC_ADP_FAST = 0x08,
+ PCF50633_MBCS2_MBC_ADP_FAST_WAIT = 0x09,
+ PCF50633_MBCS2_MBC_BAT_FULL = 0x0a,
+ PCF50633_MBCS2_MBC_HALT = 0x0b,
+};
+#define PCF50633_MBCS2_MBC_MASK 0x0f
+enum pcf50633_reg_mbcs2_chgstat {
+ PCF50633_MBCS2_CHGS_NONE = 0x00,
+ PCF50633_MBCS2_CHGS_ADAPTER = 0x10,
+ PCF50633_MBCS2_CHGS_USB = 0x20,
+ PCF50633_MBCS2_CHGS_BOTH = 0x30,
+};
+#define PCF50633_MBCS2_RESSTAT_AUTO 0x40
+
+enum pcf50633_reg_mbcs3 {
+ PCF50633_MBCS3_USBLIM_PLAY = 0x01,
+ PCF50633_MBCS3_USBLIM_CGH = 0x02,
+ PCF50633_MBCS3_TLIM_PLAY = 0x04,
+ PCF50633_MBCS3_TLIM_CHG = 0x08,
+ PCF50633_MBCS3_ILIM = 0x10, /* 1: Ibat > Icutoff */
+ PCF50633_MBCS3_VLIM = 0x20, /* 1: Vbat == Vmax */
+ PCF50633_MBCS3_VBATSTAT = 0x40, /* 1: Vbat > Vbatcond */
+ PCF50633_MBCS3_VRES = 0x80, /* 1: Vbat > Vth(RES) */
+};
+
+#define PCF50633_MBCC2_VBATCOND_MASK 0x03
+#define PCF50633_MBCC2_VMAX_MASK 0x3c
+
+struct pcf50633;
+
+void pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma);
+
+struct pcf50633_mbc {
+ int adapter_active;
+ int adapter_online;
+ int usb_active;
+ int usb_online;
+
+ struct power_supply usb;
+ struct power_supply adapter;
+
+ struct platform_device *pdev;
+};
+#endif
+
diff --git a/include/linux/mfd/pcf50633/pmic.h b/include/linux/mfd/pcf50633/pmic.h
new file mode 100644
index 0000000..395da7c
--- /dev/null
+++ b/include/linux/mfd/pcf50633/pmic.h
@@ -0,0 +1,75 @@
+#ifndef __LINUX_MFD_PCF50633_PMIC_H
+#define __LINUX_MFD_PCF50633_PMIC_H
+
+#include <linux/platform_device.h>
+
+#define PCF50633_REG_AUTOOUT 0x1a
+#define PCF50633_REG_AUTOENA 0x1b
+#define PCF50633_REG_AUTOCTL 0x1c
+#define PCF50633_REG_AUTOMXC 0x1d
+#define PCF50633_REG_DOWN1OUT 0x1e
+#define PCF50633_REG_DOWN1ENA 0x1f
+#define PCF50633_REG_DOWN1CTL 0x20
+#define PCF50633_REG_DOWN1MXC 0x21
+#define PCF50633_REG_DOWN2OUT 0x22
+#define PCF50633_REG_DOWN2ENA 0x23
+#define PCF50633_REG_DOWN2CTL 0x24
+#define PCF50633_REG_DOWN2MXC 0x25
+#define PCF50633_REG_MEMLDOOUT 0x26
+#define PCF50633_REG_MEMLDOENA 0x27
+#define PCF50633_REG_LDO1OUT 0x2d
+#define PCF50633_REG_LDO1ENA 0x2e
+#define PCF50633_REG_LDO2OUT 0x2f
+#define PCF50633_REG_LDO2ENA 0x30
+#define PCF50633_REG_LDO3OUT 0x31
+#define PCF50633_REG_LDO3ENA 0x32
+#define PCF50633_REG_LDO4OUT 0x33
+#define PCF50633_REG_LDO4ENA 0x34
+#define PCF50633_REG_LDO5OUT 0x35
+#define PCF50633_REG_LDO5ENA 0x36
+#define PCF50633_REG_LDO6OUT 0x37
+#define PCF50633_REG_LDO6ENA 0x38
+#define PCF50633_REG_HCLDOOUT 0x39
+#define PCF50633_REG_HCLDOENA 0x3a
+#define PCF50633_REG_HCLDOOVL 0x40
+
+enum pcf50633_regulator_enable {
+ PCF50633_REGULATOR_ON = 0x01,
+ PCF50633_REGULATOR_ON_GPIO1 = 0x02,
+ PCF50633_REGULATOR_ON_GPIO2 = 0x04,
+ PCF50633_REGULATOR_ON_GPIO3 = 0x08,
+};
+#define PCF50633_REGULATOR_ON_MASK 0x0f
+
+enum pcf50633_regulator_phase {
+ PCF50633_REGULATOR_ACTPH1 = 0x00,
+ PCF50633_REGULATOR_ACTPH2 = 0x10,
+ PCF50633_REGULATOR_ACTPH3 = 0x20,
+ PCF50633_REGULATOR_ACTPH4 = 0x30,
+};
+#define PCF50633_REGULATOR_ACTPH_MASK 0x30
+
+
+enum pcf50633_regulator_id {
+ PCF50633_REGULATOR_AUTO,
+ PCF50633_REGULATOR_DOWN1,
+ PCF50633_REGULATOR_DOWN2,
+ PCF50633_REGULATOR_LDO1,
+ PCF50633_REGULATOR_LDO2,
+ PCF50633_REGULATOR_LDO3,
+ PCF50633_REGULATOR_LDO4,
+ PCF50633_REGULATOR_LDO5,
+ PCF50633_REGULATOR_LDO6,
+ PCF50633_REGULATOR_HCLDO,
+ PCF50633_REGULATOR_MEMLDO,
+
+ PCF50633_NUM_REGULATORS
+};
+
+extern const u8 pcf50633_regulator_registers[];
+
+struct pcf50633_pmic {
+ struct platform_device *pdev[PCF50633_NUM_REGULATORS];
+};
+#endif
+
diff --git a/include/linux/mfd/pcf50633/rtc.h b/include/linux/mfd/pcf50633/rtc.h
new file mode 100644
index 0000000..ce8ad8f
--- /dev/null
+++ b/include/linux/mfd/pcf50633/rtc.h
@@ -0,0 +1,43 @@
+/*
+ * rtc.h -- RTC driver for NXP PCF50633
+ *
+ * (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_PCF50633_RTC_H
+#define __LINUX_MFD_PCF50633_RTC_H
+
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+
+#define PCF50633_REG_RTCSC 0x59 /* Second */
+#define PCF50633_REG_RTCMN 0x5a /* Minute */
+#define PCF50633_REG_RTCHR 0x5b /* Hour */
+#define PCF50633_REG_RTCWD 0x5c /* Weekday */
+#define PCF50633_REG_RTCDT 0x5d /* Day */
+#define PCF50633_REG_RTCMT 0x5e /* Month */
+#define PCF50633_REG_RTCYR 0x5f /* Year */
+#define PCF50633_REG_RTCSCA 0x60 /* Alarm Second */
+#define PCF50633_REG_RTCMNA 0x61 /* Alarm Minute */
+#define PCF50633_REG_RTCHRA 0x62 /* Alarm Hour */
+#define PCF50633_REG_RTCWDA 0x63 /* Alarm Weekday */
+#define PCF50633_REG_RTCDTA 0x64 /* Alarm Day */
+#define PCF50633_REG_RTCMTA 0x65 /* Alarm Month */
+#define PCF50633_REG_RTCYRA 0x66 /* Alarm Year */
+
+struct pcf50633_rtc {
+ int alarm_enabled;
+ int second_enabled;
+
+ struct rtc_device *rtc_dev;
+ struct platform_device *pdev;
+};
+
+#endif
+

2008-12-14 11:37:06

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 3/7] mfd: PCF50633 gpio support

What the PCF05633 calls as a 'GPIO' is much more than the GPIO in the linux
sense and there are only 4 of them - which means, the gpiolib is not used
here.

Signed-off-by: Balaji Rao <[email protected]>
Cc: Andy Green <[email protected]>
Cc: Samuel Ortiz <[email protected]>
---
drivers/mfd/Kconfig | 7 ++++
drivers/mfd/Makefile | 1 +
drivers/mfd/pcf50633-gpio.c | 86 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 94 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/pcf50633-gpio.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 02e3d73..cdb178b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -169,6 +169,13 @@ config PCF50633_ADC
Say yes here if you want to include support for ADC in the
NXP PCF50633 chip.

+config PCF50633_GPIO
+ tristate "Support for NXP PCF50633 GPIO"
+ depends on MFD_PCF50633
+ help
+ Say yes here if you want to include support GPIO for pins on
+ the PCF50633 chip.
+
endmenu

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 7a12b09..5bc7c9f 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_PMIC_DA903X) += da903x.o

obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o
obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o
+obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o
diff --git a/drivers/mfd/pcf50633-gpio.c b/drivers/mfd/pcf50633-gpio.c
new file mode 100644
index 0000000..79cab89
--- /dev/null
+++ b/drivers/mfd/pcf50633-gpio.c
@@ -0,0 +1,86 @@
+/* NXP PCF50633 GPIO Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <[email protected]>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 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 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 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/gpio.h>
+#include <linux/mfd/pcf50633/pmic.h>
+
+int pcf50633_gpio_set(struct pcf50633 *pcf, int gpio, u8 val)
+{
+ u8 reg;
+
+ reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
+
+ return pcf50633_reg_set_bit_mask(pcf, reg, 0x07, val);
+}
+EXPORT_SYMBOL_GPL(pcf50633_gpio_set);
+
+u8 pcf50633_gpio_get(struct pcf50633 *pcf, int gpio)
+{
+ u8 reg, val;
+
+ reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
+ val = pcf50633_reg_read(pcf, reg) & 0x07;
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(pcf50633_gpio_get);
+
+int pcf50633_gpio_invert_set(struct pcf50633 *pcf, int gpio, int invert)
+{
+ u8 val, reg;
+
+ reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
+ val = !!invert << 3;
+
+ return pcf50633_reg_set_bit_mask(pcf, reg, 1 << 3, val);
+}
+EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_set);
+
+int pcf50633_gpio_invert_get(struct pcf50633 *pcf, int gpio)
+{
+ u8 reg, val;
+
+ reg = gpio - PCF50633_GPIO1 + PCF50633_REG_GPIO1CFG;
+ val = pcf50633_reg_read(pcf, reg);
+
+ return val & (1 << 3);
+}
+EXPORT_SYMBOL_GPL(pcf50633_gpio_invert_get);
+
+int pcf50633_gpio_power_supply_set(struct pcf50633 *pcf,
+ int gpio, int regulator, int on)
+{
+ u8 reg, val, mask;
+
+ /* the *ENA register is always one after the *OUT register */
+ reg = pcf50633_regulator_registers[regulator] + 1;
+
+ val = !!on << (gpio - PCF50633_GPIO1);
+ mask = 1 << (gpio - PCF50633_GPIO1);
+
+ return pcf50633_reg_set_bit_mask(pcf, reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(pcf50633_gpio_power_supply_set);

2008-12-14 11:37:35

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 5/7] power_supply: PCF50633 battery charger driver

Signed-off-by: Balaji Rao <[email protected]>
Cc: Andy Green <[email protected]>
Cc: Anton Vorontsov <[email protected]>
Cc: David Woodhouse <[email protected]>
---
drivers/power/Kconfig | 6 +
drivers/power/Makefile | 2
drivers/power/pcf50633-charger.c | 285 ++++++++++++++++++++++++++++++++++++++
3 files changed, 293 insertions(+), 0 deletions(-)
create mode 100644 drivers/power/pcf50633-charger.c

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 52f8676..04f6316 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -75,4 +75,10 @@ config BATTERY_BQ27x00
help
Say Y here to enable support for batteries with BQ27200(I2C) chip.

+config CHARGER_PCF50633
+ tristate "NXP PCF50633 MBC"
+ depends on MFD_PCF50633
+ help
+ Say Y to include support for NXP PCF50633 Main Battery Charger.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index e6f6865..62bcb76 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -24,3 +24,5 @@ obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
+
+obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c
new file mode 100644
index 0000000..d4a0de5
--- /dev/null
+++ b/drivers/power/pcf50633-charger.c
@@ -0,0 +1,285 @@
+/* NXP PCF50633 Main Battery Charger Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <[email protected]>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 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 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 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/mbc.h>
+
+void pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma)
+{
+ int ret;
+ u8 bits;
+
+ if (ma >= 1000)
+ bits = PCF50633_MBCC7_USB_1000mA;
+ else if (ma >= 500)
+ bits = PCF50633_MBCC7_USB_500mA;
+ else if (ma >= 100)
+ bits = PCF50633_MBCC7_USB_100mA;
+ else
+ bits = PCF50633_MBCC7_USB_SUSPEND;
+
+ ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
+ PCF50633_MBCC7_USB_MASK, bits);
+ if (ret)
+ dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma);
+ else
+ dev_info(pcf->dev, "usb curlim to %d mA\n", ma);
+
+ power_supply_changed(&pcf->mbc.usb);
+}
+EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set);
+
+static ssize_t
+show_chgmode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633 *pcf = dev_get_drvdata(dev);
+
+ u8 mbcs2 = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
+ u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
+
+ return sprintf(buf, "%d\n", chgmod);
+}
+static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL);
+
+static ssize_t
+show_usblim(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633 *pcf = dev_get_drvdata(dev);
+ u8 usblim = pcf50633_reg_read(pcf, PCF50633_REG_MBCC7) &
+ PCF50633_MBCC7_USB_MASK;
+ unsigned int ma;
+
+ if (usblim == PCF50633_MBCC7_USB_1000mA)
+ ma = 1000;
+ else if (usblim == PCF50633_MBCC7_USB_500mA)
+ ma = 500;
+ else if (usblim == PCF50633_MBCC7_USB_100mA)
+ ma = 100;
+ else
+ ma = 0;
+
+ return sprintf(buf, "%u\n", ma);
+}
+
+static ssize_t set_usblim(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct pcf50633 *pcf = dev_get_drvdata(dev);
+ unsigned long ma;
+ int ret;
+
+ ret = strict_strtoul(buf, 10, &ma);
+ if (ret)
+ return -EINVAL;
+
+ pcf50633_mbc_usb_curlim_set(pcf, ma);
+
+ return count;
+}
+
+static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim);
+
+static struct attribute *pcf50633_mbc_sysfs_entries[] = {
+ &dev_attr_chgmode.attr,
+ &dev_attr_usb_curlim.attr,
+ NULL,
+};
+
+static struct attribute_group mbc_attr_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = pcf50633_mbc_sysfs_entries,
+};
+
+static void
+pcf50633_mbc_irq_handler(struct pcf50633 *pcf, int irq, void *unused)
+{
+ struct pcf50633_mbc *mbc;
+
+ mbc = &pcf->mbc;
+
+ /* USB */
+ if (irq == PCF50633_IRQ_USBINS) {
+ mbc->usb_online = 1;
+ } else if (irq == PCF50633_IRQ_USBREM) {
+ mbc->usb_online = 0;
+ mbc->usb_active = 0;
+ pcf50633_mbc_usb_curlim_set(pcf, 0);
+ }
+
+ /* Adapter */
+ if (irq == PCF50633_IRQ_ADPINS) {
+ pcf->mbc.adapter_online = 1;
+ pcf->mbc.adapter_active = 1;
+ } else if (irq == PCF50633_IRQ_ADPREM) {
+ mbc->adapter_online = 0;
+ mbc->adapter_active = 0;
+ }
+
+ if (irq == PCF50633_IRQ_BATFULL) {
+ mbc->usb_active = 0;
+ mbc->adapter_active = 0;
+ }
+
+ power_supply_changed(&mbc->usb);
+ power_supply_changed(&mbc->adapter);
+
+ if (pcf->pdata->mbc_event_callback)
+ pcf->pdata->mbc_event_callback(pcf, irq);
+}
+
+static int adapter_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->adapter_online;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->usb_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[] = {
+ PCF50633_IRQ_ADPINS,
+ PCF50633_IRQ_ADPREM,
+ PCF50633_IRQ_USBINS,
+ PCF50633_IRQ_USBREM,
+ PCF50633_IRQ_BATFULL,
+ PCF50633_IRQ_CHGHALT,
+ PCF50633_IRQ_THLIMON,
+ PCF50633_IRQ_THLIMOFF,
+ PCF50633_IRQ_USBLIMON,
+ PCF50633_IRQ_USBLIMOFF,
+ PCF50633_IRQ_LOWSYS,
+ PCF50633_IRQ_LOWBAT,
+};
+
+int __init pcf50633_mbc_probe(struct platform_device *pdev)
+{
+ struct pcf50633 *pcf;
+ struct pcf50633_mbc *mbc;
+ int ret, i;
+
+ pcf = platform_get_drvdata(pdev);
+ mbc = &pcf->mbc;
+
+ /* Set up IRQ handlers */
+ for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
+ pcf->irq_handler[mbc_irq_handlers[i]] =
+ pcf50633_mbc_irq_handler;
+
+ /* Create power supplies */
+ mbc->adapter.name = "adapter";
+ mbc->adapter.type = POWER_SUPPLY_TYPE_MAINS;
+ mbc->adapter.properties = power_props;
+ mbc->adapter.num_properties = ARRAY_SIZE(power_props);
+ mbc->adapter.get_property = &adapter_get_property;
+ mbc->adapter.supplied_to = pcf->pdata->batteries;
+ mbc->adapter.num_supplicants = pcf->pdata->num_batteries;
+
+ mbc->usb.name = "usb";
+ mbc->usb.type = POWER_SUPPLY_TYPE_USB;
+ mbc->usb.properties = power_props;
+ mbc->usb.num_properties = ARRAY_SIZE(power_props);
+ mbc->usb.get_property = usb_get_property;
+ mbc->usb.supplied_to = pcf->pdata->batteries;
+ mbc->usb.num_supplicants = pcf->pdata->num_batteries;
+
+ ret = power_supply_register(&pdev->dev, &mbc->adapter);
+ if (ret)
+ dev_err(pcf->dev, "failed to register adapter\n");
+
+ ret = power_supply_register(&pdev->dev, &mbc->usb);
+ if (ret)
+ dev_err(pcf->dev, "failed to register usb\n");
+
+ return sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group);
+}
+
+static int __devexit pcf50633_mbc_remove(struct platform_device *pdev)
+{
+ struct pcf50633 *pcf;
+
+ pcf = platform_get_drvdata(pdev);
+
+ /* Remove IRQ handlers */
+ for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
+ pcf->irq_handler[mbc_irq_handlers[i]] = NULL;
+
+ return 0;
+}
+
+struct platform_driver pcf50633_mbc_driver = {
+ .driver = {
+ .name = "pcf50633-mbc",
+ },
+ .probe = pcf50633_mbc_probe,
+ .remove = __devexit_p(pcf50633_mbc_remove),
+};
+
+static int __init pcf50633_mbc_init(void)
+{
+ return platform_driver_register(&pcf50633_mbc_driver);
+}
+module_init(pcf50633_mbc_init);
+
+static void __exit pcf50633_mbc_exit(void)
+{
+ platform_driver_unregister(&pcf50633_mbc_driver);
+}
+module_exit(pcf50633_mbc_exit);
+
+MODULE_AUTHOR("Balaji Rao <[email protected]>");
+MODULE_DESCRIPTION("PCF50633 mbc driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50633-mbc");

2008-12-14 11:37:52

by Balaji Rao

[permalink] [raw]
Subject: [PATCH 7/7] regulator: PCF50633 pmic driver

Signed-off-by: Balaji Rao <[email protected]>
Cc: Andy Green <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: Mark Brown <[email protected]>
---
drivers/regulator/Kconfig | 7 +
drivers/regulator/Makefile | 1
drivers/regulator/pcf50633-regulator.c | 338 ++++++++++++++++++++++++++++++++
3 files changed, 346 insertions(+), 0 deletions(-)
create mode 100644 drivers/regulator/pcf50633-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 39360e2..e7e0cf1 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -73,4 +73,11 @@ config REGULATOR_DA903X
Say y here to support the BUCKs and LDOs regulators found on
Dialog Semiconductor DA9030/DA9034 PMIC.

+config REGULATOR_PCF50633
+ tristate "PCF50633 regulator driver"
+ depends on MFD_PCF50633
+ help
+ Say Y here to support the voltage regulators and convertors
+ on PCF50633
+
endif
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 254d40c..61b30c6 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -11,5 +11,6 @@ obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o
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

ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/pcf50633-regulator.c b/drivers/regulator/pcf50633-regulator.c
new file mode 100644
index 0000000..5277760
--- /dev/null
+++ b/drivers/regulator/pcf50633-regulator.c
@@ -0,0 +1,338 @@
+/* NXP PCF50633 PMIC Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <[email protected]>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 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.
+ *
+ * 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 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/regulator/driver.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/pmic.h>
+
+#define PCF50633_REGULATOR(_name, _id) \
+ { \
+ .name = _name, \
+ .id = _id, \
+ .ops = &pcf50633_regulator_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ }
+
+const u8 pcf50633_regulator_registers[PCF50633_NUM_REGULATORS] = {
+ [PCF50633_REGULATOR_AUTO] = PCF50633_REG_AUTOOUT,
+ [PCF50633_REGULATOR_DOWN1] = PCF50633_REG_DOWN1OUT,
+ [PCF50633_REGULATOR_DOWN2] = PCF50633_REG_DOWN2OUT,
+ [PCF50633_REGULATOR_MEMLDO] = PCF50633_REG_MEMLDOOUT,
+ [PCF50633_REGULATOR_LDO1] = PCF50633_REG_LDO1OUT,
+ [PCF50633_REGULATOR_LDO2] = PCF50633_REG_LDO2OUT,
+ [PCF50633_REGULATOR_LDO3] = PCF50633_REG_LDO3OUT,
+ [PCF50633_REGULATOR_LDO4] = PCF50633_REG_LDO4OUT,
+ [PCF50633_REGULATOR_LDO5] = PCF50633_REG_LDO5OUT,
+ [PCF50633_REGULATOR_LDO6] = PCF50633_REG_LDO6OUT,
+ [PCF50633_REGULATOR_HCLDO] = PCF50633_REG_HCLDOOUT,
+};
+
+/* Bits from voltage value */
+static u8 auto_voltage_bits(unsigned int millivolts)
+{
+ if (millivolts < 1800)
+ return 0;
+ if (millivolts > 3800)
+ return 0xff;
+
+ millivolts -= 625;
+
+ return millivolts / 25;
+}
+
+static u8 down_voltage_bits(unsigned int millivolts)
+{
+ if (millivolts < 625)
+ return 0;
+ else if (millivolts > 3000)
+ return 0xff;
+
+ millivolts -= 625;
+
+ return millivolts / 25;
+}
+
+static u8 ldo_voltage_bits(unsigned int millivolts)
+{
+ if (millivolts < 900)
+ return 0;
+ else if (millivolts > 3600)
+ return 0x1f;
+
+ millivolts -= 900;
+ return millivolts / 100;
+}
+
+/* Obtain voltage value from bits */
+
+static unsigned int auto_voltage_value(u8 bits)
+{
+ if (bits < 0x2f)
+ return 0;
+
+ return 625 + (bits * 25);
+}
+
+
+static unsigned int down_voltage_value(u8 bits)
+{
+ return 625 + (bits * 25);
+}
+
+
+static unsigned int ldo_voltage_value(u8 bits)
+{
+ bits &= 0x1f;
+
+ return 900 + (bits * 100);
+}
+
+static int pcf50633_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ struct pcf50633 *pcf;
+ int regulator_id, millivolts;
+ u8 volt_bits, regnr;
+
+ pcf = rdev_get_drvdata(rdev);
+
+ regulator_id = rdev_get_id(rdev);
+ if (regulator_id >= PCF50633_NUM_REGULATORS)
+ return -EINVAL;
+
+ millivolts = min_uV / 1000;
+
+ regnr = pcf50633_regulator_registers[regulator_id];
+
+ switch (regulator_id) {
+ case PCF50633_REGULATOR_AUTO:
+ volt_bits = auto_voltage_bits(millivolts);
+ break;
+ case PCF50633_REGULATOR_DOWN1:
+ volt_bits = down_voltage_bits(millivolts);
+ break;
+ case PCF50633_REGULATOR_DOWN2:
+ volt_bits = down_voltage_bits(millivolts);
+ break;
+ case PCF50633_REGULATOR_LDO1:
+ case PCF50633_REGULATOR_LDO2:
+ case PCF50633_REGULATOR_LDO3:
+ case PCF50633_REGULATOR_LDO4:
+ case PCF50633_REGULATOR_LDO5:
+ case PCF50633_REGULATOR_LDO6:
+ case PCF50633_REGULATOR_HCLDO:
+ volt_bits = ldo_voltage_bits(millivolts);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return pcf50633_reg_write(pcf, regnr, volt_bits);
+}
+
+static int pcf50633_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct pcf50633 *pcf;
+ int regulator_id, millivolts, volt_bits;
+ u8 regnr;
+
+ pcf = rdev_get_drvdata(rdev);;
+
+ regulator_id = rdev_get_id(rdev);
+ if (regulator_id >= PCF50633_NUM_REGULATORS)
+ return -EINVAL;
+
+ regnr = pcf50633_regulator_registers[regulator_id];
+
+ volt_bits = pcf50633_reg_read(pcf, regnr);
+ if (volt_bits < 0)
+ return -1;
+
+ switch (regulator_id) {
+ case PCF50633_REGULATOR_AUTO:
+ millivolts = auto_voltage_value(volt_bits);
+ break;
+ case PCF50633_REGULATOR_DOWN1:
+ millivolts = down_voltage_value(volt_bits);
+ break;
+ case PCF50633_REGULATOR_DOWN2:
+ millivolts = down_voltage_value(volt_bits);
+ break;
+ case PCF50633_REGULATOR_LDO1:
+ case PCF50633_REGULATOR_LDO2:
+ case PCF50633_REGULATOR_LDO3:
+ case PCF50633_REGULATOR_LDO4:
+ case PCF50633_REGULATOR_LDO5:
+ case PCF50633_REGULATOR_LDO6:
+ case PCF50633_REGULATOR_HCLDO:
+ millivolts = ldo_voltage_value(volt_bits);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return millivolts * 1000;
+}
+
+static int pcf50633_regulator_enable(struct regulator_dev *rdev)
+{
+ struct pcf50633 *pcf = rdev_get_drvdata(rdev);
+ int regulator_id;
+ u8 regnr;
+
+ regulator_id = rdev_get_id(rdev);
+ if (regulator_id >= PCF50633_NUM_REGULATORS)
+ return -EINVAL;
+
+ /* The *ENA register is always one after the *OUT register */
+ regnr = pcf50633_regulator_registers[regulator_id] + 1;
+
+ return pcf50633_reg_set_bit_mask(pcf, regnr, PCF50633_REGULATOR_ON,
+ PCF50633_REGULATOR_ON);
+
+}
+
+static int pcf50633_regulator_disable(struct regulator_dev *rdev)
+{
+ struct pcf50633 *pcf = rdev_get_drvdata(rdev);
+ int regulator_id;
+ u8 regnr;
+
+ regulator_id = rdev_get_id(rdev);
+ if (regulator_id >= PCF50633_NUM_REGULATORS)
+ return -EINVAL;
+
+ /* the *ENA register is always one after the *OUT register */
+ regnr = pcf50633_regulator_registers[regulator_id] + 1;
+
+ return pcf50633_reg_set_bit_mask(pcf, regnr,
+ PCF50633_REGULATOR_ON, 0);
+}
+
+static int pcf50633_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct pcf50633 *pcf = rdev_get_drvdata(rdev);
+ int regulator_id = rdev_get_id(rdev);
+ u8 regnr;
+
+ regulator_id = rdev_get_id(rdev);
+ if (regulator_id >= PCF50633_NUM_REGULATORS)
+ return -EINVAL;
+
+ /* the *ENA register is always one after the *OUT register */
+ regnr = pcf50633_regulator_registers[regulator_id] + 1;
+
+ return pcf50633_reg_read(pcf, regnr) & PCF50633_REGULATOR_ON;
+}
+
+struct regulator_ops pcf50633_regulator_ops = {
+ .set_voltage = pcf50633_regulator_set_voltage,
+ .get_voltage = pcf50633_regulator_get_voltage,
+ .enable = pcf50633_regulator_enable,
+ .disable = pcf50633_regulator_disable,
+ .is_enabled = pcf50633_regulator_is_enabled,
+ .set_suspend_enable = pcf50633_regulator_enable,
+ .set_suspend_disable = pcf50633_regulator_disable,
+};
+
+static struct regulator_desc regulators[] = {
+ [PCF50633_REGULATOR_AUTO] =
+ PCF50633_REGULATOR("auto", PCF50633_REGULATOR_AUTO),
+ [PCF50633_REGULATOR_DOWN1] =
+ PCF50633_REGULATOR("down1", PCF50633_REGULATOR_DOWN1),
+ [PCF50633_REGULATOR_DOWN2] =
+ PCF50633_REGULATOR("down2", PCF50633_REGULATOR_DOWN2),
+ [PCF50633_REGULATOR_LDO1] =
+ PCF50633_REGULATOR("ldo1", PCF50633_REGULATOR_LDO1),
+ [PCF50633_REGULATOR_LDO2] =
+ PCF50633_REGULATOR("ldo2", PCF50633_REGULATOR_LDO2),
+ [PCF50633_REGULATOR_LDO3] =
+ PCF50633_REGULATOR("ldo3", PCF50633_REGULATOR_LDO3),
+ [PCF50633_REGULATOR_LDO4] =
+ PCF50633_REGULATOR("ldo4", PCF50633_REGULATOR_LDO4),
+ [PCF50633_REGULATOR_LDO5] =
+ PCF50633_REGULATOR("ldo5", PCF50633_REGULATOR_LDO5),
+ [PCF50633_REGULATOR_LDO6] =
+ PCF50633_REGULATOR("ldo6", PCF50633_REGULATOR_LDO6),
+ [PCF50633_REGULATOR_HCLDO] =
+ PCF50633_REGULATOR("hcldo", PCF50633_REGULATOR_HCLDO),
+ [PCF50633_REGULATOR_MEMLDO] =
+ PCF50633_REGULATOR("memldo", PCF50633_REGULATOR_MEMLDO),
+};
+
+int __init pcf50633_regulator_probe(struct platform_device *pdev)
+{
+ struct regulator_dev *rdev;
+ struct pcf50633 *pcf;
+
+ pcf = pdev->dev.driver_data;
+
+ 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 pcf50633_regulator_remove(struct platform_device *pdev)
+{
+ struct regulator_dev *rdev = platform_get_drvdata(pdev);
+
+ regulator_unregister(rdev);
+
+ return 0;
+}
+
+struct platform_driver pcf50633_regulator_driver = {
+ .driver = {
+ .name = "pcf50633-regltr",
+ },
+ .probe = pcf50633_regulator_probe,
+ .remove = __devexit_p(pcf50633_regulator_remove),
+};
+
+static int __init pcf50633_regulator_init(void)
+{
+ return platform_driver_register(&pcf50633_regulator_driver);
+}
+module_init(pcf50633_regulator_init);
+
+static void __exit pcf50633_regulator_exit(void)
+{
+ platform_driver_unregister(&pcf50633_regulator_driver);
+}
+module_exit(pcf50633_regulator_exit);
+
+MODULE_AUTHOR("Balaji Rao <[email protected]>");
+MODULE_DESCRIPTION("PCF50633 regulator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50633-regulator");

2008-12-14 19:30:35

by Alessandro Zummo

[permalink] [raw]
Subject: Re: [PATCH 4/7] rtc: PCF50633 rtc driver

On Sun, 14 Dec 2008 16:33:05 +0530
Balaji Rao <[email protected]> wrote:

Hello,

first review below. Please always add the rtc-linux mailing
list in cc so that patchwork[1] can track your submission.

[1]
http://patchwork.ozlabs.org/project/rtc-linux/list/?state=*

> Signed-off-by: Balaji Rao <[email protected]>
> Cc: Andy Green <[email protected]>
> Cc: Alessandro Zummo <[email protected]>
> Cc: Paul Gortmaker <[email protected]>
> ---
> drivers/rtc/Kconfig | 6 +
> drivers/rtc/Makefile | 1
> drivers/rtc/rtc-pcf50633.c | 302 ++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 309 insertions(+), 0 deletions(-)
> create mode 100644 drivers/rtc/rtc-pcf50633.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 123092d..68e68d2 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -497,6 +497,12 @@ config RTC_DRV_WM8350
> This driver can also be built as a module. If so, the module
> will be called "rtc-wm8350".
>
> +config RTC_DRV_PCF50633
> + depends on MFD_PCF50633
> + tristate "NXP PCF50633 RTC"
> + help
> + If you say yes here you get support for the NXP PCF50633 RTC.
> +
> comment "on-CPU RTC drivers"
>
> config RTC_DRV_OMAP
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 6e79c91..a717fec 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -70,3 +70,4 @@ obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
> obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
> obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
> obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
> +obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
> diff --git a/drivers/rtc/rtc-pcf50633.c b/drivers/rtc/rtc-pcf50633.c
> new file mode 100644
> index 0000000..f314810
> --- /dev/null
> +++ b/drivers/rtc/rtc-pcf50633.c
> @@ -0,0 +1,302 @@
> +/* NXP PCF50633 RTC Driver
> + *
> + * (C) 2006-2008 by Openmoko, Inc.
> + * Author: Balaji Rao <[email protected]>
> + * All rights reserved.
> + *
> + * Broken down from monstrous PCF50633 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 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 330, Boston,
> + * MA 02111-1307 USA

I believe the shorter form of the GPL could be good as well.

> + */
> +
> +#include <linux/rtc.h>
> +#include <linux/platform_device.h>
> +#include <linux/bcd.h>
> +
> +#include <linux/mfd/pcf50633/core.h>

> +#include <linux/mfd/pcf50633/rtc.h>

this file should be included with the patch.


> +
> +enum pcf50633_time_indexes {
> + PCF50633_TI_SEC,
> + PCF50633_TI_MIN,
> + PCF50633_TI_HOUR,
> + PCF50633_TI_WKDAY,
> + PCF50633_TI_DAY,
> + PCF50633_TI_MONTH,
> + PCF50633_TI_YEAR,
> + PCF50633_TI_EXTENT /* always last */
> +};
> +
> +
> +struct pcf50633_time {
> + u_int8_t time[PCF50633_TI_EXTENT];
> +};
> +
> +static void pcf2rtc_time(struct rtc_time *rtc, struct pcf50633_time *pcf)
> +{
> + rtc->tm_sec = bcd2bin(pcf->time[PCF50633_TI_SEC]);
> + rtc->tm_min = bcd2bin(pcf->time[PCF50633_TI_MIN]);
> + rtc->tm_hour = bcd2bin(pcf->time[PCF50633_TI_HOUR]);
> + rtc->tm_wday = bcd2bin(pcf->time[PCF50633_TI_WKDAY]);
> + rtc->tm_mday = bcd2bin(pcf->time[PCF50633_TI_DAY]);
> + rtc->tm_mon = bcd2bin(pcf->time[PCF50633_TI_MONTH]);
> + rtc->tm_year = bcd2bin(pcf->time[PCF50633_TI_YEAR]) + 100;
> +}
> +
> +static void rtc2pcf_time(struct pcf50633_time *pcf, struct rtc_time *rtc)
> +{
> + pcf->time[PCF50633_TI_SEC] = bin2bcd(rtc->tm_sec);
> + pcf->time[PCF50633_TI_MIN] = bin2bcd(rtc->tm_min);
> + pcf->time[PCF50633_TI_HOUR] = bin2bcd(rtc->tm_hour);
> + pcf->time[PCF50633_TI_WKDAY] = bin2bcd(rtc->tm_wday);
> + pcf->time[PCF50633_TI_DAY] = bin2bcd(rtc->tm_mday);
> + pcf->time[PCF50633_TI_MONTH] = bin2bcd(rtc->tm_mon);
> + pcf->time[PCF50633_TI_YEAR] = bin2bcd(rtc->tm_year - 100);

you should add a check in the caller for tm_year < 100

> +}
> +
> +static int
> +pcf50633_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
> +{
> + struct pcf50633 *pcf;
> +
> + pcf = dev_get_drvdata(dev);

this could be an one-liner (not mandatory).


> + switch (cmd) {
> + case RTC_AIE_OFF:
> + pcf->rtc.alarm_enabled = 0;
> + pcf50633_irq_mask(pcf, PCF50633_IRQ_ALARM);
> + return 0;
> + case RTC_AIE_ON:
> + pcf->rtc.alarm_enabled = 1;
> + pcf50633_irq_unmask(pcf, PCF50633_IRQ_ALARM);
> + return 0;
> + case RTC_PIE_OFF:
> + pcf->rtc.second_enabled = 0;
> + pcf50633_irq_mask(pcf, PCF50633_IRQ_SECOND);
> + return 0;
> + case RTC_PIE_ON:
> + pcf->rtc.second_enabled = 1;
> + pcf50633_irq_unmask(pcf, PCF50633_IRQ_SECOND);
> + return 0;
> + }

we have recently improved the API for interrupts handling.
the patch is now in -mm and you can check it here:
http://patchwork.ozlabs.org/patch/10039/

that involves AIE and UIE.

the API for PIE was always there and it's implemented by ops->irq_set_state
and ops->irq_set_freq

Is your PIE a real PIE or an UIE?


> + return -ENOIOCTLCMD;
> +}
> +
> +static int pcf50633_rtc_read_time(struct device *dev, struct rtc_time *tm)
> +{
> + struct pcf50633 *pcf;
> + struct pcf50633_time pcf_tm;
> + int ret;
> +
> + pcf = dev_get_drvdata(dev);
> +
> + ret = pcf50633_read_block(pcf, PCF50633_REG_RTCSC,
> + PCF50633_TI_EXTENT,
> + &pcf_tm.time[0]);
> + if (ret != PCF50633_TI_EXTENT)
> + dev_err(dev, "Failed to read time\n");

so return -EIO or something to that effect.

> + dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
> + pcf_tm.time[PCF50633_TI_DAY],
> + pcf_tm.time[PCF50633_TI_MONTH],
> + pcf_tm.time[PCF50633_TI_YEAR],
> + pcf_tm.time[PCF50633_TI_HOUR],
> + pcf_tm.time[PCF50633_TI_MIN],
> + pcf_tm.time[PCF50633_TI_SEC]);
> +
> + pcf2rtc_time(tm, &pcf_tm);
> +
> + 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);
> +
> + return 0;

nope. always return rtc_valid_tm(tm);

> +}
> +
> +static int pcf50633_rtc_set_time(struct device *dev, struct rtc_time *tm)
> +{
> + struct pcf50633 *pcf;
> + struct pcf50633_time pcf_tm;
> + int second_masked, alarm_masked, ret = 0;
> +
> + pcf = 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[PCF50633_TI_DAY],
> + pcf_tm.time[PCF50633_TI_MONTH],
> + pcf_tm.time[PCF50633_TI_YEAR],
> + pcf_tm.time[PCF50633_TI_HOUR],
> + pcf_tm.time[PCF50633_TI_MIN],
> + pcf_tm.time[PCF50633_TI_SEC]);
> +
> +
> + second_masked = pcf50633_irq_mask_get(pcf, PCF50633_IRQ_SECOND);
> + alarm_masked = pcf50633_irq_mask_get(pcf, PCF50633_IRQ_ALARM);
> +
> + if (!second_masked)
> + pcf50633_irq_mask(pcf, PCF50633_IRQ_SECOND);
> + if (!alarm_masked)
> + pcf50633_irq_mask(pcf, PCF50633_IRQ_ALARM);
> +
> + ret = pcf50633_write_block(pcf, PCF50633_REG_RTCSC,
> + PCF50633_TI_EXTENT,
> + &pcf_tm.time[0]);
> + if (ret)
> + dev_err(dev, "Failed to set time %d\n", ret);
> +
> + if (!second_masked)
> + pcf50633_irq_unmask(pcf, PCF50633_IRQ_SECOND);
> + if (!alarm_masked)
> + pcf50633_irq_unmask(pcf, PCF50633_IRQ_ALARM);
> +
> + return ret;

is this ret an appropriate error code?

> +}
> +
> +static int pcf50633_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + struct pcf50633 *pcf;
> + struct pcf50633_time pcf_tm;
> + int ret = 0;
> +
> + pcf = dev_get_drvdata(dev);
> +
> + alrm->enabled = pcf->rtc.alarm_enabled;
> +
> + ret = pcf50633_read_block(pcf, PCF50633_REG_RTCSCA,
> + PCF50633_TI_EXTENT, &pcf_tm.time[0]);
> +
> + if (ret != PCF50633_TI_EXTENT)
> + dev_err(dev, "Failed to read Alarm time %d\n", ret);
> +
> + pcf2rtc_time(&alrm->time, &pcf_tm);
> +
> + return ret;

probably wrong, ret must be 0 on success.

> +}
> +
> +static int pcf50633_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + struct pcf50633 *pcf;
> + struct pcf50633_time pcf_tm;
> + int alarm_masked, ret = 0;
> +
> + pcf = dev_get_drvdata(dev);
> +
> + rtc2pcf_time(&pcf_tm, &alrm->time);
> +
> + /* do like mktime does and ignore tm_wday */
> + pcf_tm.time[PCF50633_TI_WKDAY] = 7;
> +
> + alarm_masked = pcf50633_irq_mask_get(pcf, PCF50633_IRQ_ALARM);
> +
> + /* disable alarm interrupt */
> + if (!alarm_masked)
> + pcf50633_irq_mask(pcf, PCF50633_IRQ_ALARM);
> +
> + ret = pcf50633_write_block(pcf, PCF50633_REG_RTCSCA,
> + PCF50633_TI_EXTENT, &pcf_tm.time[0]);
> + if (ret)
> + dev_err(dev, "Failed to write alarm time %d\n", ret);
> +
> + if (!alarm_masked)
> + pcf50633_irq_unmask(pcf, PCF50633_IRQ_ALARM);
> +
> + return ret;

ditto?

> +}
> +static struct rtc_class_ops pcf50633_rtc_ops = {
> + .ioctl = pcf50633_rtc_ioctl,
> + .read_time = pcf50633_rtc_read_time,
> + .set_time = pcf50633_rtc_set_time,
> + .read_alarm = pcf50633_rtc_read_alarm,
> + .set_alarm = pcf50633_rtc_set_alarm,
> +};
> +
> +static void pcf50633_rtc_irq(struct pcf50633 *pcf, int irq, void *unused)
> +{
> + switch (irq) {
> + case PCF50633_IRQ_ALARM:
> + rtc_update_irq(pcf->rtc.rtc_dev, 1, RTC_AF | RTC_IRQF);
> + break;
> + case PCF50633_IRQ_SECOND:
> + rtc_update_irq(pcf->rtc.rtc_dev, 1, RTC_PF | RTC_IRQF);
> + break;
> + }
> +}
> +
> +static int pcf50633_rtc_probe(struct platform_device *pdev)
> +{
> + struct rtc_device *rtc;
> + struct pcf50633 *pcf;
> +
> + rtc = rtc_device_register("pcf50633-rtc", &pdev->dev,
> + &pcf50633_rtc_ops, THIS_MODULE);
> + if (IS_ERR(rtc))
> + return -ENODEV;

nope. if IS_ERR means that the rtc pointer has a valid error
code that you should return to the caller.

> + pcf = platform_get_drvdata(pdev);

uh? where did you set up the pointer?


> + /* Set up IRQ handlers */
> + pcf->irq_handler[PCF50633_IRQ_ALARM].handler = pcf50633_rtc_irq;
> + pcf->irq_handler[PCF50633_IRQ_SECOND].handler = pcf50633_rtc_irq;
> +
> + pcf->rtc.rtc_dev = rtc;

??

> + return 0;
> +}
> +
> +static int pcf50633_rtc_remove(struct platform_device *pdev)
> +{
> + struct pcf50633 *pcf;
> +
> + pcf = platform_get_drvdata(pdev);
> + rtc_device_unregister(pcf->rtc.rtc_dev);
> +
> + return 0;
> +}
> +
> +
> +static struct platform_driver pcf50633_rtc_driver = {
> + .driver = {
> + .name = "pcf50633-rtc",
> + },
> + .probe = pcf50633_rtc_probe,
> + .remove = __devexit_p(pcf50633_rtc_remove),

you marked __devexit_p but forgot to mark the function
itself.

> +};
> +
> +static int __init pcf50633_rtc_init(void)
> +{
> + return platform_driver_register(&pcf50633_rtc_driver);

can't you use platform_driver_probe ?

> +}
> +module_init(pcf50633_rtc_init);
> +
> +static void __exit pcf50633_rtc_exit(void)
> +{
> + platform_driver_unregister(&pcf50633_rtc_driver);
> +}
> +module_exit(pcf50633_rtc_exit);
> +
> +
> +MODULE_DESCRIPTION("PCF50633 RTC driver");
> +MODULE_AUTHOR("Balaji Rao <[email protected]>");
> +MODULE_LICENSE("GPL");
> +
>


--

Best regards,

Alessandro Zummo,
Tower Technologies - Torino, Italy

http://www.towertech.it

2008-12-14 22:05:01

by Balaji Rao

[permalink] [raw]
Subject: Re: [PATCH 4/7] rtc: PCF50633 rtc driver

On Sun, Dec 14, 2008 at 08:29:56PM +0100, Alessandro Zummo wrote:
> On Sun, 14 Dec 2008 16:33:05 +0530
> Balaji Rao <[email protected]> wrote:
>
> Hello,
>
> first review below. Please always add the rtc-linux mailing
> list in cc so that patchwork[1] can track your submission.
>
> [1]
> http://patchwork.ozlabs.org/project/rtc-linux/list/?state=*
>

OK, noted,

> > +#include <linux/bcd.h>
> > +
> > +#include <linux/mfd/pcf50633/core.h>
>
> > +#include <linux/mfd/pcf50633/rtc.h>
>
> this file should be included with the patch.
>

Hmm. This patch is included in [PATCH 1/7] of the series - which
implements the core driver. This core driver needs this file to compile
and not including there is going to break the bisectability of the
series. Isn't this what I'm supposed to do ? Please correct me if I'm
wrong.

> > +static void rtc2pcf_time(struct pcf50633_time *pcf, struct rtc_time *rtc)
> > +{
> > + pcf->time[PCF50633_TI_SEC] = bin2bcd(rtc->tm_sec);
> > + pcf->time[PCF50633_TI_MIN] = bin2bcd(rtc->tm_min);
> > + pcf->time[PCF50633_TI_HOUR] = bin2bcd(rtc->tm_hour);
> > + pcf->time[PCF50633_TI_WKDAY] = bin2bcd(rtc->tm_wday);
> > + pcf->time[PCF50633_TI_DAY] = bin2bcd(rtc->tm_mday);
> > + pcf->time[PCF50633_TI_MONTH] = bin2bcd(rtc->tm_mon);
> > + pcf->time[PCF50633_TI_YEAR] = bin2bcd(rtc->tm_year - 100);
>
> you should add a check in the caller for tm_year < 100
>

OK.

> > + case RTC_PIE_OFF:
> > + pcf->rtc.second_enabled = 0;
> > + pcf50633_irq_mask(pcf, PCF50633_IRQ_SECOND);
> > + return 0;
> > + case RTC_PIE_ON:
> > + pcf->rtc.second_enabled = 1;
> > + pcf50633_irq_unmask(pcf, PCF50633_IRQ_SECOND);
> > + return 0;
> > + }
>
> we have recently improved the API for interrupts handling.
> the patch is now in -mm and you can check it here:
> http://patchwork.ozlabs.org/patch/10039/
>
> that involves AIE and UIE.
>
> the API for PIE was always there and it's implemented by ops->irq_set_state
> and ops->irq_set_freq
>
> Is your PIE a real PIE or an UIE?
>
OK.

Oh.. yes, it's actually an UIE! Sorry about that - will change.

> > + pcf = dev_get_drvdata(dev);
> > +
> > + ret = pcf50633_read_block(pcf, PCF50633_REG_RTCSC,
> > + PCF50633_TI_EXTENT,
> > + &pcf_tm.time[0]);
> > + if (ret != PCF50633_TI_EXTENT)
> > + dev_err(dev, "Failed to read time\n");
>
> so return -EIO or something to that effect.
>

OK.

> > + dev_dbg(dev, "PCF_TIME: %02x.%02x.%02x %02x:%02x:%02x\n",
> > + pcf_tm.time[PCF50633_TI_DAY],
> > + pcf_tm.time[PCF50633_TI_MONTH],
> > + pcf_tm.time[PCF50633_TI_YEAR],
> > + pcf_tm.time[PCF50633_TI_HOUR],
> > + pcf_tm.time[PCF50633_TI_MIN],
> > + pcf_tm.time[PCF50633_TI_SEC]);
> > +
> > + pcf2rtc_time(tm, &pcf_tm);
> > +
> > + 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);
> > +
> > + return 0;
>
> nope. always return rtc_valid_tm(tm);
>

OK.

> > +
> > + ret = pcf50633_write_block(pcf, PCF50633_REG_RTCSC,
> > + PCF50633_TI_EXTENT,
> > + &pcf_tm.time[0]);
> > + if (ret)
> > + dev_err(dev, "Failed to set time %d\n", ret);
> > +
> > + if (!second_masked)
> > + pcf50633_irq_unmask(pcf, PCF50633_IRQ_SECOND);
> > + if (!alarm_masked)
> > + pcf50633_irq_unmask(pcf, PCF50633_IRQ_ALARM);
> > +
> > + return ret;
>
> is this ret an appropriate error code?
>

Oops! No, it's wrong. Will fix.

> > + ret = pcf50633_read_block(pcf, PCF50633_REG_RTCSCA,
> > + PCF50633_TI_EXTENT, &pcf_tm.time[0]);
> > +
> > + if (ret != PCF50633_TI_EXTENT)
> > + dev_err(dev, "Failed to read Alarm time %d\n", ret);
> > +
> > + pcf2rtc_time(&alrm->time, &pcf_tm);
> > +
> > + return ret;
>
> probably wrong, ret must be 0 on success.
>

Right. Will fix.

> > + struct rtc_device *rtc;
> > + struct pcf50633 *pcf;
> > +
> > + rtc = rtc_device_register("pcf50633-rtc", &pdev->dev,
> > + &pcf50633_rtc_ops, THIS_MODULE);
> > + if (IS_ERR(rtc))
> > + return -ENODEV;
>
> nope. if IS_ERR means that the rtc pointer has a valid error
> code that you should return to the caller.
>

Fine. Will change.

> > + pcf = platform_get_drvdata(pdev);
>
> uh? where did you set up the pointer?
>
>
> > + /* Set up IRQ handlers */
> > + pcf->irq_handler[PCF50633_IRQ_ALARM].handler = pcf50633_rtc_irq;
> > + pcf->irq_handler[PCF50633_IRQ_SECOND].handler = pcf50633_rtc_irq;
> > +
> > + pcf->rtc.rtc_dev = rtc;
>
> ??
>

It's done in the core driver - [PATCH 1/7] of this series.

> > + .probe = pcf50633_rtc_probe,
> > + .remove = __devexit_p(pcf50633_rtc_remove),
>
> you marked __devexit_p but forgot to mark the function
> itself.
>

Oh! will fix.

> > +{
> > + return platform_driver_register(&pcf50633_rtc_driver);
>
> can't you use platform_driver_probe ?
>

Yes, I probably can. Will change.

Thank you for the review. Will send again after resolving the issues.

- Balaji

2008-12-14 22:10:43

by Balaji Rao

[permalink] [raw]
Subject: Re: [PATCH 5/7] power_supply: PCF50633 battery charger driver

On Sun, Dec 14, 2008 at 04:33:23PM +0530, Balaji Rao wrote:
> +
> +static int __devexit pcf50633_mbc_remove(struct platform_device *pdev)
> +{
> + struct pcf50633 *pcf;
> +
> + pcf = platform_get_drvdata(pdev);
> +
> + /* Remove IRQ handlers */
> + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
> + pcf->irq_handler[mbc_irq_handlers[i]] = NULL;
> +
> + return 0;
> +}
> +

Whoops! 'i not declared'. Oh., These last minute changes..

Will wait for more comments before sending a fixed version.

- Balaji
>

2008-12-14 22:22:00

by Alessandro Zummo

[permalink] [raw]
Subject: Re: [rtc-linux] Re: [PATCH 4/7] rtc: PCF50633 rtc driver

On Mon, 15 Dec 2008 03:34:30 +0530
Balaji Rao <[email protected]> wrote:

> Hmm. This patch is included in [PATCH 1/7] of the series - which
> implements the core driver. This core driver needs this file to compile
> and not including there is going to break the bisectability of the
> series. Isn't this what I'm supposed to do ? Please correct me if I'm
> wrong.

ok, then don't break.

> > > + pcf = platform_get_drvdata(pdev);
> >
> > uh? where did you set up the pointer?
> >
> >
> > > + /* Set up IRQ handlers */
> > > + pcf->irq_handler[PCF50633_IRQ_ALARM].handler = pcf50633_rtc_irq;
> > > + pcf->irq_handler[PCF50633_IRQ_SECOND].handler = pcf50633_rtc_irq;
> > > +
> > > + pcf->rtc.rtc_dev = rtc;
> >
> > ??
> >
>
> It's done in the core driver - [PATCH 1/7] of this series.

mm. that's strange. a platform driver is supposed to receive informational
structures (regions, irqs, ...) and init driver data by itself. you shouldn't need
to mess with the drvdata pointer.

I do not also feel fine with the irq handlers setup. Your core should export
a function for irq registration.

> Thank you for the review. Will send again after resolving the issues.

thanks for your contribution. you are doing great things at openmoko.

--

Best regards,

Alessandro Zummo,
Tower Technologies - Torino, Italy

http://www.towertech.it

2008-12-14 23:30:17

by Balaji Rao

[permalink] [raw]
Subject: Re: [rtc-linux] Re: [PATCH 4/7] rtc: PCF50633 rtc driver

On Sun, Dec 14, 2008 at 11:21:28PM +0100, Alessandro Zummo wrote:
> On Mon, 15 Dec 2008 03:34:30 +0530
> Balaji Rao <[email protected]> wrote:
>
> > Hmm. This patch is included in [PATCH 1/7] of the series - which
> > implements the core driver. This core driver needs this file to compile
> > and not including there is going to break the bisectability of the
> > series. Isn't this what I'm supposed to do ? Please correct me if I'm
> > wrong.
>
> ok, then don't break.
>

OK, thinking more about this, I realised that this series, has to be
taken in together, as rtc-pcf50633.c alone wouldn't compile for you..

Probably it could be taken into the MFD tree once you ACK this ? I don't
know.. If this is not a problem at all, please feel free to ignore me!

> > > > + pcf = platform_get_drvdata(pdev);
> > >
> > > uh? where did you set up the pointer?
> > >
> > >
> > > > + /* Set up IRQ handlers */
> > > > + pcf->irq_handler[PCF50633_IRQ_ALARM].handler = pcf50633_rtc_irq;
> > > > + pcf->irq_handler[PCF50633_IRQ_SECOND].handler = pcf50633_rtc_irq;
> > > > +
> > > > + pcf->rtc.rtc_dev = rtc;
> > >
> > > ??
> > >
> >
> > It's done in the core driver - [PATCH 1/7] of this series.
>
> mm. that's strange. a platform driver is supposed to receive informational
> structures (regions, irqs, ...) and init driver data by itself. you shouldn't need
> to mess with the drvdata pointer.
>

Oh, ok. I now see how I can do better. Will wait for comments for the
rest of the series and post a improved version.

> I do not also feel fine with the irq handlers setup. Your core should export
> a function for irq registration.
>

Yes, ok.

> > Thank you for the review. Will send again after resolving the issues.
>
> thanks for your contribution. you are doing great things at openmoko.
>

You're welcome.

- Balaji

2008-12-14 23:39:34

by Alessandro Zummo

[permalink] [raw]
Subject: Re: [rtc-linux] Re: [PATCH 4/7] rtc: PCF50633 rtc driver

On Mon, 15 Dec 2008 04:59:46 +0530
Balaji Rao <[email protected]> wrote:

> OK, thinking more about this, I realised that this series, has to be
> taken in together, as rtc-pcf50633.c alone wouldn't compile for you..
>
> Probably it could be taken into the MFD tree once you ACK this ? I don't
> know.. If this is not a problem at all, please feel free to ignore me!

Any tree is good for me

--

Best regards,

Alessandro Zummo,
Tower Technologies - Torino, Italy

http://www.towertech.it

2008-12-15 07:35:59

by Dmitry Torokhov

[permalink] [raw]
Subject: Re: [PATCH 6/7] input: PCF50633 input driver

Hi Balaji,

On Sun, Dec 14, 2008 at 04:33:41PM +0530, Balaji Rao wrote:
> Signed-off-by: Balaji Rao <[email protected]>

Looks good, some small nitpicks:

>
> +config INPUT_PCF50633_PMU
> + tristate "PCF50633 PMU events"
> + depends on MFD_PCF50633
> + help
> + Say Y to include support for input events on NXP PCF50633.
> +

I'd like to see something like ".. support for deliveing PMU events via
input layer on NXP PCF50633".

> endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index d7db2ae..bb62e6e 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -21,3 +21,4 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
> obj-$(CONFIG_INPUT_UINPUT) += uinput.o
> obj-$(CONFIG_INPUT_APANEL) += apanel.o
> obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
> +obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o
> diff --git a/drivers/input/misc/pcf50633-input.c b/drivers/input/misc/pcf50633-input.c
> new file mode 100644
> index 0000000..36ea8b2
> --- /dev/null
> +++ b/drivers/input/misc/pcf50633-input.c
> @@ -0,0 +1,119 @@
> +/* NXP PCF50633 Input Driver
> + *
> + * (C) 2006-2008 by Openmoko, Inc.
> + * Author: Balaji Rao <[email protected]>
> + * All rights reserved.
> + *
> + * Broken down from monstrous PCF50633 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 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 330, Boston,
> + * MA 02111-1307 USA
> + */
> +
> +#include <linux/input.h>
> +
> +#include <linux/mfd/pcf50633/core.h>
> +#include <linux/mfd/pcf50633/input.h>
> +
> +static void
> +pcf50633_input_irq(struct pcf50633 *pcf, int irq, void *unused)
> +{
> + struct input_dev *input_dev = pcf->input.input_dev;
> + int onkey_released;
> +
> + /* We report only one event depending on the key press status */
> + onkey_released = pcf50633_reg_read(pcf, PCF50633_REG_OOCSTAT)
> + & PCF50633_OOCSTAT_ONKEY;
> +
> + if (irq == PCF50633_IRQ_ONKEYF && !onkey_released)
> + input_report_key(input_dev, KEY_POWER, 1);
> + else if (irq == PCF50633_IRQ_ONKEYR && onkey_released)
> + input_report_key(input_dev, KEY_POWER, 0);
> +
> + input_sync(input_dev);
> +}
> +
> +int __init pcf50633_input_probe(struct platform_device *pdev)

Static? __devinit?

> +{
> + struct pcf50633 *pcf;
> + struct input_dev *input_dev;
> + int ret;
> +
> + pcf = platform_get_drvdata(pdev);
> +
> + input_dev = input_allocate_device();
> + if (!input_dev)
> + return -ENOMEM;
> +
> + input_dev->name = "PCF50633 PMU events";
> + input_dev->id.bustype = BUS_I2C;
> +
> + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_PWR);
> + set_bit(KEY_POWER, input_dev->keybit);
> +
> + ret = input_register_device(input_dev);
> + if (ret)
> + goto out;
> +
> + pcf->input.input_dev = input_dev;
> +
> + /* Set up interrupt handlers */
> + pcf->irq_handler[PCF50633_IRQ_ONKEYR].handler = pcf50633_input_irq;
> + pcf->irq_handler[PCF50633_IRQ_ONKEYF].handler = pcf50633_input_irq;
> +
> + return 0;
> +
> +out:
> + input_free_device(input_dev);
> + return ret;
> +}
> +
> +static int __devexit pcf50633_input_remove(struct platform_device *pdev)
> +{
> + struct pcf50633 *pcf;
> +
> + pcf = platform_get_drvdata(pdev);
> +
> + input_unregister_device(pcf->input.input_dev);
> + input_free_device(pcf->input.input_dev);

Don't call input_free_device() after input_unregister_device(), it will
lead to double free.

> +
> + return 0;
> +}
> +
> +struct platform_driver pcf50633_input_driver = {
> + .driver = {
> + .name = "pcf50633-input",
> + },
> + .probe = pcf50633_input_probe,
> + .remove = __devexit_p(pcf50633_input_remove),
> +};
> +
> +static int __init pcf50633_input_init(void)
> +{
> + return platform_driver_register(&pcf50633_input_driver);

Just one tab should be enough.

> +}
> +module_init(pcf50633_input_init);
> +
> +static void __exit pcf50633_input_exit(void)
> +{
> + platform_driver_unregister(&pcf50633_input_driver);

Same here.

> +}
> +module_exit(pcf50633_input_exit);
> +
> +MODULE_AUTHOR("Balaji Rao <[email protected]>");
> +MODULE_DESCRIPTION("PCF50633 input driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:pcf50633-input");
>

--
Dmitry

2008-12-15 11:35:59

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 7/7] regulator: PCF50633 pmic driver

On Sun, Dec 14, 2008 at 04:34:00PM +0530, Balaji Rao wrote:

> +struct regulator_ops pcf50633_regulator_ops = {
> + .set_voltage = pcf50633_regulator_set_voltage,
> + .get_voltage = pcf50633_regulator_get_voltage,
> + .enable = pcf50633_regulator_enable,
> + .disable = pcf50633_regulator_disable,
> + .is_enabled = pcf50633_regulator_is_enabled,
> + .set_suspend_enable = pcf50633_regulator_enable,
> + .set_suspend_disable = pcf50633_regulator_disable,
> +};

Are you sure that the suspend variants of the operations should be the
same as the regular versions?

> +struct platform_driver pcf50633_regulator_driver = {
> + .driver = {
> + .name = "pcf50633-regltr",
> + },
> + .probe = pcf50633_regulator_probe,
> + .remove = __devexit_p(pcf50633_regulator_remove),
> +};

Not that it matters too much but I have a hard time liking "regltr".

2008-12-15 14:05:20

by Balaji Rao

[permalink] [raw]
Subject: Re: [PATCH 7/7] regulator: PCF50633 pmic driver

On Mon, Dec 15, 2008 at 11:35:49AM +0000, Mark Brown wrote:
> On Sun, Dec 14, 2008 at 04:34:00PM +0530, Balaji Rao wrote:
>
> > +struct regulator_ops pcf50633_regulator_ops = {
> > + .set_voltage = pcf50633_regulator_set_voltage,
> > + .get_voltage = pcf50633_regulator_get_voltage,
> > + .enable = pcf50633_regulator_enable,
> > + .disable = pcf50633_regulator_disable,
> > + .is_enabled = pcf50633_regulator_is_enabled,
> > + .set_suspend_enable = pcf50633_regulator_enable,
> > + .set_suspend_disable = pcf50633_regulator_disable,
> > +};
>
> Are you sure that the suspend variants of the operations should be the
> same as the regular versions?
>

Yes, basically the chip doesn't change state during suspend/resume.
We just enable or disable them the usual way.

> > +struct platform_driver pcf50633_regulator_driver = {
> > + .driver = {
> > + .name = "pcf50633-regltr",
> > + },
> > + .probe = pcf50633_regulator_probe,
> > + .remove = __devexit_p(pcf50633_regulator_remove),
> > +};
>
> Not that it matters too much but I have a hard time liking "regltr".

Ha! The name pcf50633-regulator exceeds 20 chars and there is no space
remaining for the ".0" or ".1" at the end. This gave me a sysfs warning
and so I changed it.

- Balaji

2008-12-15 22:38:27

by Anton Vorontsov

[permalink] [raw]
Subject: Re: [PATCH 5/7] power_supply: PCF50633 battery charger driver

Hello Balaji,

It's great to see OpenMoko patches submitted, much thanks
for your work!

You can find few comments below.

On Sun, Dec 14, 2008 at 04:33:23PM +0530, Balaji Rao wrote:
> Signed-off-by: Balaji Rao <[email protected]>
> Cc: Andy Green <[email protected]>
> Cc: Anton Vorontsov <[email protected]>
> Cc: David Woodhouse <[email protected]>
> ---
> drivers/power/Kconfig | 6 +
> drivers/power/Makefile | 2
> drivers/power/pcf50633-charger.c | 285 ++++++++++++++++++++++++++++++++++++++

> 3 files changed, 293 insertions(+), 0 deletions(-)
> create mode 100644 drivers/power/pcf50633-charger.c
>
> diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
> index 52f8676..04f6316 100644
> --- a/drivers/power/Kconfig
> +++ b/drivers/power/Kconfig
> @@ -75,4 +75,10 @@ config BATTERY_BQ27x00
> help
> Say Y here to enable support for batteries with BQ27200(I2C) chip.
>
> +config CHARGER_PCF50633
> + tristate "NXP PCF50633 MBC"
> + depends on MFD_PCF50633
> + help
> + Say Y to include support for NXP PCF50633 Main Battery Charger.
> +
> endif # POWER_SUPPLY
> diff --git a/drivers/power/Makefile b/drivers/power/Makefile
> index e6f6865..62bcb76 100644
> --- a/drivers/power/Makefile
> +++ b/drivers/power/Makefile
> @@ -24,3 +24,5 @@ obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
> obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
> obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
> obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
> +
> +obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
> diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c
> new file mode 100644
> index 0000000..d4a0de5
> --- /dev/null
> +++ b/drivers/power/pcf50633-charger.c
> @@ -0,0 +1,285 @@
> +/* NXP PCF50633 Main Battery Charger Driver
> + *
> + * (C) 2006-2008 by Openmoko, Inc.
> + * Author: Balaji Rao <[email protected]>
> + * All rights reserved.
> + *
> + * Broken down from monstrous PCF50633 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 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 330, Boston,
> + * MA 02111-1307 USA
> + */
> +

The code should explicitly include all the needed headers.

#include <linux/kernel.h> (for container_of, sprintf, ...)
#include <linux/module.h> (MODULE_, EXPORT_SYMBOL macros)
#include <linux/init.h> (initcalls)
#include <linux/types.h> (ssize_t)
#include <linux/device.h> (dev_info e.t.c.)
#include <linux/platform_device.h>
#include <linux/power_supply.h>

> +#include <linux/mfd/pcf50633/core.h>
> +#include <linux/mfd/pcf50633/mbc.h>
> +
> +void pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma)
> +{
> + int ret;
> + u8 bits;
> +
> + if (ma >= 1000)
> + bits = PCF50633_MBCC7_USB_1000mA;
> + else if (ma >= 500)
> + bits = PCF50633_MBCC7_USB_500mA;
> + else if (ma >= 100)
> + bits = PCF50633_MBCC7_USB_100mA;
> + else
> + bits = PCF50633_MBCC7_USB_SUSPEND;
> +
> + ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
> + PCF50633_MBCC7_USB_MASK, bits);
> + if (ret)
> + dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma);
> + else
> + dev_info(pcf->dev, "usb curlim to %d mA\n", ma);
> +
> + power_supply_changed(&pcf->mbc.usb);
> +}
> +EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set);
> +
> +static ssize_t
> +show_chgmode(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct pcf50633 *pcf = dev_get_drvdata(dev);
> +

Stray empty line.

> + u8 mbcs2 = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
> + u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
> +
> + return sprintf(buf, "%d\n", chgmod);
> +}
> +static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL);
> +
> +static ssize_t
> +show_usblim(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct pcf50633 *pcf = dev_get_drvdata(dev);
> + u8 usblim = pcf50633_reg_read(pcf, PCF50633_REG_MBCC7) &
> + PCF50633_MBCC7_USB_MASK;
> + unsigned int ma;
> +
> + if (usblim == PCF50633_MBCC7_USB_1000mA)
> + ma = 1000;
> + else if (usblim == PCF50633_MBCC7_USB_500mA)
> + ma = 500;
> + else if (usblim == PCF50633_MBCC7_USB_100mA)
> + ma = 100;
> + else
> + ma = 0;
> +
> + return sprintf(buf, "%u\n", ma);
> +}
> +
> +static ssize_t set_usblim(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t count)
> +{
> + struct pcf50633 *pcf = dev_get_drvdata(dev);
> + unsigned long ma;
> + int ret;
> +
> + ret = strict_strtoul(buf, 10, &ma);
> + if (ret)
> + return -EINVAL;
> +
> + pcf50633_mbc_usb_curlim_set(pcf, ma);
> +
> + return count;
> +}
> +
> +static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim);
> +
> +static struct attribute *pcf50633_mbc_sysfs_entries[] = {
> + &dev_attr_chgmode.attr,
> + &dev_attr_usb_curlim.attr,
> + NULL,
> +};
> +
> +static struct attribute_group mbc_attr_group = {
> + .name = NULL, /* put in device directory */
> + .attrs = pcf50633_mbc_sysfs_entries,
> +};
> +
> +static void
> +pcf50633_mbc_irq_handler(struct pcf50633 *pcf, int irq, void *unused)
> +{
> + struct pcf50633_mbc *mbc;
> +
> + mbc = &pcf->mbc;

struct pcf50633_mbc *mbc = &pcf->mbc; would save us two lines.

> +
> + /* USB */
> + if (irq == PCF50633_IRQ_USBINS) {
> + mbc->usb_online = 1;
> + } else if (irq == PCF50633_IRQ_USBREM) {
> + mbc->usb_online = 0;
> + mbc->usb_active = 0;
> + pcf50633_mbc_usb_curlim_set(pcf, 0);
> + }
> +
> + /* Adapter */
> + if (irq == PCF50633_IRQ_ADPINS) {
> + pcf->mbc.adapter_online = 1;
> + pcf->mbc.adapter_active = 1;
> + } else if (irq == PCF50633_IRQ_ADPREM) {
> + mbc->adapter_online = 0;
> + mbc->adapter_active = 0;
> + }
> +
> + if (irq == PCF50633_IRQ_BATFULL) {
> + mbc->usb_active = 0;
> + mbc->adapter_active = 0;
> + }
> +
> + power_supply_changed(&mbc->usb);
> + power_supply_changed(&mbc->adapter);
> +
> + if (pcf->pdata->mbc_event_callback)
> + pcf->pdata->mbc_event_callback(pcf, irq);
> +}
> +
> +static int adapter_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + int ret = 0;
> + struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb);
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_ONLINE:
> + val->intval = mbc->adapter_online;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + return ret;
> +}
> +
> +static int usb_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + int ret = 0;
> + struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb);
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_ONLINE:
> + val->intval = mbc->usb_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[] = {
> + PCF50633_IRQ_ADPINS,
> + PCF50633_IRQ_ADPREM,
> + PCF50633_IRQ_USBINS,
> + PCF50633_IRQ_USBREM,
> + PCF50633_IRQ_BATFULL,
> + PCF50633_IRQ_CHGHALT,
> + PCF50633_IRQ_THLIMON,
> + PCF50633_IRQ_THLIMOFF,
> + PCF50633_IRQ_USBLIMON,
> + PCF50633_IRQ_USBLIMOFF,
> + PCF50633_IRQ_LOWSYS,
> + PCF50633_IRQ_LOWBAT,
> +};
> +
> +int __init pcf50633_mbc_probe(struct platform_device *pdev)
> +{
> + struct pcf50633 *pcf;
> + struct pcf50633_mbc *mbc;
> + int ret, i;

int ret;
int i;

> +
> + pcf = platform_get_drvdata(pdev);
> + mbc = &pcf->mbc;

The two lines can be placed on the same lines with variables
definitions.

> +
> + /* Set up IRQ handlers */
> + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
> + pcf->irq_handler[mbc_irq_handlers[i]] =
> + pcf50633_mbc_irq_handler;

Ugh. Is there any particular reason why you don't implement a
chained interrupt controller in the PCF core? (as in
drivers/mfd/asic3.c, for example).

That way you could do ordinary request_irq() for the MFD devices'
interrupts.

(I can only guess: you didn't make it because PCF is on the
I2C bus, and it's just easier to handle devices via single
workqueue in the core driver?)

> + /* Create power supplies */
> + mbc->adapter.name = "adapter";
> + mbc->adapter.type = POWER_SUPPLY_TYPE_MAINS;
> + mbc->adapter.properties = power_props;
> + mbc->adapter.num_properties = ARRAY_SIZE(power_props);
> + mbc->adapter.get_property = &adapter_get_property;
> + mbc->adapter.supplied_to = pcf->pdata->batteries;
> + mbc->adapter.num_supplicants = pcf->pdata->num_batteries;
> +
> + mbc->usb.name = "usb";
> + mbc->usb.type = POWER_SUPPLY_TYPE_USB;
> + mbc->usb.properties = power_props;
> + mbc->usb.num_properties = ARRAY_SIZE(power_props);
> + mbc->usb.get_property = usb_get_property;
> + mbc->usb.supplied_to = pcf->pdata->batteries;
> + mbc->usb.num_supplicants = pcf->pdata->num_batteries;
> +
> + ret = power_supply_register(&pdev->dev, &mbc->adapter);
> + if (ret)
> + dev_err(pcf->dev, "failed to register adapter\n");
> +
> + ret = power_supply_register(&pdev->dev, &mbc->usb);
> + if (ret)
> + dev_err(pcf->dev, "failed to register usb\n");
> +
> + return sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group);

If this fail we'll leak two registered power supplies...

> +}
> +
> +static int __devexit pcf50633_mbc_remove(struct platform_device *pdev)
> +{
> + struct pcf50633 *pcf;
> +
> + pcf = platform_get_drvdata(pdev);

This could be placed on a single line.

> +
> + /* Remove IRQ handlers */
> + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
> + pcf->irq_handler[mbc_irq_handlers[i]] = NULL;
> +
> + return 0;

The function doesn't handle power supplies unregistration?

> +}
> +
> +struct platform_driver pcf50633_mbc_driver = {
> + .driver = {
> + .name = "pcf50633-mbc",
> + },
> + .probe = pcf50633_mbc_probe,
> + .remove = __devexit_p(pcf50633_mbc_remove),
> +};
> +
> +static int __init pcf50633_mbc_init(void)
> +{
> + return platform_driver_register(&pcf50633_mbc_driver);

One tab is enough.

> +}
> +module_init(pcf50633_mbc_init);
> +
> +static void __exit pcf50633_mbc_exit(void)
> +{
> + platform_driver_unregister(&pcf50633_mbc_driver);

Ditto.

> +}
> +module_exit(pcf50633_mbc_exit);
> +
> +MODULE_AUTHOR("Balaji Rao <[email protected]>");
> +MODULE_DESCRIPTION("PCF50633 mbc driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:pcf50633-mbc");

Thanks again,

--
Anton Vorontsov
email: [email protected]
irc://irc.freenode.net/bd2

2008-12-16 10:27:46

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 7/7] regulator: PCF50633 pmic driver

On Mon, Dec 15, 2008 at 07:34:50PM +0530, Balaji Rao wrote:
> On Mon, Dec 15, 2008 at 11:35:49AM +0000, Mark Brown wrote:
> > On Sun, Dec 14, 2008 at 04:34:00PM +0530, Balaji Rao wrote:

> > > + .set_suspend_enable = pcf50633_regulator_enable,
> > > + .set_suspend_disable = pcf50633_regulator_disable,

> > Are you sure that the suspend variants of the operations should be the
> > same as the regular versions?

> Yes, basically the chip doesn't change state during suspend/resume.
> We just enable or disable them the usual way.

In that case you shouldn't be providing these variants - they are for
configuring alternative settings which can be switched to in hardware
when the system suspends. If the regular functions should be used then
the code to do this should be in the core since the same thing will be
needed for all regulators that don't have the hardware support.

2008-12-16 15:18:47

by Balaji Rao

[permalink] [raw]
Subject: Re: [PATCH 6/7] input: PCF50633 input driver

On Mon, Dec 15, 2008 at 02:35:40AM -0500, Dmitry Torokhov wrote:
> Hi Balaji,
>
> On Sun, Dec 14, 2008 at 04:33:41PM +0530, Balaji Rao wrote:
> > Signed-off-by: Balaji Rao <[email protected]>
>
> Looks good, some small nitpicks:
>

Hi Dmitry,

I've incorporated the fixes and will send a V2 shortly.

Thank you for the review!

- Balaji

2008-12-16 15:24:41

by Balaji Rao

[permalink] [raw]
Subject: Re: [PATCH 5/7] power_supply: PCF50633 battery charger driver

On Tue, Dec 16, 2008 at 01:38:11AM +0300, Anton Vorontsov wrote:
> Hello Balaji,
>
> It's great to see OpenMoko patches submitted, much thanks
> for your work!
>

Hi Anton,

You are welcome!

> > +
> > + /* Set up IRQ handlers */
> > + for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
> > + pcf->irq_handler[mbc_irq_handlers[i]] =
> > + pcf50633_mbc_irq_handler;
>
> Ugh. Is there any particular reason why you don't implement a
> chained interrupt controller in the PCF core? (as in
> drivers/mfd/asic3.c, for example).
>
> That way you could do ordinary request_irq() for the MFD devices'
> interrupts.
>
> (I can only guess: you didn't make it because PCF is on the
> I2C bus, and it's just easier to handle devices via single
> workqueue in the core driver?)
>

Since we have a fixed number of irq consumers, it's much simpler doing
chained interrupts this way. Using an irq_chip is going to introduce
unnecessary complezity, I feel.

The V2, of the series (about to be sent) changes how irq managemt
is done - pcf50633_request_irq and pcf50633_free_irq are used.

Thank you for the review.

Will send a new version soon.

- Balaji