Pipad: Identifying the ambient light sensor

2024/03/05

View or comment on this project log on Hackaday.io

I've long been ignoring the ambient light sensor flex cable that's part of the iPad's display assembly. This connects to a 24-pin board-to-board connector on the logic board, pictured on the right edge here:

Only 7 of the 24 pins are used:

The other 17 pins are ground.

(Fun fact: this connector, the AA03-S024VA1, is also what they used for the camera in the iPhone 3G, though with a different pin configuration. Maybe they had a lot of extras and decided to reuse it on the iPad. It's not like they were low on space in the iPad, so using a larger-than-necessary connector wouldn't be a big deal.)

The last two pins are connected in the iPad schematics to "COMPASS_RST_L" and "COMPASS_INT_L", 

I've been including this connector in all my prototype PCBs, and I also created a breakout board for it. This weekend, I wired up the breakout board to a Pi 4 (just power and I2C, none of the interrupt/reset pins) and got to work trying to figure out what ambient light sensor IC is used.

What's i2c address 0x39?

By wiring up the breakout board and connecting it to a Raspberry Pi, we can see that this cable only seems to have one device on it, at address 0x39: 

meatmanek@pi4:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    

That's odd. I was expecting two devices: one for the compass, one for the ambient light sensor. Maybe it's a single IC?

Searching the internet for "i2c address 0x39" brought me to https://i2cdevices.org/addresses/0x39 which gives us a few options:

Hmm. 3 of those could be ambient light sensors (APDS-9960, TSL2561, VEML6070), but also surely there are more devices out there that use this address.

Not wanting to tear apart my iPad more than necessary, I took another look at iFixit's images of this flex cable: 

Enhance:

Enhance:

Looks like there's just one IC and a decoupling capacitor.

Here's what we know about this IC:

(I guess the compass may have been removed before the iPad shipped?)

Looking at the three options from i2cdevices.org:

The APDS-9960 does RGB and gesture detection, and doesn't look right:

The APDS-9960 has a black plastic shroud, which our IC doesn't have. Also it has too many features to be an ambient light sensor.

The VEML-6070 is a UV sensor, which doesn't make sense, and also it doesn't look right:

The TSL2561 looks promising:

The TSL2561 seems to be the right shape, not obviously the wrong color, and is the right kind of IC (Luminosity/Lux/Light sensor)

To check if this is correct, I got the Adafruit python driver for this chip:

meatmanek@pi4:~ $ python3 -m venv venv
meatmanek@pi4:~ $ . venv/bin/activate
(venv) meatmanek@pi4:~ $  pip3 install adafruit-circuitpython-tsl2561
...
Successfully installed Adafruit-Blinka-8.34.0 Adafruit-Circuitpython-ConnectionManager-1.0.1 Adafruit-PlatformDetect-3.62.0 Adafruit-PureIO-1.1.11 RPi.GPIO-0.7.1 adafruit-circuitpython-busdevice-5.2.6 adafruit-circuitpython-requests-3.0.1 adafruit-circuitpython-tsl2561-3.3.18 adafruit-circuitpython-typing-1.10.2 pyftdi-0.55.0 pyserial-3.5 pyusb-1.2.1 rpi-ws281x-5.0.0 sysv-ipc-1.1.0 typing-extensions-4.10.0
(venv) meatmanek@pi4:~ $ cat > tsl2561.py
# This is from https://learn.adafruit.com/tsl2561/python-circuitpython
import board
import busio
import adafruit_tsl2561
i2c = busio.I2C(board.SCL, board.SDA)
sensor = adafruit_tsl2561.TSL2561(i2c)

print('Lux: {}'.format(sensor.lux))
print('Broadband: {}'.format(sensor.broadband))
print('Infrared: {}'.format(sensor.infrared))
print('Luminosity: {}'.format(sensor.luminosity))
(venv) meatmanek@pi4:~ $ python ./tsl2561.py 
Traceback (most recent call last):
  File "/home/meatmanek/./tsl2561.py", line 5, in <module>
    sensor = adafruit_tsl2561.TSL2561(i2c)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/meatmanek/venv/lib/python3.11/site-packages/adafruit_tsl2561.py", line 75, in __init__
    raise RuntimeError(
RuntimeError: Failed to find TSL2561! Part 0x0 Rev 0x0

From reading the code, it seems to look at register 0x0A and report the upper 4 bits as the part number and the lower 4 bits as the revision number. The TSL2561 should have the part number set to 0x5.

Maybe it's some other chip. Let's see how many other 6-pin I2C light sensors that support 3.0V power exist on Digikey. 124? We're going to need more information. I decided to pull the flex PCB off my display to get a higher-resolution photo of the IC itself.

A photo taken with my USB microscope, showing the ambient light sensor IC.

This is actually surprisingly helpful. We can see the bond wires and the patterns on the IC, which we can try to match against the Digi-Key photos.

The TSL2561 looks promising, but we know that the Adafruit library for it doesn't work: 

The TSL2561 photo on Digi-Key. This is also the same photo they use for the TSL2571.

Other manufacturers look different:

The APDS-9306-063 has the bond wires going to the wrong place.
Wrong bond wires again on the VEML6035, plus the black background.

Based on this, I'm assuming it's another chip from ams-OSRAM, in the same family as the TSL2561.

Adafruit also has a driver for the TSL2591, since they sell that breakout board too. Let's try that?

meatmanek@pi4:~ $ cat > tsl2591.py
# adapted from https://learn.adafruit.com/adafruit-tsl2591/python-circuitpython
import board
import adafruit_tsl2591
i2c = board.I2C()
sensor = adafruit_tsl2591.TSL2591(i2c, address=0x39)

print('Light: {0}lux'.format(sensor.lux))
print('Visible: {0}'.format(sensor.visible))
print('Infrared: {0}'.format(sensor.infrared))

meatmanek@pi4:~ $ . venv/bin/activate
(venv) meatmanek@pi4:~ $ python ./tsl2591.py 
Traceback (most recent call last):
  File "/home/meatmanek/./tsl2591.py", line 2, in 
    import adafruit_tsl2591
ModuleNotFoundError: No module named 'adafruit_tsl2591'

(venv) meatmanek@pi4:~ $ pip3 install adafruit-circuitpython-tsl2591
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
...
Successfully installed adafruit-circuitpython-tsl2591-1.3.12
(venv) meatmanek@pi4:~ $ python ./tsl2591.py 
Traceback (most recent call last):
  File "/home/meatmanek/./tsl2591.py", line 4, in 
    sensor = adafruit_tsl2591.TSL2591(i2c, address=0x39)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/meatmanek/venv/lib/python3.11/site-packages/adafruit_tsl2591.py", line 132, in __init__
    raise RuntimeError("Failed to find TSL2591, check wiring!")
RuntimeError: Failed to find TSL2591, check wiring!

 Looking at line 132 in adafruit_tsl2591.py, it looks like it's also checking a register to see if the chip has the right device ID:

    def __init__(self, i2c: I2C, address: int = _TSL2591_ADDR) -> None:
        ...
        # Verify the chip ID.
        if self._read_u8(_TSL2591_REGISTER_DEVICE_ID) != 0x50:
            raise RuntimeError("Failed to find TSL2591, check wiring!")
        ... 

At this point I start looking through the Linux drivers for TSL* light sensor ICs:

meatmanek@brix1:~/raspberrypi-linux$ (ccache arm64) git grep -i ,tsl Documentation/
Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml:$id: http://devicetree.org/schemas/iio/light/amstaos,tsl2563.yaml#
Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml:      - amstaos,tsl2560
Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml:      - amstaos,tsl2561
Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml:      - amstaos,tsl2562
Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml:      - amstaos,tsl2563
Documentation/devicetree/bindings/iio/light/amstaos,tsl2563.yaml:        compatible = "amstaos,tsl2563";
Documentation/devicetree/bindings/iio/light/amstaos,tsl2591.yaml:$id: http://devicetree.org/schemas/iio/light/amstaos,tsl2591.yaml#
Documentation/devicetree/bindings/iio/light/amstaos,tsl2591.yaml:    const: amstaos,tsl2591
Documentation/devicetree/bindings/iio/light/amstaos,tsl2591.yaml:            compatible = "amstaos,tsl2591";
Documentation/devicetree/bindings/iio/light/tsl2583.yaml:      - amstaos,tsl2580
Documentation/devicetree/bindings/iio/light/tsl2583.yaml:      - amstaos,tsl2581
Documentation/devicetree/bindings/iio/light/tsl2583.yaml:      - amstaos,tsl2583
Documentation/devicetree/bindings/iio/light/tsl2583.yaml:                compatible = "amstaos,tsl2581";
Documentation/devicetree/bindings/iio/light/tsl2772.yaml:      - amstaos,tsl2571
Documentation/devicetree/bindings/iio/light/tsl2772.yaml:      - amstaos,tsl2671
Documentation/devicetree/bindings/iio/light/tsl2772.yaml:      - amstaos,tsl2771
Documentation/devicetree/bindings/iio/light/tsl2772.yaml:      - amstaos,tsl2572
Documentation/devicetree/bindings/iio/light/tsl2772.yaml:      - amstaos,tsl2672
Documentation/devicetree/bindings/iio/light/tsl2772.yaml:      - amstaos,tsl2772
Documentation/devicetree/bindings/iio/light/tsl2772.yaml:                compatible = "amstaos,tsl2772";
Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml:      cdns,tslch-ns:
Documentation/devicetree/bindings/trivial-devices.yaml:          - taos,tsl2550

Looks like there are 4 distinct drivers, some handling multiple chips. We've already eliminated the tsl2561 (handled by the tsl2563 driver) and the tsl2591 (handled by its own driver), let's try the tsl2571 (handled by the tsl2772 driver). I create this device tree overlay file:

#include <dt-bindings/interrupt-controller/irq.h>

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835";

    fragment@0 {
        target = <&i2c1>;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";

            als@39 {
                    compatible = "amstaos,tsl2571";
                    reg = <0x39>;
            };
        };
    };
};

This should get the tsl2772 driver loaded and configured to talk to a tsl2571 on i2c bus 1, address 0x39.

When loading this dtoverlay file, I get an error:

meatmanek@pi4:~ $ sudo dtoverlay pipad-als
meatmanek@pi4:~ $ dmesg | grep tsl
[   61.915038] tsl2772 1-0039: supply vdd not found, using dummy regulator
[   61.916654] tsl2772 1-0039: supply vddio not found, using dummy regulator
[   61.943592] tsl2772 1-0039: tsl2772_probe: i2c device found does not match expected id
[   61.943846] tsl2772: probe of 1-0039 failed with error -22

Looks like more of the same problems. I patched the code to print out the ID it found (0x93). Looking at the code in this driver that checks the chip ID, none of the chips supported by this driver would have ID 0x93 -- it only supports 0x0*, 0x2*, and 0x3*:

/* Use the default register values to identify the Taos device */
static int tsl2772_device_id_verif(int id, int target)
{
    switch (target) {
    case tsl2571:
    case tsl2671:
    case tsl2771:
        return (id & 0xf0) == TRITON_ID;
    case tmd2671:
    case tmd2771:
        return (id & 0xf0) == HALIBUT_ID;
    case tsl2572:
    case tsl2672:
    case tmd2672:
    case tsl2772:
    case tmd2772:
    case apds9930:
        return (id & 0xf0) == SWORDFISH_ID;
    }

    return -EINVAL;
}

Around this time I decided to see if I could get a better photo of the IC:

Quite a bit clearer. 

Back to the search -- if we've tried the 2561, 2571, and 2591, maybe it's the 2581? I google for tsl2581 and this image pops up: 

That looks extremely close (closer than the 2561/2571 image does), let's give it a try. Just a 1-character change to my device tree overlay file, and:

meatmanek@pi4:~ $ sudo dtoverlay pipad-als
meatmanek@pi4:~ $ dmesg | grep tsl
[  181.899420] tsl2583 1-0039: Light sensor found.

Well, that's encouraging!

Since this is an iio driver, the sysfs files live in a directory under /sys/bus/iio/devices:

meatmanek@pi4:~ $ cd /sys/bus/iio/devices/iio\:device0/
meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ ls
in_illuminance_both_raw   in_illuminance_calibscale            in_illuminance_input_target                in_illuminance_ir_raw     of_node    uevent
in_illuminance_calibbias  in_illuminance_calibscale_available  in_illuminance_integration_time            in_illuminance_lux_table  power
in_illuminance_calibrate  in_illuminance_input                 in_illuminance_integration_time_available  name                      subsystem
meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ cat in_illuminance_ir_raw 
0
meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ cat in_illuminance_both_raw 
0

Turns out that once you read in_illuminance_input, then in_illuminance_ir_raw and in_illuminance_both_raw will have nonzero values:

meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ cat in_illuminance_input
0
meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ cat in_illuminance_ir_raw 
130
meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ cat in_illuminance_both_raw 
1025

Success! Hopefully this is actually the right chip, and not just something that happens to have a similar register map. It does seem to respond to changes in brightness, and can even tell the difference between an incandescent light and LED lights:

# Under incandescent light:
meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ grep ^ in_illuminance_*_raw
in_illuminance_both_raw:677
in_illuminance_ir_raw:133

# Under white LED light, dimmed to give a similar "both" value:
meatmanek@pi4:/sys/bus/iio/devices/iio:device0 $ grep ^ in_illuminance_*_raw
in_illuminance_both_raw:687
in_illuminance_ir_raw:87

Now I just need to get this thing re-adhered to the back of the LCD without looking too ugly.