Building a custom Android 4.1 kernel with BLN for Nexus S

Preface

In July 2012, Google demonstrated Android 4.1 Jelly Bean1 release on their yearly Google I/O event. It was an incremental overhaul over Android 4.0 Ice Cream Sandwich and brought better, smoother performance via project Butter, expandable notifications, resizable widgets and more.

Since it usually took a month a more for a custom ROM to fully update to the newest Android release, I didn’t want to wait and switched from running CyanogenMod to the official factory image. It was in my opinion the best Android release yet, a sufficient daily driver and I completely lost the need to flash any custom ROMs anymore.

But there was a few things missing and needed to be fixed. Having left CyanogenMod behind, BLN was such an useful addition to the daily phone usage, that I simply had to look into getting the same functionality again on the stock Android image. It still needed a custom kernel for the device and the BLN control app2. Downloading and installing an app was simple enough, but building a kernel myself ? That was fresh territory for me.

Google also introduced a bug with Android 4.1 where the power off animation on Nexus S i9023 (which was a SLCD panel version) was broken. It worked fine on its AMOLED i9020 counterpart. The effect was also called as CRT off (because it mimicked an old cathod ray tube3 monitor power off). Here’s a video of it in action:


Android kernel

I already wrote about kernels back in one of the previous posts, but again here is a good resource to read all about the topic.

At the time I’ve been using a custom kernel by xda-developers user _thalamus who had a good philosophy about what he wanted and what not in his work. His kernels were close to stock, with little as many added features and focus on stability and power efficience. Many users were requesting that he adds BLN support as also fix the CRT off on SLCD Nexus S devices. That was outside his focus at the time, but he kindly posted instructions how one could patch those fixed and build a kernel himself. Based on my previous experience with building, I was clearly intrigued.

Setting the build environment

Prerequisites are the same as in the previous post about building from the source to have required packages installed, such as git4 tool to access and clone online code repositories on GitHub.

Then we create a directory where we want to put the kernel source code and toolchain.

$ mkdir WORKING_DIRECTORY
$ cd WORKING_DIRECTORY

Now we’re going to clone the _thalamus kernel repository.

$ git clone git://github.com/thalamus/private-kernel.git

We also need the prebuilt toolchain from Google, as per instructions here.

$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6 -b master

Now we’re going to enter the kernel source directory and select the correct working branch called S5PC110-3.0.31-28JB_test.

$ cd private-kernel
$ git checkout S5PC110-3.0.31-28JB_test

For the CRT off issue, we’re going to fix it with a .patch file by Daniel Hillenbrand a.k.a. codeworkx, found here https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/45f909a1e7d2eb479eb8a7cddcb1fbc783181728.patch:

From 45f909a1e7d2eb479eb8a7cddcb1fbc783181728 Mon Sep 17 00:00:00 2001
From: Daniel Hillenbrand <daniel.hillenbrand@codeworkx.de>
Date: Mon, 23 Jul 2012 13:34:08 +0200
Subject: [PATCH] HACK: block fbearlysuspend to not break androids crt-off
 animation

---
 kernel/power/fbearlysuspend.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/kernel/power/fbearlysuspend.c b/kernel/power/fbearlysuspend.c
index 1513765..d260059 100644
--- a/kernel/power/fbearlysuspend.c
+++ b/kernel/power/fbearlysuspend.c
@@ -13,6 +13,7 @@
  *
  */
 
+#include <linux/delay.h>
 #include <linux/earlysuspend.h>
 #include <linux/module.h>
 #include <linux/wait.h>
@@ -33,6 +34,10 @@ static void stop_drawing_early_suspend(struct early_suspend *h)
 	int ret;
 	unsigned long irq_flags;
 
+    /* FIXME: earlysuspend breaks androids CRT-off animation
+     * Sleep a little bit to get it played properly */
+    msleep(500);
+
 	spin_lock_irqsave(&fb_state_lock, irq_flags);
 	fb_state = FB_STATE_REQUEST_STOP_DRAWING;
 	spin_unlock_irqrestore(&fb_state_lock, irq_flags);

Now we’re going to download the .patch file with curl and apply it to the working directory with git am.

$ curl https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/45f909a1e7d2eb479eb8a7cddcb1fbc783181728.patch | git am

For adding the BLN functionality into the kernel, we need three .patch files. First one on https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/d5750ccf7200f8277bada0191f05355bd2f4ac2a.patch enables BLN.

From d5750ccf7200f8277bada0191f05355bd2f4ac2a Mon Sep 17 00:00:00 2001
From: Ezekeel <notezekeel@googlemail.com>
Date: Sat, 17 Dec 2011 08:22:10 +0100
Subject: [PATCH] Added Backlight Notification (BLN) version 9.

---
 arch/arm/mach-s5pv210/herring-touchkey-led.c |  41 ++++
 arch/arm/mach-s5pv210/mach-herring.c         |  19 ++
 drivers/input/keyboard/cypress-touchkey.c    |  95 ++++++++
 drivers/misc/Kconfig                         |   7 +
 drivers/misc/Makefile                        |   1 +
 drivers/misc/bln.c                           | 313 +++++++++++++++++++++++++++
 include/linux/bln.h                          |  13 ++
 include/linux/input/cypress-touchkey.h       |   1 +
 8 files changed, 490 insertions(+)
 create mode 100644 drivers/misc/bln.c
 create mode 100644 include/linux/bln.h

diff --git a/arch/arm/mach-s5pv210/herring-touchkey-led.c b/arch/arm/mach-s5pv210/herring-touchkey-led.c
index b36e0f0..2e24df8 100644
--- a/arch/arm/mach-s5pv210/herring-touchkey-led.c
+++ b/arch/arm/mach-s5pv210/herring-touchkey-led.c
@@ -17,6 +17,10 @@
 #include <linux/earlysuspend.h>
 #include <asm/mach-types.h>
 
+#ifdef CONFIG_GENERIC_BLN
+#include <linux/bln.h>
+#endif
+
 #include "herring.h"
 
 static int led_gpios[] = { 2, 3, 6, 7 };
@@ -29,6 +33,23 @@ static void herring_touchkey_led_onoff(int onoff)
 		gpio_direction_output(S5PV210_GPJ3(led_gpios[i]), !!onoff);
 }
 
+#ifdef CONFIG_GENERIC_BLN
+static void herring_touchkey_bln_enable(void)
+{
+	herring_touchkey_led_onoff(1);
+}
+
+static void herring_touchkey_bln_disable(void)
+{
+	herring_touchkey_led_onoff(0);
+}
+
+static struct bln_implementation herring_touchkey_bln = {
+	.enable = herring_touchkey_bln_enable,
+	.disable = herring_touchkey_bln_disable,
+};
+#endif
+
 static void herring_touchkey_led_early_suspend(struct early_suspend *h)
 {
 	herring_touchkey_led_onoff(0);
@@ -50,10 +71,25 @@ static int __init herring_init_touchkey_led(void)
 	int i;
 	int ret = 0;
 
+#ifdef CONFIG_GENERIC_BLN
+	u32 gpio;
+#endif
+
 	if (!machine_is_herring() || !herring_is_tft_dev())
 		return 0;
 
 	for (i = 0; i < ARRAY_SIZE(led_gpios); i++) {
+#ifdef CONFIG_GENERIC_BLN
+		gpio = S5PV210_GPJ3(led_gpios[i]);
+		ret = gpio_request(gpio, "touchkey led");
+		if (ret) {
+			pr_err("Failed to request touchkey led gpio %d\n", i);
+			goto err_req;
+		}
+		s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
+		s3c_gpio_slp_cfgpin(gpio, S3C_GPIO_SLP_PREV);
+		s3c_gpio_slp_setpull_updown(gpio, S3C_GPIO_PULL_NONE);
+#else
 		ret = gpio_request(S5PV210_GPJ3(led_gpios[i]), "touchkey led");
 		if (ret) {
 			pr_err("Failed to request touchkey led gpio %d\n", i);
@@ -61,12 +97,17 @@ static int __init herring_init_touchkey_led(void)
 		}
 		s3c_gpio_setpull(S5PV210_GPJ3(led_gpios[i]),
 							S3C_GPIO_PULL_NONE);
+#endif
 	}
 
 	herring_touchkey_led_onoff(1);
 
 	register_early_suspend(&early_suspend);
 
+#ifdef CONFIG_GENERIC_BLN
+	register_bln_implementation(&herring_touchkey_bln);
+#endif
+
 	return 0;
 
 err_req:
diff --git a/arch/arm/mach-s5pv210/mach-herring.c b/arch/arm/mach-s5pv210/mach-herring.c
index 6eff467..ad25182 100755
--- a/arch/arm/mach-s5pv210/mach-herring.c
+++ b/arch/arm/mach-s5pv210/mach-herring.c
@@ -2000,6 +2000,24 @@ static void touch_keypad_onoff(int onoff)
 		msleep(50);
 }
 
+static void touch_keypad_gpio_sleep(int onoff){
+	if(onoff == TOUCHKEY_ON){
+		/*
+		 * reconfigure gpio to activate touchkey controller vdd in sleep mode
+		 */
+		s3c_gpio_slp_cfgpin(_3_GPIO_TOUCH_EN, S3C_GPIO_SLP_OUT1);
+		//s3c_gpio_slp_setpull_updown(_3_GPIO_TOUCH_EN, S3C_GPIO_PULL_NONE);
+	} else {
+		/*
+		 * reconfigure gpio to deactivate touchkey vdd in sleep mode,
+		 * this is the default
+		 */
+		s3c_gpio_slp_cfgpin(_3_GPIO_TOUCH_EN, S3C_GPIO_SLP_OUT0);
+		//s3c_gpio_slp_setpull_updown(_3_GPIO_TOUCH_EN, S3C_GPIO_PULL_NONE);
+	}
+
+}
+
 static const int touch_keypad_code[] = {
 	KEY_MENU,
 	KEY_HOME,
@@ -2011,6 +2029,7 @@ static struct touchkey_platform_data touchkey_data = {
 	.keycode_cnt = ARRAY_SIZE(touch_keypad_code),
 	.keycode = touch_keypad_code,
 	.touchkey_onoff = touch_keypad_onoff,
+	.touchkey_sleep_onoff = touch_keypad_gpio_sleep,
 	.fw_name = "cypress-touchkey.bin",
 	.scl_pin = _3_TOUCH_SCL_28V,
 	.sda_pin = _3_TOUCH_SDA_28V,
diff --git a/drivers/input/keyboard/cypress-touchkey.c b/drivers/input/keyboard/cypress-touchkey.c
index 296c0f4..91a8407 100755
--- a/drivers/input/keyboard/cypress-touchkey.c
+++ b/drivers/input/keyboard/cypress-touchkey.c
@@ -29,6 +29,10 @@
 #include <linux/input/cypress-touchkey.h>
 #include <linux/firmware.h>
 
+#ifdef CONFIG_GENERIC_BLN
+#include <linux/bln.h>
+#endif
+
 #define SCANCODE_MASK		0x07
 #define UPDOWN_EVENT_MASK	0x08
 #define ESD_STATE_MASK		0x10
@@ -55,6 +59,10 @@ struct cypress_touchkey_devdata {
 	bool has_legacy_keycode;
 };
 
+#ifdef CONFIG_GENERIC_BLN
+static struct cypress_touchkey_devdata *blndevdata;
+#endif
+
 static int i2c_touchkey_read_byte(struct cypress_touchkey_devdata *devdata,
 					u8 *val)
 {
@@ -209,7 +217,19 @@ static void cypress_touchkey_early_suspend(struct early_suspend *h)
 		return;
 
 	disable_irq(devdata->client->irq);
+
+#ifdef CONFIG_GENERIC_BLN
+	/*
+	 * Disallow powering off the touchkey controller
+	 * while a led notification is ongoing
+	 */
+	if(!bln_is_ongoing()) {
+		devdata->pdata->touchkey_onoff(TOUCHKEY_OFF);
+		devdata->pdata->touchkey_sleep_onoff(TOUCHKEY_OFF);
+	}
+#else
 	devdata->pdata->touchkey_onoff(TOUCHKEY_OFF);
+#endif
 
 	all_keys_up(devdata);
 }
@@ -330,6 +350,76 @@ static int cypress_touchkey_open(struct input_dev *input_dev)
 	return 0;
 }
 
+#ifdef CONFIG_GENERIC_BLN
+static void enable_touchkey_backlights(void){
+       i2c_touchkey_write_byte(blndevdata, blndevdata->backlight_on);
+}
+
+static void disable_touchkey_backlights(void){
+       i2c_touchkey_write_byte(blndevdata, blndevdata->backlight_off);
+}
+
+static void cypress_touchkey_enable_led_notification(void){
+	/* is_powering_on signals whether touchkey lights are used for touchmode */
+	if (blndevdata->is_powering_on){
+		/* reconfigure gpio for sleep mode */
+		blndevdata->pdata->touchkey_sleep_onoff(TOUCHKEY_ON);
+
+		/*
+		 * power on the touchkey controller
+		 * This is actually not needed, but it is intentionally
+		 * left for the case that the early_resume() function
+		 * did not power on the touchkey controller for some reasons
+		 */
+		blndevdata->pdata->touchkey_onoff(TOUCHKEY_ON);
+
+		/* write to i2cbus, enable backlights */
+		enable_touchkey_backlights();
+	}
+	else
+#ifdef CONFIG_TOUCH_WAKE
+	    {
+		enable_touchkey_backlights();
+	    }
+#else
+	    pr_info("%s: cannot set notification led, touchkeys are enabled\n",__FUNCTION__);
+#endif
+}
+
+static void cypress_touchkey_disable_led_notification(void){
+	/*
+	 * reconfigure gpio for sleep mode, this has to be done
+	 * independently from the power status
+	 */
+	blndevdata->pdata->touchkey_sleep_onoff(TOUCHKEY_OFF);
+
+	/* if touchkeys lights are not used for touchmode */
+	if (blndevdata->is_powering_on){
+		disable_touchkey_backlights();
+
+		#if 0
+		/*
+		 * power off the touchkey controller
+		 * This is actually not needed, the early_suspend function
+		 * should take care of powering off the touchkey controller
+		 */
+		blndevdata->pdata->touchkey_onoff(TOUCHKEY_OFF);
+		#endif
+	}
+#ifdef CONFIG_TOUCH_WAKE
+	else
+	    {
+		disable_touchkey_backlights();
+	    }
+#endif	
+}
+
+static struct bln_implementation cypress_touchkey_bln = {
+	.enable = cypress_touchkey_enable_led_notification,
+	.disable = cypress_touchkey_disable_led_notification,
+};
+#endif
+
 static int cypress_touchkey_probe(struct i2c_client *client,
 		const struct i2c_device_id *id)
 {
@@ -429,6 +519,11 @@ static int cypress_touchkey_probe(struct i2c_client *client,
 
 	devdata->is_powering_on = false;
 
+#ifdef CONFIG_GENERIC_BLN
+	blndevdata = devdata;
+	register_bln_implementation(&cypress_touchkey_bln);
+#endif
+
 	return 0;
 
 err_req_irq:
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 803b67e..849bd4d 100755
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -575,4 +575,11 @@ config PN544
 	help
 	  NXP PN544 Near Field Communication controller support.
 
+config GENERIC_BLN
+       bool "Generic BLN support for backlight notification"
+       default y
+       help
+         Say Y here to enable the backlight notification
+         for android led-notification (modified liblight needed)
+
 endif # MISC_DEVICES
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 898516f..2777757 100755
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_PN544)		+= pn544.o
 obj-$(CONFIG_SAMSUNG_JACK)	+= sec_jack.o
 obj-$(CONFIG_USB_SWITCH_FSA9480)	+= fsa9480.o
 obj-$(CONFIG_SAMSUNG_MODEMCTL) += samsung_modemctl/
+obj-$(CONFIG_GENERIC_BLN)	+= bln.o
diff --git a/drivers/misc/bln.c b/drivers/misc/bln.c
new file mode 100644
index 0000000..85300ef
--- /dev/null
+++ b/drivers/misc/bln.c
@@ -0,0 +1,313 @@
+/* drivers/misc/bln.c
+ *
+ * Copyright 2011  Michael Richter (alias neldar)
+ * Copyright 2011  Adam Kent <adam@semicircular.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/earlysuspend.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/bln.h>
+#include <linux/mutex.h>
+#include <linux/timer.h>
+#include <linux/wakelock.h>
+
+static bool bln_enabled = true; /* is BLN function is enabled */
+static bool bln_ongoing = false; /* ongoing LED Notification */
+static int bln_blink_state = 0;
+static bool bln_suspended = false; /* is system suspended */
+static struct bln_implementation *bln_imp = NULL;
+static bool in_kernel_blink = false;
+static uint32_t blink_count;
+
+static struct wake_lock bln_wake_lock;
+
+void bl_timer_callback(unsigned long data);
+static struct timer_list blink_timer =
+		TIMER_INITIALIZER(bl_timer_callback, 0, 0);
+static void blink_callback(struct work_struct *blink_work);
+static DECLARE_WORK(blink_work, blink_callback);
+
+#define BLINK_INTERVAL 500 /* on / off every 500ms */
+#define MAX_BLINK_COUNT 600 /* 10 minutes */
+#define BACKLIGHTNOTIFICATION_VERSION 9
+
+static void bln_enable_backlights(void)
+{
+	if (bln_imp)
+		bln_imp->enable();
+}
+
+static void bln_disable_backlights(void)
+{
+	if (bln_imp)
+		bln_imp->disable();
+}
+
+static void bln_early_suspend(struct early_suspend *h)
+{
+	bln_suspended = true;
+}
+
+static void bln_late_resume(struct early_suspend *h)
+{
+	bln_suspended = false;
+}
+
+static struct early_suspend bln_suspend_data = {
+	.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1,
+	.suspend = bln_early_suspend,
+	.resume = bln_late_resume,
+};
+
+static void enable_led_notification(void)
+{
+	if (!bln_enabled)
+		return;
+
+	if (in_kernel_blink) {
+		wake_lock(&bln_wake_lock);
+
+		/* Start timer */
+		blink_timer.expires = jiffies +
+				msecs_to_jiffies(BLINK_INTERVAL);
+		blink_count = MAX_BLINK_COUNT;
+		add_timer(&blink_timer);
+	}
+
+	bln_enable_backlights();
+	pr_info("%s: notification led enabled\n", __FUNCTION__);
+	bln_ongoing = true;
+}
+
+static void disable_led_notification(void)
+{
+	pr_info("%s: notification led disabled\n", __FUNCTION__);
+
+	bln_blink_state = 0;
+	bln_ongoing = false;
+
+	if (bln_suspended)
+		bln_disable_backlights();
+
+	if (in_kernel_blink)
+		del_timer(&blink_timer);
+
+	wake_unlock(&bln_wake_lock);
+
+}
+
+static ssize_t backlightnotification_status_read(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", (bln_enabled ? 1 : 0));
+}
+
+static ssize_t backlightnotification_status_write(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	unsigned int data;
+	if(sscanf(buf, "%u\n", &data) == 1) {
+		pr_devel("%s: %u \n", __FUNCTION__, data);
+		if (data == 1) {
+			pr_info("%s: BLN function enabled\n", __FUNCTION__);
+			bln_enabled = true;
+		} else if (data == 0) {
+			pr_info("%s: BLN function disabled\n", __FUNCTION__);
+			bln_enabled = false;
+			if (bln_ongoing)
+				disable_led_notification();
+		} else {
+			pr_info("%s: invalid input range %u\n", __FUNCTION__,
+					data);
+		}
+	} else {
+		pr_info("%s: invalid input\n", __FUNCTION__);
+	}
+
+	return size;
+}
+
+static ssize_t notification_led_status_read(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf,"%u\n", (bln_ongoing ? 1 : 0));
+}
+
+static ssize_t notification_led_status_write(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	unsigned int data;
+
+	if (sscanf(buf, "%u\n", &data) == 1) {
+		if (data == 1)
+			enable_led_notification();
+		else if (data == 0)
+			disable_led_notification();
+		else
+			pr_info("%s: wrong input %u\n", __FUNCTION__, data);
+	} else {
+		pr_info("%s: input error\n", __FUNCTION__);
+	}
+
+	return size;
+}
+
+static ssize_t in_kernel_blink_status_read(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf,"%u\n", (in_kernel_blink ? 1 : 0));
+}
+
+static ssize_t in_kernel_blink_status_write(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	unsigned int data;
+
+	if (sscanf(buf, "%u\n", &data) == 1)
+		in_kernel_blink = !!(data);
+	else
+		pr_info("%s: input error\n", __FUNCTION__);
+
+	return size;
+}
+static ssize_t blink_control_read(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", bln_blink_state);
+}
+
+static ssize_t blink_control_write(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	unsigned int data;
+
+	if (!bln_ongoing)
+		return size;
+
+	if (sscanf(buf, "%u\n", &data) == 1) {
+		if (data == 1) {
+			bln_blink_state = 1;
+			bln_disable_backlights();
+		} else if (data == 0) {
+			bln_blink_state = 0;
+			bln_enable_backlights();
+		} else {
+			pr_info("%s: wrong input %u\n", __FUNCTION__, data);
+		}
+	} else {
+		pr_info("%s: input error\n", __FUNCTION__);
+	}
+
+	return size;
+}
+
+static ssize_t backlightnotification_version(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", BACKLIGHTNOTIFICATION_VERSION);
+}
+
+static DEVICE_ATTR(blink_control, S_IRUGO | S_IWUGO, blink_control_read,
+		blink_control_write);
+static DEVICE_ATTR(enabled, S_IRUGO | S_IWUGO,
+		backlightnotification_status_read,
+		backlightnotification_status_write);
+static DEVICE_ATTR(notification_led, S_IRUGO | S_IWUGO,
+		notification_led_status_read,
+		notification_led_status_write);
+static DEVICE_ATTR(in_kernel_blink, S_IRUGO | S_IWUGO,
+		in_kernel_blink_status_read,
+		in_kernel_blink_status_write);
+static DEVICE_ATTR(version, S_IRUGO , backlightnotification_version, NULL);
+
+static struct attribute *bln_notification_attributes[] = {
+	&dev_attr_blink_control.attr,
+	&dev_attr_enabled.attr,
+	&dev_attr_notification_led.attr,
+	&dev_attr_in_kernel_blink.attr,
+	&dev_attr_version.attr,
+	NULL
+};
+
+static struct attribute_group bln_notification_group = {
+	.attrs  = bln_notification_attributes,
+};
+
+static struct miscdevice bln_device = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "backlightnotification",
+};
+
+void register_bln_implementation(struct bln_implementation *imp)
+{
+	bln_imp = imp;
+}
+EXPORT_SYMBOL(register_bln_implementation);
+
+bool bln_is_ongoing()
+{
+	return bln_ongoing;
+}
+EXPORT_SYMBOL(bln_is_ongoing);
+
+
+static void blink_callback(struct work_struct *blink_work)
+{
+	if (--blink_count == 0) {
+		pr_info("%s: notification timed out\n", __FUNCTION__);
+		bln_enable_backlights();
+		del_timer(&blink_timer);
+		wake_unlock(&bln_wake_lock);
+		return;
+	}
+
+	if (bln_blink_state)
+		bln_enable_backlights();
+	else
+		bln_disable_backlights();
+
+	bln_blink_state = !bln_blink_state;
+}
+
+void bl_timer_callback(unsigned long data)
+{
+	schedule_work(&blink_work);
+	mod_timer(&blink_timer, jiffies + msecs_to_jiffies(BLINK_INTERVAL));
+}
+
+static int __init bln_control_init(void)
+{
+	int ret;
+
+	pr_info("%s misc_register(%s)\n", __FUNCTION__, bln_device.name);
+	ret = misc_register(&bln_device);
+	if (ret) {
+		pr_err("%s misc_register(%s) fail\n", __FUNCTION__,
+				bln_device.name);
+		return 1;
+	}
+
+	/* add the bln attributes */
+	if (sysfs_create_group(&bln_device.this_device->kobj,
+				&bln_notification_group) < 0) {
+		pr_err("%s sysfs_create_group fail\n", __FUNCTION__);
+		pr_err("Failed to create sysfs group for device (%s)!\n",
+				bln_device.name);
+	}
+
+	register_early_suspend(&bln_suspend_data);
+
+    /* Initialize wake locks */
+	wake_lock_init(&bln_wake_lock, WAKE_LOCK_SUSPEND, "bln_wake");
+
+	return 0;
+}
+
+device_initcall(bln_control_init);
diff --git a/include/linux/bln.h b/include/linux/bln.h
new file mode 100644
index 0000000..a5de5e4
--- /dev/null
+++ b/include/linux/bln.h
@@ -0,0 +1,13 @@
+/* include/linux/bln.h */
+
+#ifndef _LINUX_BLN_H
+#define _LINUX_BLN_H
+
+struct bln_implementation {
+	void (*enable)(void);
+	void (*disable)(void);
+};
+
+void register_bln_implementation(struct bln_implementation *imp);
+bool bln_is_ongoing(void);
+#endif
diff --git a/include/linux/input/cypress-touchkey.h b/include/linux/input/cypress-touchkey.h
index ca65472..cd4ff4d 100755
--- a/include/linux/input/cypress-touchkey.h
+++ b/include/linux/input/cypress-touchkey.h
@@ -25,6 +25,7 @@ struct touchkey_platform_data {
 	int keycode_cnt;
 	const int *keycode;
 	void (*touchkey_onoff) (int);
+	void (*touchkey_sleep_onoff) (int);
 	const char *fw_name;
 	int scl_pin;
 	int sda_pin;

Second one available on https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/ed7c1efa3c60f3835c5fad58c0b6115d4afaf977.patch adds support for BLN to blink the soft keys instead of glowing all the time.

From ed7c1efa3c60f3835c5fad58c0b6115d4afaf977 Mon Sep 17 00:00:00 2001
From: drizztbsd <timothy.redaelli@gmail.com>
Date: Mon, 2 Jan 2012 21:10:04 +0100
Subject: [PATCH] Add blink_interval and max_blink_count in sysfs (bln)

Signed-off-by: Ezekeel <notezekeel@googlemail.com>
---
 drivers/misc/bln.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 53 insertions(+), 5 deletions(-)

diff --git a/drivers/misc/bln.c b/drivers/misc/bln.c
index 85300ef..88b47a8 100644
--- a/drivers/misc/bln.c
+++ b/drivers/misc/bln.c
@@ -34,8 +34,9 @@ static struct timer_list blink_timer =
 static void blink_callback(struct work_struct *blink_work);
 static DECLARE_WORK(blink_work, blink_callback);
 
-#define BLINK_INTERVAL 500 /* on / off every 500ms */
-#define MAX_BLINK_COUNT 600 /* 10 minutes */
+static uint32_t blink_interval = 750;	/* on / off every 750ms */
+static uint32_t max_blink_count = 600;  /* 10 minutes */
+
 #define BACKLIGHTNOTIFICATION_VERSION 9
 
 static void bln_enable_backlights(void)
@@ -76,8 +77,8 @@ static void enable_led_notification(void)
 
 		/* Start timer */
 		blink_timer.expires = jiffies +
-				msecs_to_jiffies(BLINK_INTERVAL);
-		blink_count = MAX_BLINK_COUNT;
+				msecs_to_jiffies(blink_interval);
+		blink_count = max_blink_count;
 		add_timer(&blink_timer);
 	}
 
@@ -177,6 +178,45 @@ static ssize_t in_kernel_blink_status_write(struct device *dev,
 
 	return size;
 }
+
+static ssize_t blink_interval_status_read(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf,"%u\n", blink_interval);
+}
+
+static ssize_t blink_interval_status_write(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	unsigned int data;
+
+	if (sscanf(buf, "%u\n", &data) == 1)
+		blink_interval = data;
+	else
+		pr_info("%s: input error\n", __FUNCTION__);
+
+	return size;
+}
+
+static ssize_t max_blink_count_status_read(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf,"%u\n", max_blink_count);
+}
+
+static ssize_t max_blink_count_status_write(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	unsigned int data;
+
+	if (sscanf(buf, "%u\n", &data) == 1)
+		max_blink_count = data;
+	else
+		pr_info("%s: input error\n", __FUNCTION__);
+
+	return size;
+}
+
 static ssize_t blink_control_read(struct device *dev,
 		struct device_attribute *attr, char *buf)
 {
@@ -225,6 +265,12 @@ static DEVICE_ATTR(notification_led, S_IRUGO | S_IWUGO,
 static DEVICE_ATTR(in_kernel_blink, S_IRUGO | S_IWUGO,
 		in_kernel_blink_status_read,
 		in_kernel_blink_status_write);
+static DEVICE_ATTR(blink_interval, S_IRUGO | S_IWUGO,
+		blink_interval_status_read,
+		blink_interval_status_write);
+static DEVICE_ATTR(max_blink_count, S_IRUGO | S_IWUGO,
+		max_blink_count_status_read,
+		max_blink_count_status_write);
 static DEVICE_ATTR(version, S_IRUGO , backlightnotification_version, NULL);
 
 static struct attribute *bln_notification_attributes[] = {
@@ -232,6 +278,8 @@ static struct attribute *bln_notification_attributes[] = {
 	&dev_attr_enabled.attr,
 	&dev_attr_notification_led.attr,
 	&dev_attr_in_kernel_blink.attr,
+	&dev_attr_blink_interval.attr,
+	&dev_attr_max_blink_count.attr,
 	&dev_attr_version.attr,
 	NULL
 };
@@ -279,7 +327,7 @@ static void blink_callback(struct work_struct *blink_work)
 void bl_timer_callback(unsigned long data)
 {
 	schedule_work(&blink_work);
-	mod_timer(&blink_timer, jiffies + msecs_to_jiffies(BLINK_INTERVAL));
+	mod_timer(&blink_timer, jiffies + msecs_to_jiffies(blink_interval));
 }
 
 static int __init bln_control_init(void)

Third .patch file, available at https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/a346ac514f6c9a7d5c7b0b1345f2e3f2475d12ef.patch is basically a bug fix for the previous .patch file regarding the blinking functionality.

From a346ac514f6c9a7d5c7b0b1345f2e3f2475d12ef Mon Sep 17 00:00:00 2001
From: Will Tisdale <willtisdale@gmail.com>
Date: Fri, 27 Jul 2012 20:25:37 +0100
Subject: [PATCH] bln: Fix kernel panic caused by BUG() in timer.c Check to see
 if blink_timer is pending before attempting to add it. If it is pending, use
 mod_timer to change the timeout. Adding regardless results in a BUG() and a
 subsequent panic.

---
 drivers/misc/bln.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/drivers/misc/bln.c b/drivers/misc/bln.c
index 96ce234..be1ed92 100644
--- a/drivers/misc/bln.c
+++ b/drivers/misc/bln.c
@@ -79,7 +79,15 @@ static void enable_led_notification(void)
 		blink_timer.expires = jiffies +
 				msecs_to_jiffies(blink_interval);
 		blink_count = max_blink_count;
-		add_timer(&blink_timer);
+		/*
+		 * Check for pending timer and use mod_timer
+		 * if it exists instead of attempting to
+		 * add another, which results in a panic
+		 */
+		if (timer_pending(&blink_timer))
+			mod_timer(&blink_timer, blink_timer.expires);
+		else
+			add_timer(&blink_timer);
 	}
 
 	bln_enable_backlights();

We download all .patch files one by one to be sure and apply them same as demonstrated before.

$ curl https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/d5750ccf7200f8277bada0191f05355bd2f4ac2a.patch | git am
$ curl https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/ed7c1efa3c60f3835c5fad58c0b6115d4afaf977.patch | git am
$ curl https://github.com/jellybelly-rom/android_kernel_samsung_crespo/commit/a346ac514f6c9a7d5c7b0b1345f2e3f2475d12ef.patch | git am

Now we’re set and can proceed to building.

Building the kernel

While being in the same working directory as before we configure the building configuration. This configures the defaults for the architecture.

$ make ARCH=arm crespo_test_defconfig

After that all we have to do is initiate the build using the downloaded ARM-eabi toolchain.

$ make ARCH=arm CROSS_COMPILE=../arm-eabi-4.6/bin/arm-eabi-

The finished kernel file zImage will be in the arch/arm/boot/ folder. We could flash it via fastboot tool in the phone bootloader, but we will make a flashable .zip instead for friendlier distribution.

Getting it flash ready

I’ve explained how to make one in an earlier post. Structure differs for this one a bit, since it containes a kernel image and a an extra script which will convert the zImage to boot.img and replace the stock kernel:

├── system/
|    ├── lib/
|        ├── hw/                        # folder where the .so will be flashed to
|            ├── lights.s5pc110.so      # modified system module with support for BLN
├── kernel
|   ├── zImage                          # built kernel
|   ├── mkbootimg_crespo.sh             # shell script which makes a boot.img out of zImage
|   ├── mkbootimg                       # helper binaries
|   ├── unpackbootimg                   # binaries
|   ├── dump_image                      # for conversion
├── META-INF/
|    ├── com/
         ├── google/
             ├── android/
                 ├── updater-script     # script where you declare how the .zip should operate
                 ├── update-binary      # update binary compatible with the signature, it's reusable               
|    ├── CERT.RSA                       # signature files which
|    ├── CERT.SF                        # are reusable
|    └── MANIFEST.MF                    # in every .zip

update-script, which is written in Edify needs to look like this:

assert(getprop("ro.product.device") == "crespo" || getprop("ro.build.product") == "crespo" || getprop("ro.product.device") == "crespo4g" || getprop("ro.build.product") == "crespo4g");
ui_print("_thalamus kernel installer for crespo/crespo4g...");
ui_print("Extracting Kernel files...");
package_extract_dir("kernel", "/tmp");
set_perm(0, 0, 0777, "/tmp/dump_image");
set_perm(0, 0, 0777, "/tmp/mkbootimg_crespo.sh");
set_perm(0, 0, 0777, "/tmp/mkbootimg");
set_perm(0, 0, 0777, "/tmp/unpackbootimg");
ui_print("Dumping boot image...");
run_program("/tmp/dump_image", "boot", "/tmp/boot.img");
ui_print("Unpacking boot image...");
run_program("/tmp/unpackbootimg", "-p", "0x00001000", "-i", "/tmp/boot.img", "-o", "/tmp/");
ui_print("Generating new boot image...");
run_program("/tmp/mkbootimg_crespo.sh");
ui_print("Writing boot image...");
assert(write_raw_image("/tmp/newboot.img", "boot"), 
	delete("/tmp/boot.img"));
ui_print("Boot image successfully written...");

ui_print("Mounting system...");
mount("ext4", "EMMC", "/dev/block/platform/s3c-sdhci.0/by-name/system", "/system");
ui_print("Deleting old kernel modules...");
delete_recursive("/system/modules/");
delete_recursive("/system/lib/modules/");
ui_print("Extracting new kernel modules & liblights for BLN...");
package_extract_dir("system", "/system");
symlink("/system/modules/bcm4329.ko", "/system/lib/modules/bcm4329.ko");
ui_print("Unmounting system...");
unmount("/system");
ui_print("Complete."); 

Script basically makes sure the .zip is flashed on the proper device crespo and crespo4g, then it dumps the current kernel image, runs the mkbootimg.sh shell script to create a new boot.img from our built zImage and replaces the old one.

Then it removes old kernel modules and copies the modified liblights module.

Now we can use BLN with our Nexus S again and have a working CRT off effect.


Conclusion

As usually I’ve posted back my work to the xda-developers, which made quite some fans of the _thalamus kernel happy, as _thalamus himself, as he didn’t need to maintain the BLN and CRT off fixes himself.

It was a fresh learning experience, where I gained some insights about kernel building and using git and .patch files.