linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/9] Support TI LMU devices
@ 2015-11-26  6:56 Milo Kim
  2015-11-26  6:56 ` [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information Milo Kim
                   ` (9 more replies)
  0 siblings, 10 replies; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:56 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

TI Lighting Management Unit drivers support lighting devices below.

         Enable pin  Backlight  HW fault monitoring  LEDs   Regulators
         ----------  ---------  -------------------  ----  ------------
LM3532       o           o               x            x         x
LM3631       o           o               x            x    5 regulators
LM3632       o           o               x            x    3 regulators
LM3633       o           o               o            o         x
LM3695       o           o               x            x         x
LM3697       o           o               o            x         x

This patch-set consists of several parts below.

  DT bindings        : Binding information for each module
  LMU MFD            : Device registration and HW enable pin control
  LMU fault monitor  : HW fault monitoring for open and short circuit
  LMU backlight      : Consolidated LMU backlight driver
  LM3633 LED         : LED subsystem and dimming pattern generation
                       supported
  LM363X regulator   : LM3631 and LM3632 regulator driver for the
                       display bias

Updates from v1
---------------
  * DT bindings
    mfd       : Describe complete DT properties.
    backlight : Move backlight properties into leds/backlight/.
                Use common LED properties like 'led-sources' and 'label'.
    hwmon     : LMU fault monitoring driver is not HWMON any more.
                So related properties are moved into 'ti-lmu' binding.
    leds      : Use LED common properties like 'led-sources' and 'label'.

  * MFD
    Remove LMU helpers for I2C register access. Each driver uses regmap
    helpers instead.

  * LMU fault monitoring driver
    In v1, it was HWMON driver but HWMON subsystem maintainer suggested
    moving it into MFD because it has no sensor data like temperature or
    voltage. Device attributes were replaced with debugfs files because
    monitoring should be processed for debug purpose only.

  * Backlight
    Six separate driver code was consolidated.
    Driver control code is implemented in 'ti-lmu-backlight-core.c'.
    Device specific data is defined in 'ti-lmu-backlight-data.c'.
    194 lines are saved in v2. The text segment is decreased by removing
    duplicate instructions.

    Lines of code:
      v1: 1420 (8 files)
      v2: 1226 (3 files)

    Size:
      v1:
      text  data  bss  filename
     12202   720   40  drivers/video/backlight/built-in.o
      v2:
      text  data  bss  filename
      6883   712   41  drivers/video/backlight/built-in.o

  * LED
    Use single device attribute for LED dimming operation.
    Max brightness is determined by DT property, 'led-max-microamp'.
    Remove brightness workqueue.

  * Regulator
    Use 'of_match' in regulator_desc instead of calling of_regulator_match.
    Remove unnecessary OF device ID because MFD core registers a platform
    device based on the compatible string.

Milo Kim (9):
  Documentation: dt-bindings: mfd: add TI LMU device binding information
  Documentation: dt-bindings: leds: backlight: add TI LMU backlight
    binding information
  Documentation: dt-bindings: leds: add LM3633 LED binding information
  Documentation: dt-bindings: regulator: add LM363x regulator binding
    information
  mfd: add TI LMU driver
  mfd: add TI LMU hardware fault monitoring driver
  backlight: add TI LMU backlight driver
  leds: add LM3633 driver
  regulator: add LM363X driver

 .../ABI/testing/debugfs-ti-lmu-fault-monitor       |  32 +
 Documentation/ABI/testing/sysfs-class-led-lm3633   |  97 +++
 .../bindings/leds/backlight/ti-lmu-backlight.txt   |  65 ++
 .../devicetree/bindings/leds/leds-lm3633.txt       |  24 +
 Documentation/devicetree/bindings/mfd/ti-lmu.txt   | 243 ++++++
 .../bindings/regulator/lm363x-regulator.txt        |  34 +
 drivers/leds/Kconfig                               |  10 +
 drivers/leds/Makefile                              |   1 +
 drivers/leds/leds-lm3633.c                         | 840 +++++++++++++++++++++
 drivers/mfd/Kconfig                                |  22 +
 drivers/mfd/Makefile                               |   3 +
 drivers/mfd/ti-lmu-fault-monitor.c                 | 405 ++++++++++
 drivers/mfd/ti-lmu.c                               | 259 +++++++
 drivers/regulator/Kconfig                          |   9 +
 drivers/regulator/Makefile                         |   1 +
 drivers/regulator/lm363x-regulator.c               | 309 ++++++++
 drivers/video/backlight/Kconfig                    |   7 +
 drivers/video/backlight/Makefile                   |   3 +
 drivers/video/backlight/ti-lmu-backlight-core.c    | 649 ++++++++++++++++
 drivers/video/backlight/ti-lmu-backlight-data.c    | 287 +++++++
 include/linux/mfd/ti-lmu-backlight.h               | 290 +++++++
 include/linux/mfd/ti-lmu-register.h                | 280 +++++++
 include/linux/mfd/ti-lmu.h                         |  87 +++
 23 files changed, 3957 insertions(+)
 create mode 100644 Documentation/ABI/testing/debugfs-ti-lmu-fault-monitor
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-lm3633
 create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
 create mode 100644 Documentation/devicetree/bindings/leds/leds-lm3633.txt
 create mode 100644 Documentation/devicetree/bindings/mfd/ti-lmu.txt
 create mode 100644 Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
 create mode 100644 drivers/leds/leds-lm3633.c
 create mode 100644 drivers/mfd/ti-lmu-fault-monitor.c
 create mode 100644 drivers/mfd/ti-lmu.c
 create mode 100644 drivers/regulator/lm363x-regulator.c
 create mode 100644 drivers/video/backlight/ti-lmu-backlight-core.c
 create mode 100644 drivers/video/backlight/ti-lmu-backlight-data.c
 create mode 100644 include/linux/mfd/ti-lmu-backlight.h
 create mode 100644 include/linux/mfd/ti-lmu-register.h
 create mode 100644 include/linux/mfd/ti-lmu.h

-- 
1.9.1


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

* [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
@ 2015-11-26  6:56 ` Milo Kim
  2015-11-27 20:55   ` Rob Herring
  2016-01-11  9:46   ` Lee Jones
  2015-11-26  6:56 ` [PATCH v2 2/9] Documentation: dt-bindings: leds: backlight: add TI LMU backlight " Milo Kim
                   ` (8 subsequent siblings)
  9 siblings, 2 replies; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:56 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

This patch describes overall binding for TI LMU MFD devices.

Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: linux-leds@vger.kernel.org 
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 Documentation/devicetree/bindings/mfd/ti-lmu.txt | 243 +++++++++++++++++++++++
 1 file changed, 243 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/ti-lmu.txt

diff --git a/Documentation/devicetree/bindings/mfd/ti-lmu.txt b/Documentation/devicetree/bindings/mfd/ti-lmu.txt
new file mode 100644
index 0000000..c885cf8
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/ti-lmu.txt
@@ -0,0 +1,243 @@
+TI LMU (Lighting Management Unit) device tree bindings
+
+TI LMU driver supports lighting devices below.
+
+   Name                  Child nodes
+  ------      ---------------------------------
+  LM3532       Backlight
+  LM3631       Backlight and regulator
+  LM3632       Backlight and regulator
+  LM3633       Backlight, LED and fault monitor
+  LM3695       Backlight
+  LM3697       Backlight and fault monitor
+
+Required properties:
+  - compatible: Should be one of:
+                "ti,lm3532"
+                "ti,lm3631"
+                "ti,lm3632"
+                "ti,lm3633"
+                "ti,lm3695"
+                "ti,lm3697"
+  - reg: I2C slave address.
+         0x11 for LM3632
+         0x29 for LM3631
+         0x36 for LM3633, LM3697
+         0x38 for LM3532
+         0x63 for LM3695
+
+Optional property:
+  - enable-gpios: A GPIO specifier for hardware enable pin.
+
+Required node:
+  - backlight: All LMU devices have backlight child nodes.
+               For the properties, please refer to [1].
+
+Optional nodes:
+  - fault-monitor: Hardware fault monitoring driver for LM3633 and LM3697.
+    Required properties:
+      - compatible: Should be one of:
+                    "ti,lm3633-fault-monitor"
+                    "ti,lm3697-fault-monitor"
+  - leds: LED properties for LM3633. Please refer to [2].
+  - regulators: Regulator properties for LM3631 and LM3632.
+                Please refer to [3].
+
+[1] ../leds/backlight/ti-lmu-backlight.txt
+[2] ../leds/leds-lm3633.txt
+[3] ../regulator/lm363x-regulator.txt
+
+lm3532@38 {
+	compatible = "ti,lm3532";
+	reg = <0x38>;
+
+	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
+
+	backlight {
+		compatible = "ti,lm3532-backlight";
+
+		lcd {
+			led-sources = <0 1 2>;
+			ramp-up-msec = <30>;
+			ramp-down-msec = <0>;
+		};
+	};
+};
+
+lm3631@29 {
+	compatible = "ti,lm3631";
+	reg = <0x29>;
+
+	regulators {
+		compatible = "ti,lm363x-regulator";
+
+		vboost {
+			regulator-name = "lcd_boost";
+			regulator-min-microvolt = <4500000>;
+			regulator-max-microvolt = <6350000>;
+			regulator-always-on;
+		};
+
+		vcont {
+			regulator-name = "lcd_vcont";
+			regulator-min-microvolt = <1800000>;
+			regulator-max-microvolt = <3300000>;
+		};
+
+		voref {
+			regulator-name = "lcd_voref";
+			regulator-min-microvolt = <4000000>;
+			regulator-max-microvolt = <6000000>;
+		};
+
+		vpos {
+			regulator-name = "lcd_vpos";
+			regulator-min-microvolt = <4000000>;
+			regulator-max-microvolt = <6000000>;
+			regulator-boot-on;
+		};
+
+		vneg {
+			regulator-name = "lcd_vneg";
+			regulator-min-microvolt = <4000000>;
+			regulator-max-microvolt = <6000000>;
+			regulator-boot-on;
+		};
+	};
+
+	backlight {
+		compatible = "ti,lm3631-backlight";
+
+		lcd_bl {
+			led-sources = <0 1>;
+			ramp-up-msec = <300>;
+		};
+	};
+};
+
+lm3632@11 {
+	compatible = "ti,lm3632";
+	reg = <0x11>;
+
+	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>; /* PC2 */
+
+	regulators {
+		compatible = "ti,lm363x-regulator";
+
+		ti,lcm-en1-gpio = <&pioC 0 GPIO_ACTIVE_HIGH>; /* PC0 */
+		ti,lcm-en2-gpio = <&pioC 1 GPIO_ACTIVE_HIGH>; /* PC1 */
+
+		vboost {
+			regulator-name = "lcd_boost";
+			regulator-min-microvolt = <4500000>;
+			regulator-max-microvolt = <6400000>;
+			regulator-always-on;
+		};
+
+		vpos {
+			regulator-name = "lcd_vpos";
+			regulator-min-microvolt = <4000000>;
+			regulator-max-microvolt = <6000000>;
+		};
+
+		vneg {
+			regulator-name = "lcd_vneg";
+			regulator-min-microvolt = <4000000>;
+			regulator-max-microvolt = <6000000>;
+		};
+	};
+
+	backlight {
+		compatible = "ti,lm3632-backlight";
+
+		pwms = <&pwm0 0 10000 0>; /* pwm number, period, polarity */
+		pwm-names = "lmu-backlight";
+
+		lcd {
+			led-sources = <0 1>;
+			pwm-period = <10000>;
+		};
+	};
+};
+
+lm3633@36 {
+	compatible = "ti,lm3633";
+	reg = <0x36>;
+
+	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
+
+	backlight {
+		compatible = "ti,lm3633-backlight";
+
+		main {
+			label = "main_lcd";
+			led-sources = <1 2>;
+			ramp-up-msec = <500>;
+			ramp-down-msec = <500>;
+		};
+
+		front {
+			label = "front_lcd";
+			led-sources = <0>;
+			ramp-up-msec = <1000>;
+			ramp-down-msec = <0>;
+		};
+	};
+
+	leds {
+		compatible = "ti,lm3633-leds";
+
+		chan1 {
+			label = "status";
+			led-sources = <1>;
+			led-max-microamp = <6000>;
+		};
+
+		chan345 {
+			label = "rgb";
+			led-sources = <3 4 5>;
+			led-max-microamp = <10000>;
+		};
+	};
+
+	fault-monitor {
+		compatible = "ti,lm3633-fault-monitor";
+	};
+};
+
+lm3695@63 {
+	compatible = "ti,lm3695";
+	reg = <0x63>;
+
+	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
+
+	backlight {
+		compatible = "ti,lm3695-backlight";
+
+		lcd {
+			label = "bl";
+			led-sources = <0 1>;
+		};
+	};
+};
+
+lm3697@36 {
+	compatible = "ti,lm3697";
+	reg = <0x36>;
+
+	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
+
+	backlight {
+		compatible = "ti,lm3697-backlight";
+
+		lcd {
+			led-sources = <0 1 2>;
+			ramp-up-msec = <200>;
+			ramp-down-msec = <200>;
+		};
+	};
+
+	fault-monitor {
+		compatible = "ti,lm3697-fault-monitor";
+	};
+};
-- 
1.9.1


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

* [PATCH v2 2/9] Documentation: dt-bindings: leds: backlight: add TI LMU backlight binding information
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
  2015-11-26  6:56 ` [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information Milo Kim
@ 2015-11-26  6:56 ` Milo Kim
  2016-01-11  9:53   ` Lee Jones
  2015-11-26  6:56 ` [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED " Milo Kim
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:56 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697 use common dt-bindings
for describing backlight device.

Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: linux-leds@vger.kernel.org 
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 .../bindings/leds/backlight/ti-lmu-backlight.txt   | 65 ++++++++++++++++++++++
 1 file changed, 65 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt

diff --git a/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
new file mode 100644
index 0000000..c2c35b2
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
@@ -0,0 +1,65 @@
+TI LMU backlight device tree bindings
+
+Required property:
+  - compatible: Should be one of:
+                "ti,lm3532-backlight"
+                "ti,lm3631-backlight"
+                "ti,lm3632-backlight"
+                "ti,lm3633-backlight"
+                "ti,lm3695-backlight"
+                "ti,lm3697-backlight"
+
+Optional properties:
+  There are two backlight control mode. One is I2C, the other is PWM mode.
+  Following properties are only specified in PWM mode.
+  Please note that LMU backlight device can have only one PWM channel.
+
+  - pwms: OF device-tree PWM specification.
+  - pwm-names: a list of names for the PWM devices specified in the "pwms"
+               property.
+
+  For the PWM user nodes, please refer to [1].
+
+Child nodes:
+  LMU backlight is represented as sub-nodes of the TI LMU device [2].
+  So, LMU backlight should have more than one backlight child node.
+  Each node exactly matches with backlight control bank configuration.
+  Maximum numbers of child nodes depend on the device.
+  1 = LM3631, LM3632, LM3695
+  2 = LM3633, LM3697
+  3 = LM3532
+
+  Required property of a child node:
+  - led-sources: List of enabled channels from 0 to 2.
+                 Please refer to LED binding [3].
+                 For output channels, please refer to the datasheets [4].
+
+  Optional properties of a child node:
+  - label: Backlight channel identification.
+           Please refer to LED binding [3].
+  - default-brightness-level: Backlight initial brightness value.
+                              Type is <u32>. It is set as soon as backlight
+                              device is created.
+                              0 ~ 2047 = LM3631, LM3632, LM3633, LM3695 and
+                                         LM3697
+                              0 ~ 255  = LM3532
+  - ramp-up-msec, ramp-down-msec: Light dimming effect properties.
+                                  Type is <u32>. Unit is millisecond.
+                                  0 ~ 65 msec    = LM3532
+                                  0 ~ 4000 msec  = LM3631
+                                  0 ~ 16000 msec = LM3633 and LM3697
+  - pwm-period: PWM period. Only valid in PWM brightness mode.
+                Type is <u32>. If this property is missing, then control
+                mode is set to I2C by default.
+
+Examples: Please refer to ti-lmu dt-bindings. [2].
+
+[1] ../pwm/pwm.txt
+[2] ../mfd/ti-lmu.txt
+[3] ../leds/common.txt
+[4] LM3532: http://www.ti.com/product/LM3532/datasheet
+    LM3631: http://www.ti.com/product/LM3631/datasheet
+    LM3632: http://www.ti.com/product/LM3632A/datasheet
+    LM3633: http://www.ti.com/product/LM3633/datasheet
+    LM3695: Datasheet is not opened yet, but only two strings are used.
+    LM3697: http://www.ti.com/product/LM3697/datasheet
-- 
1.9.1


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

* [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED binding information
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
  2015-11-26  6:56 ` [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information Milo Kim
  2015-11-26  6:56 ` [PATCH v2 2/9] Documentation: dt-bindings: leds: backlight: add TI LMU backlight " Milo Kim
@ 2015-11-26  6:56 ` Milo Kim
  2015-11-27 11:19   ` Jacek Anaszewski
  2015-11-26  6:57 ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator " Milo Kim
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:56 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

LM3633 LED device is one of TI LMU device list.

Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: linux-leds@vger.kernel.org 
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 .../devicetree/bindings/leds/leds-lm3633.txt       | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/leds-lm3633.txt

diff --git a/Documentation/devicetree/bindings/leds/leds-lm3633.txt b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
new file mode 100644
index 0000000..a553894
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
@@ -0,0 +1,24 @@
+TI LMU LM3633 LED device tree bindings
+
+Required properties:
+  - compatible: "ti,lm3633-leds"
+
+Child nodes:
+  Each node matches with LED control bank.
+  Please refer to the datasheet [1].
+
+  Required properties of a child node:
+  - led-sources: List of enabled channels from 0 to 5.
+                 Please refer to LED binding [2].
+
+  Optional properties of a child node:
+  - label: LED channel identification. Please refer to LED binding [2].
+  - led-max-microamp: Max current setting. Type is <u32>.
+                      Unit is microampere. Range is from 5000 to 30000.
+                      Step is 1000. Please refer to LED binding [2].
+
+Example: Please refer to ti-lmu dt-bindings [3].
+
+[1] http://www.ti.com/product/LM3633/datasheet
+[2] ../leds/common.txt
+[2] ../mfd/ti-lmu.txt
-- 
1.9.1


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

* [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
                   ` (2 preceding siblings ...)
  2015-11-26  6:56 ` [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED " Milo Kim
@ 2015-11-26  6:57 ` Milo Kim
  2015-11-27 12:37   ` Mark Brown
                     ` (2 more replies)
  2015-11-26  6:57 ` [PATCH v2 5/9] mfd: add TI LMU driver Milo Kim
                   ` (5 subsequent siblings)
  9 siblings, 3 replies; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:57 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

This binding describes LM3631 and LM3632 regulator properties.

Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: linux-leds@vger.kernel.org 
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 .../bindings/regulator/lm363x-regulator.txt        | 34 ++++++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/regulator/lm363x-regulator.txt

diff --git a/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt b/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
new file mode 100644
index 0000000..8f14df9
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
@@ -0,0 +1,34 @@
+TI LMU LM363x regulator device tree bindings
+
+LM363x regulator driver supports LM3631 and LM3632.
+LM3631 has five regulators and LM3632 supports three regulators.
+
+Required property:
+  - compatible: "ti,lm363x-regulator"
+
+Optional properties:
+  LM3632 has external enable pins for two LDOs.
+  - ti,lcm-en1-gpio: A GPIO specifier for Vpos control pin.
+  - ti,lcm-en2-gpio: A GPIO specifier for Vneg control pin.
+
+Child nodes:
+  LM3631
+  - vboost
+  - vcont
+  - voref
+  - vpos
+  - vneg
+
+  LM3632
+  - vboost
+  - vpos
+  - vneg
+
+  Optional properties of a child node:
+  Each sub-node should contain the constraints and initialization.
+  Please refer to [1].
+
+Examples: Please refer to ti-lmu dt-bindings [2].
+
+[1] ../regulator/regulator.txt
+[2] ../mfd/ti-lmu.txt
-- 
1.9.1


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

* [PATCH v2 5/9] mfd: add TI LMU driver
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
                   ` (3 preceding siblings ...)
  2015-11-26  6:57 ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator " Milo Kim
@ 2015-11-26  6:57 ` Milo Kim
  2016-01-11 10:17   ` Lee Jones
  2015-11-26  6:57 ` [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver Milo Kim
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:57 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

TI LMU (Lighting Management Unit) driver supports lighting devices below.

  LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.

LMU devices have common features.
  - I2C interface for accessing device registers
  - Hardware enable pin control
  - Backlight brightness control
  - Notifier for hardware fault monitoring
  - Regulators for LCD display bias

It contains fault monitor, backlight, LED and regulator driver.

LMU fault monitor
-----------------
  LM3633 and LM3697 provide hardware monitoring feature.
  It enables open or short circuit detection.
  After monitoring is done, each device should be re-initialized.
  Notifier is used for this case.
  Please refer to separate patch for 'ti-lmu-fault-monitor'.

Backlight
---------
  It's handled by TI LMU backlight consolidated driver and
  chip dependent data. Please refer to separate patches for
  'ti-lmu-backlight'.

LED indicator
-------------
  LM3633 has 6 indicator LEDs. Programmable dimming pattern is also
  supported. Please refer to separate patch for 'leds-lm3633'.

Regulator
---------
  LM3631 has 5 regulators for the display bias.
  LM3632 supports 3 regulators. One consolidated driver enables it.
  Please refer to separate patch for 'lm363x-regulator'.

Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: linux-leds@vger.kernel.org 
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/mfd/Kconfig                 |  12 ++
 drivers/mfd/Makefile                |   2 +
 drivers/mfd/ti-lmu.c                | 259 +++++++++++++++++++++++++++++++++
 include/linux/mfd/ti-lmu-register.h | 280 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/ti-lmu.h          |  87 +++++++++++
 5 files changed, 640 insertions(+)
 create mode 100644 drivers/mfd/ti-lmu.c
 create mode 100644 include/linux/mfd/ti-lmu-register.h
 create mode 100644 include/linux/mfd/ti-lmu.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 4d92df6..a6aab27 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1049,6 +1049,18 @@ config MFD_LP8788
 	  TI LP8788 PMU supports regulators, battery charger, RTC,
 	  ADC, backlight driver and current sinks.
 
+config MFD_TI_LMU
+	tristate "TI Lighting Management Unit driver"
+	depends on I2C
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Say yes here to enable support for TI LMU chips.
+
+	  TI LMU MFD supports LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
+	  It consists of backlight, LED and regulator driver.
+	  It provides consistent device controls for lighting functions.
+
 config MFD_OMAP_USB_HOST
 	bool "TI OMAP USBHS core and TLL driver"
 	depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index a8b76b8..10e4bc2 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -111,6 +111,8 @@ obj-$(CONFIG_MFD_AXP20X)	+= axp20x.o
 obj-$(CONFIG_MFD_LP3943)	+= lp3943.o
 obj-$(CONFIG_MFD_LP8788)	+= lp8788.o lp8788-irq.o
 
+obj-$(CONFIG_MFD_TI_LMU)	+= ti-lmu.o
+
 da9055-objs			:= da9055-core.o da9055-i2c.o
 obj-$(CONFIG_MFD_DA9055)	+= da9055.o
 obj-$(CONFIG_MFD_DA9062)	+= da9062-core.o
diff --git a/drivers/mfd/ti-lmu.c b/drivers/mfd/ti-lmu.c
new file mode 100644
index 0000000..aeb1e7d
--- /dev/null
+++ b/drivers/mfd/ti-lmu.c
@@ -0,0 +1,259 @@
+/*
+ * TI LMU (Lighting Management Unit) Core 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/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.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_device.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+
+struct ti_lmu_data {
+	struct mfd_cell *cells;
+	int num_cells;
+	unsigned int max_register;
+};
+
+static int ti_lmu_enable_hw(struct ti_lmu *lmu, enum ti_lmu_id id)
+{
+	int ret;
+
+	if (gpio_is_valid(lmu->en_gpio)) {
+		ret = devm_gpio_request_one(lmu->dev, lmu->en_gpio,
+					    GPIOF_OUT_INIT_HIGH, "lmu_hwen");
+		if (ret) {
+			dev_err(lmu->dev, "Can not request enable GPIO: %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	/* Delay about 1ms after HW enable pin control */
+	usleep_range(1000, 1500);
+
+	/* LM3631 has additional power up sequence - enable LCD_EN bit. */
+	if (id == LM3631) {
+		return regmap_update_bits(lmu->regmap, LM3631_REG_DEVCTRL,
+					  LM3631_LCD_EN_MASK,
+					  LM3631_LCD_EN_MASK);
+	}
+
+	return 0;
+}
+
+static void ti_lmu_disable_hw(struct ti_lmu *lmu)
+{
+	if (gpio_is_valid(lmu->en_gpio))
+		gpio_set_value(lmu->en_gpio, 0);
+}
+
+static struct mfd_cell lm3532_devices[] = {
+	{
+		.name          = "ti-lmu-backlight",
+		.id            = LM3532,
+		.of_compatible = "ti,lm3532-backlight",
+	},
+};
+
+#define LM363X_REGULATOR(_id)			\
+{						\
+	.name          = "lm363x-regulator",	\
+	.id            = _id,			\
+	.of_compatible = "ti,lm363x-regulator",	\
+}						\
+
+static struct mfd_cell lm3631_devices[] = {
+	LM363X_REGULATOR(LM3631_BOOST),
+	LM363X_REGULATOR(LM3631_LDO_CONT),
+	LM363X_REGULATOR(LM3631_LDO_OREF),
+	LM363X_REGULATOR(LM3631_LDO_POS),
+	LM363X_REGULATOR(LM3631_LDO_NEG),
+	{
+		.name          = "ti-lmu-backlight",
+		.id            = LM3631,
+		.of_compatible = "ti,lm3631-backlight",
+	},
+};
+
+static struct mfd_cell lm3632_devices[] = {
+	LM363X_REGULATOR(LM3632_BOOST),
+	LM363X_REGULATOR(LM3632_LDO_POS),
+	LM363X_REGULATOR(LM3632_LDO_NEG),
+	{
+		.name          = "ti-lmu-backlight",
+		.id            = LM3632,
+		.of_compatible = "ti,lm3632-backlight",
+	},
+};
+
+static struct mfd_cell lm3633_devices[] = {
+	{
+		.name          = "ti-lmu-backlight",
+		.id            = LM3633,
+		.of_compatible = "ti,lm3633-backlight",
+	},
+	{
+		.name          = "lm3633-leds",
+		.of_compatible = "ti,lm3633-leds",
+	},
+	/* Monitoring driver for open/short circuit detection */
+	{
+		.name          = "ti-lmu-fault-monitor",
+		.id            = LM3633,
+		.of_compatible = "ti,lm3633-fault-monitor",
+	},
+};
+
+static struct mfd_cell lm3695_devices[] = {
+	{
+		.name          = "ti-lmu-backlight",
+		.id            = LM3695,
+		.of_compatible = "ti,lm3695-backlight",
+	},
+};
+
+static struct mfd_cell lm3697_devices[] = {
+	{
+		.name          = "ti-lmu-backlight",
+		.id            = LM3697,
+		.of_compatible = "ti,lm3697-backlight",
+	},
+	/* Monitoring driver for open/short circuit detection */
+	{
+		.name          = "ti-lmu-fault-monitor",
+		.id            = LM3697,
+		.of_compatible = "ti,lm3697-fault-monitor",
+	},
+};
+
+#define TI_LMU_DATA(chip, max_reg)		\
+static const struct ti_lmu_data chip##_data =	\
+{						\
+	.cells = chip##_devices,		\
+	.num_cells = ARRAY_SIZE(chip##_devices),\
+	.max_register = max_reg,		\
+}						\
+
+TI_LMU_DATA(lm3532, LM3532_MAX_REG);
+TI_LMU_DATA(lm3631, LM3631_MAX_REG);
+TI_LMU_DATA(lm3632, LM3632_MAX_REG);
+TI_LMU_DATA(lm3633, LM3633_MAX_REG);
+TI_LMU_DATA(lm3695, LM3695_MAX_REG);
+TI_LMU_DATA(lm3697, LM3697_MAX_REG);
+
+static const struct of_device_id ti_lmu_of_match[] = {
+	{ .compatible = "ti,lm3532", .data = &lm3532_data },
+	{ .compatible = "ti,lm3631", .data = &lm3631_data },
+	{ .compatible = "ti,lm3632", .data = &lm3632_data },
+	{ .compatible = "ti,lm3633", .data = &lm3633_data },
+	{ .compatible = "ti,lm3695", .data = &lm3695_data },
+	{ .compatible = "ti,lm3697", .data = &lm3697_data },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ti_lmu_of_match);
+
+static int ti_lmu_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+	struct device *dev = &cl->dev;
+	const struct of_device_id *match;
+	const struct ti_lmu_data *data;
+	struct regmap_config regmap_cfg;
+	struct ti_lmu *lmu;
+	int ret;
+
+	match = of_match_device(ti_lmu_of_match, dev);
+	if (!match)
+		return -ENODEV;
+	/*
+	 * Get device specific data from of_match table.
+	 * This data is defined by using TI_LMU_DATA() macro.
+	 */
+	data = (struct ti_lmu_data *)match->data;
+
+	lmu = devm_kzalloc(dev, sizeof(*lmu), GFP_KERNEL);
+	if (!lmu)
+		return -ENOMEM;
+
+	lmu->dev = &cl->dev;
+
+	/* Setup regmap */
+	memset(&regmap_cfg, 0, sizeof(struct regmap_config));
+	regmap_cfg.reg_bits = 8;
+	regmap_cfg.val_bits = 8;
+	regmap_cfg.name = id->name;
+	regmap_cfg.max_register = data->max_register;
+
+	lmu->regmap = devm_regmap_init_i2c(cl, &regmap_cfg);
+	if (IS_ERR(lmu->regmap))
+		return PTR_ERR(lmu->regmap);
+
+	/* HW enable pin control and additional power up sequence if required */
+	lmu->en_gpio = of_get_named_gpio(dev->of_node, "enable-gpios", 0);
+	ret = ti_lmu_enable_hw(lmu, id->driver_data);
+	if (ret)
+		return ret;
+
+	/*
+	 * Fault circuit(open/short) can be detected by ti-lmu-fault-monitor.
+	 * After fault detection is done, some devices should re-initialize
+	 * configuration. The notifier enables such kind of handling.
+	 */
+	BLOCKING_INIT_NOTIFIER_HEAD(&lmu->notifier);
+
+	i2c_set_clientdata(cl, lmu);
+
+	return mfd_add_devices(lmu->dev, 0, data->cells,
+			       data->num_cells, NULL, 0, NULL);
+}
+
+static int ti_lmu_remove(struct i2c_client *cl)
+{
+	struct ti_lmu *lmu = i2c_get_clientdata(cl);
+
+	ti_lmu_disable_hw(lmu);
+	mfd_remove_devices(lmu->dev);
+	return 0;
+}
+
+static const struct i2c_device_id ti_lmu_ids[] = {
+	{ "lm3532", LM3532 },
+	{ "lm3631", LM3631 },
+	{ "lm3632", LM3632 },
+	{ "lm3633", LM3633 },
+	{ "lm3695", LM3695 },
+	{ "lm3697", LM3697 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ti_lmu_ids);
+
+static struct i2c_driver ti_lmu_driver = {
+	.probe = ti_lmu_probe,
+	.remove = ti_lmu_remove,
+	.driver = {
+		.name = "ti-lmu",
+		.of_match_table = ti_lmu_of_match,
+	},
+	.id_table = ti_lmu_ids,
+};
+
+module_i2c_driver(ti_lmu_driver);
+
+MODULE_DESCRIPTION("TI LMU MFD Core Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/ti-lmu-register.h b/include/linux/mfd/ti-lmu-register.h
new file mode 100644
index 0000000..0ea4804
--- /dev/null
+++ b/include/linux/mfd/ti-lmu-register.h
@@ -0,0 +1,280 @@
+/*
+ * TI LMU (Lighting Management Unit) Device Register Map
+ *
+ * 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.
+ */
+
+#ifndef __MFD_TI_LMU_REGISTER_H__
+#define __MFD_TI_LMU_REGISTER_H__
+
+#include <linux/bitops.h>
+
+/* LM3532 */
+#define LM3532_REG_OUTPUT_CFG			0x10
+#define LM3532_ILED1_CFG_MASK			0x03
+#define LM3532_ILED2_CFG_MASK			0x0C
+#define LM3532_ILED3_CFG_MASK			0x30
+#define LM3532_ILED1_CFG_SHIFT			0
+#define LM3532_ILED2_CFG_SHIFT			2
+#define LM3532_ILED3_CFG_SHIFT			4
+
+#define LM3532_REG_RAMPUP			0x12
+#define LM3532_REG_RAMPDN			LM3532_REG_RAMPUP
+#define LM3532_RAMPUP_MASK			0x07
+#define LM3532_RAMPUP_SHIFT			0
+#define LM3532_RAMPDN_MASK			0x38
+#define LM3532_RAMPDN_SHIFT			3
+
+#define LM3532_REG_ENABLE			0x1D
+
+#define LM3532_REG_PWM_A_CFG			0x13
+#define LM3532_PWM_A_MASK			0x05	/* zone 0 */
+#define LM3532_PWM_ZONE_0			BIT(2)
+
+#define LM3532_REG_PWM_B_CFG			0x14
+#define LM3532_PWM_B_MASK			0x09	/* zone 1 */
+#define LM3532_PWM_ZONE_1			BIT(3)
+
+#define LM3532_REG_PWM_C_CFG			0x15
+#define LM3532_PWM_C_MASK			0x11	/* zone 2 */
+#define LM3532_PWM_ZONE_2			BIT(4)
+
+#define LM3532_REG_ZONE_CFG_A			0x16
+#define LM3532_REG_ZONE_CFG_B			0x18
+#define LM3532_REG_ZONE_CFG_C			0x1A
+#define LM3532_ZONE_MASK			(BIT(2) | BIT(3) | BIT(4))
+#define LM3532_ZONE_0				0
+#define LM3532_ZONE_1				BIT(2)
+#define LM3532_ZONE_2				BIT(3)
+
+#define LM3532_REG_BRT_A			0x70	/* zone 0 */
+#define LM3532_REG_BRT_B			0x76	/* zone 1 */
+#define LM3532_REG_BRT_C			0x7C	/* zone 2 */
+
+#define LM3532_MAX_REG				0x7E
+
+/* LM3631 */
+#define LM3631_REG_DEVCTRL			0x00
+#define LM3631_LCD_EN_MASK			BIT(1)
+#define LM3631_BL_EN_MASK			BIT(0)
+
+#define LM3631_REG_BRT_LSB			0x01
+#define LM3631_REG_BRT_MSB			0x02
+
+#define LM3631_REG_BL_CFG			0x06
+#define LM3631_BL_CHANNEL_MASK			BIT(3)
+#define LM3631_BL_DUAL_CHANNEL			0
+#define LM3631_BL_SINGLE_CHANNEL		BIT(3)
+#define LM3631_MAP_MASK				BIT(5)
+#define LM3631_EXPONENTIAL_MAP			0
+
+#define LM3631_REG_BRT_MODE			0x08
+#define LM3631_MODE_MASK			(BIT(1) | BIT(2) | BIT(3))
+#define LM3631_DEFAULT_MODE			(BIT(1) | BIT(3))
+
+#define LM3631_REG_SLOPE			0x09
+#define LM3631_SLOPE_MASK			0xF0
+#define LM3631_SLOPE_SHIFT			4
+
+#define LM3631_REG_LDO_CTRL1			0x0A
+#define LM3631_EN_OREF_MASK			BIT(0)
+#define LM3631_EN_VNEG_MASK			BIT(1)
+#define LM3631_EN_VPOS_MASK			BIT(2)
+
+#define LM3631_REG_LDO_CTRL2			0x0B
+#define LM3631_EN_CONT_MASK			BIT(0)
+
+#define LM3631_REG_VOUT_CONT			0x0C
+#define LM3631_VOUT_CONT_MASK			(BIT(6) | BIT(7))
+
+#define LM3631_REG_VOUT_BOOST			0x0C
+#define LM3631_REG_VOUT_POS			0x0D
+#define LM3631_REG_VOUT_NEG			0x0E
+#define LM3631_REG_VOUT_OREF			0x0F
+#define LM3631_VOUT_MASK			0x3F
+
+#define LM3631_REG_ENTIME_VCONT			0x0B
+#define LM3631_ENTIME_CONT_MASK			0x70
+
+#define LM3631_REG_ENTIME_VOREF			0x0F
+#define LM3631_REG_ENTIME_VPOS			0x10
+#define LM3631_REG_ENTIME_VNEG			0x11
+#define LM3631_ENTIME_MASK			0xF0
+#define LM3631_ENTIME_SHIFT			4
+
+#define LM3631_MAX_REG				0x16
+
+/* LM3632 */
+#define LM3632_REG_CONFIG1			0x02
+#define LM3632_OVP_MASK				(BIT(5) | BIT(6) | BIT(7))
+#define LM3632_OVP_25V				BIT(6)
+
+#define LM3632_REG_CONFIG2			0x03
+#define LM3632_SWFREQ_MASK			BIT(7)
+#define LM3632_SWFREQ_1MHZ			BIT(7)
+
+#define LM3632_REG_BRT_LSB			0x04
+#define LM3632_REG_BRT_MSB			0x05
+
+#define LM3632_REG_IO_CTRL			0x09
+#define LM3632_PWM_MASK				BIT(6)
+#define LM3632_I2C_MODE				0
+#define LM3632_PWM_MODE				BIT(6)
+
+#define LM3632_REG_ENABLE			0x0A
+#define LM3632_BL_EN_MASK			BIT(0)
+#define LM3632_BL_CHANNEL_MASK			(BIT(3) | BIT(4))
+#define LM3632_BL_SINGLE_CHANNEL			BIT(4)
+#define LM3632_BL_DUAL_CHANNEL			BIT(3)
+
+#define LM3632_REG_BIAS_CONFIG			0x0C
+#define LM3632_EXT_EN_MASK			BIT(0)
+#define LM3632_EN_VNEG_MASK			BIT(1)
+#define LM3632_EN_VPOS_MASK			BIT(2)
+
+#define LM3632_REG_VOUT_BOOST			0x0D
+#define LM3632_REG_VOUT_POS			0x0E
+#define LM3632_REG_VOUT_NEG			0x0F
+#define LM3632_VOUT_MASK			0x3F
+
+#define LM3632_MAX_REG				0x10
+
+/* LM3633 */
+#define LM3633_REG_HVLED_OUTPUT_CFG		0x10
+#define LM3633_HVLED1_CFG_MASK			BIT(0)
+#define LM3633_HVLED2_CFG_MASK			BIT(1)
+#define LM3633_HVLED3_CFG_MASK			BIT(2)
+#define LM3633_HVLED1_CFG_SHIFT			0
+#define LM3633_HVLED2_CFG_SHIFT			1
+#define LM3633_HVLED3_CFG_SHIFT			2
+
+#define LM3633_REG_BANK_SEL			0x11
+
+#define LM3633_REG_BL0_RAMP			0x12
+#define LM3633_REG_BL1_RAMP			0x13
+#define LM3633_BL_RAMPUP_MASK			0xF0
+#define LM3633_BL_RAMPUP_SHIFT			4
+#define LM3633_BL_RAMPDN_MASK			0x0F
+#define LM3633_BL_RAMPDN_SHIFT			0
+
+#define LM3633_REG_BL_RAMP_CONF			0x1B
+#define LM3633_BL_RAMP_MASK			0x0F
+#define LM3633_BL_RAMP_EACH			0x05
+
+#define LM3633_REG_PTN0_RAMP			0x1C
+#define LM3633_REG_PTN1_RAMP			0x1D
+#define LM3633_PTN_RAMPUP_MASK			0x70
+#define LM3633_PTN_RAMPUP_SHIFT			4
+#define LM3633_PTN_RAMPDN_MASK			0x07
+#define LM3633_PTN_RAMPDN_SHIFT			0
+
+#define LM3633_REG_LED_MAPPING_MODE		0x1F
+#define LM3633_LED_EXPONENTIAL			BIT(1)
+
+#define LM3633_REG_IMAX_HVLED_A			0x20
+#define LM3633_REG_IMAX_HVLED_B			0x21
+#define LM3633_REG_IMAX_LVLED_BASE		0x22
+
+#define LM3633_REG_BL_FEEDBACK_ENABLE		0x28
+
+#define LM3633_REG_ENABLE			0x2B
+#define LM3633_LED_BANK_OFFSET			2
+
+#define LM3633_REG_PATTERN			0x2C
+
+#define LM3633_REG_BOOST_CFG			0x2D
+#define LM3633_OVP_MASK				(BIT(1) | BIT(2))
+#define LM3633_OVP_40V				0x6
+
+#define LM3633_REG_PWM_CFG			0x2F
+#define LM3633_PWM_A_MASK			BIT(0)
+#define LM3633_PWM_B_MASK			BIT(1)
+
+#define LM3633_REG_BRT_HVLED_A_LSB		0x40
+#define LM3633_REG_BRT_HVLED_A_MSB		0x41
+#define LM3633_REG_BRT_HVLED_B_LSB		0x42
+#define LM3633_REG_BRT_HVLED_B_MSB		0x43
+
+#define LM3633_REG_BRT_LVLED_BASE		0x44
+
+#define LM3633_REG_PTN_DELAY			0x50
+
+#define LM3633_REG_PTN_LOWTIME			0x51
+
+#define LM3633_REG_PTN_HIGHTIME			0x52
+
+#define LM3633_REG_PTN_LOWBRT			0x53
+
+#define LM3633_REG_PTN_HIGHBRT			LM3633_REG_BRT_LVLED_BASE
+
+#define LM3633_REG_BL_OPEN_FAULT_STATUS		0xB0
+
+#define LM3633_REG_BL_SHORT_FAULT_STATUS	0xB2
+
+#define LM3633_REG_MONITOR_ENABLE		0xB4
+
+#define LM3633_MAX_REG				0xB4
+
+/* LM3695 */
+#define LM3695_REG_GP				0x10
+#define LM3695_BL_CHANNEL_MASK			BIT(3)
+#define LM3695_BL_DUAL_CHANNEL			0
+#define LM3695_BL_SINGLE_CHANNEL			BIT(3)
+#define LM3695_BRT_RW_MASK			BIT(2)
+#define LM3695_BL_EN_MASK			BIT(0)
+
+#define LM3695_REG_BRT_LSB			0x13
+#define LM3695_REG_BRT_MSB			0x14
+
+#define LM3695_MAX_REG				0x14
+
+/* LM3697 */
+#define LM3697_REG_HVLED_OUTPUT_CFG		0x10
+#define LM3697_HVLED1_CFG_MASK			BIT(0)
+#define LM3697_HVLED2_CFG_MASK			BIT(1)
+#define LM3697_HVLED3_CFG_MASK			BIT(2)
+#define LM3697_HVLED1_CFG_SHIFT			0
+#define LM3697_HVLED2_CFG_SHIFT			1
+#define LM3697_HVLED3_CFG_SHIFT			2
+
+#define LM3697_REG_BL0_RAMP			0x11
+#define LM3697_REG_BL1_RAMP			0x12
+#define LM3697_RAMPUP_MASK			0xF0
+#define LM3697_RAMPUP_SHIFT			4
+#define LM3697_RAMPDN_MASK			0x0F
+#define LM3697_RAMPDN_SHIFT			0
+
+#define LM3697_REG_RAMP_CONF			0x14
+#define LM3697_RAMP_MASK			0x0F
+#define LM3697_RAMP_EACH			0x05
+
+#define LM3697_REG_PWM_CFG			0x1C
+#define LM3697_PWM_A_MASK			BIT(0)
+#define LM3697_PWM_B_MASK			BIT(1)
+
+#define LM3697_REG_IMAX_A			0x17
+#define LM3697_REG_IMAX_B			0x18
+
+#define LM3697_REG_FEEDBACK_ENABLE		0x19
+
+#define LM3697_REG_BRT_A_LSB			0x20
+#define LM3697_REG_BRT_A_MSB			0x21
+#define LM3697_REG_BRT_B_LSB			0x22
+#define LM3697_REG_BRT_B_MSB			0x23
+
+#define LM3697_REG_ENABLE			0x24
+
+#define LM3697_REG_OPEN_FAULT_STATUS		0xB0
+
+#define LM3697_REG_SHORT_FAULT_STATUS		0xB2
+
+#define LM3697_REG_MONITOR_ENABLE		0xB4
+
+#define LM3697_MAX_REG				0xB4
+#endif
diff --git a/include/linux/mfd/ti-lmu.h b/include/linux/mfd/ti-lmu.h
new file mode 100644
index 0000000..cbb1074
--- /dev/null
+++ b/include/linux/mfd/ti-lmu.h
@@ -0,0 +1,87 @@
+/*
+ * TI LMU (Lighting Management Unit) Devices
+ *
+ * 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.
+ */
+
+#ifndef __MFD_TI_LMU_H__
+#define __MFD_TI_LMU_H__
+
+#include <linux/gpio.h>
+#include <linux/notifier.h>
+#include <linux/regmap.h>
+
+/* Notifier event */
+#define LMU_EVENT_MONITOR_DONE		0x01
+
+enum ti_lmu_id {
+	LM3532,
+	LM3631,
+	LM3632,
+	LM3633,
+	LM3695,
+	LM3697,
+	LMU_MAX_ID,
+};
+
+enum ti_lmu_max_current {
+	LMU_IMAX_5mA,
+	LMU_IMAX_6mA,
+	LMU_IMAX_7mA = 0x03,
+	LMU_IMAX_8mA,
+	LMU_IMAX_9mA,
+	LMU_IMAX_10mA = 0x07,
+	LMU_IMAX_11mA,
+	LMU_IMAX_12mA,
+	LMU_IMAX_13mA,
+	LMU_IMAX_14mA,
+	LMU_IMAX_15mA = 0x0D,
+	LMU_IMAX_16mA,
+	LMU_IMAX_17mA,
+	LMU_IMAX_18mA,
+	LMU_IMAX_19mA,
+	LMU_IMAX_20mA = 0x13,
+	LMU_IMAX_21mA,
+	LMU_IMAX_22mA,
+	LMU_IMAX_23mA = 0x17,
+	LMU_IMAX_24mA,
+	LMU_IMAX_25mA,
+	LMU_IMAX_26mA,
+	LMU_IMAX_27mA = 0x1C,
+	LMU_IMAX_28mA,
+	LMU_IMAX_29mA,
+	LMU_IMAX_30mA,
+};
+
+enum lm363x_regulator_id {
+	LM3631_BOOST,		/* Boost output */
+	LM3631_LDO_CONT,	/* Display panel controller */
+	LM3631_LDO_OREF,	/* Gamma reference */
+	LM3631_LDO_POS,		/* Positive display bias output */
+	LM3631_LDO_NEG,		/* Negative display bias output */
+	LM3632_BOOST,		/* Boost output */
+	LM3632_LDO_POS,		/* Positive display bias output */
+	LM3632_LDO_NEG,		/* Negative display bias output */
+};
+
+/**
+ * struct ti_lmu
+ *
+ * @dev:	Parent device pointer
+ * @regmap:	Used for i2c communcation on accessing registers
+ * @en_gpio:	GPIO for HWEN pin [Optional]
+ * @notifier:	Notifier for reporting hwmon event
+ */
+struct ti_lmu {
+	struct device *dev;
+	struct regmap *regmap;
+	int en_gpio;
+	struct blocking_notifier_head notifier;
+};
+#endif
-- 
1.9.1


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

* [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
                   ` (4 preceding siblings ...)
  2015-11-26  6:57 ` [PATCH v2 5/9] mfd: add TI LMU driver Milo Kim
@ 2015-11-26  6:57 ` Milo Kim
  2016-01-11 10:21   ` Lee Jones
  2015-11-26  6:57 ` [PATCH v2 7/9] backlight: add TI LMU backlight driver Milo Kim
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:57 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

LM3633 and LM3697 are TI LMU MFD device.
Those devices have hardware monitoring feature which detects open or
short circuit case.

Debugfs
-------
  Two files are created.
    open_fault:  check light output channel is open or not.
    short_fault: check light output channel is shorted or not.

  The driver checks the status of backlight output channels.
  LM3633 and LM3697 have same sequence to check channels, so common
  functions are used.
  ABI/testing document is also included.

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

Event notifier
--------------
  After fault monitoring is done, LMU device is reset. So backlight and
  LED device should be reinitialized. It notifies an event as soon as
  the monitoring is done. Then, LM3633 and LM3697 backlight and LED drivers
  handle this event.

Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: linux-leds@vger.kernel.org 
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 .../ABI/testing/debugfs-ti-lmu-fault-monitor       |  32 ++
 drivers/mfd/Kconfig                                |  10 +
 drivers/mfd/Makefile                               |   1 +
 drivers/mfd/ti-lmu-fault-monitor.c                 | 405 +++++++++++++++++++++
 4 files changed, 448 insertions(+)
 create mode 100644 Documentation/ABI/testing/debugfs-ti-lmu-fault-monitor
 create mode 100644 drivers/mfd/ti-lmu-fault-monitor.c

diff --git a/Documentation/ABI/testing/debugfs-ti-lmu-fault-monitor b/Documentation/ABI/testing/debugfs-ti-lmu-fault-monitor
new file mode 100644
index 0000000..7e39e4a
--- /dev/null
+++ b/Documentation/ABI/testing/debugfs-ti-lmu-fault-monitor
@@ -0,0 +1,32 @@
+TI LMU (Lighting Management Unit) Fault Monitoring via the debugfs
+
+LM3633 and LM3697 support hardware fault monitoring which detects
+open or short circuit case.
+
+What:		/sys/kernel/debug/ti-lmu-fault-monitor/open_fault
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	read only
+                Check whether light channel works or open circuit is detected.
+
+		Example:
+		cat /sys/kernel/debug/ti-lmu-fault-monitor/open_fault
+
+		Channel 0 works
+		Channel 1 works
+		Channel 2 is open
+
+What:		/sys/kernel/debug/ti-lmu-fault-monitor/short_fault
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	read only
+                Check whether light channel works or short circuit is detected.
+
+		Example:
+		cat /sys/kernel/debug/ti-lmu-fault-monitor/short_fault
+
+		Channel 0 is shorted
+		Channel 1 works
+		Channel 2 works
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index a6aab27..e08acba 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1061,6 +1061,16 @@ config MFD_TI_LMU
 	  It consists of backlight, LED and regulator driver.
 	  It provides consistent device controls for lighting functions.
 
+config MFD_TI_LMU_FAULT_MONITOR
+	tristate "TI LMU Hardware Fault Monitoring Driver"
+	depends on MFD_TI_LMU && DEBUG_FS
+	help
+	  Say Y here to include support for open and short circuit fault
+	  detection of TI LMU devices.
+
+	  This driver can also be built as a module. If so the module
+	  will be called ti-lmu-fault-monitor.
+
 config MFD_OMAP_USB_HOST
 	bool "TI OMAP USBHS core and TLL driver"
 	depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 10e4bc2..5ddb4e6 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -112,6 +112,7 @@ obj-$(CONFIG_MFD_LP3943)	+= lp3943.o
 obj-$(CONFIG_MFD_LP8788)	+= lp8788.o lp8788-irq.o
 
 obj-$(CONFIG_MFD_TI_LMU)	+= ti-lmu.o
+obj-$(CONFIG_MFD_TI_LMU_FAULT_MONITOR)	+= ti-lmu-fault-monitor.o
 
 da9055-objs			:= da9055-core.o da9055-i2c.o
 obj-$(CONFIG_MFD_DA9055)	+= da9055.o
diff --git a/drivers/mfd/ti-lmu-fault-monitor.c b/drivers/mfd/ti-lmu-fault-monitor.c
new file mode 100644
index 0000000..ba65c93
--- /dev/null
+++ b/drivers/mfd/ti-lmu-fault-monitor.c
@@ -0,0 +1,405 @@
+/*
+ * 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/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/gpio.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/platform_device.h>
+#include <linux/slab.h>
+
+#define LMU_ENABLE_OPEN_MONITORING		BIT(0)
+#define LMU_ENABLE_SHORT_MONITORING		BIT(1)
+#define LMU_DEFAULT_BANK			0
+#define LMU_BANK_MASK(n)			(~BIT(n) & 0x07)
+#define LMU_MAX_CHANNELS			3
+#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_fault_monitor_id {
+	LMU_OPEN_CIRCUIT,
+	LMU_SHORT_CIRCUIT,
+};
+
+/**
+ * struct ti_lmu_reg
+ *
+ * @monitor:		Enable monitoring register
+ * @bank:		Control 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 open circuit status register
+ * @short_fault:	Detect short 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_fault_monitor {
+	struct ti_lmu *lmu;
+	struct device *dev;
+	const struct ti_lmu_reg *regs;
+	struct dentry *dentry_root;
+	struct dentry *dentry_open;
+	struct dentry *dentry_short;
+};
+
+static void ti_lmu_reset_device(struct ti_lmu_fault_monitor *monitor)
+{
+	unsigned int en_gpio = monitor->lmu->en_gpio;
+
+	gpio_set_value(en_gpio, 0);
+	msleep(LMU_DELAY_STARTUP);
+
+	gpio_set_value(en_gpio, 1);
+	msleep(LMU_DELAY_STARTUP);
+}
+
+static int ti_lmu_monitor_enable(struct ti_lmu_fault_monitor *monitor,
+				 enum ti_lmu_fault_monitor_id id)
+{
+	struct regmap *regmap = monitor->lmu->regmap;
+	u8 reg = monitor->regs->monitor;
+
+	switch (id) {
+	case LMU_OPEN_CIRCUIT:
+		return regmap_write(regmap, reg, LMU_ENABLE_OPEN_MONITORING);
+	case LMU_SHORT_CIRCUIT:
+		return regmap_write(regmap, reg, LMU_ENABLE_SHORT_MONITORING);
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+static int ti_lmu_monitor_assign_bank(struct ti_lmu_fault_monitor *monitor,
+				      u8 val)
+{
+	struct regmap *regmap = monitor->lmu->regmap;
+
+	return regmap_write(regmap, monitor->regs->bank, val);
+}
+
+static int ti_lmu_monitor_config_channel(struct ti_lmu_fault_monitor *monitor)
+{
+	struct regmap *regmap = monitor->lmu->regmap;
+	const struct ti_lmu_reg *reg = monitor->regs;
+	int ret;
+
+	/* Set ramp time to the fatest setting */
+	ret = regmap_write(regmap, reg->ramp, LMU_NO_RAMP);
+	if (ret)
+		return ret;
+
+	/* Set max current to 20mA */
+	ret = regmap_write(regmap, reg->imax, LMU_IMAX_20mA);
+	if (ret)
+		return ret;
+
+	/* Enable feedback */
+	ret = regmap_write(regmap, reg->feedback, LMU_ENABLE_FEEDBACK);
+	if (ret)
+		return ret;
+
+	/* Set max brightness */
+	ret = regmap_write(regmap, reg->brightness, LMU_MAX_BRIGHTNESS);
+	if (ret)
+		return ret;
+
+	/* Enable a control bank */
+	ret = regmap_write(regmap, reg->enable, 1);
+	if (ret)
+		return ret;
+
+	/* Wait until device completes fault detection */
+	msleep(LMU_DELAY_FEEDBACK);
+
+	return 0;
+}
+
+static int ti_lmu_monitor_open_fault(struct ti_lmu_fault_monitor *monitor,
+				     struct seq_file *s)
+{
+	int ret, i;
+	struct regmap *regmap = monitor->lmu->regmap;
+	u8 status = 0;
+
+	ret = regmap_read(regmap, monitor->regs->open_fault,
+			  (unsigned int *)&status);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < LMU_MAX_CHANNELS; i++) {
+		if (BIT(i) & status)
+			seq_printf(s, "Channel %d is open\n", i);
+		else
+			seq_printf(s, "Channel %d works\n", i);
+	}
+
+	return 0;
+}
+
+static int ti_lmu_monitor_short_fault(struct ti_lmu_fault_monitor *monitor,
+				      struct seq_file *s, int channel)
+{
+	struct regmap *regmap = monitor->lmu->regmap;
+	u8 status = 0;
+	int ret;
+
+	ret = regmap_read(regmap, monitor->regs->short_fault,
+			  (unsigned int *)&status);
+	if (ret)
+		return ret;
+
+	if (BIT(channel) & status)
+		seq_printf(s, "Channel %d is shorted\n", channel);
+	else
+		seq_printf(s, "Channel %d works\n", channel);
+
+	return 0;
+}
+
+static int ti_lmu_disable_all_banks(struct ti_lmu_fault_monitor *monitor)
+{
+	struct regmap *regmap = monitor->lmu->regmap;
+
+	return regmap_write(regmap, monitor->regs->enable, 0);
+}
+
+static int ti_lmu_notifier_call_chain(struct ti_lmu_fault_monitor *monitor)
+{
+	int ret;
+
+	ti_lmu_reset_device(monitor);
+
+	ret = blocking_notifier_call_chain(&monitor->lmu->notifier,
+					   LMU_EVENT_MONITOR_DONE, NULL);
+	if (ret == NOTIFY_OK || ret == NOTIFY_DONE)
+		return 0;
+	else
+		return -EINVAL;
+}
+
+static int ti_lmu_diagnose_hw(struct seq_file *s,
+			      enum ti_lmu_fault_monitor_id id)
+{
+	struct ti_lmu_fault_monitor *monitor = s->private;
+	u8 bank = LMU_DEFAULT_BANK;
+	int i, ret;
+
+	/* Device should be reset prior to fault detection */
+	ti_lmu_reset_device(monitor);
+
+	ret = ti_lmu_monitor_enable(monitor, id);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < LMU_MAX_CHANNELS; i++) {
+		if (id == LMU_SHORT_CIRCUIT)
+			bank = LMU_BANK_MASK(i);
+
+		ret = ti_lmu_monitor_assign_bank(monitor, bank);
+		if (ret)
+			return ret;
+
+		ret = ti_lmu_monitor_config_channel(monitor);
+		if (ret)
+			return ret;
+
+		/*
+		 * Open fault monitoring requires single operation -
+		 * checking status register.
+		 */
+		if (id == LMU_OPEN_CIRCUIT) {
+			ret = ti_lmu_monitor_open_fault(monitor, s);
+			if (ret)
+				return ret;
+			break;
+		}
+
+		ret = ti_lmu_monitor_short_fault(monitor, s, i);
+		if (ret)
+			return ret;
+
+		ret = ti_lmu_disable_all_banks(monitor);
+		if (ret)
+			return ret;
+	}
+
+	return ti_lmu_notifier_call_chain(monitor);
+}
+
+static int ti_lmu_show_open_fault(struct seq_file *s, void *p)
+{
+	return ti_lmu_diagnose_hw(s, LMU_OPEN_CIRCUIT);
+}
+
+static ssize_t ti_lmu_show_short_fault(struct seq_file *s, void *p)
+{
+	return ti_lmu_diagnose_hw(s, LMU_SHORT_CIRCUIT);
+}
+
+static int dbg_open_fault(struct inode *inode, struct file *file)
+{
+	return single_open(file, ti_lmu_show_open_fault, inode->i_private);
+}
+
+static int dbg_short_fault(struct inode *inode, struct file *file)
+{
+	return single_open(file, ti_lmu_show_short_fault, inode->i_private);
+}
+
+static const struct ti_lmu_reg lm3633_regs = {
+	.monitor	= LM3633_REG_MONITOR_ENABLE,
+	.bank		= LM3633_REG_HVLED_OUTPUT_CFG,
+	.ramp		= LM3633_REG_BL0_RAMP,
+	.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_RAMP,
+	.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 file_operations open_fault_fops = {
+	.open		= dbg_open_fault,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static const struct file_operations short_fault_fops = {
+	.open		= dbg_short_fault,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int ti_lmu_fault_monitor_create_debugfs(const char *root_name,
+				struct ti_lmu_fault_monitor *monitor)
+{
+	monitor->dentry_root = debugfs_create_dir(root_name, NULL);
+	if (!monitor->dentry_root)
+		return -ENODEV;
+
+	monitor->dentry_open = debugfs_create_file("open_fault", S_IRUGO,
+						   monitor->dentry_root,
+						   monitor, &open_fault_fops);
+	if (!monitor->dentry_open)
+		return -ENODEV;
+
+	monitor->dentry_short = debugfs_create_file("short_fault", S_IRUGO,
+						    monitor->dentry_root,
+						    monitor, &short_fault_fops);
+	if (!monitor->dentry_short)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int ti_lmu_fault_monitor_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
+	struct ti_lmu_fault_monitor *monitor;
+	const struct ti_lmu_reg *regs;
+
+	/*
+	 * HW enable pin is used for device reset on monitoring fault status.
+	 * So gpio should be configured.
+	 */
+	if (!gpio_is_valid(lmu->en_gpio))
+		return -EINVAL;
+
+	switch (pdev->id) {
+	case LM3633:
+		regs = &lm3633_regs;
+		break;
+	case LM3697:
+		regs = &lm3697_regs;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	monitor = devm_kzalloc(dev, sizeof(*monitor), GFP_KERNEL);
+	if (!monitor)
+		return -ENOMEM;
+
+	monitor->lmu = lmu;
+	monitor->dev = dev;
+	monitor->regs = regs;
+
+	platform_set_drvdata(pdev, monitor);
+
+	return ti_lmu_fault_monitor_create_debugfs(pdev->name, monitor);
+}
+
+static int ti_lmu_fault_monitor_remove(struct platform_device *pdev)
+{
+	struct ti_lmu_fault_monitor *monitor = platform_get_drvdata(pdev);
+
+	debugfs_remove_recursive(monitor->dentry_root);
+
+	return 0;
+}
+
+static struct platform_driver ti_lmu_fault_monitor_driver = {
+	.probe  = ti_lmu_fault_monitor_probe,
+	.remove = ti_lmu_fault_monitor_remove,
+	.driver = {
+		.name = "ti-lmu-fault-monitor",
+	},
+};
+
+module_platform_driver(ti_lmu_fault_monitor_driver);
+
+MODULE_DESCRIPTION("TI LMU Hardware Fault Monitoring Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-lmu-fault-monitor");
-- 
1.9.1


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

* [PATCH v2 7/9] backlight: add TI LMU backlight driver
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
                   ` (5 preceding siblings ...)
  2015-11-26  6:57 ` [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver Milo Kim
@ 2015-11-26  6:57 ` Milo Kim
  2016-01-11  9:57   ` Lee Jones
  2015-11-26  6:57 ` [PATCH v2 8/9] leds: add LM3633 driver Milo Kim
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:57 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

This is consolidated driver which supports backlight devices below.
  LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.

Structure
---------
  It consists of two parts - core and data.

  Core part supports features below.
    - Backlight subsystem control
    - Channel configuration from DT properties
    - Light dimming effect control: ramp up and down.
    - LMU fault monitor notifier handling
    - PWM brightness control

  Data part describes device specific data.
    - Register value configuration for each LMU device
      : initialization, channel configuration, control mode, enable and
        brightness.
    - PWM action configuration
    - Light dimming effect table
    - Option for LMU fault monitor support

Macros for register data
------------------------
  All LMU devices have 8-bit based registers. LMU_BL_REG() creates 24-bit
  register value in data part. It consists of address, mask and value.
  On the other hand, register value should be parsed when the driver
  reads/writes data from/to I2C registers. Driver uses LMU_BL_GET_ADDR(),
  LMU_BL_GET_MASK() and LMU_BL_GET_VAL() for this purpose.

Data structure
--------------
  ti_lmu_bl:         Backlight output channel data
  ti_lmu_bl_chip:    Backlight device data. One device can have multiple
                     backlight channel data.
  ti_lmu_bl_reg:     Backlight device register data
  ti_lmu_bl_cfg:     Backlight configuration data for each LMU device

Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Mark Brown <broonie@kernel.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: linux-leds@vger.kernel.org 
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/video/backlight/Kconfig                 |   7 +
 drivers/video/backlight/Makefile                |   3 +
 drivers/video/backlight/ti-lmu-backlight-core.c | 649 ++++++++++++++++++++++++
 drivers/video/backlight/ti-lmu-backlight-data.c | 287 +++++++++++
 include/linux/mfd/ti-lmu-backlight.h            | 290 +++++++++++
 5 files changed, 1236 insertions(+)
 create mode 100644 drivers/video/backlight/ti-lmu-backlight-core.c
 create mode 100644 drivers/video/backlight/ti-lmu-backlight-data.c
 create mode 100644 include/linux/mfd/ti-lmu-backlight.h

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5ffa4b4..451d043 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -427,6 +427,13 @@ config BACKLIGHT_SKY81452
 	  To compile this driver as a module, choose M here: the module will
 	  be called sky81452-backlight
 
+config BACKLIGHT_TI_LMU
+	tristate "Backlight driver for TI LMU"
+	depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU
+	help
+	  Say Y to enable the backlight driver for TI LMU devices.
+	  This supports LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
+
 config BACKLIGHT_TPS65217
 	tristate "TPS65217 Backlight"
 	depends on BACKLIGHT_CLASS_DEVICE && MFD_TPS65217
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 16ec534..0f74ce7 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -52,6 +52,9 @@ obj-$(CONFIG_BACKLIGHT_PM8941_WLED)	+= pm8941-wled.o
 obj-$(CONFIG_BACKLIGHT_PWM)		+= pwm_bl.o
 obj-$(CONFIG_BACKLIGHT_SAHARA)		+= kb3886_bl.o
 obj-$(CONFIG_BACKLIGHT_SKY81452)	+= sky81452-backlight.o
+ti-lmu-backlight-objs			:= ti-lmu-backlight-core.o \
+					   ti-lmu-backlight-data.o
+obj-$(CONFIG_BACKLIGHT_TI_LMU)		+= ti-lmu-backlight.o
 obj-$(CONFIG_BACKLIGHT_TOSA)		+= tosa_bl.o
 obj-$(CONFIG_BACKLIGHT_TPS65217)	+= tps65217_bl.o
 obj-$(CONFIG_BACKLIGHT_WM831X)		+= wm831x_bl.o
diff --git a/drivers/video/backlight/ti-lmu-backlight-core.c b/drivers/video/backlight/ti-lmu-backlight-core.c
new file mode 100644
index 0000000..838e2c2
--- /dev/null
+++ b/drivers/video/backlight/ti-lmu-backlight-core.c
@@ -0,0 +1,649 @@
+/*
+ * TI LMU (Lighting Management Unit) 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/backlight.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-backlight.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+
+#define NUM_DUAL_CHANNEL			2
+#define LMU_BACKLIGHT_DUAL_CHANNEL_USED		(BIT(0) | BIT(1))
+#define LMU_BACKLIGHT_11BIT_LSB_MASK		(BIT(0) | BIT(1) | BIT(2))
+#define LMU_BACKLIGHT_11BIT_MSB_SHIFT		3
+#define DEFAULT_PWM_NAME			"lmu-backlight"
+
+static int ti_lmu_backlight_enable(struct ti_lmu_bl *lmu_bl, int enable)
+{
+	struct ti_lmu_bl_chip *chip = lmu_bl->chip;
+	struct regmap *regmap = chip->lmu->regmap;
+	unsigned long enable_time = chip->cfg->reginfo->enable_usec;
+	u8 *reg = chip->cfg->reginfo->enable;
+	u8 mask = BIT(lmu_bl->bank_id);
+
+	if (!reg)
+		return -EINVAL;
+
+	if (enable)
+		return regmap_update_bits(regmap, *reg, mask, mask);
+	else
+		return regmap_update_bits(regmap, *reg, mask, 0);
+
+	if (enable_time > 0)
+		usleep_range(enable_time, enable_time + 100);
+}
+
+static void ti_lmu_backlight_pwm_ctrl(struct ti_lmu_bl *lmu_bl, int brightness,
+				      int max_brightness)
+{
+	struct pwm_device *pwm;
+	unsigned int duty, period;
+
+	if (!lmu_bl->pwm) {
+		pwm = devm_pwm_get(lmu_bl->chip->dev, DEFAULT_PWM_NAME);
+		if (IS_ERR(pwm)) {
+			dev_err(lmu_bl->chip->dev,
+				"Can not get PWM device, err: %ld\n",
+				PTR_ERR(pwm));
+			return;
+		}
+
+		lmu_bl->pwm = pwm;
+	}
+
+	period = lmu_bl->pwm_period;
+	duty = brightness * period / max_brightness;
+
+	pwm_config(lmu_bl->pwm, duty, period);
+	if (duty)
+		pwm_enable(lmu_bl->pwm);
+	else
+		pwm_disable(lmu_bl->pwm);
+}
+
+static int ti_lmu_backlight_update_brightness_register(struct ti_lmu_bl *lmu_bl,
+						       int brightness)
+{
+	const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg;
+	const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
+	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
+	u8 reg, val;
+	int ret;
+
+	if (lmu_bl->mode == BL_PWM_BASED) {
+		switch (cfg->pwm_action) {
+		case UPDATE_PWM_ONLY:
+			/* No register update is required */
+			return 0;
+		case UPDATE_MAX_BRT:
+			/*
+			 * 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 = MAX_BRIGHTNESS_11BIT;
+			else
+				brightness = 0;
+			break;
+		default:
+			break;
+		}
+	}
+
+	/*
+	 * Brightness register update
+	 *
+	 * 11 bit dimming: update LSB bits and write MSB byte.
+	 *		   MSB brightness should be shifted.
+	 *  8 bit dimming: write MSB byte.
+	 */
+
+	if (!reginfo->brightness_msb)
+		return -EINVAL;
+
+	if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
+		if (!reginfo->brightness_lsb)
+			return -EINVAL;
+
+		reg = reginfo->brightness_lsb[lmu_bl->bank_id];
+		ret = regmap_update_bits(regmap, reg,
+					 LMU_BACKLIGHT_11BIT_LSB_MASK,
+					 brightness);
+		if (ret)
+			return ret;
+
+		val = (brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT) & 0xFF;
+	} else {
+		val = brightness & 0xFF;
+	}
+
+	reg = reginfo->brightness_msb[lmu_bl->bank_id];
+	return regmap_write(regmap, reg, val);
+}
+
+static int ti_lmu_backlight_update_status(struct backlight_device *bl_dev)
+{
+	struct ti_lmu_bl *lmu_bl = bl_get_data(bl_dev);
+	int brightness = bl_dev->props.brightness;
+	int ret;
+
+	if (bl_dev->props.state & BL_CORE_SUSPENDED)
+		brightness = 0;
+
+	if (brightness > 0)
+		ret = ti_lmu_backlight_enable(lmu_bl, 1);
+	else
+		ret = ti_lmu_backlight_enable(lmu_bl, 0);
+
+	if (ret)
+		return ret;
+
+	if (lmu_bl->mode == BL_PWM_BASED)
+		ti_lmu_backlight_pwm_ctrl(lmu_bl, brightness,
+					  bl_dev->props.max_brightness);
+
+	return ti_lmu_backlight_update_brightness_register(lmu_bl, brightness);
+}
+
+static const struct backlight_ops lmu_backlight_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.update_status = ti_lmu_backlight_update_status,
+};
+
+static int ti_lmu_backlight_of_get_ctrl_bank(struct device_node *np,
+					     struct ti_lmu_bl *lmu_bl)
+{
+	const char *name;
+	u32 *sources;
+	int num_channels = lmu_bl->chip->cfg->num_channels;
+	int ret, num_sources;
+
+	sources = devm_kzalloc(lmu_bl->chip->dev, num_channels, GFP_KERNEL);
+	if (!sources)
+		return -ENOMEM;
+
+	if (!of_property_read_string(np, "label", &name))
+		lmu_bl->name = name;
+	else
+		lmu_bl->name = np->name;
+
+	ret = of_property_count_u32_elems(np, "led-sources");
+	if (ret < 0 || ret > num_channels)
+		return -EINVAL;
+
+	num_sources = ret;
+	ret = of_property_read_u32_array(np, "led-sources", sources,
+					 num_sources);
+	if (ret)
+		return ret;
+
+	lmu_bl->led_sources = 0;
+	while (num_sources--)
+		set_bit(sources[num_sources], &lmu_bl->led_sources);
+
+	return 0;
+}
+
+static void ti_lmu_backlight_of_get_light_properties(struct device_node *np,
+						     struct ti_lmu_bl *lmu_bl)
+{
+	of_property_read_u32(np, "default-brightness-level",
+			     &lmu_bl->default_brightness);
+
+	of_property_read_u32(np, "ramp-up-msec",  &lmu_bl->ramp_up_msec);
+	of_property_read_u32(np, "ramp-down-msec", &lmu_bl->ramp_down_msec);
+}
+
+static void ti_lmu_backlight_of_get_brightness_mode(struct device_node *np,
+						    struct ti_lmu_bl *lmu_bl)
+{
+	of_property_read_u32(np, "pwm-period", &lmu_bl->pwm_period);
+
+	if (lmu_bl->pwm_period > 0)
+		lmu_bl->mode = BL_PWM_BASED;
+	else
+		lmu_bl->mode = BL_REGISTER_BASED;
+}
+
+static int ti_lmu_backlight_of_create(struct ti_lmu_bl_chip *chip,
+				      struct device_node *np)
+{
+	struct device_node *child;
+	struct ti_lmu_bl *lmu_bl, *each;
+	int ret, num_backlights;
+	int i = 0;
+
+	num_backlights = of_get_child_count(np);
+	if (num_backlights == 0) {
+		dev_err(chip->dev, "No backlight strings\n");
+		return -ENODEV;
+	}
+
+	/* One chip can have mulitple backlight strings */
+	lmu_bl = devm_kzalloc(chip->dev, sizeof(*lmu_bl) * num_backlights,
+			      GFP_KERNEL);
+	if (!lmu_bl)
+		return -ENOMEM;
+
+	/* Child is mapped to LMU backlight control bank */
+	for_each_child_of_node(np, child) {
+		each = lmu_bl + i;
+		each->bank_id = i;
+		each->chip = chip;
+
+		ret = ti_lmu_backlight_of_get_ctrl_bank(child, each);
+		if (ret) {
+			of_node_put(np);
+			return ret;
+		}
+
+		ti_lmu_backlight_of_get_light_properties(child, each);
+		ti_lmu_backlight_of_get_brightness_mode(child, each);
+
+		i++;
+	}
+
+	chip->lmu_bl = lmu_bl;
+	chip->num_backlights = num_backlights;
+
+	return 0;
+}
+
+static int ti_lmu_backlight_create_channel(struct ti_lmu_bl *lmu_bl)
+{
+	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
+	u32 *reg = lmu_bl->chip->cfg->reginfo->channel;
+	int num_channels = lmu_bl->chip->cfg->num_channels;
+	int i, ret;
+	u8 shift;
+
+	/*
+	 * How to create backlight output channels:
+	 *   Check 'led_sources' bit and update registers.
+	 *
+	 *   1) Dual channel configuration
+	 *     The 1st register data is used for single channel.
+	 *     The 2nd register data is used for dual channel.
+	 *
+	 *   2) Multiple channel configuration
+	 *     Each register data is mapped to bank ID.
+	 *     Bit shift operation is defined in channel registers.
+	 *
+	 * Channel register data consists of address, mask, value.
+	 * Driver can get each data by using LMU_BL_GET_ADDR(),
+	 * LMU_BL_GET_MASK(), LMU_BL_GET_VAL().
+	 */
+
+	if (num_channels == NUM_DUAL_CHANNEL) {
+		if (lmu_bl->led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
+			++reg;
+
+		return regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
+					  LMU_BL_GET_MASK(*reg),
+					  LMU_BL_GET_VAL(*reg));
+	}
+
+	for (i = 0; i < num_channels; i++) {
+		if (!reg)
+			break;
+
+		/*
+		 * Note that the result of LMU_BL_GET_VAL() is shift bit.
+		 * The bank_id should be shifted for the channel configuration.
+		 */
+		if (test_bit(i, &lmu_bl->led_sources)) {
+			shift = LMU_BL_GET_VAL(*reg);
+			ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
+						 LMU_BL_GET_MASK(*reg),
+						 lmu_bl->bank_id << shift);
+			if (ret)
+				return ret;
+		}
+
+		reg++;
+	}
+
+	return 0;
+}
+
+static int ti_lmu_backlight_update_ctrl_mode(struct ti_lmu_bl *lmu_bl)
+{
+	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
+	u32 *reg = lmu_bl->chip->cfg->reginfo->mode + lmu_bl->bank_id;
+	u8 val;
+
+	if (!reg)
+		return 0;
+
+	/*
+	 * Update PWM configuration register.
+	 * If the mode is register based, then clear the bit.
+	 */
+	if (lmu_bl->mode == BL_PWM_BASED)
+		val = LMU_BL_GET_VAL(*reg);
+	else
+		val = 0;
+
+	return regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
+				  LMU_BL_GET_MASK(*reg), val);
+}
+
+static int ti_lmu_backlight_convert_ramp_to_index(struct ti_lmu_bl *lmu_bl,
+						  enum ti_lmu_bl_ramp_mode mode)
+{
+	const int *ramp_table = lmu_bl->chip->cfg->ramp_table;
+	const int size = lmu_bl->chip->cfg->size_ramp;
+	unsigned int msec;
+	int i;
+
+	if (!ramp_table)
+		return -EINVAL;
+
+	switch (mode) {
+	case BL_RAMP_UP:
+		msec = lmu_bl->ramp_up_msec;
+		break;
+	case BL_RAMP_DOWN:
+		msec = lmu_bl->ramp_down_msec;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	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 an approximate index 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 -EINVAL;
+}
+
+static int ti_lmu_backlight_set_ramp(struct ti_lmu_bl *lmu_bl)
+{
+	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
+	const struct ti_lmu_bl_reg *reginfo = lmu_bl->chip->cfg->reginfo;
+	int offset = reginfo->ramp_reg_offset;
+	int i, ret, index;
+	u32 reg;
+
+	for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
+		index = ti_lmu_backlight_convert_ramp_to_index(lmu_bl, i);
+		if (index > 0) {
+			if (!reginfo->ramp)
+				break;
+
+			if (lmu_bl->bank_id == 0)
+				reg = reginfo->ramp[i];
+			else
+				reg = reginfo->ramp[i] + offset;
+
+			/*
+			 * Note that the result of LMU_BL_GET_VAL() is
+			 * shift bit. So updated bit is shifted index value.
+			 */
+			ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(reg),
+						 LMU_BL_GET_MASK(reg),
+						 index << LMU_BL_GET_VAL(reg));
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int ti_lmu_backlight_configure(struct ti_lmu_bl *lmu_bl)
+{
+	int ret;
+
+	ret = ti_lmu_backlight_create_channel(lmu_bl);
+	if (ret)
+		return ret;
+
+	ret = ti_lmu_backlight_update_ctrl_mode(lmu_bl);
+	if (ret)
+		return ret;
+
+	return ti_lmu_backlight_set_ramp(lmu_bl);
+}
+
+static int ti_lmu_backlight_init(struct ti_lmu_bl_chip *chip)
+{
+	struct regmap *regmap = chip->lmu->regmap;
+	u32 *reg = chip->cfg->reginfo->init;
+	int num_init = chip->cfg->reginfo->num_init;
+	int i, ret;
+
+	/*
+	 * 'init' register data consists of address, mask, value.
+	 * Driver can get each data by using LMU_BL_GET_ADDR(),
+	 * LMU_BL_GET_MASK(), LMU_BL_GET_VAL().
+	 */
+
+	for (i = 0; i < num_init; i++) {
+		if (!reg)
+			break;
+
+		ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
+					 LMU_BL_GET_MASK(*reg),
+					 LMU_BL_GET_VAL(*reg));
+		if (ret)
+			return ret;
+
+		reg++;
+	}
+
+	return 0;
+}
+
+static int ti_lmu_backlight_reload(struct ti_lmu_bl_chip *chip)
+{
+	struct ti_lmu_bl *each;
+	int i, ret;
+
+	ret = ti_lmu_backlight_init(chip);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < chip->num_backlights; i++) {
+		each = chip->lmu_bl + i;
+		ret = ti_lmu_backlight_configure(each);
+		if (ret)
+			return ret;
+
+		backlight_update_status(each->bl_dev);
+	}
+
+	return 0;
+}
+
+static int ti_lmu_backlight_add_device(struct device *dev,
+				       struct ti_lmu_bl *lmu_bl)
+{
+	struct backlight_device *bl_dev;
+	struct backlight_properties props;
+
+	memset(&props, 0, sizeof(struct backlight_properties));
+	props.type = BACKLIGHT_PLATFORM;
+	props.brightness = lmu_bl->default_brightness;
+	props.max_brightness = lmu_bl->chip->cfg->max_brightness;
+
+	bl_dev = devm_backlight_device_register(dev, lmu_bl->name,
+						lmu_bl->chip->dev, lmu_bl,
+						&lmu_backlight_ops, &props);
+	if (IS_ERR(bl_dev))
+		return PTR_ERR(bl_dev);
+
+	lmu_bl->bl_dev = bl_dev;
+
+	return 0;
+}
+
+static struct ti_lmu_bl_chip *
+ti_lmu_backlight_register(struct device *dev, struct ti_lmu *lmu,
+			  const struct ti_lmu_bl_cfg *cfg)
+{
+	struct ti_lmu_bl_chip *chip;
+	struct ti_lmu_bl *each;
+	int i, ret;
+
+	if (!cfg) {
+		dev_err(dev, "Operation is not configured\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return ERR_PTR(-ENOMEM);
+
+	chip->dev = dev;
+	chip->lmu = lmu;
+	chip->cfg = cfg;
+
+	ret = ti_lmu_backlight_of_create(chip, dev->of_node);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = ti_lmu_backlight_init(chip);
+	if (ret) {
+		dev_err(dev, "Backlight init err: %d\n", ret);
+		return ERR_PTR(ret);
+	}
+
+	for (i = 0; i < chip->num_backlights; i++) {
+		each = chip->lmu_bl + i;
+
+		ret = ti_lmu_backlight_configure(each);
+		if (ret) {
+			dev_err(dev, "Backlight config err: %d\n", ret);
+			return ERR_PTR(ret);
+		}
+
+		ret = ti_lmu_backlight_add_device(dev, each);
+		if (ret) {
+			dev_err(dev, "Backlight device err: %d\n", ret);
+			return ERR_PTR(ret);
+		}
+
+		backlight_update_status(each->bl_dev);
+	}
+
+	return chip;
+}
+
+static void ti_lmu_backlight_unregister(struct ti_lmu_bl_chip *chip)
+{
+	struct ti_lmu_bl *each;
+	int i;
+
+	/* Turn off the brightness */
+	for (i = 0; i < chip->num_backlights; i++) {
+		each = chip->lmu_bl + i;
+		each->bl_dev->props.brightness = 0;
+		backlight_update_status(each->bl_dev);
+	}
+}
+
+static int ti_lmu_backlight_monitor_notifier(struct notifier_block *nb,
+					     unsigned long action, void *unused)
+{
+	struct ti_lmu_bl_chip *chip = container_of(nb, struct ti_lmu_bl_chip,
+						   nb);
+
+	if (action == LMU_EVENT_MONITOR_DONE) {
+		if (ti_lmu_backlight_reload(chip))
+			return NOTIFY_STOP;
+	}
+
+	return NOTIFY_OK;
+}
+
+static int ti_lmu_backlight_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
+	struct ti_lmu_bl_chip *chip;
+	int ret;
+
+	chip = ti_lmu_backlight_register(dev, lmu, &lmu_bl_cfg[pdev->id]);
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	/*
+	 * Notifier callback is required because backlight device needs
+	 * reconfiguration after fault detection procedure is done by
+	 * ti-lmu-fault-monitor driver.
+	 */
+	if (chip->cfg->fault_monitor_used) {
+		chip->nb.notifier_call = ti_lmu_backlight_monitor_notifier;
+		ret = blocking_notifier_chain_register(&chip->lmu->notifier,
+						       &chip->nb);
+		if (ret)
+			return ret;
+	}
+
+	platform_set_drvdata(pdev, chip);
+
+	return 0;
+}
+
+static int ti_lmu_backlight_remove(struct platform_device *pdev)
+{
+	struct ti_lmu_bl_chip *chip = platform_get_drvdata(pdev);
+
+	if (chip->cfg->fault_monitor_used)
+		blocking_notifier_chain_unregister(&chip->lmu->notifier,
+						   &chip->nb);
+
+	ti_lmu_backlight_unregister(chip);
+
+	return 0;
+}
+
+static struct platform_driver ti_lmu_backlight_driver = {
+	.probe  = ti_lmu_backlight_probe,
+	.remove = ti_lmu_backlight_remove,
+	.driver = {
+		.name = "ti-lmu-backlight",
+	},
+};
+
+module_platform_driver(ti_lmu_backlight_driver)
+
+MODULE_DESCRIPTION("TI LMU Backlight Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-lmu-backlight");
diff --git a/drivers/video/backlight/ti-lmu-backlight-data.c b/drivers/video/backlight/ti-lmu-backlight-data.c
new file mode 100644
index 0000000..0a486b2
--- /dev/null
+++ b/drivers/video/backlight/ti-lmu-backlight-data.c
@@ -0,0 +1,287 @@
+/*
+ * TI LMU (Lighting Management Unit) Backlight Device Data
+ *
+ * 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-backlight.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+
+/* LM3532 */
+static u32 lm3532_init_regs[] = {
+	LM3532_INIT_ZONE_0,
+	LM3532_INIT_ZONE_1,
+	LM3532_INIT_ZONE_2,
+};
+
+static u32 lm3532_channel_regs[] = {
+	LM3532_CHANNEL_1,
+	LM3532_CHANNEL_2,
+	LM3532_CHANNEL_3,
+};
+
+static u32 lm3532_mode_regs[] = {
+	LM3532_MODE_PWM_A,
+	LM3532_MODE_PWM_B,
+	LM3532_MODE_PWM_C,
+};
+
+static u32 lm3532_ramp_regs[] = {
+	LM3532_RAMPUP,
+	LM3532_RAMPDN,
+};
+
+static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
+
+static u8 lm3532_brightness_regs[] = {
+	LM3532_REG_BRT_A,
+	LM3532_REG_BRT_B,
+	LM3532_REG_BRT_C,
+};
+
+static const struct ti_lmu_bl_reg lm3532_reg_info = {
+	.init		= lm3532_init_regs,
+	.num_init	= ARRAY_SIZE(lm3532_init_regs),
+	.channel	= lm3532_channel_regs,
+	.mode		= lm3532_mode_regs,
+	.ramp		= lm3532_ramp_regs,
+	.enable		= &lm3532_enable_reg,
+	.brightness_msb	= lm3532_brightness_regs,
+};
+
+/* LM3631 */
+static u32 lm3631_init_regs[] = {
+	LM3631_INIT_BRT_MODE,
+	LM3631_INIT_DIMMING_MODE,
+};
+
+static u32 lm3631_channel_regs[]  = {
+	LM3631_SINGLE_CHANNEL,
+	LM3631_DUAL_CHANNEL,
+};
+
+static u32 lm3631_ramp_reg = LM3631_RAMP;
+static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
+static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
+static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3631_reg_info = {
+	.init		= lm3631_init_regs,
+	.num_init	= ARRAY_SIZE(lm3631_init_regs),
+	.channel	= lm3631_channel_regs,
+	.ramp		= &lm3631_ramp_reg,
+	.enable		= &lm3631_enable_reg,
+	.brightness_msb	= &lm3631_brightness_msb_reg,
+	.brightness_lsb	= &lm3631_brightness_lsb_reg,
+};
+
+/* LM3632 */
+static u32 lm3632_init_regs[] = {
+	LM3632_INIT_OVP_25V,
+	LM3632_INIT_SWFREQ_1MHZ,
+};
+
+static u32 lm3632_channel_regs[]  = {
+	LM3632_SINGLE_CHANNEL,
+	LM3632_DUAL_CHANNEL,
+};
+
+static u32 lm3632_mode_reg = LM3632_MODE_PWM;
+static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
+static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
+static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3632_reg_info = {
+	.init		= lm3632_init_regs,
+	.num_init	= ARRAY_SIZE(lm3632_init_regs),
+	.channel	= lm3632_channel_regs,
+	.mode		= &lm3632_mode_reg,
+	.enable		= &lm3632_enable_reg,
+	.brightness_msb	= &lm3632_brightness_msb_reg,
+	.brightness_lsb	= &lm3632_brightness_lsb_reg,
+};
+
+/* LM3633 */
+static u32 lm3633_init_regs[] = {
+	LM3633_INIT_OVP_40V,
+	LM3633_INIT_RAMP_SELECT,
+};
+
+static u32 lm3633_channel_regs[]  = {
+	LM3633_CHANNEL_HVLED1,
+	LM3633_CHANNEL_HVLED2,
+	LM3633_CHANNEL_HVLED3,
+};
+
+static u32 lm3633_mode_regs[] = {
+	LM3633_MODE_PWM_A,
+	LM3633_MODE_PWM_B,
+};
+
+static u32 lm3633_ramp_regs[] = {
+	LM3633_RAMPUP,
+	LM3633_RAMPDN,
+};
+
+static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
+
+static u8 lm3633_brightness_msb_regs[] = {
+	LM3633_REG_BRT_HVLED_A_MSB,
+	LM3633_REG_BRT_HVLED_B_MSB,
+};
+
+static u8 lm3633_brightness_lsb_regs[] = {
+	LM3633_REG_BRT_HVLED_A_LSB,
+	LM3633_REG_BRT_HVLED_B_LSB,
+};
+
+static const struct ti_lmu_bl_reg lm3633_reg_info = {
+	.init		 = lm3633_init_regs,
+	.num_init	 = ARRAY_SIZE(lm3633_init_regs),
+	.channel	 = lm3633_channel_regs,
+	.mode		 = lm3633_mode_regs,
+	.ramp		 = lm3633_ramp_regs,
+	.ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
+	.enable		 = &lm3633_enable_reg,
+	.brightness_msb	 = lm3633_brightness_msb_regs,
+	.brightness_lsb	 = lm3633_brightness_lsb_regs,
+};
+
+/* LM3695 */
+static u32 lm3695_init_regs[] = {
+	LM3695_INIT_BRT_MODE,
+};
+
+static u32 lm3695_channel_regs[]  = {
+	LM3695_SINGLE_CHANNEL,
+	LM3695_DUAL_CHANNEL,
+};
+
+static u8 lm3695_enable_reg = LM3695_REG_GP;
+static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
+static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3695_reg_info = {
+	.init		= lm3695_init_regs,
+	.num_init	= ARRAY_SIZE(lm3695_init_regs),
+	.channel	= lm3695_channel_regs,
+	.enable		= &lm3695_enable_reg,
+	.enable_usec	= 600,
+	.brightness_msb	= &lm3695_brightness_msb_reg,
+	.brightness_lsb	= &lm3695_brightness_lsb_reg,
+};
+
+/* LM3697 */
+static u32 lm3697_init_regs[] = {
+	LM3697_INIT_RAMP_SELECT,
+};
+
+static u32 lm3697_channel_regs[]  = {
+	LM3697_CHANNEL_1,
+	LM3697_CHANNEL_2,
+	LM3697_CHANNEL_3,
+};
+
+static u32 lm3697_mode_regs[] = {
+	LM3697_MODE_PWM_A,
+	LM3697_MODE_PWM_B,
+};
+
+static u32 lm3697_ramp_regs[] = {
+	LM3697_RAMPUP,
+	LM3697_RAMPDN,
+};
+
+static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
+
+static u8 lm3697_brightness_msb_regs[] = {
+	LM3697_REG_BRT_A_MSB,
+	LM3697_REG_BRT_B_MSB,
+};
+
+static u8 lm3697_brightness_lsb_regs[] = {
+	LM3697_REG_BRT_A_LSB,
+	LM3697_REG_BRT_B_LSB,
+};
+
+static const struct ti_lmu_bl_reg lm3697_reg_info = {
+	.init		 = lm3697_init_regs,
+	.num_init	 = ARRAY_SIZE(lm3697_init_regs),
+	.channel	 = lm3697_channel_regs,
+	.mode		 = lm3697_mode_regs,
+	.ramp		 = lm3697_ramp_regs,
+	.ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
+	.enable		 = &lm3697_enable_reg,
+	.brightness_msb	 = lm3697_brightness_msb_regs,
+	.brightness_lsb	 = lm3697_brightness_lsb_regs,
+};
+
+static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
+
+static int lm3631_ramp_table[] = {
+	   0,   1,   2,    5,   10,   20,   50,  100,
+	 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
+};
+
+static int common_ramp_table[] = {
+	   2, 250, 500, 1000, 2000, 4000, 8000, 16000,
+};
+
+struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
+	{
+		.reginfo		= &lm3532_reg_info,
+		.num_channels		= LM3532_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_8BIT,
+		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
+		.ramp_table		= lm3532_ramp_table,
+		.size_ramp		= ARRAY_SIZE(lm3532_ramp_table),
+	},
+	{
+		.reginfo		= &lm3631_reg_info,
+		.num_channels		= LM3631_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_ONLY,
+		.ramp_table		= lm3631_ramp_table,
+		.size_ramp		= ARRAY_SIZE(lm3631_ramp_table),
+	},
+	{
+		.reginfo		= &lm3632_reg_info,
+		.num_channels		= LM3632_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_ONLY,
+	},
+	{
+		.reginfo		= &lm3633_reg_info,
+		.num_channels		= LM3633_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_MAX_BRT,
+		.ramp_table		= common_ramp_table,
+		.size_ramp		= ARRAY_SIZE(common_ramp_table),
+		.fault_monitor_used	= true,
+	},
+	{
+		.reginfo		= &lm3695_reg_info,
+		.num_channels		= LM3695_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
+	},
+	{
+		.reginfo		= &lm3697_reg_info,
+		.num_channels		= LM3697_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
+		.ramp_table		= common_ramp_table,
+		.size_ramp		= ARRAY_SIZE(common_ramp_table),
+		.fault_monitor_used	= true,
+	},
+};
+EXPORT_SYMBOL_GPL(lmu_bl_cfg);
diff --git a/include/linux/mfd/ti-lmu-backlight.h b/include/linux/mfd/ti-lmu-backlight.h
new file mode 100644
index 0000000..43e5300
--- /dev/null
+++ b/include/linux/mfd/ti-lmu-backlight.h
@@ -0,0 +1,290 @@
+/*
+ * TI LMU (Lighting Management Unit) Backlight Common 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.
+ */
+
+#ifndef __TI_LMU_BACKLIGHT_H__
+#define __TI_LMU_BACKLIGHT_H__
+
+#include <linux/backlight.h>
+#include <linux/device.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/notifier.h>
+
+/**
+ * LMU backlight register data
+ *	value[23:16] | mask[15:8] | address[7:0]
+ */
+#define LMU_BL_REG(addr, mask, value)				\
+	((value << 16) | (mask << 8) | addr)
+
+#define LMU_BL_GET_ADDR(x)	(x & 0xFF)
+#define LMU_BL_GET_MASK(x)	((x >> 8) & 0xFF)
+#define LMU_BL_GET_VAL(x)	((x >> 16) & 0xFF)
+
+#define LM3532_INIT_ZONE_0						\
+	LMU_BL_REG(LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0)
+#define LM3532_INIT_ZONE_1						\
+	LMU_BL_REG(LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1)
+#define LM3532_INIT_ZONE_2						\
+	LMU_BL_REG(LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2)
+#define LM3532_CHANNEL_1						\
+	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,	\
+	LM3532_ILED1_CFG_SHIFT)
+#define LM3532_CHANNEL_2						\
+	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,	\
+	LM3532_ILED2_CFG_SHIFT)
+#define LM3532_CHANNEL_3						\
+	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,	\
+	LM3532_ILED3_CFG_SHIFT)
+#define LM3532_MODE_PWM_A						\
+	LMU_BL_REG(LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0)
+#define LM3532_MODE_PWM_B						\
+	LMU_BL_REG(LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1)
+#define LM3532_MODE_PWM_C						\
+	LMU_BL_REG(LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2)
+#define LM3532_RAMPUP							\
+	LMU_BL_REG(LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT)
+#define LM3532_RAMPDN							\
+	LMU_BL_REG(LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT)
+
+#define LM3631_INIT_BRT_MODE						\
+	LMU_BL_REG(LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE)
+#define LM3631_INIT_DIMMING_MODE					\
+	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP)
+#define LM3631_SINGLE_CHANNEL						\
+	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK,		\
+	LM3631_BL_SINGLE_CHANNEL)
+#define LM3631_DUAL_CHANNEL						\
+	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK,		\
+	LM3631_BL_DUAL_CHANNEL)
+#define LM3631_RAMP							\
+	LMU_BL_REG(LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT)
+
+#define LM3632_INIT_OVP_25V						\
+	LMU_BL_REG(LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V)
+#define LM3632_INIT_SWFREQ_1MHZ						\
+	LMU_BL_REG(LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ)
+#define LM3632_SINGLE_CHANNEL						\
+	LMU_BL_REG(LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK,		\
+	LM3632_BL_SINGLE_CHANNEL)
+#define LM3632_DUAL_CHANNEL						\
+	LMU_BL_REG(LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK,		\
+	LM3632_BL_DUAL_CHANNEL)
+#define LM3632_MODE_PWM							\
+	LMU_BL_REG(LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE)
+
+#define LM3633_INIT_OVP_40V						\
+	LMU_BL_REG(LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V)
+#define LM3633_INIT_RAMP_SELECT						\
+	LMU_BL_REG(LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK,	\
+	LM3633_BL_RAMP_EACH)
+#define LM3633_CHANNEL_HVLED1						\
+	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,	\
+	LM3633_HVLED1_CFG_SHIFT)
+#define LM3633_CHANNEL_HVLED2						\
+	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,	\
+	LM3633_HVLED2_CFG_SHIFT)
+#define LM3633_CHANNEL_HVLED3						\
+	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,	\
+	LM3633_HVLED3_CFG_SHIFT)
+#define LM3633_MODE_PWM_A						\
+	LMU_BL_REG(LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK)
+#define LM3633_MODE_PWM_B						\
+	LMU_BL_REG(LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK)
+#define LM3633_RAMPUP							\
+	LMU_BL_REG(LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK,		\
+	LM3633_BL_RAMPUP_SHIFT)
+#define LM3633_RAMPDN							\
+	LMU_BL_REG(LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK,		\
+	LM3633_BL_RAMPDN_SHIFT)
+
+#define LM3695_INIT_BRT_MODE						\
+	LMU_BL_REG(LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK)
+#define LM3695_SINGLE_CHANNEL						\
+	LMU_BL_REG(LM3695_REG_GP, LM3695_BL_CHANNEL_MASK,		\
+	LM3695_BL_SINGLE_CHANNEL)
+#define LM3695_DUAL_CHANNEL						\
+	LMU_BL_REG(LM3695_REG_GP, LM3695_BL_CHANNEL_MASK,		\
+	LM3695_BL_DUAL_CHANNEL)
+
+#define LM3697_INIT_RAMP_SELECT						\
+	LMU_BL_REG(LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH)
+#define LM3697_CHANNEL_1						\
+	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,	\
+	LM3697_HVLED1_CFG_SHIFT)
+#define LM3697_CHANNEL_2						\
+	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,	\
+	LM3697_HVLED2_CFG_SHIFT)
+#define LM3697_CHANNEL_3						\
+	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,	\
+	LM3697_HVLED3_CFG_SHIFT)
+#define LM3697_MODE_PWM_A						\
+	LMU_BL_REG(LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK)
+#define LM3697_MODE_PWM_B						\
+	LMU_BL_REG(LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK)
+#define LM3697_RAMPUP							\
+	LMU_BL_REG(LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT)
+#define LM3697_RAMPDN							\
+	LMU_BL_REG(LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT)
+
+#define LM3532_MAX_CHANNELS		3
+#define LM3631_MAX_CHANNELS		2
+#define LM3632_MAX_CHANNELS		2
+#define LM3633_MAX_CHANNELS		3
+#define LM3695_MAX_CHANNELS		2
+#define LM3697_MAX_CHANNELS		3
+
+#define MAX_BRIGHTNESS_8BIT		255
+#define MAX_BRIGHTNESS_11BIT		2047
+
+enum ti_lmu_bl_ctrl_mode {
+	BL_REGISTER_BASED,
+	BL_PWM_BASED,
+};
+
+enum ti_lmu_bl_pwm_action {
+	/* Update PWM duty, no brightness register update is required */
+	UPDATE_PWM_ONLY,
+	/* Update not only duty but also brightness register */
+	UPDATE_PWM_AND_BRT_REGISTER,
+	/* Update max value in brightness registers */
+	UPDATE_MAX_BRT,
+};
+
+enum ti_lmu_bl_ramp_mode {
+	BL_RAMP_UP,
+	BL_RAMP_DOWN,
+};
+
+struct ti_lmu_bl;
+struct ti_lmu_bl_chip;
+
+/**
+ * struct ti_lmu_bl_reg
+ *
+ * @init:		Device initialization registers
+ * @num_init:		Numbers of initialization registers
+ * @channel:		Backlight channel configuration registers
+ * @mode:		Brightness control mode registers
+ * @ramp:		Ramp registers for lighting effect
+ * @ramp_reg_offset:	Ramp register offset.
+ *			Only used for multiple ramp registers.
+ * @enable:		Enable control register address
+ * @enable_usec:	Delay time for updating enable register.
+ *			Unit is microsecond.
+ * @brightness_msb:	Brightness MSB(Upper 8 bits) registers.
+ *			Concatenated with LSB in 11 bit dimming mode.
+ *			In 8 bit dimming, only MSB is used.
+ * @brightness_lsb:	Brightness LSB(Lower 3 bits) registers.
+ *			Only valid in 11 bit dimming mode.
+ */
+struct ti_lmu_bl_reg {
+	u32 *init;
+	int num_init;
+	u32 *channel;
+	u32 *mode;
+	u32 *ramp;
+	int ramp_reg_offset;
+	u8 *enable;
+	unsigned long enable_usec;
+	u8 *brightness_msb;
+	u8 *brightness_lsb;
+};
+
+/**
+ * struct ti_lmu_bl_cfg
+ *
+ * @reginfo:		Device register configuration
+ * @num_channels:	Number of backlight channels
+ * @max_brightness:	Max brightness value of backlight device
+ * @pwm_action:		How to control brightness registers in PWM mode
+ * @ramp_table:		[Optional] Ramp time table for lighting effect.
+ *			It's used for searching approximate register index.
+ * @size_ramp:		[Optional] Size of ramp table
+ * @fault_monitor_used:	[Optional] Set true if the device needs to handle
+ *			LMU fault monitor event.
+ *
+ * This structure is used for device specific data configuration.
+ */
+struct ti_lmu_bl_cfg {
+	const struct ti_lmu_bl_reg *reginfo;
+	int num_channels;
+	int max_brightness;
+	enum ti_lmu_bl_pwm_action pwm_action;
+	int *ramp_table;
+	int size_ramp;
+	bool fault_monitor_used;
+};
+
+/**
+ * struct ti_lmu_bl_chip
+ *
+ * @dev:		Parent device pointer
+ * @lmu:		LMU structure.
+ *			Used for register R/W access and notification.
+ * @cfg:		Device configuration data
+ * @lmu_bl:		Multiple backlight channels
+ * @num_backlights:	Number of backlight channels
+ * @nb:			Notifier block for handling LMU fault monitor event
+ *
+ * One backlight chip can have multiple backlight channels, 'ti_lmu_bl'.
+ */
+struct ti_lmu_bl_chip {
+	struct device *dev;
+	struct ti_lmu *lmu;
+	const struct ti_lmu_bl_cfg *cfg;
+	struct ti_lmu_bl *lmu_bl;
+	int num_backlights;
+	struct notifier_block nb;
+};
+
+/**
+ * struct ti_lmu_bl
+ *
+ * @chip:		Pointer to parent backlight device
+ * @bl_dev:		Backlight subsystem device structure
+ * @bank_id:		Backlight bank ID
+ * @name:		Backlight channel name
+ * @mode:		Backlight control mode
+ * @led_sources:	Backlight output channel configuration.
+ *			Bit mask is set on parsing DT.
+ * @default_brightness:	[Optional] Initial brightness value
+ * @ramp_up_msec:	[Optional] Ramp up time
+ * @ramp_down_msec:	[Optional] Ramp down time
+ * @pwm_period:		[Optional] PWM period
+ * @pwm:		[Optional] PWM subsystem structure
+ *
+ * Each backlight device has its own channel configuration.
+ * For chip control, parent chip data structure is used.
+ */
+struct ti_lmu_bl {
+	struct ti_lmu_bl_chip *chip;
+	struct backlight_device *bl_dev;
+
+	int bank_id;
+	const char *name;
+	enum ti_lmu_bl_ctrl_mode mode;
+	unsigned long led_sources;
+
+	unsigned int default_brightness;
+
+	/* Used for lighting effect */
+	unsigned int ramp_up_msec;
+	unsigned int ramp_down_msec;
+
+	/* Only valid in PWM mode */
+	unsigned int pwm_period;
+	struct pwm_device *pwm;
+};
+
+extern struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
+#endif
-- 
1.9.1


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

* [PATCH v2 8/9] leds: add LM3633 driver
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
                   ` (6 preceding siblings ...)
  2015-11-26  6:57 ` [PATCH v2 7/9] backlight: add TI LMU backlight driver Milo Kim
@ 2015-11-26  6:57 ` Milo Kim
  2015-11-27 11:19   ` Jacek Anaszewski
  2015-11-26  6:57 ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
  2016-01-06  7:20 ` [PATCH v2 0/9] Support TI LMU devices Milo Kim
  9 siblings, 1 reply; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:57 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

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, eight attributes are used. Sysfs ABI describes it.
  - Time domain
    : 'pattern_time_delay', 'pattern_time_rise', 'pattern_time_high',
      'pattern_time_fall' and 'pattern_time_low'.
  - Level domain
    : 'pattern_brightness_low' and 'pattern_brightness_high'.
  - Start or stop
    : 'run_pattern'

LMU fault monitor event handling
--------------------------------
  As soon as LMU fault monitoring is done, LMU device is reset. So LED
  device should be reinitialized. lm3633_led_fault_monitor_notifier() is
  used for this purpose.

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

Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: linux-leds@vger.kernel.org 
Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Mark Brown <broonie@kernel.org>
Cc: Rob Herring <robh+dt@kernel.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 |  97 +++
 drivers/leds/Kconfig                             |  10 +
 drivers/leds/Makefile                            |   1 +
 drivers/leds/leds-lm3633.c                       | 840 +++++++++++++++++++++++
 4 files changed, 948 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..46217d4
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-lm3633
@@ -0,0 +1,97 @@
+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_time_delay
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern startup delay. Please refer to (1).
+                Range is from 0 to 9700. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_rise
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern rising dimming time. Please refer to (2).
+                Range is from 0 to 16000. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_high
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern high level time. Please refer to (3).
+                It means how much time stays at high level.
+                Range is from 0 to 9700. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_fall
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern falling dimming time. Please refer to (4).
+                Range is from 0 to 16000. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_low
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern low level time. Please refer to (5).
+                It means how much time stays at low level.
+                Range is from 0 to 9700. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_brightness_high
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern brightness value at high level.
+                Please refer to (a). Range is from 0 to max brightness value.
+
+What:		/sys/class/leds/<led>/pattern_brightness_low
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern brightness value at low level.
+                Please refer to (b). Range is from 0 to max brightness value.
+
+What:		/sys/class/leds/<led>/run_pattern
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                It is used for activating or deactivating programmed LED
+                dimming pattern. Please make sure pattern parameters
+                should be written prior to accessing this attribute.
+
+                1 : activate programmed pattern
+                0 : deactivate the pattern
+
+                Example:
+                To run the pattern,
+
+                echo 0 > /sys/class/leds/<led>/pattern_time_delay
+                echo 200 > /sys/class/leds/<led>/pattern_time_rise
+                echo 300 > /sys/class/leds/<led>/pattern_time_high
+                echo 100 > /sys/class/leds/<led>/pattern_time_fall
+                echo 400 > /sys/class/leds/<led>/pattern_time_low
+                echo 0 > /sys/class/leds/<led>/pattern_brightness_low
+                echo 255 > /sys/class/leds/<led>/pattern_brightness_high
+                echo 1 > /sys/class/leds/<led>/run_pattern
+
+                To stop running pattern,
+                echo 0 > /sys/class/leds/<led>/run_pattern
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index b1ab8bd..ed071ac 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 e9d53092..e183b7d 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..9c391ca
--- /dev/null
+++ b/drivers/leds/leds-lm3633.c
@@ -0,0 +1,840 @@
+/*
+ * 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/bitops.h>
+#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_MAX_PWM				255
+#define LM3633_MIN_CURRENT			5000
+#define LM3633_MAX_CURRENT			30000
+#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
+#define LM3633_IMAX_OFFSET			6
+
+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 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 LMU fault monitor 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_sources:	LED output channel configuration.
+ *			Bit mask is set on parsing DT.
+ * @max_brightness:	Max brightness value.
+ *			Is is determined by led-max-microamp.
+ *
+ * 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_sources;
+	u8 max_brightness;
+};
+
+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)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, mask);
+}
+
+static int lm3633_led_disable_bank(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, 0);
+}
+
+static int lm3633_led_config_bank(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	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_sources)) {
+			if (lmu_led->led_sources & group_led[i]) {
+				lmu_led->bank_id = default_id[i];
+				val = 0;
+			} else {
+				lmu_led->bank_id = separate_id[i];
+				val = BIT(i);
+			}
+
+			ret = regmap_update_bits(regmap, LM3633_REG_BANK_SEL,
+						 BIT(i), val);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static u8 lm3633_convert_time_to_index(unsigned int msec)
+{
+	u8 idx, offset;
+
+	/*
+	 * Find an approximate index
+	 *
+	 *      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 an approximate index 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 int lm3633_led_update_linear_time(const char *buf,
+					 struct ti_lmu_led *lmu_led, u8 reg)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	unsigned long time;
+	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
+
+	/*
+	 * 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.
+	 */
+
+	if (kstrtoul(buf, 0, &time))
+		return -EINVAL;
+
+	return regmap_write(regmap, reg + offset,
+			    lm3633_convert_time_to_index(time));
+}
+
+static int lm3633_led_update_ramp_time(const char *buf,
+				       struct ti_lmu_led *lmu_led,
+				       u8 mask, u8 shift)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	unsigned long ramp_time;
+	u8 reg, val;
+
+	/*
+	 * Register values are index domain, so input time value should be
+	 * converted to index.
+	 */
+
+	if (kstrtoul(buf, 0, &ramp_time))
+		return -EINVAL;
+
+	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:
+		return -EINVAL;
+	}
+
+	val = lm3633_convert_ramp_to_index(ramp_time) << shift;
+	return regmap_update_bits(regmap, reg, mask, val);
+}
+
+static ssize_t lm3633_led_store_pattern_time_delay(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_linear_time(buf, lmu_led, LM3633_REG_PTN_DELAY);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_high(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_linear_time(buf, lmu_led,
+					    LM3633_REG_PTN_HIGHTIME);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_low(struct device *dev,
+						 struct device_attribute *attr,
+						 const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_linear_time(buf, lmu_led,
+					    LM3633_REG_PTN_LOWTIME);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_rise(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPUP_MASK,
+					  LM3633_PTN_RAMPUP_SHIFT);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_fall(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPDN_MASK,
+				       LM3633_PTN_RAMPDN_SHIFT);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static int lm3633_led_set_pattern_brightness(const char *buf,
+					     struct ti_lmu_led *lmu_led, u8 reg)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	unsigned long brt;
+	int ret;
+
+	if (kstrtoul(buf, 0, &brt))
+		return -EINVAL;
+
+	ret = lm3633_led_disable_bank(lmu_led);
+	if (ret)
+		return ret;
+
+	brt = min_t(unsigned long, brt, lmu_led->max_brightness);
+
+	return regmap_write(regmap, reg, (u8)brt);
+}
+
+static ssize_t lm3633_led_store_pattern_brightness_low(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
+	u8 reg = LM3633_REG_PTN_LOWBRT + offset;
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_brightness_high(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	u8 reg = LM3633_REG_PTN_HIGHBRT + lmu_led->bank_id;
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static int lm3633_led_activate_pattern(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+	int ret;
+
+	ret = regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, mask);
+	if (ret)
+		return ret;
+
+	return lm3633_led_enable_bank(lmu_led);
+}
+
+static int lm3633_led_deactivate_pattern(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, 0);
+}
+
+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);
+	unsigned long run;
+	int ret;
+
+	if (kstrtoul(buf, 0, &run))
+		return -EINVAL;
+
+	mutex_lock(&lmu_led->chip->lock);
+
+	if (!run)
+		ret = lm3633_led_deactivate_pattern(lmu_led);
+	else
+		ret = lm3633_led_activate_pattern(lmu_led);
+
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define LM3633_PATTERN_ATTR(name)	\
+DEVICE_ATTR(pattern_##name, S_IWUSR, NULL, lm3633_led_store_pattern_##name)
+
+static LM3633_PATTERN_ATTR(time_delay);
+static LM3633_PATTERN_ATTR(time_rise);
+static LM3633_PATTERN_ATTR(time_high);
+static LM3633_PATTERN_ATTR(time_fall);
+static LM3633_PATTERN_ATTR(time_low);
+static LM3633_PATTERN_ATTR(brightness_low);
+static LM3633_PATTERN_ATTR(brightness_high);
+static DEVICE_ATTR(run_pattern, S_IWUSR, NULL, lm3633_led_run_pattern);
+
+static struct attribute *lm3633_led_attrs[] = {
+	&dev_attr_pattern_time_delay.attr,
+	&dev_attr_pattern_time_rise.attr,
+	&dev_attr_pattern_time_high.attr,
+	&dev_attr_pattern_time_fall.attr,
+	&dev_attr_pattern_time_low.attr,
+	&dev_attr_pattern_brightness_low.attr,
+	&dev_attr_pattern_brightness_high.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)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 reg = LM3633_REG_IMAX_LVLED_BASE + lmu_led->bank_id;
+
+	return regmap_write(regmap, reg, LMU_IMAX_30mA);
+}
+
+static int lm3633_led_brightness_set(struct led_classdev *cdev,
+				     enum led_brightness brightness)
+{
+	struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
+						  cdev);
+	struct ti_lmu_led_chip *chip = lmu_led->chip;
+	struct regmap *regmap = chip->lmu->regmap;
+	u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = regmap_write(regmap, reg, brightness);
+	if (ret) {
+		mutex_unlock(&chip->lock);
+		return ret;
+	}
+
+	if (brightness == 0)
+		lm3633_led_disable_bank(lmu_led);
+	else
+		lm3633_led_enable_bank(lmu_led);
+
+	mutex_unlock(&chip->lock);
+
+	return 0;
+}
+
+static int lm3633_led_init(struct ti_lmu_led *lmu_led, int bank_id)
+{
+	struct device *dev = lmu_led->chip->dev;
+	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 = lmu_led->max_brightness;
+	lmu_led->cdev.brightness_set_blocking = lm3633_led_brightness_set;
+	lmu_led->cdev.groups = lm3633_led_groups;
+	lmu_led->cdev.name = lmu_led->name;
+
+	ret = devm_led_classdev_register(dev, &lmu_led->cdev);
+	if (ret) {
+		dev_err(dev, "LED register err: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void lm3633_led_of_set_label(struct device_node *np,
+				    struct ti_lmu_led *lmu_led)
+{
+	const char *name;
+
+	if (!of_property_read_string(np, "label", &name))
+		lmu_led->name = name;
+	else
+		lmu_led->name = np->name;
+}
+
+static int lm3633_led_of_create_channel(struct device_node *np,
+					struct ti_lmu_led *lmu_led)
+{
+	int ret, num_sources;
+	u32 sources[LM3633_MAX_LEDS];
+
+	ret = of_property_count_u32_elems(np, "led-sources");
+	if (ret < 0 || ret > LM3633_MAX_LEDS)
+		return -EINVAL;
+
+	num_sources = ret;
+	ret = of_property_read_u32_array(np, "led-sources", sources,
+					 num_sources);
+	if (ret)
+		return ret;
+
+	lmu_led->led_sources = 0;
+	while (num_sources--)
+		set_bit(sources[num_sources], &lmu_led->led_sources);
+
+	return 0;
+}
+
+static u8 lm3633_led_convert_current_to_index(u32 imax_microamp)
+{
+	u8 imax_milliamp = imax_microamp / 1000;
+	const u8 imax_table[] = {
+		LMU_IMAX_6mA,  LMU_IMAX_7mA,  LMU_IMAX_8mA,  LMU_IMAX_9mA,
+		LMU_IMAX_10mA, LMU_IMAX_11mA, LMU_IMAX_12mA, LMU_IMAX_13mA,
+		LMU_IMAX_14mA, LMU_IMAX_15mA, LMU_IMAX_16mA, LMU_IMAX_17mA,
+		LMU_IMAX_18mA, LMU_IMAX_19mA, LMU_IMAX_20mA, LMU_IMAX_21mA,
+		LMU_IMAX_22mA, LMU_IMAX_23mA, LMU_IMAX_24mA, LMU_IMAX_25mA,
+		LMU_IMAX_26mA, LMU_IMAX_27mA, LMU_IMAX_28mA, LMU_IMAX_29mA,
+	};
+
+	/* Valid range is from 5mA to 30mA */
+	if (imax_milliamp <= 5)
+		return LMU_IMAX_5mA;
+
+	if (imax_milliamp >= 30)
+		return LMU_IMAX_30mA;
+
+	return imax_table[imax_milliamp - LM3633_IMAX_OFFSET];
+}
+
+static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led, u32 imax)
+{
+	u8 max_current = lm3633_led_convert_current_to_index(imax);
+	const u8 max_brightness_table[] = {
+		[LMU_IMAX_5mA]  = 191,
+		[LMU_IMAX_6mA]  = 197,
+		[LMU_IMAX_7mA]  = 203,
+		[LMU_IMAX_8mA]  = 208,
+		[LMU_IMAX_9mA]  = 212,
+		[LMU_IMAX_10mA] = 216,
+		[LMU_IMAX_11mA] = 219,
+		[LMU_IMAX_12mA] = 222,
+		[LMU_IMAX_13mA] = 225,
+		[LMU_IMAX_14mA] = 228,
+		[LMU_IMAX_15mA] = 230,
+		[LMU_IMAX_16mA] = 233,
+		[LMU_IMAX_17mA] = 235,
+		[LMU_IMAX_18mA] = 237,
+		[LMU_IMAX_19mA] = 239,
+		[LMU_IMAX_20mA] = 241,
+		[LMU_IMAX_21mA] = 242,
+		[LMU_IMAX_22mA] = 244,
+		[LMU_IMAX_23mA] = 246,
+		[LMU_IMAX_24mA] = 247,
+		[LMU_IMAX_25mA] = 249,
+		[LMU_IMAX_26mA] = 250,
+		[LMU_IMAX_27mA] = 251,
+		[LMU_IMAX_28mA] = 253,
+		[LMU_IMAX_29mA] = 254,
+		[LMU_IMAX_30mA] = 255,
+	};
+
+	return max_brightness_table[max_current];
+}
+
+static void lm3633_led_of_get_max_brightness(struct device_node *np,
+					     struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u32 imax = 0;
+	u8 mode = 0;
+
+	of_property_read_u32(np, "led-max-microamp", &imax);
+
+	if (imax <= LM3633_MIN_CURRENT)
+		imax = LM3633_MIN_CURRENT;
+	else
+		imax = min_t(unsigned int, imax, LM3633_MAX_CURRENT);
+
+	/*
+	 * Max brightness is determined by mapping mode - exponential or
+	 * linear.
+	 */
+	regmap_read(regmap, LM3633_REG_LED_MAPPING_MODE, (unsigned int *)&mode);
+
+	if (mode & LM3633_LED_EXPONENTIAL)
+		lmu_led->max_brightness =
+				lm3633_led_scale_max_brightness(lmu_led, imax);
+	else
+		lmu_led->max_brightness =
+				(imax * LM3633_MAX_PWM) / LM3633_MAX_CURRENT;
+}
+
+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 ret, num_leds;
+	int i = 0;
+
+	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;
+		each->bank_id = 0;
+		each->chip = chip;
+
+		lm3633_led_of_set_label(child, each);
+
+		ret = lm3633_led_of_create_channel(child, each);
+		if (ret) {
+			of_node_put(np);
+			return ret;
+		}
+
+		lm3633_led_of_get_max_brightness(child, each);
+		i++;
+	}
+
+	chip->lmu_led = lmu_led;
+	chip->num_leds = num_leds;
+
+	return 0;
+}
+
+static int lm3633_led_fault_monitor_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;
+
+	/*
+	 * LMU fault monitor driver resets the device, so LED should be
+	 * re-configured after fault detection procedure is done.
+	 */
+	if (action == LMU_EVENT_MONITOR_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;
+			}
+
+			led_set_brightness(&each->cdev, each->cdev.brightness);
+		}
+	}
+
+	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;
+
+	mutex_init(&chip->lock);
+
+	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);
+			return ret;
+		}
+	}
+
+	/*
+	 * Notifier callback is required because LED device needs
+	 * reconfiguration after open/short circuit fault monitoring
+	 * by ti-lmu-fault-monitor driver.
+	 */
+	chip->nb.notifier_call = lm3633_led_fault_monitor_notifier;
+	ret = blocking_notifier_chain_register(&chip->lmu->notifier, &chip->nb);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, chip);
+
+	return 0;
+}
+
+static int lm3633_led_remove(struct platform_device *pdev)
+{
+	struct ti_lmu_led_chip *chip = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < chip->num_leds; i++)
+		lm3633_led_deactivate_pattern(chip->lmu_led + i);
+
+	blocking_notifier_chain_unregister(&chip->lmu->notifier, &chip->nb);
+
+	return 0;
+}
+
+static struct platform_driver lm3633_led_driver = {
+	.probe = lm3633_led_probe,
+	.remove = lm3633_led_remove,
+	.driver = {
+		.name = "lm3633-leds",
+	},
+};
+
+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


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

* [PATCH v2 9/9] regulator: add LM363X driver
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
                   ` (7 preceding siblings ...)
  2015-11-26  6:57 ` [PATCH v2 8/9] leds: add LM3633 driver Milo Kim
@ 2015-11-26  6:57 ` Milo Kim
  2015-11-27 12:55   ` Applied "regulator: add LM363X driver" to the regulator tree Mark Brown
  2016-01-14  7:56   ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
  2016-01-06  7:20 ` [PATCH v2 0/9] Support TI LMU devices Milo Kim
  9 siblings, 2 replies; 38+ messages in thread
From: Milo Kim @ 2015-11-26  6:57 UTC (permalink / raw)
  To: robh+dt, lee.jones, j.anaszewski, broonie
  Cc: devicetree, linux-leds, linux-kernel, Milo Kim

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 configured through the DT properties.

Cc: Mark Brown <broonie@kernel.org>
Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: linux-leds@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 | 309 +++++++++++++++++++++++++++++++++++
 3 files changed, 319 insertions(+)
 create mode 100644 drivers/regulator/lm363x-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 3e028d9..971ebfa 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 5a963d9..21a3e67 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..e1b683e
--- /dev/null
+++ b/drivers/regulator/lm363x-regulator.c
@@ -0,0 +1,309 @@
+/*
+ * 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 regmap *regmap;
+	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 (regmap_read(lm363x_regulator->regmap, addr, (unsigned int *)&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",
+		.of_match	= "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",
+		.of_match	= "vcont",
+		.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",
+		.of_match	= "voref",
+		.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",
+		.of_match	= "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",
+		.of_match	= "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",
+		.of_match	= "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",
+		.of_match	= "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",
+		.of_match	= "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 int lm363x_regulator_of_get_enable_gpio(struct device_node *np, int id)
+{
+	/*
+	 * Check LCM_EN1/2_GPIO is configured.
+	 * Those pins are used for enabling VPOS/VNEG LDOs.
+	 */
+	switch (id) {
+	case LM3632_LDO_POS:
+		return of_get_named_gpio(np, "ti,lcm-en1-gpio", 0);
+	case LM3632_LDO_NEG:
+		return of_get_named_gpio(np, "ti,lcm-en2-gpio", 0);
+	default:
+		return -EINVAL;
+	}
+}
+
+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 regmap *regmap = lmu->regmap;
+	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->regmap = regmap;
+
+	cfg.dev = dev;
+	cfg.driver_data = lm363x_regulator;
+	cfg.regmap = 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->of_node, id);
+	if (gpio_is_valid(ena_gpio)) {
+		cfg.ena_gpio = ena_gpio;
+		cfg.ena_gpio_flags = GPIOF_OUT_INIT_LOW;
+
+		ret = regmap_update_bits(regmap, 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 = devm_regulator_register(dev, &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 struct platform_driver lm363x_regulator_driver = {
+	.probe = lm363x_regulator_probe,
+	.driver = {
+		.name = "lm363x-regulator",
+	},
+};
+
+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


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

* Re: [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED binding information
  2015-11-26  6:56 ` [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED " Milo Kim
@ 2015-11-27 11:19   ` Jacek Anaszewski
  2015-11-30  8:19     ` Kim, Milo
  0 siblings, 1 reply; 38+ messages in thread
From: Jacek Anaszewski @ 2015-11-27 11:19 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

Hi Milo,

On 11/26/2015 07:56 AM, Milo Kim wrote:
> LM3633 LED device is one of TI LMU device list.
>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: linux-leds@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>   .../devicetree/bindings/leds/leds-lm3633.txt       | 24 ++++++++++++++++++++++
>   1 file changed, 24 insertions(+)
>   create mode 100644 Documentation/devicetree/bindings/leds/leds-lm3633.txt
>
> diff --git a/Documentation/devicetree/bindings/leds/leds-lm3633.txt b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
> new file mode 100644
> index 0000000..a553894
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
> @@ -0,0 +1,24 @@
> +TI LMU LM3633 LED device tree bindings
> +
> +Required properties:
> +  - compatible: "ti,lm3633-leds"
> +
> +Child nodes:
> +  Each node matches with LED control bank.
> +  Please refer to the datasheet [1].

leds/common.txt documentation says that child nodes represent discrete
LED elements.

> +  Required properties of a child node:
> +  - led-sources: List of enabled channels from 0 to 5.
> +                 Please refer to LED binding [2].

led-sources property should contain always one element -
a control bank identifier that the iout is to be associated with.
For example, if there are three LEDs and they should be controlled
with control bank A, then the bindings should look as follows
(assuming that control bank identifiers start from 0 [A:0, B:1,
C:2, etc. - it has to be also explicitly stated in the documentation]:

lvled1 {
	led-sources = <2>;
	led-max-microamp = <1000>;
}

lvled2 {
	led-sources = <2>;
	led-max-microamp = <29000>;
}

lvled3 {
	led-sources = <2>;
	led-max-microamp = <7000>;
}

The driver should expose singled LED class device for this arrangement.

Since all of these LEDs will be controlled by the common bank, the
lowest led-max-micromp constraint value from the three should be taken
as a base for calculating max_brightness property of the LED class
device. Of course in the sane case all these properties should have
the same values.

If the LEDs are to be connected to separate banks, then DT child nodes
should look e.g. as follows (led-max-microamp values are set
arbitrarily here):

lvled4 {
	led-sources = <6>;
	led-max-microamp = <20000>;
}

lvled5 {
	led-sources = <7>;
	led-max-microamp = <17000>;
}

lvled6 {
	led-sources = <8>;
	led-max-microamp = <11000>;
}

You can of course add label properties to the child nodes.
LED class device name could be composed out of LED names
separated with colon, so here it would be:

lvled4:levled5:lvled6

> +  Optional properties of a child node:
> +  - label: LED channel identification. Please refer to LED binding [2].
> +  - led-max-microamp: Max current setting. Type is <u32>.
> +                      Unit is microampere. Range is from 5000 to 30000.
> +                      Step is 1000. Please refer to LED binding [2].
> +
> +Example: Please refer to ti-lmu dt-bindings [3].
> +
> +[1] http://www.ti.com/product/LM3633/datasheet
> +[2] ../leds/common.txt
> +[2] ../mfd/ti-lmu.txt

Last index should be [3] and all references above should be corrected
accordingly.

-- 
Best Regards,
Jacek Anaszewski

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

* Re: [PATCH v2 8/9] leds: add LM3633 driver
  2015-11-26  6:57 ` [PATCH v2 8/9] leds: add LM3633 driver Milo Kim
@ 2015-11-27 11:19   ` Jacek Anaszewski
  2015-11-28  8:28     ` Jacek Anaszewski
  2015-11-30  8:48     ` Kim, Milo
  0 siblings, 2 replies; 38+ messages in thread
From: Jacek Anaszewski @ 2015-11-27 11:19 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

Hi Milo,

Thanks for the update. I have few comments below.

On 11/26/2015 07:57 AM, Milo Kim wrote:
> 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, eight attributes are used. Sysfs ABI describes it.
>    - Time domain
>      : 'pattern_time_delay', 'pattern_time_rise', 'pattern_time_high',
>        'pattern_time_fall' and 'pattern_time_low'.
>    - Level domain
>      : 'pattern_brightness_low' and 'pattern_brightness_high'.
>    - Start or stop
>      : 'run_pattern'
>
> LMU fault monitor event handling
> --------------------------------
>    As soon as LMU fault monitoring is done, LMU device is reset. So LED
>    device should be reinitialized. lm3633_led_fault_monitor_notifier() is
>    used for this purpose.
>
> Data structure
> --------------
>    ti_lmu_led:         LED output channel data.
>    ti_lmu_led_chip:    LED device data. One LED device can have multiple
>                        LED channel data.
>
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: linux-leds@vger.kernel.org
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: Rob Herring <robh+dt@kernel.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 |  97 +++
>   drivers/leds/Kconfig                             |  10 +
>   drivers/leds/Makefile                            |   1 +
>   drivers/leds/leds-lm3633.c                       | 840 +++++++++++++++++++++++
>   4 files changed, 948 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..46217d4
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-led-lm3633
> @@ -0,0 +1,97 @@
> +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_time_delay
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern startup delay. Please refer to (1).
> +                Range is from 0 to 9700. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_rise
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern rising dimming time. Please refer to (2).
> +                Range is from 0 to 16000. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_high
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern high level time. Please refer to (3).
> +                It means how much time stays at high level.
> +                Range is from 0 to 9700. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_fall
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern falling dimming time. Please refer to (4).
> +                Range is from 0 to 16000. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_low
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern low level time. Please refer to (5).
> +                It means how much time stays at low level.
> +                Range is from 0 to 9700. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_brightness_high
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern brightness value at high level.
> +                Please refer to (a). Range is from 0 to max brightness value.
> +
> +What:		/sys/class/leds/<led>/pattern_brightness_low
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern brightness value at low level.
> +                Please refer to (b). Range is from 0 to max brightness value.
> +
> +What:		/sys/class/leds/<led>/run_pattern
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                It is used for activating or deactivating programmed LED
> +                dimming pattern. Please make sure pattern parameters
> +                should be written prior to accessing this attribute.
> +
> +                1 : activate programmed pattern
> +                0 : deactivate the pattern
> +
> +                Example:
> +                To run the pattern,
> +
> +                echo 0 > /sys/class/leds/<led>/pattern_time_delay
> +                echo 200 > /sys/class/leds/<led>/pattern_time_rise
> +                echo 300 > /sys/class/leds/<led>/pattern_time_high
> +                echo 100 > /sys/class/leds/<led>/pattern_time_fall
> +                echo 400 > /sys/class/leds/<led>/pattern_time_low
> +                echo 0 > /sys/class/leds/<led>/pattern_brightness_low
> +                echo 255 > /sys/class/leds/<led>/pattern_brightness_high
> +                echo 1 > /sys/class/leds/<led>/run_pattern
> +
> +                To stop running pattern,
> +                echo 0 > /sys/class/leds/<led>/run_pattern
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index b1ab8bd..ed071ac 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 e9d53092..e183b7d 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..9c391ca
> --- /dev/null
> +++ b/drivers/leds/leds-lm3633.c
> @@ -0,0 +1,840 @@
> +/*
> + * 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/bitops.h>
> +#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_MAX_PWM				255
> +#define LM3633_MIN_CURRENT			5000
> +#define LM3633_MAX_CURRENT			30000
> +#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
> +#define LM3633_IMAX_OFFSET			6
> +
> +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 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

Could you clarify what "string" means here?

> + * @num_leds:		Number of LED strings

and here?

> + * @nb:			Notifier block for handling LMU fault monitor 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_sources:	LED output channel configuration.
> + *			Bit mask is set on parsing DT.
> + * @max_brightness:	Max brightness value.
> + *			Is is determined by led-max-microamp.
> + *
> + * 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;

You have it in the struct led_classdev.

> +	unsigned long led_sources;
> +	u8 max_brightness;

This is also present in the struct led_classdev.

> +};
> +
> +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)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +
> +	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, mask);
> +}
> +
> +static int lm3633_led_disable_bank(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +
> +	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, 0);
> +}
> +
> +static int lm3633_led_config_bank(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	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_sources)) {
> +			if (lmu_led->led_sources & group_led[i]) {
> +				lmu_led->bank_id = default_id[i];
> +				val = 0;
> +			} else {
> +				lmu_led->bank_id = separate_id[i];
> +				val = BIT(i);
> +			}
> +
> +			ret = regmap_update_bits(regmap, LM3633_REG_BANK_SEL,
> +						 BIT(i), val);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static u8 lm3633_convert_time_to_index(unsigned int msec)
> +{
> +	u8 idx, offset;
> +
> +	/*
> +	 * Find an approximate index

I think that we shouldn't approximate but clamp the msec
to the nearest possible device setting.

> +	 *
> +	 *      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 an approximate index by looking up the table */

Similarly here. So you should have an array of the possible msec
values and iterate through it to look for the nearest one.

> +		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 int lm3633_led_update_linear_time(const char *buf,
> +					 struct ti_lmu_led *lmu_led, u8 reg)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	unsigned long time;
> +	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
> +
> +	/*
> +	 * 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.
> +	 */
> +
> +	if (kstrtoul(buf, 0, &time))
> +		return -EINVAL;
> +
> +	return regmap_write(regmap, reg + offset,
> +			    lm3633_convert_time_to_index(time));
> +}
> +
> +static int lm3633_led_update_ramp_time(const char *buf,
> +				       struct ti_lmu_led *lmu_led,
> +				       u8 mask, u8 shift)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	unsigned long ramp_time;
> +	u8 reg, val;
> +
> +	/*
> +	 * Register values are index domain, so input time value should be
> +	 * converted to index.
> +	 */
> +
> +	if (kstrtoul(buf, 0, &ramp_time))
> +		return -EINVAL;
> +
> +	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:
> +		return -EINVAL;
> +	}
> +
> +	val = lm3633_convert_ramp_to_index(ramp_time) << shift;

Please add empty line here.

> +	return regmap_update_bits(regmap, reg, mask, val);
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_delay(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_linear_time(buf, lmu_led, LM3633_REG_PTN_DELAY);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_high(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_linear_time(buf, lmu_led,
> +					    LM3633_REG_PTN_HIGHTIME);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_low(struct device *dev,
> +						 struct device_attribute *attr,
> +						 const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_linear_time(buf, lmu_led,
> +					    LM3633_REG_PTN_LOWTIME);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_rise(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPUP_MASK,
> +					  LM3633_PTN_RAMPUP_SHIFT);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_fall(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPDN_MASK,
> +				       LM3633_PTN_RAMPDN_SHIFT);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static int lm3633_led_set_pattern_brightness(const char *buf,
> +					     struct ti_lmu_led *lmu_led, u8 reg)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	unsigned long brt;
> +	int ret;
> +
> +	if (kstrtoul(buf, 0, &brt))
> +		return -EINVAL;
> +
> +	ret = lm3633_led_disable_bank(lmu_led);
> +	if (ret)
> +		return ret;
> +
> +	brt = min_t(unsigned long, brt, lmu_led->max_brightness);
> +
> +	return regmap_write(regmap, reg, (u8)brt);
> +}
> +
> +static ssize_t lm3633_led_store_pattern_brightness_low(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
> +	u8 reg = LM3633_REG_PTN_LOWBRT + offset;
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_brightness_high(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	u8 reg = LM3633_REG_PTN_HIGHBRT + lmu_led->bank_id;
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static int lm3633_led_activate_pattern(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +	int ret;
> +
> +	ret = regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, mask);
> +	if (ret)
> +		return ret;
> +
> +	return lm3633_led_enable_bank(lmu_led);
> +}
> +
> +static int lm3633_led_deactivate_pattern(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +
> +	return regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, 0);
> +}
> +
> +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);
> +	unsigned long run;
> +	int ret;
> +
> +	if (kstrtoul(buf, 0, &run))
> +		return -EINVAL;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +
> +	if (!run)
> +		ret = lm3633_led_deactivate_pattern(lmu_led);
> +	else
> +		ret = lm3633_led_activate_pattern(lmu_led);
> +
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +#define LM3633_PATTERN_ATTR(name)	\
> +DEVICE_ATTR(pattern_##name, S_IWUSR, NULL, lm3633_led_store_pattern_##name)
> +
> +static LM3633_PATTERN_ATTR(time_delay);
> +static LM3633_PATTERN_ATTR(time_rise);
> +static LM3633_PATTERN_ATTR(time_high);
> +static LM3633_PATTERN_ATTR(time_fall);
> +static LM3633_PATTERN_ATTR(time_low);
> +static LM3633_PATTERN_ATTR(brightness_low);
> +static LM3633_PATTERN_ATTR(brightness_high);
> +static DEVICE_ATTR(run_pattern, S_IWUSR, NULL, lm3633_led_run_pattern);
> +
> +static struct attribute *lm3633_led_attrs[] = {
> +	&dev_attr_pattern_time_delay.attr,
> +	&dev_attr_pattern_time_rise.attr,
> +	&dev_attr_pattern_time_high.attr,
> +	&dev_attr_pattern_time_fall.attr,
> +	&dev_attr_pattern_time_low.attr,
> +	&dev_attr_pattern_brightness_low.attr,
> +	&dev_attr_pattern_brightness_high.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)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 reg = LM3633_REG_IMAX_LVLED_BASE + lmu_led->bank_id;
> +
> +	return regmap_write(regmap, reg, LMU_IMAX_30mA);
> +}
> +
> +static int lm3633_led_brightness_set(struct led_classdev *cdev,
> +				     enum led_brightness brightness)
> +{
> +	struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
> +						  cdev);
> +	struct ti_lmu_led_chip *chip = lmu_led->chip;
> +	struct regmap *regmap = chip->lmu->regmap;
> +	u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
> +	int ret;
> +
> +	mutex_lock(&chip->lock);
> +
> +	ret = regmap_write(regmap, reg, brightness);
> +	if (ret) {
> +		mutex_unlock(&chip->lock);
> +		return ret;
> +	}
> +
> +	if (brightness == 0)
> +		lm3633_led_disable_bank(lmu_led);

This should be checked at first, as I suppose that disabling
a bank suffices to turn the LED off.

> +	else
> +		lm3633_led_enable_bank(lmu_led);
> +
> +	mutex_unlock(&chip->lock);
> +
> +	return 0;
> +}
> +
> +static int lm3633_led_init(struct ti_lmu_led *lmu_led, int bank_id)
> +{
> +	struct device *dev = lmu_led->chip->dev;
> +	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 = lmu_led->max_brightness;
> +	lmu_led->cdev.brightness_set_blocking = lm3633_led_brightness_set;
> +	lmu_led->cdev.groups = lm3633_led_groups;
> +	lmu_led->cdev.name = lmu_led->name;
> +
> +	ret = devm_led_classdev_register(dev, &lmu_led->cdev);
> +	if (ret) {
> +		dev_err(dev, "LED register err: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void lm3633_led_of_set_label(struct device_node *np,
> +				    struct ti_lmu_led *lmu_led)
> +{
> +	const char *name;
> +
> +	if (!of_property_read_string(np, "label", &name))
> +		lmu_led->name = name;
> +	else
> +		lmu_led->name = np->name;
> +}
> +
> +static int lm3633_led_of_create_channel(struct device_node *np,
> +					struct ti_lmu_led *lmu_led)
> +{
> +	int ret, num_sources;
> +	u32 sources[LM3633_MAX_LEDS];
> +
> +	ret = of_property_count_u32_elems(np, "led-sources");
> +	if (ret < 0 || ret > LM3633_MAX_LEDS)
> +		return -EINVAL;
> +
> +	num_sources = ret;
> +	ret = of_property_read_u32_array(np, "led-sources", sources,
> +					 num_sources);
> +	if (ret)
> +		return ret;
> +
> +	lmu_led->led_sources = 0;
> +	while (num_sources--)
> +		set_bit(sources[num_sources], &lmu_led->led_sources);
> +
> +	return 0;
> +}
> +
> +static u8 lm3633_led_convert_current_to_index(u32 imax_microamp)
> +{
> +	u8 imax_milliamp = imax_microamp / 1000;
> +	const u8 imax_table[] = {
> +		LMU_IMAX_6mA,  LMU_IMAX_7mA,  LMU_IMAX_8mA,  LMU_IMAX_9mA,
> +		LMU_IMAX_10mA, LMU_IMAX_11mA, LMU_IMAX_12mA, LMU_IMAX_13mA,
> +		LMU_IMAX_14mA, LMU_IMAX_15mA, LMU_IMAX_16mA, LMU_IMAX_17mA,
> +		LMU_IMAX_18mA, LMU_IMAX_19mA, LMU_IMAX_20mA, LMU_IMAX_21mA,
> +		LMU_IMAX_22mA, LMU_IMAX_23mA, LMU_IMAX_24mA, LMU_IMAX_25mA,
> +		LMU_IMAX_26mA, LMU_IMAX_27mA, LMU_IMAX_28mA, LMU_IMAX_29mA,
> +	};
> +
> +	/* Valid range is from 5mA to 30mA */
> +	if (imax_milliamp <= 5)
> +		return LMU_IMAX_5mA;
> +
> +	if (imax_milliamp >= 30)
> +		return LMU_IMAX_30mA;
> +
> +	return imax_table[imax_milliamp - LM3633_IMAX_OFFSET];
> +}
> +
> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led, u32 imax)
> +{
> +	u8 max_current = lm3633_led_convert_current_to_index(imax);
> +	const u8 max_brightness_table[] = {
> +		[LMU_IMAX_5mA]  = 191,
> +		[LMU_IMAX_6mA]  = 197,
> +		[LMU_IMAX_7mA]  = 203,
> +		[LMU_IMAX_8mA]  = 208,
> +		[LMU_IMAX_9mA]  = 212,
> +		[LMU_IMAX_10mA] = 216,
> +		[LMU_IMAX_11mA] = 219,
> +		[LMU_IMAX_12mA] = 222,
> +		[LMU_IMAX_13mA] = 225,
> +		[LMU_IMAX_14mA] = 228,
> +		[LMU_IMAX_15mA] = 230,
> +		[LMU_IMAX_16mA] = 233,
> +		[LMU_IMAX_17mA] = 235,
> +		[LMU_IMAX_18mA] = 237,
> +		[LMU_IMAX_19mA] = 239,
> +		[LMU_IMAX_20mA] = 241,
> +		[LMU_IMAX_21mA] = 242,
> +		[LMU_IMAX_22mA] = 244,
> +		[LMU_IMAX_23mA] = 246,
> +		[LMU_IMAX_24mA] = 247,
> +		[LMU_IMAX_25mA] = 249,
> +		[LMU_IMAX_26mA] = 250,
> +		[LMU_IMAX_27mA] = 251,
> +		[LMU_IMAX_28mA] = 253,
> +		[LMU_IMAX_29mA] = 254,
> +		[LMU_IMAX_30mA] = 255,
> +	};

After analyzing the subject one more time I think that we need to
change the approach regarding max brightness issue.

At first - we shouldn't fix max current to max possible register value.
Instead we should take led-max-microamp property and write its value
to the [0x22 + bank offset] registers.

With this approach whole 0-255 range of brightness levels will be
valid for the driver.

In effect all LMU_IMAX* enums seem to be not needed.


> +	return max_brightness_table[max_current];
> +}
> +
> +static void lm3633_led_of_get_max_brightness(struct device_node *np,
> +					     struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u32 imax = 0;
> +	u8 mode = 0;
> +
> +	of_property_read_u32(np, "led-max-microamp", &imax);
> +
> +	if (imax <= LM3633_MIN_CURRENT)
> +		imax = LM3633_MIN_CURRENT;
> +	else
> +		imax = min_t(unsigned int, imax, LM3633_MAX_CURRENT);
> +
> +	/*
> +	 * Max brightness is determined by mapping mode - exponential or
> +	 * linear.
> +	 */
> +	regmap_read(regmap, LM3633_REG_LED_MAPPING_MODE, (unsigned int *)&mode);
> +
> +	if (mode & LM3633_LED_EXPONENTIAL)
> +		lmu_led->max_brightness =
> +				lm3633_led_scale_max_brightness(lmu_led, imax);
> +	else
> +		lmu_led->max_brightness =
> +				(imax * LM3633_MAX_PWM) / LM3633_MAX_CURRENT;
> +}
> +
> +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 ret, num_leds;
> +	int i = 0;
> +
> +	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;
> +		each->bank_id = 0;
> +		each->chip = chip;
> +
> +		lm3633_led_of_set_label(child, each);
> +
> +		ret = lm3633_led_of_create_channel(child, each);
> +		if (ret) {
> +			of_node_put(np);
> +			return ret;
> +		}
> +
> +		lm3633_led_of_get_max_brightness(child, each);
> +		i++;
> +	}
> +
> +	chip->lmu_led = lmu_led;
> +	chip->num_leds = num_leds;
> +
> +	return 0;
> +}
> +
> +static int lm3633_led_fault_monitor_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;
> +
> +	/*
> +	 * LMU fault monitor driver resets the device, so LED should be
> +	 * re-configured after fault detection procedure is done.
> +	 */
> +	if (action == LMU_EVENT_MONITOR_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;
> +			}
> +
> +			led_set_brightness(&each->cdev, each->cdev.brightness);
> +		}
> +	}
> +
> +	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;
> +
> +	mutex_init(&chip->lock);
> +
> +	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);
> +			return ret;
> +		}
> +	}
> +
> +	/*
> +	 * Notifier callback is required because LED device needs
> +	 * reconfiguration after open/short circuit fault monitoring
> +	 * by ti-lmu-fault-monitor driver.
> +	 */
> +	chip->nb.notifier_call = lm3633_led_fault_monitor_notifier;
> +	ret = blocking_notifier_chain_register(&chip->lmu->notifier, &chip->nb);
> +	if (ret)
> +		return ret;
> +
> +	platform_set_drvdata(pdev, chip);
> +
> +	return 0;
> +}
> +
> +static int lm3633_led_remove(struct platform_device *pdev)
> +{
> +	struct ti_lmu_led_chip *chip = platform_get_drvdata(pdev);
> +	int i;
> +
> +	for (i = 0; i < chip->num_leds; i++)
> +		lm3633_led_deactivate_pattern(chip->lmu_led + i);
> +
> +	blocking_notifier_chain_unregister(&chip->lmu->notifier, &chip->nb);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver lm3633_led_driver = {
> +	.probe = lm3633_led_probe,
> +	.remove = lm3633_led_remove,
> +	.driver = {
> +		.name = "lm3633-leds",
> +	},
> +};
> +
> +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");
>


-- 
Best Regards,
Jacek Anaszewski

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

* Re: [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information
  2015-11-26  6:57 ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator " Milo Kim
@ 2015-11-27 12:37   ` Mark Brown
  2015-11-27 20:44     ` Rob Herring
  2015-11-27 12:55   ` Applied "regulator: lm363x: add LM363x regulator binding information" to the regulator tree Mark Brown
  2015-11-27 20:57   ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information Rob Herring
  2 siblings, 1 reply; 38+ messages in thread
From: Mark Brown @ 2015-11-27 12:37 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, lee.jones, j.anaszewski, devicetree, linux-leds, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 187 bytes --]

On Thu, Nov 26, 2015 at 03:57:00PM +0900, Milo Kim wrote:
> This binding describes LM3631 and LM3632 regulator properties.

Please use subject lines matching the style for the subsystem.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 473 bytes --]

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

* Applied "regulator: lm363x: add LM363x regulator binding information" to the regulator tree
  2015-11-26  6:57 ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator " Milo Kim
  2015-11-27 12:37   ` Mark Brown
@ 2015-11-27 12:55   ` Mark Brown
  2015-11-27 20:57   ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information Rob Herring
  2 siblings, 0 replies; 38+ messages in thread
From: Mark Brown @ 2015-11-27 12:55 UTC (permalink / raw)
  To: Milo Kim, Mark Brown; +Cc: linux-kernel

The patch

   regulator: lm363x: add LM363x regulator binding information

has been applied to the regulator tree at

   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

>From eaea7d27129b9b493c3029608486c157827a92ce Mon Sep 17 00:00:00 2001
From: Milo Kim <milo.kim@ti.com>
Date: Thu, 26 Nov 2015 15:57:00 +0900
Subject: [PATCH] regulator: lm363x: add LM363x regulator binding information

This binding describes LM3631 and LM3632 regulator properties.

Signed-off-by: Milo Kim <milo.kim@ti.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 .../bindings/regulator/lm363x-regulator.txt        | 34 ++++++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/regulator/lm363x-regulator.txt

diff --git a/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt b/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
new file mode 100644
index 000000000000..8f14df9d1205
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
@@ -0,0 +1,34 @@
+TI LMU LM363x regulator device tree bindings
+
+LM363x regulator driver supports LM3631 and LM3632.
+LM3631 has five regulators and LM3632 supports three regulators.
+
+Required property:
+  - compatible: "ti,lm363x-regulator"
+
+Optional properties:
+  LM3632 has external enable pins for two LDOs.
+  - ti,lcm-en1-gpio: A GPIO specifier for Vpos control pin.
+  - ti,lcm-en2-gpio: A GPIO specifier for Vneg control pin.
+
+Child nodes:
+  LM3631
+  - vboost
+  - vcont
+  - voref
+  - vpos
+  - vneg
+
+  LM3632
+  - vboost
+  - vpos
+  - vneg
+
+  Optional properties of a child node:
+  Each sub-node should contain the constraints and initialization.
+  Please refer to [1].
+
+Examples: Please refer to ti-lmu dt-bindings [2].
+
+[1] ../regulator/regulator.txt
+[2] ../mfd/ti-lmu.txt
-- 
2.6.2


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

* Applied "regulator: add LM363X driver" to the regulator tree
  2015-11-26  6:57 ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
@ 2015-11-27 12:55   ` Mark Brown
  2016-01-14  7:56   ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
  1 sibling, 0 replies; 38+ messages in thread
From: Mark Brown @ 2015-11-27 12:55 UTC (permalink / raw)
  To: Milo Kim, Mark Brown; +Cc: linux-kernel

The patch

   regulator: add LM363X driver

has been applied to the regulator tree at

   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git 

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.  

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

>From 3a8d1a73a037e1bf099dbbd477e017607bc3dc20 Mon Sep 17 00:00:00 2001
From: Milo Kim <milo.kim@ti.com>
Date: Thu, 26 Nov 2015 15:57:05 +0900
Subject: [PATCH] regulator: add LM363X driver

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 configured through the DT properties.

Signed-off-by: Milo Kim <milo.kim@ti.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
---
 drivers/regulator/Kconfig            |   9 +
 drivers/regulator/Makefile           |   1 +
 drivers/regulator/lm363x-regulator.c | 309 +++++++++++++++++++++++++++++++++++
 3 files changed, 319 insertions(+)
 create mode 100644 drivers/regulator/lm363x-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 8df0b0e62976..ca8f46579f01 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 0f8174913c17..0ea87cdd389c 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 000000000000..e1b683e02561
--- /dev/null
+++ b/drivers/regulator/lm363x-regulator.c
@@ -0,0 +1,309 @@
+/*
+ * 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 regmap *regmap;
+	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 (regmap_read(lm363x_regulator->regmap, addr, (unsigned int *)&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",
+		.of_match	= "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",
+		.of_match	= "vcont",
+		.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",
+		.of_match	= "voref",
+		.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",
+		.of_match	= "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",
+		.of_match	= "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",
+		.of_match	= "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",
+		.of_match	= "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",
+		.of_match	= "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 int lm363x_regulator_of_get_enable_gpio(struct device_node *np, int id)
+{
+	/*
+	 * Check LCM_EN1/2_GPIO is configured.
+	 * Those pins are used for enabling VPOS/VNEG LDOs.
+	 */
+	switch (id) {
+	case LM3632_LDO_POS:
+		return of_get_named_gpio(np, "ti,lcm-en1-gpio", 0);
+	case LM3632_LDO_NEG:
+		return of_get_named_gpio(np, "ti,lcm-en2-gpio", 0);
+	default:
+		return -EINVAL;
+	}
+}
+
+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 regmap *regmap = lmu->regmap;
+	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->regmap = regmap;
+
+	cfg.dev = dev;
+	cfg.driver_data = lm363x_regulator;
+	cfg.regmap = 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->of_node, id);
+	if (gpio_is_valid(ena_gpio)) {
+		cfg.ena_gpio = ena_gpio;
+		cfg.ena_gpio_flags = GPIOF_OUT_INIT_LOW;
+
+		ret = regmap_update_bits(regmap, 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 = devm_regulator_register(dev, &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 struct platform_driver lm363x_regulator_driver = {
+	.probe = lm363x_regulator_probe,
+	.driver = {
+		.name = "lm363x-regulator",
+	},
+};
+
+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");
-- 
2.6.2


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

* Re: [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information
  2015-11-27 12:37   ` Mark Brown
@ 2015-11-27 20:44     ` Rob Herring
  2015-11-27 22:07       ` Mark Brown
  0 siblings, 1 reply; 38+ messages in thread
From: Rob Herring @ 2015-11-27 20:44 UTC (permalink / raw)
  To: Mark Brown
  Cc: Milo Kim, lee.jones, j.anaszewski, devicetree, linux-leds, linux-kernel

On Fri, Nov 27, 2015 at 12:37:50PM +0000, Mark Brown wrote:
> On Thu, Nov 26, 2015 at 03:57:00PM +0900, Milo Kim wrote:
> > This binding describes LM3631 and LM3632 regulator properties.
> 
> Please use subject lines matching the style for the subsystem.

Which for bindings, my preference is "dt-bindings: ..." I assume you 
meant to follow regulator style though. 

Rob

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

* Re: [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information
  2015-11-26  6:56 ` [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information Milo Kim
@ 2015-11-27 20:55   ` Rob Herring
  2016-01-11  9:46   ` Lee Jones
  1 sibling, 0 replies; 38+ messages in thread
From: Rob Herring @ 2015-11-27 20:55 UTC (permalink / raw)
  To: Milo Kim
  Cc: lee.jones, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On Thu, Nov 26, 2015 at 03:56:57PM +0900, Milo Kim wrote:
> This patch describes overall binding for TI LMU MFD devices.
> 
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: Lee Jones <lee.jones@linaro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: linux-leds@vger.kernel.org 
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>

Acked-by: Rob Herring <robh@kernel.org>

> ---
>  Documentation/devicetree/bindings/mfd/ti-lmu.txt | 243 +++++++++++++++++++++++
>  1 file changed, 243 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mfd/ti-lmu.txt
> 
> diff --git a/Documentation/devicetree/bindings/mfd/ti-lmu.txt b/Documentation/devicetree/bindings/mfd/ti-lmu.txt
> new file mode 100644
> index 0000000..c885cf8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/ti-lmu.txt
> @@ -0,0 +1,243 @@
> +TI LMU (Lighting Management Unit) device tree bindings
> +
> +TI LMU driver supports lighting devices below.
> +
> +   Name                  Child nodes
> +  ------      ---------------------------------
> +  LM3532       Backlight
> +  LM3631       Backlight and regulator
> +  LM3632       Backlight and regulator
> +  LM3633       Backlight, LED and fault monitor
> +  LM3695       Backlight
> +  LM3697       Backlight and fault monitor
> +
> +Required properties:
> +  - compatible: Should be one of:
> +                "ti,lm3532"
> +                "ti,lm3631"
> +                "ti,lm3632"
> +                "ti,lm3633"
> +                "ti,lm3695"
> +                "ti,lm3697"
> +  - reg: I2C slave address.
> +         0x11 for LM3632
> +         0x29 for LM3631
> +         0x36 for LM3633, LM3697
> +         0x38 for LM3532
> +         0x63 for LM3695
> +
> +Optional property:
> +  - enable-gpios: A GPIO specifier for hardware enable pin.
> +
> +Required node:
> +  - backlight: All LMU devices have backlight child nodes.
> +               For the properties, please refer to [1].
> +
> +Optional nodes:
> +  - fault-monitor: Hardware fault monitoring driver for LM3633 and LM3697.
> +    Required properties:
> +      - compatible: Should be one of:
> +                    "ti,lm3633-fault-monitor"
> +                    "ti,lm3697-fault-monitor"
> +  - leds: LED properties for LM3633. Please refer to [2].
> +  - regulators: Regulator properties for LM3631 and LM3632.
> +                Please refer to [3].
> +
> +[1] ../leds/backlight/ti-lmu-backlight.txt
> +[2] ../leds/leds-lm3633.txt
> +[3] ../regulator/lm363x-regulator.txt
> +
> +lm3532@38 {
> +	compatible = "ti,lm3532";
> +	reg = <0x38>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3532-backlight";
> +
> +		lcd {
> +			led-sources = <0 1 2>;
> +			ramp-up-msec = <30>;
> +			ramp-down-msec = <0>;
> +		};
> +	};
> +};
> +
> +lm3631@29 {
> +	compatible = "ti,lm3631";
> +	reg = <0x29>;
> +
> +	regulators {
> +		compatible = "ti,lm363x-regulator";
> +
> +		vboost {
> +			regulator-name = "lcd_boost";
> +			regulator-min-microvolt = <4500000>;
> +			regulator-max-microvolt = <6350000>;
> +			regulator-always-on;
> +		};
> +
> +		vcont {
> +			regulator-name = "lcd_vcont";
> +			regulator-min-microvolt = <1800000>;
> +			regulator-max-microvolt = <3300000>;
> +		};
> +
> +		voref {
> +			regulator-name = "lcd_voref";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +		};
> +
> +		vpos {
> +			regulator-name = "lcd_vpos";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +			regulator-boot-on;
> +		};
> +
> +		vneg {
> +			regulator-name = "lcd_vneg";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +			regulator-boot-on;
> +		};
> +	};
> +
> +	backlight {
> +		compatible = "ti,lm3631-backlight";
> +
> +		lcd_bl {
> +			led-sources = <0 1>;
> +			ramp-up-msec = <300>;
> +		};
> +	};
> +};
> +
> +lm3632@11 {
> +	compatible = "ti,lm3632";
> +	reg = <0x11>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>; /* PC2 */
> +
> +	regulators {
> +		compatible = "ti,lm363x-regulator";
> +
> +		ti,lcm-en1-gpio = <&pioC 0 GPIO_ACTIVE_HIGH>; /* PC0 */
> +		ti,lcm-en2-gpio = <&pioC 1 GPIO_ACTIVE_HIGH>; /* PC1 */
> +
> +		vboost {
> +			regulator-name = "lcd_boost";
> +			regulator-min-microvolt = <4500000>;
> +			regulator-max-microvolt = <6400000>;
> +			regulator-always-on;
> +		};
> +
> +		vpos {
> +			regulator-name = "lcd_vpos";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +		};
> +
> +		vneg {
> +			regulator-name = "lcd_vneg";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +		};
> +	};
> +
> +	backlight {
> +		compatible = "ti,lm3632-backlight";
> +
> +		pwms = <&pwm0 0 10000 0>; /* pwm number, period, polarity */
> +		pwm-names = "lmu-backlight";
> +
> +		lcd {
> +			led-sources = <0 1>;
> +			pwm-period = <10000>;
> +		};
> +	};
> +};
> +
> +lm3633@36 {
> +	compatible = "ti,lm3633";
> +	reg = <0x36>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3633-backlight";
> +
> +		main {
> +			label = "main_lcd";
> +			led-sources = <1 2>;
> +			ramp-up-msec = <500>;
> +			ramp-down-msec = <500>;
> +		};
> +
> +		front {
> +			label = "front_lcd";
> +			led-sources = <0>;
> +			ramp-up-msec = <1000>;
> +			ramp-down-msec = <0>;
> +		};
> +	};
> +
> +	leds {
> +		compatible = "ti,lm3633-leds";
> +
> +		chan1 {
> +			label = "status";
> +			led-sources = <1>;
> +			led-max-microamp = <6000>;
> +		};
> +
> +		chan345 {
> +			label = "rgb";
> +			led-sources = <3 4 5>;
> +			led-max-microamp = <10000>;
> +		};
> +	};
> +
> +	fault-monitor {
> +		compatible = "ti,lm3633-fault-monitor";
> +	};
> +};
> +
> +lm3695@63 {
> +	compatible = "ti,lm3695";
> +	reg = <0x63>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3695-backlight";
> +
> +		lcd {
> +			label = "bl";
> +			led-sources = <0 1>;
> +		};
> +	};
> +};
> +
> +lm3697@36 {
> +	compatible = "ti,lm3697";
> +	reg = <0x36>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3697-backlight";
> +
> +		lcd {
> +			led-sources = <0 1 2>;
> +			ramp-up-msec = <200>;
> +			ramp-down-msec = <200>;
> +		};
> +	};
> +
> +	fault-monitor {
> +		compatible = "ti,lm3697-fault-monitor";
> +	};
> +};
> -- 
> 1.9.1
> 

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

* Re: [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information
  2015-11-26  6:57 ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator " Milo Kim
  2015-11-27 12:37   ` Mark Brown
  2015-11-27 12:55   ` Applied "regulator: lm363x: add LM363x regulator binding information" to the regulator tree Mark Brown
@ 2015-11-27 20:57   ` Rob Herring
  2 siblings, 0 replies; 38+ messages in thread
From: Rob Herring @ 2015-11-27 20:57 UTC (permalink / raw)
  To: Milo Kim
  Cc: lee.jones, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On Thu, Nov 26, 2015 at 03:57:00PM +0900, Milo Kim wrote:
> This binding describes LM3631 and LM3632 regulator properties.
> 
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: Lee Jones <lee.jones@linaro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: linux-leds@vger.kernel.org 
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>  .../bindings/regulator/lm363x-regulator.txt        | 34 ++++++++++++++++++++++
>  1 file changed, 34 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
> 
> diff --git a/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt b/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
> new file mode 100644
> index 0000000..8f14df9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
> @@ -0,0 +1,34 @@
> +TI LMU LM363x regulator device tree bindings
> +
> +LM363x regulator driver supports LM3631 and LM3632.
> +LM3631 has five regulators and LM3632 supports three regulators.
> +
> +Required property:
> +  - compatible: "ti,lm363x-regulator"
> +
> +Optional properties:
> +  LM3632 has external enable pins for two LDOs.
> +  - ti,lcm-en1-gpio: A GPIO specifier for Vpos control pin.
> +  - ti,lcm-en2-gpio: A GPIO specifier for Vneg control pin.

Use "-gpios" please.

> +
> +Child nodes:
> +  LM3631
> +  - vboost
> +  - vcont
> +  - voref
> +  - vpos
> +  - vneg
> +
> +  LM3632
> +  - vboost
> +  - vpos
> +  - vneg
> +
> +  Optional properties of a child node:
> +  Each sub-node should contain the constraints and initialization.
> +  Please refer to [1].

What about the regulator names?

> +
> +Examples: Please refer to ti-lmu dt-bindings [2].
> +
> +[1] ../regulator/regulator.txt
> +[2] ../mfd/ti-lmu.txt
> -- 
> 1.9.1
> 

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

* Re: [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information
  2015-11-27 20:44     ` Rob Herring
@ 2015-11-27 22:07       ` Mark Brown
  0 siblings, 0 replies; 38+ messages in thread
From: Mark Brown @ 2015-11-27 22:07 UTC (permalink / raw)
  To: Rob Herring
  Cc: Milo Kim, lee.jones, j.anaszewski, devicetree, linux-leds, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 596 bytes --]

On Fri, Nov 27, 2015 at 02:44:50PM -0600, Rob Herring wrote:
> On Fri, Nov 27, 2015 at 12:37:50PM +0000, Mark Brown wrote:
> > On Thu, Nov 26, 2015 at 03:57:00PM +0900, Milo Kim wrote:

> > > This binding describes LM3631 and LM3632 regulator properties.

> > Please use subject lines matching the style for the subsystem.

> Which for bindings, my preference is "dt-bindings: ..." I assume you 
> meant to follow regulator style though. 

Given that maintainers are generally expected to review and apply
bindings themselves it seems sensible (and it's what we mostly seem to
do).

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 473 bytes --]

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

* Re: [PATCH v2 8/9] leds: add LM3633 driver
  2015-11-27 11:19   ` Jacek Anaszewski
@ 2015-11-28  8:28     ` Jacek Anaszewski
  2015-11-30  8:48     ` Kim, Milo
  1 sibling, 0 replies; 38+ messages in thread
From: Jacek Anaszewski @ 2015-11-28  8:28 UTC (permalink / raw)
  To: Milo Kim
  Cc: Jacek Anaszewski, robh+dt, lee.jones, broonie, devicetree,
	linux-leds, linux-kernel

On 11/27/2015 12:19 PM, Jacek Anaszewski wrote:
> Hi Milo,
>
> Thanks for the update. I have few comments below.
>
[...]
>> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led,
>> u32 imax)
>> +{
>> +    u8 max_current = lm3633_led_convert_current_to_index(imax);
>> +    const u8 max_brightness_table[] = {
>> +        [LMU_IMAX_5mA]  = 191,
>> +        [LMU_IMAX_6mA]  = 197,
>> +        [LMU_IMAX_7mA]  = 203,
>> +        [LMU_IMAX_8mA]  = 208,
>> +        [LMU_IMAX_9mA]  = 212,
>> +        [LMU_IMAX_10mA] = 216,
>> +        [LMU_IMAX_11mA] = 219,
>> +        [LMU_IMAX_12mA] = 222,
>> +        [LMU_IMAX_13mA] = 225,
>> +        [LMU_IMAX_14mA] = 228,
>> +        [LMU_IMAX_15mA] = 230,
>> +        [LMU_IMAX_16mA] = 233,
>> +        [LMU_IMAX_17mA] = 235,
>> +        [LMU_IMAX_18mA] = 237,
>> +        [LMU_IMAX_19mA] = 239,
>> +        [LMU_IMAX_20mA] = 241,
>> +        [LMU_IMAX_21mA] = 242,
>> +        [LMU_IMAX_22mA] = 244,
>> +        [LMU_IMAX_23mA] = 246,
>> +        [LMU_IMAX_24mA] = 247,
>> +        [LMU_IMAX_25mA] = 249,
>> +        [LMU_IMAX_26mA] = 250,
>> +        [LMU_IMAX_27mA] = 251,
>> +        [LMU_IMAX_28mA] = 253,
>> +        [LMU_IMAX_29mA] = 254,
>> +        [LMU_IMAX_30mA] = 255,
>> +    };
>
> After analyzing the subject one more time I think that we need to
> change the approach regarding max brightness issue.
>
> At first - we shouldn't fix max current to max possible register value.
> Instead we should take led-max-microamp property and write its value
> to the [0x22 + bank offset] registers.

It was of course a mental shortcut. The value to write should be
calculated by transforming the formula given next to the register
documentation:

11111 = 29.8 mA
0.8mA steps, FS = 5mA + code * 0.8 mA

code is here the [0x22 + bank_offset] register value.

Effectively, the formula to calculate the register value basing
on led-max-microamp value should be:

FS = led-max-microamp - 5000 / 800

E.g. for 20.2 mA:

FS = (20200 - 5000) / 800 = 19

for 29.8 mA:

FS = (29800 - 5000) / 800 = 31

for 5 mA:

FS = (5000 - 5000) / 800 = 0

 From the above min, max and step values for led-max-microamp
are 5000, 29800 and 800 respectively. Please correct DT documentation
accordingly.

> With this approach whole 0-255 range of brightness levels will be
> valid for the driver.
>
> In effect all LMU_IMAX* enums seem to be not needed.


-- 
Best Regards,
Jacek Anaszewski

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

* Re: [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED binding information
  2015-11-27 11:19   ` Jacek Anaszewski
@ 2015-11-30  8:19     ` Kim, Milo
  2015-11-30 12:26       ` Jacek Anaszewski
  0 siblings, 1 reply; 38+ messages in thread
From: Kim, Milo @ 2015-11-30  8:19 UTC (permalink / raw)
  To: Jacek Anaszewski
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

Hi Jacek,

On 11/27/2015 8:19 PM, Jacek Anaszewski wrote:
> Hi Milo,
>
> On 11/26/2015 07:56 AM, Milo Kim wrote:
>> LM3633 LED device is one of TI LMU device list.
>>
>> Cc: Rob Herring <robh+dt@kernel.org>
>> Cc: devicetree@vger.kernel.org
>> Cc: Lee Jones <lee.jones@linaro.org>
>> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
>> Cc: Mark Brown <broonie@kernel.org>
>> Cc: linux-leds@vger.kernel.org
>> Cc: linux-kernel@vger.kernel.org
>> Signed-off-by: Milo Kim <milo.kim@ti.com>
>> ---
>>    .../devicetree/bindings/leds/leds-lm3633.txt       | 24 ++++++++++++++++++++++
>>    1 file changed, 24 insertions(+)
>>    create mode 100644 Documentation/devicetree/bindings/leds/leds-lm3633.txt
>>
>> diff --git a/Documentation/devicetree/bindings/leds/leds-lm3633.txt b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
>> new file mode 100644
>> index 0000000..a553894
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
>> @@ -0,0 +1,24 @@
>> +TI LMU LM3633 LED device tree bindings
>> +
>> +Required properties:
>> +  - compatible: "ti,lm3633-leds"
>> +
>> +Child nodes:
>> +  Each node matches with LED control bank.
>> +  Please refer to the datasheet [1].
>
> leds/common.txt documentation says that child nodes represent discrete
> LED elements.
>
>> +  Required properties of a child node:
>> +  - led-sources: List of enabled channels from 0 to 5.
>> +                 Please refer to LED binding [2].
>
> led-sources property should contain always one element -
> a control bank identifier that the iout is to be associated with.
> For example, if there are three LEDs and they should be controlled
> with control bank A, then the bindings should look as follows
> (assuming that control bank identifiers start from 0 [A:0, B:1,
> C:2, etc. - it has to be also explicitly stated in the documentation]:

My understanding is 'led-sources' means output channel rather than 
control bank. Output channel is visible and intuitive - just output LED.
On the other hand, control banks works inside the silicon, LM3633.
I'm not sure other LED devices have control bank or not, but output 
channel is common concept.

>
> lvled1 {
> 	led-sources = <2>;
> 	led-max-microamp = <1000>;
> }
>
> lvled2 {
> 	led-sources = <2>;
> 	led-max-microamp = <29000>;
> }
>
> lvled3 {
> 	led-sources = <2>;
> 	led-max-microamp = <7000>;
> }

For this reason, I don't understand this LED configuration - one output 
channel with multiple LED devices. LED child node should be matched with 
led class device.
If three output channels are controlled by one control bank, then it 
should be represented as below.

lvled-group-A {
	led-sources = <0>, <1>, <2>;
	led-max-microamp = <some value>;
}

Please let me know if I misunderstand.

Best regards,
Milo

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

* Re: [PATCH v2 8/9] leds: add LM3633 driver
  2015-11-27 11:19   ` Jacek Anaszewski
  2015-11-28  8:28     ` Jacek Anaszewski
@ 2015-11-30  8:48     ` Kim, Milo
  2015-11-30 12:26       ` Jacek Anaszewski
  1 sibling, 1 reply; 38+ messages in thread
From: Kim, Milo @ 2015-11-30  8:48 UTC (permalink / raw)
  To: Jacek Anaszewski
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

Hi Jacek,

Thanks for your detailed feedback. Please refer to my comments below.

On 11/27/2015 8:19 PM, Jacek Anaszewski wrote:
> Hi Milo,
>
> Thanks for the update. I have few comments below.
>
>> diff --git a/drivers/leds/leds-lm3633.c b/drivers/leds/leds-lm3633.c
>> new file mode 100644
>> index 0000000..9c391ca
>> --- /dev/null
>> +++ b/drivers/leds/leds-lm3633.c
>> @@ -0,0 +1,840 @@
>> +/*
>> + * 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/bitops.h>
>> +#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_MAX_PWM				255
>> +#define LM3633_MIN_CURRENT			5000
>> +#define LM3633_MAX_CURRENT			30000
>> +#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
>> +#define LM3633_IMAX_OFFSET			6
>> +
>> +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 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
>
> Could you clarify what "string" means here?
>
>> + * @num_leds:		Number of LED strings
>
> and here?

Oops! I forgot to replace this term with 'output channel'. Thanks for 
catching this.

>> +static u8 lm3633_convert_time_to_index(unsigned int msec)
>> +{
>> +	u8 idx, offset;
>> +
>> +	/*
>> +	 * Find an approximate index
>
> I think that we shouldn't approximate but clamp the msec
> to the nearest possible device setting.
>
>> +	 *
>> +	 *      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 an approximate index by looking up the table */
>
> Similarly here. So you should have an array of the possible msec
> values and iterate through it to look for the nearest one.

Driver uses 'ramp_table[]' for this purpose. Could you describe it in 
more details?

>> +
>> +static int lm3633_led_brightness_set(struct led_classdev *cdev,
>> +				     enum led_brightness brightness)
>> +{
>> +	struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
>> +						  cdev);
>> +	struct ti_lmu_led_chip *chip = lmu_led->chip;
>> +	struct regmap *regmap = chip->lmu->regmap;
>> +	u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
>> +	int ret;
>> +
>> +	mutex_lock(&chip->lock);
>> +
>> +	ret = regmap_write(regmap, reg, brightness);
>> +	if (ret) {
>> +		mutex_unlock(&chip->lock);
>> +		return ret;
>> +	}
>> +
>> +	if (brightness == 0)
>> +		lm3633_led_disable_bank(lmu_led);
>
> This should be checked at first, as I suppose that disabling
> a bank suffices to turn the LED off.

Well, it's not a big deal when turn off the LED. However, our silicon 
designer recommended brightness update should be done prior to enabling 
LED bank. Let me share some block diagram.

[I2C logic] - [brightness control] - [control bank] - [output channel]

If control bank is enabled first, then previous brightness value in 
register can be used instead of new updated brightness. So brightness 
update should be done first.

>> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led, u32 imax)
>> +{
>> +	u8 max_current = lm3633_led_convert_current_to_index(imax);
>> +	const u8 max_brightness_table[] = {
>> +		[LMU_IMAX_5mA]  = 191,
>> +		[LMU_IMAX_6mA]  = 197,
>> +		[LMU_IMAX_7mA]  = 203,
>> +		[LMU_IMAX_8mA]  = 208,
>> +		[LMU_IMAX_9mA]  = 212,
>> +		[LMU_IMAX_10mA] = 216,
>> +		[LMU_IMAX_11mA] = 219,
>> +		[LMU_IMAX_12mA] = 222,
>> +		[LMU_IMAX_13mA] = 225,
>> +		[LMU_IMAX_14mA] = 228,
>> +		[LMU_IMAX_15mA] = 230,
>> +		[LMU_IMAX_16mA] = 233,
>> +		[LMU_IMAX_17mA] = 235,
>> +		[LMU_IMAX_18mA] = 237,
>> +		[LMU_IMAX_19mA] = 239,
>> +		[LMU_IMAX_20mA] = 241,
>> +		[LMU_IMAX_21mA] = 242,
>> +		[LMU_IMAX_22mA] = 244,
>> +		[LMU_IMAX_23mA] = 246,
>> +		[LMU_IMAX_24mA] = 247,
>> +		[LMU_IMAX_25mA] = 249,
>> +		[LMU_IMAX_26mA] = 250,
>> +		[LMU_IMAX_27mA] = 251,
>> +		[LMU_IMAX_28mA] = 253,
>> +		[LMU_IMAX_29mA] = 254,
>> +		[LMU_IMAX_30mA] = 255,
>> +	};
>
> After analyzing the subject one more time I think that we need to
> change the approach regarding max brightness issue.
>
> At first - we shouldn't fix max current to max possible register value.
> Instead we should take led-max-microamp property and write its value
> to the [0x22 + bank offset] registers.
>
> With this approach whole 0-255 range of brightness levels will be
> valid for the driver.
>
> In effect all LMU_IMAX* enums seem to be not needed.

Good to hear that, it's the most simplest work for the device ;)
Let me update the driver. Thanks!

Best regards,
Milo

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

* Re: [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED binding information
  2015-11-30  8:19     ` Kim, Milo
@ 2015-11-30 12:26       ` Jacek Anaszewski
  2015-12-07  8:46         ` Kim, Milo
  0 siblings, 1 reply; 38+ messages in thread
From: Jacek Anaszewski @ 2015-11-30 12:26 UTC (permalink / raw)
  To: Kim, Milo
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

Hi Milo,

On 11/30/2015 09:19 AM, Kim, Milo wrote:
> Hi Jacek,
>
> On 11/27/2015 8:19 PM, Jacek Anaszewski wrote:
>> Hi Milo,
>>
>> On 11/26/2015 07:56 AM, Milo Kim wrote:
>>> LM3633 LED device is one of TI LMU device list.
>>>
>>> Cc: Rob Herring <robh+dt@kernel.org>
>>> Cc: devicetree@vger.kernel.org
>>> Cc: Lee Jones <lee.jones@linaro.org>
>>> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
>>> Cc: Mark Brown <broonie@kernel.org>
>>> Cc: linux-leds@vger.kernel.org
>>> Cc: linux-kernel@vger.kernel.org
>>> Signed-off-by: Milo Kim <milo.kim@ti.com>
>>> ---
>>>    .../devicetree/bindings/leds/leds-lm3633.txt       | 24
>>> ++++++++++++++++++++++
>>>    1 file changed, 24 insertions(+)
>>>    create mode 100644
>>> Documentation/devicetree/bindings/leds/leds-lm3633.txt
>>>
>>> diff --git a/Documentation/devicetree/bindings/leds/leds-lm3633.txt
>>> b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
>>> new file mode 100644
>>> index 0000000..a553894
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/leds/leds-lm3633.txt
>>> @@ -0,0 +1,24 @@
>>> +TI LMU LM3633 LED device tree bindings
>>> +
>>> +Required properties:
>>> +  - compatible: "ti,lm3633-leds"
>>> +
>>> +Child nodes:
>>> +  Each node matches with LED control bank.
>>> +  Please refer to the datasheet [1].
>>
>> leds/common.txt documentation says that child nodes represent discrete
>> LED elements.
>>
>>> +  Required properties of a child node:
>>> +  - led-sources: List of enabled channels from 0 to 5.
>>> +                 Please refer to LED binding [2].
>>
>> led-sources property should contain always one element -

Here I should have added that this would have been the correct approach
for this device. For led flash controllers where we have one LED
connected to two outputs there are two element in the led-sources array.

>> a control bank identifier that the iout is to be associated with.
>> For example, if there are three LEDs and they should be controlled
>> with control bank A, then the bindings should look as follows
>> (assuming that control bank identifiers start from 0 [A:0, B:1,
>> C:2, etc. - it has to be also explicitly stated in the documentation]:
>
> My understanding is 'led-sources' means output channel rather than
> control bank. Output channel is visible and intuitive - just output LED.
> On the other hand, control banks works inside the silicon, LM3633.
> I'm not sure other LED devices have control bank or not, but output
> channel is common concept.

There is no "channel" notion present in the leds/common.txt
documentation.

led-sources property is documented as "list of device current outputs
the LED is connected to." In case of your device the LVLEDn pins can be
configured so that they are routed to the common current output.

led-sources property has been initially designed for representing
one-LED-to many-iouts relation, but it can be equally well exploited for
representing many-LEDs-to-one-iout relation, if used in conjunction
with DT child nodes.

>>
>> lvled1 {
>>     led-sources = <2>;
>>     led-max-microamp = <1000>;
>> }
>>
>> lvled2 {
>>     led-sources = <2>;
>>     led-max-microamp = <29000>;
>> }
>>
>> lvled3 {
>>     led-sources = <2>;
>>     led-max-microamp = <7000>;
>> }
>
> For this reason, I don't understand this LED configuration - one output
> channel with multiple LED devices. LED child node should be matched with
> led class device.

I can't find any claim saying that child node should represent
LED class device. Instead, there is a statement saying that
discrete LED components "are represented by child nodes of the parent
LED device binding". What is more e.g. leds-bcm6328.txt bindings don't
expose LED class device for the LEDs marked as hardware controlled.

Device tree describes hardware, and above bindings properly reflect
hardware arrangement - there are three LEDs and each of them
is connected to the same current output. Please note that
common/leds.txt documentation doesn't make any reservation saying
that current output necessarily reflects a hardware pin.

In case of LM3633 the real current outputs are banks and multiplexing
that occurs between banks and LVLEDn pins can be conveniently expressed
with the bindings I proposed above.

> If three output channels are controlled by one control bank, then it
> should be represented as below.
>
> lvled-group-A {
>      led-sources = <0>, <1>, <2>;
>      led-max-microamp = <some value>;
> }
>
> Please let me know if I misunderstand.

You silently assumed some definition of a "channel". led-sources means
in fact current sources, and when device is configured so that three
output pins are routed to the same current source, then in fact
they share common current source.

-- 
Best Regards,
Jacek Anaszewski

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

* Re: [PATCH v2 8/9] leds: add LM3633 driver
  2015-11-30  8:48     ` Kim, Milo
@ 2015-11-30 12:26       ` Jacek Anaszewski
  0 siblings, 0 replies; 38+ messages in thread
From: Jacek Anaszewski @ 2015-11-30 12:26 UTC (permalink / raw)
  To: Kim, Milo
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

On 11/30/2015 09:48 AM, Kim, Milo wrote:
> Hi Jacek,
>
> Thanks for your detailed feedback. Please refer to my comments below.
>
> On 11/27/2015 8:19 PM, Jacek Anaszewski wrote:
>> Hi Milo,
>>
>> Thanks for the update. I have few comments below.
>>
>>> diff --git a/drivers/leds/leds-lm3633.c b/drivers/leds/leds-lm3633.c
>>> new file mode 100644
>>> index 0000000..9c391ca
>>> --- /dev/null
>>> +++ b/drivers/leds/leds-lm3633.c
>>> @@ -0,0 +1,840 @@
>>> +/*
>>> + * 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/bitops.h>
>>> +#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_MAX_PWM                255
>>> +#define LM3633_MIN_CURRENT            5000
>>> +#define LM3633_MAX_CURRENT            30000
>>> +#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
>>> +#define LM3633_IMAX_OFFSET            6
>>> +
>>> +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 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
>>
>> Could you clarify what "string" means here?
>>
>>> + * @num_leds:        Number of LED strings
>>
>> and here?
>
> Oops! I forgot to replace this term with 'output channel'. Thanks for
> catching this.
>
>>> +static u8 lm3633_convert_time_to_index(unsigned int msec)
>>> +{
>>> +    u8 idx, offset;
>>> +
>>> +    /*
>>> +     * Find an approximate index
>>
>> I think that we shouldn't approximate but clamp the msec
>> to the nearest possible device setting.
>>
>>> +     *
>>> +     *      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 an approximate index by looking up the table */
>>
>> Similarly here. So you should have an array of the possible msec
>> values and iterate through it to look for the nearest one.
>
> Driver uses 'ramp_table[]' for this purpose. Could you describe it in
> more details?

Right, but why the values don't match the ones from the documentation?
e.g.: 2, 262, 524, 1049, 2097 etc.

And regarding sysfs attributes - they should be readable and return
the aligned value.

>>> +
>>> +static int lm3633_led_brightness_set(struct led_classdev *cdev,
>>> +                     enum led_brightness brightness)
>>> +{
>>> +    struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
>>> +                          cdev);
>>> +    struct ti_lmu_led_chip *chip = lmu_led->chip;
>>> +    struct regmap *regmap = chip->lmu->regmap;
>>> +    u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
>>> +    int ret;
>>> +
>>> +    mutex_lock(&chip->lock);
>>> +
>>> +    ret = regmap_write(regmap, reg, brightness);
>>> +    if (ret) {
>>> +        mutex_unlock(&chip->lock);
>>> +        return ret;
>>> +    }
>>> +
>>> +    if (brightness == 0)
>>> +        lm3633_led_disable_bank(lmu_led);
>>
>> This should be checked at first, as I suppose that disabling
>> a bank suffices to turn the LED off.
>
> Well, it's not a big deal when turn off the LED. However, our silicon
> designer recommended brightness update should be done prior to enabling
> LED bank. Let me share some block diagram.
>
> [I2C logic] - [brightness control] - [control bank] - [output channel]
>
> If control bank is enabled first, then previous brightness value in
> register can be used instead of new updated brightness. So brightness
> update should be done first.

So, call "regmap_write(regmap, reg, brightness)" only if brightness > 0.

How about below (error handling skipped):

if (brightness > 0) {
         regmap_write(regmap, reg, brightness);
	lm3633_led_enable_bank(lmu_led);
} else {
	lm3633_led_disable_bank(lmu_led);
}


>
>>> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led
>>> *lmu_led, u32 imax)
>>> +{
>>> +    u8 max_current = lm3633_led_convert_current_to_index(imax);
>>> +    const u8 max_brightness_table[] = {
>>> +        [LMU_IMAX_5mA]  = 191,
>>> +        [LMU_IMAX_6mA]  = 197,
>>> +        [LMU_IMAX_7mA]  = 203,
>>> +        [LMU_IMAX_8mA]  = 208,
>>> +        [LMU_IMAX_9mA]  = 212,
>>> +        [LMU_IMAX_10mA] = 216,
>>> +        [LMU_IMAX_11mA] = 219,
>>> +        [LMU_IMAX_12mA] = 222,
>>> +        [LMU_IMAX_13mA] = 225,
>>> +        [LMU_IMAX_14mA] = 228,
>>> +        [LMU_IMAX_15mA] = 230,
>>> +        [LMU_IMAX_16mA] = 233,
>>> +        [LMU_IMAX_17mA] = 235,
>>> +        [LMU_IMAX_18mA] = 237,
>>> +        [LMU_IMAX_19mA] = 239,
>>> +        [LMU_IMAX_20mA] = 241,
>>> +        [LMU_IMAX_21mA] = 242,
>>> +        [LMU_IMAX_22mA] = 244,
>>> +        [LMU_IMAX_23mA] = 246,
>>> +        [LMU_IMAX_24mA] = 247,
>>> +        [LMU_IMAX_25mA] = 249,
>>> +        [LMU_IMAX_26mA] = 250,
>>> +        [LMU_IMAX_27mA] = 251,
>>> +        [LMU_IMAX_28mA] = 253,
>>> +        [LMU_IMAX_29mA] = 254,
>>> +        [LMU_IMAX_30mA] = 255,
>>> +    };
>>
>> After analyzing the subject one more time I think that we need to
>> change the approach regarding max brightness issue.
>>
>> At first - we shouldn't fix max current to max possible register value.
>> Instead we should take led-max-microamp property and write its value
>> to the [0x22 + bank offset] registers.
>>
>> With this approach whole 0-255 range of brightness levels will be
>> valid for the driver.
>>
>> In effect all LMU_IMAX* enums seem to be not needed.
>
> Good to hear that, it's the most simplest work for the device ;)
> Let me update the driver. Thanks!
>
> Best regards,
> Milo
>
>


-- 
Best Regards,
Jacek Anaszewski

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

* Re: [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED binding information
  2015-11-30 12:26       ` Jacek Anaszewski
@ 2015-12-07  8:46         ` Kim, Milo
  2015-12-07 10:50           ` Jacek Anaszewski
  0 siblings, 1 reply; 38+ messages in thread
From: Kim, Milo @ 2015-12-07  8:46 UTC (permalink / raw)
  To: Jacek Anaszewski
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

Hi Jacek,

On 11/30/2015 9:26 PM, Jacek Anaszewski wrote:
> In case of LM3633 the real current outputs are banks and multiplexing
> that occurs between banks and LVLEDn pins can be conveniently expressed
> with the bindings I proposed above.
>
>> >If three output channels are controlled by one control bank, then it
>> >should be represented as below.
>> >
>> >lvled-group-A {
>> >      led-sources = <0>, <1>, <2>;
>> >      led-max-microamp = <some value>;
>> >}
>> >
>> >Please let me know if I misunderstand.
> You silently assumed some definition of a "channel". led-sources means
> in fact current sources, and when device is configured so that three
> output pins are routed to the same current source, then in fact
> they share common current source.

Thanks for comments. I've modified 'led-sources' based on your feedback.
Could you check updated dt-bindings below?

* Updates
- led-sources: List of current sources from 0 to 5.
- ti,led-outputs: Output channel specifier

The 'ti,led-outputs' property is exactly matched with datasheet 
description, so it's easy to understand which current source is used for 
output channels.
---
TI LMU LM3633 LED device tree bindings

Required properties:
   - compatible: "ti,lm3633-leds"

Child nodes:
   Each node matches with LED control bank.
   Please refer to the datasheet [1].

   Required properties of a child node:
   - led-sources: List of current sources from 0 to 5.
                  0: control bank C for output LED 1
                     control bank C for output LED 1 and 2
                     control bank C for output LED 1, 2 and 3
                  1: control bank D for output LED 2
                  2: control bank E for output LED 3
                  3: control bank F for output LED 4
                     control bank F for output LED 4 and 5
                     control bank F for output LED 4, 5 and 6
                  4: control bank G for output LED 5
                  5: control bank H for output LED 6
                  Please refer to LED binding [2].

   - ti,led-outputs: Output channel specifier from 0 to 5.
                     0: LED 1
                     1: LED 2
                     2: LED 3
                     3: LED 4
                     4: LED 5
                     5: LED 6

   Optional properties of a child node:
   - label: LED channel identification. Please refer to LED binding [2].
   - led-max-microamp: Max current setting. Type is <u32>.
                       Unit is microampere. Range is from 5000 to 29800.
                       Step is 800. Please refer to LED binding [2].

Examples:

LED 1 is assigned for status LED. LED 4,5 and 6 are used for RGB 
indicator. RGB channels are controlled by bank F internally.

leds {
	compatible = "ti,lm3633-leds";

	status {
		led-sources = <0>;
		led-max-microamp = <5000>;
		ti,led-outputs = <0>;
	};

	rgb {
		led-sources = <3>;
		led-max-microamp = <6600>;
		ti,led-outputs = <3 4 5>;
	};
};

LED 2 is power LED. LED 5 is notification LED.

leds {
	compatible = "ti,lm3633-leds";

	lvled2 {
		label = "power";
		led-sources = <1>;
		led-max-microamp = <29000>;
		ti,led-outputs = <1>;
	};

	lvled5 {
		label = "noti";
		led-sources = <4>;
		led-max-microamp = <6600>;
		ti,led-outputs = <4>;
	};
};


[1] http://www.ti.com/product/LM3633/datasheet
[2] ../leds/common.txt

Best regards,
Milo

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

* Re: [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED binding information
  2015-12-07  8:46         ` Kim, Milo
@ 2015-12-07 10:50           ` Jacek Anaszewski
  0 siblings, 0 replies; 38+ messages in thread
From: Jacek Anaszewski @ 2015-12-07 10:50 UTC (permalink / raw)
  To: Kim, Milo
  Cc: robh+dt, lee.jones, broonie, devicetree, linux-leds, linux-kernel

Hi Milo,

On 12/07/2015 09:46 AM, Kim, Milo wrote:
> Hi Jacek,
>
> On 11/30/2015 9:26 PM, Jacek Anaszewski wrote:
>> In case of LM3633 the real current outputs are banks and multiplexing
>> that occurs between banks and LVLEDn pins can be conveniently expressed
>> with the bindings I proposed above.
>>
>>> >If three output channels are controlled by one control bank, then it
>>> >should be represented as below.
>>> >
>>> >lvled-group-A {
>>> >      led-sources = <0>, <1>, <2>;
>>> >      led-max-microamp = <some value>;
>>> >}
>>> >
>>> >Please let me know if I misunderstand.
>> You silently assumed some definition of a "channel". led-sources means
>> in fact current sources, and when device is configured so that three
>> output pins are routed to the same current source, then in fact
>> they share common current source.
>
> Thanks for comments. I've modified 'led-sources' based on your feedback.
> Could you check updated dt-bindings below?
>
> * Updates
> - led-sources: List of current sources from 0 to 5.
> - ti,led-outputs: Output channel specifier
>
> The 'ti,led-outputs' property is exactly matched with datasheet
> description, so it's easy to understand which current source is used for
> output channels.
> ---
> TI LMU LM3633 LED device tree bindings
>
> Required properties:
>    - compatible: "ti,lm3633-leds"
>
> Child nodes:
>    Each node matches with LED control bank.
>    Please refer to the datasheet [1].
>
>    Required properties of a child node:
>    - led-sources: List of current sources from 0 to 5.
>                   0: control bank C for output LED 1
>                      control bank C for output LED 1 and 2
>                      control bank C for output LED 1, 2 and 3
>                   1: control bank D for output LED 2
>                   2: control bank E for output LED 3
>                   3: control bank F for output LED 4
>                      control bank F for output LED 4 and 5
>                      control bank F for output LED 4, 5 and 6
>                   4: control bank G for output LED 5
>                   5: control bank H for output LED 6
>                   Please refer to LED binding [2].
>
>    - ti,led-outputs: Output channel specifier from 0 to 5.
>                      0: LED 1
>                      1: LED 2
>                      2: LED 3
>                      3: LED 4
>                      4: LED 5
>                      5: LED 6

This property is redundant. LED class driver can infer this
information by analyzing the led-sources property from each
child node.

Below arrangement:

led1 {
	led-sources = <0>;
};

led2 {
	led-sources = <0>;
};

led3 {
	led-sources = <2>;
};

led4 {
	led-sources = <3>;
};

led5 {
	led-sources = <3>;
};

led6 {
	led-sources = <3>;
};

suffices to discover the following:

BANKC controls LVLED1, LVLED2
BANKE controls LVLED3
BANKF controls LVLED4, LVLED5, LVLED6

>    Optional properties of a child node:
>    - label: LED channel identification. Please refer to LED binding [2].
>    - led-max-microamp: Max current setting. Type is <u32>.
>                        Unit is microampere. Range is from 5000 to 29800.
>                        Step is 800. Please refer to LED binding [2].
>
> Examples:
>
> LED 1 is assigned for status LED. LED 4,5 and 6 are used for RGB
> indicator. RGB channels are controlled by bank F internally.
>
> leds {
>      compatible = "ti,lm3633-leds";
>
>      status {
>          led-sources = <0>;
>          led-max-microamp = <5000>;
>          ti,led-outputs = <0>;
>      };
>
>      rgb {
>          led-sources = <3>;
>          led-max-microamp = <6600>;
>          ti,led-outputs = <3 4 5>;
>      };
> };
>
> LED 2 is power LED. LED 5 is notification LED.
>
> leds {
>      compatible = "ti,lm3633-leds";
>
>      lvled2 {
>          label = "power";
>          led-sources = <1>;
>          led-max-microamp = <29000>;
>          ti,led-outputs = <1>;
>      };
>
>      lvled5 {
>          label = "noti";
>          led-sources = <4>;
>          led-max-microamp = <6600>;
>          ti,led-outputs = <4>;
>      };
> };
>
>
> [1] http://www.ti.com/product/LM3633/datasheet
> [2] ../leds/common.txt
>
> Best regards,
> Milo
>
>


-- 
Best Regards,
Jacek Anaszewski

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

* Re: [PATCH v2 0/9] Support TI LMU devices
  2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
                   ` (8 preceding siblings ...)
  2015-11-26  6:57 ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
@ 2016-01-06  7:20 ` Milo Kim
  9 siblings, 0 replies; 38+ messages in thread
From: Milo Kim @ 2016-01-06  7:20 UTC (permalink / raw)
  To: lee.jones
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

Hi Lee,

On 11/26/2015 03:56 PM, Milo Kim wrote:
> TI Lighting Management Unit drivers support lighting devices below.
>
>           Enable pin  Backlight  HW fault monitoring  LEDs   Regulators
>           ----------  ---------  -------------------  ----  ------------
> LM3532       o           o               x            x         x
> LM3631       o           o               x            x    5 regulators
> LM3632       o           o               x            x    3 regulators
> LM3633       o           o               o            o         x
> LM3695       o           o               x            x         x
> LM3697       o           o               o            x         x
>
> This patch-set consists of several parts below.
>
>    DT bindings        : Binding information for each module
>    LMU MFD            : Device registration and HW enable pin control
>    LMU fault monitor  : HW fault monitoring for open and short circuit
>    LMU backlight      : Consolidated LMU backlight driver
>    LM3633 LED         : LED subsystem and dimming pattern generation
>                         supported
>    LM363X regulator   : LM3631 and LM3632 regulator driver for the
>                         display bias
>
> Updates from v1
> ---------------
>    * DT bindings
>      mfd       : Describe complete DT properties.
>      backlight : Move backlight properties into leds/backlight/.
>                  Use common LED properties like 'led-sources' and 'label'.
>      hwmon     : LMU fault monitoring driver is not HWMON any more.
>                  So related properties are moved into 'ti-lmu' binding.
>      leds      : Use LED common properties like 'led-sources' and 'label'.
>
>    * MFD
>      Remove LMU helpers for I2C register access. Each driver uses regmap
>      helpers instead.
>
>    * LMU fault monitoring driver
>      In v1, it was HWMON driver but HWMON subsystem maintainer suggested
>      moving it into MFD because it has no sensor data like temperature or
>      voltage. Device attributes were replaced with debugfs files because
>      monitoring should be processed for debug purpose only.
>
>    * Backlight
>      Six separate driver code was consolidated.
>      Driver control code is implemented in 'ti-lmu-backlight-core.c'.
>      Device specific data is defined in 'ti-lmu-backlight-data.c'.
>      194 lines are saved in v2. The text segment is decreased by removing
>      duplicate instructions.
>
>      Lines of code:
>        v1: 1420 (8 files)
>        v2: 1226 (3 files)
>
>      Size:
>        v1:
>        text  data  bss  filename
>       12202   720   40  drivers/video/backlight/built-in.o
>        v2:
>        text  data  bss  filename
>        6883   712   41  drivers/video/backlight/built-in.o
>
>    * LED
>      Use single device attribute for LED dimming operation.
>      Max brightness is determined by DT property, 'led-max-microamp'.
>      Remove brightness workqueue.
>
>    * Regulator
>      Use 'of_match' in regulator_desc instead of calling of_regulator_match.
>      Remove unnecessary OF device ID because MFD core registers a platform
>      device based on the compatible string.
>
> Milo Kim (9):
>    Documentation: dt-bindings: mfd: add TI LMU device binding information
>    Documentation: dt-bindings: leds: backlight: add TI LMU backlight
>      binding information
>    Documentation: dt-bindings: leds: add LM3633 LED binding information
>    Documentation: dt-bindings: regulator: add LM363x regulator binding
>      information
>    mfd: add TI LMU driver
>    mfd: add TI LMU hardware fault monitoring driver
>    backlight: add TI LMU backlight driver

For patch 5,6 and 7,
I'd like to get some feedback. Thanks!

>    leds: add LM3633 driver
>    regulator: add LM363X driver
>
>   .../ABI/testing/debugfs-ti-lmu-fault-monitor       |  32 +
>   Documentation/ABI/testing/sysfs-class-led-lm3633   |  97 +++
>   .../bindings/leds/backlight/ti-lmu-backlight.txt   |  65 ++
>   .../devicetree/bindings/leds/leds-lm3633.txt       |  24 +
>   Documentation/devicetree/bindings/mfd/ti-lmu.txt   | 243 ++++++
>   .../bindings/regulator/lm363x-regulator.txt        |  34 +
>   drivers/leds/Kconfig                               |  10 +
>   drivers/leds/Makefile                              |   1 +
>   drivers/leds/leds-lm3633.c                         | 840 +++++++++++++++++++++
>   drivers/mfd/Kconfig                                |  22 +
>   drivers/mfd/Makefile                               |   3 +
>   drivers/mfd/ti-lmu-fault-monitor.c                 | 405 ++++++++++
>   drivers/mfd/ti-lmu.c                               | 259 +++++++
>   drivers/regulator/Kconfig                          |   9 +
>   drivers/regulator/Makefile                         |   1 +
>   drivers/regulator/lm363x-regulator.c               | 309 ++++++++
>   drivers/video/backlight/Kconfig                    |   7 +
>   drivers/video/backlight/Makefile                   |   3 +
>   drivers/video/backlight/ti-lmu-backlight-core.c    | 649 ++++++++++++++++
>   drivers/video/backlight/ti-lmu-backlight-data.c    | 287 +++++++
>   include/linux/mfd/ti-lmu-backlight.h               | 290 +++++++
>   include/linux/mfd/ti-lmu-register.h                | 280 +++++++
>   include/linux/mfd/ti-lmu.h                         |  87 +++
>   23 files changed, 3957 insertions(+)
>   create mode 100644 Documentation/ABI/testing/debugfs-ti-lmu-fault-monitor
>   create mode 100644 Documentation/ABI/testing/sysfs-class-led-lm3633
>   create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
>   create mode 100644 Documentation/devicetree/bindings/leds/leds-lm3633.txt
>   create mode 100644 Documentation/devicetree/bindings/mfd/ti-lmu.txt
>   create mode 100644 Documentation/devicetree/bindings/regulator/lm363x-regulator.txt
>   create mode 100644 drivers/leds/leds-lm3633.c
>   create mode 100644 drivers/mfd/ti-lmu-fault-monitor.c
>   create mode 100644 drivers/mfd/ti-lmu.c
>   create mode 100644 drivers/regulator/lm363x-regulator.c
>   create mode 100644 drivers/video/backlight/ti-lmu-backlight-core.c
>   create mode 100644 drivers/video/backlight/ti-lmu-backlight-data.c
>   create mode 100644 include/linux/mfd/ti-lmu-backlight.h
>   create mode 100644 include/linux/mfd/ti-lmu-register.h
>   create mode 100644 include/linux/mfd/ti-lmu.h
>

Best regards,
Milo

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

* Re: [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information
  2015-11-26  6:56 ` [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information Milo Kim
  2015-11-27 20:55   ` Rob Herring
@ 2016-01-11  9:46   ` Lee Jones
  1 sibling, 0 replies; 38+ messages in thread
From: Lee Jones @ 2016-01-11  9:46 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On Thu, 26 Nov 2015, Milo Kim wrote:

> This patch describes overall binding for TI LMU MFD devices.
> 
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: Lee Jones <lee.jones@linaro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: linux-leds@vger.kernel.org 
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>  Documentation/devicetree/bindings/mfd/ti-lmu.txt | 243 +++++++++++++++++++++++
>  1 file changed, 243 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mfd/ti-lmu.txt

Acked-by: Lee Jones <lee.jones@linaro.org>

> diff --git a/Documentation/devicetree/bindings/mfd/ti-lmu.txt b/Documentation/devicetree/bindings/mfd/ti-lmu.txt
> new file mode 100644
> index 0000000..c885cf8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/ti-lmu.txt
> @@ -0,0 +1,243 @@
> +TI LMU (Lighting Management Unit) device tree bindings
> +
> +TI LMU driver supports lighting devices below.
> +
> +   Name                  Child nodes
> +  ------      ---------------------------------
> +  LM3532       Backlight
> +  LM3631       Backlight and regulator
> +  LM3632       Backlight and regulator
> +  LM3633       Backlight, LED and fault monitor
> +  LM3695       Backlight
> +  LM3697       Backlight and fault monitor
> +
> +Required properties:
> +  - compatible: Should be one of:
> +                "ti,lm3532"
> +                "ti,lm3631"
> +                "ti,lm3632"
> +                "ti,lm3633"
> +                "ti,lm3695"
> +                "ti,lm3697"
> +  - reg: I2C slave address.
> +         0x11 for LM3632
> +         0x29 for LM3631
> +         0x36 for LM3633, LM3697
> +         0x38 for LM3532
> +         0x63 for LM3695
> +
> +Optional property:
> +  - enable-gpios: A GPIO specifier for hardware enable pin.
> +
> +Required node:
> +  - backlight: All LMU devices have backlight child nodes.
> +               For the properties, please refer to [1].
> +
> +Optional nodes:
> +  - fault-monitor: Hardware fault monitoring driver for LM3633 and LM3697.
> +    Required properties:
> +      - compatible: Should be one of:
> +                    "ti,lm3633-fault-monitor"
> +                    "ti,lm3697-fault-monitor"
> +  - leds: LED properties for LM3633. Please refer to [2].
> +  - regulators: Regulator properties for LM3631 and LM3632.
> +                Please refer to [3].
> +
> +[1] ../leds/backlight/ti-lmu-backlight.txt
> +[2] ../leds/leds-lm3633.txt
> +[3] ../regulator/lm363x-regulator.txt
> +
> +lm3532@38 {
> +	compatible = "ti,lm3532";
> +	reg = <0x38>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3532-backlight";
> +
> +		lcd {
> +			led-sources = <0 1 2>;
> +			ramp-up-msec = <30>;
> +			ramp-down-msec = <0>;
> +		};
> +	};
> +};
> +
> +lm3631@29 {
> +	compatible = "ti,lm3631";
> +	reg = <0x29>;
> +
> +	regulators {
> +		compatible = "ti,lm363x-regulator";
> +
> +		vboost {
> +			regulator-name = "lcd_boost";
> +			regulator-min-microvolt = <4500000>;
> +			regulator-max-microvolt = <6350000>;
> +			regulator-always-on;
> +		};
> +
> +		vcont {
> +			regulator-name = "lcd_vcont";
> +			regulator-min-microvolt = <1800000>;
> +			regulator-max-microvolt = <3300000>;
> +		};
> +
> +		voref {
> +			regulator-name = "lcd_voref";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +		};
> +
> +		vpos {
> +			regulator-name = "lcd_vpos";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +			regulator-boot-on;
> +		};
> +
> +		vneg {
> +			regulator-name = "lcd_vneg";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +			regulator-boot-on;
> +		};
> +	};
> +
> +	backlight {
> +		compatible = "ti,lm3631-backlight";
> +
> +		lcd_bl {
> +			led-sources = <0 1>;
> +			ramp-up-msec = <300>;
> +		};
> +	};
> +};
> +
> +lm3632@11 {
> +	compatible = "ti,lm3632";
> +	reg = <0x11>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>; /* PC2 */
> +
> +	regulators {
> +		compatible = "ti,lm363x-regulator";
> +
> +		ti,lcm-en1-gpio = <&pioC 0 GPIO_ACTIVE_HIGH>; /* PC0 */
> +		ti,lcm-en2-gpio = <&pioC 1 GPIO_ACTIVE_HIGH>; /* PC1 */
> +
> +		vboost {
> +			regulator-name = "lcd_boost";
> +			regulator-min-microvolt = <4500000>;
> +			regulator-max-microvolt = <6400000>;
> +			regulator-always-on;
> +		};
> +
> +		vpos {
> +			regulator-name = "lcd_vpos";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +		};
> +
> +		vneg {
> +			regulator-name = "lcd_vneg";
> +			regulator-min-microvolt = <4000000>;
> +			regulator-max-microvolt = <6000000>;
> +		};
> +	};
> +
> +	backlight {
> +		compatible = "ti,lm3632-backlight";
> +
> +		pwms = <&pwm0 0 10000 0>; /* pwm number, period, polarity */
> +		pwm-names = "lmu-backlight";
> +
> +		lcd {
> +			led-sources = <0 1>;
> +			pwm-period = <10000>;
> +		};
> +	};
> +};
> +
> +lm3633@36 {
> +	compatible = "ti,lm3633";
> +	reg = <0x36>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3633-backlight";
> +
> +		main {
> +			label = "main_lcd";
> +			led-sources = <1 2>;
> +			ramp-up-msec = <500>;
> +			ramp-down-msec = <500>;
> +		};
> +
> +		front {
> +			label = "front_lcd";
> +			led-sources = <0>;
> +			ramp-up-msec = <1000>;
> +			ramp-down-msec = <0>;
> +		};
> +	};
> +
> +	leds {
> +		compatible = "ti,lm3633-leds";
> +
> +		chan1 {
> +			label = "status";
> +			led-sources = <1>;
> +			led-max-microamp = <6000>;
> +		};
> +
> +		chan345 {
> +			label = "rgb";
> +			led-sources = <3 4 5>;
> +			led-max-microamp = <10000>;
> +		};
> +	};
> +
> +	fault-monitor {
> +		compatible = "ti,lm3633-fault-monitor";
> +	};
> +};
> +
> +lm3695@63 {
> +	compatible = "ti,lm3695";
> +	reg = <0x63>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3695-backlight";
> +
> +		lcd {
> +			label = "bl";
> +			led-sources = <0 1>;
> +		};
> +	};
> +};
> +
> +lm3697@36 {
> +	compatible = "ti,lm3697";
> +	reg = <0x36>;
> +
> +	enable-gpios = <&pioC 2 GPIO_ACTIVE_HIGH>;
> +
> +	backlight {
> +		compatible = "ti,lm3697-backlight";
> +
> +		lcd {
> +			led-sources = <0 1 2>;
> +			ramp-up-msec = <200>;
> +			ramp-down-msec = <200>;
> +		};
> +	};
> +
> +	fault-monitor {
> +		compatible = "ti,lm3697-fault-monitor";
> +	};
> +};

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH v2 2/9] Documentation: dt-bindings: leds: backlight: add TI LMU backlight binding information
  2015-11-26  6:56 ` [PATCH v2 2/9] Documentation: dt-bindings: leds: backlight: add TI LMU backlight " Milo Kim
@ 2016-01-11  9:53   ` Lee Jones
  0 siblings, 0 replies; 38+ messages in thread
From: Lee Jones @ 2016-01-11  9:53 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On Thu, 26 Nov 2015, Milo Kim wrote:

> LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697 use common dt-bindings
> for describing backlight device.
> 
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: Lee Jones <lee.jones@linaro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: linux-leds@vger.kernel.org 
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>  .../bindings/leds/backlight/ti-lmu-backlight.txt   | 65 ++++++++++++++++++++++
>  1 file changed, 65 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt

Any reason that Jingoo wasn't Cc'ed here?

> diff --git a/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
> new file mode 100644
> index 0000000..c2c35b2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
> @@ -0,0 +1,65 @@
> +TI LMU backlight device tree bindings
> +
> +Required property:
> +  - compatible: Should be one of:
> +                "ti,lm3532-backlight"
> +                "ti,lm3631-backlight"
> +                "ti,lm3632-backlight"
> +                "ti,lm3633-backlight"
> +                "ti,lm3695-backlight"
> +                "ti,lm3697-backlight"
> +
> +Optional properties:
> +  There are two backlight control mode. One is I2C, the other is PWM mode.
> +  Following properties are only specified in PWM mode.
> +  Please note that LMU backlight device can have only one PWM channel.
> +
> +  - pwms: OF device-tree PWM specification.
> +  - pwm-names: a list of names for the PWM devices specified in the "pwms"
> +               property.
> +
> +  For the PWM user nodes, please refer to [1].
> +
> +Child nodes:
> +  LMU backlight is represented as sub-nodes of the TI LMU device [2].
> +  So, LMU backlight should have more than one backlight child node.
> +  Each node exactly matches with backlight control bank configuration.
> +  Maximum numbers of child nodes depend on the device.
> +  1 = LM3631, LM3632, LM3695
> +  2 = LM3633, LM3697
> +  3 = LM3532
> +
> +  Required property of a child node:
> +  - led-sources: List of enabled channels from 0 to 2.
> +                 Please refer to LED binding [3].
> +                 For output channels, please refer to the datasheets [4].
> +
> +  Optional properties of a child node:
> +  - label: Backlight channel identification.
> +           Please refer to LED binding [3].
> +  - default-brightness-level: Backlight initial brightness value.
> +                              Type is <u32>. It is set as soon as backlight
> +                              device is created.
> +                              0 ~ 2047 = LM3631, LM3632, LM3633, LM3695 and
> +                                         LM3697
> +                              0 ~ 255  = LM3532
> +  - ramp-up-msec, ramp-down-msec: Light dimming effect properties.
> +                                  Type is <u32>. Unit is millisecond.
> +                                  0 ~ 65 msec    = LM3532
> +                                  0 ~ 4000 msec  = LM3631
> +                                  0 ~ 16000 msec = LM3633 and LM3697
> +  - pwm-period: PWM period. Only valid in PWM brightness mode.
> +                Type is <u32>. If this property is missing, then control
> +                mode is set to I2C by default.
> +
> +Examples: Please refer to ti-lmu dt-bindings. [2].
> +
> +[1] ../pwm/pwm.txt
> +[2] ../mfd/ti-lmu.txt
> +[3] ../leds/common.txt
> +[4] LM3532: http://www.ti.com/product/LM3532/datasheet
> +    LM3631: http://www.ti.com/product/LM3631/datasheet
> +    LM3632: http://www.ti.com/product/LM3632A/datasheet
> +    LM3633: http://www.ti.com/product/LM3633/datasheet
> +    LM3695: Datasheet is not opened yet, but only two strings are used.
> +    LM3697: http://www.ti.com/product/LM3697/datasheet

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH v2 7/9] backlight: add TI LMU backlight driver
  2015-11-26  6:57 ` [PATCH v2 7/9] backlight: add TI LMU backlight driver Milo Kim
@ 2016-01-11  9:57   ` Lee Jones
  2016-01-11 23:32     ` Milo Kim
  0 siblings, 1 reply; 38+ messages in thread
From: Lee Jones @ 2016-01-11  9:57 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

Jingoo (not CC'ed) needs to review this.

> This is consolidated driver which supports backlight devices below.
>   LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> 
> Structure
> ---------
>   It consists of two parts - core and data.
> 
>   Core part supports features below.
>     - Backlight subsystem control
>     - Channel configuration from DT properties
>     - Light dimming effect control: ramp up and down.
>     - LMU fault monitor notifier handling
>     - PWM brightness control
> 
>   Data part describes device specific data.
>     - Register value configuration for each LMU device
>       : initialization, channel configuration, control mode, enable and
>         brightness.
>     - PWM action configuration
>     - Light dimming effect table
>     - Option for LMU fault monitor support
> 
> Macros for register data
> ------------------------
>   All LMU devices have 8-bit based registers. LMU_BL_REG() creates 24-bit
>   register value in data part. It consists of address, mask and value.
>   On the other hand, register value should be parsed when the driver
>   reads/writes data from/to I2C registers. Driver uses LMU_BL_GET_ADDR(),
>   LMU_BL_GET_MASK() and LMU_BL_GET_VAL() for this purpose.
> 
> Data structure
> --------------
>   ti_lmu_bl:         Backlight output channel data
>   ti_lmu_bl_chip:    Backlight device data. One device can have multiple
>                      backlight channel data.
>   ti_lmu_bl_reg:     Backlight device register data
>   ti_lmu_bl_cfg:     Backlight configuration data for each LMU device
> 
> Cc: Lee Jones <lee.jones@linaro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: linux-leds@vger.kernel.org 
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>  drivers/video/backlight/Kconfig                 |   7 +
>  drivers/video/backlight/Makefile                |   3 +
>  drivers/video/backlight/ti-lmu-backlight-core.c | 649 ++++++++++++++++++++++++
>  drivers/video/backlight/ti-lmu-backlight-data.c | 287 +++++++++++
>  include/linux/mfd/ti-lmu-backlight.h            | 290 +++++++++++
>  5 files changed, 1236 insertions(+)
>  create mode 100644 drivers/video/backlight/ti-lmu-backlight-core.c
>  create mode 100644 drivers/video/backlight/ti-lmu-backlight-data.c
>  create mode 100644 include/linux/mfd/ti-lmu-backlight.h
> 
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 5ffa4b4..451d043 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -427,6 +427,13 @@ config BACKLIGHT_SKY81452
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called sky81452-backlight
>  
> +config BACKLIGHT_TI_LMU
> +	tristate "Backlight driver for TI LMU"
> +	depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU
> +	help
> +	  Say Y to enable the backlight driver for TI LMU devices.
> +	  This supports LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> +
>  config BACKLIGHT_TPS65217
>  	tristate "TPS65217 Backlight"
>  	depends on BACKLIGHT_CLASS_DEVICE && MFD_TPS65217
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index 16ec534..0f74ce7 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -52,6 +52,9 @@ obj-$(CONFIG_BACKLIGHT_PM8941_WLED)	+= pm8941-wled.o
>  obj-$(CONFIG_BACKLIGHT_PWM)		+= pwm_bl.o
>  obj-$(CONFIG_BACKLIGHT_SAHARA)		+= kb3886_bl.o
>  obj-$(CONFIG_BACKLIGHT_SKY81452)	+= sky81452-backlight.o
> +ti-lmu-backlight-objs			:= ti-lmu-backlight-core.o \
> +					   ti-lmu-backlight-data.o
> +obj-$(CONFIG_BACKLIGHT_TI_LMU)		+= ti-lmu-backlight.o
>  obj-$(CONFIG_BACKLIGHT_TOSA)		+= tosa_bl.o
>  obj-$(CONFIG_BACKLIGHT_TPS65217)	+= tps65217_bl.o
>  obj-$(CONFIG_BACKLIGHT_WM831X)		+= wm831x_bl.o
> diff --git a/drivers/video/backlight/ti-lmu-backlight-core.c b/drivers/video/backlight/ti-lmu-backlight-core.c
> new file mode 100644
> index 0000000..838e2c2
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,649 @@
> +/*
> + * TI LMU (Lighting Management Unit) 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/backlight.h>
> +#include <linux/bitops.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-backlight.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +
> +#define NUM_DUAL_CHANNEL			2
> +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED		(BIT(0) | BIT(1))
> +#define LMU_BACKLIGHT_11BIT_LSB_MASK		(BIT(0) | BIT(1) | BIT(2))
> +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT		3
> +#define DEFAULT_PWM_NAME			"lmu-backlight"
> +
> +static int ti_lmu_backlight_enable(struct ti_lmu_bl *lmu_bl, int enable)
> +{
> +	struct ti_lmu_bl_chip *chip = lmu_bl->chip;
> +	struct regmap *regmap = chip->lmu->regmap;
> +	unsigned long enable_time = chip->cfg->reginfo->enable_usec;
> +	u8 *reg = chip->cfg->reginfo->enable;
> +	u8 mask = BIT(lmu_bl->bank_id);
> +
> +	if (!reg)
> +		return -EINVAL;
> +
> +	if (enable)
> +		return regmap_update_bits(regmap, *reg, mask, mask);
> +	else
> +		return regmap_update_bits(regmap, *reg, mask, 0);
> +
> +	if (enable_time > 0)
> +		usleep_range(enable_time, enable_time + 100);
> +}
> +
> +static void ti_lmu_backlight_pwm_ctrl(struct ti_lmu_bl *lmu_bl, int brightness,
> +				      int max_brightness)
> +{
> +	struct pwm_device *pwm;
> +	unsigned int duty, period;
> +
> +	if (!lmu_bl->pwm) {
> +		pwm = devm_pwm_get(lmu_bl->chip->dev, DEFAULT_PWM_NAME);
> +		if (IS_ERR(pwm)) {
> +			dev_err(lmu_bl->chip->dev,
> +				"Can not get PWM device, err: %ld\n",
> +				PTR_ERR(pwm));
> +			return;
> +		}
> +
> +		lmu_bl->pwm = pwm;
> +	}
> +
> +	period = lmu_bl->pwm_period;
> +	duty = brightness * period / max_brightness;
> +
> +	pwm_config(lmu_bl->pwm, duty, period);
> +	if (duty)
> +		pwm_enable(lmu_bl->pwm);
> +	else
> +		pwm_disable(lmu_bl->pwm);
> +}
> +
> +static int ti_lmu_backlight_update_brightness_register(struct ti_lmu_bl *lmu_bl,
> +						       int brightness)
> +{
> +	const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg;
> +	const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	u8 reg, val;
> +	int ret;
> +
> +	if (lmu_bl->mode == BL_PWM_BASED) {
> +		switch (cfg->pwm_action) {
> +		case UPDATE_PWM_ONLY:
> +			/* No register update is required */
> +			return 0;
> +		case UPDATE_MAX_BRT:
> +			/*
> +			 * 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 = MAX_BRIGHTNESS_11BIT;
> +			else
> +				brightness = 0;
> +			break;
> +		default:
> +			break;
> +		}
> +	}
> +
> +	/*
> +	 * Brightness register update
> +	 *
> +	 * 11 bit dimming: update LSB bits and write MSB byte.
> +	 *		   MSB brightness should be shifted.
> +	 *  8 bit dimming: write MSB byte.
> +	 */
> +
> +	if (!reginfo->brightness_msb)
> +		return -EINVAL;
> +
> +	if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> +		if (!reginfo->brightness_lsb)
> +			return -EINVAL;
> +
> +		reg = reginfo->brightness_lsb[lmu_bl->bank_id];
> +		ret = regmap_update_bits(regmap, reg,
> +					 LMU_BACKLIGHT_11BIT_LSB_MASK,
> +					 brightness);
> +		if (ret)
> +			return ret;
> +
> +		val = (brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT) & 0xFF;
> +	} else {
> +		val = brightness & 0xFF;
> +	}
> +
> +	reg = reginfo->brightness_msb[lmu_bl->bank_id];
> +	return regmap_write(regmap, reg, val);
> +}
> +
> +static int ti_lmu_backlight_update_status(struct backlight_device *bl_dev)
> +{
> +	struct ti_lmu_bl *lmu_bl = bl_get_data(bl_dev);
> +	int brightness = bl_dev->props.brightness;
> +	int ret;
> +
> +	if (bl_dev->props.state & BL_CORE_SUSPENDED)
> +		brightness = 0;
> +
> +	if (brightness > 0)
> +		ret = ti_lmu_backlight_enable(lmu_bl, 1);
> +	else
> +		ret = ti_lmu_backlight_enable(lmu_bl, 0);
> +
> +	if (ret)
> +		return ret;
> +
> +	if (lmu_bl->mode == BL_PWM_BASED)
> +		ti_lmu_backlight_pwm_ctrl(lmu_bl, brightness,
> +					  bl_dev->props.max_brightness);
> +
> +	return ti_lmu_backlight_update_brightness_register(lmu_bl, brightness);
> +}
> +
> +static const struct backlight_ops lmu_backlight_ops = {
> +	.options = BL_CORE_SUSPENDRESUME,
> +	.update_status = ti_lmu_backlight_update_status,
> +};
> +
> +static int ti_lmu_backlight_of_get_ctrl_bank(struct device_node *np,
> +					     struct ti_lmu_bl *lmu_bl)
> +{
> +	const char *name;
> +	u32 *sources;
> +	int num_channels = lmu_bl->chip->cfg->num_channels;
> +	int ret, num_sources;
> +
> +	sources = devm_kzalloc(lmu_bl->chip->dev, num_channels, GFP_KERNEL);
> +	if (!sources)
> +		return -ENOMEM;
> +
> +	if (!of_property_read_string(np, "label", &name))
> +		lmu_bl->name = name;
> +	else
> +		lmu_bl->name = np->name;
> +
> +	ret = of_property_count_u32_elems(np, "led-sources");
> +	if (ret < 0 || ret > num_channels)
> +		return -EINVAL;
> +
> +	num_sources = ret;
> +	ret = of_property_read_u32_array(np, "led-sources", sources,
> +					 num_sources);
> +	if (ret)
> +		return ret;
> +
> +	lmu_bl->led_sources = 0;
> +	while (num_sources--)
> +		set_bit(sources[num_sources], &lmu_bl->led_sources);
> +
> +	return 0;
> +}
> +
> +static void ti_lmu_backlight_of_get_light_properties(struct device_node *np,
> +						     struct ti_lmu_bl *lmu_bl)
> +{
> +	of_property_read_u32(np, "default-brightness-level",
> +			     &lmu_bl->default_brightness);
> +
> +	of_property_read_u32(np, "ramp-up-msec",  &lmu_bl->ramp_up_msec);
> +	of_property_read_u32(np, "ramp-down-msec", &lmu_bl->ramp_down_msec);
> +}
> +
> +static void ti_lmu_backlight_of_get_brightness_mode(struct device_node *np,
> +						    struct ti_lmu_bl *lmu_bl)
> +{
> +	of_property_read_u32(np, "pwm-period", &lmu_bl->pwm_period);
> +
> +	if (lmu_bl->pwm_period > 0)
> +		lmu_bl->mode = BL_PWM_BASED;
> +	else
> +		lmu_bl->mode = BL_REGISTER_BASED;
> +}
> +
> +static int ti_lmu_backlight_of_create(struct ti_lmu_bl_chip *chip,
> +				      struct device_node *np)
> +{
> +	struct device_node *child;
> +	struct ti_lmu_bl *lmu_bl, *each;
> +	int ret, num_backlights;
> +	int i = 0;
> +
> +	num_backlights = of_get_child_count(np);
> +	if (num_backlights == 0) {
> +		dev_err(chip->dev, "No backlight strings\n");
> +		return -ENODEV;
> +	}
> +
> +	/* One chip can have mulitple backlight strings */
> +	lmu_bl = devm_kzalloc(chip->dev, sizeof(*lmu_bl) * num_backlights,
> +			      GFP_KERNEL);
> +	if (!lmu_bl)
> +		return -ENOMEM;
> +
> +	/* Child is mapped to LMU backlight control bank */
> +	for_each_child_of_node(np, child) {
> +		each = lmu_bl + i;
> +		each->bank_id = i;
> +		each->chip = chip;
> +
> +		ret = ti_lmu_backlight_of_get_ctrl_bank(child, each);
> +		if (ret) {
> +			of_node_put(np);
> +			return ret;
> +		}
> +
> +		ti_lmu_backlight_of_get_light_properties(child, each);
> +		ti_lmu_backlight_of_get_brightness_mode(child, each);
> +
> +		i++;
> +	}
> +
> +	chip->lmu_bl = lmu_bl;
> +	chip->num_backlights = num_backlights;
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_create_channel(struct ti_lmu_bl *lmu_bl)
> +{
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	u32 *reg = lmu_bl->chip->cfg->reginfo->channel;
> +	int num_channels = lmu_bl->chip->cfg->num_channels;
> +	int i, ret;
> +	u8 shift;
> +
> +	/*
> +	 * How to create backlight output channels:
> +	 *   Check 'led_sources' bit and update registers.
> +	 *
> +	 *   1) Dual channel configuration
> +	 *     The 1st register data is used for single channel.
> +	 *     The 2nd register data is used for dual channel.
> +	 *
> +	 *   2) Multiple channel configuration
> +	 *     Each register data is mapped to bank ID.
> +	 *     Bit shift operation is defined in channel registers.
> +	 *
> +	 * Channel register data consists of address, mask, value.
> +	 * Driver can get each data by using LMU_BL_GET_ADDR(),
> +	 * LMU_BL_GET_MASK(), LMU_BL_GET_VAL().
> +	 */
> +
> +	if (num_channels == NUM_DUAL_CHANNEL) {
> +		if (lmu_bl->led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> +			++reg;
> +
> +		return regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +					  LMU_BL_GET_MASK(*reg),
> +					  LMU_BL_GET_VAL(*reg));
> +	}
> +
> +	for (i = 0; i < num_channels; i++) {
> +		if (!reg)
> +			break;
> +
> +		/*
> +		 * Note that the result of LMU_BL_GET_VAL() is shift bit.
> +		 * The bank_id should be shifted for the channel configuration.
> +		 */
> +		if (test_bit(i, &lmu_bl->led_sources)) {
> +			shift = LMU_BL_GET_VAL(*reg);
> +			ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +						 LMU_BL_GET_MASK(*reg),
> +						 lmu_bl->bank_id << shift);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		reg++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_update_ctrl_mode(struct ti_lmu_bl *lmu_bl)
> +{
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	u32 *reg = lmu_bl->chip->cfg->reginfo->mode + lmu_bl->bank_id;
> +	u8 val;
> +
> +	if (!reg)
> +		return 0;
> +
> +	/*
> +	 * Update PWM configuration register.
> +	 * If the mode is register based, then clear the bit.
> +	 */
> +	if (lmu_bl->mode == BL_PWM_BASED)
> +		val = LMU_BL_GET_VAL(*reg);
> +	else
> +		val = 0;
> +
> +	return regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +				  LMU_BL_GET_MASK(*reg), val);
> +}
> +
> +static int ti_lmu_backlight_convert_ramp_to_index(struct ti_lmu_bl *lmu_bl,
> +						  enum ti_lmu_bl_ramp_mode mode)
> +{
> +	const int *ramp_table = lmu_bl->chip->cfg->ramp_table;
> +	const int size = lmu_bl->chip->cfg->size_ramp;
> +	unsigned int msec;
> +	int i;
> +
> +	if (!ramp_table)
> +		return -EINVAL;
> +
> +	switch (mode) {
> +	case BL_RAMP_UP:
> +		msec = lmu_bl->ramp_up_msec;
> +		break;
> +	case BL_RAMP_DOWN:
> +		msec = lmu_bl->ramp_down_msec;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	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 an approximate index 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 -EINVAL;
> +}
> +
> +static int ti_lmu_backlight_set_ramp(struct ti_lmu_bl *lmu_bl)
> +{
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	const struct ti_lmu_bl_reg *reginfo = lmu_bl->chip->cfg->reginfo;
> +	int offset = reginfo->ramp_reg_offset;
> +	int i, ret, index;
> +	u32 reg;
> +
> +	for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> +		index = ti_lmu_backlight_convert_ramp_to_index(lmu_bl, i);
> +		if (index > 0) {
> +			if (!reginfo->ramp)
> +				break;
> +
> +			if (lmu_bl->bank_id == 0)
> +				reg = reginfo->ramp[i];
> +			else
> +				reg = reginfo->ramp[i] + offset;
> +
> +			/*
> +			 * Note that the result of LMU_BL_GET_VAL() is
> +			 * shift bit. So updated bit is shifted index value.
> +			 */
> +			ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(reg),
> +						 LMU_BL_GET_MASK(reg),
> +						 index << LMU_BL_GET_VAL(reg));
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_configure(struct ti_lmu_bl *lmu_bl)
> +{
> +	int ret;
> +
> +	ret = ti_lmu_backlight_create_channel(lmu_bl);
> +	if (ret)
> +		return ret;
> +
> +	ret = ti_lmu_backlight_update_ctrl_mode(lmu_bl);
> +	if (ret)
> +		return ret;
> +
> +	return ti_lmu_backlight_set_ramp(lmu_bl);
> +}
> +
> +static int ti_lmu_backlight_init(struct ti_lmu_bl_chip *chip)
> +{
> +	struct regmap *regmap = chip->lmu->regmap;
> +	u32 *reg = chip->cfg->reginfo->init;
> +	int num_init = chip->cfg->reginfo->num_init;
> +	int i, ret;
> +
> +	/*
> +	 * 'init' register data consists of address, mask, value.
> +	 * Driver can get each data by using LMU_BL_GET_ADDR(),
> +	 * LMU_BL_GET_MASK(), LMU_BL_GET_VAL().
> +	 */
> +
> +	for (i = 0; i < num_init; i++) {
> +		if (!reg)
> +			break;
> +
> +		ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +					 LMU_BL_GET_MASK(*reg),
> +					 LMU_BL_GET_VAL(*reg));
> +		if (ret)
> +			return ret;
> +
> +		reg++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_reload(struct ti_lmu_bl_chip *chip)
> +{
> +	struct ti_lmu_bl *each;
> +	int i, ret;
> +
> +	ret = ti_lmu_backlight_init(chip);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < chip->num_backlights; i++) {
> +		each = chip->lmu_bl + i;
> +		ret = ti_lmu_backlight_configure(each);
> +		if (ret)
> +			return ret;
> +
> +		backlight_update_status(each->bl_dev);
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_add_device(struct device *dev,
> +				       struct ti_lmu_bl *lmu_bl)
> +{
> +	struct backlight_device *bl_dev;
> +	struct backlight_properties props;
> +
> +	memset(&props, 0, sizeof(struct backlight_properties));
> +	props.type = BACKLIGHT_PLATFORM;
> +	props.brightness = lmu_bl->default_brightness;
> +	props.max_brightness = lmu_bl->chip->cfg->max_brightness;
> +
> +	bl_dev = devm_backlight_device_register(dev, lmu_bl->name,
> +						lmu_bl->chip->dev, lmu_bl,
> +						&lmu_backlight_ops, &props);
> +	if (IS_ERR(bl_dev))
> +		return PTR_ERR(bl_dev);
> +
> +	lmu_bl->bl_dev = bl_dev;
> +
> +	return 0;
> +}
> +
> +static struct ti_lmu_bl_chip *
> +ti_lmu_backlight_register(struct device *dev, struct ti_lmu *lmu,
> +			  const struct ti_lmu_bl_cfg *cfg)
> +{
> +	struct ti_lmu_bl_chip *chip;
> +	struct ti_lmu_bl *each;
> +	int i, ret;
> +
> +	if (!cfg) {
> +		dev_err(dev, "Operation is not configured\n");
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
> +	if (!chip)
> +		return ERR_PTR(-ENOMEM);
> +
> +	chip->dev = dev;
> +	chip->lmu = lmu;
> +	chip->cfg = cfg;
> +
> +	ret = ti_lmu_backlight_of_create(chip, dev->of_node);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	ret = ti_lmu_backlight_init(chip);
> +	if (ret) {
> +		dev_err(dev, "Backlight init err: %d\n", ret);
> +		return ERR_PTR(ret);
> +	}
> +
> +	for (i = 0; i < chip->num_backlights; i++) {
> +		each = chip->lmu_bl + i;
> +
> +		ret = ti_lmu_backlight_configure(each);
> +		if (ret) {
> +			dev_err(dev, "Backlight config err: %d\n", ret);
> +			return ERR_PTR(ret);
> +		}
> +
> +		ret = ti_lmu_backlight_add_device(dev, each);
> +		if (ret) {
> +			dev_err(dev, "Backlight device err: %d\n", ret);
> +			return ERR_PTR(ret);
> +		}
> +
> +		backlight_update_status(each->bl_dev);
> +	}
> +
> +	return chip;
> +}
> +
> +static void ti_lmu_backlight_unregister(struct ti_lmu_bl_chip *chip)
> +{
> +	struct ti_lmu_bl *each;
> +	int i;
> +
> +	/* Turn off the brightness */
> +	for (i = 0; i < chip->num_backlights; i++) {
> +		each = chip->lmu_bl + i;
> +		each->bl_dev->props.brightness = 0;
> +		backlight_update_status(each->bl_dev);
> +	}
> +}
> +
> +static int ti_lmu_backlight_monitor_notifier(struct notifier_block *nb,
> +					     unsigned long action, void *unused)
> +{
> +	struct ti_lmu_bl_chip *chip = container_of(nb, struct ti_lmu_bl_chip,
> +						   nb);
> +
> +	if (action == LMU_EVENT_MONITOR_DONE) {
> +		if (ti_lmu_backlight_reload(chip))
> +			return NOTIFY_STOP;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static int ti_lmu_backlight_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
> +	struct ti_lmu_bl_chip *chip;
> +	int ret;
> +
> +	chip = ti_lmu_backlight_register(dev, lmu, &lmu_bl_cfg[pdev->id]);
> +	if (IS_ERR(chip))
> +		return PTR_ERR(chip);
> +
> +	/*
> +	 * Notifier callback is required because backlight device needs
> +	 * reconfiguration after fault detection procedure is done by
> +	 * ti-lmu-fault-monitor driver.
> +	 */
> +	if (chip->cfg->fault_monitor_used) {
> +		chip->nb.notifier_call = ti_lmu_backlight_monitor_notifier;
> +		ret = blocking_notifier_chain_register(&chip->lmu->notifier,
> +						       &chip->nb);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, chip);
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_remove(struct platform_device *pdev)
> +{
> +	struct ti_lmu_bl_chip *chip = platform_get_drvdata(pdev);
> +
> +	if (chip->cfg->fault_monitor_used)
> +		blocking_notifier_chain_unregister(&chip->lmu->notifier,
> +						   &chip->nb);
> +
> +	ti_lmu_backlight_unregister(chip);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver ti_lmu_backlight_driver = {
> +	.probe  = ti_lmu_backlight_probe,
> +	.remove = ti_lmu_backlight_remove,
> +	.driver = {
> +		.name = "ti-lmu-backlight",
> +	},
> +};
> +
> +module_platform_driver(ti_lmu_backlight_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight Driver");
> +MODULE_AUTHOR("Milo Kim");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-backlight");
> diff --git a/drivers/video/backlight/ti-lmu-backlight-data.c b/drivers/video/backlight/ti-lmu-backlight-data.c
> new file mode 100644
> index 0000000..0a486b2
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-data.c
> @@ -0,0 +1,287 @@
> +/*
> + * TI LMU (Lighting Management Unit) Backlight Device Data
> + *
> + * 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-backlight.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +
> +/* LM3532 */
> +static u32 lm3532_init_regs[] = {
> +	LM3532_INIT_ZONE_0,
> +	LM3532_INIT_ZONE_1,
> +	LM3532_INIT_ZONE_2,
> +};
> +
> +static u32 lm3532_channel_regs[] = {
> +	LM3532_CHANNEL_1,
> +	LM3532_CHANNEL_2,
> +	LM3532_CHANNEL_3,
> +};
> +
> +static u32 lm3532_mode_regs[] = {
> +	LM3532_MODE_PWM_A,
> +	LM3532_MODE_PWM_B,
> +	LM3532_MODE_PWM_C,
> +};
> +
> +static u32 lm3532_ramp_regs[] = {
> +	LM3532_RAMPUP,
> +	LM3532_RAMPDN,
> +};
> +
> +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> +
> +static u8 lm3532_brightness_regs[] = {
> +	LM3532_REG_BRT_A,
> +	LM3532_REG_BRT_B,
> +	LM3532_REG_BRT_C,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> +	.init		= lm3532_init_regs,
> +	.num_init	= ARRAY_SIZE(lm3532_init_regs),
> +	.channel	= lm3532_channel_regs,
> +	.mode		= lm3532_mode_regs,
> +	.ramp		= lm3532_ramp_regs,
> +	.enable		= &lm3532_enable_reg,
> +	.brightness_msb	= lm3532_brightness_regs,
> +};
> +
> +/* LM3631 */
> +static u32 lm3631_init_regs[] = {
> +	LM3631_INIT_BRT_MODE,
> +	LM3631_INIT_DIMMING_MODE,
> +};
> +
> +static u32 lm3631_channel_regs[]  = {
> +	LM3631_SINGLE_CHANNEL,
> +	LM3631_DUAL_CHANNEL,
> +};
> +
> +static u32 lm3631_ramp_reg = LM3631_RAMP;
> +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> +	.init		= lm3631_init_regs,
> +	.num_init	= ARRAY_SIZE(lm3631_init_regs),
> +	.channel	= lm3631_channel_regs,
> +	.ramp		= &lm3631_ramp_reg,
> +	.enable		= &lm3631_enable_reg,
> +	.brightness_msb	= &lm3631_brightness_msb_reg,
> +	.brightness_lsb	= &lm3631_brightness_lsb_reg,
> +};
> +
> +/* LM3632 */
> +static u32 lm3632_init_regs[] = {
> +	LM3632_INIT_OVP_25V,
> +	LM3632_INIT_SWFREQ_1MHZ,
> +};
> +
> +static u32 lm3632_channel_regs[]  = {
> +	LM3632_SINGLE_CHANNEL,
> +	LM3632_DUAL_CHANNEL,
> +};
> +
> +static u32 lm3632_mode_reg = LM3632_MODE_PWM;
> +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> +	.init		= lm3632_init_regs,
> +	.num_init	= ARRAY_SIZE(lm3632_init_regs),
> +	.channel	= lm3632_channel_regs,
> +	.mode		= &lm3632_mode_reg,
> +	.enable		= &lm3632_enable_reg,
> +	.brightness_msb	= &lm3632_brightness_msb_reg,
> +	.brightness_lsb	= &lm3632_brightness_lsb_reg,
> +};
> +
> +/* LM3633 */
> +static u32 lm3633_init_regs[] = {
> +	LM3633_INIT_OVP_40V,
> +	LM3633_INIT_RAMP_SELECT,
> +};
> +
> +static u32 lm3633_channel_regs[]  = {
> +	LM3633_CHANNEL_HVLED1,
> +	LM3633_CHANNEL_HVLED2,
> +	LM3633_CHANNEL_HVLED3,
> +};
> +
> +static u32 lm3633_mode_regs[] = {
> +	LM3633_MODE_PWM_A,
> +	LM3633_MODE_PWM_B,
> +};
> +
> +static u32 lm3633_ramp_regs[] = {
> +	LM3633_RAMPUP,
> +	LM3633_RAMPDN,
> +};
> +
> +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> +
> +static u8 lm3633_brightness_msb_regs[] = {
> +	LM3633_REG_BRT_HVLED_A_MSB,
> +	LM3633_REG_BRT_HVLED_B_MSB,
> +};
> +
> +static u8 lm3633_brightness_lsb_regs[] = {
> +	LM3633_REG_BRT_HVLED_A_LSB,
> +	LM3633_REG_BRT_HVLED_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> +	.init		 = lm3633_init_regs,
> +	.num_init	 = ARRAY_SIZE(lm3633_init_regs),
> +	.channel	 = lm3633_channel_regs,
> +	.mode		 = lm3633_mode_regs,
> +	.ramp		 = lm3633_ramp_regs,
> +	.ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> +	.enable		 = &lm3633_enable_reg,
> +	.brightness_msb	 = lm3633_brightness_msb_regs,
> +	.brightness_lsb	 = lm3633_brightness_lsb_regs,
> +};
> +
> +/* LM3695 */
> +static u32 lm3695_init_regs[] = {
> +	LM3695_INIT_BRT_MODE,
> +};
> +
> +static u32 lm3695_channel_regs[]  = {
> +	LM3695_SINGLE_CHANNEL,
> +	LM3695_DUAL_CHANNEL,
> +};
> +
> +static u8 lm3695_enable_reg = LM3695_REG_GP;
> +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> +	.init		= lm3695_init_regs,
> +	.num_init	= ARRAY_SIZE(lm3695_init_regs),
> +	.channel	= lm3695_channel_regs,
> +	.enable		= &lm3695_enable_reg,
> +	.enable_usec	= 600,
> +	.brightness_msb	= &lm3695_brightness_msb_reg,
> +	.brightness_lsb	= &lm3695_brightness_lsb_reg,
> +};
> +
> +/* LM3697 */
> +static u32 lm3697_init_regs[] = {
> +	LM3697_INIT_RAMP_SELECT,
> +};
> +
> +static u32 lm3697_channel_regs[]  = {
> +	LM3697_CHANNEL_1,
> +	LM3697_CHANNEL_2,
> +	LM3697_CHANNEL_3,
> +};
> +
> +static u32 lm3697_mode_regs[] = {
> +	LM3697_MODE_PWM_A,
> +	LM3697_MODE_PWM_B,
> +};
> +
> +static u32 lm3697_ramp_regs[] = {
> +	LM3697_RAMPUP,
> +	LM3697_RAMPDN,
> +};
> +
> +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> +
> +static u8 lm3697_brightness_msb_regs[] = {
> +	LM3697_REG_BRT_A_MSB,
> +	LM3697_REG_BRT_B_MSB,
> +};
> +
> +static u8 lm3697_brightness_lsb_regs[] = {
> +	LM3697_REG_BRT_A_LSB,
> +	LM3697_REG_BRT_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> +	.init		 = lm3697_init_regs,
> +	.num_init	 = ARRAY_SIZE(lm3697_init_regs),
> +	.channel	 = lm3697_channel_regs,
> +	.mode		 = lm3697_mode_regs,
> +	.ramp		 = lm3697_ramp_regs,
> +	.ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> +	.enable		 = &lm3697_enable_reg,
> +	.brightness_msb	 = lm3697_brightness_msb_regs,
> +	.brightness_lsb	 = lm3697_brightness_lsb_regs,
> +};
> +
> +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> +
> +static int lm3631_ramp_table[] = {
> +	   0,   1,   2,    5,   10,   20,   50,  100,
> +	 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> +};
> +
> +static int common_ramp_table[] = {
> +	   2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> +};
> +
> +struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> +	{
> +		.reginfo		= &lm3532_reg_info,
> +		.num_channels		= LM3532_MAX_CHANNELS,
> +		.max_brightness		= MAX_BRIGHTNESS_8BIT,
> +		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
> +		.ramp_table		= lm3532_ramp_table,
> +		.size_ramp		= ARRAY_SIZE(lm3532_ramp_table),
> +	},
> +	{
> +		.reginfo		= &lm3631_reg_info,
> +		.num_channels		= LM3631_MAX_CHANNELS,
> +		.max_brightness		= MAX_BRIGHTNESS_11BIT,
> +		.pwm_action		= UPDATE_PWM_ONLY,
> +		.ramp_table		= lm3631_ramp_table,
> +		.size_ramp		= ARRAY_SIZE(lm3631_ramp_table),
> +	},
> +	{
> +		.reginfo		= &lm3632_reg_info,
> +		.num_channels		= LM3632_MAX_CHANNELS,
> +		.max_brightness		= MAX_BRIGHTNESS_11BIT,
> +		.pwm_action		= UPDATE_PWM_ONLY,
> +	},
> +	{
> +		.reginfo		= &lm3633_reg_info,
> +		.num_channels		= LM3633_MAX_CHANNELS,
> +		.max_brightness		= MAX_BRIGHTNESS_11BIT,
> +		.pwm_action		= UPDATE_MAX_BRT,
> +		.ramp_table		= common_ramp_table,
> +		.size_ramp		= ARRAY_SIZE(common_ramp_table),
> +		.fault_monitor_used	= true,
> +	},
> +	{
> +		.reginfo		= &lm3695_reg_info,
> +		.num_channels		= LM3695_MAX_CHANNELS,
> +		.max_brightness		= MAX_BRIGHTNESS_11BIT,
> +		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
> +	},
> +	{
> +		.reginfo		= &lm3697_reg_info,
> +		.num_channels		= LM3697_MAX_CHANNELS,
> +		.max_brightness		= MAX_BRIGHTNESS_11BIT,
> +		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
> +		.ramp_table		= common_ramp_table,
> +		.size_ramp		= ARRAY_SIZE(common_ramp_table),
> +		.fault_monitor_used	= true,
> +	},
> +};
> +EXPORT_SYMBOL_GPL(lmu_bl_cfg);
> diff --git a/include/linux/mfd/ti-lmu-backlight.h b/include/linux/mfd/ti-lmu-backlight.h
> new file mode 100644
> index 0000000..43e5300
> --- /dev/null
> +++ b/include/linux/mfd/ti-lmu-backlight.h
> @@ -0,0 +1,290 @@
> +/*
> + * TI LMU (Lighting Management Unit) Backlight Common 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.
> + */
> +
> +#ifndef __TI_LMU_BACKLIGHT_H__
> +#define __TI_LMU_BACKLIGHT_H__
> +
> +#include <linux/backlight.h>
> +#include <linux/device.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/notifier.h>
> +
> +/**
> + * LMU backlight register data
> + *	value[23:16] | mask[15:8] | address[7:0]
> + */
> +#define LMU_BL_REG(addr, mask, value)				\
> +	((value << 16) | (mask << 8) | addr)
> +
> +#define LMU_BL_GET_ADDR(x)	(x & 0xFF)
> +#define LMU_BL_GET_MASK(x)	((x >> 8) & 0xFF)
> +#define LMU_BL_GET_VAL(x)	((x >> 16) & 0xFF)
> +
> +#define LM3532_INIT_ZONE_0						\
> +	LMU_BL_REG(LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0)
> +#define LM3532_INIT_ZONE_1						\
> +	LMU_BL_REG(LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1)
> +#define LM3532_INIT_ZONE_2						\
> +	LMU_BL_REG(LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2)
> +#define LM3532_CHANNEL_1						\
> +	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,	\
> +	LM3532_ILED1_CFG_SHIFT)
> +#define LM3532_CHANNEL_2						\
> +	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,	\
> +	LM3532_ILED2_CFG_SHIFT)
> +#define LM3532_CHANNEL_3						\
> +	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,	\
> +	LM3532_ILED3_CFG_SHIFT)
> +#define LM3532_MODE_PWM_A						\
> +	LMU_BL_REG(LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0)
> +#define LM3532_MODE_PWM_B						\
> +	LMU_BL_REG(LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1)
> +#define LM3532_MODE_PWM_C						\
> +	LMU_BL_REG(LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2)
> +#define LM3532_RAMPUP							\
> +	LMU_BL_REG(LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT)
> +#define LM3532_RAMPDN							\
> +	LMU_BL_REG(LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT)
> +
> +#define LM3631_INIT_BRT_MODE						\
> +	LMU_BL_REG(LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE)
> +#define LM3631_INIT_DIMMING_MODE					\
> +	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP)
> +#define LM3631_SINGLE_CHANNEL						\
> +	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK,		\
> +	LM3631_BL_SINGLE_CHANNEL)
> +#define LM3631_DUAL_CHANNEL						\
> +	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK,		\
> +	LM3631_BL_DUAL_CHANNEL)
> +#define LM3631_RAMP							\
> +	LMU_BL_REG(LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT)
> +
> +#define LM3632_INIT_OVP_25V						\
> +	LMU_BL_REG(LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V)
> +#define LM3632_INIT_SWFREQ_1MHZ						\
> +	LMU_BL_REG(LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ)
> +#define LM3632_SINGLE_CHANNEL						\
> +	LMU_BL_REG(LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK,		\
> +	LM3632_BL_SINGLE_CHANNEL)
> +#define LM3632_DUAL_CHANNEL						\
> +	LMU_BL_REG(LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK,		\
> +	LM3632_BL_DUAL_CHANNEL)
> +#define LM3632_MODE_PWM							\
> +	LMU_BL_REG(LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE)
> +
> +#define LM3633_INIT_OVP_40V						\
> +	LMU_BL_REG(LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V)
> +#define LM3633_INIT_RAMP_SELECT						\
> +	LMU_BL_REG(LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK,	\
> +	LM3633_BL_RAMP_EACH)
> +#define LM3633_CHANNEL_HVLED1						\
> +	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,	\
> +	LM3633_HVLED1_CFG_SHIFT)
> +#define LM3633_CHANNEL_HVLED2						\
> +	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,	\
> +	LM3633_HVLED2_CFG_SHIFT)
> +#define LM3633_CHANNEL_HVLED3						\
> +	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,	\
> +	LM3633_HVLED3_CFG_SHIFT)
> +#define LM3633_MODE_PWM_A						\
> +	LMU_BL_REG(LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK)
> +#define LM3633_MODE_PWM_B						\
> +	LMU_BL_REG(LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK)
> +#define LM3633_RAMPUP							\
> +	LMU_BL_REG(LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK,		\
> +	LM3633_BL_RAMPUP_SHIFT)
> +#define LM3633_RAMPDN							\
> +	LMU_BL_REG(LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK,		\
> +	LM3633_BL_RAMPDN_SHIFT)
> +
> +#define LM3695_INIT_BRT_MODE						\
> +	LMU_BL_REG(LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK)
> +#define LM3695_SINGLE_CHANNEL						\
> +	LMU_BL_REG(LM3695_REG_GP, LM3695_BL_CHANNEL_MASK,		\
> +	LM3695_BL_SINGLE_CHANNEL)
> +#define LM3695_DUAL_CHANNEL						\
> +	LMU_BL_REG(LM3695_REG_GP, LM3695_BL_CHANNEL_MASK,		\
> +	LM3695_BL_DUAL_CHANNEL)
> +
> +#define LM3697_INIT_RAMP_SELECT						\
> +	LMU_BL_REG(LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH)
> +#define LM3697_CHANNEL_1						\
> +	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,	\
> +	LM3697_HVLED1_CFG_SHIFT)
> +#define LM3697_CHANNEL_2						\
> +	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,	\
> +	LM3697_HVLED2_CFG_SHIFT)
> +#define LM3697_CHANNEL_3						\
> +	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,	\
> +	LM3697_HVLED3_CFG_SHIFT)
> +#define LM3697_MODE_PWM_A						\
> +	LMU_BL_REG(LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK)
> +#define LM3697_MODE_PWM_B						\
> +	LMU_BL_REG(LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK)
> +#define LM3697_RAMPUP							\
> +	LMU_BL_REG(LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT)
> +#define LM3697_RAMPDN							\
> +	LMU_BL_REG(LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT)
> +
> +#define LM3532_MAX_CHANNELS		3
> +#define LM3631_MAX_CHANNELS		2
> +#define LM3632_MAX_CHANNELS		2
> +#define LM3633_MAX_CHANNELS		3
> +#define LM3695_MAX_CHANNELS		2
> +#define LM3697_MAX_CHANNELS		3
> +
> +#define MAX_BRIGHTNESS_8BIT		255
> +#define MAX_BRIGHTNESS_11BIT		2047
> +
> +enum ti_lmu_bl_ctrl_mode {
> +	BL_REGISTER_BASED,
> +	BL_PWM_BASED,
> +};
> +
> +enum ti_lmu_bl_pwm_action {
> +	/* Update PWM duty, no brightness register update is required */
> +	UPDATE_PWM_ONLY,
> +	/* Update not only duty but also brightness register */
> +	UPDATE_PWM_AND_BRT_REGISTER,
> +	/* Update max value in brightness registers */
> +	UPDATE_MAX_BRT,
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> +	BL_RAMP_UP,
> +	BL_RAMP_DOWN,
> +};
> +
> +struct ti_lmu_bl;
> +struct ti_lmu_bl_chip;
> +
> +/**
> + * struct ti_lmu_bl_reg
> + *
> + * @init:		Device initialization registers
> + * @num_init:		Numbers of initialization registers
> + * @channel:		Backlight channel configuration registers
> + * @mode:		Brightness control mode registers
> + * @ramp:		Ramp registers for lighting effect
> + * @ramp_reg_offset:	Ramp register offset.
> + *			Only used for multiple ramp registers.
> + * @enable:		Enable control register address
> + * @enable_usec:	Delay time for updating enable register.
> + *			Unit is microsecond.
> + * @brightness_msb:	Brightness MSB(Upper 8 bits) registers.
> + *			Concatenated with LSB in 11 bit dimming mode.
> + *			In 8 bit dimming, only MSB is used.
> + * @brightness_lsb:	Brightness LSB(Lower 3 bits) registers.
> + *			Only valid in 11 bit dimming mode.
> + */
> +struct ti_lmu_bl_reg {
> +	u32 *init;
> +	int num_init;
> +	u32 *channel;
> +	u32 *mode;
> +	u32 *ramp;
> +	int ramp_reg_offset;
> +	u8 *enable;
> +	unsigned long enable_usec;
> +	u8 *brightness_msb;
> +	u8 *brightness_lsb;
> +};
> +
> +/**
> + * struct ti_lmu_bl_cfg
> + *
> + * @reginfo:		Device register configuration
> + * @num_channels:	Number of backlight channels
> + * @max_brightness:	Max brightness value of backlight device
> + * @pwm_action:		How to control brightness registers in PWM mode
> + * @ramp_table:		[Optional] Ramp time table for lighting effect.
> + *			It's used for searching approximate register index.
> + * @size_ramp:		[Optional] Size of ramp table
> + * @fault_monitor_used:	[Optional] Set true if the device needs to handle
> + *			LMU fault monitor event.
> + *
> + * This structure is used for device specific data configuration.
> + */
> +struct ti_lmu_bl_cfg {
> +	const struct ti_lmu_bl_reg *reginfo;
> +	int num_channels;
> +	int max_brightness;
> +	enum ti_lmu_bl_pwm_action pwm_action;
> +	int *ramp_table;
> +	int size_ramp;
> +	bool fault_monitor_used;
> +};
> +
> +/**
> + * struct ti_lmu_bl_chip
> + *
> + * @dev:		Parent device pointer
> + * @lmu:		LMU structure.
> + *			Used for register R/W access and notification.
> + * @cfg:		Device configuration data
> + * @lmu_bl:		Multiple backlight channels
> + * @num_backlights:	Number of backlight channels
> + * @nb:			Notifier block for handling LMU fault monitor event
> + *
> + * One backlight chip can have multiple backlight channels, 'ti_lmu_bl'.
> + */
> +struct ti_lmu_bl_chip {
> +	struct device *dev;
> +	struct ti_lmu *lmu;
> +	const struct ti_lmu_bl_cfg *cfg;
> +	struct ti_lmu_bl *lmu_bl;
> +	int num_backlights;
> +	struct notifier_block nb;
> +};
> +
> +/**
> + * struct ti_lmu_bl
> + *
> + * @chip:		Pointer to parent backlight device
> + * @bl_dev:		Backlight subsystem device structure
> + * @bank_id:		Backlight bank ID
> + * @name:		Backlight channel name
> + * @mode:		Backlight control mode
> + * @led_sources:	Backlight output channel configuration.
> + *			Bit mask is set on parsing DT.
> + * @default_brightness:	[Optional] Initial brightness value
> + * @ramp_up_msec:	[Optional] Ramp up time
> + * @ramp_down_msec:	[Optional] Ramp down time
> + * @pwm_period:		[Optional] PWM period
> + * @pwm:		[Optional] PWM subsystem structure
> + *
> + * Each backlight device has its own channel configuration.
> + * For chip control, parent chip data structure is used.
> + */
> +struct ti_lmu_bl {
> +	struct ti_lmu_bl_chip *chip;
> +	struct backlight_device *bl_dev;
> +
> +	int bank_id;
> +	const char *name;
> +	enum ti_lmu_bl_ctrl_mode mode;
> +	unsigned long led_sources;
> +
> +	unsigned int default_brightness;
> +
> +	/* Used for lighting effect */
> +	unsigned int ramp_up_msec;
> +	unsigned int ramp_down_msec;
> +
> +	/* Only valid in PWM mode */
> +	unsigned int pwm_period;
> +	struct pwm_device *pwm;
> +};
> +
> +extern struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> +#endif

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH v2 5/9] mfd: add TI LMU driver
  2015-11-26  6:57 ` [PATCH v2 5/9] mfd: add TI LMU driver Milo Kim
@ 2016-01-11 10:17   ` Lee Jones
  0 siblings, 0 replies; 38+ messages in thread
From: Lee Jones @ 2016-01-11 10:17 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On Thu, 26 Nov 2015, Milo Kim wrote:

> TI LMU (Lighting Management Unit) driver supports lighting devices below.
> 
>   LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> 
> LMU devices have common features.
>   - I2C interface for accessing device registers
>   - Hardware enable pin control
>   - Backlight brightness control
>   - Notifier for hardware fault monitoring
>   - Regulators for LCD display bias
> 
> It contains fault monitor, backlight, LED and regulator driver.
> 
> LMU fault monitor
> -----------------
>   LM3633 and LM3697 provide hardware monitoring feature.
>   It enables open or short circuit detection.
>   After monitoring is done, each device should be re-initialized.
>   Notifier is used for this case.
>   Please refer to separate patch for 'ti-lmu-fault-monitor'.
> 
> Backlight
> ---------
>   It's handled by TI LMU backlight consolidated driver and
>   chip dependent data. Please refer to separate patches for
>   'ti-lmu-backlight'.
> 
> LED indicator
> -------------
>   LM3633 has 6 indicator LEDs. Programmable dimming pattern is also
>   supported. Please refer to separate patch for 'leds-lm3633'.
> 
> Regulator
> ---------
>   LM3631 has 5 regulators for the display bias.
>   LM3632 supports 3 regulators. One consolidated driver enables it.
>   Please refer to separate patch for 'lm363x-regulator'.
> 
> Cc: Lee Jones <lee.jones@linaro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: linux-leds@vger.kernel.org 
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>  drivers/mfd/Kconfig                 |  12 ++
>  drivers/mfd/Makefile                |   2 +
>  drivers/mfd/ti-lmu.c                | 259 +++++++++++++++++++++++++++++++++
>  include/linux/mfd/ti-lmu-register.h | 280 ++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/ti-lmu.h          |  87 +++++++++++
>  5 files changed, 640 insertions(+)
>  create mode 100644 drivers/mfd/ti-lmu.c
>  create mode 100644 include/linux/mfd/ti-lmu-register.h
>  create mode 100644 include/linux/mfd/ti-lmu.h

Acked-by: Lee Jones <lee.jones@linaro.org>

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver
  2015-11-26  6:57 ` [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver Milo Kim
@ 2016-01-11 10:21   ` Lee Jones
  2016-01-12  3:36     ` Milo Kim
  0 siblings, 1 reply; 38+ messages in thread
From: Lee Jones @ 2016-01-11 10:21 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On Thu, 26 Nov 2015, Milo Kim wrote:

> LM3633 and LM3697 are TI LMU MFD device.
> Those devices have hardware monitoring feature which detects open or
> short circuit case.
> 
> Debugfs
> -------
>   Two files are created.
>     open_fault:  check light output channel is open or not.
>     short_fault: check light output channel is shorted or not.
> 
>   The driver checks the status of backlight output channels.
>   LM3633 and LM3697 have same sequence to check channels, so common
>   functions are used.
>   ABI/testing document is also included.
> 
> Operations
> ----------
>   Two devices have common control flow but register addresses are different.
>   The structure, 'ti_lmu_reg' is used for register configuration.
> 
> Event notifier
> --------------
>   After fault monitoring is done, LMU device is reset. So backlight and
>   LED device should be reinitialized. It notifies an event as soon as
>   the monitoring is done. Then, LM3633 and LM3697 backlight and LED drivers
>   handle this event.
> 
> Cc: Lee Jones <lee.jones@linaro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: linux-leds@vger.kernel.org 
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>  .../ABI/testing/debugfs-ti-lmu-fault-monitor       |  32 ++
>  drivers/mfd/Kconfig                                |  10 +
>  drivers/mfd/Makefile                               |   1 +
>  drivers/mfd/ti-lmu-fault-monitor.c                 | 405 +++++++++++++++++++++

I think this device is part of the MFD, rather than an MFD itself.
Please relocate it to somewhere more appropriate.

>  4 files changed, 448 insertions(+)
>  create mode 100644 Documentation/ABI/testing/debugfs-ti-lmu-fault-monitor
>  create mode 100644 drivers/mfd/ti-lmu-fault-monitor.c

[...]

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH v2 7/9] backlight: add TI LMU backlight driver
  2016-01-11  9:57   ` Lee Jones
@ 2016-01-11 23:32     ` Milo Kim
  0 siblings, 0 replies; 38+ messages in thread
From: Milo Kim @ 2016-01-11 23:32 UTC (permalink / raw)
  To: Lee Jones
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On 01/11/2016 06:57 PM, Lee Jones wrote:
> Jingoo (not CC'ed) needs to review this.

Sorry, I forgot to CC him. Let me send out next patch-set including him.

Best regards,
Milo

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

* Re: [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver
  2016-01-11 10:21   ` Lee Jones
@ 2016-01-12  3:36     ` Milo Kim
  2016-01-12  7:37       ` Lee Jones
  0 siblings, 1 reply; 38+ messages in thread
From: Milo Kim @ 2016-01-12  3:36 UTC (permalink / raw)
  To: Lee Jones
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On 01/11/2016 07:21 PM, Lee Jones wrote:
> On Thu, 26 Nov 2015, Milo Kim wrote:
>
>> LM3633 and LM3697 are TI LMU MFD device.
>> Those devices have hardware monitoring feature which detects open or
>> short circuit case.
>>
>> Debugfs
>> -------
>>    Two files are created.
>>      open_fault:  check light output channel is open or not.
>>      short_fault: check light output channel is shorted or not.
>>
>>    The driver checks the status of backlight output channels.
>>    LM3633 and LM3697 have same sequence to check channels, so common
>>    functions are used.
>>    ABI/testing document is also included.
>>
>> Operations
>> ----------
>>    Two devices have common control flow but register addresses are different.
>>    The structure, 'ti_lmu_reg' is used for register configuration.
>>
>> Event notifier
>> --------------
>>    After fault monitoring is done, LMU device is reset. So backlight and
>>    LED device should be reinitialized. It notifies an event as soon as
>>    the monitoring is done. Then, LM3633 and LM3697 backlight and LED drivers
>>    handle this event.
>>
>> Cc: Lee Jones <lee.jones@linaro.org>
>> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
>> Cc: Mark Brown <broonie@kernel.org>
>> Cc: Rob Herring <robh+dt@kernel.org>
>> Cc: devicetree@vger.kernel.org
>> Cc: linux-leds@vger.kernel.org
>> Cc: linux-kernel@vger.kernel.org
>> Signed-off-by: Milo Kim <milo.kim@ti.com>
>> ---
>>   .../ABI/testing/debugfs-ti-lmu-fault-monitor       |  32 ++
>>   drivers/mfd/Kconfig                                |  10 +
>>   drivers/mfd/Makefile                               |   1 +
>>   drivers/mfd/ti-lmu-fault-monitor.c                 | 405 +++++++++++++++++++++
>
> I think this device is part of the MFD, rather than an MFD itself.
> Please relocate it to somewhere more appropriate.

Actually, initial patch driver was created under 'drivers/hwmon/' but 
HWMON maintainer pointed this driver doesn't include HW sensor feature.
So I moved here.

This driver doesn't need any subsystem infrastructure, 'drivers/misc/' 
could be OK. Does it make sense?

Best regards,
Milo

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

* Re: [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver
  2016-01-12  3:36     ` Milo Kim
@ 2016-01-12  7:37       ` Lee Jones
  0 siblings, 0 replies; 38+ messages in thread
From: Lee Jones @ 2016-01-12  7:37 UTC (permalink / raw)
  To: Milo Kim
  Cc: robh+dt, j.anaszewski, broonie, devicetree, linux-leds, linux-kernel

On Tue, 12 Jan 2016, Milo Kim wrote:

> On 01/11/2016 07:21 PM, Lee Jones wrote:
> >On Thu, 26 Nov 2015, Milo Kim wrote:
> >
> >>LM3633 and LM3697 are TI LMU MFD device.
> >>Those devices have hardware monitoring feature which detects open or
> >>short circuit case.
> >>
> >>Debugfs
> >>-------
> >>   Two files are created.
> >>     open_fault:  check light output channel is open or not.
> >>     short_fault: check light output channel is shorted or not.
> >>
> >>   The driver checks the status of backlight output channels.
> >>   LM3633 and LM3697 have same sequence to check channels, so common
> >>   functions are used.
> >>   ABI/testing document is also included.
> >>
> >>Operations
> >>----------
> >>   Two devices have common control flow but register addresses are different.
> >>   The structure, 'ti_lmu_reg' is used for register configuration.
> >>
> >>Event notifier
> >>--------------
> >>   After fault monitoring is done, LMU device is reset. So backlight and
> >>   LED device should be reinitialized. It notifies an event as soon as
> >>   the monitoring is done. Then, LM3633 and LM3697 backlight and LED drivers
> >>   handle this event.
> >>
> >>Cc: Lee Jones <lee.jones@linaro.org>
> >>Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> >>Cc: Mark Brown <broonie@kernel.org>
> >>Cc: Rob Herring <robh+dt@kernel.org>
> >>Cc: devicetree@vger.kernel.org
> >>Cc: linux-leds@vger.kernel.org
> >>Cc: linux-kernel@vger.kernel.org
> >>Signed-off-by: Milo Kim <milo.kim@ti.com>
> >>---
> >>  .../ABI/testing/debugfs-ti-lmu-fault-monitor       |  32 ++
> >>  drivers/mfd/Kconfig                                |  10 +
> >>  drivers/mfd/Makefile                               |   1 +
> >>  drivers/mfd/ti-lmu-fault-monitor.c                 | 405 +++++++++++++++++++++
> >
> >I think this device is part of the MFD, rather than an MFD itself.
> >Please relocate it to somewhere more appropriate.
> 
> Actually, initial patch driver was created under 'drivers/hwmon/'
> but HWMON maintainer pointed this driver doesn't include HW sensor
> feature.
> So I moved here.
> 
> This driver doesn't need any subsystem infrastructure,
> 'drivers/misc/' could be OK. Does it make sense?

Either drivers/misc or drivers/platform I guess.

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

* Re: [PATCH v2 9/9] regulator: add LM363X driver
  2015-11-26  6:57 ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
  2015-11-27 12:55   ` Applied "regulator: add LM363X driver" to the regulator tree Mark Brown
@ 2016-01-14  7:56   ` Milo Kim
  2016-01-14 10:27     ` Mark Brown
  1 sibling, 1 reply; 38+ messages in thread
From: Milo Kim @ 2016-01-14  7:56 UTC (permalink / raw)
  To: lee.jones, broonie
  Cc: robh+dt, j.anaszewski, devicetree, linux-leds, linux-kernel

Lee and Mark,

On 11/26/2015 03:57 PM, Milo Kim wrote:
> 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 configured through the DT properties.
>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: linux-leds@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>

I'm creating the 3rd patch-set but this driver is found in linux-next 
tree. And Axel Lin has patched this driver. In my patch v3, modified 
part is DT properties for external enable pins. (Use '-gpios' instead of 
'-gpio')

diff --git a/drivers/regulator/lm363x-regulator.c 
b/drivers/regulator/lm363x-regulator.c
index f53e633..4a11290 100644
--- a/drivers/regulator/lm363x-regulator.c
+++ b/drivers/regulator/lm363x-regulator.c
@@ -227,9 +227,9 @@ static int 
lm363x_regulator_of_get_enable_gpio(struct device_node *np, int id)
*/
switch (id) {
case LM3632_LDO_POS:
- return of_get_named_gpio(np, "ti,lcm-en1-gpio", 0);
+ return of_get_named_gpio(np, "ti,lcm-en1-gpios", 0);
case LM3632_LDO_NEG:
- return of_get_named_gpio(np, "ti,lcm-en2-gpio", 0);
+ return of_get_named_gpio(np, "ti,lcm-en2-gpios", 0);
default:
return -EINVAL;
}

So, I'd like to know which is better for you.
a) Create a patch based on linux-next tree (the above patch)
Or
b) Re-generate a patch based on linux-mfd tree

Best regards,
Milo

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

* Re: [PATCH v2 9/9] regulator: add LM363X driver
  2016-01-14  7:56   ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
@ 2016-01-14 10:27     ` Mark Brown
  2016-01-14 23:41       ` Kim, Milo
  0 siblings, 1 reply; 38+ messages in thread
From: Mark Brown @ 2016-01-14 10:27 UTC (permalink / raw)
  To: Milo Kim
  Cc: lee.jones, robh+dt, j.anaszewski, devicetree, linux-leds, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 344 bytes --]

On Thu, Jan 14, 2016 at 04:56:03PM +0900, Milo Kim wrote:

> So, I'd like to know which is better for you.
> a) Create a patch based on linux-next tree (the above patch)
> Or
> b) Re-generate a patch based on linux-mfd tree

At this point the easiest thing might be to wait for v4.5-rc1 then send
a patch for that after everything gets merged.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 473 bytes --]

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

* Re: [PATCH v2 9/9] regulator: add LM363X driver
  2016-01-14 10:27     ` Mark Brown
@ 2016-01-14 23:41       ` Kim, Milo
  0 siblings, 0 replies; 38+ messages in thread
From: Kim, Milo @ 2016-01-14 23:41 UTC (permalink / raw)
  To: Mark Brown
  Cc: lee.jones, robh+dt, j.anaszewski, devicetree, linux-leds, linux-kernel

On 1/14/2016 7:27 PM, Mark Brown wrote:
> On Thu, Jan 14, 2016 at 04:56:03PM +0900, Milo Kim wrote:
>
>> So, I'd like to know which is better for you.
>> a) Create a patch based on linux-next tree (the above patch)
>> Or
>> b) Re-generate a patch based on linux-mfd tree
>
> At this point the easiest thing might be to wait for v4.5-rc1 then send
> a patch for that after everything gets merged.

OK, thanks!

Best regards,
Milo

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

end of thread, other threads:[~2016-01-14 23:41 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-26  6:56 [PATCH v2 0/9] Support TI LMU devices Milo Kim
2015-11-26  6:56 ` [PATCH v2 1/9] Documentation: dt-bindings: mfd: add TI LMU device binding information Milo Kim
2015-11-27 20:55   ` Rob Herring
2016-01-11  9:46   ` Lee Jones
2015-11-26  6:56 ` [PATCH v2 2/9] Documentation: dt-bindings: leds: backlight: add TI LMU backlight " Milo Kim
2016-01-11  9:53   ` Lee Jones
2015-11-26  6:56 ` [PATCH v2 3/9] Documentation: dt-bindings: leds: add LM3633 LED " Milo Kim
2015-11-27 11:19   ` Jacek Anaszewski
2015-11-30  8:19     ` Kim, Milo
2015-11-30 12:26       ` Jacek Anaszewski
2015-12-07  8:46         ` Kim, Milo
2015-12-07 10:50           ` Jacek Anaszewski
2015-11-26  6:57 ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator " Milo Kim
2015-11-27 12:37   ` Mark Brown
2015-11-27 20:44     ` Rob Herring
2015-11-27 22:07       ` Mark Brown
2015-11-27 12:55   ` Applied "regulator: lm363x: add LM363x regulator binding information" to the regulator tree Mark Brown
2015-11-27 20:57   ` [PATCH v2 4/9] Documentation: dt-bindings: regulator: add LM363x regulator binding information Rob Herring
2015-11-26  6:57 ` [PATCH v2 5/9] mfd: add TI LMU driver Milo Kim
2016-01-11 10:17   ` Lee Jones
2015-11-26  6:57 ` [PATCH v2 6/9] mfd: add TI LMU hardware fault monitoring driver Milo Kim
2016-01-11 10:21   ` Lee Jones
2016-01-12  3:36     ` Milo Kim
2016-01-12  7:37       ` Lee Jones
2015-11-26  6:57 ` [PATCH v2 7/9] backlight: add TI LMU backlight driver Milo Kim
2016-01-11  9:57   ` Lee Jones
2016-01-11 23:32     ` Milo Kim
2015-11-26  6:57 ` [PATCH v2 8/9] leds: add LM3633 driver Milo Kim
2015-11-27 11:19   ` Jacek Anaszewski
2015-11-28  8:28     ` Jacek Anaszewski
2015-11-30  8:48     ` Kim, Milo
2015-11-30 12:26       ` Jacek Anaszewski
2015-11-26  6:57 ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
2015-11-27 12:55   ` Applied "regulator: add LM363X driver" to the regulator tree Mark Brown
2016-01-14  7:56   ` [PATCH v2 9/9] regulator: add LM363X driver Milo Kim
2016-01-14 10:27     ` Mark Brown
2016-01-14 23:41       ` Kim, Milo
2016-01-06  7:20 ` [PATCH v2 0/9] Support TI LMU devices Milo Kim

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).