glxiic – FreeBSD I2C driver for Geode LX

This is the project homepage for glxiic(4), a FreeBSD I2C bus driver for systems based on the AMD Geode LX chipset. The website hosts the driver and the corresponding documentation as well as a few patches for FreeBSD that were created when writing the driver.

Hardware

Some (most? all?) AMD Geode LX based designs use the AMD Geode CS5536 southbridge which integrates an I2C/SMBus master function. Alternatively, the same logic can be programmed to behave as an I2C/SMBus slave device. The glxiic(4)driver supports the I2C/SMBus master functionality in the CS5536 chip. The driver has been tested on an Alix 1.C board running FreeBSD 8.2 – other boards, similar chips and different versions of FreeBSD have not been tested. 

Download

The source code of the glxisab as well as the glxiic driver can be downloaded here:

Installation

Update Dec 20th, 2012: I’ve found that loading the glxisab(4) driver as a module doesn’t work on FreeBSD 9.1 anymore. Actually, I’m not sure if this ever worked. The solution is to compile the device into the kernel. The instructions below reflect that.

Here’s a step-by-step tutorial on how to compile the driver into the kernel:

  • Extract the archive and enter the glxiic directory.
  • Run make, followed by make install to compile and install the glxiic module. Note that you need to be root to install the module.
  • Create a symbolic link to the glxiic directory in /usr/src/sys/modules as well as in /usr/src/sys/dev. In order to avoid conflicts with the stock driver, name the link glx.
  • Add two lines such as below to /usr/src/sys/conf/files.i386:
dev/glx/glx.c optional glx 
dev/glx/glx_isab.c optional glx
  • Add device glx to a custom kernel config. Then build and install that kernel.
  • Add a line that reads hint.glxiic.0.at=”isa” and one that reads hint.glxiic.0.irq=”12″ to /boot/device.hints. Note that in this example, I used IRQ 12 which needs to be unused in order for this to work.
hint.glxiic.0.at="isa" 
hint.glxiic.0.irq="12"
  • After a reboot, you can load the glxiic module via kldload /path/to/glxiic.ko. I’ve found that loading the module on boot through /boot/loader.conf does not work.

Here is the same procedure in a more condensed version:

$ tar -xzvf glxiic.tgz 
$ ln -s ./glxiic /usr/src/sys/modules/glx 
$ ln -s ./glxiic /usr/src/sys/dev/glx 
$ echo 'dev/glx/glx.c optional glx' >> /usr/src/sys/conf/files.i386 
$ echo 'dev/glx/glx_isab.c optional glx' >> /usr/src/sys/conf/files.i386 
$ cp /usr/src/sys/i386/conf/GENERIC /usr/src/sys/i386/conf/ALIX1C 
$ echo 'glx' >> /usr/src/sys/i386/conf/ALIX1C 
$ ( cd /usr/src && sudo make buildkernel KERNCONF=ALIX1C ) 
$ ( cd /usr/src && sudo make installkernel KERNCONF=ALIX1C ) 
$ cd glxiic 
$ make && sudo make install 
$ echo 'glxiic_load="YES"' >> /boot/loader.conf 
$ echo 'hint.glxiic.0.at="isa"' >> /boot/device.hints 
$ echo 'hint.glxiic.0.irq="12"' >> /boot/device.hints 
$ sudo reboot 
$ sudo kldload glxiic

Please note that in order to use the driver, you also need iicbus(4) as well as iic(4). Their usage is documented in the appropriate manual pages.

Architecture

The AMD Geode CS5536 contains what the data sheet calls “diverse integration logic” (DIVIL). The DIVIL block contains a bunch of devices that respond to legacy I/O ports such as a Programmable Interrupt Controller (PIC), AT Keyboard Controller, Real-time Clock (RTC) or Low Pin Count (LPC) bus controller. Those devices, if enabled, respond to the well known I/O port addresses of those functions.

In addition to the legacy devices, the DIVIL also contains the I2C/SMBus controller and a few other functions that respond to I/O ports configured in a Base Address Register (BAR).

To system software running on the host CPU, i.e. the host firmware or operating system, the DIVIL is represented as a function of a PCI device, a PCI-ISA bridge. Each of the BARs in the bridge’s PCI configuration space configures the base address of one logic block in the DIVIL block.

On FreeBSD, the PCI bus code scans the configuration space of all PCI devices it finds and resumes responsibility for the resources announced in the device’s configuration space. In addition to that, there is a generic PCI-ISA bridge driver that attaches to all PCI devices that announce themselves as a PCI-ISA bridge.

Because only direct child devices of the PCI bus driver can allocate those resources that a PCI device announces in its configuration space, and because the DIVIL block of the CS5536 south bridge announces a PCI-ISA bridge with a set of set up BARs, the glxiic(4) device driver needs to be split in two parts.

Geode LX PCI-ISA Bridge Driver

The glxisab driver is a replacement of the generic PCI-ISA bridge driver. It attaches to the device and vendor ID of the DIVIL function in the CS5536 south bridge. The driver assumes responsibility of all resources announced in the BARs and makes sure that child drivers of the ISA bus can allocate those resources. To prevent the generic PCI-ISA bridge driver from attaching to the device, the glxisab driver must be compiled into the kernel. Loading the module through /boot/loader.conf does not work for unkown reasons.

Geode LX I2C/SMBus Driver

The glxiic(4) driver is an ISA device driver. As such, it relies on information provided by the user via the /boot/device.hintsfile. Contrary to most ISA device drivers, the glxiic(4) driver can probe for the device and find out its I/O space addresses, but still requires the user to assign an interrupt.

The glxiic(4) driver provides an interface to the generic iicbus(4) driver which in turn provides an interface to the generic iic(4) driver which in turn creates a device node in /dev. This device node can be used from userland, e.g. via the i2c(8)utility, to control I2C devices attached to the bus.

In versions prior to v1.2, the glxiic(4) driver failed to allocate the required interrupt from the ISA bus when loaded at boot time. The work around was to load the kernel module after the kernel has booted up, either manually or through an init script. This issue has been fixed in v1.2 and the regular mechanism, i.e. loading the module at boot time via /boot/loader.confworks. For completeness, here’s the line you need to add to /boot/loader.conf:glxiic_load=”YES”

Patches

When writing the device driver, a few patches to FreeBSD were created. All of them should apply to FreeBSD 8.2 but may also work for other versions of the FreeBSD source tree. All patches mentioned below can be applied by this command:

$ cd /usr/src 
$ patch -p1 < /path/to/patch/file

i2c Utility

The i2c(8) utility is a little program using the iic(4) interface to access I2C devices. In my opinion, it lacked three features:

  • Reads did not work for me.
  • It used 7-Bit addresses, but I find it more intuitive to use a device’s 8-Bit address, e.g. I’d like to specify an address of 0xA0, instead of 0x50.
  • There was no way of splitting reads/writes into multiple chunks. I thought this would be useful for S-EEPROMs which are often organized in pages.

These issues are addressed by the this patch.

Links

Here are a few links related to this project.