Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris...
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 12 Nov 2010 16:00:25 +0000 (08:00 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 12 Nov 2010 16:00:25 +0000 (08:00 -0800)
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/security-testing-2.6:
  APPARMOR: Fix memory leak of apparmor_init()
  APPARMOR: Fix memory leak of alloc_namespace()

51 files changed:
Documentation/ABI/obsolete/proc-pid-oom_adj [new file with mode: 0644]
Documentation/leds-class.txt
Documentation/leds/leds-lp5521.txt [new file with mode: 0644]
Documentation/leds/leds-lp5523.txt [new file with mode: 0644]
Documentation/sysctl/kernel.txt
arch/um/include/asm/ptrace-generic.h
arch/um/kernel/ptrace.c
crypto/pcrypt.c
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/led-class.c
drivers/leds/led-triggers.c
drivers/leds/leds-gpio.c
drivers/leds/leds-lp5521.c [new file with mode: 0644]
drivers/leds/leds-lp5523.c [new file with mode: 0644]
drivers/leds/ledtrig-timer.c
drivers/macintosh/adb-iop.c
drivers/misc/apds9802als.c
drivers/misc/bh1770glc.c
drivers/misc/isl29020.c
drivers/net/wireless/rt2x00/Kconfig
drivers/rapidio/rio.c
drivers/video/backlight/adp8860_bl.c
drivers/video/backlight/l4f00242t03.c
drivers/video/backlight/lms283gf05.c
drivers/video/backlight/mbp_nvidia_bl.c
drivers/video/backlight/pwm_bl.c
drivers/video/backlight/s6e63m0.c
fs/hugetlbfs/inode.c
fs/locks.c
fs/nfsd/nfs4state.c
include/linux/atomic.h [new file with mode: 0644]
include/linux/highmem.h
include/linux/kernel.h
include/linux/leds-lp5521.h [new file with mode: 0644]
include/linux/leds-lp5523.h [new file with mode: 0644]
include/linux/leds.h
include/linux/pwm_backlight.h
include/linux/radix-tree.h
include/linux/resource.h
include/linux/sunrpc/svc_xprt.h
kernel/latencytop.c
kernel/printk.c
kernel/range.c
kernel/sysctl.c
lib/radix-tree.c
mm/filemap.c
mm/memcontrol.c
mm/vmscan.c
security/Kconfig
security/commoncap.c

diff --git a/Documentation/ABI/obsolete/proc-pid-oom_adj b/Documentation/ABI/obsolete/proc-pid-oom_adj
new file mode 100644 (file)
index 0000000..cf63f26
--- /dev/null
@@ -0,0 +1,22 @@
+What:  /proc/<pid>/oom_adj
+When:  August 2012
+Why:   /proc/<pid>/oom_adj allows userspace to influence the oom killer's
+       badness heuristic used to determine which task to kill when the kernel
+       is out of memory.
+
+       The badness heuristic has since been rewritten since the introduction of
+       this tunable such that its meaning is deprecated.  The value was
+       implemented as a bitshift on a score generated by the badness()
+       function that did not have any precise units of measure.  With the
+       rewrite, the score is given as a proportion of available memory to the
+       task allocating pages, so using a bitshift which grows the score
+       exponentially is, thus, impossible to tune with fine granularity.
+
+       A much more powerful interface, /proc/<pid>/oom_score_adj, was
+       introduced with the oom killer rewrite that allows users to increase or
+       decrease the badness() score linearly.  This interface will replace
+       /proc/<pid>/oom_adj.
+
+       A warning will be emitted to the kernel log if an application uses this
+       deprecated interface.  After it is printed once, future warnings will be
+       suppressed until the kernel is rebooted.
index 8fd5ca2..58b266b 100644 (file)
@@ -60,15 +60,18 @@ Hardware accelerated blink of LEDs
 
 Some LEDs can be programmed to blink without any CPU interaction. To
 support this feature, a LED driver can optionally implement the
-blink_set() function (see <linux/leds.h>). If implemented, triggers can
-attempt to use it before falling back to software timers. The blink_set()
-function should return 0 if the blink setting is supported, or -EINVAL
-otherwise, which means that LED blinking will be handled by software.
-
-The blink_set() function should choose a user friendly blinking
-value if it is called with *delay_on==0 && *delay_off==0 parameters. In
-this case the driver should give back the chosen value through delay_on
-and delay_off parameters to the leds subsystem.
+blink_set() function (see <linux/leds.h>). To set an LED to blinking,
+however, it is better to use use the API function led_blink_set(),
+as it will check and implement software fallback if necessary.
+
+To turn off blinking again, use the API function led_brightness_set()
+as that will not just set the LED brightness but also stop any software
+timers that may have been required for blinking.
+
+The blink_set() function should choose a user friendly blinking value
+if it is called with *delay_on==0 && *delay_off==0 parameters. In this
+case the driver should give back the chosen value through delay_on and
+delay_off parameters to the leds subsystem.
 
 Setting the brightness to zero with brightness_set() callback function
 should completely turn off the LED and cancel the previously programmed
diff --git a/Documentation/leds/leds-lp5521.txt b/Documentation/leds/leds-lp5521.txt
new file mode 100644 (file)
index 0000000..c4d8d15
--- /dev/null
@@ -0,0 +1,88 @@
+Kernel driver for lp5521
+========================
+
+* National Semiconductor LP5521 led driver chip
+* Datasheet: http://www.national.com/pf/LP/LP5521.html
+
+Authors: Mathias Nyman, Yuri Zaporozhets, Samu Onkalo
+Contact: Samu Onkalo (samu.p.onkalo-at-nokia.com)
+
+Description
+-----------
+
+LP5521 can drive up to 3 channels. Leds can be controlled directly via
+the led class control interface. Channels have generic names:
+lp5521:channelx, where x is 0 .. 2
+
+All three channels can be also controlled using the engine micro programs.
+More details of the instructions can be found from the public data sheet.
+
+Control interface for the engines:
+x is 1 .. 3
+enginex_mode : disabled, load, run
+enginex_load : store program (visible only in engine load mode)
+
+Example (start to blink the channel 2 led):
+cd   /sys/class/leds/lp5521:channel2/device
+echo "load" > engine3_mode
+echo "037f4d0003ff6000" > engine3_load
+echo "run" > engine3_mode
+
+stop the engine:
+echo "disabled" > engine3_mode
+
+sysfs contains a selftest entry.
+The test communicates with the chip and checks that
+the clock mode is automatically set to the requested one.
+
+Each channel has its own led current settings.
+/sys/class/leds/lp5521:channel0/led_current - RW
+/sys/class/leds/lp5521:channel0/max_current - RO
+Format: 10x mA i.e 10 means 1.0 mA
+
+example platform data:
+
+Note: chan_nr can have values between 0 and 2.
+
+static struct lp5521_led_config lp5521_led_config[] = {
+        {
+                .chan_nr        = 0,
+                .led_current    = 50,
+               .max_current    = 130,
+        }, {
+                .chan_nr        = 1,
+                .led_current    = 0,
+               .max_current    = 130,
+        }, {
+                .chan_nr        = 2,
+                .led_current    = 0,
+               .max_current    = 130,
+        }
+};
+
+static int lp5521_setup(void)
+{
+       /* setup HW resources */
+}
+
+static void lp5521_release(void)
+{
+       /* Release HW resources */
+}
+
+static void lp5521_enable(bool state)
+{
+       /* Control of chip enable signal */
+}
+
+static struct lp5521_platform_data lp5521_platform_data = {
+        .led_config     = lp5521_led_config,
+        .num_channels   = ARRAY_SIZE(lp5521_led_config),
+        .clock_mode     = LP5521_CLOCK_EXT,
+        .setup_resources   = lp5521_setup,
+        .release_resources = lp5521_release,
+        .enable            = lp5521_enable,
+};
+
+If the current is set to 0 in the platform data, that channel is
+disabled and it is not visible in the sysfs.
diff --git a/Documentation/leds/leds-lp5523.txt b/Documentation/leds/leds-lp5523.txt
new file mode 100644 (file)
index 0000000..fad2feb
--- /dev/null
@@ -0,0 +1,83 @@
+Kernel driver for lp5523
+========================
+
+* National Semiconductor LP5523 led driver chip
+* Datasheet: http://www.national.com/pf/LP/LP5523.html
+
+Authors: Mathias Nyman, Yuri Zaporozhets, Samu Onkalo
+Contact: Samu Onkalo (samu.p.onkalo-at-nokia.com)
+
+Description
+-----------
+LP5523 can drive up to 9 channels. Leds can be controlled directly via
+the led class control interface. Channels have generic names:
+lp5523:channelx where x is 0...8
+
+The chip provides 3 engines. Each engine can control channels without
+interaction from the main CPU. Details of the micro engine code can be found
+from the public data sheet. Leds can be muxed to different channels.
+
+Control interface for the engines:
+x is 1 .. 3
+enginex_mode : disabled, load, run
+enginex_load : microcode load (visible only in load mode)
+enginex_leds : led mux control (visible only in load mode)
+
+cd /sys/class/leds/lp5523:channel2/device
+echo "load" > engine3_mode
+echo "9d80400004ff05ff437f0000" > engine3_load
+echo "111111111" > engine3_leds
+echo "run" > engine3_mode
+
+sysfs contains a selftest entry. It measures each channel
+voltage level and checks if it looks reasonable. If the level is too high,
+the led is missing; if the level is too low, there is a short circuit.
+
+Selftest uses always the current from the platform data.
+
+Each channel contains led current settings.
+/sys/class/leds/lp5523:channel2/led_current - RW
+/sys/class/leds/lp5523:channel2/max_current - RO
+Format: 10x mA i.e 10 means 1.0 mA
+
+Example platform data:
+
+Note - chan_nr can have values between 0 and 8.
+
+static struct lp5523_led_config lp5523_led_config[] = {
+        {
+                .chan_nr        = 0,
+                .led_current    = 50,
+               .max_current    = 130,
+        },
+...
+        }, {
+                .chan_nr        = 8,
+                .led_current    = 50,
+               .max_current    = 130,
+        }
+};
+
+static int lp5523_setup(void)
+{
+       /* Setup HW resources */
+}
+
+static void lp5523_release(void)
+{
+       /* Release HW resources */
+}
+
+static void lp5523_enable(bool state)
+{
+       /* Control chip enable signal */
+}
+
+static struct lp5523_platform_data lp5523_platform_data = {
+        .led_config     = lp5523_led_config,
+        .num_channels   = ARRAY_SIZE(lp5523_led_config),
+        .clock_mode     = LP5523_CLOCK_EXT,
+        .setup_resources   = lp5523_setup,
+        .release_resources = lp5523_release,
+        .enable            = lp5523_enable,
+};
index 3894eaa..209e158 100644 (file)
@@ -28,6 +28,7 @@ show up in /proc/sys/kernel:
 - core_uses_pid
 - ctrl-alt-del
 - dentry-state
+- dmesg_restrict
 - domainname
 - hostname
 - hotplug
@@ -213,6 +214,19 @@ to decide what to do with it.
 
 ==============================================================
 
+dmesg_restrict:
+
+This toggle indicates whether unprivileged users are prevented from using
+dmesg(8) to view messages from the kernel's log buffer.  When
+dmesg_restrict is set to (0) there are no restrictions.  When
+dmesg_restrict is set set to (1), users must have CAP_SYS_ADMIN to use
+dmesg(8).
+
+The kernel config option CONFIG_SECURITY_DMESG_RESTRICT sets the default
+value of dmesg_restrict.
+
+==============================================================
+
 domainname & hostname:
 
 These files can be used to set the NIS/YP domainname and the
index 2cd899f..b7c5bab 100644 (file)
@@ -38,8 +38,8 @@ struct pt_regs {
 
 struct task_struct;
 
-extern long subarch_ptrace(struct task_struct *child, long request, long addr,
-                          long data);
+extern long subarch_ptrace(struct task_struct *child, long request,
+       unsigned long addr, unsigned long data);
 extern unsigned long getreg(struct task_struct *child, int regno);
 extern int putreg(struct task_struct *child, int regno, unsigned long value);
 extern int get_fpregs(struct user_i387_struct __user *buf,
index a5e33f2..701b672 100644 (file)
@@ -122,7 +122,7 @@ long arch_ptrace(struct task_struct *child, long request,
                break;
 
        case PTRACE_SET_THREAD_AREA:
-               ret = ptrace_set_thread_area(child, addr, datavp);
+               ret = ptrace_set_thread_area(child, addr, vp);
                break;
 
        case PTRACE_FAULTINFO: {
index de30782..75586f1 100644 (file)
@@ -504,7 +504,6 @@ err:
 
 static void pcrypt_fini_padata(struct padata_pcrypt *pcrypt)
 {
-       kobject_put(&pcrypt->pinst->kobj);
        free_cpumask_var(pcrypt->cb_cpumask->mask);
        kfree(pcrypt->cb_cpumask);
 
index cc2a88d..77b8fd2 100644 (file)
@@ -10,7 +10,7 @@ menuconfig NEW_LEDS
 if NEW_LEDS
 
 config LEDS_CLASS
-       tristate "LED Class Support"
+       bool "LED Class Support"
        help
          This option enables the led sysfs class in /sys/class/leds.  You'll
          need this to do anything useful with LEDs.  If unsure, say N.
@@ -176,6 +176,24 @@ config LEDS_LP3944
          To compile this driver as a module, choose M here: the
          module will be called leds-lp3944.
 
+config LEDS_LP5521
+       tristate "LED Support for N.S. LP5521 LED driver chip"
+       depends on LEDS_CLASS && I2C
+       help
+         If you say yes here you get support for the National Semiconductor
+         LP5521 LED driver. It is 3 channel chip with programmable engines.
+         Driver provides direct control via LED class and interface for
+         programming the engines.
+
+config LEDS_LP5523
+       tristate "LED Support for N.S. LP5523 LED driver chip"
+       depends on LEDS_CLASS && I2C
+       help
+         If you say yes here you get support for the National Semiconductor
+         LP5523 LED driver. It is 9 channel chip with programmable engines.
+         Driver provides direct control via LED class and interface for
+         programming the engines.
+
 config LEDS_CLEVO_MAIL
        tristate "Mail LED on Clevo notebook"
        depends on X86 && SERIO_I8042 && DMI
index 9c96db4..aae6989 100644 (file)
@@ -23,6 +23,8 @@ obj-$(CONFIG_LEDS_SUNFIRE)            += leds-sunfire.o
 obj-$(CONFIG_LEDS_PCA9532)             += leds-pca9532.o
 obj-$(CONFIG_LEDS_GPIO)                        += leds-gpio.o
 obj-$(CONFIG_LEDS_LP3944)              += leds-lp3944.o
+obj-$(CONFIG_LEDS_LP5521)              += leds-lp5521.o
+obj-$(CONFIG_LEDS_LP5523)              += leds-lp5523.o
 obj-$(CONFIG_LEDS_CLEVO_MAIL)          += leds-clevo-mail.o
 obj-$(CONFIG_LEDS_HP6XX)               += leds-hp6xx.o
 obj-$(CONFIG_LEDS_FSG)                 += leds-fsg.o
index 2606600..211e21f 100644 (file)
@@ -81,6 +81,79 @@ static struct device_attribute led_class_attrs[] = {
        __ATTR_NULL,
 };
 
+static void led_timer_function(unsigned long data)
+{
+       struct led_classdev *led_cdev = (void *)data;
+       unsigned long brightness;
+       unsigned long delay;
+
+       if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
+               led_set_brightness(led_cdev, LED_OFF);
+               return;
+       }
+
+       brightness = led_get_brightness(led_cdev);
+       if (!brightness) {
+               /* Time to switch the LED on. */
+               brightness = led_cdev->blink_brightness;
+               delay = led_cdev->blink_delay_on;
+       } else {
+               /* Store the current brightness value to be able
+                * to restore it when the delay_off period is over.
+                */
+               led_cdev->blink_brightness = brightness;
+               brightness = LED_OFF;
+               delay = led_cdev->blink_delay_off;
+       }
+
+       led_set_brightness(led_cdev, brightness);
+
+       mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
+}
+
+static void led_stop_software_blink(struct led_classdev *led_cdev)
+{
+       /* deactivate previous settings */
+       del_timer_sync(&led_cdev->blink_timer);
+       led_cdev->blink_delay_on = 0;
+       led_cdev->blink_delay_off = 0;
+}
+
+static void led_set_software_blink(struct led_classdev *led_cdev,
+                                  unsigned long delay_on,
+                                  unsigned long delay_off)
+{
+       int current_brightness;
+
+       current_brightness = led_get_brightness(led_cdev);
+       if (current_brightness)
+               led_cdev->blink_brightness = current_brightness;
+       if (!led_cdev->blink_brightness)
+               led_cdev->blink_brightness = led_cdev->max_brightness;
+
+       if (delay_on == led_cdev->blink_delay_on &&
+           delay_off == led_cdev->blink_delay_off)
+               return;
+
+       led_stop_software_blink(led_cdev);
+
+       led_cdev->blink_delay_on = delay_on;
+       led_cdev->blink_delay_off = delay_off;
+
+       /* never on - don't blink */
+       if (!delay_on)
+               return;
+
+       /* never off - just set to brightness */
+       if (!delay_off) {
+               led_set_brightness(led_cdev, led_cdev->blink_brightness);
+               return;
+       }
+
+       mod_timer(&led_cdev->blink_timer, jiffies + 1);
+}
+
+
 /**
  * led_classdev_suspend - suspend an led_classdev.
  * @led_cdev: the led_classdev to suspend.
@@ -148,6 +221,10 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
 
        led_update_brightness(led_cdev);
 
+       init_timer(&led_cdev->blink_timer);
+       led_cdev->blink_timer.function = led_timer_function;
+       led_cdev->blink_timer.data = (unsigned long)led_cdev;
+
 #ifdef CONFIG_LEDS_TRIGGERS
        led_trigger_set_default(led_cdev);
 #endif
@@ -157,7 +234,6 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
 
        return 0;
 }
-
 EXPORT_SYMBOL_GPL(led_classdev_register);
 
 /**
@@ -175,6 +251,9 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
        up_write(&led_cdev->trigger_lock);
 #endif
 
+       /* Stop blinking */
+       led_brightness_set(led_cdev, LED_OFF);
+
        device_unregister(led_cdev->dev);
 
        down_write(&leds_list_lock);
@@ -183,6 +262,30 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
 }
 EXPORT_SYMBOL_GPL(led_classdev_unregister);
 
+void led_blink_set(struct led_classdev *led_cdev,
+                  unsigned long *delay_on,
+                  unsigned long *delay_off)
+{
+       if (led_cdev->blink_set &&
+           led_cdev->blink_set(led_cdev, delay_on, delay_off))
+               return;
+
+       /* blink with 1 Hz as default if nothing specified */
+       if (!*delay_on && !*delay_off)
+               *delay_on = *delay_off = 500;
+
+       led_set_software_blink(led_cdev, *delay_on, *delay_off);
+}
+EXPORT_SYMBOL(led_blink_set);
+
+void led_brightness_set(struct led_classdev *led_cdev,
+                       enum led_brightness brightness)
+{
+       led_stop_software_blink(led_cdev);
+       led_cdev->brightness_set(led_cdev, brightness);
+}
+EXPORT_SYMBOL(led_brightness_set);
+
 static int __init leds_init(void)
 {
        leds_class = class_create(THIS_MODULE, "leds");
index f1c00db..c41eb61 100644 (file)
@@ -113,7 +113,7 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
                if (led_cdev->trigger->deactivate)
                        led_cdev->trigger->deactivate(led_cdev);
                led_cdev->trigger = NULL;
-               led_set_brightness(led_cdev, LED_OFF);
+               led_brightness_set(led_cdev, LED_OFF);
        }
        if (trigger) {
                write_lock_irqsave(&trigger->leddev_list_lock, flags);
index ea57e05..4d9fa38 100644 (file)
@@ -316,7 +316,7 @@ static struct of_platform_driver of_gpio_leds_driver = {
 
 static int __init gpio_led_init(void)
 {
-       int ret;
+       int ret = 0;
 
 #ifdef CONFIG_LEDS_GPIO_PLATFORM       
        ret = platform_driver_register(&gpio_led_driver);
diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c
new file mode 100644 (file)
index 0000000..3782f31
--- /dev/null
@@ -0,0 +1,821 @@
+/*
+ * LP5521 LED chip driver.
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/leds.h>
+#include <linux/leds-lp5521.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+#define LP5521_PROGRAM_LENGTH          32      /* in bytes */
+
+#define LP5521_MAX_LEDS                        3       /* Maximum number of LEDs */
+#define LP5521_MAX_ENGINES             3       /* Maximum number of engines */
+
+#define LP5521_ENG_MASK_BASE           0x30    /* 00110000 */
+#define LP5521_ENG_STATUS_MASK         0x07    /* 00000111 */
+
+#define LP5521_CMD_LOAD                        0x15    /* 00010101 */
+#define LP5521_CMD_RUN                 0x2a    /* 00101010 */
+#define LP5521_CMD_DIRECT              0x3f    /* 00111111 */
+#define LP5521_CMD_DISABLED            0x00    /* 00000000 */
+
+/* Registers */
+#define LP5521_REG_ENABLE              0x00
+#define LP5521_REG_OP_MODE             0x01
+#define LP5521_REG_R_PWM               0x02
+#define LP5521_REG_G_PWM               0x03
+#define LP5521_REG_B_PWM               0x04
+#define LP5521_REG_R_CURRENT           0x05
+#define LP5521_REG_G_CURRENT           0x06
+#define LP5521_REG_B_CURRENT           0x07
+#define LP5521_REG_CONFIG              0x08
+#define LP5521_REG_R_CHANNEL_PC                0x09
+#define LP5521_REG_G_CHANNEL_PC                0x0A
+#define LP5521_REG_B_CHANNEL_PC                0x0B
+#define LP5521_REG_STATUS              0x0C
+#define LP5521_REG_RESET               0x0D
+#define LP5521_REG_GPO                 0x0E
+#define LP5521_REG_R_PROG_MEM          0x10
+#define LP5521_REG_G_PROG_MEM          0x30
+#define LP5521_REG_B_PROG_MEM          0x50
+
+#define LP5521_PROG_MEM_BASE           LP5521_REG_R_PROG_MEM
+#define LP5521_PROG_MEM_SIZE           0x20
+
+/* Base register to set LED current */
+#define LP5521_REG_LED_CURRENT_BASE    LP5521_REG_R_CURRENT
+
+/* Base register to set the brightness */
+#define LP5521_REG_LED_PWM_BASE                LP5521_REG_R_PWM
+
+/* Bits in ENABLE register */
+#define LP5521_MASTER_ENABLE           0x40    /* Chip master enable */
+#define LP5521_LOGARITHMIC_PWM         0x80    /* Logarithmic PWM adjustment */
+#define LP5521_EXEC_RUN                        0x2A
+
+/* Bits in CONFIG register */
+#define LP5521_PWM_HF                  0x40    /* PWM: 0 = 256Hz, 1 = 558Hz */
+#define LP5521_PWRSAVE_EN              0x20    /* 1 = Power save mode */
+#define LP5521_CP_MODE_OFF             0       /* Charge pump (CP) off */
+#define LP5521_CP_MODE_BYPASS          8       /* CP forced to bypass mode */
+#define LP5521_CP_MODE_1X5             0x10    /* CP forced to 1.5x mode */
+#define LP5521_CP_MODE_AUTO            0x18    /* Automatic mode selection */
+#define LP5521_R_TO_BATT               4       /* R out: 0 = CP, 1 = Vbat */
+#define LP5521_CLK_SRC_EXT             0       /* Ext-clk source (CLK_32K) */
+#define LP5521_CLK_INT                 1       /* Internal clock */
+#define LP5521_CLK_AUTO                        2       /* Automatic clock selection */
+
+/* Status */
+#define LP5521_EXT_CLK_USED            0x08
+
+struct lp5521_engine {
+       const struct attribute_group *attributes;
+       int             id;
+       u8              mode;
+       u8              prog_page;
+       u8              engine_mask;
+};
+
+struct lp5521_led {
+       int                     id;
+       u8                      chan_nr;
+       u8                      led_current;
+       u8                      max_current;
+       struct led_classdev     cdev;
+       struct work_struct      brightness_work;
+       u8                      brightness;
+};
+
+struct lp5521_chip {
+       struct lp5521_platform_data *pdata;
+       struct mutex            lock; /* Serialize control */
+       struct i2c_client       *client;
+       struct lp5521_engine    engines[LP5521_MAX_ENGINES];
+       struct lp5521_led       leds[LP5521_MAX_LEDS];
+       u8                      num_channels;
+       u8                      num_leds;
+};
+
+#define cdev_to_led(c)         container_of(c, struct lp5521_led, cdev)
+#define engine_to_lp5521(eng)  container_of((eng), struct lp5521_chip, \
+                                               engines[(eng)->id - 1])
+#define led_to_lp5521(led)     container_of((led), struct lp5521_chip, \
+                                               leds[(led)->id])
+
+static void lp5521_led_brightness_work(struct work_struct *work);
+
+static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value)
+{
+       return i2c_smbus_write_byte_data(client, reg, value);
+}
+
+static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf)
+{
+       s32 ret;
+
+       ret = i2c_smbus_read_byte_data(client, reg);
+       if (ret < 0)
+               return -EIO;
+
+       *buf = ret;
+       return 0;
+}
+
+static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode)
+{
+       struct lp5521_chip *chip = engine_to_lp5521(engine);
+       struct i2c_client *client = chip->client;
+       int ret;
+       u8 engine_state;
+
+       /* Only transition between RUN and DIRECT mode are handled here */
+       if (mode == LP5521_CMD_LOAD)
+               return 0;
+
+       if (mode == LP5521_CMD_DISABLED)
+               mode = LP5521_CMD_DIRECT;
+
+       ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state);
+
+       /* set mode only for this engine */
+       engine_state &= ~(engine->engine_mask);
+       mode &= engine->engine_mask;
+       engine_state |= mode;
+       ret |= lp5521_write(client, LP5521_REG_OP_MODE, engine_state);
+
+       return ret;
+}
+
+static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern)
+{
+       struct lp5521_chip *chip = engine_to_lp5521(eng);
+       struct i2c_client *client = chip->client;
+       int ret;
+       int addr;
+       u8 mode;
+
+       /* move current engine to direct mode and remember the state */
+       ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT);
+       usleep_range(1000, 10000);
+       ret |= lp5521_read(client, LP5521_REG_OP_MODE, &mode);
+
+       /* For loading, all the engines to load mode */
+       lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
+       usleep_range(1000, 10000);
+       lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_LOAD);
+       usleep_range(1000, 10000);
+
+       addr = LP5521_PROG_MEM_BASE + eng->prog_page * LP5521_PROG_MEM_SIZE;
+       i2c_smbus_write_i2c_block_data(client,
+                               addr,
+                               LP5521_PROG_MEM_SIZE,
+                               pattern);
+
+       ret |= lp5521_write(client, LP5521_REG_OP_MODE, mode);
+       return ret;
+}
+
+static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr)
+{
+       return lp5521_write(chip->client,
+                   LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr,
+                   curr);
+}
+
+static void lp5521_init_engine(struct lp5521_chip *chip,
+                       const struct attribute_group *attr_group)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(chip->engines); i++) {
+               chip->engines[i].id = i + 1;
+               chip->engines[i].engine_mask = LP5521_ENG_MASK_BASE >> (i * 2);
+               chip->engines[i].prog_page = i;
+               chip->engines[i].attributes = &attr_group[i];
+       }
+}
+
+static int lp5521_configure(struct i2c_client *client,
+                       const struct attribute_group *attr_group)
+{
+       struct lp5521_chip *chip = i2c_get_clientdata(client);
+       int ret;
+
+       lp5521_init_engine(chip, attr_group);
+
+       lp5521_write(client, LP5521_REG_RESET, 0xff);
+
+       usleep_range(10000, 20000);
+
+       /* Set all PWMs to direct control mode */
+       ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F);
+
+       /* Enable auto-powersave, set charge pump to auto, red to battery */
+       ret |= lp5521_write(client, LP5521_REG_CONFIG,
+               LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT);
+
+       /* Initialize all channels PWM to zero -> leds off */
+       ret |= lp5521_write(client, LP5521_REG_R_PWM, 0);
+       ret |= lp5521_write(client, LP5521_REG_G_PWM, 0);
+       ret |= lp5521_write(client, LP5521_REG_B_PWM, 0);
+
+       /* Set engines are set to run state when OP_MODE enables engines */
+       ret |= lp5521_write(client, LP5521_REG_ENABLE,
+                       LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM |
+                       LP5521_EXEC_RUN);
+       /* enable takes 500us */
+       usleep_range(500, 20000);
+
+       return ret;
+}
+
+static int lp5521_run_selftest(struct lp5521_chip *chip, char *buf)
+{
+       int ret;
+       u8 status;
+
+       ret = lp5521_read(chip->client, LP5521_REG_STATUS, &status);
+       if (ret < 0)
+               return ret;
+
+       /* Check that ext clock is really in use if requested */
+       if (chip->pdata && chip->pdata->clock_mode == LP5521_CLOCK_EXT)
+               if  ((status & LP5521_EXT_CLK_USED) == 0)
+                       return -EIO;
+       return 0;
+}
+
+static void lp5521_set_brightness(struct led_classdev *cdev,
+                            enum led_brightness brightness)
+{
+       struct lp5521_led *led = cdev_to_led(cdev);
+       led->brightness = (u8)brightness;
+       schedule_work(&led->brightness_work);
+}
+
+static void lp5521_led_brightness_work(struct work_struct *work)
+{
+       struct lp5521_led *led = container_of(work,
+                                             struct lp5521_led,
+                                             brightness_work);
+       struct lp5521_chip *chip = led_to_lp5521(led);
+       struct i2c_client *client = chip->client;
+
+       mutex_lock(&chip->lock);
+       lp5521_write(client, LP5521_REG_LED_PWM_BASE + led->chan_nr,
+               led->brightness);
+       mutex_unlock(&chip->lock);
+}
+
+/* Detect the chip by setting its ENABLE register and reading it back. */
+static int lp5521_detect(struct i2c_client *client)
+{
+       int ret;
+       u8 buf;
+
+       ret = lp5521_write(client, LP5521_REG_ENABLE,
+                       LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM);
+       if (ret)
+               return ret;
+       usleep_range(1000, 10000);
+       ret = lp5521_read(client, LP5521_REG_ENABLE, &buf);
+       if (ret)
+               return ret;
+       if (buf != (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM))
+               return -ENODEV;
+
+       return 0;
+}
+
+/* Set engine mode and create appropriate sysfs attributes, if required. */
+static int lp5521_set_mode(struct lp5521_engine *engine, u8 mode)
+{
+       struct lp5521_chip *chip = engine_to_lp5521(engine);
+       struct i2c_client *client = chip->client;
+       struct device *dev = &client->dev;
+       int ret = 0;
+
+       /* if in that mode already do nothing, except for run */
+       if (mode == engine->mode && mode != LP5521_CMD_RUN)
+               return 0;
+
+       if (mode == LP5521_CMD_RUN) {
+               ret = lp5521_set_engine_mode(engine, LP5521_CMD_RUN);
+       } else if (mode == LP5521_CMD_LOAD) {
+               lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED);
+               lp5521_set_engine_mode(engine, LP5521_CMD_LOAD);
+
+               ret = sysfs_create_group(&dev->kobj, engine->attributes);
+               if (ret)
+                       return ret;
+       } else if (mode == LP5521_CMD_DISABLED) {
+               lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED);
+       }
+
+       /* remove load attribute from sysfs if not in load mode */
+       if (engine->mode == LP5521_CMD_LOAD && mode != LP5521_CMD_LOAD)
+               sysfs_remove_group(&dev->kobj, engine->attributes);
+
+       engine->mode = mode;
+
+       return ret;
+}
+
+static int lp5521_do_store_load(struct lp5521_engine *engine,
+                               const char *buf, size_t len)
+{
+       struct lp5521_chip *chip = engine_to_lp5521(engine);
+       struct i2c_client *client = chip->client;
+       int  ret, nrchars, offset = 0, i = 0;
+       char c[3];
+       unsigned cmd;
+       u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
+
+       while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) {
+               /* separate sscanfs because length is working only for %s */
+               ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
+               ret = sscanf(c, "%2x", &cmd);
+               if (ret != 1)
+                       goto fail;
+               pattern[i] = (u8)cmd;
+
+               offset += nrchars;
+               i++;
+       }
+
+       /* Each instruction is 16bit long. Check that length is even */
+       if (i % 2)
+               goto fail;
+
+       mutex_lock(&chip->lock);
+       ret = lp5521_load_program(engine, pattern);
+       mutex_unlock(&chip->lock);
+
+       if (ret) {
+               dev_err(&client->dev, "failed loading pattern\n");
+               return ret;
+       }
+
+       return len;
+fail:
+       dev_err(&client->dev, "wrong pattern format\n");
+       return -EINVAL;
+}
+
+static ssize_t store_engine_load(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t len, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5521_chip *chip = i2c_get_clientdata(client);
+       return lp5521_do_store_load(&chip->engines[nr - 1], buf, len);
+}
+
+#define store_load(nr)                                                 \
+static ssize_t store_engine##nr##_load(struct device *dev,             \
+                                    struct device_attribute *attr,     \
+                                    const char *buf, size_t len)       \
+{                                                                      \
+       return store_engine_load(dev, attr, buf, len, nr);              \
+}
+store_load(1)
+store_load(2)
+store_load(3)
+
+static ssize_t show_engine_mode(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5521_chip *chip = i2c_get_clientdata(client);
+       switch (chip->engines[nr - 1].mode) {
+       case LP5521_CMD_RUN:
+               return sprintf(buf, "run\n");
+       case LP5521_CMD_LOAD:
+               return sprintf(buf, "load\n");
+       case LP5521_CMD_DISABLED:
+               return sprintf(buf, "disabled\n");
+       default:
+               return sprintf(buf, "disabled\n");
+       }
+}
+
+#define show_mode(nr)                                                  \
+static ssize_t show_engine##nr##_mode(struct device *dev,              \
+                                   struct device_attribute *attr,      \
+                                   char *buf)                          \
+{                                                                      \
+       return show_engine_mode(dev, attr, buf, nr);                    \
+}
+show_mode(1)
+show_mode(2)
+show_mode(3)
+
+static ssize_t store_engine_mode(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t len, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5521_chip *chip = i2c_get_clientdata(client);
+       struct lp5521_engine *engine = &chip->engines[nr - 1];
+       mutex_lock(&chip->lock);
+
+       if (!strncmp(buf, "run", 3))
+               lp5521_set_mode(engine, LP5521_CMD_RUN);
+       else if (!strncmp(buf, "load", 4))
+               lp5521_set_mode(engine, LP5521_CMD_LOAD);
+       else if (!strncmp(buf, "disabled", 8))
+               lp5521_set_mode(engine, LP5521_CMD_DISABLED);
+
+       mutex_unlock(&chip->lock);
+       return len;
+}
+
+#define store_mode(nr)                                                 \
+static ssize_t store_engine##nr##_mode(struct device *dev,             \
+                                    struct device_attribute *attr,     \
+                                    const char *buf, size_t len)       \
+{                                                                      \
+       return store_engine_mode(dev, attr, buf, len, nr);              \
+}
+store_mode(1)
+store_mode(2)
+store_mode(3)
+
+static ssize_t show_max_current(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lp5521_led *led = cdev_to_led(led_cdev);
+
+       return sprintf(buf, "%d\n", led->max_current);
+}
+
+static ssize_t show_current(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lp5521_led *led = cdev_to_led(led_cdev);
+
+       return sprintf(buf, "%d\n", led->led_current);
+}
+
+static ssize_t store_current(struct device *dev,
+                            struct device_attribute *attr,
+                            const char *buf, size_t len)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lp5521_led *led = cdev_to_led(led_cdev);
+       struct lp5521_chip *chip = led_to_lp5521(led);
+       ssize_t ret;
+       unsigned long curr;
+
+       if (strict_strtoul(buf, 0, &curr))
+               return -EINVAL;
+
+       if (curr > led->max_current)
+               return -EINVAL;
+
+       mutex_lock(&chip->lock);
+       ret = lp5521_set_led_current(chip, led->id, curr);
+       mutex_unlock(&chip->lock);
+
+       if (ret < 0)
+               return ret;
+
+       led->led_current = (u8)curr;
+
+       return len;
+}
+
+static ssize_t lp5521_selftest(struct device *dev,
+                              struct device_attribute *attr,
+                              char *buf)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5521_chip *chip = i2c_get_clientdata(client);
+       int ret;
+
+       mutex_lock(&chip->lock);
+       ret = lp5521_run_selftest(chip, buf);
+       mutex_unlock(&chip->lock);
+       return sprintf(buf, "%s\n", ret ? "FAIL" : "OK");
+}
+
+/* led class device attributes */
+static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current);
+static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL);
+
+static struct attribute *lp5521_led_attributes[] = {
+       &dev_attr_led_current.attr,
+       &dev_attr_max_current.attr,
+       NULL,
+};
+
+static struct attribute_group lp5521_led_attribute_group = {
+       .attrs = lp5521_led_attributes
+};
+
+/* device attributes */
+static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO,
+                  show_engine1_mode, store_engine1_mode);
+static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO,
+                  show_engine2_mode, store_engine2_mode);
+static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO,
+                  show_engine3_mode, store_engine3_mode);
+static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load);
+static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load);
+static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load);
+static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL);
+
+static struct attribute *lp5521_attributes[] = {
+       &dev_attr_engine1_mode.attr,
+       &dev_attr_engine2_mode.attr,
+       &dev_attr_engine3_mode.attr,
+       &dev_attr_selftest.attr,
+       NULL
+};
+
+static struct attribute *lp5521_engine1_attributes[] = {
+       &dev_attr_engine1_load.attr,
+       NULL
+};
+
+static struct attribute *lp5521_engine2_attributes[] = {
+       &dev_attr_engine2_load.attr,
+       NULL
+};
+
+static struct attribute *lp5521_engine3_attributes[] = {
+       &dev_attr_engine3_load.attr,
+       NULL
+};
+
+static const struct attribute_group lp5521_group = {
+       .attrs = lp5521_attributes,
+};
+
+static const struct attribute_group lp5521_engine_group[] = {
+       {.attrs = lp5521_engine1_attributes },
+       {.attrs = lp5521_engine2_attributes },
+       {.attrs = lp5521_engine3_attributes },
+};
+
+static int lp5521_register_sysfs(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       return sysfs_create_group(&dev->kobj, &lp5521_group);
+}
+
+static void lp5521_unregister_sysfs(struct i2c_client *client)
+{
+       struct lp5521_chip *chip = i2c_get_clientdata(client);
+       struct device *dev = &client->dev;
+       int i;
+
+       sysfs_remove_group(&dev->kobj, &lp5521_group);
+
+       for (i = 0; i <  ARRAY_SIZE(chip->engines); i++) {
+               if (chip->engines[i].mode == LP5521_CMD_LOAD)
+                       sysfs_remove_group(&dev->kobj,
+                                       chip->engines[i].attributes);
+       }
+
+       for (i = 0; i < chip->num_leds; i++)
+               sysfs_remove_group(&chip->leds[i].cdev.dev->kobj,
+                               &lp5521_led_attribute_group);
+}
+
+static int __init lp5521_init_led(struct lp5521_led *led,
+                               struct i2c_client *client,
+                               int chan, struct lp5521_platform_data *pdata)
+{
+       struct device *dev = &client->dev;
+       char name[32];
+       int res;
+
+       if (chan >= LP5521_MAX_LEDS)
+               return -EINVAL;
+
+       if (pdata->led_config[chan].led_current == 0)
+               return 0;
+
+       led->led_current = pdata->led_config[chan].led_current;
+       led->max_current = pdata->led_config[chan].max_current;
+       led->chan_nr = pdata->led_config[chan].chan_nr;
+
+       if (led->chan_nr >= LP5521_MAX_LEDS) {
+               dev_err(dev, "Use channel numbers between 0 and %d\n",
+                       LP5521_MAX_LEDS - 1);
+               return -EINVAL;
+       }
+
+       snprintf(name, sizeof(name), "%s:channel%d", client->name, chan);
+       led->cdev.brightness_set = lp5521_set_brightness;
+       led->cdev.name = name;
+       res = led_classdev_register(dev, &led->cdev);
+       if (res < 0) {
+               dev_err(dev, "couldn't register led on channel %d\n", chan);
+               return res;
+       }
+
+       res = sysfs_create_group(&led->cdev.dev->kobj,
+                       &lp5521_led_attribute_group);
+       if (res < 0) {
+               dev_err(dev, "couldn't register current attribute\n");
+               led_classdev_unregister(&led->cdev);
+               return res;
+       }
+       return 0;
+}
+
+static int lp5521_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct lp5521_chip              *chip;
+       struct lp5521_platform_data     *pdata;
+       int ret, i, led;
+
+       chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+       if (!chip)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, chip);
+       chip->client = client;
+
+       pdata = client->dev.platform_data;
+
+       if (!pdata) {
+               dev_err(&client->dev, "no platform data\n");
+               ret = -EINVAL;
+               goto fail1;
+       }
+
+       mutex_init(&chip->lock);
+
+       chip->pdata   = pdata;
+
+       if (pdata->setup_resources) {
+               ret = pdata->setup_resources();
+               if (ret < 0)
+                       goto fail1;
+       }
+
+       if (pdata->enable) {
+               pdata->enable(0);
+               usleep_range(1000, 10000);
+               pdata->enable(1);
+               usleep_range(1000, 10000); /* Spec says min 500us */
+       }
+
+       ret = lp5521_detect(client);
+
+       if (ret) {
+               dev_err(&client->dev, "Chip not found\n");
+               goto fail2;
+       }
+
+       dev_info(&client->dev, "%s programmable led chip found\n", id->name);
+
+       ret = lp5521_configure(client, lp5521_engine_group);
+       if (ret < 0) {
+               dev_err(&client->dev, "error configuring chip\n");
+               goto fail2;
+       }
+
+       /* Initialize leds */
+       chip->num_channels = pdata->num_channels;
+       chip->num_leds = 0;
+       led = 0;
+       for (i = 0; i < pdata->num_channels; i++) {
+               /* Do not initialize channels that are not connected */
+               if (pdata->led_config[i].led_current == 0)
+                       continue;
+
+               ret = lp5521_init_led(&chip->leds[led], client, i, pdata);
+               if (ret) {
+                       dev_err(&client->dev, "error initializing leds\n");
+                       goto fail3;
+               }
+               chip->num_leds++;
+
+               chip->leds[led].id = led;
+               /* Set initial LED current */
+               lp5521_set_led_current(chip, led,
+                               chip->leds[led].led_current);
+
+               INIT_WORK(&(chip->leds[led].brightness_work),
+                       lp5521_led_brightness_work);
+
+               led++;
+       }
+
+       ret = lp5521_register_sysfs(client);
+       if (ret) {
+               dev_err(&client->dev, "registering sysfs failed\n");
+               goto fail3;
+       }
+       return ret;
+fail3:
+       for (i = 0; i < chip->num_leds; i++) {
+               led_classdev_unregister(&chip->leds[i].cdev);
+               cancel_work_sync(&chip->leds[i].brightness_work);
+       }
+fail2:
+       if (pdata->enable)
+               pdata->enable(0);
+       if (pdata->release_resources)
+               pdata->release_resources();
+fail1:
+       kfree(chip);
+       return ret;
+}
+
+static int lp5521_remove(struct i2c_client *client)
+{
+       struct lp5521_chip *chip = i2c_get_clientdata(client);
+       int i;
+
+       lp5521_unregister_sysfs(client);
+
+       for (i = 0; i < chip->num_leds; i++) {
+               led_classdev_unregister(&chip->leds[i].cdev);
+               cancel_work_sync(&chip->leds[i].brightness_work);
+       }
+
+       if (chip->pdata->enable)
+               chip->pdata->enable(0);
+       if (chip->pdata->release_resources)
+               chip->pdata->release_resources();
+       kfree(chip);
+       return 0;
+}
+
+static const struct i2c_device_id lp5521_id[] = {
+       { "lp5521", 0 }, /* Three channel chip */
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, lp5521_id);
+
+static struct i2c_driver lp5521_driver = {
+       .driver = {
+               .name   = "lp5521",
+       },
+       .probe          = lp5521_probe,
+       .remove         = lp5521_remove,
+       .id_table       = lp5521_id,
+};
+
+static int __init lp5521_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&lp5521_driver);
+
+       if (ret < 0)
+               printk(KERN_ALERT "Adding lp5521 driver failed\n");
+
+       return ret;
+}
+
+static void __exit lp5521_exit(void)
+{
+       i2c_del_driver(&lp5521_driver);
+}
+
+module_init(lp5521_init);
+module_exit(lp5521_exit);
+
+MODULE_AUTHOR("Mathias Nyman, Yuri Zaporozhets, Samu Onkalo");
+MODULE_DESCRIPTION("LP5521 LED engine");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
new file mode 100644 (file)
index 0000000..1e11fcc
--- /dev/null
@@ -0,0 +1,1065 @@
+/*
+ * lp5523.c - LP5523 LED Driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/leds.h>
+#include <linux/leds-lp5523.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+#define LP5523_REG_ENABLE              0x00
+#define LP5523_REG_OP_MODE             0x01
+#define LP5523_REG_RATIOMETRIC_MSB     0x02
+#define LP5523_REG_RATIOMETRIC_LSB     0x03
+#define LP5523_REG_ENABLE_LEDS_MSB     0x04
+#define LP5523_REG_ENABLE_LEDS_LSB     0x05
+#define LP5523_REG_LED_CNTRL_BASE      0x06
+#define LP5523_REG_LED_PWM_BASE                0x16
+#define LP5523_REG_LED_CURRENT_BASE    0x26
+#define LP5523_REG_CONFIG              0x36
+#define LP5523_REG_CHANNEL1_PC         0x37
+#define LP5523_REG_CHANNEL2_PC         0x38
+#define LP5523_REG_CHANNEL3_PC         0x39
+#define LP5523_REG_STATUS              0x3a
+#define LP5523_REG_GPO                 0x3b
+#define LP5523_REG_VARIABLE            0x3c
+#define LP5523_REG_RESET               0x3d
+#define LP5523_REG_TEMP_CTRL           0x3e
+#define LP5523_REG_TEMP_READ           0x3f
+#define LP5523_REG_TEMP_WRITE          0x40
+#define LP5523_REG_LED_TEST_CTRL       0x41
+#define LP5523_REG_LED_TEST_ADC                0x42
+#define LP5523_REG_ENG1_VARIABLE       0x45
+#define LP5523_REG_ENG2_VARIABLE       0x46
+#define LP5523_REG_ENG3_VARIABLE       0x47
+#define LP5523_REG_MASTER_FADER1       0x48
+#define LP5523_REG_MASTER_FADER2       0x49
+#define LP5523_REG_MASTER_FADER3       0x4a
+#define LP5523_REG_CH1_PROG_START      0x4c
+#define LP5523_REG_CH2_PROG_START      0x4d
+#define LP5523_REG_CH3_PROG_START      0x4e
+#define LP5523_REG_PROG_PAGE_SEL       0x4f
+#define LP5523_REG_PROG_MEM            0x50
+
+#define LP5523_CMD_LOAD                        0x15 /* 00010101 */
+#define LP5523_CMD_RUN                 0x2a /* 00101010 */
+#define LP5523_CMD_DISABLED            0x00 /* 00000000 */
+
+#define LP5523_ENABLE                  0x40
+#define LP5523_AUTO_INC                        0x40
+#define LP5523_PWR_SAVE                        0x20
+#define LP5523_PWM_PWR_SAVE            0x04
+#define LP5523_CP_1                    0x08
+#define LP5523_CP_1_5                  0x10
+#define LP5523_CP_AUTO                 0x18
+#define LP5523_INT_CLK                 0x01
+#define LP5523_AUTO_CLK                        0x02
+#define LP5523_EN_LEDTEST              0x80
+#define LP5523_LEDTEST_DONE            0x80
+
+#define LP5523_DEFAULT_CURRENT         50 /* microAmps */
+#define LP5523_PROGRAM_LENGTH          32 /* in bytes */
+#define LP5523_PROGRAM_PAGES           6
+#define LP5523_ADC_SHORTCIRC_LIM       80
+
+#define LP5523_LEDS                    9
+#define LP5523_ENGINES                 3
+
+#define LP5523_ENG_MASK_BASE           0x30 /* 00110000 */
+
+#define LP5523_ENG_STATUS_MASK          0x07 /* 00000111 */
+
+#define LP5523_IRQ_FLAGS                IRQF_TRIGGER_FALLING
+
+#define LP5523_EXT_CLK_USED            0x08
+
+#define LED_ACTIVE(mux, led)           (!!(mux & (0x0001 << led)))
+#define SHIFT_MASK(id)                 (((id) - 1) * 2)
+
+struct lp5523_engine {
+       const struct attribute_group *attributes;
+       int             id;
+       u8              mode;
+       u8              prog_page;
+       u8              mux_page;
+       u16             led_mux;
+       u8              engine_mask;
+};
+
+struct lp5523_led {
+       int                     id;
+       u8                      chan_nr;
+       u8                      led_current;
+       u8                      max_current;
+       struct led_classdev     cdev;
+       struct work_struct      brightness_work;
+       u8                      brightness;
+};
+
+struct lp5523_chip {
+       struct mutex            lock; /* Serialize control */
+       struct i2c_client       *client;
+       struct lp5523_engine    engines[LP5523_ENGINES];
+       struct lp5523_led       leds[LP5523_LEDS];
+       struct lp5523_platform_data *pdata;
+       u8                      num_channels;
+       u8                      num_leds;
+};
+
+#define cdev_to_led(c)          container_of(c, struct lp5523_led, cdev)
+
+static struct lp5523_chip *engine_to_lp5523(struct lp5523_engine *engine)
+{
+       return container_of(engine, struct lp5523_chip,
+                           engines[engine->id - 1]);
+}
+
+static struct lp5523_chip *led_to_lp5523(struct lp5523_led *led)
+{
+       return container_of(led, struct lp5523_chip,
+                           leds[led->id]);
+}
+
+static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode);
+static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode);
+static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern);
+
+static void lp5523_led_brightness_work(struct work_struct *work);
+
+static int lp5523_write(struct i2c_client *client, u8 reg, u8 value)
+{
+       return i2c_smbus_write_byte_data(client, reg, value);
+}
+
+static int lp5523_read(struct i2c_client *client, u8 reg, u8 *buf)
+{
+       s32 ret = i2c_smbus_read_byte_data(client, reg);
+
+       if (ret < 0)
+               return -EIO;
+
+       *buf = ret;
+       return 0;
+}
+
+static int lp5523_detect(struct i2c_client *client)
+{
+       int ret;
+       u8 buf;
+
+       ret = lp5523_write(client, LP5523_REG_ENABLE, 0x40);
+       if (ret)
+               return ret;
+       ret = lp5523_read(client, LP5523_REG_ENABLE, &buf);
+       if (ret)
+               return ret;
+       if (buf == 0x40)
+               return 0;
+       else
+               return -ENODEV;
+}
+
+static int lp5523_configure(struct i2c_client *client)
+{
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       int ret = 0;
+       u8 status;
+
+       /* one pattern per engine setting led mux start and stop addresses */
+       u8 pattern[][LP5523_PROGRAM_LENGTH] =  {
+               { 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0},
+               { 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0},
+               { 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0},
+       };
+
+       lp5523_write(client, LP5523_REG_RESET, 0xff);
+
+       usleep_range(10000, 100000);
+
+       ret |= lp5523_write(client, LP5523_REG_ENABLE, LP5523_ENABLE);
+       /* Chip startup time after reset is 500 us */
+       usleep_range(1000, 10000);
+
+       ret |= lp5523_write(client, LP5523_REG_CONFIG,
+                           LP5523_AUTO_INC | LP5523_PWR_SAVE |
+                           LP5523_CP_AUTO | LP5523_AUTO_CLK |
+                           LP5523_PWM_PWR_SAVE);
+
+       /* turn on all leds */
+       ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_MSB, 0x01);
+       ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
+
+       /* hardcode 32 bytes of memory for each engine from program memory */
+       ret |= lp5523_write(client, LP5523_REG_CH1_PROG_START, 0x00);
+       ret |= lp5523_write(client, LP5523_REG_CH2_PROG_START, 0x10);
+       ret |= lp5523_write(client, LP5523_REG_CH3_PROG_START, 0x20);
+
+       /* write led mux address space for each channel */
+       ret |= lp5523_load_program(&chip->engines[0], pattern[0]);
+       ret |= lp5523_load_program(&chip->engines[1], pattern[1]);
+       ret |= lp5523_load_program(&chip->engines[2], pattern[2]);
+
+       if (ret) {
+               dev_err(&client->dev, "could not load mux programs\n");
+               return -1;
+       }
+
+       /* set all engines exec state and mode to run 00101010 */
+       ret |= lp5523_write(client, LP5523_REG_ENABLE,
+                           (LP5523_CMD_RUN | LP5523_ENABLE));
+
+       ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_RUN);
+
+       if (ret) {
+               dev_err(&client->dev, "could not start mux programs\n");
+               return -1;
+       }
+
+       /* Wait 3ms and check the engine status */
+       usleep_range(3000, 20000);
+       lp5523_read(client, LP5523_REG_STATUS, &status);
+       status &= LP5523_ENG_STATUS_MASK;
+
+       if (status == LP5523_ENG_STATUS_MASK) {
+               dev_dbg(&client->dev, "all engines configured\n");
+       } else {
+               dev_info(&client->dev, "status == %x\n", status);
+               dev_err(&client->dev, "cound not configure LED engine\n");
+               return -1;
+       }
+
+       dev_info(&client->dev, "disabling engines\n");
+
+       ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_DISABLED);
+
+       return ret;
+}
+
+static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode)
+{
+       struct lp5523_chip *chip = engine_to_lp5523(engine);
+       struct i2c_client *client = chip->client;
+       int ret;
+       u8 engine_state;
+
+       ret = lp5523_read(client, LP5523_REG_OP_MODE, &engine_state);
+       if (ret)
+               goto fail;
+
+       engine_state &= ~(engine->engine_mask);
+
+       /* set mode only for this engine */
+       mode &= engine->engine_mask;
+
+       engine_state |= mode;
+
+       ret |= lp5523_write(client, LP5523_REG_OP_MODE, engine_state);
+fail:
+       return ret;
+}
+
+static int lp5523_load_mux(struct lp5523_engine *engine, u16 mux)
+{
+       struct lp5523_chip *chip = engine_to_lp5523(engine);
+       struct i2c_client *client = chip->client;
+       int ret = 0;
+
+       ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
+
+       ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL, engine->mux_page);
+       ret |= lp5523_write(client, LP5523_REG_PROG_MEM,
+                           (u8)(mux >> 8));
+       ret |= lp5523_write(client, LP5523_REG_PROG_MEM + 1, (u8)(mux));
+       engine->led_mux = mux;
+
+       return ret;
+}
+
+static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern)
+{
+       struct lp5523_chip *chip = engine_to_lp5523(engine);
+       struct i2c_client *client = chip->client;
+
+       int ret = 0;
+
+       ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
+
+       ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL,
+                           engine->prog_page);
+       ret |= i2c_smbus_write_i2c_block_data(client, LP5523_REG_PROG_MEM,
+                                             LP5523_PROGRAM_LENGTH, pattern);
+
+       return ret;
+}
+
+static int lp5523_run_program(struct lp5523_engine *engine)
+{
+       struct lp5523_chip *chip = engine_to_lp5523(engine);
+       struct i2c_client *client = chip->client;
+       int ret;
+
+       ret = lp5523_write(client, LP5523_REG_ENABLE,
+                                       LP5523_CMD_RUN | LP5523_ENABLE);
+       if (ret)
+               goto fail;
+
+       ret = lp5523_set_engine_mode(engine, LP5523_CMD_RUN);
+fail:
+       return ret;
+}
+
+static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
+{
+       int i;
+       u16 tmp_mux = 0;
+       len = len < LP5523_LEDS ? len : LP5523_LEDS;
+       for (i = 0; i < len; i++) {
+               switch (buf[i]) {
+               case '1':
+                       tmp_mux |= (1 << i);
+                       break;
+               case '0':
+                       break;
+               case '\n':
+                       i = len;
+                       break;
+               default:
+                       return -1;
+               }
+       }
+       *mux = tmp_mux;
+
+       return 0;
+}
+
+static void lp5523_mux_to_array(u16 led_mux, char *array)
+{
+       int i, pos = 0;
+       for (i = 0; i < LP5523_LEDS; i++)
+               pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
+
+       array[pos] = '\0';
+}
+
+/*--------------------------------------------------------------*/
+/*                     Sysfs interface                         */
+/*--------------------------------------------------------------*/
+
+static ssize_t show_engine_leds(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       char mux[LP5523_LEDS + 1];
+
+       lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux);
+
+       return sprintf(buf, "%s\n", mux);
+}
+
+#define show_leds(nr)                                                  \
+static ssize_t show_engine##nr##_leds(struct device *dev,              \
+                           struct device_attribute *attr,              \
+                           char *buf)                                  \
+{                                                                      \
+       return show_engine_leds(dev, attr, buf, nr);                    \
+}
+show_leds(1)
+show_leds(2)
+show_leds(3)
+
+static ssize_t store_engine_leds(struct device *dev,
+                            struct device_attribute *attr,
+                            const char *buf, size_t len, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       u16 mux = 0;
+
+       if (lp5523_mux_parse(buf, &mux, len))
+               return -EINVAL;
+
+       if (lp5523_load_mux(&chip->engines[nr - 1], mux))
+               return -EINVAL;
+
+       return len;
+}
+
+#define store_leds(nr)                                         \
+static ssize_t store_engine##nr##_leds(struct device *dev,     \
+                            struct device_attribute *attr,     \
+                            const char *buf, size_t len)       \
+{                                                              \
+       return store_engine_leds(dev, attr, buf, len, nr);      \
+}
+store_leds(1)
+store_leds(2)
+store_leds(3)
+
+static ssize_t lp5523_selftest(struct device *dev,
+                              struct device_attribute *attr,
+                              char *buf)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       int i, ret, pos = 0;
+       int led = 0;
+       u8 status, adc, vdd;
+
+       mutex_lock(&chip->lock);
+
+       ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
+       if (ret < 0)
+               goto fail;
+
+       /* Check that ext clock is really in use if requested */
+       if ((chip->pdata) && (chip->pdata->clock_mode == LP5523_CLOCK_EXT))
+               if  ((status & LP5523_EXT_CLK_USED) == 0)
+                       goto fail;
+
+       /* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */
+       lp5523_write(chip->client, LP5523_REG_LED_TEST_CTRL,
+                                   LP5523_EN_LEDTEST | 16);
+       usleep_range(3000, 10000);
+       ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
+       if (!(status & LP5523_LEDTEST_DONE))
+               usleep_range(3000, 10000);
+
+       ret |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &vdd);
+       vdd--;  /* There may be some fluctuation in measurement */
+
+       for (i = 0; i < LP5523_LEDS; i++) {
+               /* Skip non-existing channels */
+               if (chip->pdata->led_config[i].led_current == 0)
+                       continue;
+
+               /* Set default current */
+               lp5523_write(chip->client,
+                       LP5523_REG_LED_CURRENT_BASE + i,
+                       chip->pdata->led_config[i].led_current);
+
+               lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0xff);
+               /* let current stabilize 2ms before measurements start */
+               usleep_range(2000, 10000);
+               lp5523_write(chip->client,
+                            LP5523_REG_LED_TEST_CTRL,
+                            LP5523_EN_LEDTEST | i);
+               /* ledtest takes 2.7ms */
+               usleep_range(3000, 10000);
+               ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
+               if (!(status & LP5523_LEDTEST_DONE))
+                       usleep_range(3000, 10000);
+               ret |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &adc);
+
+               if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM)
+                       pos += sprintf(buf + pos, "LED %d FAIL\n", i);
+
+               lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0x00);
+
+               /* Restore current */
+               lp5523_write(chip->client,
+                       LP5523_REG_LED_CURRENT_BASE + i,
+                       chip->leds[led].led_current);
+               led++;
+       }
+       if (pos == 0)
+               pos = sprintf(buf, "OK\n");
+       goto release_lock;
+fail:
+       pos = sprintf(buf, "FAIL\n");
+
+release_lock:
+       mutex_unlock(&chip->lock);
+
+       return pos;
+}
+
+static void lp5523_set_brightness(struct led_classdev *cdev,
+                            enum led_brightness brightness)
+{
+       struct lp5523_led *led = cdev_to_led(cdev);
+
+       led->brightness = (u8)brightness;
+
+       schedule_work(&led->brightness_work);
+}
+
+static void lp5523_led_brightness_work(struct work_struct *work)
+{
+       struct lp5523_led *led = container_of(work,
+                                             struct lp5523_led,
+                                             brightness_work);
+       struct lp5523_chip *chip = led_to_lp5523(led);
+       struct i2c_client *client = chip->client;
+
+       mutex_lock(&chip->lock);
+
+       lp5523_write(client, LP5523_REG_LED_PWM_BASE + led->chan_nr,
+                    led->brightness);
+
+       mutex_unlock(&chip->lock);
+}
+
+static int lp5523_do_store_load(struct lp5523_engine *engine,
+                               const char *buf, size_t len)
+{
+       struct lp5523_chip *chip = engine_to_lp5523(engine);
+       struct i2c_client *client = chip->client;
+       int  ret, nrchars, offset = 0, i = 0;
+       char c[3];
+       unsigned cmd;
+       u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
+
+       while ((offset < len - 1) && (i < LP5523_PROGRAM_LENGTH)) {
+               /* separate sscanfs because length is working only for %s */
+               ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
+               ret = sscanf(c, "%2x", &cmd);
+               if (ret != 1)
+                       goto fail;
+               pattern[i] = (u8)cmd;
+
+               offset += nrchars;
+               i++;
+       }
+
+       /* Each instruction is 16bit long. Check that length is even */
+       if (i % 2)
+               goto fail;
+
+       mutex_lock(&chip->lock);
+
+       ret = lp5523_load_program(engine, pattern);
+       mutex_unlock(&chip->lock);
+
+       if (ret) {
+               dev_err(&client->dev, "failed loading pattern\n");
+               return ret;
+       }
+
+       return len;
+fail:
+       dev_err(&client->dev, "wrong pattern format\n");
+       return -EINVAL;
+}
+
+static ssize_t store_engine_load(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t len, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       return lp5523_do_store_load(&chip->engines[nr - 1], buf, len);
+}
+
+#define store_load(nr)                                                 \
+static ssize_t store_engine##nr##_load(struct device *dev,             \
+                                    struct device_attribute *attr,     \
+                                    const char *buf, size_t len)       \
+{                                                                      \
+       return store_engine_load(dev, attr, buf, len, nr);              \
+}
+store_load(1)
+store_load(2)
+store_load(3)
+
+static ssize_t show_engine_mode(struct device *dev,
+                               struct device_attribute *attr,
+                               char *buf, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       switch (chip->engines[nr - 1].mode) {
+       case LP5523_CMD_RUN:
+               return sprintf(buf, "run\n");
+       case LP5523_CMD_LOAD:
+               return sprintf(buf, "load\n");
+       case LP5523_CMD_DISABLED:
+               return sprintf(buf, "disabled\n");
+       default:
+               return sprintf(buf, "disabled\n");
+       }
+}
+
+#define show_mode(nr)                                                  \
+static ssize_t show_engine##nr##_mode(struct device *dev,              \
+                                   struct device_attribute *attr,      \
+                                   char *buf)                          \
+{                                                                      \
+       return show_engine_mode(dev, attr, buf, nr);                    \
+}
+show_mode(1)
+show_mode(2)
+show_mode(3)
+
+static ssize_t store_engine_mode(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t len, int nr)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       struct lp5523_engine *engine = &chip->engines[nr - 1];
+       mutex_lock(&chip->lock);
+
+       if (!strncmp(buf, "run", 3))
+               lp5523_set_mode(engine, LP5523_CMD_RUN);
+       else if (!strncmp(buf, "load", 4))
+               lp5523_set_mode(engine, LP5523_CMD_LOAD);
+       else if (!strncmp(buf, "disabled", 8))
+               lp5523_set_mode(engine, LP5523_CMD_DISABLED);
+
+       mutex_unlock(&chip->lock);
+       return len;
+}
+
+#define store_mode(nr)                                                 \
+static ssize_t store_engine##nr##_mode(struct device *dev,             \
+                                    struct device_attribute *attr,     \
+                                    const char *buf, size_t len)       \
+{                                                                      \
+       return store_engine_mode(dev, attr, buf, len, nr);              \
+}
+store_mode(1)
+store_mode(2)
+store_mode(3)
+
+static ssize_t show_max_current(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lp5523_led *led = cdev_to_led(led_cdev);
+
+       return sprintf(buf, "%d\n", led->max_current);
+}
+
+static ssize_t show_current(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lp5523_led *led = cdev_to_led(led_cdev);
+
+       return sprintf(buf, "%d\n", led->led_current);
+}
+
+static ssize_t store_current(struct device *dev,
+                            struct device_attribute *attr,
+                            const char *buf, size_t len)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lp5523_led *led = cdev_to_led(led_cdev);
+       struct lp5523_chip *chip = led_to_lp5523(led);
+       ssize_t ret;
+       unsigned long curr;
+
+       if (strict_strtoul(buf, 0, &curr))
+               return -EINVAL;
+
+       if (curr > led->max_current)
+               return -EINVAL;
+
+       mutex_lock(&chip->lock);
+       ret = lp5523_write(chip->client,
+                       LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
+                       (u8)curr);
+       mutex_unlock(&chip->lock);
+
+       if (ret < 0)
+               return ret;
+
+       led->led_current = (u8)curr;
+
+       return len;
+}
+
+/* led class device attributes */
+static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current);
+static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL);
+
+static struct attribute *lp5523_led_attributes[] = {
+       &dev_attr_led_current.attr,
+       &dev_attr_max_current.attr,
+       NULL,
+};
+
+static struct attribute_group lp5523_led_attribute_group = {
+       .attrs = lp5523_led_attributes
+};
+
+/* device attributes */
+static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO,
+                  show_engine1_mode, store_engine1_mode);
+static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO,
+                  show_engine2_mode, store_engine2_mode);
+static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO,
+                  show_engine3_mode, store_engine3_mode);
+static DEVICE_ATTR(engine1_leds, S_IRUGO | S_IWUGO,
+                  show_engine1_leds, store_engine1_leds);
+static DEVICE_ATTR(engine2_leds, S_IRUGO | S_IWUGO,
+                  show_engine2_leds, store_engine2_leds);
+static DEVICE_ATTR(engine3_leds, S_IRUGO | S_IWUGO,
+                  show_engine3_leds, store_engine3_leds);
+static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load);
+static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load);
+static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load);
+static DEVICE_ATTR(selftest, S_IRUGO, lp5523_selftest, NULL);
+
+static struct attribute *lp5523_attributes[] = {
+       &dev_attr_engine1_mode.attr,
+       &dev_attr_engine2_mode.attr,
+       &dev_attr_engine3_mode.attr,
+       &dev_attr_selftest.attr,
+       NULL
+};
+
+static struct attribute *lp5523_engine1_attributes[] = {
+       &dev_attr_engine1_load.attr,
+       &dev_attr_engine1_leds.attr,
+       NULL
+};
+
+static struct attribute *lp5523_engine2_attributes[] = {
+       &dev_attr_engine2_load.attr,
+       &dev_attr_engine2_leds.attr,
+       NULL
+};
+
+static struct attribute *lp5523_engine3_attributes[] = {
+       &dev_attr_engine3_load.attr,
+       &dev_attr_engine3_leds.attr,
+       NULL
+};
+
+static const struct attribute_group lp5523_group = {
+       .attrs = lp5523_attributes,
+};
+
+static const struct attribute_group lp5523_engine_group[] = {
+       {.attrs = lp5523_engine1_attributes },
+       {.attrs = lp5523_engine2_attributes },
+       {.attrs = lp5523_engine3_attributes },
+};
+
+static int lp5523_register_sysfs(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       int ret;
+
+       ret = sysfs_create_group(&dev->kobj, &lp5523_group);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static void lp5523_unregister_sysfs(struct i2c_client *client)
+{
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       struct device *dev = &client->dev;
+       int i;
+
+       sysfs_remove_group(&dev->kobj, &lp5523_group);
+
+       for (i = 0; i < ARRAY_SIZE(chip->engines); i++)
+               if (chip->engines[i].mode == LP5523_CMD_LOAD)
+                       sysfs_remove_group(&dev->kobj, &lp5523_engine_group[i]);
+
+       for (i = 0; i < chip->num_leds; i++)
+               sysfs_remove_group(&chip->leds[i].cdev.dev->kobj,
+                               &lp5523_led_attribute_group);
+}
+
+/*--------------------------------------------------------------*/
+/*                     Set chip operating mode                 */
+/*--------------------------------------------------------------*/
+static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode)
+{
+       /*  engine to chip */
+       struct lp5523_chip *chip = engine_to_lp5523(engine);
+       struct i2c_client *client = chip->client;
+       struct device *dev = &client->dev;
+       int ret = 0;
+
+       /* if in that mode already do nothing, except for run */
+       if (mode == engine->mode && mode != LP5523_CMD_RUN)
+               return 0;
+
+       if (mode == LP5523_CMD_RUN) {
+               ret = lp5523_run_program(engine);
+       } else if (mode == LP5523_CMD_LOAD) {
+               lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED);
+               lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
+
+               ret = sysfs_create_group(&dev->kobj, engine->attributes);
+               if (ret)
+                       return ret;
+       } else if (mode == LP5523_CMD_DISABLED) {
+               lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED);
+       }
+
+       /* remove load attribute from sysfs if not in load mode */
+       if (engine->mode == LP5523_CMD_LOAD && mode != LP5523_CMD_LOAD)
+               sysfs_remove_group(&dev->kobj, engine->attributes);
+
+       engine->mode = mode;
+
+       return ret;
+}
+
+/*--------------------------------------------------------------*/
+/*                     Probe, Attach, Remove                   */
+/*--------------------------------------------------------------*/
+static int __init lp5523_init_engine(struct lp5523_engine *engine, int id)
+{
+       if (id < 1 || id > LP5523_ENGINES)
+               return -1;
+       engine->id = id;
+       engine->engine_mask = LP5523_ENG_MASK_BASE >> SHIFT_MASK(id);
+       engine->prog_page = id - 1;
+       engine->mux_page = id + 2;
+       engine->attributes = &lp5523_engine_group[id - 1];
+
+       return 0;
+}
+
+static int __init lp5523_init_led(struct lp5523_led *led, struct device *dev,
+                          int chan, struct lp5523_platform_data *pdata)
+{
+       char name[32];
+       int res;
+
+       if (chan >= LP5523_LEDS)
+               return -EINVAL;
+
+       if (pdata->led_config[chan].led_current) {
+               led->led_current = pdata->led_config[chan].led_current;
+               led->max_current = pdata->led_config[chan].max_current;
+               led->chan_nr = pdata->led_config[chan].chan_nr;
+
+               if (led->chan_nr >= LP5523_LEDS) {
+                       dev_err(dev, "Use channel numbers between 0 and %d\n",
+                               LP5523_LEDS - 1);
+                       return -EINVAL;
+               }
+
+               snprintf(name, 32, "lp5523:channel%d", chan);
+
+               led->cdev.name = name;
+               led->cdev.brightness_set = lp5523_set_brightness;
+               res = led_classdev_register(dev, &led->cdev);
+               if (res < 0) {
+                       dev_err(dev, "couldn't register led on channel %d\n",
+                               chan);
+                       return res;
+               }
+               res = sysfs_create_group(&led->cdev.dev->kobj,
+                               &lp5523_led_attribute_group);
+               if (res < 0) {
+                       dev_err(dev, "couldn't register current attribute\n");
+                       led_classdev_unregister(&led->cdev);
+                       return res;
+               }
+       } else {
+               led->led_current = 0;
+       }
+       return 0;
+}
+
+static struct i2c_driver lp5523_driver;
+
+static int lp5523_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct lp5523_chip              *chip;
+       struct lp5523_platform_data     *pdata;
+       int ret, i, led;
+
+       chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+       if (!chip)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, chip);
+       chip->client = client;
+
+       pdata = client->dev.platform_data;
+
+       if (!pdata) {
+               dev_err(&client->dev, "no platform data\n");
+               ret = -EINVAL;
+               goto fail1;
+       }
+
+       mutex_init(&chip->lock);
+
+       chip->pdata   = pdata;
+
+       if (pdata->setup_resources) {
+               ret = pdata->setup_resources();
+               if (ret < 0)
+                       goto fail1;
+       }
+
+       if (pdata->enable) {
+               pdata->enable(0);
+               usleep_range(1000, 10000);
+               pdata->enable(1);
+               usleep_range(1000, 10000); /* Spec says min 500us */
+       }
+
+       ret = lp5523_detect(client);
+       if (ret)
+               goto fail2;
+
+       dev_info(&client->dev, "LP5523 Programmable led chip found\n");
+
+       /* Initialize engines */
+       for (i = 0; i < ARRAY_SIZE(chip->engines); i++) {
+               ret = lp5523_init_engine(&chip->engines[i], i + 1);
+               if (ret) {
+                       dev_err(&client->dev, "error initializing engine\n");
+                       goto fail2;
+               }
+       }
+       ret = lp5523_configure(client);
+       if (ret < 0) {
+               dev_err(&client->dev, "error configuring chip\n");
+               goto fail2;
+       }
+
+       /* Initialize leds */
+       chip->num_channels = pdata->num_channels;
+       chip->num_leds = 0;
+       led = 0;
+       for (i = 0; i < pdata->num_channels; i++) {
+               /* Do not initialize channels that are not connected */
+               if (pdata->led_config[i].led_current == 0)
+                       continue;
+
+               ret = lp5523_init_led(&chip->leds[led], &client->dev, i, pdata);
+               if (ret) {
+                       dev_err(&client->dev, "error initializing leds\n");
+                       goto fail3;
+               }
+               chip->num_leds++;
+
+               chip->leds[led].id = led;
+               /* Set LED current */
+               lp5523_write(client,
+                         LP5523_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr,
+                         chip->leds[led].led_current);
+
+               INIT_WORK(&(chip->leds[led].brightness_work),
+                       lp5523_led_brightness_work);
+
+               led++;
+       }
+
+       ret = lp5523_register_sysfs(client);
+       if (ret) {
+               dev_err(&client->dev, "registering sysfs failed\n");
+               goto fail3;
+       }
+       return ret;
+fail3:
+       for (i = 0; i < chip->num_leds; i++) {
+               led_classdev_unregister(&chip->leds[i].cdev);
+               cancel_work_sync(&chip->leds[i].brightness_work);
+       }
+fail2:
+       if (pdata->enable)
+               pdata->enable(0);
+       if (pdata->release_resources)
+               pdata->release_resources();
+fail1:
+       kfree(chip);
+       return ret;
+}
+
+static int lp5523_remove(struct i2c_client *client)
+{
+       struct lp5523_chip *chip = i2c_get_clientdata(client);
+       int i;
+
+       lp5523_unregister_sysfs(client);
+
+       for (i = 0; i < chip->num_leds; i++) {
+               led_classdev_unregister(&chip->leds[i].cdev);
+               cancel_work_sync(&chip->leds[i].brightness_work);
+       }
+
+       if (chip->pdata->enable)
+               chip->pdata->enable(0);
+       if (chip->pdata->release_resources)
+               chip->pdata->release_resources();
+       kfree(chip);
+       return 0;
+}
+
+static const struct i2c_device_id lp5523_id[] = {
+       { "lp5523", 0 },
+       { }
+};
+
+MODULE_DEVICE_TABLE(i2c, lp5523_id);
+
+static struct i2c_driver lp5523_driver = {
+       .driver = {
+               .name   = "lp5523",
+       },
+       .probe          = lp5523_probe,
+       .remove         = lp5523_remove,
+       .id_table       = lp5523_id,
+};
+
+static int __init lp5523_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&lp5523_driver);
+
+       if (ret < 0)
+               printk(KERN_ALERT "Adding lp5523 driver failed\n");
+
+       return ret;
+}
+
+static void __exit lp5523_exit(void)
+{
+       i2c_del_driver(&lp5523_driver);
+}
+
+module_init(lp5523_init);
+module_exit(lp5523_exit);
+
+MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>");
+MODULE_DESCRIPTION("LP5523 LED engine");
+MODULE_LICENSE("GPL");
index 82b77bd..b09bcbe 100644 (file)
  */
 
 #include <linux/module.h>
-#include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
-#include <linux/list.h>
-#include <linux/spinlock.h>
 #include <linux/device.h>
-#include <linux/sysdev.h>
-#include <linux/timer.h>
 #include <linux/ctype.h>
 #include <linux/leds.h>
-#include <linux/slab.h>
 #include "leds.h"
 
-struct timer_trig_data {
-       int brightness_on;              /* LED brightness during "on" period.
-                                        * (LED_OFF < brightness_on <= LED_FULL)
-                                        */
-       unsigned long delay_on;         /* milliseconds on */
-       unsigned long delay_off;        /* milliseconds off */
-       struct timer_list timer;
-};
-
-static void led_timer_function(unsigned long data)
-{
-       struct led_classdev *led_cdev = (struct led_classdev *) data;
-       struct timer_trig_data *timer_data = led_cdev->trigger_data;
-       unsigned long brightness;
-       unsigned long delay;
-
-       if (!timer_data->delay_on || !timer_data->delay_off) {
-               led_set_brightness(led_cdev, LED_OFF);
-               return;
-       }
-
-       brightness = led_get_brightness(led_cdev);
-       if (!brightness) {
-               /* Time to switch the LED on. */
-               brightness = timer_data->brightness_on;
-               delay = timer_data->delay_on;
-       } else {
-               /* Store the current brightness value to be able
-                * to restore it when the delay_off period is over.
-                */
-               timer_data->brightness_on = brightness;
-               brightness = LED_OFF;
-               delay = timer_data->delay_off;
-       }
-
-       led_set_brightness(led_cdev, brightness);
-
-       mod_timer(&timer_data->timer, jiffies + msecs_to_jiffies(delay));
-}
-
 static ssize_t led_delay_on_show(struct device *dev,
                struct device_attribute *attr, char *buf)
 {
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
-       struct timer_trig_data *timer_data = led_cdev->trigger_data;
 
-       return sprintf(buf, "%lu\n", timer_data->delay_on);
+       return sprintf(buf, "%lu\n", led_cdev->blink_delay_on);
 }
 
 static ssize_t led_delay_on_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t size)
 {
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
-       struct timer_trig_data *timer_data = led_cdev->trigger_data;
        int ret = -EINVAL;
        char *after;
        unsigned long state = simple_strtoul(buf, &after, 10);
@@ -88,21 +40,7 @@ static ssize_t led_delay_on_store(struct device *dev,
                count++;
 
        if (count == size) {
-               if (timer_data->delay_on != state) {
-                       /* the new value differs from the previous */
-                       timer_data->delay_on = state;
-
-                       /* deactivate previous settings */
-                       del_timer_sync(&timer_data->timer);
-
-                       /* try to activate hardware acceleration, if any */
-                       if (!led_cdev->blink_set ||
-                           led_cdev->blink_set(led_cdev,
-                             &timer_data->delay_on, &timer_data->delay_off)) {
-                               /* no hardware acceleration, blink via timer */
-                               mod_timer(&timer_data->timer, jiffies + 1);
-                       }
-               }
+               led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off);
                ret = count;
        }
 
@@ -113,16 +51,14 @@ static ssize_t led_delay_off_show(struct device *dev,
                struct device_attribute *attr, char *buf)
 {
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
-       struct timer_trig_data *timer_data = led_cdev->trigger_data;
 
-       return sprintf(buf, "%lu\n", timer_data->delay_off);
+       return sprintf(buf, "%lu\n", led_cdev->blink_delay_off);
 }
 
 static ssize_t led_delay_off_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t size)
 {
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
-       struct timer_trig_data *timer_data = led_cdev->trigger_data;
        int ret = -EINVAL;
        char *after;
        unsigned long state = simple_strtoul(buf, &after, 10);
@@ -132,21 +68,7 @@ static ssize_t led_delay_off_store(struct device *dev,
                count++;
 
        if (count == size) {
-               if (timer_data->delay_off != state) {
-                       /* the new value differs from the previous */
-                       timer_data->delay_off = state;
-
-                       /* deactivate previous settings */
-                       del_timer_sync(&timer_data->timer);
-
-                       /* try to activate hardware acceleration, if any */
-                       if (!led_cdev->blink_set ||
-                           led_cdev->blink_set(led_cdev,
-                             &timer_data->delay_on, &timer_data->delay_off)) {
-                               /* no hardware acceleration, blink via timer */
-                               mod_timer(&timer_data->timer, jiffies + 1);
-                       }
-               }
+               led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state);
                ret = count;
        }
 
@@ -158,60 +80,34 @@ static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);
 
 static void timer_trig_activate(struct led_classdev *led_cdev)
 {
-       struct timer_trig_data *timer_data;
        int rc;
 
-       timer_data = kzalloc(sizeof(struct timer_trig_data), GFP_KERNEL);
-       if (!timer_data)
-               return;
-
-       timer_data->brightness_on = led_get_brightness(led_cdev);
-       if (timer_data->brightness_on == LED_OFF)
-               timer_data->brightness_on = led_cdev->max_brightness;
-       led_cdev->trigger_data = timer_data;
-
-       init_timer(&timer_data->timer);
-       timer_data->timer.function = led_timer_function;
-       timer_data->timer.data = (unsigned long) led_cdev;
+       led_cdev->trigger_data = NULL;
 
        rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
        if (rc)
-               goto err_out;
+               return;
        rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
        if (rc)
                goto err_out_delayon;
 
-       /* If there is hardware support for blinking, start one
-        * user friendly blink rate chosen by the driver.
-        */
-       if (led_cdev->blink_set)
-               led_cdev->blink_set(led_cdev,
-                       &timer_data->delay_on, &timer_data->delay_off);
+       led_cdev->trigger_data = (void *)1;
 
        return;
 
 err_out_delayon:
        device_remove_file(led_cdev->dev, &dev_attr_delay_on);
-err_out:
-       led_cdev->trigger_data = NULL;
-       kfree(timer_data);
 }
 
 static void timer_trig_deactivate(struct led_classdev *led_cdev)
 {
-       struct timer_trig_data *timer_data = led_cdev->trigger_data;
-       unsigned long on = 0, off = 0;
-
-       if (timer_data) {
+       if (led_cdev->trigger_data) {
                device_remove_file(led_cdev->dev, &dev_attr_delay_on);
                device_remove_file(led_cdev->dev, &dev_attr_delay_off);
-               del_timer_sync(&timer_data->timer);
-               kfree(timer_data);
        }
 
-       /* If there is hardware support for blinking, stop it */
-       if (led_cdev->blink_set)
-               led_cdev->blink_set(led_cdev, &on, &off);
+       /* Stop blinking */
+       led_brightness_set(led_cdev, LED_OFF);
 }
 
 static struct led_trigger timer_led_trigger = {
index 4446966..f5f4da3 100644 (file)
@@ -80,7 +80,7 @@ static void adb_iop_end_req(struct adb_request *req, int state)
 static void adb_iop_complete(struct iop_msg *msg)
 {
        struct adb_request *req;
-       uint flags;
+       unsigned long flags;
 
        local_irq_save(flags);
 
@@ -103,7 +103,7 @@ static void adb_iop_listen(struct iop_msg *msg)
 {
        struct adb_iopmsg *amsg = (struct adb_iopmsg *) msg->message;
        struct adb_request *req;
-       uint flags;
+       unsigned long flags;
 #ifdef DEBUG_ADB_IOP
        int i;
 #endif
index f9b91ba..0ed0935 100644 (file)
@@ -123,7 +123,7 @@ static ssize_t als_sensing_range_store(struct device *dev,
 {
        struct i2c_client *client = to_i2c_client(dev);
        struct als_data *data = i2c_get_clientdata(client);
-       unsigned int ret_val;
+       int ret_val;
        unsigned long val;
 
        if (strict_strtoul(buf, 10, &val))
index cee632e..d79a972 100644 (file)
@@ -649,7 +649,7 @@ static ssize_t bh1770_power_state_store(struct device *dev,
 {
        struct bh1770_chip *chip =  dev_get_drvdata(dev);
        unsigned long value;
-       size_t ret;
+       ssize_t ret;
 
        if (strict_strtoul(buf, 0, &value))
                return -EINVAL;
@@ -659,8 +659,12 @@ static ssize_t bh1770_power_state_store(struct device *dev,
                pm_runtime_get_sync(dev);
 
                ret = bh1770_lux_rate(chip, chip->lux_rate_index);
-               ret |= bh1770_lux_interrupt_control(chip, BH1770_ENABLE);
+               if (ret < 0) {
+                       pm_runtime_put(dev);
+                       goto leave;
+               }
 
+               ret = bh1770_lux_interrupt_control(chip, BH1770_ENABLE);
                if (ret < 0) {
                        pm_runtime_put(dev);
                        goto leave;
index 34fe835..ca47e62 100644 (file)
@@ -87,7 +87,7 @@ static ssize_t als_sensing_range_store(struct device *dev,
                struct device_attribute *attr, const  char *buf, size_t count)
 {
        struct i2c_client *client = to_i2c_client(dev);
-       unsigned int ret_val;
+       int ret_val;
        unsigned long val;
 
        if (strict_strtoul(buf, 10, &val))
@@ -106,6 +106,8 @@ static ssize_t als_sensing_range_store(struct device *dev,
                val = 4;
 
        ret_val = i2c_smbus_read_byte_data(client, 0x00);
+       if (ret_val < 0)
+               return ret_val;
 
        ret_val &= 0xFC; /*reset the bit before setting them */
        ret_val |= val - 1;
index eea1ef2..4396d4b 100644 (file)
@@ -221,9 +221,6 @@ config RT2X00_LIB_LEDS
        boolean
        default y if (RT2X00_LIB=y && LEDS_CLASS=y) || (RT2X00_LIB=m && LEDS_CLASS!=n)
 
-comment "rt2x00 leds support disabled due to modularized LEDS_CLASS and built-in rt2x00"
-       depends on RT2X00_LIB=y && LEDS_CLASS=m
-
 config RT2X00_LIB_DEBUGFS
        bool "Ralink debugfs support"
        depends on RT2X00_LIB && MAC80211_DEBUGFS
index 68cf0c9..7b5080c 100644 (file)
@@ -1159,11 +1159,11 @@ int __devinit rio_init_mports(void)
 
        list_for_each_entry(port, &rio_mports, node) {
                if (!request_mem_region(port->iores.start,
-                                       port->iores.end - port->iores.start,
+                                       resource_size(&port->iores),
                                        port->name)) {
                        printk(KERN_ERR
                               "RIO: Error requesting master port region 0x%016llx-0x%016llx\n",
-                              (u64)port->iores.start, (u64)port->iores.end - 1);
+                              (u64)port->iores.start, (u64)port->iores.end);
                        rc = -ENOMEM;
                        goto out;
                }
index 3ec2460..734c650 100644 (file)
@@ -502,8 +502,10 @@ static ssize_t adp8860_bl_l1_daylight_max_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t count)
 {
        struct adp8860_bl *data = dev_get_drvdata(dev);
+       int ret = strict_strtoul(buf, 10, &data->cached_daylight_max);
+       if (ret)
+               return ret;
 
-       strict_strtoul(buf, 10, &data->cached_daylight_max);
        return adp8860_store(dev, buf, count, ADP8860_BLMX1);
 }
 static DEVICE_ATTR(l1_daylight_max, 0664, adp8860_bl_l1_daylight_max_show,
@@ -614,7 +616,7 @@ static ssize_t adp8860_bl_ambient_light_zone_store(struct device *dev,
        if (val == 0) {
                /* Enable automatic ambient light sensing */
                adp8860_set_bits(data->client, ADP8860_MDCR, CMP_AUTOEN);
-       } else if ((val > 0) && (val < 6)) {
+       } else if ((val > 0) && (val <= 3)) {
                /* Disable automatic ambient light sensing */
                adp8860_clr_bits(data->client, ADP8860_MDCR, CMP_AUTOEN);
 
@@ -622,7 +624,7 @@ static ssize_t adp8860_bl_ambient_light_zone_store(struct device *dev,
                mutex_lock(&data->lock);
                adp8860_read(data->client, ADP8860_CFGR, &reg_val);
                reg_val &= ~(CFGR_BLV_MASK << CFGR_BLV_SHIFT);
-               reg_val |= val << CFGR_BLV_SHIFT;
+               reg_val |= (val - 1) << CFGR_BLV_SHIFT;
                adp8860_write(data->client, ADP8860_CFGR, reg_val);
                mutex_unlock(&data->lock);
        }
index 9093ef0..c67801e 100644 (file)
@@ -78,7 +78,7 @@ static int l4f00242t03_lcd_power_set(struct lcd_device *ld, int power)
        const u16 slpin = 0x10;
        const u16 disoff = 0x28;
 
-       if (power) {
+       if (power <= FB_BLANK_NORMAL) {
                if (priv->lcd_on)
                        return 0;
 
index abc43a0..5d3cf33 100644 (file)
@@ -129,7 +129,7 @@ static int lms283gf05_power_set(struct lcd_device *ld, int power)
        struct spi_device *spi = st->spi;
        struct lms283gf05_pdata *pdata = spi->dev.platform_data;
 
-       if (power) {
+       if (power <= FB_BLANK_NORMAL) {
                if (pdata)
                        lms283gf05_reset(pdata->reset_gpio,
                                        pdata->reset_inverted);
index 9fb533f..1485f73 100644 (file)
@@ -335,6 +335,24 @@ static const struct dmi_system_id __initdata mbp_device_table[] = {
                },
                .driver_data    = (void *)&nvidia_chipset_data,
        },
+       {
+               .callback       = mbp_dmi_match,
+               .ident          = "MacBookAir 3,1",
+               .matches        = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "MacBookAir3,1"),
+               },
+               .driver_data    = (void *)&nvidia_chipset_data,
+       },
+       {
+               .callback       = mbp_dmi_match,
+               .ident          = "MacBookAir 3,2",
+               .matches        = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "MacBookAir3,2"),
+               },
+               .driver_data    = (void *)&nvidia_chipset_data,
+       },
        { }
 };
 
index 5504435..21866ec 100644 (file)
@@ -25,6 +25,7 @@ struct pwm_bl_data {
        struct pwm_device       *pwm;
        struct device           *dev;
        unsigned int            period;
+       unsigned int            lth_brightness;
        int                     (*notify)(struct device *,
                                          int brightness);
 };
@@ -48,7 +49,9 @@ static int pwm_backlight_update_status(struct backlight_device *bl)
                pwm_config(pb->pwm, 0, pb->period);
                pwm_disable(pb->pwm);
        } else {
-               pwm_config(pb->pwm, brightness * pb->period / max, pb->period);
+               brightness = pb->lth_brightness +
+                       (brightness * (pb->period - pb->lth_brightness) / max);
+               pwm_config(pb->pwm, brightness, pb->period);
                pwm_enable(pb->pwm);
        }
        return 0;
@@ -92,6 +95,8 @@ static int pwm_backlight_probe(struct platform_device *pdev)
 
        pb->period = data->pwm_period_ns;
        pb->notify = data->notify;
+       pb->lth_brightness = data->lth_brightness *
+               (data->pwm_period_ns / data->max_brightness);
        pb->dev = &pdev->dev;
 
        pb->pwm = pwm_request(data->pwm_id, "backlight");
index a3128c9..5927db0 100644 (file)
@@ -729,10 +729,10 @@ static ssize_t s6e63m0_sysfs_show_gamma_table(struct device *dev,
 
        return strlen(buf);
 }
-static DEVICE_ATTR(gamma_table, 0644,
+static DEVICE_ATTR(gamma_table, 0444,
                s6e63m0_sysfs_show_gamma_table, NULL);
 
-static int __init s6e63m0_probe(struct spi_device *spi)
+static int __devinit s6e63m0_probe(struct spi_device *spi)
 {
        int ret = 0;
        struct s6e63m0 *lcd = NULL;
@@ -829,6 +829,9 @@ static int __devexit s6e63m0_remove(struct spi_device *spi)
        struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev);
 
        s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
+       device_remove_file(&spi->dev, &dev_attr_gamma_table);
+       device_remove_file(&spi->dev, &dev_attr_gamma_mode);
+       backlight_device_unregister(lcd->bd);
        lcd_device_unregister(lcd->ld);
        kfree(lcd);
 
index d6cfac1..a5fe681 100644 (file)
@@ -932,8 +932,7 @@ struct file *hugetlb_file_setup(const char *name, size_t size, int acctflag,
        if (creat_flags == HUGETLB_SHMFS_INODE && !can_do_hugetlb_shm()) {
                *user = current_user();
                if (user_shm_lock(size, *user)) {
-                       WARN_ONCE(1,
-                         "Using mlock ulimits for SHM_HUGETLB deprecated\n");
+                       printk_once(KERN_WARNING "Using mlock ulimits for SHM_HUGETLB is deprecated\n");
                } else {
                        *user = NULL;
                        return ERR_PTR(-EPERM);
index 65765cb..0e62dd3 100644 (file)
@@ -1504,9 +1504,8 @@ static int do_fcntl_delete_lease(struct file *filp)
 
 static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg)
 {
-       struct file_lock *fl;
+       struct file_lock *fl, *ret;
        struct fasync_struct *new;
-       struct inode *inode = filp->f_path.dentry->d_inode;
        int error;
 
        fl = lease_alloc(filp, arg);
@@ -1518,13 +1517,16 @@ static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg)
                locks_free_lock(fl);
                return -ENOMEM;
        }
+       ret = fl;
        lock_flocks();
-       error = __vfs_setlease(filp, arg, &fl);
+       error = __vfs_setlease(filp, arg, &ret);
        if (error) {
                unlock_flocks();
                locks_free_lock(fl);
                goto out_free_fasync;
        }
+       if (ret != fl)
+               locks_free_lock(fl);
 
        /*
         * fasync_insert_entry() returns the old entry if any.
@@ -1532,17 +1534,10 @@ static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg)
         * inserted it into the fasync list. Clear new so that
         * we don't release it here.
         */
-       if (!fasync_insert_entry(fd, filp, &fl->fl_fasync, new))
+       if (!fasync_insert_entry(fd, filp, &ret->fl_fasync, new))
                new = NULL;
 
-       if (error < 0) {
-               /* remove lease just inserted by setlease */
-               fl->fl_type = F_UNLCK | F_INPROGRESS;
-               fl->fl_break_time = jiffies - 10;
-               time_out_leases(inode);
-       } else {
-               error = __f_setown(filp, task_pid(current), PIDTYPE_PID, 0);
-       }
+       error = __f_setown(filp, task_pid(current), PIDTYPE_PID, 0);
        unlock_flocks();
 
 out_free_fasync:
index f1e5ec6..ad2bfa6 100644 (file)
@@ -673,16 +673,17 @@ static void nfsd4_hash_conn(struct nfsd4_conn *conn, struct nfsd4_session *ses)
        spin_unlock(&clp->cl_lock);
 }
 
-static void nfsd4_register_conn(struct nfsd4_conn *conn)
+static int nfsd4_register_conn(struct nfsd4_conn *conn)
 {
        conn->cn_xpt_user.callback = nfsd4_conn_lost;
-       register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user);
+       return register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user);
 }
 
 static __be32 nfsd4_new_conn(struct svc_rqst *rqstp, struct nfsd4_session *ses)
 {
        struct nfsd4_conn *conn;
        u32 flags = NFS4_CDFC4_FORE;
+       int ret;
 
        if (ses->se_flags & SESSION4_BACK_CHAN)
                flags |= NFS4_CDFC4_BACK;
@@ -690,7 +691,10 @@ static __be32 nfsd4_new_conn(struct svc_rqst *rqstp, struct nfsd4_session *ses)
        if (!conn)
                return nfserr_jukebox;
        nfsd4_hash_conn(conn, ses);
-       nfsd4_register_conn(conn);
+       ret = nfsd4_register_conn(conn);
+       if (ret)
+               /* oops; xprt is already down: */
+               nfsd4_conn_lost(&conn->cn_xpt_user);
        return nfs_ok;
 }
 
@@ -1644,6 +1648,7 @@ static void nfsd4_sequence_check_conn(struct nfsd4_conn *new, struct nfsd4_sessi
 {
        struct nfs4_client *clp = ses->se_client;
        struct nfsd4_conn *c;
+       int ret;
 
        spin_lock(&clp->cl_lock);
        c = __nfsd4_find_conn(new->cn_xprt, ses);
@@ -1654,7 +1659,10 @@ static void nfsd4_sequence_check_conn(struct nfsd4_conn *new, struct nfsd4_sessi
        }
        __nfsd4_hash_conn(new, ses);
        spin_unlock(&clp->cl_lock);
-       nfsd4_register_conn(new);
+       ret = nfsd4_register_conn(new);
+       if (ret)
+               /* oops; xprt is already down: */
+               nfsd4_conn_lost(&new->cn_xpt_user);
        return;
 }
 
diff --git a/include/linux/atomic.h b/include/linux/atomic.h
new file mode 100644 (file)
index 0000000..96c038e
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef _LINUX_ATOMIC_H
+#define _LINUX_ATOMIC_H
+#include <asm/atomic.h>
+
+/**
+ * atomic_inc_not_zero_hint - increment if not null
+ * @v: pointer of type atomic_t
+ * @hint: probable value of the atomic before the increment
+ *
+ * This version of atomic_inc_not_zero() gives a hint of probable
+ * value of the atomic. This helps processor to not read the memory
+ * before doing the atomic read/modify/write cycle, lowering
+ * number of bus transactions on some arches.
+ *
+ * Returns: 0 if increment was not done, 1 otherwise.
+ */
+#ifndef atomic_inc_not_zero_hint
+static inline int atomic_inc_not_zero_hint(atomic_t *v, int hint)
+{
+       int val, c = hint;
+
+       /* sanity test, should be removed by compiler if hint is a constant */
+       if (!hint)
+               return atomic_inc_not_zero(v);
+
+       do {
+               val = atomic_cmpxchg(v, c, c + 1);
+               if (val == c)
+                       return 1;
+               c = val;
+       } while (c);
+
+       return 0;
+}
+#endif
+
+#endif /* _LINUX_ATOMIC_H */
index e913819..b676c58 100644 (file)
@@ -5,6 +5,7 @@
 #include <linux/kernel.h>
 #include <linux/mm.h>
 #include <linux/uaccess.h>
+#include <linux/hardirq.h>
 
 #include <asm/cacheflush.h>
 
index b526947..fc3da9e 100644 (file)
@@ -293,6 +293,7 @@ extern bool printk_timed_ratelimit(unsigned long *caller_jiffies,
                                   unsigned int interval_msec);
 
 extern int printk_delay_msec;
+extern int dmesg_restrict;
 
 /*
  * Print a one-time message (analogous to WARN_ONCE() et al):
diff --git a/include/linux/leds-lp5521.h b/include/linux/leds-lp5521.h
new file mode 100644 (file)
index 0000000..38368d7
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * LP5521 LED chip driver.
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef __LINUX_LP5521_H
+#define __LINUX_LP5521_H
+
+/* See Documentation/leds/leds-lp5521.txt */
+
+struct lp5521_led_config {
+       u8              chan_nr;
+       u8              led_current; /* mA x10, 0 if led is not connected */
+       u8              max_current;
+};
+
+#define LP5521_CLOCK_AUTO      0
+#define LP5521_CLOCK_INT       1
+#define LP5521_CLOCK_EXT       2
+
+struct lp5521_platform_data {
+       struct lp5521_led_config *led_config;
+       u8      num_channels;
+       u8      clock_mode;
+       int     (*setup_resources)(void);
+       void    (*release_resources)(void);
+       void    (*enable)(bool state);
+};
+
+#endif /* __LINUX_LP5521_H */
diff --git a/include/linux/leds-lp5523.h b/include/linux/leds-lp5523.h
new file mode 100644 (file)
index 0000000..7967476
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * LP5523 LED Driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ * Contact: Samu Onkalo <samu.p.onkalo@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef __LINUX_LP5523_H
+#define __LINUX_LP5523_H
+
+/* See Documentation/leds/leds-lp5523.txt */
+
+struct lp5523_led_config {
+       u8              chan_nr;
+       u8              led_current; /* mA x10, 0 if led is not connected */
+       u8              max_current;
+};
+
+#define LP5523_CLOCK_AUTO      0
+#define LP5523_CLOCK_INT       1
+#define LP5523_CLOCK_EXT       2
+
+struct lp5523_platform_data {
+       struct lp5523_led_config *led_config;
+       u8      num_channels;
+       u8      clock_mode;
+       int     (*setup_resources)(void);
+       void    (*release_resources)(void);
+       void    (*enable)(bool state);
+};
+
+#endif /* __LINUX_LP5523_H */
index ba6986a..0f19df9 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/list.h>
 #include <linux/spinlock.h>
 #include <linux/rwsem.h>
+#include <linux/timer.h>
 
 struct device;
 /*
@@ -45,10 +46,14 @@ struct led_classdev {
        /* Get LED brightness level */
        enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
 
-       /* Activate hardware accelerated blink, delays are in
-        * miliseconds and if none is provided then a sensible default
-        * should be chosen. The call can adjust the timings if it can't
-        * match the values specified exactly. */
+       /*
+        * Activate hardware accelerated blink, delays are in milliseconds
+        * and if both are zero then a sensible default should be chosen.
+        * The call should adjust the timings in that case and if it can't
+        * match the values specified exactly.
+        * Deactivate blinking again when the brightness is set to a fixed
+        * value via the brightness_set() callback.
+        */
        int             (*blink_set)(struct led_classdev *led_cdev,
                                     unsigned long *delay_on,
                                     unsigned long *delay_off);
@@ -57,6 +62,10 @@ struct led_classdev {
        struct list_head         node;                  /* LED Device list */
        const char              *default_trigger;       /* Trigger to use */
 
+       unsigned long            blink_delay_on, blink_delay_off;
+       struct timer_list        blink_timer;
+       int                      blink_brightness;
+
 #ifdef CONFIG_LEDS_TRIGGERS
        /* Protects the trigger data below */
        struct rw_semaphore      trigger_lock;
@@ -73,6 +82,36 @@ extern void led_classdev_unregister(struct led_classdev *led_cdev);
 extern void led_classdev_suspend(struct led_classdev *led_cdev);
 extern void led_classdev_resume(struct led_classdev *led_cdev);
 
+/**
+ * led_blink_set - set blinking with software fallback
+ * @led_cdev: the LED to start blinking
+ * @delay_on: the time it should be on (in ms)
+ * @delay_off: the time it should ble off (in ms)
+ *
+ * This function makes the LED blink, attempting to use the
+ * hardware acceleration if possible, but falling back to
+ * software blinking if there is no hardware blinking or if
+ * the LED refuses the passed values.
+ *
+ * Note that if software blinking is active, simply calling
+ * led_cdev->brightness_set() will not stop the blinking,
+ * use led_classdev_brightness_set() instead.
+ */
+extern void led_blink_set(struct led_classdev *led_cdev,
+                         unsigned long *delay_on,
+                         unsigned long *delay_off);
+/**
+ * led_brightness_set - set LED brightness
+ * @led_cdev: the LED to set
+ * @brightness: the brightness to set it to
+ *
+ * Set an LED's brightness, and, if necessary, cancel the
+ * software blink timer that implements blinking when the
+ * hardware doesn't.
+ */
+extern void led_brightness_set(struct led_classdev *led_cdev,
+                              enum led_brightness brightness);
+
 /*
  * LED Triggers
  */
index 01b3d75..e031e1a 100644 (file)
@@ -8,6 +8,7 @@ struct platform_pwm_backlight_data {
        int pwm_id;
        unsigned int max_brightness;
        unsigned int dft_brightness;
+       unsigned int lth_brightness;
        unsigned int pwm_period_ns;
        int (*init)(struct device *dev);
        int (*notify)(struct device *dev, int brightness);
index a39cbed..ab2baa5 100644 (file)
  * needed for RCU lookups (because root->height is unreliable). The only
  * time callers need worry about this is when doing a lookup_slot under
  * RCU.
+ *
+ * Indirect pointer in fact is also used to tag the last pointer of a node
+ * when it is shrunk, before we rcu free the node. See shrink code for
+ * details.
  */
 #define RADIX_TREE_INDIRECT_PTR        1
-#define RADIX_TREE_RETRY ((void *)-1UL)
-
-static inline void *radix_tree_ptr_to_indirect(void *ptr)
-{
-       return (void *)((unsigned long)ptr | RADIX_TREE_INDIRECT_PTR);
-}
 
-static inline void *radix_tree_indirect_to_ptr(void *ptr)
-{
-       return (void *)((unsigned long)ptr & ~RADIX_TREE_INDIRECT_PTR);
-}
 #define radix_tree_indirect_to_ptr(ptr) \
        radix_tree_indirect_to_ptr((void __force *)(ptr))
 
@@ -140,16 +134,29 @@ do {                                                                      \
  *             removed.
  *
  * For use with radix_tree_lookup_slot().  Caller must hold tree at least read
- * locked across slot lookup and dereference.  More likely, will be used with
- * radix_tree_replace_slot(), as well, so caller will hold tree write locked.
+ * locked across slot lookup and dereference. Not required if write lock is
+ * held (ie. items cannot be concurrently inserted).
+ *
+ * radix_tree_deref_retry must be used to confirm validity of the pointer if
+ * only the read lock is held.
  */
 static inline void *radix_tree_deref_slot(void **pslot)
 {
-       void *ret = rcu_dereference(*pslot);
-       if (unlikely(radix_tree_is_indirect_ptr(ret)))
-               ret = RADIX_TREE_RETRY;
-       return ret;
+       return rcu_dereference(*pslot);
 }
+
+/**
+ * radix_tree_deref_retry      - check radix_tree_deref_slot
+ * @arg:       pointer returned by radix_tree_deref_slot
+ * Returns:    0 if retry is not required, otherwise retry is required
+ *
+ * radix_tree_deref_retry must be used with radix_tree_deref_slot.
+ */
+static inline int radix_tree_deref_retry(void *arg)
+{
+       return unlikely((unsigned long)arg & RADIX_TREE_INDIRECT_PTR);
+}
+
 /**
  * radix_tree_replace_slot     - replace item in a slot
  * @pslot:     pointer to slot, returned by radix_tree_lookup_slot
index 88d36f9..d01c96c 100644 (file)
@@ -2,6 +2,7 @@
 #define _LINUX_RESOURCE_H
 
 #include <linux/time.h>
+#include <linux/types.h>
 
 /*
  * Resource control/accounting header file for linux
index bbdb680..aea0d43 100644 (file)
@@ -82,18 +82,28 @@ struct svc_xprt {
        struct net              *xpt_net;
 };
 
-static inline void register_xpt_user(struct svc_xprt *xpt, struct svc_xpt_user *u)
+static inline void unregister_xpt_user(struct svc_xprt *xpt, struct svc_xpt_user *u)
 {
        spin_lock(&xpt->xpt_lock);
-       list_add(&u->list, &xpt->xpt_users);
+       list_del_init(&u->list);
        spin_unlock(&xpt->xpt_lock);
 }
 
-static inline void unregister_xpt_user(struct svc_xprt *xpt, struct svc_xpt_user *u)
+static inline int register_xpt_user(struct svc_xprt *xpt, struct svc_xpt_user *u)
 {
        spin_lock(&xpt->xpt_lock);
-       list_del_init(&u->list);
+       if (test_bit(XPT_CLOSE, &xpt->xpt_flags)) {
+               /*
+                * The connection is about to be deleted soon (or,
+                * worse, may already be deleted--in which case we've
+                * already notified the xpt_users).
+                */
+               spin_unlock(&xpt->xpt_lock);
+               return -ENOTCONN;
+       }
+       list_add(&u->list, &xpt->xpt_users);
        spin_unlock(&xpt->xpt_lock);
+       return 0;
 }
 
 int    svc_reg_xprt_class(struct svc_xprt_class *);
index 877fb30..17110a4 100644 (file)
@@ -194,14 +194,7 @@ __account_scheduler_latency(struct task_struct *tsk, int usecs, int inter)
 
        account_global_scheduler_latency(tsk, &lat);
 
-       /*
-        * short term hack; if we're > 32 we stop; future we recycle:
-        */
-       tsk->latency_record_count++;
-       if (tsk->latency_record_count >= LT_SAVECOUNT)
-               goto out_unlock;
-
-       for (i = 0; i < LT_SAVECOUNT; i++) {
+       for (i = 0; i < tsk->latency_record_count; i++) {
                struct latency_record *mylat;
                int same = 1;
 
@@ -227,8 +220,14 @@ __account_scheduler_latency(struct task_struct *tsk, int usecs, int inter)
                }
        }
 
+       /*
+        * short term hack; if we're > 32 we stop; future we recycle:
+        */
+       if (tsk->latency_record_count >= LT_SAVECOUNT)
+               goto out_unlock;
+
        /* Allocated a new one: */
-       i = tsk->latency_record_count;
+       i = tsk->latency_record_count++;
        memcpy(&tsk->latency_record[i], &lat, sizeof(struct latency_record));
 
 out_unlock:
index b2ebaee..38e7d58 100644 (file)
@@ -261,6 +261,12 @@ static inline void boot_delay_msec(void)
 }
 #endif
 
+#ifdef CONFIG_SECURITY_DMESG_RESTRICT
+int dmesg_restrict = 1;
+#else
+int dmesg_restrict;
+#endif
+
 int do_syslog(int type, char __user *buf, int len, bool from_file)
 {
        unsigned i, j, limit, count;
index 471b66a..37fa9b9 100644 (file)
@@ -119,7 +119,7 @@ static int cmp_range(const void *x1, const void *x2)
 
 int clean_sort_range(struct range *range, int az)
 {
-       int i, j, k = az - 1, nr_range = 0;
+       int i, j, k = az - 1, nr_range = az;
 
        for (i = 0; i < k; i++) {
                if (range[i].end)
index c33a1ed..b65bf63 100644 (file)
@@ -703,6 +703,15 @@ static struct ctl_table kern_table[] = {
                .extra2         = &ten_thousand,
        },
 #endif
+       {
+               .procname       = "dmesg_restrict",
+               .data           = &dmesg_restrict,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one,
+       },
        {
                .procname       = "ngroups_max",
                .data           = &ngroups_max,
index 6f412ab..5086bb9 100644 (file)
@@ -82,6 +82,16 @@ struct radix_tree_preload {
 };
 static DEFINE_PER_CPU(struct radix_tree_preload, radix_tree_preloads) = { 0, };
 
+static inline void *ptr_to_indirect(void *ptr)
+{
+       return (void *)((unsigned long)ptr | RADIX_TREE_INDIRECT_PTR);
+}
+
+static inline void *indirect_to_ptr(void *ptr)
+{
+       return (void *)((unsigned long)ptr & ~RADIX_TREE_INDIRECT_PTR);
+}
+
 static inline gfp_t root_gfp_mask(struct radix_tree_root *root)
 {
        return root->gfp_mask & __GFP_BITS_MASK;
@@ -265,7 +275,7 @@ static int radix_tree_extend(struct radix_tree_root *root, unsigned long index)
                        return -ENOMEM;
 
                /* Increase the height.  */
-               node->slots[0] = radix_tree_indirect_to_ptr(root->rnode);
+               node->slots[0] = indirect_to_ptr(root->rnode);
 
                /* Propagate the aggregated tag info into the new root */
                for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
@@ -276,7 +286,7 @@ static int radix_tree_extend(struct radix_tree_root *root, unsigned long index)
                newheight = root->height+1;
                node->height = newheight;
                node->count = 1;
-               node = radix_tree_ptr_to_indirect(node);
+               node = ptr_to_indirect(node);
                rcu_assign_pointer(root->rnode, node);
                root->height = newheight;
        } while (height > root->height);
@@ -309,7 +319,7 @@ int radix_tree_insert(struct radix_tree_root *root,
                        return error;
        }
 
-       slot = radix_tree_indirect_to_ptr(root->rnode);
+       slot = indirect_to_ptr(root->rnode);
 
        height = root->height;
        shift = (height-1) * RADIX_TREE_MAP_SHIFT;
@@ -325,8 +335,7 @@ int radix_tree_insert(struct radix_tree_root *root,
                                rcu_assign_pointer(node->slots[offset], slot);
                                node->count++;
                        } else
-                               rcu_assign_pointer(root->rnode,
-                                       radix_tree_ptr_to_indirect(slot));
+                               rcu_assign_pointer(root->rnode, ptr_to_indirect(slot));
                }
 
                /* Go a level down */
@@ -374,7 +383,7 @@ static void *radix_tree_lookup_element(struct radix_tree_root *root,
                        return NULL;
                return is_slot ? (void *)&root->rnode : node;
        }
-       node = radix_tree_indirect_to_ptr(node);
+       node = indirect_to_ptr(node);
 
        height = node->height;
        if (index > radix_tree_maxindex(height))
@@ -393,7 +402,7 @@ static void *radix_tree_lookup_element(struct radix_tree_root *root,
                height--;
        } while (height > 0);
 
-       return is_slot ? (void *)slot:node;
+       return is_slot ? (void *)slot : indirect_to_ptr(node);
 }
 
 /**
@@ -455,7 +464,7 @@ void *radix_tree_tag_set(struct radix_tree_root *root,
        height = root->height;
        BUG_ON(index > radix_tree_maxindex(height));
 
-       slot = radix_tree_indirect_to_ptr(root->rnode);
+       slot = indirect_to_ptr(root->rnode);
        shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
 
        while (height > 0) {
@@ -509,7 +518,7 @@ void *radix_tree_tag_clear(struct radix_tree_root *root,
 
        shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
        pathp->node = NULL;
-       slot = radix_tree_indirect_to_ptr(root->rnode);
+       slot = indirect_to_ptr(root->rnode);
 
        while (height > 0) {
                int offset;
@@ -579,7 +588,7 @@ int radix_tree_tag_get(struct radix_tree_root *root,
 
        if (!radix_tree_is_indirect_ptr(node))
                return (index == 0);
-       node = radix_tree_indirect_to_ptr(node);
+       node = indirect_to_ptr(node);
 
        height = node->height;
        if (index > radix_tree_maxindex(height))
@@ -666,7 +675,7 @@ unsigned long radix_tree_range_tag_if_tagged(struct radix_tree_root *root,
        }
 
        shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
-       slot = radix_tree_indirect_to_ptr(root->rnode);
+       slot = indirect_to_ptr(root->rnode);
 
        /*
         * we fill the path from (root->height - 2) to 0, leaving the index at
@@ -897,7 +906,7 @@ radix_tree_gang_lookup(struct radix_tree_root *root, void **results,
                results[0] = node;
                return 1;
        }
-       node = radix_tree_indirect_to_ptr(node);
+       node = indirect_to_ptr(node);
 
        max_index = radix_tree_maxindex(node->height);
 
@@ -916,7 +925,8 @@ radix_tree_gang_lookup(struct radix_tree_root *root, void **results,
                        slot = *(((void ***)results)[ret + i]);
                        if (!slot)
                                continue;
-                       results[ret + nr_found] = rcu_dereference_raw(slot);
+                       results[ret + nr_found] =
+                               indirect_to_ptr(rcu_dereference_raw(slot));
                        nr_found++;
                }
                ret += nr_found;
@@ -965,7 +975,7 @@ radix_tree_gang_lookup_slot(struct radix_tree_root *root, void ***results,
                results[0] = (void **)&root->rnode;
                return 1;
        }
-       node = radix_tree_indirect_to_ptr(node);
+       node = indirect_to_ptr(node);
 
        max_index = radix_tree_maxindex(node->height);
 
@@ -1090,7 +1100,7 @@ radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results,
                results[0] = node;
                return 1;
        }
-       node = radix_tree_indirect_to_ptr(node);
+       node = indirect_to_ptr(node);
 
        max_index = radix_tree_maxindex(node->height);
 
@@ -1109,7 +1119,8 @@ radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results,
                        slot = *(((void ***)results)[ret + i]);
                        if (!slot)
                                continue;
-                       results[ret + nr_found] = rcu_dereference_raw(slot);
+                       results[ret + nr_found] =
+                               indirect_to_ptr(rcu_dereference_raw(slot));
                        nr_found++;
                }
                ret += nr_found;
@@ -1159,7 +1170,7 @@ radix_tree_gang_lookup_tag_slot(struct radix_tree_root *root, void ***results,
                results[0] = (void **)&root->rnode;
                return 1;
        }
-       node = radix_tree_indirect_to_ptr(node);
+       node = indirect_to_ptr(node);
 
        max_index = radix_tree_maxindex(node->height);
 
@@ -1195,7 +1206,7 @@ static inline void radix_tree_shrink(struct radix_tree_root *root)
                void *newptr;
 
                BUG_ON(!radix_tree_is_indirect_ptr(to_free));
-               to_free = radix_tree_indirect_to_ptr(to_free);
+               to_free = indirect_to_ptr(to_free);
 
                /*
                 * The candidate node has more than one child, or its child
@@ -1208,16 +1219,39 @@ static inline void radix_tree_shrink(struct radix_tree_root *root)
 
                /*
                 * We don't need rcu_assign_pointer(), since we are simply
-                * moving the node from one part of the tree to another. If
-                * it was safe to dereference the old pointer to it
+                * moving the node from one part of the tree to another: if it
+                * was safe to dereference the old pointer to it
                 * (to_free->slots[0]), it will be safe to dereference the new
-                * one (root->rnode).
+                * one (root->rnode) as far as dependent read barriers go.
                 */
                newptr = to_free->slots[0];
                if (root->height > 1)
-                       newptr = radix_tree_ptr_to_indirect(newptr);
+                       newptr = ptr_to_indirect(newptr);
                root->rnode = newptr;
                root->height--;
+
+               /*
+                * We have a dilemma here. The node's slot[0] must not be
+                * NULLed in case there are concurrent lookups expecting to
+                * find the item. However if this was a bottom-level node,
+                * then it may be subject to the slot pointer being visible
+                * to callers dereferencing it. If item corresponding to
+                * slot[0] is subsequently deleted, these callers would expect
+                * their slot to become empty sooner or later.
+                *
+                * For example, lockless pagecache will look up a slot, deref
+                * the page pointer, and if the page is 0 refcount it means it
+                * was concurrently deleted from pagecache so try the deref
+                * again. Fortunately there is already a requirement for logic
+                * to retry the entire slot lookup -- the indirect pointer
+                * problem (replacing direct root node with an indirect pointer
+                * also results in a stale slot). So tag the slot as indirect
+                * to force callers to retry.
+                */
+               if (root->height == 0)
+                       *((unsigned long *)&to_free->slots[0]) |=
+                                               RADIX_TREE_INDIRECT_PTR;
+
                radix_tree_node_free(to_free);
        }
 }
@@ -1254,7 +1288,7 @@ void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
                root->rnode = NULL;
                goto out;
        }
-       slot = radix_tree_indirect_to_ptr(slot);
+       slot = indirect_to_ptr(slot);
 
        shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
        pathp->node = NULL;
@@ -1296,8 +1330,7 @@ void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
                        radix_tree_node_free(to_free);
 
                if (pathp->node->count) {
-                       if (pathp->node ==
-                                       radix_tree_indirect_to_ptr(root->rnode))
+                       if (pathp->node == indirect_to_ptr(root->rnode))
                                radix_tree_shrink(root);
                        goto out;
                }
index 61ba5e4..ea89840 100644 (file)
@@ -644,7 +644,9 @@ repeat:
        pagep = radix_tree_lookup_slot(&mapping->page_tree, offset);
        if (pagep) {
                page = radix_tree_deref_slot(pagep);
-               if (unlikely(!page || page == RADIX_TREE_RETRY))
+               if (unlikely(!page))
+                       goto out;
+               if (radix_tree_deref_retry(page))
                        goto repeat;
 
                if (!page_cache_get_speculative(page))
@@ -660,6 +662,7 @@ repeat:
                        goto repeat;
                }
        }
+out:
        rcu_read_unlock();
 
        return page;
@@ -777,12 +780,11 @@ repeat:
                page = radix_tree_deref_slot((void **)pages[i]);
                if (unlikely(!page))
                        continue;
-               /*
-                * this can only trigger if nr_found == 1, making livelock
-                * a non issue.
-                */
-               if (unlikely(page == RADIX_TREE_RETRY))
+               if (radix_tree_deref_retry(page)) {
+                       if (ret)
+                               start = pages[ret-1]->index;
                        goto restart;
+               }
 
                if (!page_cache_get_speculative(page))
                        goto repeat;
@@ -830,11 +832,7 @@ repeat:
                page = radix_tree_deref_slot((void **)pages[i]);
                if (unlikely(!page))
                        continue;
-               /*
-                * this can only trigger if nr_found == 1, making livelock
-                * a non issue.
-                */
-               if (unlikely(page == RADIX_TREE_RETRY))
+               if (radix_tree_deref_retry(page))
                        goto restart;
 
                if (page->mapping == NULL || page->index != index)
@@ -887,11 +885,7 @@ repeat:
                page = radix_tree_deref_slot((void **)pages[i]);
                if (unlikely(!page))
                        continue;
-               /*
-                * this can only trigger if nr_found == 1, making livelock
-                * a non issue.
-                */
-               if (unlikely(page == RADIX_TREE_RETRY))
+               if (radix_tree_deref_retry(page))
                        goto restart;
 
                if (!page_cache_get_speculative(page))
@@ -1029,6 +1023,9 @@ find_page:
                                goto page_not_up_to_date;
                        if (!trylock_page(page))
                                goto page_not_up_to_date;
+                       /* Did it get truncated before we got the lock? */
+                       if (!page->mapping)
+                               goto page_not_up_to_date_locked;
                        if (!mapping->a_ops->is_partially_uptodate(page,
                                                                desc, offset))
                                goto page_not_up_to_date_locked;
index 9a99cfa..2efa8ea 100644 (file)
@@ -4208,15 +4208,17 @@ static struct mem_cgroup *mem_cgroup_alloc(void)
 
        memset(mem, 0, size);
        mem->stat = alloc_percpu(struct mem_cgroup_stat_cpu);
-       if (!mem->stat) {
-               if (size < PAGE_SIZE)
-                       kfree(mem);
-               else
-                       vfree(mem);
-               mem = NULL;
-       }
+       if (!mem->stat)
+               goto out_free;
        spin_lock_init(&mem->pcp_counter_lock);
        return mem;
+
+out_free:
+       if (size < PAGE_SIZE)
+               kfree(mem);
+       else
+               vfree(mem);
+       return NULL;
 }
 
 /*
index b8a6fdc..d31d7ce 100644 (file)
@@ -913,7 +913,7 @@ keep_lumpy:
         * back off and wait for congestion to clear because further reclaim
         * will encounter the same problem
         */
-       if (nr_dirty == nr_congested)
+       if (nr_dirty == nr_congested && nr_dirty != 0)
                zone_set_flag(zone, ZONE_CONGESTED);
 
        free_page_list(&free_pages);
index bd72ae6..e80da95 100644 (file)
@@ -39,6 +39,18 @@ config KEYS_DEBUG_PROC_KEYS
 
          If you are unsure as to whether this is required, answer N.
 
+config SECURITY_DMESG_RESTRICT
+       bool "Restrict unprivileged access to the kernel syslog"
+       default n
+       help
+         This enforces restrictions on unprivileged users reading the kernel
+         syslog via dmesg(8).
+
+         If this option is not selected, no restrictions will be enforced
+         unless the dmesg_restrict sysctl is explicitly set to (1).
+
+         If you are unsure how to answer this question, answer N.
+
 config SECURITY
        bool "Enable different security models"
        depends on SYSFS
index 5e632b4..04b80f9 100644 (file)
@@ -895,6 +895,8 @@ int cap_syslog(int type, bool from_file)
 {
        if (type != SYSLOG_ACTION_OPEN && from_file)
                return 0;
+       if (dmesg_restrict && !capable(CAP_SYS_ADMIN))
+               return -EPERM;
        if ((type != SYSLOG_ACTION_READ_ALL &&
             type != SYSLOG_ACTION_SIZE_BUFFER) && !capable(CAP_SYS_ADMIN))
                return -EPERM;