All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/5] backlight: Add WM831x backlight driver
@ 2009-08-10 16:43 Mark Brown
  2009-08-10 16:43 ` [PATCH 2/5] leds: Add WM831x status LED driver Mark Brown
                   ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: Mark Brown @ 2009-08-10 16:43 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Richard Purdie

The WM831x series of PMICs provide DC-DC boost convertors and current
sinks which can be used together to drive LEDs for use as backlights.
Expose this functionality via the backlight API.

Since when used in this configuration the current sink and boost
convertor are very tightly coupled with a multi-stage startup for
the current sink which overlaps with the boost convertor startup
this driver bypasses the regulator API. Machine inititialisation
is responsible for ensuring that the regulators are not accessed
via both APIs.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Richard Purdie <rpurdie@rpsys.net>
---
 drivers/video/backlight/Kconfig     |    7 +
 drivers/video/backlight/Makefile    |    1 +
 drivers/video/backlight/wm831x_bl.c |  250 +++++++++++++++++++++++++++++++++++
 3 files changed, 258 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/backlight/wm831x_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 90861cd..205de1a 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -229,3 +229,10 @@ config BACKLIGHT_SAHARA
 	help
 	  If you have a Tabletkiosk Sahara Touch-iT, say y to enable the
 	  backlight driver.
+
+config BACKLIGHT_WM831X
+	tristate "WM831x PMIC Backlight Driver"
+	depends on BACKLIGHT_CLASS_DEVICE && MFD_WM831X
+	help
+	  If you have a backlight driven by the ISINK and DCDC of a
+	  WM831x PMIC say y to enable the backlight driver for it.
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 4eb178c..df0b67c 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -24,4 +24,5 @@ obj-$(CONFIG_BACKLIGHT_DA903X)	+= da903x_bl.o
 obj-$(CONFIG_BACKLIGHT_MBP_NVIDIA) += mbp_nvidia_bl.o
 obj-$(CONFIG_BACKLIGHT_TOSA)	+= tosa_bl.o
 obj-$(CONFIG_BACKLIGHT_SAHARA)	+= kb3886_bl.o
+obj-$(CONFIG_BACKLIGHT_WM831X)	+= wm831x_bl.o
 
diff --git a/drivers/video/backlight/wm831x_bl.c b/drivers/video/backlight/wm831x_bl.c
new file mode 100644
index 0000000..467bdb7
--- /dev/null
+++ b/drivers/video/backlight/wm831x_bl.c
@@ -0,0 +1,250 @@
+/*
+ * Backlight driver for Wolfson Microelectronics WM831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectonics plc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/regulator.h>
+
+struct wm831x_backlight_data {
+	struct wm831x *wm831x;
+	int isink_reg;
+	int current_brightness;
+};
+
+static int wm831x_backlight_set(struct backlight_device *bl, int brightness)
+{
+	struct wm831x_backlight_data *data = bl_get_data(bl);
+	struct wm831x *wm831x = data->wm831x;
+	int power_up = !data->current_brightness && brightness;
+	int power_down = data->current_brightness && !brightness;
+	int ret;
+
+	if (power_up) {
+		/* Enable the ISINK */
+		ret = wm831x_set_bits(wm831x, data->isink_reg,
+				      WM831X_CS1_ENA, WM831X_CS1_ENA);
+		if (ret < 0)
+			goto err;
+
+		/* Enable the DC-DC */
+		ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
+				      WM831X_DC4_ENA, WM831X_DC4_ENA);
+		if (ret < 0)
+			goto err;
+	}
+
+	if (power_down) {
+		/* DCDC first */
+		ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
+				      WM831X_DC4_ENA, 0);
+		if (ret < 0)
+			goto err;
+
+		/* ISINK */
+		ret = wm831x_set_bits(wm831x, data->isink_reg,
+				      WM831X_CS1_DRIVE | WM831X_CS1_ENA, 0);
+		if (ret < 0)
+			goto err;
+	}
+
+	/* Set the new brightness */
+	ret = wm831x_set_bits(wm831x, data->isink_reg,
+			      WM831X_CS1_ISEL_MASK, brightness);
+	if (ret < 0)
+		goto err;
+
+	if (power_up) {
+		/* Drive current through the ISINK */
+		ret = wm831x_set_bits(wm831x, data->isink_reg,
+				      WM831X_CS1_DRIVE, WM831X_CS1_DRIVE);
+		if (ret < 0)
+			return ret;
+	}
+
+	data->current_brightness = brightness;
+
+	return 0;
+
+err:
+	/* If we were in the middle of a power transition always shut down
+	 * for safety.
+	 */
+	if (power_up || power_down) {
+		wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
+		wm831x_set_bits(wm831x, data->isink_reg, WM831X_CS1_ENA, 0);
+	}
+
+	return ret;
+}
+
+static int wm831x_backlight_update_status(struct backlight_device *bl)
+{
+	int brightness = bl->props.brightness;
+
+	if (bl->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	if (bl->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	if (bl->props.state & BL_CORE_SUSPENDED)
+		brightness = 0;
+
+	return wm831x_backlight_set(bl, brightness);
+}
+
+static int wm831x_backlight_get_brightness(struct backlight_device *bl)
+{
+	struct wm831x_backlight_data *data = bl_get_data(bl);
+	return data->current_brightness;
+}
+
+static struct backlight_ops wm831x_backlight_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.update_status	= wm831x_backlight_update_status,
+	.get_brightness	= wm831x_backlight_get_brightness,
+};
+
+static int wm831x_backlight_probe(struct platform_device *pdev)
+{
+	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+	struct wm831x_pdata *wm831x_pdata;
+	struct wm831x_backlight_pdata *pdata;
+	struct wm831x_backlight_data *data;
+	struct backlight_device *bl;
+	int ret, i, max_isel, isink_reg, dcdc_cfg;
+
+	/* We need platform data */
+	if (pdev->dev.parent->platform_data) {
+		wm831x_pdata = pdev->dev.parent->platform_data;
+		pdata = wm831x_pdata->backlight;
+	} else {
+		pdata = NULL;
+	}
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data supplied\n");
+		return -EINVAL;
+	}
+
+	/* Figure out the maximum current we can use */
+	for (i = 0; i < WM831X_ISINK_MAX_ISEL; i++) {
+		if (wm831x_isinkv_values[i] > pdata->max_uA)
+			break;
+	}
+
+	if (i == 0) {
+		dev_err(&pdev->dev, "Invalid max_uA: %duA\n", pdata->max_uA);
+		return -EINVAL;
+	}
+	max_isel = i - 1;
+
+	if (pdata->max_uA != wm831x_isinkv_values[max_isel])
+		dev_warn(&pdev->dev,
+			 "Maximum current is %duA not %duA as requested\n",
+			 wm831x_isinkv_values[max_isel], pdata->max_uA);
+
+	switch (pdata->isink) {
+	case 1:
+		isink_reg = WM831X_CURRENT_SINK_1;
+		dcdc_cfg = 0;
+		break;
+	case 2:
+		isink_reg = WM831X_CURRENT_SINK_2;
+		dcdc_cfg = WM831X_DC4_FBSRC;
+		break;
+	default:
+		dev_err(&pdev->dev, "Invalid ISINK %d\n", pdata->isink);
+		return -EINVAL;
+	}
+
+	/* Configure the ISINK to use for feedback */
+	ret = wm831x_reg_unlock(wm831x);
+	if (ret < 0)
+		return ret;
+
+	ret = wm831x_set_bits(wm831x, WM831X_DC4_CONTROL, WM831X_DC4_FBSRC,
+			      dcdc_cfg);
+
+	wm831x_reg_lock(wm831x);
+	if (ret < 0)
+		return ret;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	data->wm831x = wm831x;
+	data->current_brightness = 0;
+	data->isink_reg = isink_reg;
+
+	bl = backlight_device_register("wm831x", &pdev->dev,
+			data, &wm831x_backlight_ops);
+	if (IS_ERR(bl)) {
+		dev_err(&pdev->dev, "failed to register backlight\n");
+		kfree(data);
+		return PTR_ERR(bl);
+	}
+
+	bl->props.max_brightness = max_isel;
+	bl->props.brightness = max_isel;
+
+	platform_set_drvdata(pdev, bl);
+
+	/* Disable the DCDC if it was started so we can bootstrap */
+	wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
+
+
+	backlight_update_status(bl);
+
+	return 0;
+}
+
+static int wm831x_backlight_remove(struct platform_device *pdev)
+{
+	struct backlight_device *bl = platform_get_drvdata(pdev);
+	struct wm831x_backlight_data *data = bl_get_data(bl);
+
+	backlight_device_unregister(bl);
+	kfree(data);
+	return 0;
+}
+
+static struct platform_driver wm831x_backlight_driver = {
+	.driver		= {
+		.name	= "wm831x-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= wm831x_backlight_probe,
+	.remove		= wm831x_backlight_remove,
+};
+
+static int __init wm831x_backlight_init(void)
+{
+	return platform_driver_register(&wm831x_backlight_driver);
+}
+module_init(wm831x_backlight_init);
+
+static void __exit wm831x_backlight_exit(void)
+{
+	platform_driver_unregister(&wm831x_backlight_driver);
+}
+module_exit(wm831x_backlight_exit);
+
+MODULE_DESCRIPTION("Backlight Driver for WM831x PMICs");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-backlight");
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 2/5] leds: Add WM831x status LED driver
  2009-08-10 16:43 [PATCH 1/5] backlight: Add WM831x backlight driver Mark Brown
@ 2009-08-10 16:43 ` Mark Brown
  2009-08-10 16:43 ` [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs Mark Brown
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 16+ messages in thread
From: Mark Brown @ 2009-08-10 16:43 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Richard Purdie

The WM831x devices feature two software controlled status LEDs with
hardware assisted blinking.

The device can also autonomously control the LEDs based on a selection
of sources.  This can be configured at boot time using either platform
data or the chip OTP.  A sysfs file in the style of that for triggers
allowing the control source to be configured at run time.  Triggers
can't be used here since they can't depend on the implementation details
of a specific LED type.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Richard Purdie <rpurdie@rpsys.net>
---
 drivers/leds/Kconfig              |    7 +
 drivers/leds/Makefile             |    1 +
 drivers/leds/leds-wm831x-status.c |  341 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/wm831x/status.h |   34 ++++
 4 files changed, 383 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-wm831x-status.c
 create mode 100644 include/linux/mfd/wm831x/status.h

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 7c8e712..edfd4e3 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -195,6 +195,13 @@ config LEDS_PCA955X
 	  LED driver chips accessed via the I2C bus.  Supported
 	  devices include PCA9550, PCA9551, PCA9552, and PCA9553.
 
+config LEDS_WM831X_STATUS
+	tristate "LED support for status LEDs on WM831x PMICs"
+	depends on LEDS_CLASS && MFD_WM831X
+	help
+	  This option enables support for the status LEDs of the WM831x
+          series of PMICs.
+
 config LEDS_WM8350
 	tristate "LED Support for WM8350 AudioPlus PMIC"
 	depends on LEDS_CLASS && MFD_WM8350
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index e8cdcf7..46d7270 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_LEDS_HP6XX)		+= leds-hp6xx.o
 obj-$(CONFIG_LEDS_FSG)			+= leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
+obj-$(CONFIG_LEDS_WM831X_STATUS)	+= leds-wm831x-status.o
 obj-$(CONFIG_LEDS_WM8350)		+= leds-wm8350.o
 obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
 
diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c
new file mode 100644
index 0000000..c586d05
--- /dev/null
+++ b/drivers/leds/leds-wm831x-status.c
@@ -0,0 +1,341 @@
+/*
+ * LED driver for WM831x status LEDs
+ *
+ * Copyright(C) 2009 Wolfson Microelectronics PLC.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/status.h>
+
+
+struct wm831x_status {
+	struct led_classdev cdev;
+	struct wm831x *wm831x;
+	struct work_struct work;
+	struct mutex mutex;
+
+	spinlock_t value_lock;
+	int reg;     /* Control register */
+	int reg_val; /* Control register value */
+
+	int blink;
+	int blink_time;
+	int blink_cyc;
+	int src;
+	enum led_brightness brightness;
+};
+
+#define to_wm831x_status(led_cdev) \
+	container_of(led_cdev, struct wm831x_status, cdev)
+
+static void wm831x_status_work(struct work_struct *work)
+{
+	struct wm831x_status *led = container_of(work, struct wm831x_status,
+						 work);
+	unsigned long flags;
+
+	mutex_lock(&led->mutex);
+
+	led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK |
+			  WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK);
+
+	spin_lock_irqsave(&led->value_lock, flags);
+
+	led->reg_val |= led->src << WM831X_LED_SRC_SHIFT;
+	if (led->blink) {
+		led->reg_val |= 2 << WM831X_LED_MODE_SHIFT;
+		led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT;
+		led->reg_val |= led->blink_cyc;
+	} else {
+		if (led->brightness != LED_OFF)
+			led->reg_val |= 1 << WM831X_LED_MODE_SHIFT;
+	}
+
+	spin_unlock_irqrestore(&led->value_lock, flags);
+
+	wm831x_reg_write(led->wm831x, led->reg, led->reg_val);
+
+	mutex_unlock(&led->mutex);
+}
+
+static void wm831x_status_set(struct led_classdev *led_cdev,
+			   enum led_brightness value)
+{
+	struct wm831x_status *led = to_wm831x_status(led_cdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->value_lock, flags);
+	led->brightness = value;
+	if (value == LED_OFF)
+		led->blink = 0;
+	schedule_work(&led->work);
+	spin_unlock_irqrestore(&led->value_lock, flags);
+}
+
+static int wm831x_status_blink_set(struct led_classdev *led_cdev,
+				   unsigned long *delay_on,
+				   unsigned long *delay_off)
+{
+	struct wm831x_status *led = to_wm831x_status(led_cdev);
+	unsigned long flags;
+	int ret = 0;
+
+	/* Pick some defaults if we've not been given times */
+	if (*delay_on == 0 && *delay_off == 0) {
+		*delay_on = 250;
+		*delay_off = 250;
+	}
+
+	spin_lock_irqsave(&led->value_lock, flags);
+
+	/* We only have a limited selection of settings, see if we can
+	 * support the configuration we're being given */
+	switch (*delay_on) {
+	case 1000:
+		led->blink_time = 0;
+		break;
+	case 250:
+		led->blink_time = 1;
+		break;
+	case 125:
+		led->blink_time = 2;
+		break;
+	case 62:
+	case 63:
+		/* Actually 62.5ms */
+		led->blink_time = 3;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	if (ret == 0) {
+		switch (*delay_off / *delay_on) {
+		case 1:
+			led->blink_cyc = 0;
+			break;
+		case 3:
+			led->blink_cyc = 1;
+			break;
+		case 4:
+			led->blink_cyc = 2;
+			break;
+		case 8:
+			led->blink_cyc = 3;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+	}
+
+	if (ret == 0)
+		led->blink = 1;
+	else
+		led->blink = 0;
+
+	/* Always update; if we fail turn off blinking since we expect
+	 * a software fallback. */
+	schedule_work(&led->work);
+
+	spin_unlock_irqrestore(&led->value_lock, flags);
+
+	return ret;
+}
+
+static const char *led_src_texts[] = {
+	"otp",
+	"power",
+	"charger",
+	"soft",
+};
+
+static ssize_t wm831x_status_src_show(struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct wm831x_status *led = to_wm831x_status(led_cdev);
+	int i;
+	ssize_t ret = 0;
+
+	mutex_lock(&led->mutex);
+
+	for (i = 0; i < ARRAY_SIZE(led_src_texts); i++)
+		if (i == led->src)
+			ret += sprintf(&buf[ret], "[%s] ", led_src_texts[i]);
+		else
+			ret += sprintf(&buf[ret], "%s ", led_src_texts[i]);
+
+	mutex_unlock(&led->mutex);
+
+	ret += sprintf(&buf[ret], "\n");
+
+	return ret;
+}
+
+static ssize_t wm831x_status_src_store(struct device *dev,
+				       struct device_attribute *attr,
+				       const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct wm831x_status *led = to_wm831x_status(led_cdev);
+	char name[20];
+	int i;
+	size_t len;
+
+	name[sizeof(name) - 1] = '\0';
+	strncpy(name, buf, sizeof(name) - 1);
+	len = strlen(name);
+
+	if (len && name[len - 1] == '\n')
+		name[len - 1] = '\0';
+
+	for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) {
+		if (!strcmp(name, led_src_texts[i])) {
+			mutex_lock(&led->mutex);
+
+			led->src = i;
+			schedule_work(&led->work);
+
+			mutex_unlock(&led->mutex);
+		}
+	}
+
+	return size;
+}
+
+static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store);
+
+static int wm831x_status_probe(struct platform_device *pdev)
+{
+	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+	struct wm831x_pdata *chip_pdata;
+	struct wm831x_status_pdata pdata;
+	struct wm831x_status *drvdata;
+	struct resource *res;
+	int id = pdev->id % ARRAY_SIZE(chip_pdata->status);
+	int ret;
+
+	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "No I/O resource\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	drvdata = kzalloc(sizeof(struct wm831x_status), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+	dev_set_drvdata(&pdev->dev, drvdata);
+
+	drvdata->wm831x = wm831x;
+	drvdata->reg = res->start;
+
+	if (wm831x->dev->platform_data)
+		chip_pdata = wm831x->dev->platform_data;
+	else
+		chip_pdata = NULL;
+
+	memset(&pdata, 0, sizeof(pdata));
+	if (chip_pdata && chip_pdata->status[id])
+		memcpy(&pdata, chip_pdata->status[id], sizeof(pdata));
+	else
+		pdata.name = dev_name(&pdev->dev);
+
+	mutex_init(&drvdata->mutex);
+	INIT_WORK(&drvdata->work, wm831x_status_work);
+	spin_lock_init(&drvdata->value_lock);
+
+	/* We cache the configuration register and read startup values
+	 * from it. */
+	drvdata->reg_val = wm831x_reg_read(wm831x, drvdata->reg);
+
+	if (drvdata->reg_val & WM831X_LED_MODE_MASK)
+		drvdata->brightness = LED_FULL;
+	else
+		drvdata->brightness = LED_OFF;
+
+	/* Set a default source if configured, otherwise leave the
+	 * current hardware setting.
+	 */
+	if (pdata.default_src == WM831X_STATUS_PRESERVE) {
+		drvdata->src = drvdata->reg_val;
+		drvdata->src &= WM831X_LED_SRC_MASK;
+		drvdata->src >>= WM831X_LED_SRC_SHIFT;
+	} else {
+		drvdata->src = pdata.default_src - 1;
+	}
+
+	drvdata->cdev.name = pdata.name;
+	drvdata->cdev.default_trigger = pdata.default_trigger;
+	drvdata->cdev.brightness_set = wm831x_status_set;
+	drvdata->cdev.blink_set = wm831x_status_blink_set;
+
+	ret = led_classdev_register(wm831x->dev, &drvdata->cdev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
+		goto err_led;
+	}
+
+	ret = device_create_file(drvdata->cdev.dev, &dev_attr_src);
+	if (ret != 0)
+		dev_err(&pdev->dev,
+			"No source control for LED: %d\n", ret);
+
+	return 0;
+
+err_led:
+	led_classdev_unregister(&drvdata->cdev);
+	kfree(drvdata);
+err:
+	return ret;
+}
+
+static int wm831x_status_remove(struct platform_device *pdev)
+{
+	struct wm831x_status *drvdata = platform_get_drvdata(pdev);
+
+	device_remove_file(drvdata->cdev.dev, &dev_attr_src);
+	led_classdev_unregister(&drvdata->cdev);
+	kfree(drvdata);
+
+	return 0;
+}
+
+static struct platform_driver wm831x_status_driver = {
+	.driver = {
+		   .name = "wm831x-status",
+		   .owner = THIS_MODULE,
+		   },
+	.probe = wm831x_status_probe,
+	.remove = wm831x_status_remove,
+};
+
+static int __devinit wm831x_status_init(void)
+{
+	return platform_driver_register(&wm831x_status_driver);
+}
+module_init(wm831x_status_init);
+
+static void wm831x_status_exit(void)
+{
+	platform_driver_unregister(&wm831x_status_driver);
+}
+module_exit(wm831x_status_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM831x status LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-status");
diff --git a/include/linux/mfd/wm831x/status.h b/include/linux/mfd/wm831x/status.h
new file mode 100644
index 0000000..6bc090d
--- /dev/null
+++ b/include/linux/mfd/wm831x/status.h
@@ -0,0 +1,34 @@
+/*
+ * include/linux/mfd/wm831x/status.h -- Status LEDs for WM831x
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  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 __MFD_WM831X_STATUS_H__
+#define __MFD_WM831X_STATUS_H__
+
+#define WM831X_LED_SRC_MASK                    0xC000  /* LED_SRC - [15:14] */
+#define WM831X_LED_SRC_SHIFT                       14  /* LED_SRC - [15:14] */
+#define WM831X_LED_SRC_WIDTH                        2  /* LED_SRC - [15:14] */
+#define WM831X_LED_MODE_MASK                   0x0300  /* LED_MODE - [9:8] */
+#define WM831X_LED_MODE_SHIFT                       8  /* LED_MODE - [9:8] */
+#define WM831X_LED_MODE_WIDTH                       2  /* LED_MODE - [9:8] */
+#define WM831X_LED_SEQ_LEN_MASK                0x0030  /* LED_SEQ_LEN - [5:4] */
+#define WM831X_LED_SEQ_LEN_SHIFT                    4  /* LED_SEQ_LEN - [5:4] */
+#define WM831X_LED_SEQ_LEN_WIDTH                    2  /* LED_SEQ_LEN - [5:4] */
+#define WM831X_LED_DUR_MASK                    0x000C  /* LED_DUR - [3:2] */
+#define WM831X_LED_DUR_SHIFT                        2  /* LED_DUR - [3:2] */
+#define WM831X_LED_DUR_WIDTH                        2  /* LED_DUR - [3:2] */
+#define WM831X_LED_DUTY_CYC_MASK               0x0003  /* LED_DUTY_CYC - [1:0] */
+#define WM831X_LED_DUTY_CYC_SHIFT                   0  /* LED_DUTY_CYC - [1:0] */
+#define WM831X_LED_DUTY_CYC_WIDTH                   2  /* LED_DUTY_CYC - [1:0] */
+
+#endif
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs
  2009-08-10 16:43 [PATCH 1/5] backlight: Add WM831x backlight driver Mark Brown
  2009-08-10 16:43 ` [PATCH 2/5] leds: Add WM831x status LED driver Mark Brown
@ 2009-08-10 16:43 ` Mark Brown
  2009-08-14  2:00   ` Anton Vorontsov
  2009-08-10 16:43 ` [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices Mark Brown
  2009-08-10 16:43 ` [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog Mark Brown
  3 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2009-08-10 16:43 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Anton Vorontsov

The WM831x PMICs provide power path management from three sources:
a wall supply, USB and a battery with integrated charger. They also
provide an additional backup supply with integrated for maintaining
always on functionality such as the RTC and monitoring of power
switches.

After some initial configuration at startup the device operates
autonomously, the driver simply provides reporting of the current
state.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Anton Vorontsov <cbouatmailru@gmail.com>
---
 drivers/power/Kconfig          |    7 +
 drivers/power/Makefile         |    1 +
 drivers/power/wm831x_power.c   |  779 ++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/wm831x/pmu.h |  189 ++++++++++
 4 files changed, 976 insertions(+), 0 deletions(-)
 create mode 100644 drivers/power/wm831x_power.c
 create mode 100644 include/linux/mfd/wm831x/pmu.h

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index bdbc4f7..343194c 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -29,6 +29,13 @@ config APM_POWER
 	  Say Y here to enable support APM status emulation using
 	  battery class devices.
 
+config WM831X_POWER
+        tristate "WM831X PMU support"
+        depends on MFD_WM831X
+        help
+          Say Y here to enable support for the power management unit
+	  provided by Wolfson Microelectronics WM831x PMICs.
+
 config WM8350_POWER
         tristate "WM8350 PMU support"
         depends on MFD_WM8350
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 380d17c..b96f29d 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_POWER_SUPPLY)	+= power_supply.o
 
 obj-$(CONFIG_PDA_POWER)		+= pda_power.o
 obj-$(CONFIG_APM_POWER)		+= apm_power.o
+obj-$(CONFIG_WM831X_POWER)	+= wm831x_power.o
 obj-$(CONFIG_WM8350_POWER)	+= wm8350_power.o
 
 obj-$(CONFIG_BATTERY_DS2760)	+= ds2760_battery.o
diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c
new file mode 100644
index 0000000..2a4c8b0
--- /dev/null
+++ b/drivers/power/wm831x_power.c
@@ -0,0 +1,779 @@
+/*
+ * PMU driver for Wolfson Microelectronics wm831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/auxadc.h>
+#include <linux/mfd/wm831x/pmu.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+struct wm831x_power {
+	struct wm831x *wm831x;
+	struct power_supply wall;
+	struct power_supply backup;
+	struct power_supply usb;
+	struct power_supply battery;
+};
+
+static int wm831x_power_check_online(struct wm831x *wm831x, int supply,
+				     union power_supply_propval *val)
+{
+	int ret;
+
+	ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS);
+	if (ret < 0)
+		return ret;
+
+	if (ret & supply)
+		val->intval = 1;
+	else
+		val->intval = 0;
+
+	return 0;
+}
+
+static int wm831x_power_read_voltage(struct wm831x *wm831x,
+				     enum wm831x_auxadc src,
+				     union power_supply_propval *val)
+{
+	int ret;
+
+	ret = wm831x_auxadc_read_uv(wm831x, src);
+	if (ret >= 0)
+		val->intval = ret;
+
+	return ret;
+}
+
+/*********************************************************************
+ *		WALL Power
+ *********************************************************************/
+static int wm831x_wall_get_prop(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent);
+	struct wm831x *wm831x = wm831x_power->wm831x;
+	int ret = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = wm831x_power_check_online(wm831x, WM831X_PWR_WALL, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_WALL, val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property wm831x_wall_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ *		USB Power
+ *********************************************************************/
+static int wm831x_usb_get_prop(struct power_supply *psy,
+			       enum power_supply_property psp,
+			       union power_supply_propval *val)
+{
+	struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent);
+	struct wm831x *wm831x = wm831x_power->wm831x;
+	int ret = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = wm831x_power_check_online(wm831x, WM831X_PWR_USB, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_USB, val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property wm831x_usb_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ *		Battery properties
+ *********************************************************************/
+
+struct chg_map {
+	int val;
+	int reg_val;
+};
+
+static struct chg_map trickle_ilims[] = {
+	{  50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT },
+	{ 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT },
+	{ 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT },
+	{ 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT },
+};
+
+static struct chg_map vsels[] = {
+	{ 4050, 0 << WM831X_CHG_VSEL_SHIFT },
+	{ 4100, 1 << WM831X_CHG_VSEL_SHIFT },
+	{ 4150, 2 << WM831X_CHG_VSEL_SHIFT },
+	{ 4200, 3 << WM831X_CHG_VSEL_SHIFT },
+};
+
+static struct chg_map fast_ilims[] = {
+	{    0,  0 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{   50,  1 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  100,  2 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  150,  3 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  200,  4 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  250,  5 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  300,  6 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  350,  7 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  400,  8 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  450,  9 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  500, 10 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  600, 11 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  700, 12 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  800, 13 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{  900, 14 << WM831X_CHG_FAST_ILIM_SHIFT },
+	{ 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT },
+};
+
+static struct chg_map eoc_iterms[] = {
+	{ 20, 0 << WM831X_CHG_ITERM_SHIFT },
+	{ 30, 1 << WM831X_CHG_ITERM_SHIFT },
+	{ 40, 2 << WM831X_CHG_ITERM_SHIFT },
+	{ 50, 3 << WM831X_CHG_ITERM_SHIFT },
+	{ 60, 4 << WM831X_CHG_ITERM_SHIFT },
+	{ 70, 5 << WM831X_CHG_ITERM_SHIFT },
+	{ 80, 6 << WM831X_CHG_ITERM_SHIFT },
+	{ 90, 7 << WM831X_CHG_ITERM_SHIFT },
+};
+
+static struct chg_map chg_times[] = {
+	{  60,  0 << WM831X_CHG_TIME_SHIFT },
+	{  90,  1 << WM831X_CHG_TIME_SHIFT },
+	{ 120,  2 << WM831X_CHG_TIME_SHIFT },
+	{ 150,  3 << WM831X_CHG_TIME_SHIFT },
+	{ 180,  4 << WM831X_CHG_TIME_SHIFT },
+	{ 210,  5 << WM831X_CHG_TIME_SHIFT },
+	{ 240,  6 << WM831X_CHG_TIME_SHIFT },
+	{ 270,  7 << WM831X_CHG_TIME_SHIFT },
+	{ 300,  8 << WM831X_CHG_TIME_SHIFT },
+	{ 330,  9 << WM831X_CHG_TIME_SHIFT },
+	{ 360, 10 << WM831X_CHG_TIME_SHIFT },
+	{ 390, 11 << WM831X_CHG_TIME_SHIFT },
+	{ 420, 12 << WM831X_CHG_TIME_SHIFT },
+	{ 450, 13 << WM831X_CHG_TIME_SHIFT },
+	{ 480, 14 << WM831X_CHG_TIME_SHIFT },
+	{ 510, 15 << WM831X_CHG_TIME_SHIFT },
+};
+
+static void wm831x_battey_apply_config(struct wm831x *wm831x,
+				       struct chg_map *map, int count, int val,
+				       int *reg, const char *name,
+				       const char *units)
+{
+	int i;
+
+	for (i = 0; i < count; i++)
+		if (val == map[i].val)
+			break;
+	if (i == count) {
+		dev_err(wm831x->dev, "Invalid %s %d%s\n",
+			name, val, units);
+	} else {
+		*reg |= map[i].reg_val;
+		dev_dbg(wm831x->dev, "Set %s of %d%s\n", name, val, units);
+	}
+}
+
+static void wm831x_config_battery(struct wm831x *wm831x)
+{
+	struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+	struct wm831x_battery_pdata *pdata;
+	int ret, reg1, reg2;
+
+	if (!wm831x_pdata || !wm831x_pdata->battery) {
+		dev_warn(wm831x->dev,
+			 "No battery charger configuration\n");
+		return;
+	}
+
+	pdata = wm831x_pdata->battery;
+
+	reg1 = 0;
+	reg2 = 0;
+
+	if (!pdata->enable) {
+		dev_info(wm831x->dev, "Battery charger disabled\n");
+		return;
+	}
+
+	reg1 |= WM831X_CHG_ENA;
+	if (pdata->off_mask)
+		reg2 |= WM831X_CHG_OFF_MSK;
+	if (pdata->fast_enable)
+		reg1 |= WM831X_CHG_FAST;
+
+	wm831x_battey_apply_config(wm831x, trickle_ilims,
+				   ARRAY_SIZE(trickle_ilims),
+				   pdata->trickle_ilim, &reg2,
+				   "trickle charge current limit", "mA");
+
+	wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels),
+				   pdata->vsel, &reg2,
+				   "target voltage", "mV");
+
+	wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims),
+				   pdata->fast_ilim, &reg2,
+				   "fast charge current limit", "mA");
+
+	wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms),
+				   pdata->eoc_iterm, &reg1,
+				   "end of charge current threshold", "mA");
+
+	wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times),
+				   pdata->timeout, &reg2,
+				   "charger timeout", "min");
+
+	ret = wm831x_reg_unlock(wm831x);
+	if (ret != 0) {
+		dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret);
+		return;
+	}
+
+	ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1,
+			      WM831X_CHG_ENA_MASK |
+			      WM831X_CHG_FAST_MASK |
+			      WM831X_CHG_ITERM_MASK |
+			      WM831X_CHG_ITERM_MASK,
+			      reg1);
+	if (ret != 0)
+		dev_err(wm831x->dev, "Failed to set charger control 1: %d\n",
+			ret);
+
+	ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_2,
+			      WM831X_CHG_OFF_MSK |
+			      WM831X_CHG_TIME_MASK |
+			      WM831X_CHG_FAST_ILIM_MASK |
+			      WM831X_CHG_TRKL_ILIM_MASK |
+			      WM831X_CHG_VSEL_MASK,
+			      reg2);
+	if (ret != 0)
+		dev_err(wm831x->dev, "Failed to set charger control 2: %d\n",
+			ret);
+
+	wm831x_reg_lock(wm831x);
+}
+
+static int wm831x_bat_check_status(struct wm831x *wm831x, int *status)
+{
+	int ret;
+
+	ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS);
+	if (ret < 0)
+		return ret;
+
+	if (ret & WM831X_PWR_SRC_BATT) {
+		*status = POWER_SUPPLY_STATUS_DISCHARGING;
+		return 0;
+	}
+
+	ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+	if (ret < 0)
+		return ret;
+
+	switch (ret & WM831X_CHG_STATE_MASK) {
+	case WM831X_CHG_STATE_OFF:
+		*status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+	case WM831X_CHG_STATE_TRICKLE:
+	case WM831X_CHG_STATE_FAST:
+		*status = POWER_SUPPLY_STATUS_CHARGING;
+		break;
+
+	default:
+		*status = POWER_SUPPLY_STATUS_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+static int wm831x_bat_check_type(struct wm831x *wm831x, int *type)
+{
+	int ret;
+
+	ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+	if (ret < 0)
+		return ret;
+
+	switch (ret & WM831X_CHG_STATE_MASK) {
+	case WM831X_CHG_STATE_TRICKLE:
+	case WM831X_CHG_STATE_TRICKLE_OT:
+		*type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+		break;
+	case WM831X_CHG_STATE_FAST:
+	case WM831X_CHG_STATE_FAST_OT:
+		*type = POWER_SUPPLY_CHARGE_TYPE_FAST;
+		break;
+	default:
+		*type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+		break;
+	}
+
+	return 0;
+}
+
+static int wm831x_bat_check_health(struct wm831x *wm831x, int *health)
+{
+	int ret;
+
+	ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+	if (ret < 0)
+		return ret;
+
+	if (ret & WM831X_BATT_HOT_STS) {
+		*health = POWER_SUPPLY_HEALTH_OVERHEAT;
+		return 0;
+	}
+
+	if (ret & WM831X_BATT_COLD_STS) {
+		*health = POWER_SUPPLY_HEALTH_COLD;
+		return 0;
+	}
+
+	if (ret & WM831X_BATT_OV_STS) {
+		*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+		return 0;
+	}
+
+	switch (ret & WM831X_CHG_STATE_MASK) {
+	case WM831X_CHG_STATE_TRICKLE_OT:
+	case WM831X_CHG_STATE_FAST_OT:
+		*health = POWER_SUPPLY_HEALTH_OVERHEAT;
+		break;
+	case WM831X_CHG_STATE_DEFECTIVE:
+		*health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+		break;
+	default:
+		*health = POWER_SUPPLY_HEALTH_GOOD;
+		break;
+	}
+
+	return 0;
+}
+
+static int wm831x_bat_get_prop(struct power_supply *psy,
+			       enum power_supply_property psp,
+			       union power_supply_propval *val)
+{
+	struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent);
+	struct wm831x *wm831x = wm831x_power->wm831x;
+	int ret = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		ret = wm831x_bat_check_status(wm831x, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = wm831x_power_check_online(wm831x, WM831X_PWR_SRC_BATT,
+						val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_BATT, val);
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		ret = wm831x_bat_check_health(wm831x, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		ret = wm831x_bat_check_type(wm831x, &val->intval);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property wm831x_bat_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+static const char *wm831x_bat_irqs[] = {
+	"BATT HOT",
+	"BATT COLD",
+	"BATT FAIL",
+	"OV",
+	"END",
+	"TO",
+	"MODE",
+	"START",
+};
+
+static irqreturn_t wm831x_bat_irq(int irq, void *data)
+{
+	struct wm831x_power *wm831x_power = data;
+	struct wm831x *wm831x = wm831x_power->wm831x;
+
+	dev_dbg(wm831x->dev, "Battery status changed: %d\n", irq);
+
+	/* The battery charger is autonomous so we don't need to do
+	 * anything except kick user space */
+	power_supply_changed(&wm831x_power->battery);
+
+	return IRQ_HANDLED;
+}
+
+
+/*********************************************************************
+ *		Backup supply properties
+ *********************************************************************/
+
+static void wm831x_config_backup(struct wm831x *wm831x)
+{
+	struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+	struct wm831x_backup_pdata *pdata;
+	int ret, reg;
+
+	if (!wm831x_pdata || !wm831x_pdata->backup) {
+		dev_warn(wm831x->dev,
+			 "No backup battery charger configuration\n");
+		return;
+	}
+
+	pdata = wm831x_pdata->backup;
+
+	reg = 0;
+
+	if (pdata->charger_enable)
+		reg |= WM831X_BKUP_CHG_ENA | WM831X_BKUP_BATT_DET_ENA;
+	if (pdata->no_constant_voltage)
+		reg |= WM831X_BKUP_CHG_MODE;
+
+	switch (pdata->vlim) {
+	case 2500:
+		break;
+	case 3100:
+		reg |= WM831X_BKUP_CHG_VLIM;
+		break;
+	default:
+		dev_err(wm831x->dev, "Invalid backup voltage limit %dmV\n",
+			pdata->vlim);
+	}
+
+	switch (pdata->ilim) {
+	case 100:
+		break;
+	case 200:
+		reg |= 1;
+		break;
+	case 300:
+		reg |= 2;
+		break;
+	case 400:
+		reg |= 3;
+		break;
+	default:
+		dev_err(wm831x->dev, "Invalid backup current limit %duA\n",
+			pdata->ilim);
+	}
+
+	ret = wm831x_reg_unlock(wm831x);
+	if (ret != 0) {
+		dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret);
+		return;
+	}
+
+	ret = wm831x_set_bits(wm831x, WM831X_BACKUP_CHARGER_CONTROL,
+			      WM831X_BKUP_CHG_ENA_MASK |
+			      WM831X_BKUP_CHG_MODE_MASK |
+			      WM831X_BKUP_BATT_DET_ENA_MASK |
+			      WM831X_BKUP_CHG_VLIM_MASK |
+			      WM831X_BKUP_CHG_ILIM_MASK,
+			      reg);
+	if (ret != 0)
+		dev_err(wm831x->dev,
+			"Failed to set backup charger config: %d\n", ret);
+
+	wm831x_reg_lock(wm831x);
+}
+
+static int wm831x_backup_get_prop(struct power_supply *psy,
+				  enum power_supply_property psp,
+				  union power_supply_propval *val)
+{
+	struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent);
+	struct wm831x *wm831x = wm831x_power->wm831x;
+	int ret = 0;
+
+	ret = wm831x_reg_read(wm831x, WM831X_BACKUP_CHARGER_CONTROL);
+	if (ret < 0)
+		return ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		if (ret & WM831X_BKUP_CHG_STS)
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		else
+			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_BKUP_BATT,
+						val);
+		break;
+
+	case POWER_SUPPLY_PROP_PRESENT:
+		if (ret & WM831X_BKUP_CHG_STS)
+			val->intval = 1;
+		else
+			val->intval = 0;
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property wm831x_backup_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_PRESENT,
+};
+
+/*********************************************************************
+ *		Initialisation
+ *********************************************************************/
+
+static irqreturn_t wm831x_syslo_irq(int irq, void *data)
+{
+	struct wm831x_power *wm831x_power = data;
+	struct wm831x *wm831x = wm831x_power->wm831x;
+
+	/* Not much we can actually *do* but tell people for
+	 * posterity, we're probably about to run out of power. */
+	dev_crit(wm831x->dev, "SYSVDD under voltage\n");
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_pwr_src_irq(int irq, void *data)
+{
+	struct wm831x_power *wm831x_power = data;
+	struct wm831x *wm831x = wm831x_power->wm831x;
+
+	dev_dbg(wm831x->dev, "Power source changed\n");
+
+	/* Just notify for everything - little harm in overnotifying.
+	 * The backup battery is not a power source while the system
+	 * is running so skip that.
+	 */
+	power_supply_changed(&wm831x_power->battery);
+	power_supply_changed(&wm831x_power->usb);
+	power_supply_changed(&wm831x_power->wall);
+
+	return IRQ_HANDLED;
+}
+
+static __devinit int wm831x_power_probe(struct platform_device *pdev)
+{
+	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+	struct wm831x_power *power;
+	struct power_supply *usb;
+	struct power_supply *battery;
+	struct power_supply *wall;
+	struct power_supply *backup;
+	int ret, irq, i;
+
+	power = kzalloc(sizeof(struct wm831x_power), GFP_KERNEL);
+	if (power == NULL)
+		return -ENOMEM;
+
+	power->wm831x = wm831x;
+	platform_set_drvdata(pdev, power);
+
+	usb = &power->usb;
+	battery = &power->battery;
+	wall = &power->wall;
+	backup = &power->backup;
+
+	/* We ignore configuration failures since we can still read back
+	 * the status without enabling either of the chargers.
+	 */
+	wm831x_config_battery(wm831x);
+	wm831x_config_backup(wm831x);
+
+	wall->name = "wm831x-wall";
+	wall->type = POWER_SUPPLY_TYPE_MAINS;
+	wall->properties = wm831x_wall_props;
+	wall->num_properties = ARRAY_SIZE(wm831x_wall_props);
+	wall->get_property = wm831x_wall_get_prop;
+	ret = power_supply_register(&pdev->dev, wall);
+	if (ret)
+		goto err_kmalloc;
+
+	battery->name = "wm831x-battery";
+	battery->properties = wm831x_bat_props;
+	battery->num_properties = ARRAY_SIZE(wm831x_bat_props);
+	battery->get_property = wm831x_bat_get_prop;
+	battery->use_for_apm = 1;
+	ret = power_supply_register(&pdev->dev, battery);
+	if (ret)
+		goto err_wall;
+
+	usb->name = "wm831x-usb",
+	usb->type = POWER_SUPPLY_TYPE_USB;
+	usb->properties = wm831x_usb_props;
+	usb->num_properties = ARRAY_SIZE(wm831x_usb_props);
+	usb->get_property = wm831x_usb_get_prop;
+	ret = power_supply_register(&pdev->dev, usb);
+	if (ret)
+		goto err_battery;
+
+	backup->name = "wm831x-backup";
+	backup->type = POWER_SUPPLY_TYPE_BATTERY;
+	backup->properties = wm831x_backup_props;
+	backup->num_properties = ARRAY_SIZE(wm831x_backup_props);
+	backup->get_property = wm831x_backup_get_prop;
+	ret = power_supply_register(&pdev->dev, backup);
+	if (ret)
+		goto err_usb;
+
+	irq = platform_get_irq_byname(pdev, "SYSLO");
+	ret = wm831x_request_irq(wm831x, irq, wm831x_syslo_irq,
+				 IRQF_TRIGGER_RISING, "SYSLO",
+				 power);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n",
+			irq, ret);
+		goto err_backup;
+	}
+
+	irq = platform_get_irq_byname(pdev, "PWR SRC");
+	ret = wm831x_request_irq(wm831x, irq, wm831x_pwr_src_irq,
+				 IRQF_TRIGGER_RISING, "Power source",
+				 power);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n",
+			irq, ret);
+		goto err_syslo;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) {
+		irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
+		ret = wm831x_request_irq(wm831x, irq, wm831x_bat_irq,
+					 IRQF_TRIGGER_RISING,
+					 wm831x_bat_irqs[i],
+					 power);
+		if (ret != 0) {
+			dev_err(&pdev->dev,
+				"Failed to request %s IRQ %d: %d\n",
+				wm831x_bat_irqs[i], irq, ret);
+			goto err_bat_irq;
+		}
+	}
+
+	return ret;
+
+err_bat_irq:
+	for (; i >= 0; i--) {
+		irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
+		wm831x_free_irq(wm831x, irq, power);
+	}
+	irq = platform_get_irq_byname(pdev, "PWR SRC");
+	wm831x_free_irq(wm831x, irq, power);
+err_syslo:
+	irq = platform_get_irq_byname(pdev, "SYSLO");
+	wm831x_free_irq(wm831x, irq, power);
+err_backup:
+	power_supply_unregister(backup);
+err_usb:
+	power_supply_unregister(usb);
+err_battery:
+	power_supply_unregister(battery);
+err_wall:
+	power_supply_unregister(wall);
+err_kmalloc:
+	kfree(power);
+	return ret;
+}
+
+static __devexit int wm831x_power_remove(struct platform_device *pdev)
+{
+	struct wm831x_power *wm831x_power = platform_get_drvdata(pdev);
+	struct wm831x *wm831x = wm831x_power->wm831x;
+	int irq, i;
+
+	for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) {
+		irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
+		wm831x_free_irq(wm831x, irq, wm831x_power);
+	}
+
+	irq = platform_get_irq_byname(pdev, "PWR SRC");
+	wm831x_free_irq(wm831x, irq, wm831x_power);
+
+	irq = platform_get_irq_byname(pdev, "SYSLO");
+	wm831x_free_irq(wm831x, irq, wm831x_power);
+
+	power_supply_unregister(&wm831x_power->backup);
+	power_supply_unregister(&wm831x_power->battery);
+	power_supply_unregister(&wm831x_power->wall);
+	power_supply_unregister(&wm831x_power->usb);
+	return 0;
+}
+
+static struct platform_driver wm831x_power_driver = {
+	.probe = wm831x_power_probe,
+	.remove = __devexit_p(wm831x_power_remove),
+	.driver = {
+		.name = "wm831x-power",
+	},
+};
+
+static int __init wm831x_power_init(void)
+{
+	return platform_driver_register(&wm831x_power_driver);
+}
+module_init(wm831x_power_init);
+
+static void __exit wm831x_power_exit(void)
+{
+	platform_driver_unregister(&wm831x_power_driver);
+}
+module_exit(wm831x_power_exit);
+
+MODULE_DESCRIPTION("Power supply driver for WM831x PMICs");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-power");
diff --git a/include/linux/mfd/wm831x/pmu.h b/include/linux/mfd/wm831x/pmu.h
new file mode 100644
index 0000000..b18cbb0
--- /dev/null
+++ b/include/linux/mfd/wm831x/pmu.h
@@ -0,0 +1,189 @@
+/*
+ * include/linux/mfd/wm831x/pmu.h -- PMU for WM831x
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  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 __MFD_WM831X_PMU_H__
+#define __MFD_WM831X_PMU_H__
+
+/*
+ * R16387 (0x4003) - Power State
+ */
+#define WM831X_CHIP_ON                          0x8000  /* CHIP_ON */
+#define WM831X_CHIP_ON_MASK                     0x8000  /* CHIP_ON */
+#define WM831X_CHIP_ON_SHIFT                        15  /* CHIP_ON */
+#define WM831X_CHIP_ON_WIDTH                         1  /* CHIP_ON */
+#define WM831X_CHIP_SLP                         0x4000  /* CHIP_SLP */
+#define WM831X_CHIP_SLP_MASK                    0x4000  /* CHIP_SLP */
+#define WM831X_CHIP_SLP_SHIFT                       14  /* CHIP_SLP */
+#define WM831X_CHIP_SLP_WIDTH                        1  /* CHIP_SLP */
+#define WM831X_REF_LP                           0x1000  /* REF_LP */
+#define WM831X_REF_LP_MASK                      0x1000  /* REF_LP */
+#define WM831X_REF_LP_SHIFT                         12  /* REF_LP */
+#define WM831X_REF_LP_WIDTH                          1  /* REF_LP */
+#define WM831X_PWRSTATE_DLY_MASK                0x0C00  /* PWRSTATE_DLY - [11:10] */
+#define WM831X_PWRSTATE_DLY_SHIFT                   10  /* PWRSTATE_DLY - [11:10] */
+#define WM831X_PWRSTATE_DLY_WIDTH                    2  /* PWRSTATE_DLY - [11:10] */
+#define WM831X_SWRST_DLY                        0x0200  /* SWRST_DLY */
+#define WM831X_SWRST_DLY_MASK                   0x0200  /* SWRST_DLY */
+#define WM831X_SWRST_DLY_SHIFT                       9  /* SWRST_DLY */
+#define WM831X_SWRST_DLY_WIDTH                       1  /* SWRST_DLY */
+#define WM831X_USB100MA_STARTUP_MASK            0x0030  /* USB100MA_STARTUP - [5:4] */
+#define WM831X_USB100MA_STARTUP_SHIFT                4  /* USB100MA_STARTUP - [5:4] */
+#define WM831X_USB100MA_STARTUP_WIDTH                2  /* USB100MA_STARTUP - [5:4] */
+#define WM831X_USB_CURR_STS                     0x0008  /* USB_CURR_STS */
+#define WM831X_USB_CURR_STS_MASK                0x0008  /* USB_CURR_STS */
+#define WM831X_USB_CURR_STS_SHIFT                    3  /* USB_CURR_STS */
+#define WM831X_USB_CURR_STS_WIDTH                    1  /* USB_CURR_STS */
+#define WM831X_USB_ILIM_MASK                    0x0007  /* USB_ILIM - [2:0] */
+#define WM831X_USB_ILIM_SHIFT                        0  /* USB_ILIM - [2:0] */
+#define WM831X_USB_ILIM_WIDTH                        3  /* USB_ILIM - [2:0] */
+
+/*
+ * R16397 (0x400D) - System Status
+ */
+#define WM831X_THW_STS                          0x8000  /* THW_STS */
+#define WM831X_THW_STS_MASK                     0x8000  /* THW_STS */
+#define WM831X_THW_STS_SHIFT                        15  /* THW_STS */
+#define WM831X_THW_STS_WIDTH                         1  /* THW_STS */
+#define WM831X_PWR_SRC_BATT                     0x0400  /* PWR_SRC_BATT */
+#define WM831X_PWR_SRC_BATT_MASK                0x0400  /* PWR_SRC_BATT */
+#define WM831X_PWR_SRC_BATT_SHIFT                   10  /* PWR_SRC_BATT */
+#define WM831X_PWR_SRC_BATT_WIDTH                    1  /* PWR_SRC_BATT */
+#define WM831X_PWR_WALL                         0x0200  /* PWR_WALL */
+#define WM831X_PWR_WALL_MASK                    0x0200  /* PWR_WALL */
+#define WM831X_PWR_WALL_SHIFT                        9  /* PWR_WALL */
+#define WM831X_PWR_WALL_WIDTH                        1  /* PWR_WALL */
+#define WM831X_PWR_USB                          0x0100  /* PWR_USB */
+#define WM831X_PWR_USB_MASK                     0x0100  /* PWR_USB */
+#define WM831X_PWR_USB_SHIFT                         8  /* PWR_USB */
+#define WM831X_PWR_USB_WIDTH                         1  /* PWR_USB */
+#define WM831X_MAIN_STATE_MASK                  0x001F  /* MAIN_STATE - [4:0] */
+#define WM831X_MAIN_STATE_SHIFT                      0  /* MAIN_STATE - [4:0] */
+#define WM831X_MAIN_STATE_WIDTH                      5  /* MAIN_STATE - [4:0] */
+
+/*
+ * R16456 (0x4048) - Charger Control 1
+ */
+#define WM831X_CHG_ENA                          0x8000  /* CHG_ENA */
+#define WM831X_CHG_ENA_MASK                     0x8000  /* CHG_ENA */
+#define WM831X_CHG_ENA_SHIFT                        15  /* CHG_ENA */
+#define WM831X_CHG_ENA_WIDTH                         1  /* CHG_ENA */
+#define WM831X_CHG_FRC                          0x4000  /* CHG_FRC */
+#define WM831X_CHG_FRC_MASK                     0x4000  /* CHG_FRC */
+#define WM831X_CHG_FRC_SHIFT                        14  /* CHG_FRC */
+#define WM831X_CHG_FRC_WIDTH                         1  /* CHG_FRC */
+#define WM831X_CHG_ITERM_MASK                   0x1C00  /* CHG_ITERM - [12:10] */
+#define WM831X_CHG_ITERM_SHIFT                      10  /* CHG_ITERM - [12:10] */
+#define WM831X_CHG_ITERM_WIDTH                       3  /* CHG_ITERM - [12:10] */
+#define WM831X_CHG_FAST                         0x0020  /* CHG_FAST */
+#define WM831X_CHG_FAST_MASK                    0x0020  /* CHG_FAST */
+#define WM831X_CHG_FAST_SHIFT                        5  /* CHG_FAST */
+#define WM831X_CHG_FAST_WIDTH                        1  /* CHG_FAST */
+#define WM831X_CHG_IMON_ENA                     0x0002  /* CHG_IMON_ENA */
+#define WM831X_CHG_IMON_ENA_MASK                0x0002  /* CHG_IMON_ENA */
+#define WM831X_CHG_IMON_ENA_SHIFT                    1  /* CHG_IMON_ENA */
+#define WM831X_CHG_IMON_ENA_WIDTH                    1  /* CHG_IMON_ENA */
+#define WM831X_CHG_CHIP_TEMP_MON                0x0001  /* CHG_CHIP_TEMP_MON */
+#define WM831X_CHG_CHIP_TEMP_MON_MASK           0x0001  /* CHG_CHIP_TEMP_MON */
+#define WM831X_CHG_CHIP_TEMP_MON_SHIFT               0  /* CHG_CHIP_TEMP_MON */
+#define WM831X_CHG_CHIP_TEMP_MON_WIDTH               1  /* CHG_CHIP_TEMP_MON */
+
+/*
+ * R16457 (0x4049) - Charger Control 2
+ */
+#define WM831X_CHG_OFF_MSK                      0x4000  /* CHG_OFF_MSK */
+#define WM831X_CHG_OFF_MSK_MASK                 0x4000  /* CHG_OFF_MSK */
+#define WM831X_CHG_OFF_MSK_SHIFT                    14  /* CHG_OFF_MSK */
+#define WM831X_CHG_OFF_MSK_WIDTH                     1  /* CHG_OFF_MSK */
+#define WM831X_CHG_TIME_MASK                    0x0F00  /* CHG_TIME - [11:8] */
+#define WM831X_CHG_TIME_SHIFT                        8  /* CHG_TIME - [11:8] */
+#define WM831X_CHG_TIME_WIDTH                        4  /* CHG_TIME - [11:8] */
+#define WM831X_CHG_TRKL_ILIM_MASK               0x00C0  /* CHG_TRKL_ILIM - [7:6] */
+#define WM831X_CHG_TRKL_ILIM_SHIFT                   6  /* CHG_TRKL_ILIM - [7:6] */
+#define WM831X_CHG_TRKL_ILIM_WIDTH                   2  /* CHG_TRKL_ILIM - [7:6] */
+#define WM831X_CHG_VSEL_MASK                    0x0030  /* CHG_VSEL - [5:4] */
+#define WM831X_CHG_VSEL_SHIFT                        4  /* CHG_VSEL - [5:4] */
+#define WM831X_CHG_VSEL_WIDTH                        2  /* CHG_VSEL - [5:4] */
+#define WM831X_CHG_FAST_ILIM_MASK               0x000F  /* CHG_FAST_ILIM - [3:0] */
+#define WM831X_CHG_FAST_ILIM_SHIFT                   0  /* CHG_FAST_ILIM - [3:0] */
+#define WM831X_CHG_FAST_ILIM_WIDTH                   4  /* CHG_FAST_ILIM - [3:0] */
+
+/*
+ * R16458 (0x404A) - Charger Status
+ */
+#define WM831X_BATT_OV_STS                      0x8000  /* BATT_OV_STS */
+#define WM831X_BATT_OV_STS_MASK                 0x8000  /* BATT_OV_STS */
+#define WM831X_BATT_OV_STS_SHIFT                    15  /* BATT_OV_STS */
+#define WM831X_BATT_OV_STS_WIDTH                     1  /* BATT_OV_STS */
+#define WM831X_CHG_STATE_MASK                   0x7000  /* CHG_STATE - [14:12] */
+#define WM831X_CHG_STATE_SHIFT                      12  /* CHG_STATE - [14:12] */
+#define WM831X_CHG_STATE_WIDTH                       3  /* CHG_STATE - [14:12] */
+#define WM831X_BATT_HOT_STS                     0x0800  /* BATT_HOT_STS */
+#define WM831X_BATT_HOT_STS_MASK                0x0800  /* BATT_HOT_STS */
+#define WM831X_BATT_HOT_STS_SHIFT                   11  /* BATT_HOT_STS */
+#define WM831X_BATT_HOT_STS_WIDTH                    1  /* BATT_HOT_STS */
+#define WM831X_BATT_COLD_STS                    0x0400  /* BATT_COLD_STS */
+#define WM831X_BATT_COLD_STS_MASK               0x0400  /* BATT_COLD_STS */
+#define WM831X_BATT_COLD_STS_SHIFT                  10  /* BATT_COLD_STS */
+#define WM831X_BATT_COLD_STS_WIDTH                   1  /* BATT_COLD_STS */
+#define WM831X_CHG_TOPOFF                       0x0200  /* CHG_TOPOFF */
+#define WM831X_CHG_TOPOFF_MASK                  0x0200  /* CHG_TOPOFF */
+#define WM831X_CHG_TOPOFF_SHIFT                      9  /* CHG_TOPOFF */
+#define WM831X_CHG_TOPOFF_WIDTH                      1  /* CHG_TOPOFF */
+#define WM831X_CHG_ACTIVE                       0x0100  /* CHG_ACTIVE */
+#define WM831X_CHG_ACTIVE_MASK                  0x0100  /* CHG_ACTIVE */
+#define WM831X_CHG_ACTIVE_SHIFT                      8  /* CHG_ACTIVE */
+#define WM831X_CHG_ACTIVE_WIDTH                      1  /* CHG_ACTIVE */
+#define WM831X_CHG_TIME_ELAPSED_MASK            0x00FF  /* CHG_TIME_ELAPSED - [7:0] */
+#define WM831X_CHG_TIME_ELAPSED_SHIFT                0  /* CHG_TIME_ELAPSED - [7:0] */
+#define WM831X_CHG_TIME_ELAPSED_WIDTH                8  /* CHG_TIME_ELAPSED - [7:0] */
+
+#define WM831X_CHG_STATE_OFF         (0 << WM831X_CHG_STATE_SHIFT)
+#define WM831X_CHG_STATE_TRICKLE     (1 << WM831X_CHG_STATE_SHIFT)
+#define WM831X_CHG_STATE_FAST        (2 << WM831X_CHG_STATE_SHIFT)
+#define WM831X_CHG_STATE_TRICKLE_OT  (3 << WM831X_CHG_STATE_SHIFT)
+#define WM831X_CHG_STATE_FAST_OT     (4 << WM831X_CHG_STATE_SHIFT)
+#define WM831X_CHG_STATE_DEFECTIVE   (5 << WM831X_CHG_STATE_SHIFT)
+
+/*
+ * R16459 (0x404B) - Backup Charger Control
+ */
+#define WM831X_BKUP_CHG_ENA                     0x8000  /* BKUP_CHG_ENA */
+#define WM831X_BKUP_CHG_ENA_MASK                0x8000  /* BKUP_CHG_ENA */
+#define WM831X_BKUP_CHG_ENA_SHIFT                   15  /* BKUP_CHG_ENA */
+#define WM831X_BKUP_CHG_ENA_WIDTH                    1  /* BKUP_CHG_ENA */
+#define WM831X_BKUP_CHG_STS                     0x4000  /* BKUP_CHG_STS */
+#define WM831X_BKUP_CHG_STS_MASK                0x4000  /* BKUP_CHG_STS */
+#define WM831X_BKUP_CHG_STS_SHIFT                   14  /* BKUP_CHG_STS */
+#define WM831X_BKUP_CHG_STS_WIDTH                    1  /* BKUP_CHG_STS */
+#define WM831X_BKUP_CHG_MODE                    0x1000  /* BKUP_CHG_MODE */
+#define WM831X_BKUP_CHG_MODE_MASK               0x1000  /* BKUP_CHG_MODE */
+#define WM831X_BKUP_CHG_MODE_SHIFT                  12  /* BKUP_CHG_MODE */
+#define WM831X_BKUP_CHG_MODE_WIDTH                   1  /* BKUP_CHG_MODE */
+#define WM831X_BKUP_BATT_DET_ENA                0x0800  /* BKUP_BATT_DET_ENA */
+#define WM831X_BKUP_BATT_DET_ENA_MASK           0x0800  /* BKUP_BATT_DET_ENA */
+#define WM831X_BKUP_BATT_DET_ENA_SHIFT              11  /* BKUP_BATT_DET_ENA */
+#define WM831X_BKUP_BATT_DET_ENA_WIDTH               1  /* BKUP_BATT_DET_ENA */
+#define WM831X_BKUP_BATT_STS                    0x0400  /* BKUP_BATT_STS */
+#define WM831X_BKUP_BATT_STS_MASK               0x0400  /* BKUP_BATT_STS */
+#define WM831X_BKUP_BATT_STS_SHIFT                  10  /* BKUP_BATT_STS */
+#define WM831X_BKUP_BATT_STS_WIDTH                   1  /* BKUP_BATT_STS */
+#define WM831X_BKUP_CHG_VLIM                    0x0010  /* BKUP_CHG_VLIM */
+#define WM831X_BKUP_CHG_VLIM_MASK               0x0010  /* BKUP_CHG_VLIM */
+#define WM831X_BKUP_CHG_VLIM_SHIFT                   4  /* BKUP_CHG_VLIM */
+#define WM831X_BKUP_CHG_VLIM_WIDTH                   1  /* BKUP_CHG_VLIM */
+#define WM831X_BKUP_CHG_ILIM_MASK               0x0003  /* BKUP_CHG_ILIM - [1:0] */
+#define WM831X_BKUP_CHG_ILIM_SHIFT                   0  /* BKUP_CHG_ILIM - [1:0] */
+#define WM831X_BKUP_CHG_ILIM_WIDTH                   2  /* BKUP_CHG_ILIM - [1:0] */
+
+#endif
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices
  2009-08-10 16:43 [PATCH 1/5] backlight: Add WM831x backlight driver Mark Brown
  2009-08-10 16:43 ` [PATCH 2/5] leds: Add WM831x status LED driver Mark Brown
  2009-08-10 16:43 ` [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs Mark Brown
@ 2009-08-10 16:43 ` Mark Brown
  2009-08-10 18:55   ` [rtc-linux] " Alessandro Zummo
  2009-08-10 16:43 ` [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog Mark Brown
  3 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2009-08-10 16:43 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Alessandro Zummo, rtc-linux

The WM831x series of PMICs contain RTC functionality. The hardware
provides a 32 bit counter incrementing at 1Hz together with a per
tick interrupt and an alarm value. For simplicity the driver chooses
to define the epoch for the counter as the Unix epoch - if required
platform data can be used in future to customise this.

When powered on from a completely cold state the RTC reports that it
has not been configured - when this happens an error is returned
when attempting to read the RTC in order to avoid use of values we
know to be invalid.

The hardware also provides security features which mean that it can
ignore attempts to set the RTC time in certain circumstances, most
notably if the RTC is written to too often. These errors are detected
by verifying the written RTC value.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: rtc-linux@googlegroups.com
---
 drivers/rtc/Kconfig      |   10 +
 drivers/rtc/Makefile     |    1 +
 drivers/rtc/rtc-wm831x.c |  538 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 549 insertions(+), 0 deletions(-)
 create mode 100644 drivers/rtc/rtc-wm831x.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 139b783..f595113 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -537,6 +537,16 @@ config RTC_DRV_V3020
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-v3020.
 
+config RTC_DRV_WM831X
+	tristate "Wolfson Microelectronics WM831x RTC"
+	depends on MFD_WM831X
+	help
+	  If you say yes here you will get support for the RTC subsystem
+	  of the Wolfson Microelectronics WM831X series PMICs.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called "rtc-wm831x".
+
 config RTC_DRV_WM8350
 	tristate "Wolfson Microelectronics WM8350 RTC"
 	depends on MFD_WM8350
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2a565f8..61d5600 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_RTC_DRV_TWL4030)	+= rtc-twl4030.o
 obj-$(CONFIG_RTC_DRV_TX4939)	+= rtc-tx4939.o
 obj-$(CONFIG_RTC_DRV_V3020)	+= rtc-v3020.o
 obj-$(CONFIG_RTC_DRV_VR41XX)	+= rtc-vr41xx.o
+obj-$(CONFIG_RTC_DRV_WM831X)	+= rtc-wm831x.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-wm831x.c b/drivers/rtc/rtc-wm831x.c
new file mode 100644
index 0000000..99e7845
--- /dev/null
+++ b/drivers/rtc/rtc-wm831x.c
@@ -0,0 +1,538 @@
+/*
+ *	Real Time Clock driver for Wolfson Microelectronics WM831x
+ *
+ *	Copyright (C) 2009 Wolfson Microelectronics PLC.
+ *
+ *  Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/time.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/interrupt.h>
+#include <linux/ioctl.h>
+#include <linux/completion.h>
+#include <linux/mfd/wm831x/core.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+
+/*
+ * R16416 (0x4020) - RTC Write Counter
+ */
+#define WM831X_RTC_WR_CNT_MASK                  0xFFFF  /* RTC_WR_CNT - [15:0] */
+#define WM831X_RTC_WR_CNT_SHIFT                      0  /* RTC_WR_CNT - [15:0] */
+#define WM831X_RTC_WR_CNT_WIDTH                     16  /* RTC_WR_CNT - [15:0] */
+
+/*
+ * R16417 (0x4021) - RTC Time 1
+ */
+#define WM831X_RTC_TIME_MASK                    0xFFFF  /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_SHIFT                        0  /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_WIDTH                       16  /* RTC_TIME - [15:0] */
+
+/*
+ * R16418 (0x4022) - RTC Time 2
+ */
+#define WM831X_RTC_TIME_MASK                    0xFFFF  /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_SHIFT                        0  /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_WIDTH                       16  /* RTC_TIME - [15:0] */
+
+/*
+ * R16419 (0x4023) - RTC Alarm 1
+ */
+#define WM831X_RTC_ALM_MASK                     0xFFFF  /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_SHIFT                         0  /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_WIDTH                        16  /* RTC_ALM - [15:0] */
+
+/*
+ * R16420 (0x4024) - RTC Alarm 2
+ */
+#define WM831X_RTC_ALM_MASK                     0xFFFF  /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_SHIFT                         0  /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_WIDTH                        16  /* RTC_ALM - [15:0] */
+
+/*
+ * R16421 (0x4025) - RTC Control
+ */
+#define WM831X_RTC_VALID                        0x8000  /* RTC_VALID */
+#define WM831X_RTC_VALID_MASK                   0x8000  /* RTC_VALID */
+#define WM831X_RTC_VALID_SHIFT                      15  /* RTC_VALID */
+#define WM831X_RTC_VALID_WIDTH                       1  /* RTC_VALID */
+#define WM831X_RTC_SYNC_BUSY                    0x4000  /* RTC_SYNC_BUSY */
+#define WM831X_RTC_SYNC_BUSY_MASK               0x4000  /* RTC_SYNC_BUSY */
+#define WM831X_RTC_SYNC_BUSY_SHIFT                  14  /* RTC_SYNC_BUSY */
+#define WM831X_RTC_SYNC_BUSY_WIDTH                   1  /* RTC_SYNC_BUSY */
+#define WM831X_RTC_ALM_ENA                      0x0400  /* RTC_ALM_ENA */
+#define WM831X_RTC_ALM_ENA_MASK                 0x0400  /* RTC_ALM_ENA */
+#define WM831X_RTC_ALM_ENA_SHIFT                    10  /* RTC_ALM_ENA */
+#define WM831X_RTC_ALM_ENA_WIDTH                     1  /* RTC_ALM_ENA */
+#define WM831X_RTC_PINT_FREQ_MASK               0x0070  /* RTC_PINT_FREQ - [6:4] */
+#define WM831X_RTC_PINT_FREQ_SHIFT                   4  /* RTC_PINT_FREQ - [6:4] */
+#define WM831X_RTC_PINT_FREQ_WIDTH                   3  /* RTC_PINT_FREQ - [6:4] */
+
+/*
+ * R16422 (0x4026) - RTC Trim
+ */
+#define WM831X_RTC_TRIM_MASK                    0x03FF  /* RTC_TRIM - [9:0] */
+#define WM831X_RTC_TRIM_SHIFT                        0  /* RTC_TRIM - [9:0] */
+#define WM831X_RTC_TRIM_WIDTH                       10  /* RTC_TRIM - [9:0] */
+
+#define WM831X_SET_TIME_RETRIES	5
+#define WM831X_GET_TIME_RETRIES	5
+
+struct wm831x_rtc {
+	struct wm831x *wm831x;
+	struct rtc_device *rtc;
+	int alarm_enabled;
+	int per_irq;
+};
+
+/*
+ * Read current time and date in RTC
+ */
+static int wm831x_rtc_readtime(struct device *dev, struct rtc_time *tm)
+{
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+	struct wm831x *wm831x = wm831x_rtc->wm831x;
+	u16 time1[2], time2[2];
+	int ret;
+	int count = 0;
+
+	/* Has the RTC been programmed? */
+	ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
+	if (ret < 0) {
+		dev_err(dev, "Failed to read RTC control: %d\n", ret);
+		return ret;
+	}
+	if (!(ret & WM831X_RTC_VALID)) {
+		dev_dbg(dev, "RTC not yet configured\n");
+		return -EINVAL;
+	}
+
+	/* Read twice to make sure we don't read a corrupt, partially
+	 * incremented, value.
+	 */
+	do {
+		ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1,
+				       2, time1);
+		if (ret != 0)
+			continue;
+
+		ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1,
+				       2, time2);
+		if (ret != 0)
+			continue;
+
+		if (memcmp(time1, time2, sizeof(time1)) == 0) {
+			u32 time = (time1[0] << 16) | time1[1];
+
+			rtc_time_to_tm(time, tm);
+			return 0;
+		}
+
+	} while (++count < WM831X_GET_TIME_RETRIES);
+
+	dev_err(dev, "Timed out reading current time\n");
+
+	return -EIO;
+}
+
+/*
+ * Set current time and date in RTC
+ */
+static int wm831x_rtc_settime(struct device *dev, struct rtc_time *tm)
+{
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+	struct wm831x *wm831x = wm831x_rtc->wm831x;
+	struct rtc_time new_tm;
+	unsigned long time, new_time;
+	int ret;
+	int count = 0;
+
+	ret = rtc_tm_to_time(tm, &time);
+	if (ret < 0) {
+		dev_err(dev, "Failed to convert time: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_1,
+			       (time >> 16) & 0xffff);
+	if (ret < 0) {
+		dev_err(dev, "Failed to write TIME_1: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_2, time & 0xffff);
+	if (ret < 0) {
+		dev_err(dev, "Failed to write TIME_2: %d\n", ret);
+		return ret;
+	}
+
+	/* Wait for the update to complete - should happen first time
+	 * round but be conservative.
+	 */
+	do {
+		msleep(1);
+
+		ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
+		if (ret < 0)
+			ret = WM831X_RTC_SYNC_BUSY;
+	} while (!(ret & WM831X_RTC_SYNC_BUSY) &&
+		 ++count < WM831X_SET_TIME_RETRIES);
+
+	if (ret & WM831X_RTC_SYNC_BUSY) {
+		dev_err(dev, "Timed out writing RTC update\n");
+		return -EIO;
+	}
+
+	/* Check that the update was accepted; security features may
+	 * have caused the update to be ignored.
+	 */
+	ret = wm831x_rtc_readtime(dev, &new_tm);
+	if (ret < 0)
+		return ret;
+
+	ret = rtc_tm_to_time(&new_tm, &new_time);
+	if (ret < 0) {
+		dev_err(dev, "Failed to convert time: %d\n", ret);
+		return ret;
+	}
+
+	/* Allow a second of change in case of tick */
+	if (new_time - time > 1) {
+		dev_err(dev, "RTC update not permitted by hardware\n");
+		return -EPERM;
+	}
+
+	return 0;
+}
+
+/*
+ * Read alarm time and date in RTC
+ */
+static int wm831x_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+	int ret;
+	u16 data[2];
+	u32 time;
+
+	ret = wm831x_bulk_read(wm831x_rtc->wm831x, WM831X_RTC_ALARM_1,
+			       2, data);
+	if (ret != 0) {
+		dev_err(dev, "Failed to read alarm time: %d\n", ret);
+		return ret;
+	}
+
+	time = (data[0] << 16) | data[1];
+
+	rtc_time_to_tm(time, &alrm->time);
+
+	ret = wm831x_reg_read(wm831x_rtc->wm831x, WM831X_RTC_CONTROL);
+	if (ret < 0) {
+		dev_err(dev, "Failed to read RTC control: %d\n", ret);
+		return ret;
+	}
+
+	if (ret & WM831X_RTC_ALM_ENA)
+		alrm->enabled = 1;
+	else
+		alrm->enabled = 0;
+
+	return 0;
+}
+
+static int wm831x_rtc_stop_alarm(struct wm831x_rtc *wm831x_rtc)
+{
+	wm831x_rtc->alarm_enabled = 0;
+
+	return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+			       WM831X_RTC_ALM_ENA, 0);
+}
+
+static int wm831x_rtc_start_alarm(struct wm831x_rtc *wm831x_rtc)
+{
+	wm831x_rtc->alarm_enabled = 1;
+
+	return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+			       WM831X_RTC_ALM_ENA, WM831X_RTC_ALM_ENA);
+}
+
+static int wm831x_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+	struct wm831x *wm831x = wm831x_rtc->wm831x;
+	int ret;
+	unsigned long time;
+
+	ret = rtc_tm_to_time(&alrm->time, &time);
+	if (ret < 0) {
+		dev_err(dev, "Failed to convert time: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm831x_rtc_stop_alarm(wm831x_rtc);
+	if (ret < 0) {
+		dev_err(dev, "Failed to stop alarm: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_1,
+			       (time >> 16) & 0xffff);
+	if (ret < 0) {
+		dev_err(dev, "Failed to write ALARM_1: %d\n", ret);
+		return ret;
+	}
+
+	ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_2, time & 0xffff);
+	if (ret < 0) {
+		dev_err(dev, "Failed to write ALARM_2: %d\n", ret);
+		return ret;
+	}
+
+	if (alrm->enabled) {
+		ret = wm831x_rtc_start_alarm(wm831x_rtc);
+		if (ret < 0) {
+			dev_err(dev, "Failed to start alarm: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int wm831x_rtc_alarm_irq_enable(struct device *dev,
+				       unsigned int enabled)
+{
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+
+	if (enabled)
+		return wm831x_rtc_start_alarm(wm831x_rtc);
+	else
+		return wm831x_rtc_stop_alarm(wm831x_rtc);
+}
+
+static int wm831x_rtc_update_irq_enable(struct device *dev,
+					unsigned int enabled)
+{
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+	int val;
+
+	if (enabled)
+		val = 1 << WM831X_RTC_PINT_FREQ_SHIFT;
+	else
+		val = 0;
+
+	return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+			       WM831X_RTC_PINT_FREQ_MASK, val);
+}
+
+static irqreturn_t wm831x_alm_irq(int irq, void *data)
+{
+	struct wm831x_rtc *wm831x_rtc = data;
+
+	rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_AF);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_per_irq(int irq, void *data)
+{
+	struct wm831x_rtc *wm831x_rtc = data;
+
+	rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_UF);
+
+	return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops wm831x_rtc_ops = {
+	.read_time = wm831x_rtc_readtime,
+	.set_time = wm831x_rtc_settime,
+	.read_alarm = wm831x_rtc_readalarm,
+	.set_alarm = wm831x_rtc_setalarm,
+	.alarm_irq_enable = wm831x_rtc_alarm_irq_enable,
+	.update_irq_enable = wm831x_rtc_update_irq_enable,
+};
+
+#ifdef CONFIG_PM
+/* Turn off the alarm if it should not be a wake source. */
+static int wm831x_rtc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
+	int ret, enable;
+
+	if (wm831x_rtc->alarm_enabled && device_may_wakeup(&pdev->dev))
+		enable = WM831X_RTC_ALM_ENA;
+	else
+		enable = 0;
+
+	ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+			      WM831X_RTC_ALM_ENA, enable);
+	if (ret != 0)
+		dev_err(&pdev->dev, "Failed to update RTC alarm: %d\n", ret);
+
+	return 0;
+}
+
+/* Enable the alarm if it should be enabled (in case it was disabled to
+ * prevent use as a wake source).
+ */
+static int wm831x_rtc_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
+	int ret;
+
+	if (wm831x_rtc->alarm_enabled) {
+		ret = wm831x_rtc_start_alarm(wm831x_rtc);
+		if (ret != 0)
+			dev_err(&pdev->dev,
+				"Failed to restart RTC alarm: %d\n", ret);
+	}
+
+	return 0;
+}
+
+/* Unconditionally disable the alarm */
+static int wm831x_rtc_freeze(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
+	int ret;
+
+	ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+			      WM831X_RTC_ALM_ENA, 0);
+	if (ret != 0)
+		dev_err(&pdev->dev, "Failed to stop RTC alarm: %d\n", ret);
+
+	return 0;
+}
+#else
+#define wm831x_rtc_suspend NULL
+#define wm831x_rtc_resume NULL
+#define wm831x_rtc_freeze NULL
+#endif
+
+static int wm831x_rtc_probe(struct platform_device *pdev)
+{
+	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+	struct wm831x_rtc *wm831x_rtc;
+	int per_irq = platform_get_irq_byname(pdev, "PER");
+	int alm_irq = platform_get_irq_byname(pdev, "ALM");
+	int ret = 0;
+
+	wm831x_rtc = kzalloc(sizeof(*wm831x_rtc), GFP_KERNEL);
+	if (wm831x_rtc == NULL)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, wm831x_rtc);
+	wm831x_rtc->wm831x = wm831x;
+	wm831x_rtc->per_irq = per_irq;
+
+	ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to read RTC control: %d\n", ret);
+		goto err;
+	}
+	if (ret & WM831X_RTC_ALM_ENA)
+		wm831x_rtc->alarm_enabled = 1;
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	wm831x_rtc->rtc = rtc_device_register("wm831x", &pdev->dev,
+					      &wm831x_rtc_ops, THIS_MODULE);
+	if (IS_ERR(wm831x_rtc->rtc)) {
+		ret = PTR_ERR(wm831x_rtc->rtc);
+		dev_err(&pdev->dev, "Failed to register RTC: %d\n", ret);
+		goto err;
+	}
+
+	ret = wm831x_request_irq(wm831x, per_irq, wm831x_per_irq,
+				 IRQF_TRIGGER_RISING, "wm831x_rtc_per",
+				 wm831x_rtc);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "Failed to request periodic IRQ %d: %d\n",
+			per_irq, ret);
+		goto err_rtc;
+	}
+
+	ret = wm831x_request_irq(wm831x, alm_irq, wm831x_alm_irq,
+				 IRQF_TRIGGER_RISING, "wm831x_rtc_alm",
+				 wm831x_rtc);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n",
+			alm_irq, ret);
+		goto err_per;
+	}
+
+	return 0;
+
+err_per:
+	wm831x_free_irq(wm831x, per_irq, wm831x_rtc);
+err_rtc:
+	rtc_device_unregister(wm831x_rtc->rtc);
+err:
+	kfree(wm831x_rtc);
+	return ret;
+}
+
+static int __devexit wm831x_rtc_remove(struct platform_device *pdev)
+{
+	struct wm831x_rtc *wm831x_rtc = platform_get_drvdata(pdev);
+	int per_irq = platform_get_irq(pdev, 0);
+	int alm_irq = platform_get_irq(pdev, 1);
+
+	wm831x_free_irq(wm831x_rtc->wm831x, alm_irq, wm831x_rtc);
+	wm831x_free_irq(wm831x_rtc->wm831x, per_irq, wm831x_rtc);
+	rtc_device_unregister(wm831x_rtc->rtc);
+	kfree(wm831x_rtc);
+
+	return 0;
+}
+
+static struct dev_pm_ops wm831x_rtc_pm_ops = {
+	.suspend = wm831x_rtc_suspend,
+	.resume = wm831x_rtc_resume,
+
+	.freeze = wm831x_rtc_freeze,
+	.thaw = wm831x_rtc_resume,
+	.restore = wm831x_rtc_resume,
+
+	.poweroff = wm831x_rtc_suspend,
+};
+
+static struct platform_driver wm831x_rtc_driver = {
+	.probe = wm831x_rtc_probe,
+	.remove = __devexit_p(wm831x_rtc_remove),
+	.driver = {
+		.name = "wm831x-rtc",
+		.pm = &wm831x_rtc_pm_ops,
+	},
+};
+
+static int __init wm831x_rtc_init(void)
+{
+	return platform_driver_register(&wm831x_rtc_driver);
+}
+module_init(wm831x_rtc_init);
+
+static void __exit wm831x_rtc_exit(void)
+{
+	platform_driver_unregister(&wm831x_rtc_driver);
+}
+module_exit(wm831x_rtc_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("RTC driver for the WM831x series PMICs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-rtc");
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog
  2009-08-10 16:43 [PATCH 1/5] backlight: Add WM831x backlight driver Mark Brown
                   ` (2 preceding siblings ...)
  2009-08-10 16:43 ` [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices Mark Brown
@ 2009-08-10 16:43 ` Mark Brown
  2009-08-11 12:40   ` Wim Van Sebroeck
                     ` (2 more replies)
  3 siblings, 3 replies; 16+ messages in thread
From: Mark Brown @ 2009-08-10 16:43 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Wim Van Sebroeck

The WM831x series of devices provide a watchdog with configurable
behaviour on timer expiry.

Currently this driver support refreshes via a register or GPIO line and
autonomous refreshes from a hardware source (eg, a clock).

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Wim Van Sebroeck <wim@iguana.be>
---
 drivers/watchdog/Kconfig            |    7 +
 drivers/watchdog/Makefile           |    1 +
 drivers/watchdog/wm831x_wdt.c       |  441 +++++++++++++++++++++++++++++++++++
 include/linux/mfd/wm831x/watchdog.h |   52 ++++
 4 files changed, 501 insertions(+), 0 deletions(-)
 create mode 100644 drivers/watchdog/wm831x_wdt.c
 create mode 100644 include/linux/mfd/wm831x/watchdog.h

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 1940a08..8e154ee 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -55,6 +55,13 @@ config SOFT_WATCHDOG
 	  To compile this driver as a module, choose M here: the
 	  module will be called softdog.
 
+config WM831X_WATCHDOG
+	tristate "WM831x watchdog"
+	depends on MFD_WM831X
+	help
+	  Support for the watchdog in the WM831x AudioPlus PMICs.  When
+	  the watchdog triggers the system will be reset.
+
 config WM8350_WATCHDOG
 	tristate "WM8350 watchdog"
 	depends on MFD_WM8350
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 6c58e7c..a74984d 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -140,5 +140,6 @@ obj-$(CONFIG_WATCHDOG_CP1XXX)		+= cpwd.o
 # XTENSA Architecture
 
 # Architecture Independant
+obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
 obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
 obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
diff --git a/drivers/watchdog/wm831x_wdt.c b/drivers/watchdog/wm831x_wdt.c
new file mode 100644
index 0000000..775bcd8
--- /dev/null
+++ b/drivers/watchdog/wm831x_wdt.c
@@ -0,0 +1,441 @@
+/*
+ * Watchdog driver for the wm831x PMICs
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics
+ *
+ * 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
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+#include <linux/gpio.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/watchdog.h>
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout,
+		 "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static unsigned long wm831x_wdt_users;
+static struct miscdevice wm831x_wdt_miscdev;
+static int wm831x_wdt_expect_close;
+static DEFINE_MUTEX(wdt_mutex);
+static struct wm831x *wm831x;
+static unsigned int update_gpio;
+static unsigned int update_state;
+
+/* We can't use the sub-second values here but they're included
+ * for completeness.  */
+static struct {
+	int time;  /* Seconds */
+	u16 val;   /* WDOG_TO value */
+} wm831x_wdt_cfgs[] = {
+	{  1, 2 },
+	{  2, 3 },
+	{  4, 4 },
+	{  8, 5 },
+	{ 16, 6 },
+	{ 32, 7 },
+	{ 33, 7 },  /* Actually 32.768s so include both, others round down */
+};
+
+static int wm831x_wdt_set_timeout(struct wm831x *wm831x, u16 value)
+{
+	int ret;
+
+	mutex_lock(&wdt_mutex);
+
+	ret = wm831x_reg_unlock(wm831x);
+	if (ret == 0) {
+		ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+				      WM831X_WDOG_TO_MASK, value);
+		wm831x_reg_lock(wm831x);
+	} else {
+		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+			ret);
+	}
+
+	mutex_unlock(&wdt_mutex);
+
+	return ret;
+}
+
+static int wm831x_wdt_start(struct wm831x *wm831x)
+{
+	int ret;
+
+	mutex_lock(&wdt_mutex);
+
+	ret = wm831x_reg_unlock(wm831x);
+	if (ret == 0) {
+		ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+				      WM831X_WDOG_ENA, WM831X_WDOG_ENA);
+		wm831x_reg_lock(wm831x);
+	} else {
+		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+			ret);
+	}
+
+	mutex_unlock(&wdt_mutex);
+
+	return ret;
+}
+
+static int wm831x_wdt_stop(struct wm831x *wm831x)
+{
+	int ret;
+
+	mutex_lock(&wdt_mutex);
+
+	ret = wm831x_reg_unlock(wm831x);
+	if (ret == 0) {
+		ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+				      WM831X_WDOG_ENA, 0);
+		wm831x_reg_lock(wm831x);
+	} else {
+		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+			ret);
+	}
+
+	mutex_unlock(&wdt_mutex);
+
+	return ret;
+}
+
+static int wm831x_wdt_kick(struct wm831x *wm831x)
+{
+	int ret;
+	u16 reg;
+
+	mutex_lock(&wdt_mutex);
+
+	if (update_gpio) {
+		gpio_set_value_cansleep(update_gpio, update_state);
+		update_state = !update_state;
+		ret = 0;
+		goto out;
+	}
+
+
+	reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+
+	if (!(reg & WM831X_WDOG_RST_SRC)) {
+		dev_err(wm831x->dev, "Hardware watchdog update unsupported\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	reg |= WM831X_WDOG_RESET;
+
+	ret = wm831x_reg_unlock(wm831x);
+	if (ret == 0) {
+		ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
+		wm831x_reg_lock(wm831x);
+	} else {
+		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+			ret);
+	}
+
+out:
+	mutex_unlock(&wdt_mutex);
+
+	return ret;
+}
+
+static int wm831x_wdt_open(struct inode *inode, struct file *file)
+{
+	int ret;
+
+	if (!wm831x)
+		return -ENODEV;
+
+	if (test_and_set_bit(0, &wm831x_wdt_users))
+		return -EBUSY;
+
+	ret = wm831x_wdt_start(wm831x);
+	if (ret != 0)
+		return ret;
+
+	return nonseekable_open(inode, file);
+}
+
+static int wm831x_wdt_release(struct inode *inode, struct file *file)
+{
+	if (wm831x_wdt_expect_close)
+		wm831x_wdt_stop(wm831x);
+	else {
+		dev_warn(wm831x->dev, "Watchdog device closed uncleanly\n");
+		wm831x_wdt_kick(wm831x);
+	}
+
+	clear_bit(0, &wm831x_wdt_users);
+
+	return 0;
+}
+
+static ssize_t wm831x_wdt_write(struct file *file,
+				const char __user *data, size_t count,
+				loff_t *ppos)
+{
+	size_t i;
+
+	if (count) {
+		wm831x_wdt_kick(wm831x);
+
+		if (!nowayout) {
+			/* In case it was set long ago */
+			wm831x_wdt_expect_close = 0;
+
+			/* scan to see whether or not we got the magic
+			   character */
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, data + i))
+					return -EFAULT;
+				if (c == 'V')
+					wm831x_wdt_expect_close = 42;
+			}
+		}
+	}
+	return count;
+}
+
+static struct watchdog_info ident = {
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+	.identity = "WM831x Watchdog",
+};
+
+static long wm831x_wdt_ioctl(struct file *file, unsigned int cmd,
+			     unsigned long arg)
+{
+	int ret = -ENOTTY, time, i;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	u16 reg;
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+		break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, p);
+		break;
+
+	case WDIOC_SETOPTIONS:
+	{
+		int options;
+
+		if (get_user(options, p))
+			return -EFAULT;
+
+		ret = -EINVAL;
+
+		/* Setting both simultaneously means at least one must fail */
+		if (options == WDIOS_DISABLECARD)
+			ret = wm831x_wdt_start(wm831x);
+
+		if (options == WDIOS_ENABLECARD)
+			ret = wm831x_wdt_stop(wm831x);
+		break;
+	}
+
+	case WDIOC_KEEPALIVE:
+		ret = wm831x_wdt_kick(wm831x);
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		ret = get_user(time, p);
+		if (ret)
+			break;
+
+		if (time == 0) {
+			if (nowayout)
+				ret = -EINVAL;
+			else
+				wm831x_wdt_stop(wm831x);
+			break;
+		}
+
+		for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
+			if (wm831x_wdt_cfgs[i].time == time)
+				break;
+		if (i == ARRAY_SIZE(wm831x_wdt_cfgs))
+			ret = -EINVAL;
+		else
+			ret = wm831x_wdt_set_timeout(wm831x,
+						     wm831x_wdt_cfgs[i].val);
+		break;
+
+	case WDIOC_GETTIMEOUT:
+		reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+		reg &= WM831X_WDOG_TO_MASK;
+		for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
+			if (wm831x_wdt_cfgs[i].val == reg)
+				break;
+		if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) {
+			dev_warn(wm831x->dev,
+				 "Unknown watchdog configuration: %x\n", reg);
+			ret = -EINVAL;
+		} else
+			ret = put_user(wm831x_wdt_cfgs[i].time, p);
+
+	}
+
+	return ret;
+}
+
+static const struct file_operations wm831x_wdt_fops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.write = wm831x_wdt_write,
+	.unlocked_ioctl = wm831x_wdt_ioctl,
+	.open = wm831x_wdt_open,
+	.release = wm831x_wdt_release,
+};
+
+static struct miscdevice wm831x_wdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &wm831x_wdt_fops,
+};
+
+static int __devinit wm831x_wdt_probe(struct platform_device *pdev)
+{
+	struct wm831x_pdata *chip_pdata;
+	struct wm831x_watchdog_pdata *pdata;
+	int reg, ret;
+
+	wm831x = dev_get_drvdata(pdev->dev.parent);
+
+	ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+	if (ret < 0) {
+		dev_err(wm831x->dev, "Failed to read watchdog status: %d\n",
+			ret);
+		goto err;
+	}
+	reg = ret;
+
+	if (reg & WM831X_WDOG_DEBUG)
+		dev_warn(wm831x->dev, "Watchdog is paused\n");
+
+	/* Apply any configuration */
+	if (pdev->dev.parent->platform_data) {
+		chip_pdata = pdev->dev.parent->platform_data;
+		pdata = chip_pdata->watchdog;
+	} else {
+		pdata = NULL;
+	}
+
+	if (pdata) {
+		reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK |
+			 WM831X_WDOG_RST_SRC);
+
+		reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT;
+		reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT;
+		reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT;
+
+		if (pdata->update_gpio) {
+			ret = gpio_request(pdata->update_gpio,
+					   "Watchdog update");
+			if (ret < 0) {
+				dev_err(wm831x->dev,
+					"Failed to request update GPIO: %d\n",
+					ret);
+				goto err;
+			}
+
+			ret = gpio_direction_output(pdata->update_gpio, 0);
+			if (ret != 0) {
+				dev_err(wm831x->dev,
+					"gpio_direction_output returned: %d\n",
+					ret);
+				goto err_gpio;
+			}
+
+			update_gpio = pdata->update_gpio;
+
+			/* Make sure the watchdog takes hardware updates */
+			reg |= WM831X_WDOG_RST_SRC;
+		}
+
+		ret = wm831x_reg_unlock(wm831x);
+		if (ret == 0) {
+			ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
+			wm831x_reg_lock(wm831x);
+		} else {
+			dev_err(wm831x->dev,
+				"Failed to unlock security key: %d\n", ret);
+			goto err_gpio;
+		}
+	}
+
+	wm831x_wdt_miscdev.parent = &pdev->dev;
+
+	ret = misc_register(&wm831x_wdt_miscdev);
+	if (ret != 0) {
+		dev_err(wm831x->dev, "Failed to register miscdev: %d\n", ret);
+		goto err_gpio;
+	}
+
+	return 0;
+
+err_gpio:
+	if (update_gpio) {
+		gpio_free(update_gpio);
+		update_gpio = 0;
+	}
+err:
+	return ret;
+}
+
+static int __devexit wm831x_wdt_remove(struct platform_device *pdev)
+{
+	if (update_gpio) {
+		gpio_free(update_gpio);
+		update_gpio = 0;
+	}
+
+	misc_deregister(&wm831x_wdt_miscdev);
+
+	return 0;
+}
+
+static struct platform_driver wm831x_wdt_driver = {
+	.probe = wm831x_wdt_probe,
+	.remove = __devexit_p(wm831x_wdt_remove),
+	.driver = {
+		.name = "wm831x-watchdog",
+	},
+};
+
+static int __init wm831x_wdt_init(void)
+{
+	return platform_driver_register(&wm831x_wdt_driver);
+}
+module_init(wm831x_wdt_init);
+
+static void __exit wm831x_wdt_exit(void)
+{
+	platform_driver_unregister(&wm831x_wdt_driver);
+}
+module_exit(wm831x_wdt_exit);
+
+MODULE_AUTHOR("Mark Brown");
+MODULE_DESCRIPTION("WM831x Watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-watchdog");
diff --git a/include/linux/mfd/wm831x/watchdog.h b/include/linux/mfd/wm831x/watchdog.h
new file mode 100644
index 0000000..97a99b5
--- /dev/null
+++ b/include/linux/mfd/wm831x/watchdog.h
@@ -0,0 +1,52 @@
+/*
+ * include/linux/mfd/wm831x/watchdog.h -- Watchdog for WM831x
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  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 __MFD_WM831X_WATCHDOG_H__
+#define __MFD_WM831X_WATCHDOG_H__
+
+
+/*
+ * R16388 (0x4004) - Watchdog
+ */
+#define WM831X_WDOG_ENA                         0x8000  /* WDOG_ENA */
+#define WM831X_WDOG_ENA_MASK                    0x8000  /* WDOG_ENA */
+#define WM831X_WDOG_ENA_SHIFT                       15  /* WDOG_ENA */
+#define WM831X_WDOG_ENA_WIDTH                        1  /* WDOG_ENA */
+#define WM831X_WDOG_DEBUG                       0x4000  /* WDOG_DEBUG */
+#define WM831X_WDOG_DEBUG_MASK                  0x4000  /* WDOG_DEBUG */
+#define WM831X_WDOG_DEBUG_SHIFT                     14  /* WDOG_DEBUG */
+#define WM831X_WDOG_DEBUG_WIDTH                      1  /* WDOG_DEBUG */
+#define WM831X_WDOG_RST_SRC                     0x2000  /* WDOG_RST_SRC */
+#define WM831X_WDOG_RST_SRC_MASK                0x2000  /* WDOG_RST_SRC */
+#define WM831X_WDOG_RST_SRC_SHIFT                   13  /* WDOG_RST_SRC */
+#define WM831X_WDOG_RST_SRC_WIDTH                    1  /* WDOG_RST_SRC */
+#define WM831X_WDOG_SLPENA                      0x1000  /* WDOG_SLPENA */
+#define WM831X_WDOG_SLPENA_MASK                 0x1000  /* WDOG_SLPENA */
+#define WM831X_WDOG_SLPENA_SHIFT                    12  /* WDOG_SLPENA */
+#define WM831X_WDOG_SLPENA_WIDTH                     1  /* WDOG_SLPENA */
+#define WM831X_WDOG_RESET                       0x0800  /* WDOG_RESET */
+#define WM831X_WDOG_RESET_MASK                  0x0800  /* WDOG_RESET */
+#define WM831X_WDOG_RESET_SHIFT                     11  /* WDOG_RESET */
+#define WM831X_WDOG_RESET_WIDTH                      1  /* WDOG_RESET */
+#define WM831X_WDOG_SECACT_MASK                 0x0300  /* WDOG_SECACT - [9:8] */
+#define WM831X_WDOG_SECACT_SHIFT                     8  /* WDOG_SECACT - [9:8] */
+#define WM831X_WDOG_SECACT_WIDTH                     2  /* WDOG_SECACT - [9:8] */
+#define WM831X_WDOG_PRIMACT_MASK                0x0030  /* WDOG_PRIMACT - [5:4] */
+#define WM831X_WDOG_PRIMACT_SHIFT                    4  /* WDOG_PRIMACT - [5:4] */
+#define WM831X_WDOG_PRIMACT_WIDTH                    2  /* WDOG_PRIMACT - [5:4] */
+#define WM831X_WDOG_TO_MASK                     0x0007  /* WDOG_TO - [2:0] */
+#define WM831X_WDOG_TO_SHIFT                         0  /* WDOG_TO - [2:0] */
+#define WM831X_WDOG_TO_WIDTH                         3  /* WDOG_TO - [2:0] */
+
+#endif
-- 
1.6.3.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [rtc-linux] [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices
  2009-08-10 16:43 ` [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices Mark Brown
@ 2009-08-10 18:55   ` Alessandro Zummo
  2009-08-10 20:57     ` Mark Brown
  0 siblings, 1 reply; 16+ messages in thread
From: Alessandro Zummo @ 2009-08-10 18:55 UTC (permalink / raw)
  To: rtc-linux; +Cc: broonie, Samuel Ortiz, linux-kernel, Alessandro Zummo

On Mon, 10 Aug 2009 17:43:54 +0100
Mark Brown <broonie@opensource.wolfsonmicro.com> wrote:

> 
> The WM831x series of PMICs contain RTC functionality. The hardware
> provides a 32 bit counter incrementing at 1Hz together with a per
> tick interrupt and an alarm value. For simplicity the driver chooses
> to define the epoch for the counter as the Unix epoch - if required
> platform data can be used in future to customise this.

 [...]

 HI, comments below:


> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Alessandro Zummo <a.zummo@towertech.it>
> Cc: rtc-linux@googlegroups.com
> ---
>  drivers/rtc/Kconfig      |   10 +
>  drivers/rtc/Makefile     |    1 +
>  drivers/rtc/rtc-wm831x.c |  538 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 549 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/rtc/rtc-wm831x.c
> 
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 139b783..f595113 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -537,6 +537,16 @@ config RTC_DRV_V3020
>  	  This driver can also be built as a module. If so, the module
>  	  will be called rtc-v3020.
>  
> +config RTC_DRV_WM831X
> +	tristate "Wolfson Microelectronics WM831x RTC"
> +	depends on MFD_WM831X
> +	help
> +	  If you say yes here you will get support for the RTC subsystem
> +	  of the Wolfson Microelectronics WM831X series PMICs.
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called "rtc-wm831x".
> +
>  config RTC_DRV_WM8350
>  	tristate "Wolfson Microelectronics WM8350 RTC"
>  	depends on MFD_WM8350
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 2a565f8..61d5600 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -76,6 +76,7 @@ obj-$(CONFIG_RTC_DRV_TWL4030)	+= rtc-twl4030.o
>  obj-$(CONFIG_RTC_DRV_TX4939)	+= rtc-tx4939.o
>  obj-$(CONFIG_RTC_DRV_V3020)	+= rtc-v3020.o
>  obj-$(CONFIG_RTC_DRV_VR41XX)	+= rtc-vr41xx.o
> +obj-$(CONFIG_RTC_DRV_WM831X)	+= rtc-wm831x.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-wm831x.c b/drivers/rtc/rtc-wm831x.c
> new file mode 100644
> index 0000000..99e7845
> --- /dev/null
> +++ b/drivers/rtc/rtc-wm831x.c
> @@ -0,0 +1,538 @@
> +/*
> + *	Real Time Clock driver for Wolfson Microelectronics WM831x
> + *
> + *	Copyright (C) 2009 Wolfson Microelectronics PLC.
> + *
> + *  Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
> + *
> + *  This program is free software; you can redistribute  it and/or modify it
> + *  under  the terms of  the GNU General  Public License as published by the
> + *  Free Software Foundation;  either version 2 of the  License, or (at your
> + *  option) any later version.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/time.h>
> +#include <linux/rtc.h>
> +#include <linux/bcd.h>
> +#include <linux/interrupt.h>
> +#include <linux/ioctl.h>
> +#include <linux/completion.h>
> +#include <linux/mfd/wm831x/core.h>
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +
> +
> +/*
> + * R16416 (0x4020) - RTC Write Counter
> + */
> +#define WM831X_RTC_WR_CNT_MASK                  0xFFFF  /* RTC_WR_CNT - [15:0] */
> +#define WM831X_RTC_WR_CNT_SHIFT                      0  /* RTC_WR_CNT - [15:0] */
> +#define WM831X_RTC_WR_CNT_WIDTH                     16  /* RTC_WR_CNT - [15:0] */
> +
> +/*
> + * R16417 (0x4021) - RTC Time 1
> + */
> +#define WM831X_RTC_TIME_MASK                    0xFFFF  /* RTC_TIME - [15:0] */
> +#define WM831X_RTC_TIME_SHIFT                        0  /* RTC_TIME - [15:0] */
> +#define WM831X_RTC_TIME_WIDTH                       16  /* RTC_TIME - [15:0] */
> +
> +/*
> + * R16418 (0x4022) - RTC Time 2
> + */
> +#define WM831X_RTC_TIME_MASK                    0xFFFF  /* RTC_TIME - [15:0] */
> +#define WM831X_RTC_TIME_SHIFT                        0  /* RTC_TIME - [15:0] */
> +#define WM831X_RTC_TIME_WIDTH                       16  /* RTC_TIME - [15:0] */
> +
> +/*
> + * R16419 (0x4023) - RTC Alarm 1
> + */
> +#define WM831X_RTC_ALM_MASK                     0xFFFF  /* RTC_ALM - [15:0] */
> +#define WM831X_RTC_ALM_SHIFT                         0  /* RTC_ALM - [15:0] */
> +#define WM831X_RTC_ALM_WIDTH                        16  /* RTC_ALM - [15:0] */
> +
> +/*
> + * R16420 (0x4024) - RTC Alarm 2
> + */
> +#define WM831X_RTC_ALM_MASK                     0xFFFF  /* RTC_ALM - [15:0] */
> +#define WM831X_RTC_ALM_SHIFT                         0  /* RTC_ALM - [15:0] */
> +#define WM831X_RTC_ALM_WIDTH                        16  /* RTC_ALM - [15:0] */
> +
> +/*
> + * R16421 (0x4025) - RTC Control
> + */
> +#define WM831X_RTC_VALID                        0x8000  /* RTC_VALID */
> +#define WM831X_RTC_VALID_MASK                   0x8000  /* RTC_VALID */
> +#define WM831X_RTC_VALID_SHIFT                      15  /* RTC_VALID */
> +#define WM831X_RTC_VALID_WIDTH                       1  /* RTC_VALID */
> +#define WM831X_RTC_SYNC_BUSY                    0x4000  /* RTC_SYNC_BUSY */
> +#define WM831X_RTC_SYNC_BUSY_MASK               0x4000  /* RTC_SYNC_BUSY */
> +#define WM831X_RTC_SYNC_BUSY_SHIFT                  14  /* RTC_SYNC_BUSY */
> +#define WM831X_RTC_SYNC_BUSY_WIDTH                   1  /* RTC_SYNC_BUSY */
> +#define WM831X_RTC_ALM_ENA                      0x0400  /* RTC_ALM_ENA */
> +#define WM831X_RTC_ALM_ENA_MASK                 0x0400  /* RTC_ALM_ENA */
> +#define WM831X_RTC_ALM_ENA_SHIFT                    10  /* RTC_ALM_ENA */
> +#define WM831X_RTC_ALM_ENA_WIDTH                     1  /* RTC_ALM_ENA */
> +#define WM831X_RTC_PINT_FREQ_MASK               0x0070  /* RTC_PINT_FREQ - [6:4] */
> +#define WM831X_RTC_PINT_FREQ_SHIFT                   4  /* RTC_PINT_FREQ - [6:4] */
> +#define WM831X_RTC_PINT_FREQ_WIDTH                   3  /* RTC_PINT_FREQ - [6:4] */
> +
> +/*
> + * R16422 (0x4026) - RTC Trim
> + */
> +#define WM831X_RTC_TRIM_MASK                    0x03FF  /* RTC_TRIM - [9:0] */
> +#define WM831X_RTC_TRIM_SHIFT                        0  /* RTC_TRIM - [9:0] */
> +#define WM831X_RTC_TRIM_WIDTH                       10  /* RTC_TRIM - [9:0] */
> +
> +#define WM831X_SET_TIME_RETRIES	5
> +#define WM831X_GET_TIME_RETRIES	5

 given you have your own include directory undef mfd/
 you might want to move those #defines there


> +struct wm831x_rtc {
> +	struct wm831x *wm831x;
> +	struct rtc_device *rtc;
> +	int alarm_enabled;
> +	int per_irq;

 are those tows int or unsigned int? 
 or maybe alarm_enabled could be :1 ?

> +};
> +
> +/*
> + * Read current time and date in RTC
> + */
> +static int wm831x_rtc_readtime(struct device *dev, struct rtc_time *tm)
> +{
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
> +	struct wm831x *wm831x = wm831x_rtc->wm831x;
> +	u16 time1[2], time2[2];
> +	int ret;
> +	int count = 0;
> +
> +	/* Has the RTC been programmed? */
> +	ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to read RTC control: %d\n", ret);
> +		return ret;
> +	}
> +	if (!(ret & WM831X_RTC_VALID)) {
> +		dev_dbg(dev, "RTC not yet configured\n");
> +		return -EINVAL;
> +	}
> +
> +	/* Read twice to make sure we don't read a corrupt, partially
> +	 * incremented, value.
> +	 */
> +	do {
> +		ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1,
> +				       2, time1);
> +		if (ret != 0)
> +			continue;
> +
> +		ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1,
> +				       2, time2);
> +		if (ret != 0)
> +			continue;
> +
> +		if (memcmp(time1, time2, sizeof(time1)) == 0) {
> +			u32 time = (time1[0] << 16) | time1[1];
> +
> +			rtc_time_to_tm(time, tm);

 use return rtc_valid_tm(..

> +			return 0;
> +		}
> +
> +	} while (++count < WM831X_GET_TIME_RETRIES);
> +
> +	dev_err(dev, "Timed out reading current time\n");
> +
> +	return -EIO;
> +}
> +
> +/*
> + * Set current time and date in RTC
> + */
> +static int wm831x_rtc_settime(struct device *dev, struct rtc_time *tm)

 isn't rtc_set_mmss more appropriate?

> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
> +	struct wm831x *wm831x = wm831x_rtc->wm831x;
> +	struct rtc_time new_tm;
> +	unsigned long time, new_time;
> +	int ret;
> +	int count = 0;
> +
> +	ret = rtc_tm_to_time(tm, &time);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to convert time: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_1,
> +			       (time >> 16) & 0xffff);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to write TIME_1: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_2, time & 0xffff);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to write TIME_2: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Wait for the update to complete - should happen first time
> +	 * round but be conservative.
> +	 */
> +	do {
> +		msleep(1);
> +
> +		ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
> +		if (ret < 0)
> +			ret = WM831X_RTC_SYNC_BUSY;
> +	} while (!(ret & WM831X_RTC_SYNC_BUSY) &&
> +		 ++count < WM831X_SET_TIME_RETRIES);
> +
> +	if (ret & WM831X_RTC_SYNC_BUSY) {
> +		dev_err(dev, "Timed out writing RTC update\n");
> +		return -EIO;
> +	}
> +
> +	/* Check that the update was accepted; security features may
> +	 * have caused the update to be ignored.
> +	 */
> +	ret = wm831x_rtc_readtime(dev, &new_tm);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = rtc_tm_to_time(&new_tm, &new_time);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to convert time: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Allow a second of change in case of tick */
> +	if (new_time - time > 1) {
> +		dev_err(dev, "RTC update not permitted by hardware\n");
> +		return -EPERM;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * Read alarm time and date in RTC
> + */
> +static int wm831x_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
> +	int ret;
> +	u16 data[2];
> +	u32 time;
> +
> +	ret = wm831x_bulk_read(wm831x_rtc->wm831x, WM831X_RTC_ALARM_1,
> +			       2, data);
> +	if (ret != 0) {
> +		dev_err(dev, "Failed to read alarm time: %d\n", ret);
> +		return ret;
> +	}
> +
> +	time = (data[0] << 16) | data[1];
> +
> +	rtc_time_to_tm(time, &alrm->time);
> +
> +	ret = wm831x_reg_read(wm831x_rtc->wm831x, WM831X_RTC_CONTROL);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to read RTC control: %d\n", ret);
> +		return ret;
> +	}
> +
> +	if (ret & WM831X_RTC_ALM_ENA)
> +		alrm->enabled = 1;
> +	else
> +		alrm->enabled = 0;
> +
> +	return 0;
> +}
> +
> +static int wm831x_rtc_stop_alarm(struct wm831x_rtc *wm831x_rtc)
> +{
> +	wm831x_rtc->alarm_enabled = 0;
> +
> +	return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> +			       WM831X_RTC_ALM_ENA, 0);
> +}
> +
> +static int wm831x_rtc_start_alarm(struct wm831x_rtc *wm831x_rtc)
> +{
> +	wm831x_rtc->alarm_enabled = 1;
> +
> +	return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> +			       WM831X_RTC_ALM_ENA, WM831X_RTC_ALM_ENA);
> +}
> +
> +static int wm831x_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
> +	struct wm831x *wm831x = wm831x_rtc->wm831x;
> +	int ret;
> +	unsigned long time;
> +
> +	ret = rtc_tm_to_time(&alrm->time, &time);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to convert time: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wm831x_rtc_stop_alarm(wm831x_rtc);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to stop alarm: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_1,
> +			       (time >> 16) & 0xffff);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to write ALARM_1: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_2, time & 0xffff);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to write ALARM_2: %d\n", ret);
> +		return ret;
> +	}
> +
> +	if (alrm->enabled) {
> +		ret = wm831x_rtc_start_alarm(wm831x_rtc);
> +		if (ret < 0) {
> +			dev_err(dev, "Failed to start alarm: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int wm831x_rtc_alarm_irq_enable(struct device *dev,
> +				       unsigned int enabled)
> +{
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
> +
> +	if (enabled)
> +		return wm831x_rtc_start_alarm(wm831x_rtc);
> +	else
> +		return wm831x_rtc_stop_alarm(wm831x_rtc);
> +}
> +
> +static int wm831x_rtc_update_irq_enable(struct device *dev,
> +					unsigned int enabled)
> +{
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
> +	int val;
> +
> +	if (enabled)
> +		val = 1 << WM831X_RTC_PINT_FREQ_SHIFT;
> +	else
> +		val = 0;
> +
> +	return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> +			       WM831X_RTC_PINT_FREQ_MASK, val);
> +}
> +
> +static irqreturn_t wm831x_alm_irq(int irq, void *data)
> +{
> +	struct wm831x_rtc *wm831x_rtc = data;
> +
> +	rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_AF);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t wm831x_per_irq(int irq, void *data)
> +{
> +	struct wm831x_rtc *wm831x_rtc = data;
> +
> +	rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_UF);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static const struct rtc_class_ops wm831x_rtc_ops = {
> +	.read_time = wm831x_rtc_readtime,
> +	.set_time = wm831x_rtc_settime,
> +	.read_alarm = wm831x_rtc_readalarm,
> +	.set_alarm = wm831x_rtc_setalarm,
> +	.alarm_irq_enable = wm831x_rtc_alarm_irq_enable,
> +	.update_irq_enable = wm831x_rtc_update_irq_enable,
> +};
> +
> +#ifdef CONFIG_PM
> +/* Turn off the alarm if it should not be a wake source. */
> +static int wm831x_rtc_suspend(struct device *dev)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
> +	int ret, enable;
> +
> +	if (wm831x_rtc->alarm_enabled && device_may_wakeup(&pdev->dev))
> +		enable = WM831X_RTC_ALM_ENA;
> +	else
> +		enable = 0;
> +
> +	ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> +			      WM831X_RTC_ALM_ENA, enable);
> +	if (ret != 0)
> +		dev_err(&pdev->dev, "Failed to update RTC alarm: %d\n", ret);
> +
> +	return 0;

 always 0 ? (also below..)

> +}
> +
> +/* Enable the alarm if it should be enabled (in case it was disabled to
> + * prevent use as a wake source).
> + */
> +static int wm831x_rtc_resume(struct device *dev)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
> +	int ret;
> +
> +	if (wm831x_rtc->alarm_enabled) {
> +		ret = wm831x_rtc_start_alarm(wm831x_rtc);
> +		if (ret != 0)
> +			dev_err(&pdev->dev,
> +				"Failed to restart RTC alarm: %d\n", ret);
> +	}
> +
> +	return 0;
> +}
> +
> +/* Unconditionally disable the alarm */
> +static int wm831x_rtc_freeze(struct device *dev)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
> +	int ret;
> +
> +	ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> +			      WM831X_RTC_ALM_ENA, 0);
> +	if (ret != 0)
> +		dev_err(&pdev->dev, "Failed to stop RTC alarm: %d\n", ret);
> +
> +	return 0;
> +}
> +#else
> +#define wm831x_rtc_suspend NULL
> +#define wm831x_rtc_resume NULL
> +#define wm831x_rtc_freeze NULL
> +#endif
> +
> +static int wm831x_rtc_probe(struct platform_device *pdev)
> +{
> +	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
> +	struct wm831x_rtc *wm831x_rtc;
> +	int per_irq = platform_get_irq_byname(pdev, "PER");
> +	int alm_irq = platform_get_irq_byname(pdev, "ALM");
> +	int ret = 0;
> +
> +	wm831x_rtc = kzalloc(sizeof(*wm831x_rtc), GFP_KERNEL);
> +	if (wm831x_rtc == NULL)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, wm831x_rtc);
> +	wm831x_rtc->wm831x = wm831x;
> +	wm831x_rtc->per_irq = per_irq;
> +
> +	ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "Failed to read RTC control: %d\n", ret);
> +		goto err;
> +	}
> +	if (ret & WM831X_RTC_ALM_ENA)
> +		wm831x_rtc->alarm_enabled = 1;
> +
> +	device_init_wakeup(&pdev->dev, 1);
> +
> +	wm831x_rtc->rtc = rtc_device_register("wm831x", &pdev->dev,
> +					      &wm831x_rtc_ops, THIS_MODULE);
> +	if (IS_ERR(wm831x_rtc->rtc)) {
> +		ret = PTR_ERR(wm831x_rtc->rtc);
> +		dev_err(&pdev->dev, "Failed to register RTC: %d\n", ret);

 useless, rtc core will emit his own message.

> +		goto err;
> +	}
> +
> +	ret = wm831x_request_irq(wm831x, per_irq, wm831x_per_irq,
> +				 IRQF_TRIGGER_RISING, "wm831x_rtc_per",
> +				 wm831x_rtc);
> +	if (ret != 0) {
> +		dev_err(&pdev->dev, "Failed to request periodic IRQ %d: %d\n",
> +			per_irq, ret);

 can't the rtc work without the periodic irq?

> +		goto err_rtc;
> +	}
> +
> +	ret = wm831x_request_irq(wm831x, alm_irq, wm831x_alm_irq,
> +				 IRQF_TRIGGER_RISING, "wm831x_rtc_alm",
> +				 wm831x_rtc);
> +	if (ret != 0) {
> +		dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n",
> +			alm_irq, ret);

 ditto.

> +		goto err_per;
> +	}
> +
> +	return 0;
> +
> +err_per:
> +	wm831x_free_irq(wm831x, per_irq, wm831x_rtc);
> +err_rtc:
> +	rtc_device_unregister(wm831x_rtc->rtc);
> +err:
> +	kfree(wm831x_rtc);
> +	return ret;
> +}
> +
> +static int __devexit wm831x_rtc_remove(struct platform_device *pdev)
> +{
> +	struct wm831x_rtc *wm831x_rtc = platform_get_drvdata(pdev);
> +	int per_irq = platform_get_irq(pdev, 0);
> +	int alm_irq = platform_get_irq(pdev, 1);

 why not _byname() ?

> +
> +	wm831x_free_irq(wm831x_rtc->wm831x, alm_irq, wm831x_rtc);
> +	wm831x_free_irq(wm831x_rtc->wm831x, per_irq, wm831x_rtc);
> +	rtc_device_unregister(wm831x_rtc->rtc);
> +	kfree(wm831x_rtc);
> +
> +	return 0;
> +}
> +
> +static struct dev_pm_ops wm831x_rtc_pm_ops = {
> +	.suspend = wm831x_rtc_suspend,
> +	.resume = wm831x_rtc_resume,
> +
> +	.freeze = wm831x_rtc_freeze,
> +	.thaw = wm831x_rtc_resume,
> +	.restore = wm831x_rtc_resume,
> +
> +	.poweroff = wm831x_rtc_suspend,
> +};
> +
> +static struct platform_driver wm831x_rtc_driver = {
> +	.probe = wm831x_rtc_probe,
> +	.remove = __devexit_p(wm831x_rtc_remove),
> +	.driver = {
> +		.name = "wm831x-rtc",
> +		.pm = &wm831x_rtc_pm_ops,
> +	},
> +};
> +
> +static int __init wm831x_rtc_init(void)
> +{
> +	return platform_driver_register(&wm831x_rtc_driver);

 can you use platform_driver_probe() ?

> +}
> +module_init(wm831x_rtc_init);
> +
> +static void __exit wm831x_rtc_exit(void)
> +{
> +	platform_driver_unregister(&wm831x_rtc_driver);
> +}
> +module_exit(wm831x_rtc_exit);
> +
> +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
> +MODULE_DESCRIPTION("RTC driver for the WM831x series PMICs");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:wm831x-rtc");
> -- 
> 1.6.3.3
> 
> 
> --~--~---------~--~----~------------~-------~--~----~
> You received this message because you are subscribed to "rtc-linux".
> Membership options at http://groups.google.com/group/rtc-linux .
> Please read http://groups.google.com/group/rtc-linux/web/checklist
> before submitting a driver.
> -~----------~----~----~----~------~----~------~--~---
> 


-- 

 Best regards,

 Alessandro Zummo,
  Tower Technologies - Torino, Italy

  http://www.towertech.it


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [rtc-linux] [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices
  2009-08-10 18:55   ` [rtc-linux] " Alessandro Zummo
@ 2009-08-10 20:57     ` Mark Brown
  2009-08-10 21:08       ` [rtc-linux] " Alessandro Zummo
  0 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2009-08-10 20:57 UTC (permalink / raw)
  To: Alessandro Zummo; +Cc: rtc-linux, Samuel Ortiz, linux-kernel, Alessandro Zummo

On Mon, Aug 10, 2009 at 08:55:35PM +0200, Alessandro Zummo wrote:
> Mark Brown <broonie@opensource.wolfsonmicro.com> wrote:

>  given you have your own include directory undef mfd/
>  you might want to move those #defines there

Unlike the others these registers can't really be used outside of this
driver - for the other drivers there's potential for at least platform
specific code if not multiple drivers to use some or all of the register
definitions.

> > +struct wm831x_rtc {
> > +	struct wm831x *wm831x;
> > +	struct rtc_device *rtc;
> > +	int alarm_enabled;
> > +	int per_irq;

>  are those tows int or unsigned int? 

I've just dropped per_irq, it's not needed anyway.

>  or maybe alarm_enabled could be :1 ?

Done, but it doesn't really buy us much given that there's nothing
else to pack it with.

> > +/*
> > + * Set current time and date in RTC
> > + */
> > +static int wm831x_rtc_settime(struct device *dev, struct rtc_time *tm)

>  isn't rtc_set_mmss more appropriate?

Hrm, I think so so I've made the change.  It's not a particularly
discoverable API - the fact that there's no readback equivalent hides
the fact that it's supposed to be an equivalent for settime.  Some
documentation would really help here.

> > +	ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> > +			      WM831X_RTC_ALM_ENA, enable);
> > +	if (ret != 0)
> > +		dev_err(&pdev->dev, "Failed to update RTC alarm: %d\n", ret);
> > +
> > +	return 0;

>  always 0 ? (also below..)

Failing suspend and resume due to failure to disable the RTC alarm
would be excessive - indeed, I'd expect the overwhelming majority of
systems to function perfectly well with no suspend/resume support in the
driver at all.  RTC alarms are infrequent relative to other
suspend/resume events in typical systems but suspend is normally used
fairly heavily to preserve battery (typical applications include things
like MP3 players or phones).  Typically the error handling would at best
cause more serious consequences than the original error and there's
little chance the user will be able to even report the error.

> > +static int __init wm831x_rtc_init(void)
> > +{
> > +	return platform_driver_register(&wm831x_rtc_driver);

>  can you use platform_driver_probe() ?

No, this is a MFD accessed over slow buses and we can't guarantee that
the device will be registered.

Fixed everything else, will repost tomorrow.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [rtc-linux] Re: [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices
  2009-08-10 20:57     ` Mark Brown
@ 2009-08-10 21:08       ` Alessandro Zummo
  2009-08-16 22:35         ` Samuel Ortiz
  0 siblings, 1 reply; 16+ messages in thread
From: Alessandro Zummo @ 2009-08-10 21:08 UTC (permalink / raw)
  To: rtc-linux; +Cc: broonie, Samuel Ortiz, linux-kernel, akpm

On Mon, 10 Aug 2009 21:57:13 +0100
Mark Brown <broonie@opensource.wolfsonmicro.com> wrote:

> 
> >  given you have your own include directory undef mfd/
> >  you might want to move those #defines there
> 
> Unlike the others these registers can't really be used outside of this
> driver - for the other drivers there's potential for at least platform
> specific code if not multiple drivers to use some or all of the register
> definitions.

 ack.
 
> > > +struct wm831x_rtc {
> > > +	struct wm831x *wm831x;
> > > +	struct rtc_device *rtc;
> > > +	int alarm_enabled;
> > > +	int per_irq;
> 
> >  are those tows int or unsigned int? 
> 
> I've just dropped per_irq, it's not needed anyway.
> 
> >  or maybe alarm_enabled could be :1 ?
> 
> Done, but it doesn't really buy us much given that there's nothing
> else to pack it with.

 ack.

> > > +/*
> > > + * Set current time and date in RTC
> > > + */
> > > +static int wm831x_rtc_settime(struct device *dev, struct rtc_time *tm)
> 
> >  isn't rtc_set_mmss more appropriate?
> 
> Hrm, I think so so I've made the change.  It's not a particularly
> discoverable API - the fact that there's no readback equivalent hides
> the fact that it's supposed to be an equivalent for settime.  Some
> documentation would really help here.

 you're right. there's one but it's still in my tree. will push it
 one day.

> 
> > > +	ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> > > +			      WM831X_RTC_ALM_ENA, enable);
> > > +	if (ret != 0)
> > > +		dev_err(&pdev->dev, "Failed to update RTC alarm: %d\n", ret);
> > > +
> > > +	return 0;
> 
> >  always 0 ? (also below..)
> 
> Failing suspend and resume due to failure to disable the RTC alarm
> would be excessive - indeed, I'd expect the overwhelming majority of
> systems to function perfectly well with no suspend/resume support in the
> driver at all.  RTC alarms are infrequent relative to other
> suspend/resume events in typical systems but suspend is normally used
> fairly heavily to preserve battery (typical applications include things
> like MP3 players or phones).  Typically the error handling would at best
> cause more serious consequences than the original error and there's
> little chance the user will be able to even report the error.

 ack.

> > > +static int __init wm831x_rtc_init(void)
> > > +{
> > > +	return platform_driver_register(&wm831x_rtc_driver);
> 
> >  can you use platform_driver_probe() ?
> 
> No, this is a MFD accessed over slow buses and we can't guarantee that
> the device will be registered.
> 
> Fixed everything else, will repost tomorrow.

 here's my acked-by in advance, feel free to push the patch
 with the series.

 Acked-by: Alessandro Zummo <a.zummo@towertech.it>

 

-- 

 Best regards,

 Alessandro Zummo,
  Tower Technologies - Torino, Italy

  http://www.towertech.it


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog
  2009-08-10 16:43 ` [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog Mark Brown
@ 2009-08-11 12:40   ` Wim Van Sebroeck
  2009-08-16 13:28   ` Wim Van Sebroeck
  2009-08-17 12:13   ` Wim Van Sebroeck
  2 siblings, 0 replies; 16+ messages in thread
From: Wim Van Sebroeck @ 2009-08-11 12:40 UTC (permalink / raw)
  To: Mark Brown; +Cc: Samuel Ortiz, linux-kernel, Wim Van Sebroeck

Hi Mark,

> The WM831x series of devices provide a watchdog with configurable
> behaviour on timer expiry.
> 
> Currently this driver support refreshes via a register or GPIO line and
> autonomous refreshes from a hardware source (eg, a clock).
> 
> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Wim Van Sebroeck <wim@iguana.be>

Will review this the coming week.

Kind regards,
Wim.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs
  2009-08-10 16:43 ` [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs Mark Brown
@ 2009-08-14  2:00   ` Anton Vorontsov
  2009-08-14 16:49     ` Mark Brown
  0 siblings, 1 reply; 16+ messages in thread
From: Anton Vorontsov @ 2009-08-14  2:00 UTC (permalink / raw)
  To: Mark Brown; +Cc: Samuel Ortiz, linux-kernel, Anton Vorontsov

On Mon, Aug 10, 2009 at 05:43:53PM +0100, Mark Brown wrote:
> The WM831x PMICs provide power path management from three sources:
> a wall supply, USB and a battery with integrated charger. They also
> provide an additional backup supply with integrated for maintaining
> always on functionality such as the RTC and monitoring of power
> switches.
> 
> After some initial configuration at startup the device operates
> autonomously, the driver simply provides reporting of the current
> state.
> 
> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>

I don't see the rest of the series, so I assume this patch
depends on some other patches. So, feel free to merge it through
your tree.

Acked-by: Anton Vorontsov <avorontsov@ru.mvista.com>


Thanks!

-- 
Anton Vorontsov
email: cbouatmailru@gmail.com
irc://irc.freenode.net/bd2

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs
  2009-08-14  2:00   ` Anton Vorontsov
@ 2009-08-14 16:49     ` Mark Brown
  2009-08-14 16:53       ` Anton Vorontsov
  0 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2009-08-14 16:49 UTC (permalink / raw)
  To: Anton Vorontsov; +Cc: Samuel Ortiz, linux-kernel, Anton Vorontsov

On Fri, Aug 14, 2009 at 06:00:08AM +0400, Anton Vorontsov wrote:

> I don't see the rest of the series, so I assume this patch
> depends on some other patches. So, feel free to merge it through
> your tree.

> Acked-by: Anton Vorontsov <avorontsov@ru.mvista.com>

Actually the only merge dependency is on the power tree - the Kconfig
dependency on MFD_WM831X will prevent it being built until the MFD
dependencies are but the driver uses the new charge_type property from
the power tree.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs
  2009-08-14 16:49     ` Mark Brown
@ 2009-08-14 16:53       ` Anton Vorontsov
  0 siblings, 0 replies; 16+ messages in thread
From: Anton Vorontsov @ 2009-08-14 16:53 UTC (permalink / raw)
  To: Mark Brown; +Cc: Samuel Ortiz, linux-kernel, Anton Vorontsov

On Fri, Aug 14, 2009 at 05:49:52PM +0100, Mark Brown wrote:
> On Fri, Aug 14, 2009 at 06:00:08AM +0400, Anton Vorontsov wrote:
> 
> > I don't see the rest of the series, so I assume this patch
> > depends on some other patches. So, feel free to merge it through
> > your tree.
> 
> > Acked-by: Anton Vorontsov <avorontsov@ru.mvista.com>
> 
> Actually the only merge dependency is on the power tree - the Kconfig
> dependency on MFD_WM831X will prevent it being built until the MFD
> dependencies are but the driver uses the new charge_type property from
> the power tree.

OK, then I'll apply it.

Thanks again.

-- 
Anton Vorontsov
email: cbouatmailru@gmail.com
irc://irc.freenode.net/bd2

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog
  2009-08-10 16:43 ` [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog Mark Brown
  2009-08-11 12:40   ` Wim Van Sebroeck
@ 2009-08-16 13:28   ` Wim Van Sebroeck
  2009-08-16 16:27     ` Mark Brown
  2009-08-17 12:13   ` Wim Van Sebroeck
  2 siblings, 1 reply; 16+ messages in thread
From: Wim Van Sebroeck @ 2009-08-16 13:28 UTC (permalink / raw)
  To: Mark Brown; +Cc: Samuel Ortiz, linux-kernel

Hi Mark,

> The WM831x series of devices provide a watchdog with configurable
> behaviour on timer expiry.
> 
> Currently this driver support refreshes via a register or GPIO line and
> autonomous refreshes from a hardware source (eg, a clock).
> 
> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Wim Van Sebroeck <wim@iguana.be>

the driver is OK. Just 2 small remarks:
> +static struct watchdog_info ident = {
> +	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
> +	.identity = "WM831x Watchdog",
> +};

this can be static const struct in the future (need to do a clean-up on that).

> +#define WM831X_WDOG_SECACT_MASK                 0x0300  /* WDOG_SECACT - [9:8] */
> +#define WM831X_WDOG_SECACT_SHIFT                     8  /* WDOG_SECACT - [9:8] */
> +#define WM831X_WDOG_SECACT_WIDTH                     2  /* WDOG_SECACT - [9:8] */
> +#define WM831X_WDOG_PRIMACT_MASK                0x0030  /* WDOG_PRIMACT - [5:4] */
> +#define WM831X_WDOG_PRIMACT_SHIFT                    4  /* WDOG_PRIMACT - [5:4] */
> +#define WM831X_WDOG_PRIMACT_WIDTH                    2  /* WDOG_PRIMACT - [5:4] */

These lines exceed 80 characters. Is there any reason why we can't reduce the spacing between the name and the value?

Just let me know how we will proceed on my second question. I can then add this to the linux-2.6-watchdog-next tree.

Kind regards,
Wim.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog
  2009-08-16 13:28   ` Wim Van Sebroeck
@ 2009-08-16 16:27     ` Mark Brown
  0 siblings, 0 replies; 16+ messages in thread
From: Mark Brown @ 2009-08-16 16:27 UTC (permalink / raw)
  To: Wim Van Sebroeck; +Cc: Samuel Ortiz, linux-kernel

On Sun, Aug 16, 2009 at 03:28:12PM +0200, Wim Van Sebroeck wrote:

> > +#define WM831X_WDOG_PRIMACT_WIDTH                    2  /* WDOG_PRIMACT - [5:4] */

> These lines exceed 80 characters. Is there any reason why we can't reduce the spacing between the name and the value?

> Just let me know how we will proceed on my second question. I can then add this to the linux-2.6-watchdog-next tree.

The register definitions are automatically generated using the chip
design information using a tool which I'm not the only user for.  It
simplifies maintinance to keep the output code as-is since it allows
easier comparison with future devices with the same or similar IP in
them.  If anything were done I'd remove the comments.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [rtc-linux] Re: [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices
  2009-08-10 21:08       ` [rtc-linux] " Alessandro Zummo
@ 2009-08-16 22:35         ` Samuel Ortiz
  0 siblings, 0 replies; 16+ messages in thread
From: Samuel Ortiz @ 2009-08-16 22:35 UTC (permalink / raw)
  To: Alessandro Zummo; +Cc: rtc-linux, broonie, linux-kernel, akpm

Hi Alessandro,

On Mon, Aug 10, 2009 at 11:08:24PM +0200, Alessandro Zummo wrote:
> On Mon, 10 Aug 2009 21:57:13 +0100
> Mark Brown <broonie@opensource.wolfsonmicro.com> wrote:
> 
> > 
> > >  given you have your own include directory undef mfd/
> > >  you might want to move those #defines there
> > 
> > Unlike the others these registers can't really be used outside of this
> > driver - for the other drivers there's potential for at least platform
> > specific code if not multiple drivers to use some or all of the register
> > definitions.
> 
>  ack.
>  
> > > > +struct wm831x_rtc {
> > > > +	struct wm831x *wm831x;
> > > > +	struct rtc_device *rtc;
> > > > +	int alarm_enabled;
> > > > +	int per_irq;
> > 
> > >  are those tows int or unsigned int? 
> > 
> > I've just dropped per_irq, it's not needed anyway.
> > 
> > >  or maybe alarm_enabled could be :1 ?
> > 
> > Done, but it doesn't really buy us much given that there's nothing
> > else to pack it with.
> 
>  ack.
> 
> > > > +/*
> > > > + * Set current time and date in RTC
> > > > + */
> > > > +static int wm831x_rtc_settime(struct device *dev, struct rtc_time *tm)
> > 
> > >  isn't rtc_set_mmss more appropriate?
> > 
> > Hrm, I think so so I've made the change.  It's not a particularly
> > discoverable API - the fact that there's no readback equivalent hides
> > the fact that it's supposed to be an equivalent for settime.  Some
> > documentation would really help here.
> 
>  you're right. there's one but it's still in my tree. will push it
>  one day.
> 
> > 
> > > > +	ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
> > > > +			      WM831X_RTC_ALM_ENA, enable);
> > > > +	if (ret != 0)
> > > > +		dev_err(&pdev->dev, "Failed to update RTC alarm: %d\n", ret);
> > > > +
> > > > +	return 0;
> > 
> > >  always 0 ? (also below..)
> > 
> > Failing suspend and resume due to failure to disable the RTC alarm
> > would be excessive - indeed, I'd expect the overwhelming majority of
> > systems to function perfectly well with no suspend/resume support in the
> > driver at all.  RTC alarms are infrequent relative to other
> > suspend/resume events in typical systems but suspend is normally used
> > fairly heavily to preserve battery (typical applications include things
> > like MP3 players or phones).  Typically the error handling would at best
> > cause more serious consequences than the original error and there's
> > little chance the user will be able to even report the error.
> 
>  ack.
> 
> > > > +static int __init wm831x_rtc_init(void)
> > > > +{
> > > > +	return platform_driver_register(&wm831x_rtc_driver);
> > 
> > >  can you use platform_driver_probe() ?
> > 
> > No, this is a MFD accessed over slow buses and we can't guarantee that
> > the device will be registered.
> > 
> > Fixed everything else, will repost tomorrow.
> 
>  here's my acked-by in advance, feel free to push the patch
>  with the series.
> 
>  Acked-by: Alessandro Zummo <a.zummo@towertech.it>
Thanks, patch applied.

Cheers,
Samuel.

 
>  
> 
> -- 
> 
>  Best regards,
> 
>  Alessandro Zummo,
>   Tower Technologies - Torino, Italy
> 
>   http://www.towertech.it
> 

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog
  2009-08-10 16:43 ` [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog Mark Brown
  2009-08-11 12:40   ` Wim Van Sebroeck
  2009-08-16 13:28   ` Wim Van Sebroeck
@ 2009-08-17 12:13   ` Wim Van Sebroeck
  2 siblings, 0 replies; 16+ messages in thread
From: Wim Van Sebroeck @ 2009-08-17 12:13 UTC (permalink / raw)
  To: Mark Brown; +Cc: Samuel Ortiz, linux-kernel

Hi Mark,

> The WM831x series of devices provide a watchdog with configurable
> behaviour on timer expiry.
> 
> Currently this driver support refreshes via a register or GPIO line and
> autonomous refreshes from a hardware source (eg, a clock).

Added this driver to the linux-2.6-watchdog-next tree.

Kind regards,
Wim.


^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2009-08-17 12:13 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-08-10 16:43 [PATCH 1/5] backlight: Add WM831x backlight driver Mark Brown
2009-08-10 16:43 ` [PATCH 2/5] leds: Add WM831x status LED driver Mark Brown
2009-08-10 16:43 ` [PATCH 3/5] power_supply: Add driver for the PMU on WM831x PMICs Mark Brown
2009-08-14  2:00   ` Anton Vorontsov
2009-08-14 16:49     ` Mark Brown
2009-08-14 16:53       ` Anton Vorontsov
2009-08-10 16:43 ` [PATCH 4/5] RTC: Add support for RTCs on Wolfson WM831x devices Mark Brown
2009-08-10 18:55   ` [rtc-linux] " Alessandro Zummo
2009-08-10 20:57     ` Mark Brown
2009-08-10 21:08       ` [rtc-linux] " Alessandro Zummo
2009-08-16 22:35         ` Samuel Ortiz
2009-08-10 16:43 ` [PATCH 5/5] [WATCHDOG] Add support for WM831x watchdog Mark Brown
2009-08-11 12:40   ` Wim Van Sebroeck
2009-08-16 13:28   ` Wim Van Sebroeck
2009-08-16 16:27     ` Mark Brown
2009-08-17 12:13   ` Wim Van Sebroeck

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.