All of lore.kernel.org
 help / color / mirror / Atom feed
From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
To: linux-kernel@vger.kernel.org, rtc-linux@googlegroups.com,
	lm-sensors@lm-sensors.org, linux-input@vger.kernel.org,
	linux-watchdog@vger.kernel.org, linux-leds@vger.kernel.org
Cc: Alessandro Zummo <a.zummo@towertech.it>,
	Andrew Jones <drjones@redhat.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Samuel Ortiz <sameo@linux.intel.com>,
	Ashish Jangam <ashish.jangam@kpitcummins.com>,
	Mark Brown <broonie@opensource.wolfsonmicro.com>,
	Donggeun Kim <dg77.kim@samsung.com>,
	Wim Van Sebroeck <wim@iguana.be>,
	"Richard Purdie <rpurdie@rpsys.net> Anthony Olech"
	<anthony.olech@diasemi.com>, Bryan Wu <bryan.wu@canonical.com>,
	Liam Girdwood <lrg@ti.com>
Subject: [RFC PATCH 8/8] leds: Add DA906x PMIC LED driver.
Date: Fri, 24 Aug 2012 15:25:00 +0100	[thread overview]
Message-ID: <201208241525@sw-eng-lt-dc-vm2> (raw)
In-Reply-To: <201208241520@sw-eng-lt-dc-vm2>

PMIC can control three LEDs, devoted to work as RGB LED.

Beside standard brightness setting, the driver provides some hardware blinking
functionality. Driver brings access to blinking register fields inside PMIC
with LED attributes: hw_blink_dur_regval and hw_blink_frq_regval. When the
latter is not 0, hardware blinking is enabled. As long as hardware blinking
is enabled, brightness is set to its maximum.

Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
---
 drivers/leds/Kconfig       |    8 +
 drivers/leds/Makefile      |    1 +
 drivers/leds/leds-da906x.c |  438 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 447 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-da906x.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index c96bbaa..46dc3dd 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -287,6 +287,14 @@ config LEDS_DA9052
 	  This option enables support for on-chip LED drivers found
 	  on Dialog Semiconductor DA9052-BC and DA9053-AA/Bx PMICs.
 
+config LEDS_DA906X
+        tristate "Dialog DA906x LEDS"
+        depends on LEDS_CLASS
+        depends on MFD_DA906X
+        help
+          This option enables support for on-chip LED drivers found
+          on Dialog Semiconductor DA906x PMICs
+
 config LEDS_DAC124S085
 	tristate "LED Support for DAC124S085 SPI DAC"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index a4429a9..1601366 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA9633)		+= leds-pca9633.o
 obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)		+= leds-da9052.o
+obj-$(CONFIG_LEDS_DA906X)		+= leds-da906x.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-da906x.c b/drivers/leds/leds-da906x.c
new file mode 100644
index 0000000..3203fc6
--- /dev/null
+++ b/drivers/leds/leds-da906x.c
@@ -0,0 +1,438 @@
+/*
+ * LEDs driver for Dialog DA906x PMIC series
+ *
+ * Copyright 2012 Dialog Semiconductors Ltd.
+ *
+ * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>,
+ *         Krystian Garbaciak <krystian.garbaciak@diasemi.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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/mfd/da906x/core.h>
+#include <linux/mfd/da906x/pdata.h>
+
+#define DRIVER_NAME		DA906X_DRVNAME_LEDS
+
+#define	DA906X_GPIO_LED_NUM	3
+
+#define DA906X_PWM_MAX		(DA906X_GPIO_PWM_MASK >> DA906X_GPIO_PWM_SHIFT)
+#define DA906X_MAX_BRIGHTNESS	0x5F
+#define DA906X_BLINK_DUR_MAX	(DA906X_BLINK_DUR_MASK >> \
+							DA906X_BLINK_DUR_SHIFT)
+#define DA906X_BLINK_FRQ_MAX	(DA906X_BLINK_FRQ_MASK >> \
+							DA906X_BLINK_FRQ_SHIFT)
+
+struct da906x_led {
+	int id;
+	struct led_classdev cdev;
+	struct work_struct work;
+	struct da906x *da906x;
+	struct da906x_leds *leds_container;
+
+	int pwm_regval;
+};
+
+struct da906x_leds {
+	int n_leds;
+	/* Array size to be defined during init. Keep at end. */
+	struct da906x_led led[0];
+};
+
+int da906x_led_pwm_reg[DA906X_LED_NUM] = {
+	DA906X_REG_GPO11_LED,
+	DA906X_REG_GPO14_LED,
+	DA906X_REG_GPO15_LED,
+};
+
+static int da906x_led_get_hw_blinking(struct da906x *da906x,
+				      unsigned *frq, unsigned *dur)
+{
+	int ret = da906x_reg_read(da906x, DA906X_REG_CONTROL_D);
+	if (ret < 0)
+		return ret;
+
+	if (frq)
+		*frq = (ret & DA906X_BLINK_FRQ_MASK) >> DA906X_BLINK_FRQ_SHIFT;
+
+	if (dur)
+		*dur = (ret & DA906X_BLINK_DUR_MASK) >> DA906X_BLINK_DUR_SHIFT;
+
+	return 0;
+}
+
+static int
+da906x_led_set_hw_bright(struct da906x *da906x, int id, int pwm_val)
+{
+	unsigned int blink_frq;
+	int ret;
+
+	ret = da906x_led_get_hw_blinking(da906x, &blink_frq, NULL);
+	if (ret)
+		return ret;
+
+	/* For enabled blinking, brightness setting is unavailable */
+	if (!blink_frq)
+		ret = da906x_reg_write(da906x, da906x_led_pwm_reg[id],
+				       pwm_val);
+
+	return ret;
+}
+
+static inline int
+da906x_led_get_hw_bright(struct da906x *da906x, int id)
+{
+	return da906x_reg_read(da906x, da906x_led_pwm_reg[id]);
+}
+
+static void da906x_led_brightness_set(struct led_classdev *cdev,
+	enum led_brightness brightness)
+{
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+
+	if (led->pwm_regval != brightness) {
+		led->pwm_regval = brightness;
+		schedule_work(&led->work);
+	}
+}
+
+static void da906x_led_brightness_work(struct work_struct *work)
+{
+	struct da906x_led *led = container_of(work, struct da906x_led, work);
+	int ret;
+
+	ret = da906x_led_set_hw_bright(led->da906x, led->id, led->pwm_regval);
+	if (ret)
+		dev_err(led->da906x->dev,
+			"Failed to set led brightness (err = %d)\n", ret);
+}
+
+static int da906x_configure_led_pin(struct da906x_led *led,
+				    int low_level_driven)
+{
+	int ret;
+	u16 gpio_cfg_reg;
+	u8 gpio_cfg_mask, gpio_cfg_val;
+	u8 mode_cfg_bit;
+
+	switch (led->id) {
+	case DA906X_GPIO11_LED:
+		gpio_cfg_reg = DA906X_REG_GPIO_10_11;
+		gpio_cfg_mask = DA906X_GPIO11_PIN_MASK;
+		gpio_cfg_val = DA906X_GPIO11_PIN_GPO_OD;
+		mode_cfg_bit = DA906X_GPIO11_MODE_LED_ACT_LOW;
+		break;
+
+	case DA906X_GPIO14_LED:
+		gpio_cfg_reg = DA906X_REG_GPIO_14_15;
+		gpio_cfg_mask = DA906X_GPIO14_PIN_MASK;
+		gpio_cfg_val = DA906X_GPIO14_PIN_GPO_OD;
+		mode_cfg_bit = DA906X_GPIO14_MODE_LED_ACT_LOW;
+		break;
+
+	case DA906X_GPIO15_LED:
+		gpio_cfg_reg = DA906X_REG_GPIO_14_15;
+		gpio_cfg_mask = DA906X_GPIO15_PIN_MASK;
+		gpio_cfg_val = DA906X_GPIO15_PIN_GPO_OD;
+		mode_cfg_bit = DA906X_GPIO15_MODE_LED_ACT_LOW;
+		break;
+
+	default:
+		BUG();
+	}
+
+	/* Configure GPOs for open drain */
+	ret = da906x_reg_update(led->da906x, gpio_cfg_reg, gpio_cfg_mask,
+				gpio_cfg_val);
+	if (ret)
+		return ret;
+
+	/* Configure active level */
+	if (low_level_driven)
+		ret = da906x_reg_set_bits(led->da906x,
+					  DA906X_REG_GPIO_MODE_8_15,
+					  mode_cfg_bit);
+	else
+		ret = da906x_reg_clear_bits(led->da906x,
+					    DA906X_REG_GPIO_MODE_8_15,
+					    mode_cfg_bit);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static ssize_t da906x_hw_blink_dur_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	unsigned long dur;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &dur);
+	if (ret || dur > DA906X_BLINK_DUR_MAX) {
+		dev_err(led->da906x->dev,
+			"Invalid parameter (valid are %d..%d)\n",
+			0, DA906X_BLINK_DUR_MAX);
+		return -EINVAL;
+	}
+
+	ret = da906x_reg_update(led->da906x, DA906X_REG_CONTROL_D,
+				DA906X_BLINK_DUR_MASK,
+				dur << DA906X_BLINK_DUR_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t da906x_hw_blink_dur_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	int ret;
+	unsigned int dur;
+
+	ret = da906x_led_get_hw_blinking(led->da906x, NULL, &dur);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%u\n", dur);
+}
+
+static ssize_t da906x_hw_blink_frq_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	struct da906x_leds *leds = led->leds_container;
+	unsigned long frq;
+	int i;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &frq);
+	if (ret || frq > DA906X_BLINK_FRQ_MAX) {
+		dev_err(led->da906x->dev,
+			"Invalid parameter (valid are %d..%d)\n",
+			0, DA906X_BLINK_FRQ_MAX);
+		return -EINVAL;
+	}
+
+	if (frq) {
+		/* Reset all LEDs brightness before blinking */
+		for (i = leds->n_leds - 1; i >= 0; i--) {
+			ret = da906x_led_set_hw_bright(led->da906x, i, 0);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	ret = da906x_reg_update(led->da906x, DA906X_REG_CONTROL_D,
+				DA906X_BLINK_FRQ_MASK,
+				frq << DA906X_BLINK_FRQ_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	if (!frq) {
+		/* Restore all LEDs brightness, when blinking is off */
+		for (i = leds->n_leds - 1; i >= 0; i--) {
+			ret = da906x_led_set_hw_bright(led->da906x, i,
+						      leds->led[i].pwm_regval);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return count;
+}
+
+static ssize_t da906x_hw_blink_frq_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	int ret;
+	unsigned int frq;
+
+	ret = da906x_led_get_hw_blinking(led->da906x, &frq, NULL);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%u\n", frq);
+}
+
+static DEVICE_ATTR(hw_blink_dur_regval, 0644,
+		   da906x_hw_blink_dur_show, da906x_hw_blink_dur_store);
+static DEVICE_ATTR(hw_blink_frq_regval, 0644,
+		   da906x_hw_blink_frq_show, da906x_hw_blink_frq_store);
+
+static int da906x_led_probe(struct platform_device *pdev)
+{
+	struct da906x *da906x;
+	struct da906x_pdata *pdata;
+	struct led_platform_data *leds_pdata;
+	struct led_info *led_info;
+	struct da906x_leds *leds;
+	struct da906x_led *led;
+	size_t size;
+	int ret;
+	int i, j;
+
+	da906x = dev_get_drvdata(pdev->dev.parent);
+	pdata = da906x->dev->platform_data;
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "No platform data\n");
+		return -ENODEV;
+	}
+
+	leds_pdata = pdata->leds_pdata;
+	if (leds_pdata == NULL) {
+		dev_err(&pdev->dev, "No platform data for LEDs\n");
+		return -ENODEV;
+	}
+
+	size = sizeof(struct da906x_leds) +
+	       sizeof(struct da906x_led) * leds_pdata->num_leds;
+	leds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	if (leds == NULL) {
+		dev_err(&pdev->dev, "Failed to alloc memory\n");
+		return -ENOMEM;
+	}
+
+	leds->n_leds = leds_pdata->num_leds;
+	platform_set_drvdata(pdev, leds);
+
+	for (i = 0; i < leds_pdata->num_leds; i++) {
+		led_info = &leds_pdata->leds[i];
+
+		if ((led_info->flags & DA906X_LED_ID_MASK) >= DA906X_LED_NUM) {
+			dev_err(&pdev->dev, "Invalid platform data\n");
+			ret = -EINVAL;
+			goto err_register;
+		}
+
+		/* Check, if LED ID is not duplicated */
+		for (led = &leds->led[0], j = 0; j < i; led++, j++) {
+			if (led->id ==
+			    (led_info->flags & DA906X_LED_ID_MASK)) {
+				dev_err(&pdev->dev, "Duplicated LED ID\n");
+				ret = -EINVAL;
+				goto err_register;
+			}
+		}
+
+		led->id = led_info->flags & DA906X_LED_ID_MASK;
+		led->pwm_regval = da906x_led_get_hw_bright(da906x, i);
+		led->cdev.brightness = led->pwm_regval;
+		led->cdev.max_brightness = DA906X_MAX_BRIGHTNESS;
+		led->cdev.brightness_set = da906x_led_brightness_set;
+		led->cdev.default_trigger = led_info->default_trigger;
+		led->cdev.name = led_info->name;
+		led->da906x = da906x;
+		led->leds_container = leds;
+
+		INIT_WORK(&led->work, da906x_led_brightness_work);
+
+		ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+		if (ret) {
+			dev_err(&pdev->dev, "Cannot register LEDs\n");
+			goto err_register;
+		}
+
+		ret = device_create_file(led->cdev.dev,
+					 &dev_attr_hw_blink_dur_regval);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to create device file (err = %d)\n",
+				ret);
+			goto err_create_dur_attr;
+		}
+
+		ret = device_create_file(led->cdev.dev,
+					 &dev_attr_hw_blink_frq_regval);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to create device file (err = %d)\n",
+				ret);
+			goto err_create_frq_attr;
+		}
+
+		ret = da906x_configure_led_pin(led,
+				led_info->flags & DA906X_LED_LOW_LEVEL_ACTIVE);
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to configure LED pins\n");
+			goto err_configure_pin;
+		}
+	}
+
+	return 0;
+
+err_register:
+	while (--i >= 0) {
+err_configure_pin:
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_frq_regval);
+err_create_frq_attr:
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_dur_regval);
+err_create_dur_attr:
+		cancel_work_sync(&leds->led[i].work);
+		led_classdev_unregister(&leds->led[i].cdev);
+	}
+
+	kfree(leds);
+
+	return ret;
+}
+
+static int da906x_led_remove(struct platform_device *pdev)
+{
+	struct da906x_leds *leds = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = leds->n_leds - 1; i >= 0; i--) {
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_frq_regval);
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_dur_regval);
+		cancel_work_sync(&leds->led[i].work);
+		led_classdev_unregister(&leds->led[i].cdev);
+	}
+
+	kfree(leds);
+
+	return 0;
+}
+
+static struct platform_driver da906x_led_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = da906x_led_probe,
+	.remove = __devexit_p(da906x_led_remove),
+};
+
+module_platform_driver(da906x_led_driver);
+
+MODULE_DESCRIPTION("LED driver for Dialog DA906X");
+MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>, Krystian Garbaciak <krystian.garbaciak@diasemi.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
1.7.0.4


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

WARNING: multiple messages have this Message-ID (diff)
From: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
To: linux-kernel@vger.kernel.org, rtc-linux@googlegroups.com,
	lm-sensors@lm-sensors.org, linux-input@vger.kernel.org,
	linux-watchdog@vger.kernel.org, linux-leds@vger.kernel.org
Cc: Alessandro Zummo <a.zummo@towertech.it>,
	Andrew Jones <drjones@redhat.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>,
	Samuel Ortiz <sameo@linux.intel.com>,
	Ashish Jangam <ashish.jangam@kpitcummins.com>,
	Mark Brown <broonie@opensource.wolfsonmicro.com>,
	Donggeun Kim <dg77.kim@samsung.com>,
	Wim Van Sebroeck <wim@iguana.be>,
	"Richard Purdie <rpurdie@rpsys.net> Anthony Olech"
	<anthony.olech@diasemi.com>, Bryan Wu <bryan.wu@canonical.com>,
	Liam Girdwood <lrg@ti.com>
Subject: [lm-sensors] [RFC PATCH 8/8] leds: Add DA906x PMIC LED driver.
Date: Fri, 24 Aug 2012 14:25:00 +0000	[thread overview]
Message-ID: <201208241525@sw-eng-lt-dc-vm2> (raw)
In-Reply-To: <201208241520@sw-eng-lt-dc-vm2>

PMIC can control three LEDs, devoted to work as RGB LED.

Beside standard brightness setting, the driver provides some hardware blinking
functionality. Driver brings access to blinking register fields inside PMIC
with LED attributes: hw_blink_dur_regval and hw_blink_frq_regval. When the
latter is not 0, hardware blinking is enabled. As long as hardware blinking
is enabled, brightness is set to its maximum.

Signed-off-by: Krystian Garbaciak <krystian.garbaciak@diasemi.com>
---
 drivers/leds/Kconfig       |    8 +
 drivers/leds/Makefile      |    1 +
 drivers/leds/leds-da906x.c |  438 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 447 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-da906x.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index c96bbaa..46dc3dd 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -287,6 +287,14 @@ config LEDS_DA9052
 	  This option enables support for on-chip LED drivers found
 	  on Dialog Semiconductor DA9052-BC and DA9053-AA/Bx PMICs.
 
+config LEDS_DA906X
+        tristate "Dialog DA906x LEDS"
+        depends on LEDS_CLASS
+        depends on MFD_DA906X
+        help
+          This option enables support for on-chip LED drivers found
+          on Dialog Semiconductor DA906x PMICs
+
 config LEDS_DAC124S085
 	tristate "LED Support for DAC124S085 SPI DAC"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index a4429a9..1601366 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA9633)		+= leds-pca9633.o
 obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)		+= leds-da9052.o
+obj-$(CONFIG_LEDS_DA906X)		+= leds-da906x.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-da906x.c b/drivers/leds/leds-da906x.c
new file mode 100644
index 0000000..3203fc6
--- /dev/null
+++ b/drivers/leds/leds-da906x.c
@@ -0,0 +1,438 @@
+/*
+ * LEDs driver for Dialog DA906x PMIC series
+ *
+ * Copyright 2012 Dialog Semiconductors Ltd.
+ *
+ * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>,
+ *         Krystian Garbaciak <krystian.garbaciak@diasemi.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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/mfd/da906x/core.h>
+#include <linux/mfd/da906x/pdata.h>
+
+#define DRIVER_NAME		DA906X_DRVNAME_LEDS
+
+#define	DA906X_GPIO_LED_NUM	3
+
+#define DA906X_PWM_MAX		(DA906X_GPIO_PWM_MASK >> DA906X_GPIO_PWM_SHIFT)
+#define DA906X_MAX_BRIGHTNESS	0x5F
+#define DA906X_BLINK_DUR_MAX	(DA906X_BLINK_DUR_MASK >> \
+							DA906X_BLINK_DUR_SHIFT)
+#define DA906X_BLINK_FRQ_MAX	(DA906X_BLINK_FRQ_MASK >> \
+							DA906X_BLINK_FRQ_SHIFT)
+
+struct da906x_led {
+	int id;
+	struct led_classdev cdev;
+	struct work_struct work;
+	struct da906x *da906x;
+	struct da906x_leds *leds_container;
+
+	int pwm_regval;
+};
+
+struct da906x_leds {
+	int n_leds;
+	/* Array size to be defined during init. Keep at end. */
+	struct da906x_led led[0];
+};
+
+int da906x_led_pwm_reg[DA906X_LED_NUM] = {
+	DA906X_REG_GPO11_LED,
+	DA906X_REG_GPO14_LED,
+	DA906X_REG_GPO15_LED,
+};
+
+static int da906x_led_get_hw_blinking(struct da906x *da906x,
+				      unsigned *frq, unsigned *dur)
+{
+	int ret = da906x_reg_read(da906x, DA906X_REG_CONTROL_D);
+	if (ret < 0)
+		return ret;
+
+	if (frq)
+		*frq = (ret & DA906X_BLINK_FRQ_MASK) >> DA906X_BLINK_FRQ_SHIFT;
+
+	if (dur)
+		*dur = (ret & DA906X_BLINK_DUR_MASK) >> DA906X_BLINK_DUR_SHIFT;
+
+	return 0;
+}
+
+static int
+da906x_led_set_hw_bright(struct da906x *da906x, int id, int pwm_val)
+{
+	unsigned int blink_frq;
+	int ret;
+
+	ret = da906x_led_get_hw_blinking(da906x, &blink_frq, NULL);
+	if (ret)
+		return ret;
+
+	/* For enabled blinking, brightness setting is unavailable */
+	if (!blink_frq)
+		ret = da906x_reg_write(da906x, da906x_led_pwm_reg[id],
+				       pwm_val);
+
+	return ret;
+}
+
+static inline int
+da906x_led_get_hw_bright(struct da906x *da906x, int id)
+{
+	return da906x_reg_read(da906x, da906x_led_pwm_reg[id]);
+}
+
+static void da906x_led_brightness_set(struct led_classdev *cdev,
+	enum led_brightness brightness)
+{
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+
+	if (led->pwm_regval != brightness) {
+		led->pwm_regval = brightness;
+		schedule_work(&led->work);
+	}
+}
+
+static void da906x_led_brightness_work(struct work_struct *work)
+{
+	struct da906x_led *led = container_of(work, struct da906x_led, work);
+	int ret;
+
+	ret = da906x_led_set_hw_bright(led->da906x, led->id, led->pwm_regval);
+	if (ret)
+		dev_err(led->da906x->dev,
+			"Failed to set led brightness (err = %d)\n", ret);
+}
+
+static int da906x_configure_led_pin(struct da906x_led *led,
+				    int low_level_driven)
+{
+	int ret;
+	u16 gpio_cfg_reg;
+	u8 gpio_cfg_mask, gpio_cfg_val;
+	u8 mode_cfg_bit;
+
+	switch (led->id) {
+	case DA906X_GPIO11_LED:
+		gpio_cfg_reg = DA906X_REG_GPIO_10_11;
+		gpio_cfg_mask = DA906X_GPIO11_PIN_MASK;
+		gpio_cfg_val = DA906X_GPIO11_PIN_GPO_OD;
+		mode_cfg_bit = DA906X_GPIO11_MODE_LED_ACT_LOW;
+		break;
+
+	case DA906X_GPIO14_LED:
+		gpio_cfg_reg = DA906X_REG_GPIO_14_15;
+		gpio_cfg_mask = DA906X_GPIO14_PIN_MASK;
+		gpio_cfg_val = DA906X_GPIO14_PIN_GPO_OD;
+		mode_cfg_bit = DA906X_GPIO14_MODE_LED_ACT_LOW;
+		break;
+
+	case DA906X_GPIO15_LED:
+		gpio_cfg_reg = DA906X_REG_GPIO_14_15;
+		gpio_cfg_mask = DA906X_GPIO15_PIN_MASK;
+		gpio_cfg_val = DA906X_GPIO15_PIN_GPO_OD;
+		mode_cfg_bit = DA906X_GPIO15_MODE_LED_ACT_LOW;
+		break;
+
+	default:
+		BUG();
+	}
+
+	/* Configure GPOs for open drain */
+	ret = da906x_reg_update(led->da906x, gpio_cfg_reg, gpio_cfg_mask,
+				gpio_cfg_val);
+	if (ret)
+		return ret;
+
+	/* Configure active level */
+	if (low_level_driven)
+		ret = da906x_reg_set_bits(led->da906x,
+					  DA906X_REG_GPIO_MODE_8_15,
+					  mode_cfg_bit);
+	else
+		ret = da906x_reg_clear_bits(led->da906x,
+					    DA906X_REG_GPIO_MODE_8_15,
+					    mode_cfg_bit);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static ssize_t da906x_hw_blink_dur_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	unsigned long dur;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &dur);
+	if (ret || dur > DA906X_BLINK_DUR_MAX) {
+		dev_err(led->da906x->dev,
+			"Invalid parameter (valid are %d..%d)\n",
+			0, DA906X_BLINK_DUR_MAX);
+		return -EINVAL;
+	}
+
+	ret = da906x_reg_update(led->da906x, DA906X_REG_CONTROL_D,
+				DA906X_BLINK_DUR_MASK,
+				dur << DA906X_BLINK_DUR_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t da906x_hw_blink_dur_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	int ret;
+	unsigned int dur;
+
+	ret = da906x_led_get_hw_blinking(led->da906x, NULL, &dur);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%u\n", dur);
+}
+
+static ssize_t da906x_hw_blink_frq_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	struct da906x_leds *leds = led->leds_container;
+	unsigned long frq;
+	int i;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &frq);
+	if (ret || frq > DA906X_BLINK_FRQ_MAX) {
+		dev_err(led->da906x->dev,
+			"Invalid parameter (valid are %d..%d)\n",
+			0, DA906X_BLINK_FRQ_MAX);
+		return -EINVAL;
+	}
+
+	if (frq) {
+		/* Reset all LEDs brightness before blinking */
+		for (i = leds->n_leds - 1; i >= 0; i--) {
+			ret = da906x_led_set_hw_bright(led->da906x, i, 0);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	ret = da906x_reg_update(led->da906x, DA906X_REG_CONTROL_D,
+				DA906X_BLINK_FRQ_MASK,
+				frq << DA906X_BLINK_FRQ_SHIFT);
+	if (ret < 0)
+		return ret;
+
+	if (!frq) {
+		/* Restore all LEDs brightness, when blinking is off */
+		for (i = leds->n_leds - 1; i >= 0; i--) {
+			ret = da906x_led_set_hw_bright(led->da906x, i,
+						      leds->led[i].pwm_regval);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return count;
+}
+
+static ssize_t da906x_hw_blink_frq_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct da906x_led *led = container_of(cdev, struct da906x_led, cdev);
+	int ret;
+	unsigned int frq;
+
+	ret = da906x_led_get_hw_blinking(led->da906x, &frq, NULL);
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%u\n", frq);
+}
+
+static DEVICE_ATTR(hw_blink_dur_regval, 0644,
+		   da906x_hw_blink_dur_show, da906x_hw_blink_dur_store);
+static DEVICE_ATTR(hw_blink_frq_regval, 0644,
+		   da906x_hw_blink_frq_show, da906x_hw_blink_frq_store);
+
+static int da906x_led_probe(struct platform_device *pdev)
+{
+	struct da906x *da906x;
+	struct da906x_pdata *pdata;
+	struct led_platform_data *leds_pdata;
+	struct led_info *led_info;
+	struct da906x_leds *leds;
+	struct da906x_led *led;
+	size_t size;
+	int ret;
+	int i, j;
+
+	da906x = dev_get_drvdata(pdev->dev.parent);
+	pdata = da906x->dev->platform_data;
+	if (pdata = NULL) {
+		dev_err(&pdev->dev, "No platform data\n");
+		return -ENODEV;
+	}
+
+	leds_pdata = pdata->leds_pdata;
+	if (leds_pdata = NULL) {
+		dev_err(&pdev->dev, "No platform data for LEDs\n");
+		return -ENODEV;
+	}
+
+	size = sizeof(struct da906x_leds) +
+	       sizeof(struct da906x_led) * leds_pdata->num_leds;
+	leds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	if (leds = NULL) {
+		dev_err(&pdev->dev, "Failed to alloc memory\n");
+		return -ENOMEM;
+	}
+
+	leds->n_leds = leds_pdata->num_leds;
+	platform_set_drvdata(pdev, leds);
+
+	for (i = 0; i < leds_pdata->num_leds; i++) {
+		led_info = &leds_pdata->leds[i];
+
+		if ((led_info->flags & DA906X_LED_ID_MASK) >= DA906X_LED_NUM) {
+			dev_err(&pdev->dev, "Invalid platform data\n");
+			ret = -EINVAL;
+			goto err_register;
+		}
+
+		/* Check, if LED ID is not duplicated */
+		for (led = &leds->led[0], j = 0; j < i; led++, j++) {
+			if (led->id =
+			    (led_info->flags & DA906X_LED_ID_MASK)) {
+				dev_err(&pdev->dev, "Duplicated LED ID\n");
+				ret = -EINVAL;
+				goto err_register;
+			}
+		}
+
+		led->id = led_info->flags & DA906X_LED_ID_MASK;
+		led->pwm_regval = da906x_led_get_hw_bright(da906x, i);
+		led->cdev.brightness = led->pwm_regval;
+		led->cdev.max_brightness = DA906X_MAX_BRIGHTNESS;
+		led->cdev.brightness_set = da906x_led_brightness_set;
+		led->cdev.default_trigger = led_info->default_trigger;
+		led->cdev.name = led_info->name;
+		led->da906x = da906x;
+		led->leds_container = leds;
+
+		INIT_WORK(&led->work, da906x_led_brightness_work);
+
+		ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+		if (ret) {
+			dev_err(&pdev->dev, "Cannot register LEDs\n");
+			goto err_register;
+		}
+
+		ret = device_create_file(led->cdev.dev,
+					 &dev_attr_hw_blink_dur_regval);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to create device file (err = %d)\n",
+				ret);
+			goto err_create_dur_attr;
+		}
+
+		ret = device_create_file(led->cdev.dev,
+					 &dev_attr_hw_blink_frq_regval);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"Failed to create device file (err = %d)\n",
+				ret);
+			goto err_create_frq_attr;
+		}
+
+		ret = da906x_configure_led_pin(led,
+				led_info->flags & DA906X_LED_LOW_LEVEL_ACTIVE);
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to configure LED pins\n");
+			goto err_configure_pin;
+		}
+	}
+
+	return 0;
+
+err_register:
+	while (--i >= 0) {
+err_configure_pin:
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_frq_regval);
+err_create_frq_attr:
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_dur_regval);
+err_create_dur_attr:
+		cancel_work_sync(&leds->led[i].work);
+		led_classdev_unregister(&leds->led[i].cdev);
+	}
+
+	kfree(leds);
+
+	return ret;
+}
+
+static int da906x_led_remove(struct platform_device *pdev)
+{
+	struct da906x_leds *leds = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = leds->n_leds - 1; i >= 0; i--) {
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_frq_regval);
+		device_remove_file(leds->led[i].cdev.dev,
+				   &dev_attr_hw_blink_dur_regval);
+		cancel_work_sync(&leds->led[i].work);
+		led_classdev_unregister(&leds->led[i].cdev);
+	}
+
+	kfree(leds);
+
+	return 0;
+}
+
+static struct platform_driver da906x_led_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.owner = THIS_MODULE,
+	},
+	.probe = da906x_led_probe,
+	.remove = __devexit_p(da906x_led_remove),
+};
+
+module_platform_driver(da906x_led_driver);
+
+MODULE_DESCRIPTION("LED driver for Dialog DA906X");
+MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>, Krystian Garbaciak <krystian.garbaciak@diasemi.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
1.7.0.4


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

  reply	other threads:[~2012-08-24 14:25 UTC|newest]

Thread overview: 60+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-08-24 13:45 [RFC PATCH 0/8] DA906x PMIC driver Krystian Garbaciak
2012-08-24 13:45 ` [lm-sensors] " Krystian Garbaciak
2012-08-24 13:50 ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Krystian Garbaciak
2012-08-24 13:50   ` [lm-sensors] " Krystian Garbaciak
2012-08-24 13:55   ` [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Krystian Garbaciak
2012-08-24 13:55     ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:00     ` [RFC PATCH 3/8] rtc: Add RTC driver for DA906x PMIC Krystian Garbaciak
2012-08-24 14:00       ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:05       ` [RFC PATCH 4/8] hwmon: Add DA906x hardware monitoring support Krystian Garbaciak
2012-08-24 14:05         ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:10         ` [RFC PATCH 5/8] input: Add support for DA906x PMIC OnKey detection Krystian Garbaciak
2012-08-24 14:10           ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:15           ` [RFC PATCH 6/8] input: Add support for DA906x vibration motor driver Krystian Garbaciak
2012-08-24 14:15             ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:20             ` [RFC PATCH 7/8] watchdog: Add DA906x PMIC watchdog driver Krystian Garbaciak
2012-08-24 14:20               ` [lm-sensors] " Krystian Garbaciak
2012-08-24 14:25               ` Krystian Garbaciak [this message]
2012-08-24 14:25                 ` [lm-sensors] [RFC PATCH 8/8] leds: Add DA906x PMIC LED driver Krystian Garbaciak
2012-08-24 18:45         ` [RFC PATCH 4/8] hwmon: Add DA906x hardware monitoring support Guenter Roeck
2012-08-24 18:45           ` [lm-sensors] " Guenter Roeck
2012-08-29 13:25           ` [PATCH] regulator: Fix bug in regulator_mode_to_status() core function Krystian Garbaciak
2012-08-29 13:25             ` [lm-sensors] " Krystian Garbaciak
2012-08-25 15:10     ` [RFC PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Mark Brown
2012-08-25 15:10       ` [lm-sensors] " Mark Brown
2012-08-29 14:50       ` Krystian Garbaciak
2012-08-29 14:50         ` [lm-sensors] " Krystian Garbaciak
2012-08-29 14:50         ` Krystian Garbaciak
2012-08-30 17:47         ` Mark Brown
2012-08-30 17:47           ` [lm-sensors] " Mark Brown
2012-08-31 10:00           ` Krystian Garbaciak
2012-08-31 10:00             ` [lm-sensors] " Krystian Garbaciak
2012-08-31 10:00             ` Krystian Garbaciak
2013-05-09 14:05             ` Guennadi Liakhovetski
2013-05-09 14:18               ` Anthony Olech
2013-05-09 14:28                 ` Guennadi Liakhovetski
2013-05-09 14:42                   ` Anthony Olech
2013-05-09 14:50                     ` Guennadi Liakhovetski
2012-08-25 18:31   ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Mark Brown
2012-08-25 18:31     ` [lm-sensors] " Mark Brown
2012-08-31 11:20     ` Krystian Garbaciak
2012-08-31 11:20       ` [lm-sensors] " Krystian Garbaciak
2012-08-31 11:20       ` Krystian Garbaciak
2012-08-31 11:37       ` Philippe Rétornaz
2012-08-31 11:37         ` [lm-sensors] " Philippe Rétornaz
2012-08-31 11:37         ` Philippe Rétornaz
2012-08-31 11:37         ` Philippe Rétornaz
2012-08-31 17:16       ` Mark Brown
2012-08-31 17:16         ` [lm-sensors] " Mark Brown
  -- strict thread matches above, loose matches on Subject: below --
2012-08-24  8:32 [RFC PATCH 0/8] DA906x PMIC driver Krystian Garbaciak
2012-08-24  8:32 ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32 ` [PATCH 1/8] mfd: Add Dialog DA906x core driver Krystian Garbaciak
2012-08-24  8:32   ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32   ` [PATCH 2/8] regulator: Add Dialog DA906x voltage regulators support Krystian Garbaciak
2012-08-24  8:32     ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32     ` [PATCH 3/8] rtc: Add RTC driver for DA906x PMIC Krystian Garbaciak
2012-08-24  8:32       ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32       ` [PATCH 4/8] hwmon: Add DA906x hardware monitoring support Krystian Garbaciak
2012-08-24  8:32         ` [lm-sensors] " Krystian Garbaciak
2012-08-24  8:32         ` [PATCH 5/8] input: Add support for DA906x PMIC OnKey detection Krystian Garbaciak
2012-08-24  8:32           ` [lm-sensors] " Krystian Garbaciak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=201208241525@sw-eng-lt-dc-vm2 \
    --to=krystian.garbaciak@diasemi.com \
    --cc=a.zummo@towertech.it \
    --cc=anthony.olech@diasemi.com \
    --cc=ashish.jangam@kpitcummins.com \
    --cc=broonie@opensource.wolfsonmicro.com \
    --cc=bryan.wu@canonical.com \
    --cc=dg77.kim@samsung.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=drjones@redhat.com \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=linux-watchdog@vger.kernel.org \
    --cc=lm-sensors@lm-sensors.org \
    --cc=lrg@ti.com \
    --cc=rtc-linux@googlegroups.com \
    --cc=sameo@linux.intel.com \
    --cc=wim@iguana.be \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.