Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1260364 > unrolled thread

[PATCH 11/16] backlight: ti-lmu-backlight: add LM3633 driver

Started byMilo Kim <milo.kim@ti.com>
First post2015-11-02 06:10 +0100
Last post2015-11-02 06:10 +0100
Articles 6 — 1 participant

Back to article view | Back to linux.kernel

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  [PATCH 11/16] backlight: ti-lmu-backlight: add LM3633 driver Milo Kim <milo.kim@ti.com> - 2015-11-02 06:10 +0100
    [PATCH 14/16] hwmon: add TI LMU hardware fault monitoring driver Milo Kim <milo.kim@ti.com> - 2015-11-02 06:10 +0100
      [PATCH 16/16] regulator: add LM363X driver Milo Kim <milo.kim@ti.com> - 2015-11-02 06:10 +0100
      [PATCH 15/16] leds: add LM3633 driver Milo Kim <milo.kim@ti.com> - 2015-11-02 06:10 +0100
    [PATCH 12/16] backlight: ti-lmu-backlight: add LM3695 driver Milo Kim <milo.kim@ti.com> - 2015-11-02 06:10 +0100
      [PATCH 13/16] backlight: ti-lmu-backlight: add LM3697 driver Milo Kim <milo.kim@ti.com> - 2015-11-02 06:10 +0100

#1260364 — [PATCH 11/16] backlight: ti-lmu-backlight: add LM3633 driver

FromMilo Kim <milo.kim@ti.com>
Date2015-11-02 06:10 +0100
Subject[PATCH 11/16] backlight: ti-lmu-backlight: add LM3633 driver
Message-ID<qqfxv-1df-1@gated-at.bofh.it>
LM3633 has 3 backlight strings and 11 bit dimming is supported.
PWM brightness control is also supported.

LM3633 MFD has HWMON driver which monitors hardware fault detection.
After this monitoring is done, LM3633 backlight device should be
reinitialized. To enable this, 'hwmon_notifier_used' is set to true.
Then, TI LMU backlight handles LMU HWMON event in
ti_lmu_backlight_hwmon_notifier().

Common backlight driver is controlled by TI LMU backlight driver.
Only LM3633 specific code is implemented here.

Cc: Jingoo Han <jingoohan1@gmail.com>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/video/backlight/Kconfig     |   9 ++
 drivers/video/backlight/Makefile    |   1 +
 drivers/video/backlight/lm3633_bl.c | 210 ++++++++++++++++++++++++++++++++++++
 3 files changed, 220 insertions(+)
 create mode 100644 drivers/video/backlight/lm3633_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index e3bf905..e3c4adb 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -417,6 +417,15 @@ config BACKLIGHT_LM3632
 	  Up to 2 backlight strings and 11 bit dimming is supported.
 	  PWM brightness control is also supported.
 
+config BACKLIGHT_LM3633
+	tristate "Backlight driver for TI LM3633"
+	depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU
+	select TI_LMU_BACKLIGHT
+	help
+	  Say Y to enable the backlight driver for TI LM3633.
+	  Up to 3 backlight strings and 11 bit dimming is supported.
+	  PWM brightness control is also supported.
+
 config TI_LMU_BACKLIGHT
 	tristate "Backlight driver for TI LMU"
 	depends on BACKLIGHT_LM3532 || BACKLIGHT_LM3631 || \
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 6cd5232..364b854 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_BACKLIGHT_LM3533)		+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3630A)		+= lm3630a_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3631)		+= lm3631_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3632)		+= lm3632_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3633)		+= lm3633_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3639)		+= lm3639_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)		+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)		+= lp855x_bl.o
diff --git a/drivers/video/backlight/lm3633_bl.c b/drivers/video/backlight/lm3633_bl.c
new file mode 100644
index 0000000..6e01746
--- /dev/null
+++ b/drivers/video/backlight/lm3633_bl.c
@@ -0,0 +1,210 @@
+/*
+ * TI LM3633 Backlight Driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * 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/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+
+#include "ti-lmu-backlight.h"
+
+#define LM3633_DEFAULT_OVP		LM3633_BOOST_OVP_40V
+#define LM3633_BL_MAX_STRINGS		3
+#define LM3633_BL_MAX_BRIGHTNESS	2047
+
+static int lm3633_bl_init(struct ti_lmu_bl_chip *chip)
+{
+	/* Configure ramp selection for each bank */
+	return ti_lmu_update_bits(chip->lmu, LM3633_REG_BL_RAMP_CONF,
+				  LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH);
+}
+
+static int lm3633_bl_enable(struct ti_lmu_bl *lmu_bl, int enable)
+{
+	return ti_lmu_update_bits(lmu_bl->chip->lmu, LM3633_REG_ENABLE,
+				  BIT(lmu_bl->bank_id),
+				  enable << lmu_bl->bank_id);
+}
+
+static int lm3633_bl_set_brightness(struct ti_lmu_bl *lmu_bl, int brightness)
+{
+	int ret;
+	u8 data;
+	u8 reg_lsb[] = { LM3633_REG_BRT_HVLED_A_LSB,
+			 LM3633_REG_BRT_HVLED_B_LSB, };
+	u8 reg_msb[] = { LM3633_REG_BRT_HVLED_A_MSB,
+			 LM3633_REG_BRT_HVLED_B_MSB, };
+
+	if (lmu_bl->mode == BL_PWM_BASED) {
+		/*
+		 * PWM can start from any non-zero code and dim down to zero.
+		 * So, brightness register should be updated even in PWM mode.
+		 */
+		if (brightness > 0)
+			brightness = LM3633_BL_MAX_BRIGHTNESS;
+		else
+			brightness = 0;
+	}
+
+	data = brightness & LM3633_BRT_HVLED_LSB_MASK;
+	ret = ti_lmu_update_bits(lmu_bl->chip->lmu, reg_lsb[lmu_bl->bank_id],
+				 LM3633_BRT_HVLED_LSB_MASK, data);
+	if (ret)
+		return ret;
+
+	data = (brightness >> LM3633_BRT_HVLED_MSB_SHIFT) & 0xFF;
+	return ti_lmu_write_byte(lmu_bl->chip->lmu, reg_msb[lmu_bl->bank_id],
+				 data);
+}
+
+static int lm3633_bl_boost_configure(struct ti_lmu_bl *lmu_bl)
+{
+	return ti_lmu_update_bits(lmu_bl->chip->lmu, LM3633_REG_BOOST_CFG,
+				  LM3633_BOOST_OVP_MASK, LM3633_DEFAULT_OVP);
+}
+
+static int lm3633_bl_set_ctrl_mode(struct ti_lmu_bl *lmu_bl)
+{
+	int bank_id = lmu_bl->bank_id;
+
+	if (lmu_bl->mode == BL_PWM_BASED)
+		return ti_lmu_update_bits(lmu_bl->chip->lmu,
+					  LM3633_REG_PWM_CFG,
+					  BIT(bank_id), 1 << bank_id);
+
+	return 0;
+}
+
+static int lm3633_bl_string_configure(struct ti_lmu_bl *lmu_bl)
+{
+	struct ti_lmu *lmu = lmu_bl->chip->lmu;
+	int is_detected = 0;
+	int i, ret;
+
+	/* Assign control bank from backlight string configuration */
+	for (i = 0; i < LM3633_BL_MAX_STRINGS; i++) {
+		if (test_bit(i, &lmu_bl->bl_string)) {
+			ret = ti_lmu_update_bits(lmu,
+						 LM3633_REG_HVLED_OUTPUT_CFG,
+						 BIT(i), lmu_bl->bank_id << i);
+			if (ret)
+				return ret;
+
+			is_detected = 1;
+		}
+	}
+
+	if (!is_detected) {
+		dev_err(lmu_bl->chip->dev, "No backlight string found\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int lm3633_bl_set_current_limit(struct ti_lmu_bl *lmu_bl)
+{
+	u8 reg[] = { LM3633_REG_IMAX_HVLED_A, LM3633_REG_IMAX_HVLED_B, };
+
+	return ti_lmu_write_byte(lmu_bl->chip->lmu, reg[lmu_bl->bank_id],
+				 lmu_bl->imax);
+}
+
+static int lm3633_bl_set_ramp(struct ti_lmu_bl *lmu_bl)
+{
+	int ret, index;
+	u8 reg;
+
+	index = ti_lmu_backlight_get_ramp_index(lmu_bl, BL_RAMP_UP);
+	if (index > 0) {
+		if (lmu_bl->bank_id == 0)
+			reg = LM3633_REG_BL0_RAMPUP;
+		else
+			reg = LM3633_REG_BL1_RAMPUP;
+
+		ret = ti_lmu_update_bits(lmu_bl->chip->lmu, reg,
+					 LM3633_BL_RAMPUP_MASK,
+					 index << LM3633_BL_RAMPUP_SHIFT);
+		if (ret)
+			return ret;
+	}
+
+	index = ti_lmu_backlight_get_ramp_index(lmu_bl, BL_RAMP_DOWN);
+	if (index > 0) {
+		if (lmu_bl->bank_id == 0)
+			reg = LM3633_REG_BL0_RAMPDN;
+		else
+			reg = LM3633_REG_BL1_RAMPDN;
+
+		ret = ti_lmu_update_bits(lmu_bl->chip->lmu, reg,
+					 LM3633_BL_RAMPDN_MASK,
+					 index << LM3633_BL_RAMPDN_SHIFT);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int lm3633_bl_configure(struct ti_lmu_bl *lmu_bl)
+{
+	int ret;
+
+	ret = lm3633_bl_boost_configure(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = lm3633_bl_set_ctrl_mode(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = lm3633_bl_string_configure(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = lm3633_bl_set_current_limit(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = lm3633_bl_set_ramp(lmu_bl);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* Backlight ramp up/down time. Unit is msec. */
+static const int lm3633_ramp_table[] = {
+	   2, 250, 500, 1000, 2000, 4000, 8000, 16000,
+};
+
+static const struct ti_lmu_bl_ops lm3633_lmu_ops = {
+	.init			= lm3633_bl_init,
+	.configure		= lm3633_bl_configure,
+	.update_brightness	= lm3633_bl_set_brightness,
+	.bl_enable		= lm3633_bl_enable,
+	.hwmon_notifier_used	= true,
+	.max_brightness		= LM3633_BL_MAX_BRIGHTNESS,
+	.ramp_table		= lm3633_ramp_table,
+	.size_ramp		= ARRAY_SIZE(lm3633_ramp_table),
+};
+
+/* LM3633 backlight of_device_id */
+TI_LMU_BL_OF_DEVICE(lm3633, "ti,lm3633-backlight");
+
+/* LM3633 backlight platform driver */
+TI_LMU_BL_PLATFORM_DRIVER(lm3633, "lm3633-backlight");
+
+MODULE_DESCRIPTION("TI LM3633 Backlight Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:lm3633-backlight");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [next] | [standalone]


#1260373 — [PATCH 14/16] hwmon: add TI LMU hardware fault monitoring driver

FromMilo Kim <milo.kim@ti.com>
Date2015-11-02 06:10 +0100
Subject[PATCH 14/16] hwmon: add TI LMU hardware fault monitoring driver
Message-ID<qqfxw-1df-31@gated-at.bofh.it>
In reply to#1260364
LM3633 and LM3697 are TI LMU MFD device.
Those device have hardware monitoring feature which detects opened or
shorted circuit case.

Attributes
----------
  Two attributes are registered.
    open_fault:  check backlight output channel is opened or not
    short_fault: check backlight output channel is shorted or not

  With register R/W operations, LMU HWMON driver checks the
  status of backlight output channels.
  LM3633 and LM3697 have same sequence to check channels, so common
  functions are used.

Operations
----------
  Two devices have common control flow but register addresses are different.
  The structure, 'ti_lmu_reg' is used for device configuration.

HWMON notifier
--------------
  After LMU HWMON operation is done, backlight device should be
  reinitialized. LMU HWMON driver notifies an event as soon as
  the monitoring is done. Then, LM3633 and LM3697 backlight driver handles
  this event.

Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Jean Delvare <jdelvare@suse.com>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: devicetree@vger.kernel.org
Cc: lm-sensors@lm-sensors.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/hwmon/Kconfig        |  10 ++
 drivers/hwmon/Makefile       |   1 +
 drivers/hwmon/ti-lmu-hwmon.c | 393 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 404 insertions(+)
 create mode 100644 drivers/hwmon/ti-lmu-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index e13c902..989e1fa 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1483,6 +1483,16 @@ config SENSORS_THMC50
 	  This driver can also be built as a module.  If so, the module
 	  will be called thmc50.
 
+config SENSORS_TI_LMU
+	tristate "Texas Instruments LMU Hardware Fault Monitoring Driver"
+	depends on MFD_TI_LMU
+	help
+	  Say Y here to include support for the TI LMU opened and shorted
+	  circuit fault detection.
+
+	  This driver can also be built as a module. If so the module
+	  will be called ti-lmu-hwmon.
+
 config SENSORS_TMP102
 	tristate "Texas Instruments TMP102"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 9e0f3dd..de11b74 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -143,6 +143,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
 obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_TC74)	+= tc74.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
+obj-$(CONFIG_SENSORS_TI_LMU)	+= ti-lmu-hwmon.o
 obj-$(CONFIG_SENSORS_TMP102)	+= tmp102.o
 obj-$(CONFIG_SENSORS_TMP103)	+= tmp103.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
diff --git a/drivers/hwmon/ti-lmu-hwmon.c b/drivers/hwmon/ti-lmu-hwmon.c
new file mode 100644
index 0000000..c297cf9
--- /dev/null
+++ b/drivers/hwmon/ti-lmu-hwmon.c
@@ -0,0 +1,393 @@
+/*
+ * TI LMU(Lighting Management Unit) Hardware Fault Monitoring Driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * 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/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define LMU_BANK_MASK(n)		(~BIT(n) & 0x07)
+#define LMU_BL_MAX_CHANNELS		3
+#define LMU_DEFAULT_BANK		0
+#define LMU_DELAY_STARTUP		500
+#define LMU_DELAY_FEEDBACK		5
+#define LMU_ENABLE_FEEDBACK		(BIT(0) | BIT(1) | BIT(2))
+#define LMU_MAX_BRIGHTNESS		0xFF
+#define LMU_NO_RAMP			0
+
+enum ti_lmu_hwmon_id {
+	LMU_HWMON_OPEN,
+	LMU_HWMON_SHORT,
+};
+
+/**
+ * struct ti_lmu_reg
+ *
+ * @monitor:		Enable monitoring register
+ * @bank:		Bank configuration register
+ * @ramp:		Ramp(speed) configuration register
+ * @imax:		Current limit setting register
+ * @feedback:		Feedback enable register
+ * @brightness:		Brightness register
+ * @enable:		Bank enable register
+ * @open_fault:		Detect opened circuit status register
+ * @short_fault:	Detect shorted circuit status register
+ *
+ * To detect hardware fault, several registers are used.
+ * Device specific register addresses are configured in this structure.
+ */
+struct ti_lmu_reg {
+	u8 monitor;
+	u8 bank;
+	u8 ramp;
+	u8 imax;
+	u8 feedback;
+	u8 brightness;
+	u8 enable;
+	u8 open_fault;
+	u8 short_fault;
+};
+
+struct ti_lmu_hwmon {
+	struct ti_lmu *lmu;
+	struct device *dev;	/* hwmon dev */
+	const struct ti_lmu_reg *regs;
+};
+
+struct ti_lmu_hwmon_data {
+	const char *name;
+	const struct attribute_group **groups;
+	const struct ti_lmu_reg *regs;
+};
+
+static void ti_lmu_hwmon_reset_device(struct ti_lmu_hwmon *hwmon)
+{
+	unsigned int en_gpio = hwmon->lmu->en_gpio;
+
+	/* POR(power on reset) by enable pin control */
+	gpio_set_value(en_gpio, 0);
+	msleep(LMU_DELAY_STARTUP);
+
+	gpio_set_value(en_gpio, 1);
+	msleep(LMU_DELAY_STARTUP);
+}
+
+static int ti_lmu_hwmon_enable_monitoring(struct ti_lmu_hwmon *hwmon,
+					  enum ti_lmu_hwmon_id id)
+{
+	struct ti_lmu *lmu = hwmon->lmu;
+	u8 reg = hwmon->regs->monitor;
+
+	if (id == LMU_HWMON_OPEN)
+		return ti_lmu_write_byte(lmu, reg, BIT(0));
+	else if (id == LMU_HWMON_SHORT)
+		return ti_lmu_write_byte(lmu, reg, BIT(1));
+	else
+		return -EINVAL;
+}
+
+static int ti_lmu_hwmon_assign_bank(struct ti_lmu_hwmon *hwmon, u8 val)
+{
+	return ti_lmu_write_byte(hwmon->lmu, hwmon->regs->bank, val);
+}
+
+static int ti_lmu_hwmon_channel_config(struct ti_lmu_hwmon *hwmon)
+{
+	struct ti_lmu *lmu = hwmon->lmu;
+	const struct ti_lmu_reg *reg = hwmon->regs;
+	int ret;
+
+	/* Set ramp time to the fatest setting */
+	ret = ti_lmu_write_byte(lmu, reg->ramp, LMU_NO_RAMP);
+	if (ret)
+		return ret;
+
+	/* Set max current to 20mA */
+	ret = ti_lmu_write_byte(lmu, reg->imax, LMU_IMAX_20mA);
+	if (ret)
+		return ret;
+
+	/* Enable feedback */
+	ret = ti_lmu_write_byte(lmu, reg->feedback, LMU_ENABLE_FEEDBACK);
+	if (ret)
+		return ret;
+
+	/* Set max brightness */
+	ret = ti_lmu_write_byte(lmu, reg->brightness, LMU_MAX_BRIGHTNESS);
+	if (ret)
+		return ret;
+
+	/* Enable a bank */
+	ret = ti_lmu_write_byte(lmu, reg->enable, 1);
+	if (ret)
+		return ret;
+
+	/* Wait until device completes fault detection */
+	msleep(LMU_DELAY_FEEDBACK);
+
+	return 0;
+}
+
+static int ti_lmu_hwmon_get_open_fault_result(struct ti_lmu_hwmon *hwmon,
+					      char *result)
+{
+	int ret, channel, len, offset = 0;
+	u8 status = 0;
+
+	ret = ti_lmu_read_byte(hwmon->lmu, hwmon->regs->open_fault, &status);
+	if (ret)
+		return ret;
+
+	for (channel = 0; channel < LMU_BL_MAX_CHANNELS; channel++) {
+		if (BIT(channel) & status)
+			len = sprintf(&result[offset], "Channel %d is opened\n",
+				      channel);
+		else
+			len = sprintf(&result[offset], "Channel %d works\n",
+				      channel);
+
+		offset += len;
+	}
+
+	return 0;
+}
+
+static ssize_t ti_lmu_hwmon_get_short_fault_result(struct ti_lmu_hwmon *hwmon,
+						   char *buf, int channel)
+{
+	int ret;
+	u8 status = 0;
+
+	ret = ti_lmu_read_byte(hwmon->lmu, hwmon->regs->short_fault, &status);
+	if (ret)
+		return ret;
+
+	if (BIT(channel) & status)
+		return sprintf(buf, "Channel %d is shorted\n", channel);
+	else
+		return sprintf(buf, "Channel %d works\n", channel);
+}
+
+static int ti_lmu_hwmon_disable_all_banks(struct ti_lmu_hwmon *hwmon)
+{
+	return ti_lmu_write_byte(hwmon->lmu, hwmon->regs->enable, 0);
+}
+
+static int ti_lmu_hwmon_notifier_call_chain(struct ti_lmu_hwmon *hwmon)
+{
+	int ret;
+
+	ti_lmu_hwmon_reset_device(hwmon);
+
+	ret = blocking_notifier_call_chain(&hwmon->lmu->notifier,
+					   LMU_EVENT_HWMON_DONE, NULL);
+	if (ret == NOTIFY_OK || ret == NOTIFY_DONE)
+		return 0;
+	else
+		return -EINVAL;
+}
+
+static ssize_t ti_lmu_hwmon_open_fault_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buffer)
+{
+	struct ti_lmu_hwmon *hwmon = dev_get_drvdata(dev);
+	char result[100];
+	int ret;
+
+	/* Device should be reset prior to fault detection */
+	ti_lmu_hwmon_reset_device(hwmon);
+
+	ret = ti_lmu_hwmon_enable_monitoring(hwmon, LMU_HWMON_OPEN);
+	if (ret)
+		return ret;
+
+	ret = ti_lmu_hwmon_assign_bank(hwmon, LMU_DEFAULT_BANK);
+	if (ret)
+		return ret;
+
+	ret = ti_lmu_hwmon_channel_config(hwmon);
+	if (ret)
+		return ret;
+
+	memset(result, 0, sizeof(result));
+	ret = ti_lmu_hwmon_get_open_fault_result(hwmon, result);
+	if (ret)
+		return ret;
+
+	/* Notify an event */
+	ret = ti_lmu_hwmon_notifier_call_chain(hwmon);
+	if (ret)
+		dev_warn(dev, "Notify hwmon err\n");
+
+	return snprintf(buffer, PAGE_SIZE, "%s\n", result);
+}
+
+static ssize_t ti_lmu_hwmon_short_fault_show(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buffer)
+{
+	struct ti_lmu_hwmon *hwmon = dev_get_drvdata(dev);
+	int ret, i, len, offset = 0;
+	char result[100];
+
+	/* Device should be reset prior to fault detection */
+	ti_lmu_hwmon_reset_device(hwmon);
+
+	ret = ti_lmu_hwmon_enable_monitoring(hwmon, LMU_HWMON_SHORT);
+	if (ret)
+		return ret;
+
+	memset(result, 0, sizeof(result));
+
+	/* Shorted circuit detection is done by checking the bank one by one */
+	for (i = 0; i < LMU_BL_MAX_CHANNELS; i++) {
+		ret = ti_lmu_hwmon_assign_bank(hwmon, LMU_BANK_MASK(i));
+		if (ret)
+			return ret;
+
+		ret = ti_lmu_hwmon_channel_config(hwmon);
+		if (ret)
+			return ret;
+
+		len = ti_lmu_hwmon_get_short_fault_result(hwmon,
+							  &result[offset], i);
+		if (len < 0)
+			return len;
+
+		offset += len;
+
+		ret = ti_lmu_hwmon_disable_all_banks(hwmon);
+		if (ret)
+			return ret;
+	}
+
+	/* Notify an event */
+	ret = ti_lmu_hwmon_notifier_call_chain(hwmon);
+	if (ret)
+		dev_warn(dev, "Notify hwmon err\n");
+
+	return snprintf(buffer, PAGE_SIZE, "%s\n", result);
+}
+
+static DEVICE_ATTR(open_fault, S_IRUGO, ti_lmu_hwmon_open_fault_show, NULL);
+static DEVICE_ATTR(short_fault, S_IRUGO, ti_lmu_hwmon_short_fault_show, NULL);
+
+static struct attribute *ti_lmu_hwmon_attrs[] = {
+	&dev_attr_open_fault.attr,
+	&dev_attr_short_fault.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(ti_lmu_hwmon);
+
+/*
+ * Operations are dependent on the device.
+ * Device registers configuration is required.
+ */
+static const struct ti_lmu_reg lm3633_regs = {
+	.monitor	= LM3633_REG_MONITOR_ENABLE,
+	.bank		= LM3633_REG_HVLED_OUTPUT_CFG,
+	.ramp		= LM3633_REG_BL0_RAMPUP,
+	.imax		= LM3633_REG_IMAX_HVLED_A,
+	.feedback	= LM3633_REG_BL_FEEDBACK_ENABLE,
+	.brightness	= LM3633_REG_BRT_HVLED_A_MSB,
+	.enable		= LM3633_REG_ENABLE,
+	.open_fault	= LM3633_REG_BL_OPEN_FAULT_STATUS,
+	.short_fault	= LM3633_REG_BL_SHORT_FAULT_STATUS,
+};
+
+static const struct ti_lmu_reg lm3697_regs = {
+	.monitor	= LM3697_REG_MONITOR_ENABLE,
+	.bank		= LM3697_REG_HVLED_OUTPUT_CFG,
+	.ramp		= LM3697_REG_BL0_RAMPUP,
+	.imax		= LM3697_REG_IMAX_A,
+	.feedback	= LM3697_REG_FEEDBACK_ENABLE,
+	.brightness	= LM3697_REG_BRT_A_MSB,
+	.enable		= LM3697_REG_ENABLE,
+	.open_fault	= LM3697_REG_OPEN_FAULT_STATUS,
+	.short_fault	= LM3697_REG_SHORT_FAULT_STATUS,
+};
+
+static const struct ti_lmu_hwmon_data lm3633_hwmon_data = {
+	.name = "lm3633_fault_status",
+	.groups = ti_lmu_hwmon_groups,
+	.regs = &lm3633_regs,
+};
+
+static const struct ti_lmu_hwmon_data lm3697_hwmon_data = {
+	.name = "lm3697_fault_status",
+	.groups = ti_lmu_hwmon_groups,
+	.regs = &lm3697_regs,
+};
+
+static const struct of_device_id ti_lmu_hwmon_of_match[] = {
+	{ .compatible = "ti,lm3633-hwmon", .data = &lm3633_hwmon_data },
+	{ .compatible = "ti,lm3697-hwmon", .data = &lm3697_hwmon_data },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ti_lmu_hwmon_of_match);
+
+static int ti_lmu_hwmon_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
+	const struct of_device_id *match;
+	struct ti_lmu_hwmon *hwmon;
+	const struct ti_lmu_hwmon_data *data;
+
+	match = of_match_device(ti_lmu_hwmon_of_match, dev);
+	if (!match)
+		return -ENODEV;
+
+	/* To monitor hardware fault, enable pin control should be required. */
+	if (!gpio_is_valid(lmu->en_gpio))
+		return -EINVAL;
+
+	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+
+	/*
+	 * Get device specific data(name, groups and registers) from
+	 * of_match table.
+	 */
+	data = (struct ti_lmu_hwmon_data *)match->data;
+	hwmon->lmu = lmu;
+	hwmon->regs = data->regs;
+
+	hwmon->dev = devm_hwmon_device_register_with_groups(dev, data->name,
+							hwmon, data->groups);
+
+	return PTR_ERR_OR_ZERO(hwmon->dev);
+}
+
+static struct platform_driver ti_lmu_hwmon_driver = {
+	.probe = ti_lmu_hwmon_probe,
+	.driver = {
+		.name = "ti-lmu-hwmon",
+		.of_match_table = ti_lmu_hwmon_of_match,
+	},
+};
+
+module_platform_driver(ti_lmu_hwmon_driver);
+
+MODULE_DESCRIPTION("TI LMU Hardware Fault Monitoring Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-lmu-hwmon");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [next] | [standalone]


#1260375 — [PATCH 16/16] regulator: add LM363X driver

FromMilo Kim <milo.kim@ti.com>
Date2015-11-02 06:10 +0100
Subject[PATCH 16/16] regulator: add LM363X driver
Message-ID<qqfxw-1df-33@gated-at.bofh.it>
In reply to#1260373
LM363X regulator driver supports LM3631 and LM3632.
LM3631 has 5 regulators. LM3632 provides 3 regulators.
One boost output and LDOs are used for the display module.
Boost voltage is configurable but always on.
Supported operations for LDOs are enabled/disabled and voltage change.

Two LDOs of LM3632 can be controlled by external pins.
Those are described in the device tree bindings.

Cc: Mark Brown <broonie@kernel.org>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/regulator/Kconfig            |   9 +
 drivers/regulator/Makefile           |   1 +
 drivers/regulator/lm363x-regulator.c | 349 +++++++++++++++++++++++++++++++++++
 3 files changed, 359 insertions(+)
 create mode 100644 drivers/regulator/lm363x-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 64bccff..efac1eb 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -274,6 +274,15 @@ config REGULATOR_ISL6271A
 	help
 	  This driver supports ISL6271A voltage regulator chip.
 
+config REGULATOR_LM363X
+	tristate "TI LM363X voltage regulators"
+	depends on MFD_TI_LMU
+	help
+	  This driver supports LM3631 and LM3632 voltage regulators for
+	  the LCD bias.
+	  One boost output voltage is configurable and always on.
+	  Other LDOs are used for the display module.
+
 config REGULATOR_LP3971
 	tristate "National Semiconductors LP3971 PMIC regulator driver"
 	depends on I2C
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 0f81749..0ea87cd 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o
 obj-$(CONFIG_REGULATOR_HI6421) += hi6421-regulator.o
 obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o
 obj-$(CONFIG_REGULATOR_ISL9305) += isl9305.o
+obj-$(CONFIG_REGULATOR_LM363X) += lm363x-regulator.o
 obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o
 obj-$(CONFIG_REGULATOR_LP3972) += lp3972.o
 obj-$(CONFIG_REGULATOR_LP872X) += lp872x.o
diff --git a/drivers/regulator/lm363x-regulator.c b/drivers/regulator/lm363x-regulator.c
new file mode 100644
index 0000000..28408d4
--- /dev/null
+++ b/drivers/regulator/lm363x-regulator.c
@@ -0,0 +1,349 @@
+/*
+ * TI LM363X Regulator Driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * 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/err.h>
+#include <linux/kernel.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/slab.h>
+
+/* LM3631 */
+#define LM3631_BOOST_VSEL_MAX		0x25
+#define LM3631_LDO_VSEL_MAX		0x28
+#define LM3631_CONT_VSEL_MAX		0x03
+#define LM3631_VBOOST_MIN		4500000
+#define LM3631_VCONT_MIN		1800000
+#define LM3631_VLDO_MIN			4000000
+#define ENABLE_TIME_USEC		1000
+
+/* LM3632 */
+#define LM3632_BOOST_VSEL_MAX		0x26
+#define LM3632_LDO_VSEL_MAX		0x29
+#define LM3632_VBOOST_MIN		4500000
+#define LM3632_VLDO_MIN			4000000
+
+/* Common */
+#define LM363X_STEP_50mV		50000
+#define LM363X_STEP_500mV		500000
+
+struct lm363x_regulator {
+	struct ti_lmu *lmu;
+	struct regulator_dev *regulator;
+};
+
+const int ldo_cont_enable_time[] = {
+	0, 2000, 5000, 10000, 20000, 50000, 100000, 200000,
+};
+
+static int lm363x_regulator_enable_time(struct regulator_dev *rdev)
+{
+	struct lm363x_regulator *lm363x_regulator = rdev_get_drvdata(rdev);
+	enum lm363x_regulator_id id = rdev_get_id(rdev);
+	u8 val, addr, mask;
+
+	switch (id) {
+	case LM3631_LDO_CONT:
+		addr = LM3631_REG_ENTIME_VCONT;
+		mask = LM3631_ENTIME_CONT_MASK;
+		break;
+	case LM3631_LDO_OREF:
+		addr = LM3631_REG_ENTIME_VOREF;
+		mask = LM3631_ENTIME_MASK;
+		break;
+	case LM3631_LDO_POS:
+		addr = LM3631_REG_ENTIME_VPOS;
+		mask = LM3631_ENTIME_MASK;
+		break;
+	case LM3631_LDO_NEG:
+		addr = LM3631_REG_ENTIME_VNEG;
+		mask = LM3631_ENTIME_MASK;
+		break;
+	default:
+		return 0;
+	}
+
+	if (ti_lmu_read_byte(lm363x_regulator->lmu, addr, &val))
+		return -EINVAL;
+
+	val = (val & mask) >> LM3631_ENTIME_SHIFT;
+
+	if (id == LM3631_LDO_CONT)
+		return ldo_cont_enable_time[val];
+	else
+		return ENABLE_TIME_USEC * val;
+}
+
+static struct regulator_ops lm363x_boost_voltage_table_ops = {
+	.list_voltage     = regulator_list_voltage_linear,
+	.set_voltage_sel  = regulator_set_voltage_sel_regmap,
+	.get_voltage_sel  = regulator_get_voltage_sel_regmap,
+};
+
+static struct regulator_ops lm363x_regulator_voltage_table_ops = {
+	.list_voltage     = regulator_list_voltage_linear,
+	.set_voltage_sel  = regulator_set_voltage_sel_regmap,
+	.get_voltage_sel  = regulator_get_voltage_sel_regmap,
+	.enable           = regulator_enable_regmap,
+	.disable          = regulator_disable_regmap,
+	.is_enabled       = regulator_is_enabled_regmap,
+	.enable_time      = lm363x_regulator_enable_time,
+};
+
+static const struct regulator_desc lm363x_regulator_desc[] = {
+	/* LM3631 */
+	{
+		.name           = "vboost",
+		.id             = LM3631_BOOST,
+		.ops            = &lm363x_boost_voltage_table_ops,
+		.n_voltages     = LM3631_BOOST_VSEL_MAX + 1,
+		.min_uV         = LM3631_VBOOST_MIN,
+		.uV_step        = LM363X_STEP_50mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3631_REG_VOUT_BOOST,
+		.vsel_mask      = LM3631_VOUT_MASK,
+	},
+	{
+		.name           = "ldo_cont",
+		.id             = LM3631_LDO_CONT,
+		.ops            = &lm363x_regulator_voltage_table_ops,
+		.n_voltages     = LM3631_CONT_VSEL_MAX + 1,
+		.min_uV         = LM3631_VCONT_MIN,
+		.uV_step        = LM363X_STEP_500mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3631_REG_VOUT_CONT,
+		.vsel_mask      = LM3631_VOUT_CONT_MASK,
+		.enable_reg     = LM3631_REG_LDO_CTRL2,
+		.enable_mask    = LM3631_EN_CONT_MASK,
+	},
+	{
+		.name           = "ldo_oref",
+		.id             = LM3631_LDO_OREF,
+		.ops            = &lm363x_regulator_voltage_table_ops,
+		.n_voltages     = LM3631_LDO_VSEL_MAX + 1,
+		.min_uV         = LM3631_VLDO_MIN,
+		.uV_step        = LM363X_STEP_50mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3631_REG_VOUT_OREF,
+		.vsel_mask      = LM3631_VOUT_MASK,
+		.enable_reg     = LM3631_REG_LDO_CTRL1,
+		.enable_mask    = LM3631_EN_OREF_MASK,
+	},
+	{
+		.name           = "ldo_vpos",
+		.id             = LM3631_LDO_POS,
+		.ops            = &lm363x_regulator_voltage_table_ops,
+		.n_voltages     = LM3631_LDO_VSEL_MAX + 1,
+		.min_uV         = LM3631_VLDO_MIN,
+		.uV_step        = LM363X_STEP_50mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3631_REG_VOUT_POS,
+		.vsel_mask      = LM3631_VOUT_MASK,
+		.enable_reg     = LM3631_REG_LDO_CTRL1,
+		.enable_mask    = LM3631_EN_VPOS_MASK,
+	},
+	{
+		.name           = "ldo_vneg",
+		.id             = LM3631_LDO_NEG,
+		.ops            = &lm363x_regulator_voltage_table_ops,
+		.n_voltages     = LM3631_LDO_VSEL_MAX + 1,
+		.min_uV         = LM3631_VLDO_MIN,
+		.uV_step        = LM363X_STEP_50mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3631_REG_VOUT_NEG,
+		.vsel_mask      = LM3631_VOUT_MASK,
+		.enable_reg     = LM3631_REG_LDO_CTRL1,
+		.enable_mask    = LM3631_EN_VNEG_MASK,
+	},
+	/* LM3632 */
+	{
+		.name           = "vboost",
+		.id             = LM3632_BOOST,
+		.ops            = &lm363x_boost_voltage_table_ops,
+		.n_voltages     = LM3632_BOOST_VSEL_MAX + 1,
+		.min_uV         = LM3632_VBOOST_MIN,
+		.uV_step        = LM363X_STEP_50mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3632_REG_VOUT_BOOST,
+		.vsel_mask      = LM3632_VOUT_MASK,
+	},
+	{
+		.name           = "ldo_vpos",
+		.id             = LM3632_LDO_POS,
+		.ops            = &lm363x_regulator_voltage_table_ops,
+		.n_voltages     = LM3632_LDO_VSEL_MAX + 1,
+		.min_uV         = LM3632_VLDO_MIN,
+		.uV_step        = LM363X_STEP_50mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3632_REG_VOUT_POS,
+		.vsel_mask      = LM3632_VOUT_MASK,
+		.enable_reg     = LM3632_REG_BIAS_CONFIG,
+		.enable_mask    = LM3632_EN_VPOS_MASK,
+	},
+	{
+		.name           = "ldo_vneg",
+		.id             = LM3632_LDO_NEG,
+		.ops            = &lm363x_regulator_voltage_table_ops,
+		.n_voltages     = LM3632_LDO_VSEL_MAX + 1,
+		.min_uV         = LM3632_VLDO_MIN,
+		.uV_step        = LM363X_STEP_50mV,
+		.type           = REGULATOR_VOLTAGE,
+		.owner          = THIS_MODULE,
+		.vsel_reg       = LM3632_REG_VOUT_NEG,
+		.vsel_mask      = LM3632_VOUT_MASK,
+		.enable_reg     = LM3632_REG_BIAS_CONFIG,
+		.enable_mask    = LM3632_EN_VNEG_MASK,
+	},
+};
+
+static struct of_regulator_match lm363x_regulator_matches[] = {
+	[LM3631_BOOST]		= { .name = "vboost" },
+	[LM3631_LDO_CONT]	= { .name = "vcont" },
+	[LM3631_LDO_OREF]	= { .name = "voref" },
+	[LM3631_LDO_POS]	= { .name = "vpos" },
+	[LM3631_LDO_NEG]	= { .name = "vneg" },
+	[LM3632_BOOST]		= { .name = "vboost" },
+	[LM3632_LDO_POS]	= { .name = "vpos" },
+	[LM3632_LDO_NEG]	= { .name = "vneg" },
+};
+
+static struct regulator_init_data *
+lm363x_regulator_of_get_init_data(struct device *dev,
+			struct lm363x_regulator *lm363x_regulator, int id)
+{
+	struct device_node *np = dev->of_node;
+	int count;
+
+	count = of_regulator_match(dev, np, &lm363x_regulator_matches[id], 1);
+	if (count <= 0)
+		return ERR_PTR(-ENODEV);
+
+	return lm363x_regulator_matches[id].init_data;
+}
+
+static int lm363x_regulator_of_get_enable_gpio(struct device *dev, int id)
+{
+	struct device_node *np = dev->of_node;
+	int gpio = -1;
+
+	/*
+	 * Check LCM_EN1/2_GPIO is configured.
+	 * Those pins are used for enabling VPOS/VNEG LDOs.
+	 */
+	if (id == LM3632_LDO_POS)
+		gpio = of_get_named_gpio(np, "ti,lcm-en1-gpio", 0);
+	else if (id == LM3632_LDO_NEG)
+		gpio = of_get_named_gpio(np, "ti,lcm-en2-gpio", 0);
+
+	return gpio;
+}
+
+static int lm363x_regulator_probe(struct platform_device *pdev)
+{
+	struct ti_lmu *lmu = dev_get_drvdata(pdev->dev.parent);
+	struct lm363x_regulator *lm363x_regulator;
+	struct regulator_init_data *init_data;
+	struct regulator_config cfg = { };
+	struct regulator_dev *rdev;
+	struct device *dev = &pdev->dev;
+	int id = pdev->id;
+	int ret, ena_gpio;
+
+	lm363x_regulator = devm_kzalloc(dev, sizeof(*lm363x_regulator),
+					GFP_KERNEL);
+	if (!lm363x_regulator)
+		return -ENOMEM;
+
+	lm363x_regulator->lmu = lmu;
+
+	init_data = lm363x_regulator_of_get_init_data(dev, lm363x_regulator,
+						      id);
+	if (IS_ERR(init_data))
+		return PTR_ERR(init_data);
+
+	cfg.dev = dev->parent;
+	cfg.init_data = init_data;
+	cfg.driver_data = lm363x_regulator;
+	cfg.regmap = lmu->regmap;
+
+	/*
+	 * LM3632 LDOs can be controlled by external pin.
+	 * Register update is required if the pin is used.
+	 */
+	ena_gpio = lm363x_regulator_of_get_enable_gpio(dev, id);
+	if (gpio_is_valid(ena_gpio)) {
+		cfg.ena_gpio = ena_gpio;
+		cfg.ena_gpio_flags = GPIOF_OUT_INIT_LOW;
+
+		ret = ti_lmu_update_bits(lmu, LM3632_REG_BIAS_CONFIG,
+					 LM3632_EXT_EN_MASK,
+					 LM3632_EXT_EN_MASK);
+		if (ret) {
+			dev_err(dev, "External pin err: %d\n", ret);
+			return ret;
+		}
+	}
+
+	rdev = regulator_register(&lm363x_regulator_desc[id], &cfg);
+	if (IS_ERR(rdev)) {
+		ret = PTR_ERR(rdev);
+		dev_err(dev, "[%d] regulator register err: %d\n", id, ret);
+		return ret;
+	}
+
+	lm363x_regulator->regulator = rdev;
+	platform_set_drvdata(pdev, lm363x_regulator);
+
+	return 0;
+}
+
+static int lm363x_regulator_remove(struct platform_device *pdev)
+{
+	struct lm363x_regulator *lm363x_regulator = platform_get_drvdata(pdev);
+
+	regulator_unregister(lm363x_regulator->regulator);
+	return 0;
+}
+
+static const struct of_device_id lm363x_regulator_of_match[] = {
+	{ .compatible = "ti,lm363x-regulator" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, lm363x_regulator_of_match);
+
+static struct platform_driver lm363x_regulator_driver = {
+	.probe = lm363x_regulator_probe,
+	.remove = lm363x_regulator_remove,
+	.driver = {
+		.name = "lm363x-regulator",
+		.of_match_table = lm363x_regulator_of_match,
+	},
+};
+
+module_platform_driver(lm363x_regulator_driver);
+
+MODULE_DESCRIPTION("TI LM363X Regulator Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:lm363x-regulator");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [next] | [standalone]


#1260379 — [PATCH 15/16] leds: add LM3633 driver

FromMilo Kim <milo.kim@ti.com>
Date2015-11-02 06:10 +0100
Subject[PATCH 15/16] leds: add LM3633 driver
Message-ID<qqfxw-1df-25@gated-at.bofh.it>
In reply to#1260373
LM3633 LED driver supports generic LED functions and pattern generation.
Pattern is generated through the sysfs. ABI documentation is also added.

Device creation from device tree
--------------------------------
  LED channel name, LED string usage and max current settings are
  configured inside the DT.

LED dimming pattern generation
------------------------------
  LM3633 supports programmable dimming pattern generator.
  To enable it, three attributes are used.
  'pattern_times', 'pattern_levels' and 'run_pattern'.
  Sysfs ABI describes it.

LMU HWMON event handling
------------------------
  As soon as LMU HWMON operation is done, LMU HWMON driver sends the event,
  'LMU_EVENT_HWMON_DONE'. Then, LM3633 device should be reinitialized
  because the device was reset by LMU HWMON driver.
  lm3633_led_hwmon_notifier() handles this event.

Data structure
--------------
  ti_lmu_led_chip:	LED device data.
  ti_lmu_led:		LED output channel data.
			One LED device can have multiple LED channels.

Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: linux-leds@vger.kernel.org
Cc: Lee Jones <lee.jones@linaro.org>
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 Documentation/ABI/testing/sysfs-class-led-lm3633 |  60 ++
 drivers/leds/Kconfig                             |  10 +
 drivers/leds/Makefile                            |   1 +
 drivers/leds/leds-lm3633.c                       | 749 +++++++++++++++++++++++
 4 files changed, 820 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-lm3633
 create mode 100644 drivers/leds/leds-lm3633.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-lm3633 b/Documentation/ABI/testing/sysfs-class-led-lm3633
new file mode 100644
index 0000000..c1d8759
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-lm3633
@@ -0,0 +1,60 @@
+LM3633 LED driver generates programmable pattern via the sysfs.
+
+LED Pattern Generator Structure
+
+                            (3)
+  (a) --------------->  ___________
+                       /           \
+                  (2) /             \ (4)
+  (b) ----> _________/               \_________  ...
+               (1)                       (5)
+
+                     |<-----   period   -----> |
+
+What:		/sys/class/leds/<led>/pattern_times
+Date:		Oct 2015
+KernelVersion:	4.3
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	read/write
+                Set pattern time dimension. There are five arguments.
+                  (1) startup delay
+                  (2) rising dimming time
+                  (3) how much time stays at high level
+                  (4) falling dimming time
+                  (5) how much time stays at low level
+                Ranges are
+                  (1), (3), (5): 0 ~ 10000. Unit is millisecond.
+                  (2), (4): 0 ~ 16000. Unit is millisecond.
+
+                Example:
+                No delay, rising 200ms, high 300ms, falling 100ms, low 400ms.
+                echo "0 200 300 100 400" > /sys/class/leds/<led>/pattern_times
+
+                cat /sys/class/leds/<led>/pattern_times
+                delay: 0, rise: 200, high: 300, fall: 100, low: 400
+
+What:		/sys/class/leds/<led>/pattern_levels
+Date:		Oct 2015
+KernelVersion:	4.3
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	read/write
+                Set pattern level(brightness). There are two arguments.
+                  (a) Low brightness level
+                  (b) High brightness level
+                Ranges are from 0 to 255.
+
+                Example:
+                Low level is 0, high level is 255.
+                echo "0 255" > /sys/class/leds/<led>/pattern_levels
+
+                cat /sys/class/leds/<led>/pattern_levels
+                low brightness: 0, high brightness: 255
+
+What:		/sys/class/leds/<led>/run_pattern
+Date:		Oct 2015
+KernelVersion:	4.3
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                After 'pattern_times' and 'pattern_levels' are updated,
+                run the pattern by writing 1 to 'run_pattern'.
+                To stop running pattern, writes 0 to 'run_pattern'.
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 42990f2..bc41a0e 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -88,6 +88,16 @@ config LEDS_LM3533
 	  hardware-accelerated blinking with maximum on and off periods of 9.8
 	  and 77 seconds respectively.
 
+config LEDS_LM3633
+	tristate "LED support for the TI LM3633 LMU"
+	depends on LEDS_CLASS
+	depends on MFD_TI_LMU
+	help
+	  This option enables support for the LEDs on the LM3633.
+	  LM3633 has 6 low voltage indicator LEDs.
+	  All low voltage current sinks can have a programmable pattern
+	  modulated onto LED output strings.
+
 config LEDS_LM3642
 	tristate "LED support for LM3642 Chip"
 	depends on LEDS_CLASS && I2C
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index b503f92..984f44a 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
 obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
+obj-$(CONFIG_LEDS_LM3633)		+= leds-lm3633.o
 obj-$(CONFIG_LEDS_LM3642)		+= leds-lm3642.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
diff --git a/drivers/leds/leds-lm3633.c b/drivers/leds/leds-lm3633.c
new file mode 100644
index 0000000..66da609
--- /dev/null
+++ b/drivers/leds/leds-lm3633.c
@@ -0,0 +1,749 @@
+/*
+ * TI LM3633 LED driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * 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/leds.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define LM3633_LED_MAX_BRIGHTNESS		255
+#define LM3633_DEFAULT_LED_NAME			"indicator"
+#define LM3633_MAX_PERIOD			9700
+#define LM3633_SHORT_TIMESTEP			16
+#define LM3633_LONG_TIMESTEP			131
+#define LM3633_TIME_OFFSET			61
+#define LM3633_PATTERN_REG_OFFSET		16
+
+enum lm3633_led_bank_id {
+	LM3633_LED_BANK_C,
+	LM3633_LED_BANK_D,
+	LM3633_LED_BANK_E,
+	LM3633_LED_BANK_F,
+	LM3633_LED_BANK_G,
+	LM3633_LED_BANK_H,
+	LM3633_MAX_LEDS,
+};
+
+struct lm3633_pattern_time {
+	unsigned int delay;
+	unsigned int rise;
+	unsigned int high;
+	unsigned int fall;
+	unsigned int low;
+};
+
+struct lm3633_pattern_level {
+	u8 low;
+	u8 high;
+};
+
+/**
+ * struct ti_lmu_led_chip
+ *
+ * @dev:		Parent device pointer
+ * @lmu:		LMU structure. Used for register R/W access.
+ * @lock:		Secure handling for multiple user interface access
+ * @lmu_led:		Multiple LED strings
+ * @num_leds:		Number of LED strings
+ * @nb:			Notifier block for handling hwmon event
+ *
+ * One LED chip can have multiple LED strings.
+ */
+struct ti_lmu_led_chip {
+	struct device *dev;
+	struct ti_lmu *lmu;
+	struct mutex lock;
+	struct ti_lmu_led *lmu_led;
+	int num_leds;
+	struct notifier_block nb;
+};
+
+/**
+ * struct ti_lmu_led
+ *
+ * @chip:		Pointer to parent LED device
+ * @bank_id:		LED bank ID
+ * @cdev:		LED subsystem device structure
+ * @name:		LED channel name
+ * @led_string:		LED string configuration.
+ *			Bit mask is set on parsing DT.
+ * @imax:		[Optional] Max current index.
+ *			It's result of ti_lmu_get_current_code().
+ * @work:		Used for scheduling brightness control
+ * @brightness:		Brightness value
+ * @time:		Pattern time dimension
+ * @level:		Pattern level dimension
+ *
+ * Each LED device has its own channel configuration.
+ * For chip control, parent chip data structure is used.
+ */
+struct ti_lmu_led {
+	struct ti_lmu_led_chip *chip;
+	enum lm3633_led_bank_id bank_id;
+	struct led_classdev cdev;
+	const char *name;
+
+	unsigned long led_string;	/* bit OR mask of LMU_LVLEDx */;
+	#define LMU_LVLED1	BIT(0)
+	#define LMU_LVLED2	BIT(1)
+	#define LMU_LVLED3	BIT(2)
+	#define LMU_LVLED4	BIT(3)
+	#define LMU_LVLED5	BIT(4)
+	#define LMU_LVLED6	BIT(5)
+
+	struct work_struct work;
+	enum led_brightness brightness;
+	enum ti_lmu_max_current imax;
+
+	/* Pattern specific data */
+	struct lm3633_pattern_time time;
+	struct lm3633_pattern_level level;
+};
+
+static struct ti_lmu_led *to_ti_lmu_led(struct device *dev)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+
+	return container_of(cdev, struct ti_lmu_led, cdev);
+}
+
+static u8 lm3633_led_get_enable_mask(struct ti_lmu_led *lmu_led)
+{
+	return 1 << (lmu_led->bank_id + LM3633_LED_BANK_OFFSET);
+}
+
+static int lm3633_led_enable_bank(struct ti_lmu_led *lmu_led)
+{
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return ti_lmu_update_bits(lmu_led->chip->lmu, LM3633_REG_ENABLE,
+				  mask, mask);
+}
+
+static int lm3633_led_disable_bank(struct ti_lmu_led *lmu_led)
+{
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return ti_lmu_update_bits(lmu_led->chip->lmu, LM3633_REG_ENABLE,
+				  mask, 0);
+}
+
+static int lm3633_led_enable_pattern(struct ti_lmu_led *lmu_led)
+{
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return ti_lmu_update_bits(lmu_led->chip->lmu, LM3633_REG_PATTERN, mask,
+				  mask);
+}
+
+static int lm3633_led_disable_pattern(struct ti_lmu_led *lmu_led)
+{
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return ti_lmu_update_bits(lmu_led->chip->lmu, LM3633_REG_PATTERN, mask,
+				  0);
+}
+
+static int lm3633_led_config_bank(struct ti_lmu_led *lmu_led)
+{
+	const u8 group_led[] = { 0, BIT(0), BIT(0), 0, BIT(3), BIT(3), };
+	const enum lm3633_led_bank_id default_id[] = {
+		LM3633_LED_BANK_C, LM3633_LED_BANK_C, LM3633_LED_BANK_C,
+		LM3633_LED_BANK_F, LM3633_LED_BANK_F, LM3633_LED_BANK_F,
+	};
+	const enum lm3633_led_bank_id separate_id[] = {
+		LM3633_LED_BANK_C, LM3633_LED_BANK_D, LM3633_LED_BANK_E,
+		LM3633_LED_BANK_F, LM3633_LED_BANK_G, LM3633_LED_BANK_H,
+	};
+	int i, ret;
+	u8 val;
+
+	/*
+	 * Check configured LED string and assign control bank
+	 *
+	 * Each LED is tied with other LEDS (group):
+	 *   the default control bank is assigned
+	 *
+	 * Otherwise:
+	 *   separate bank is assigned
+	 */
+
+	for (i = 0; i < LM3633_MAX_LEDS; i++) {
+		/* LED 0 and LED 3 are fixed, so no assignment is required */
+		if (i == 0 || i == 3)
+			continue;
+
+		if (test_bit(i, &lmu_led->led_string)) {
+			if (lmu_led->led_string & group_led[i]) {
+				lmu_led->bank_id = default_id[i];
+				val = 0;
+			} else {
+				lmu_led->bank_id = separate_id[i];
+				val = BIT(i);
+			}
+
+			ret = ti_lmu_update_bits(lmu_led->chip->lmu,
+						 LM3633_REG_BANK_SEL,
+						 BIT(i), val);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static ssize_t lm3633_led_show_pattern_times(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buf)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+
+	return sprintf(buf,
+		       "delay: %u, rise: %u, high: %u, fall: %u, low: %u\n",
+		       lmu_led->time.delay, lmu_led->time.rise,
+		       lmu_led->time.high, lmu_led->time.fall,
+		       lmu_led->time.low);
+}
+
+static u8 lm3633_convert_time_to_index(unsigned int msec)
+{
+	u8 idx, offset;
+
+	/*
+	 * Find appropriate register index around input time value
+	 *
+	 *      0 <= time <= 1000 : 16ms step
+	 *   1000 <  time <= 9700 : 131ms step, base index is 61
+	 */
+
+	msec = min_t(int, msec, LM3633_MAX_PERIOD);
+
+	if (msec <= 1000) {
+		idx = msec / LM3633_SHORT_TIMESTEP;
+		if (idx > 1)
+			idx--;
+		offset = 0;
+	} else {
+		idx = (msec - 1000) / LM3633_LONG_TIMESTEP;
+		offset = LM3633_TIME_OFFSET;
+	}
+
+	return idx + offset;
+}
+
+static u8 lm3633_convert_ramp_to_index(unsigned int msec)
+{
+	const int ramp_table[] = { 2, 250, 500, 1000, 2000, 4000, 8000, 16000 };
+	int size = ARRAY_SIZE(ramp_table);
+	int i;
+
+	if (msec <= ramp_table[0])
+		return 0;
+
+	if (msec > ramp_table[size - 1])
+		return size - 1;
+
+	for (i = 1; i < size; i++) {
+		if (msec == ramp_table[i])
+			return i;
+
+		/* Find the most closest value by looking up the table */
+		if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
+			if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
+				return i - 1;
+			else
+				return i;
+		}
+	}
+
+	return 0;
+}
+
+static ssize_t lm3633_led_store_pattern_times(struct device *dev,
+					      struct device_attribute *attr,
+					      const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	struct ti_lmu_led_chip *chip = lmu_led->chip;
+	struct lm3633_pattern_time *time = &lmu_led->time;
+	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
+	int ret;
+	u8 reg, val;
+
+	/*
+	 * Sequence
+	 *
+	 * 1) Read pattern time data (unit: msec)
+	 * 2) Update DELAY register
+	 * 3) Update HIGH TIME register
+	 * 4) Update LOW TIME register
+	 * 5) Update RAMP TIME registers
+	 *
+	 * Time register addresses need offset number based on the LED bank.
+	 * Register values are index domain, so input time value should be
+	 * converted to index.
+	 * Please note that ramp register address has no offset value.
+	 */
+
+	ret = sscanf(buf, "%u %u %u %u %u", &time->delay, &time->rise,
+		     &time->high, &time->fall, &time->low);
+	if (ret != 5)
+		return -EINVAL;
+
+	mutex_lock(&chip->lock);
+
+	reg = LM3633_REG_PTN_DELAY + offset;
+	val = lm3633_convert_time_to_index(time->delay);
+	ret = ti_lmu_write_byte(chip->lmu, reg, val);
+	if (ret)
+		goto skip;
+
+	reg = LM3633_REG_PTN_HIGHTIME + offset;
+	val = lm3633_convert_time_to_index(time->high);
+	ret = ti_lmu_write_byte(chip->lmu, reg, val);
+	if (ret)
+		goto skip;
+
+	reg = LM3633_REG_PTN_LOWTIME + offset;
+	val = lm3633_convert_time_to_index(time->low);
+	ret = ti_lmu_write_byte(chip->lmu, reg, val);
+	if (ret)
+		goto skip;
+
+	switch (lmu_led->bank_id) {
+	case LM3633_LED_BANK_C:
+	case LM3633_LED_BANK_D:
+	case LM3633_LED_BANK_E:
+		reg = LM3633_REG_PTN0_RAMP;
+		break;
+	case LM3633_LED_BANK_F:
+	case LM3633_LED_BANK_G:
+	case LM3633_LED_BANK_H:
+		reg = LM3633_REG_PTN1_RAMP;
+		break;
+	default:
+		ret = -EINVAL;
+		goto skip;
+	}
+
+	val = lm3633_convert_ramp_to_index(time->rise);
+	ret = ti_lmu_update_bits(chip->lmu, reg, LM3633_PTN_RAMPUP_MASK,
+				 val << LM3633_PTN_RAMPUP_SHIFT);
+	if (ret)
+		goto skip;
+
+	val = lm3633_convert_ramp_to_index(time->fall);
+	ret = ti_lmu_update_bits(chip->lmu, reg, LM3633_PTN_RAMPDN_MASK,
+				 val << LM3633_PTN_RAMPDN_SHIFT);
+	if (ret)
+		goto skip;
+
+	mutex_unlock(&chip->lock);
+	return len;
+
+skip:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static ssize_t lm3633_led_show_pattern_levels(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+
+	return sprintf(buf, "low brightness: %u, high brightness: %u\n",
+		       lmu_led->level.low, lmu_led->level.high);
+}
+
+static ssize_t lm3633_led_store_pattern_levels(struct device *dev,
+					       struct device_attribute *attr,
+					       const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	struct ti_lmu_led_chip *chip = lmu_led->chip;
+	unsigned int low, high;
+	u8 reg, offset, val;
+	int ret;
+
+	/*
+	 * Sequence
+	 *
+	 * 1) Read pattern level data
+	 * 2) Disable a bank before programming a pattern
+	 * 3) Update LOW BRIGHTNESS register
+	 * 4) Update HIGH BRIGHTNESS register
+	 *
+	 * Level register addresses have offset number based on the LED bank.
+	 */
+
+	ret = sscanf(buf, "%u %u", &low, &high);
+	if (ret != 2)
+		return -EINVAL;
+
+	low = min_t(unsigned int, low, LM3633_LED_MAX_BRIGHTNESS);
+	high = min_t(unsigned int, high, LM3633_LED_MAX_BRIGHTNESS);
+	lmu_led->level.low = (u8)low;
+	lmu_led->level.high = (u8)high;
+
+	mutex_lock(&chip->lock);
+	ret = lm3633_led_disable_bank(lmu_led);
+	if (ret)
+		goto skip;
+
+	offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
+	reg = LM3633_REG_PTN_LOWBRT + offset;
+	val = lmu_led->level.low;
+	ret = ti_lmu_write_byte(chip->lmu, reg, val);
+	if (ret)
+		goto skip;
+
+	offset = lmu_led->bank_id;
+	reg = LM3633_REG_PTN_HIGHBRT + offset;
+	val = lmu_led->level.high;
+	ret = ti_lmu_write_byte(chip->lmu, reg, val);
+	if (ret)
+		goto skip;
+
+	mutex_unlock(&chip->lock);
+	return len;
+
+skip:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static ssize_t lm3633_led_run_pattern(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	struct ti_lmu_led_chip *chip = lmu_led->chip;
+	unsigned long enable;
+	int ret;
+
+	if (kstrtoul(buf, 0, &enable))
+		return -EINVAL;
+
+	mutex_lock(&chip->lock);
+
+	if (enable)
+		ret = lm3633_led_enable_pattern(lmu_led);
+	else
+		ret = lm3633_led_disable_pattern(lmu_led);
+
+	if (ret) {
+		mutex_unlock(&chip->lock);
+		return ret;
+	}
+
+	if (enable)
+		lm3633_led_enable_bank(lmu_led);
+
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static DEVICE_ATTR(pattern_times, S_IRUGO | S_IWUSR,
+		   lm3633_led_show_pattern_times,
+		   lm3633_led_store_pattern_times);
+static DEVICE_ATTR(pattern_levels, S_IRUGO | S_IWUSR,
+		   lm3633_led_show_pattern_levels,
+		   lm3633_led_store_pattern_levels);
+static DEVICE_ATTR(run_pattern, S_IWUSR, NULL,
+		   lm3633_led_run_pattern);
+
+static struct attribute *lm3633_led_attrs[] = {
+	&dev_attr_pattern_times.attr,
+	&dev_attr_pattern_levels.attr,
+	&dev_attr_run_pattern.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(lm3633_led);	/* lm3633_led_groups */
+
+static int lm3633_led_set_max_current(struct ti_lmu_led *lmu_led)
+{
+	u8 reg = LM3633_REG_IMAX_LVLED_BASE + lmu_led->bank_id;
+
+	return ti_lmu_write_byte(lmu_led->chip->lmu, reg, lmu_led->imax);
+}
+
+static void lm3633_led_work(struct work_struct *work)
+{
+	struct ti_lmu_led *lmu_led = container_of(work, struct ti_lmu_led,
+						  work);
+	struct ti_lmu_led_chip *chip = lmu_led->chip;
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = ti_lmu_write_byte(chip->lmu,
+				LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id,
+				lmu_led->brightness);
+	if (ret) {
+		mutex_unlock(&chip->lock);
+		return;
+	}
+
+	if (lmu_led->brightness == 0)
+		lm3633_led_disable_bank(lmu_led);
+	else
+		lm3633_led_enable_bank(lmu_led);
+
+	mutex_unlock(&chip->lock);
+}
+
+static void lm3633_led_brightness_set(struct led_classdev *cdev,
+				      enum led_brightness brt_val)
+{
+	struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
+						  cdev);
+
+	lmu_led->brightness = brt_val;
+	schedule_work(&lmu_led->work);
+}
+
+static int lm3633_led_init(struct ti_lmu_led *lmu_led, int bank_id)
+{
+	struct device *dev = lmu_led->chip->dev;
+	char name[12];
+	int ret;
+
+	/*
+	 * Sequence
+	 *
+	 * 1) Configure LED bank which is used for brightness control
+	 * 2) Set max current for each output channel
+	 * 3) Add LED device
+	 */
+
+	ret = lm3633_led_config_bank(lmu_led);
+	if (ret) {
+		dev_err(dev, "Output bank register err: %d\n", ret);
+		return ret;
+	}
+
+	ret = lm3633_led_set_max_current(lmu_led);
+	if (ret) {
+		dev_err(dev, "Set max current err: %d\n", ret);
+		return ret;
+	}
+
+	lmu_led->cdev.max_brightness = LM3633_LED_MAX_BRIGHTNESS;
+	lmu_led->cdev.brightness_set = lm3633_led_brightness_set;
+	lmu_led->cdev.groups = lm3633_led_groups;
+
+	if (lmu_led->name) {
+		lmu_led->cdev.name = lmu_led->name;
+	} else {
+		snprintf(name, sizeof(name), "%s:%d", LM3633_DEFAULT_LED_NAME,
+			 bank_id);
+		lmu_led->cdev.name = name;
+	}
+
+	ret = led_classdev_register(dev, &lmu_led->cdev);
+	if (ret) {
+		dev_err(dev, "LED register err: %d\n", ret);
+		return ret;
+	}
+
+	INIT_WORK(&lmu_led->work, lm3633_led_work);
+
+	return 0;
+}
+
+static int lm3633_led_of_create(struct ti_lmu_led_chip *chip,
+				struct device_node *np)
+{
+	struct device_node *child;
+	struct device *dev = chip->dev;
+	struct ti_lmu_led *lmu_led, *each;
+	int num_leds;
+	int i = 0;
+	u32 imax;
+
+	if (!np)
+		return -ENODEV;
+
+	num_leds = of_get_child_count(np);
+	if (num_leds == 0 || num_leds > LM3633_MAX_LEDS) {
+		dev_err(dev, "Invalid number of LEDs: %d\n", num_leds);
+		return -EINVAL;
+	}
+
+	lmu_led = devm_kzalloc(dev, sizeof(*lmu_led) * num_leds, GFP_KERNEL);
+	if (!lmu_led)
+		return -ENOMEM;
+
+	for_each_child_of_node(np, child) {
+		each = lmu_led + i;
+
+		of_property_read_string(child, "channel-name", &each->name);
+
+		/* Make LED strings */
+		each->led_string = 0;
+		if (of_property_read_bool(child, "lvled1-used"))
+			each->led_string |= LMU_LVLED1;
+		if (of_property_read_bool(child, "lvled2-used"))
+			each->led_string |= LMU_LVLED2;
+		if (of_property_read_bool(child, "lvled3-used"))
+			each->led_string |= LMU_LVLED3;
+		if (of_property_read_bool(child, "lvled4-used"))
+			each->led_string |= LMU_LVLED4;
+		if (of_property_read_bool(child, "lvled5-used"))
+			each->led_string |= LMU_LVLED5;
+		if (of_property_read_bool(child, "lvled6-used"))
+			each->led_string |= LMU_LVLED6;
+
+		imax = 0;
+		of_property_read_u32(child, "led-max-microamp", &imax);
+		each->imax = ti_lmu_get_current_code(imax);
+
+		each->bank_id = 0;
+		each->chip = chip;
+		i++;
+	}
+
+	chip->lmu_led = lmu_led;
+	chip->num_leds = num_leds;
+
+	return 0;
+}
+
+static int lm3633_led_hwmon_notifier(struct notifier_block *nb,
+				     unsigned long action, void *unused)
+{
+	struct ti_lmu_led_chip *chip = container_of(nb, struct ti_lmu_led_chip,
+						    nb);
+	struct ti_lmu_led *each;
+	int i, ret;
+
+	/* LED should be reconfigured after hwmon procedure is done */
+	if (action == LMU_EVENT_HWMON_DONE) {
+		for (i = 0; i < chip->num_leds; i++) {
+			each = chip->lmu_led + i;
+			ret = lm3633_led_config_bank(each);
+			if (ret) {
+				dev_err(chip->dev,
+					"Output bank register err: %d\n", ret);
+				return NOTIFY_STOP;
+			}
+
+			ret = lm3633_led_set_max_current(each);
+			if (ret) {
+				dev_err(chip->dev, "Set max current err: %d\n",
+					ret);
+				return NOTIFY_STOP;
+			}
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static int lm3633_led_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
+	struct ti_lmu_led_chip *chip;
+	struct ti_lmu_led *each;
+	int i, ret;
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = dev;
+	chip->lmu = lmu;
+
+	ret = lm3633_led_of_create(chip, dev->of_node);
+	if (ret)
+		return ret;
+
+	/*
+	 * Notifier callback is required because LED device needs
+	 * reconfiguration after opened/shorted circuit fault monitoring
+	 * by ti-lmu-hwmon driver.
+	 */
+	chip->nb.notifier_call = lm3633_led_hwmon_notifier;
+	ret = blocking_notifier_chain_register(&chip->lmu->notifier, &chip->nb);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < chip->num_leds; i++) {
+		each = chip->lmu_led + i;
+		ret = lm3633_led_init(each, i);
+		if (ret) {
+			dev_err(dev, "LED initialization err: %d\n", ret);
+			goto cleanup_leds;
+		}
+	}
+
+	mutex_init(&chip->lock);
+	platform_set_drvdata(pdev, chip);
+
+	return 0;
+
+cleanup_leds:
+	while (--i >= 0) {
+		each = chip->lmu_led + i;
+		led_classdev_unregister(&each->cdev);
+	}
+	return ret;
+}
+
+static int lm3633_led_remove(struct platform_device *pdev)
+{
+	struct ti_lmu_led_chip *chip = platform_get_drvdata(pdev);
+	struct ti_lmu_led *each;
+	int i;
+
+	blocking_notifier_chain_unregister(&chip->lmu->notifier, &chip->nb);
+
+	for (i = 0; i < chip->num_leds; i++) {
+		each = chip->lmu_led + i;
+		led_classdev_unregister(&each->cdev);
+		flush_work(&each->work);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id lm3633_led_of_match[] = {
+	{ .compatible = "ti,lm3633-leds" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, lm3633_led_of_match);
+
+static struct platform_driver lm3633_led_driver = {
+	.probe = lm3633_led_probe,
+	.remove = lm3633_led_remove,
+	.driver = {
+		.name = "lm3633-leds",
+		.of_match_table = lm3633_led_of_match,
+	},
+};
+
+module_platform_driver(lm3633_led_driver);
+
+MODULE_DESCRIPTION("TI LM3633 LED Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:lm3633-leds");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [next] | [standalone]


#1260374 — [PATCH 12/16] backlight: ti-lmu-backlight: add LM3695 driver

FromMilo Kim <milo.kim@ti.com>
Date2015-11-02 06:10 +0100
Subject[PATCH 12/16] backlight: ti-lmu-backlight: add LM3695 driver
Message-ID<qqfxw-1df-27@gated-at.bofh.it>
In reply to#1260364
LM3695 has 2 backlight strings and 11 bit dimming is supported.

Common backlight driver is controlled by TI LMU backlight driver.
Only LM3695 specific code is implemented here.

Cc: Jingoo Han <jingoohan1@gmail.com>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/video/backlight/Kconfig     |  8 ++++
 drivers/video/backlight/Makefile    |  1 +
 drivers/video/backlight/lm3695_bl.c | 91 +++++++++++++++++++++++++++++++++++++
 3 files changed, 100 insertions(+)
 create mode 100644 drivers/video/backlight/lm3695_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index e3c4adb..c1dc908 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -426,6 +426,14 @@ config BACKLIGHT_LM3633
 	  Up to 3 backlight strings and 11 bit dimming is supported.
 	  PWM brightness control is also supported.
 
+config BACKLIGHT_LM3695
+	tristate "Backlight driver for TI LM3695"
+	depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU
+	select TI_LMU_BACKLIGHT
+	help
+	  Say Y to enable the backlight driver for TI LM3695.
+	  Up to 2 backlight strings and 11 bit dimming is supported.
+
 config TI_LMU_BACKLIGHT
 	tristate "Backlight driver for TI LMU"
 	depends on BACKLIGHT_LM3532 || BACKLIGHT_LM3631 || \
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 364b854..af20285 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_BACKLIGHT_LM3631)		+= lm3631_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3632)		+= lm3632_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3633)		+= lm3633_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3639)		+= lm3639_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3695)		+= lm3695_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)		+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)		+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_LP8788)		+= lp8788_bl.o
diff --git a/drivers/video/backlight/lm3695_bl.c b/drivers/video/backlight/lm3695_bl.c
new file mode 100644
index 0000000..fe47d00
--- /dev/null
+++ b/drivers/video/backlight/lm3695_bl.c
@@ -0,0 +1,91 @@
+/*
+ * TI LM3695 Backlight Driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * 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/delay.h>
+#include <linux/kernel.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+
+#include "ti-lmu-backlight.h"
+
+#define LM3695_FULL_STRINGS		(LMU_HVLED1 | LMU_HVLED2)
+#define LM3695_MAX_BRIGHTNESS		2047
+
+static int lm3695_bl_enable(struct ti_lmu_bl *lmu_bl, int enable)
+{
+	int ret;
+
+	ret = ti_lmu_update_bits(lmu_bl->chip->lmu, LM3695_REG_GP,
+				 LM3695_BL_EN_MASK, enable);
+	if (ret)
+		return ret;
+
+	/* Wait time for brightness register wake up */
+	usleep_range(600, 700);
+
+	return 0;
+}
+
+static int lm3695_bl_set_brightness(struct ti_lmu_bl *lmu_bl, int brightness)
+{
+	u8 data;
+	int ret;
+
+	data = brightness & LM3695_BRT_LSB_MASK;
+	ret = ti_lmu_update_bits(lmu_bl->chip->lmu, LM3695_REG_BRT_LSB,
+				 LM3695_BRT_LSB_MASK, data);
+	if (ret)
+		return ret;
+
+	data = (brightness >> LM3695_BRT_MSB_SHIFT) & 0xFF;
+	return ti_lmu_write_byte(lmu_bl->chip->lmu, LM3695_REG_BRT_MSB,
+				 data);
+}
+
+static int lm3695_bl_init(struct ti_lmu_bl_chip *chip)
+{
+	return ti_lmu_update_bits(chip->lmu, LM3695_REG_GP,
+				  LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK);
+}
+
+static int lm3695_bl_configure(struct ti_lmu_bl *lmu_bl)
+{
+	u8 val;
+
+	if (lmu_bl->bl_string == LM3695_FULL_STRINGS)
+		val = LM3695_BL_TWO_STRINGS;
+	else
+		val = LM3695_BL_ONE_STRING;
+
+	return ti_lmu_update_bits(lmu_bl->chip->lmu, LM3695_REG_GP,
+				  LM3695_BL_STRING_MASK, val);
+}
+
+static const struct ti_lmu_bl_ops lm3695_lmu_ops = {
+	.init			= lm3695_bl_init,
+	.configure		= lm3695_bl_configure,
+	.update_brightness	= lm3695_bl_set_brightness,
+	.bl_enable		= lm3695_bl_enable,
+	.max_brightness		= LM3695_MAX_BRIGHTNESS,
+};
+
+/* LM3695 backlight of_device_id */
+TI_LMU_BL_OF_DEVICE(lm3695, "ti,lm3695-backlight");
+
+/* LM3695 backlight platform driver */
+TI_LMU_BL_PLATFORM_DRIVER(lm3695, "lm3695-backlight");
+
+MODULE_DESCRIPTION("TI LM3695 Backlight Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:lm3695-backlight");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [next] | [standalone]


#1260380 — [PATCH 13/16] backlight: ti-lmu-backlight: add LM3697 driver

FromMilo Kim <milo.kim@ti.com>
Date2015-11-02 06:10 +0100
Subject[PATCH 13/16] backlight: ti-lmu-backlight: add LM3697 driver
Message-ID<qqfxw-1df-29@gated-at.bofh.it>
In reply to#1260374
LM3697 has 3 backlight strings and 11 bit dimming is supported.
PWM brightness control is also supported.

LM3697 MFD has HWMON driver which monitors hardware fault detection.
After this monitoring is done, LM3697 backlight device should be
reinitialized. To enable this, 'hwmon_notifier_used' is set to true.
Then, TI LMU backlight handles LMU HWMON event in
ti_lmu_backlight_hwmon_notifier().

Common backlight driver is controlled by TI LMU backlight driver.
Only LM3697 specific code is implemented here.

Cc: Jingoo Han <jingoohan1@gmail.com>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/video/backlight/Kconfig     |   9 ++
 drivers/video/backlight/Makefile    |   1 +
 drivers/video/backlight/lm3697_bl.c | 187 ++++++++++++++++++++++++++++++++++++
 3 files changed, 197 insertions(+)
 create mode 100644 drivers/video/backlight/lm3697_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index c1dc908..c672073 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -434,6 +434,15 @@ config BACKLIGHT_LM3695
 	  Say Y to enable the backlight driver for TI LM3695.
 	  Up to 2 backlight strings and 11 bit dimming is supported.
 
+config BACKLIGHT_LM3697
+	tristate "Backlight driver for TI LM3697"
+	depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU
+	select TI_LMU_BACKLIGHT
+	help
+	  Say Y to enable the backlight driver for TI LM3697.
+	  Up to 3 backlight strings and 11 bit dimming is supported.
+	  PWM brightness control is also supported.
+
 config TI_LMU_BACKLIGHT
 	tristate "Backlight driver for TI LMU"
 	depends on BACKLIGHT_LM3532 || BACKLIGHT_LM3631 || \
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index af20285..abb7fe0 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_BACKLIGHT_LM3632)		+= lm3632_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3633)		+= lm3633_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3639)		+= lm3639_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3695)		+= lm3695_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3697)		+= lm3697_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)		+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)		+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_LP8788)		+= lp8788_bl.o
diff --git a/drivers/video/backlight/lm3697_bl.c b/drivers/video/backlight/lm3697_bl.c
new file mode 100644
index 0000000..bb0c2bf
--- /dev/null
+++ b/drivers/video/backlight/lm3697_bl.c
@@ -0,0 +1,187 @@
+/*
+ * TI LM3697 Backlight Driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * 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/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+
+#include "ti-lmu-backlight.h"
+
+#define LM3697_BL_MAX_STRINGS		3
+#define LM3697_MAX_BRIGHTNESS		2047
+
+static int lm3697_bl_init(struct ti_lmu_bl_chip *chip)
+{
+	/* Configure ramp selection for each bank */
+	return ti_lmu_update_bits(chip->lmu, LM3697_REG_RAMP_CONF,
+				  LM3697_RAMP_MASK, LM3697_RAMP_EACH);
+}
+
+static int lm3697_bl_enable(struct ti_lmu_bl *lmu_bl, int enable)
+{
+	return ti_lmu_update_bits(lmu_bl->chip->lmu, LM3697_REG_ENABLE,
+				  BIT(lmu_bl->bank_id),
+				  enable << lmu_bl->bank_id);
+}
+
+static int lm3697_bl_set_brightness(struct ti_lmu_bl *lmu_bl, int brightness)
+{
+	int ret;
+	u8 data;
+	u8 reg_lsb[] = { LM3697_REG_BRT_A_LSB, LM3697_REG_BRT_B_LSB, };
+	u8 reg_msb[] = { LM3697_REG_BRT_A_MSB, LM3697_REG_BRT_B_MSB, };
+
+	data = brightness & LM3697_BRT_LSB_MASK;
+	ret = ti_lmu_update_bits(lmu_bl->chip->lmu, reg_lsb[lmu_bl->bank_id],
+				 LM3697_BRT_LSB_MASK, data);
+	if (ret)
+		return ret;
+
+	data = (brightness >> LM3697_BRT_MSB_SHIFT) & 0xFF;
+	return ti_lmu_write_byte(lmu_bl->chip->lmu, reg_msb[lmu_bl->bank_id],
+				 data);
+}
+
+static int lm3697_bl_set_ctrl_mode(struct ti_lmu_bl *lmu_bl)
+{
+	int bank_id = lmu_bl->bank_id;
+
+	if (lmu_bl->mode == BL_PWM_BASED)
+		return ti_lmu_update_bits(lmu_bl->chip->lmu,
+					  LM3697_REG_PWM_CFG,
+					  BIT(bank_id), 1 << bank_id);
+
+	return 0;
+}
+
+static int lm3697_bl_string_configure(struct ti_lmu_bl *lmu_bl)
+{
+	struct ti_lmu *lmu = lmu_bl->chip->lmu;
+	int bank_id = lmu_bl->bank_id;
+	int is_detected = 0;
+	int i, ret;
+
+	/* Assign control bank from backlight string configuration */
+	for (i = 0; i < LM3697_BL_MAX_STRINGS; i++) {
+		if (test_bit(i, &lmu_bl->bl_string)) {
+			ret = ti_lmu_update_bits(lmu,
+						 LM3697_REG_HVLED_OUTPUT_CFG,
+						 BIT(i), bank_id << i);
+			if (ret)
+				return ret;
+
+			is_detected = 1;
+		}
+	}
+
+	if (!is_detected) {
+		dev_err(lmu_bl->chip->dev, "No backlight string found\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int lm3697_bl_set_current_limit(struct ti_lmu_bl *lmu_bl)
+{
+	u8 reg[] = { LM3697_REG_IMAX_A, LM3697_REG_IMAX_B, };
+
+	return ti_lmu_write_byte(lmu_bl->chip->lmu, reg[lmu_bl->bank_id],
+				 lmu_bl->imax);
+}
+
+static int lm3697_bl_set_ramp(struct ti_lmu_bl *lmu_bl)
+{
+	int ret, index;
+	u8 reg;
+
+	index = ti_lmu_backlight_get_ramp_index(lmu_bl, BL_RAMP_UP);
+	if (index > 0) {
+		if (lmu_bl->bank_id == 0)
+			reg = LM3697_REG_BL0_RAMPUP;
+		else
+			reg = LM3697_REG_BL1_RAMPUP;
+
+		ret = ti_lmu_update_bits(lmu_bl->chip->lmu, reg,
+					 LM3697_BL_RAMPUP_MASK,
+					 index << LM3697_BL_RAMPUP_SHIFT);
+		if (ret)
+			return ret;
+	}
+
+	index = ti_lmu_backlight_get_ramp_index(lmu_bl, BL_RAMP_DOWN);
+	if (index > 0) {
+		if (lmu_bl->bank_id == 0)
+			reg = LM3697_REG_BL0_RAMPDN;
+		else
+			reg = LM3697_REG_BL1_RAMPDN;
+
+		ret = ti_lmu_update_bits(lmu_bl->chip->lmu, reg,
+					 LM3697_BL_RAMPDN_MASK,
+					 index << LM3697_BL_RAMPDN_SHIFT);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int lm3697_bl_configure(struct ti_lmu_bl *lmu_bl)
+{
+	int ret;
+
+	ret = lm3697_bl_set_ctrl_mode(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = lm3697_bl_string_configure(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = lm3697_bl_set_current_limit(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = lm3697_bl_set_ramp(lmu_bl);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* Backlight ramp up/down time. Unit is msec. */
+static const int lm3697_ramp_table[] = {
+	   2, 250, 500, 1000, 2000, 4000, 8000, 16000,
+};
+
+static const struct ti_lmu_bl_ops lm3697_lmu_ops = {
+	.init			= lm3697_bl_init,
+	.configure		= lm3697_bl_configure,
+	.update_brightness	= lm3697_bl_set_brightness,
+	.bl_enable		= lm3697_bl_enable,
+	.hwmon_notifier_used	= true,
+	.max_brightness		= LM3697_MAX_BRIGHTNESS,
+	.ramp_table		= lm3697_ramp_table,
+	.size_ramp		= ARRAY_SIZE(lm3697_ramp_table),
+};
+
+/* LM3697 backlight of_device_id */
+TI_LMU_BL_OF_DEVICE(lm3697, "ti,lm3697-backlight");
+
+/* LM3697 backlight platform driver */
+TI_LMU_BL_PLATFORM_DRIVER(lm3697, "lm3697-backlight");
+
+MODULE_DESCRIPTION("TI LM3697 Backlight Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:lm3697-backlight");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web