Mar 07

The stock Raspberry Pi kernel, as supplied with the default Raspbian distribution, is pretty good – albeit not always especially streamlined. However, building a custom kernel with different options can be an unexpected minefield: many seemingly innocuous changes can cause the Pi to fail to boot – often with the VideoCore system failing to initialise or with USB networking problems.

There follows a list of patches and observations which should hope to make this process less trial-and-error…

Firstly, it’s worth noting that the most recent release of Linux at the time of writing is 3.8.2, whereas the Raspberry Pi custom patches only work with the 3.6.11 kernel – available from https://github.com/raspberrypi/linux.git in the ‘rpi-3.6.y‘ branch. Kernel 3.9 is due to gain initial support for the Broadcom SoC used in the Raspberry Pi – but this will likely only be sufficient to boot the system at most. For full support for the foreseeable future, the custom kernel will be required.

The raspberrypi kernel allows the selection of ‘SLAB‘, ‘SLUB‘, or ‘SLOB‘ as an allocator, with decreasing complexity and increasing simplicity. However, the kernel will crash during the boot-process if ‘SLOB‘ is chosen.

In a similar fashion, I have a Realtek RTL8192CU-based USB Wifi device for my Pi. This doesn’t explicitly depend on the kernel’s Wireless Extensions code – but will fail to compile if another driver hasn’t pulled-in these components. The following patch addresses this:

--- drivers/net/wireless/rtl8192cu/Kconfig
+++ drivers/net/wireless/rtl8192cu/Kconfig
@@ -1,6 +1,8 @@
 config RTL8192CU
        tristate "Realtek 8192C USB WiFi"
        depends on USB
+       select WIRELESS_EXT
+       select WEXT_PRIV
        ---help---
          Help message of RTL8192CU
 

The raspberrypi source has been forked at https://github.com/afterthoughtsoftware/linux to provide support for a GPIO-connected Real-Time Clock. However, the significant change is as follows:

diff -uNp raspberrypi-kernel/drivers/rtc/Kconfig afterthought-kernel/drivers/rtc/Kconfig
--- raspberrypi-kernel/drivers/rtc/Kconfig      2012-10-14 15:51:03.186723306 +0100
+++ afterthought-kernel/drivers/rtc/Kconfig     2013-03-02 12:57:33.589899248 +0000
@@ -281,6 +281,17 @@ config RTC_DRV_PCF8583
          This driver can also be built as a module. If so, the module
          will be called rtc-pcf8583.
 
+config RTC_DRV_PCF2127A
+       tristate "Philips PCF2127A (only I2C)"
+       help
+         If you say yes here you get support for the Philips PCF2127A
+         RTC. This driver implements only the i2c-interface to the chip.
+         It may work with pcf2127, but is tested only against
+         the pcf2127a.
+
+         This driver can also be built as a module. If so, the module
+         will be called rtc-pcf2127a.
+
 config RTC_DRV_M41T80
        tristate "ST M41T62/65/M41T80/81/82/83/84/85/87"
        help
diff -uNp raspberrypi-kernel/drivers/rtc/Makefile afterthought-kernel/drivers/rtc/Makefile
--- raspberrypi-kernel/drivers/rtc/Makefile     2012-10-14 15:51:03.186723306 +0100
+++ afterthought-kernel/drivers/rtc/Makefile    2013-03-02 12:57:33.589899248 +0000
@@ -77,6 +77,7 @@ obj-$(CONFIG_RTC_DRV_PCAP)    += rtc-pcap.o
 obj-$(CONFIG_RTC_DRV_PCF8563)  += rtc-pcf8563.o
 obj-$(CONFIG_RTC_DRV_PCF8583)  += rtc-pcf8583.o
 obj-$(CONFIG_RTC_DRV_PCF2123)  += rtc-pcf2123.o
+obj-$(CONFIG_RTC_DRV_PCF2127A)  += rtc-pcf2127a.o
 obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
 obj-$(CONFIG_RTC_DRV_PL030)    += rtc-pl030.o
 obj-$(CONFIG_RTC_DRV_PL031)    += rtc-pl031.o
diff -uNp raspberrypi-kernel/drivers/rtc/rtc-pcf2127a.c afterthought-kernel/drivers/rtc/rtc-pcf2127a.c
--- raspberrypi-kernel/drivers/rtc/rtc-pcf2127a.c       1970-01-01 01:00:00.000000000 +0100
+++ afterthought-kernel/drivers/rtc/rtc-pcf2127a.c      2013-03-02 12:57:33.616565987 +0000
@@ -0,0 +1,307 @@
+/*
+ * Driver the the NXP/Philips PCF2127A RTC (only I2C)
+ *
+ * Copyright (c) 2011 ECKELMANN AG.
+ *
+ * Authors:    Matthias Wolf  <m.w...@eckelmann.de>
+ *             Torsten Mehnert <t.me...@eckelmann.de>
+ *
+ * RTC-Datasheet: http://www.nxp.com/documents/data_sheet/PCF2127A.pdf
+ *
+ * NOTE: This driver only implements the i2c-interface to the RTC.
+ * This driver is based on rtc-pcf8363 from Alessandro Zummo.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+/* PCF2127A Registers */
+#define PCF2127A_REG_CTL1              0x00 /* Control 1 */
+#define PCF2127A_REG_CTL2              0x01 /* Control 2 */
+#define PCF2127A_REG_CTL3              0x02 /* Control 3 */
+
+#define PCF2127A_REG_SC                        0x03 /* second value    */
+#define PCF2127A_REG_MN                        0x04 /* minute value    */
+#define PCF2127A_REG_HR                        0x05 /* hour value      */
+#define PCF2127A_REG_DM                        0x06 /* day value       */
+#define PCF2127A_REG_DW                        0x07 /* weekday value   */
+#define PCF2127A_REG_MO                        0x08 /* month value     */
+#define PCF2127A_REG_YR                        0x09 /* year value      */
+
+#define PCF2127A_REG_SC_ALARM          0x0A /* second alarm            */
+#define PCF2127A_REG_MN_ALARM          0x0B /* minute alarm            */
+#define PCF2127A_REG_HR_ALARM          0x0C /* hour alarm              */
+#define PCF2127A_REG_DM_ALARM          0x0D /* day alarm               */
+#define PCF2127A_REG_DW_ALARM          0x0E /* weekday alarm           */
+
+#define PCF2127A_REG_CLKO              0x0F /* clock out       */
+#define PCF2127A_REG_WD_TMRC           0x10 /* watchdog timer control  */
+#define PCF2127A_REG_WD_TMVAL          0x11 /* watchdog timer value    */
+
+#define PCF2127A_REG_TSCTL             0x12 /* timestamp control       */
+#define PCF2127A_REG_SC_TS             0x13 /* second timestamp        */
+#define PCF2127A_REG_MN_TS             0x14 /* minute timestamp        */
+#define PCF2127A_REG_HR_TS             0x15 /* hour timestamp          */
+#define PCF2127A_REG_DM_TS             0x16 /* day timestamp           */
+#define PCF2127A_REG_MO_TS             0x17 /* month timestamp         */
+#define PCF2127A_REG_YR_TS             0x18 /* year timestamp          */
+#define PCF2127A_REG_AGI_OFF           0x19 /* aging offset            */
+#define PCF2127A_REG_RAM_MSB           0x1A /* RAM address MSB         */
+#define PCF2127A_REG_RAM_LSB           0x1B /* RAM address LSB         */
+#define PCF2127A_REG_RAM_WCMD          0x1C /* RAM write command       */
+#define PCF2127A_REG_RAM_RCMD          0x1D /* RAM read command        */
+
+/* Bits in PCF2127A_REG_CTL1 */
+#define PCF2127A_BIT_SI                        0x01 /* second interrupt        */
+#define PCF2127A_BIT_MI                        0x02 /* minute interrupt        */
+#define PCF2127A_BIT_H_24_12           0x04 /* 12/24 hour mode         */
+#define PCF2127A_BIT_POR_OVRD          0x08 /* POR override            */
+#define PCF2127A_BIT_TSF1              0x10 /* timestamp flag 1        */
+#define PCF2127A_BIT_STOP              0x20 /* stop bit                */
+#define PCF2127A_BIT_EXTTEST           0x80 /* external clock test mode*/
+
+/* Bits in PCF2127A_REG_CTL2 */
+#define PCF2127A_BIT_CDTIE             0x01 /* countdown timer flag    */
+#define PCF2127A_BIT_AIE               0x02 /* alarm interrupt enable  */
+#define PCF2127A_BIT_TSIE              0x04 /* timestamp int. enable   */
+#define PCF2127A_BIT_CDTF              0x08 /* countdown timer flag    */
+#define PCF2127A_BIT_AF                        0x10 /* alarm flag              */
+#define PCF2127A_BIT_TSF2              0x20 /* timestamp flag 2        */
+#define PCF2127A_BIT_WDTF              0x40 /* watchdog timer flag     */
+#define PCF2127A_BIT_MSF               0x80 /* minute/second flag      */
+
+/* Bits in PCF2127A_REG_CTL3 */
+#define PCF2127A_BIT_BLIE              0x01 /* battery low Interrupt enable */
+#define PCF2127A_BIT_BIE               0x02 /* battery Switch int. enable */
+#define PCF2127A_BIT_BLF               0x04 /* battery low flag        */
+#define PCF2127A_BIT_BF                        0x08 /* battery switch flag     */
+#define PCF2127A_BIT_BTSE              0x10 /* battery timestamp enable */
+#define PCF2127A_BIT_BLDOFF            0x20 /* battery low detection off */
+#define PCF2127A_BIT_BSM               0x40 /* battery switch mode     */
+#define PCF2127A_BIT_BSOFF             0x80 /* battery switch off      */
+
+static struct i2c_driver pcf2127a_driver;
+
+/*
+ * In the routines that deal directly with the pcf2127a hardware, we use
+ * rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch.
+ */
+static
+int pcf2127a_get_datetime(struct i2c_client *client, struct rtc_time *tm)
+{
+       unsigned char buf[10] = { PCF2127A_REG_CTL1 };
+
+       /*
+       * Note: We can't use i2c_transfer to send/recv multiple i2c_msg's
+       * to/from pcf2127a at once. The reason is that the pcf2127a doesn't
+       * handle (repeated START)-Conditions correctly.
+       *
+       * PCF2127A Manual, Section 9.2.2 START and STOP conditions:
+       * "Remark: For the PCF2127A a repeated START is not allowed. Therefore
+       * a STOP has to be released before the next START."
+       */
+
+       /* setup read ptr */
+       if (!i2c_master_send(client, buf, 1)) {
+               dev_err(&client->dev, "%s: write error\n", __func__);
+               return -EIO;
+       }
+
+       /* read status + date */
+       if (!i2c_master_recv(client, buf, 10)) {
+               dev_err(&client->dev, "%s: read error\n", __func__);
+               return -EIO;
+       }
+
+       if (buf[PCF2127A_REG_CTL3] & PCF2127A_BIT_BLF)
+               dev_info(&client->dev,
+                       "low battery detected, date/time is not reliable.\n");
+
+#ifdef DEBUG
+       unsigned char bf_buf[2];
+       if (buf[PCF2127A_REG_CTL3] & PCF2127A_BIT_BF) {
+               dev_info(&client->dev,
+                       "battery switch-over detected - reset BF flag.\n");
+
+               bf_buf[0] = PCF2127A_REG_CTL3;
+               bf_buf[1] = buf[PCF2127A_REG_CTL3] & ~PCF2127A_BIT_BF;
+               if (!i2c_master_send(client, bf_buf, 2)) {
+                               dev_err(&client->dev,
+                                       "%s: write error\n", __func__);
+                               return -EIO;
+                       }
+               }
+#endif
+
+       dev_dbg(&client->dev,
+               "%s: raw data is ctl1=%02x, ctl2=%02x, ctl3=%02x, sec=%02x, "
+               "min=%02x, hour=%02x, day=%02x, wday=%02x, month=%02x, "
+               "year=%02x\n",
+               __func__,
+               buf[0], buf[1], buf[2], buf[3],
+               buf[4], buf[5], buf[6], buf[7],
+               buf[8], buf[9]);
+
+       tm->tm_sec = bcd2bin(buf[PCF2127A_REG_SC] & 0x7F);
+       tm->tm_min = bcd2bin(buf[PCF2127A_REG_MN] & 0x7F);
+       tm->tm_hour = bcd2bin(buf[PCF2127A_REG_HR] & 0x3F); /* rtc hr 0-23 */
+       tm->tm_mday = bcd2bin(buf[PCF2127A_REG_DM] & 0x3F);
+       tm->tm_wday = buf[PCF2127A_REG_DW] & 0x07;
+       tm->tm_mon = bcd2bin(buf[PCF2127A_REG_MO] & 0x1F) - 1; /* rtc m 1-12 */
+       tm->tm_year = bcd2bin(buf[PCF2127A_REG_YR]);
+
+       if (tm->tm_year < 70)
+               tm->tm_year += 100;     /* assume we are in 1970...2069 */
+
+       /*
+        * the clock can give out invalid datetime, but we cannot return
+        * -EINVAL otherwise hwclock will refuse to set the time on bootup.
+        */
+       if (rtc_valid_tm(tm) < 0)
+               dev_err(&client->dev, "retrieved date/time is not valid.\n");
+
+       return 0;
+}
+
+static
+int pcf2127a_set_datetime(struct i2c_client *client, struct rtc_time *tm)
+{
+       int i, err;
+       unsigned char msgdata[2];       /* send data*/
+       unsigned char buf[10];          /* Index 0 - 9 */
+
+       /* hours, minutes and seconds */
+       buf[PCF2127A_REG_SC] = bin2bcd(tm->tm_sec);
+       buf[PCF2127A_REG_MN] = bin2bcd(tm->tm_min);
+       buf[PCF2127A_REG_HR] = bin2bcd(tm->tm_hour);
+
+       /* day of month */
+       buf[PCF2127A_REG_DM] = bin2bcd(tm->tm_mday);
+
+       /* weekday */
+       buf[PCF2127A_REG_DW] = tm->tm_wday & 0x07;
+
+       /* month, 1 - 12 */
+       buf[PCF2127A_REG_MO] = bin2bcd(tm->tm_mon + 1);
+
+       /* year */
+       buf[PCF2127A_REG_YR] = bin2bcd(tm->tm_year % 100);
+
+       /* write register's data */
+       for (i = 0; i < 7; i++) {
+               msgdata[0] = PCF2127A_REG_SC + i;
+               msgdata[1] = buf[PCF2127A_REG_SC + i];
+               err = i2c_master_send(client, msgdata, sizeof(msgdata));
+               if (err != sizeof(msgdata)) {
+                       dev_err(&client->dev,
+                               "%s: err=%d addr=%02x, data=%02x\n",
+                               __func__, err, msgdata[0], msgdata[1]);
+                       return -EIO;
+               }
+       }
+       return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static int pcf2127a_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+       return pcf2127a_get_datetime(to_i2c_client(dev), tm);
+}
+
+static int pcf2127a_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+       return pcf2127a_set_datetime(to_i2c_client(dev), tm);
+}
+
+static const struct rtc_class_ops pcf2127a_rtc_ops = {
+       .read_time      = pcf2127a_rtc_read_time,
+       .set_time       = pcf2127a_rtc_set_time,
+};
+
+/*---------------------------------------------------------------------------*/
+
+static int pcf2127a_probe(struct i2c_client *client,
+                               const struct i2c_device_id *id)
+{
+       struct rtc_device *pcf2127a_device;
+
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+               return -ENODEV;
+
+       dev_info(&client->dev, "chip found\n");
+
+       pcf2127a_device = rtc_device_register(pcf2127a_driver.driver.name,
+                               &client->dev, &pcf2127a_rtc_ops, THIS_MODULE);
+
+       if (IS_ERR(pcf2127a_device))
+               return PTR_ERR(pcf2127a_device);
+
+       i2c_set_clientdata(client, pcf2127a_device);
+
+       return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static int pcf2127a_remove(struct i2c_client *client)
+{
+       struct rtc_device *pcf2127a_device = i2c_get_clientdata(client);
+
+       if (pcf2127a_device)
+               rtc_device_unregister(pcf2127a_device);
+
+       return 0;
+}
+
+static const struct i2c_device_id pcf2127a_id[] = {
+       /*
+        * This driver should also work with the pcf2127, but
+        * I can't prove it.
+        */
+       { "pcf2127a", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, pcf2127a_id);
+
+static struct i2c_driver pcf2127a_driver = {
+       .driver         = {
+               .name   = "rtc-pcf2127a",
+       },
+       .probe          = pcf2127a_probe,
+       .remove         = pcf2127a_remove,
+       .id_table       = pcf2127a_id,
+};
+
+static int __init pcf2127a_init(void)
+{
+       return i2c_add_driver(&pcf2127a_driver);
+}
+module_init(pcf2127a_init);
+
+static void __exit pcf2127a_exit(void)
+{
+       i2c_del_driver(&pcf2127a_driver);
+}
+module_exit(pcf2127a_exit);
+
+MODULE_AUTHOR("ECKELMANN AG");
+MODULE_DESCRIPTION("NXP/Philips PCF2127A RTC (I2C) driver");
+MODULE_LICENSE("GPL");

Happy compiling!

3 Responses to “Compiling a new Raspberry Pi kernel”

  1. Stuart Says:

    Hmm… fail :(

    I now have a kernel configuration which is 100% reliable when used head-less, or when the HDMI cable is connected after the system boots. However, if the system is (re)booted when connected to a TV, VideoCore fails to initialise. Sigh.

  2. Stuart Says:

    … sometimes. I’m now able to reboot with HDMI connected. A purely random occurrence, or perhaps temperature-dependent?

  3. Stuart Says:

    I’ve just bought an SDHC-extension cable to make the frequent swapping of SD cards easier – my Lego case is cool, but doesn’t afford especially easy access.

    It also doesn’t inhibit RF noise all that much, it would appear – due to the long cable, reading the SD card isn’t reliable near the TV. Whether this is the TV or the (unshielded) Pi more to blame, I’m not sure. I just need a Faraday Cage for the cable…

Leave a Reply

%d bloggers like this: