For a few months now, I've been working on improving the auto configuration of
I2C and SPI devices. These busses, of course, are not self-enumerating, and
besides indirect configuration of devices listed in a kernel config file,
enumerating based on information in platform config data (e.g. Devicetree,
OpenFirmware, ACPI) was supported by having controller drivers explicitly call
OpenFirmrware, FDT, or ACPI-specific code to populate a dictionary. Yuck.
My new mechanism instead uses the platform config data to enumerate the devices
directly, without having to get a dictionary involved at all. It also provides
a tidier mechanism to pass device handles to each driver via the normal
mechanism, rather than using the i2c_attch_args::ia_cookie /
i2c_attch_args::ia_cookietype hack.
The basic idea involves the device call mechanism I introduced a while ago.
The various back-ends (OpenFirmware, ACPI, etc.) that have I2C and SPI bus
bindings define "i2c-enumerate-devices" and "spi-enumerate-devices" device
calls. Those calls in turn invoke a callback to the I2C or SPI code to
actually attach the device specified in the platform config data. There is no
duplication of information in a dictionary, and the device handles for the
child devices naturally flow from the enumeration.
Systems that lack a platform device tree (e.g. sandpoint) or that have missing
device tree nodes on some models (e.g. sparc64, macppc) have the option of
using static tables, and there are helper functions in the core I2C code to
facilitate this. Such platforms merely need to subclass the controller's
devhandle and override that handle's "i2c-enumerate-devices" call. This also
covers the case where a platform that uses OpenFirmware has a controller whose
child nodes, for whatever weird reasons, don't conform to the standard bindings
(such is the case with the "ki2c" controller on macppc).
While the changes are very straightforward, the diffs are large, so I've broken
them up into chunks that are easier to digest and describe. Sometimes the
diffs might not make a lot of sense because of a larger restructuring, so in
those cases, I encourage you to check out the thorpej-i2c-spi-conf2 branch and
simply read the file directly.
I'll start with the core I2C changes:
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/i2c-core-diffs.txt
The main changes here iic_attach(). The loop enumerating a prop_array_t of
child device descriptions is gone, and has been replaced by the following small
code block:
struct i2c_enumerate_devices_args enumargs = {
.ia = &ia,
.callback = iic_enumerate_devices_callback,
};
rv = device_call(self, "i2c-enumerate-devices", &enumargs);
The iic_enumerate_devices_callback() is quite small -- it essentially just
calls config_found() and performs some housekeeping.
You'll also see some other helpers there, like i2c_enumerate_deventries(),
which helps to support the "static tables of devices" scenario I mentioned
above.
In addition to the enumeration change, I've changed the code to not use a flat
array of device_t pointers indexed by i2c address. That array was consuming 4K
or 8K of memory (depending on pointer size) for what is typically very sparsely
populated -- what a waste! It now uses a linked list of device entries, sorted
by i2c address, and the manipulation of that list is MP-safe.
Also included in these diffs is a helper function in kern/subr_device.c that
does the work of subclassing a device handle in the common case (used by macppc
and sparc64; there is one additional case in macppc that's more complicated, so
that location is open-coded -- more on that later).
Next, let's take a look at the changes to the platform config data back-ends:
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/i2c-acpi-diffs.txt
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/i2c-ofw-diffs.txt
Now, rather than allocating a populating a dictionary with platform config
data, that data is used to directly enumerate and attach child devices.
In the OpenFirmware case, the old code that did this has been tossed
completely, and the differences between OpenFirmware variants (including the
standard Devicetree bindings) is handled in a much tidier way. Also, because
there is no more of_enter_i2c_devs() function, there is no more need for a
fdtbus_attach_i2cbus() (FDT-based attachments just attach the "iicbus" instance
like any other attachment would); note there is still a need for
fdtbus_register_i2c_controller(), but that is not anything that's I2C or SPI
specific in the FDT universe, and I'm not intending to address that issue now.
All of these changes made it necessary to adjust some (but certainly not all)
I2C device drivers, mainly things that needed to make use of
i2c_attch_args::ia_cookie / i2c_attch_args::ia_cookietype. Those diffs are
here:
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/i2c-mux-diffs.txt
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/i2c-device-diffs.txt
The MUX changes are a little larger, and spread across multiple files, so I
carved them out separately.
Now let's take a look at the simple controller diffs:
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/i2c-controller-diffs.txt
As you can see, the main changes here are to eliminate FDT- and ACPI-specific
code, making the two cases identical. I did not do the more invasive
re-factoring of these to fully de-duplicate the code that's now the same, but
that is a fairly straight-forward exercise. Additionally, a bunch of code that
existed to populate the child device dictionary has been garbage-collected from
a couple of machine-dependent I2C controller drivers (those instances can use
the standard OpenFirmware bindings).
Controllers that have built-in MUXes (the kind that the Devicetree I2C bindings
call an "i2c-mux-reg"), including "ki2c" and "smu" on macppc and "pfciic" on
sparc64, now independently attach the two channels. I may at some point in the
future revisit how these controllers work. Unfortunately, for the uses in
NetBSD, the device trees on these platforms fall squarely into the "legacy"
category and they don't really represent the topology in standardized ways
(sparc64, for example, encodes the MUX bus number in the upper 32-bits of a
64-bit address cell, even on i2c controllers that don't have a MUX, and the
macppc case is of course weirder than that). In any case, general support for
"i2c-mux-reg" MUXes is not included here, but the core I2C code can now at
least express the MUX bus number in these cases along with the more
standardized I2C MUX cases.
Another change I made was to remove the needless indirection of "smuiic" on
macppc; the "iicbus" instances now attach directly to "smu".
And now for the more complicated cases ... the ones that need to override the
standard "i2c-enumerate-devices" call.
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/i2c-override-diffs.txt
There are 4 instances of this: 2 controllers on macppc ("cuda" and "ki2c"), the
sandpoint platform, and sparc64 (specific machine models, not specific to any
specific controller type).
First, "cuda". This one is pretty basic. There are no child nodes in the
device tree. We populate a table and call i2c_enumerate_deventries() from the
"i2c-enumerate-devices" call.
Second, "ki2c". This one is a bit wackier. In this case, all of the child
nodes are there, but the device tree doesn't match the OpenFirmware bindings
like *at all*. Complicating this one is the fact that there are two schemes
found on "ki2c", depending on the OpenFirmware version. The comment I added
explains it best:
+ /*
+ * Different systems have different I2C device tree topologies.
+ *
+ * Some systems use a scheme like this:
+ *
+ * /u3@0,f8000000/i2c@f8001000/temp-monitor@98
+ * /u3@0,f8000000/i2c@f8001000/fan@15e
+ *
+ * Here, we see the channel encoded in bit #8 of the address.
+ *
+ * Other systems use a scheme like this:
+ *
+ * /ht@0,f2000000/pci@4000,0,0/mac-io@7/i2c@18000/i2c-bus@0
+ * /ht@0,f2000000/pci@4000,0,0/mac-io@7/i2c@18000/i2c-bus@0/codec@8c
+ *
+ * /u4@0,f8000000/i2c@f8001000/i2c-bus@1
+ * /u4@0,f8000000/i2c@f8001000/i2c-bus@1/temp-monitor@94
+ *
+ * Here, a separate device tree node represents the channel.
+ * Note that in BOTH cases, the I2C address of the devices are
+ * shifted left by 1 (as it would be on the wire to leave room
+ * for the read/write bit).
+ *
+ * So, what we're going to do here is look for i2c-bus nodes. If
+ * we find them, we remember those phandles, and will use them for
+ * device enumeration. If we don't, then we will use the controller
+ * phandle for device enumeration and filter based on the channel
+ * bit in the "reg" property.
+ */
Note the diff for "ki2c" is misleadingly large, because I cleaned up the code a
little while I was there, plus there's the MUX changes I mentioned earlier.
Third is the sandpoint platform. We need to select the right static list of
devices depending on the specific NAS model we're running on. This is an
example use of the DEVHANDLE_TYPE_PRIVATE devhandles; the sandpoint platform
does not use FDT or any other type of platform config data scheme, and so we're
not subclassing here, but rather creating it from whole cloth. Anyway, once
the table is selected, i2c_enumerate_deventries() actually performs the
enumeration.
The final case is sparc64. This is a case where the OpenFirmware device tree
is simply missing a bunch of child nodes. This is also tangled up with some
other "add properties for GPIO pins" stuff that jdc@ added to the port a while
ago. I kept his GPIO property fixup stuff, but completely replaced the
previous I2C fixup code he had with the new scheme. And rather than being
handled inline in device_register(), we call out to a table-driven mechanism
for matching the platforms and the specific OpenFirmware device tree nodes that
need to be fixed up. We don't rely on fixing up any specific controller driver
the way we do on macppc. Also note that not ALL of the device tree nodes are
missing! For example, on the V210, the I2C child nodes for the GPIO
controllers are present, but the nodes for the environmental sensors are not.
So, the in this case the subclass's "i2c-enumerate-devices" call first calls
the superclass's "i2c-enumerate-devices" (to enumerate the nodes present in
OpenFirmware) and then calls i2c_enumerate_deventries() to enumerate the
model-specific table of additions.
OK! That's I2C! Now onto SPI.
The general gist of the SPI changes is basically the same, and it suffered from
the same basic ugly bits as the I2C code did. Here are the core SPI changes:
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/spi-core-diffs.txt
Because we don't support wildcarding of the chip select locator on SPI, direct
and indirect config using this scheme are nearly identical... in the direct
config case, we're enumerating the platform config data (only Devicetree
bindings for now) using the "spi-enumerate-devices" device call or enumerating
kernel configuration directives using config_search(). In any case, I took the
opportunity to tidy up some of the bookkeeping on how child devices are tracked.
I only provided an OpenFirmware / Devicetree "spi-enumerate-devices"
implementation. As with I2C, there is no longer a need for
fdtbus_attach_spibus(). I also tweaked fdtbus_register_spi_controller() to
remove a pointless layer of indirection, as was done for I2C a while ago.
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/spi-ofw-diffs.txt
The controller diffs are pretty straightforward. I standardized the style of
the config_found() call sites and made sure to get the controller's devhandle
passed down to the "spi" bus instance.
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/spi-controller-diffs.txt
Now, the device diffs:
https://www.netbsd.org/~thorpej/thorpej-i2c-spi-conf2/spi-device-diffs.txt
There's kind of a lot going on here, but before I dive into each one, there are
a couple of changes I made to basically all of these drivers:
- Standardize the form of the "match" function.
- Ensure "match" does not have side-effects (e.g. setting the mode/speed of the
that "slot").
- Fleshed out the compat_data[], and ensured that all of them support direct
configuration using compat data.
And now some specific additional items:
mcp23s17.c -- Added direct configuration support, which required adding
additional code to parse the device tree properties so as to be able to de-mux
the chip select (TL;DR - go read the chip's data sheet and the Devicetree
bindings for this device).
mcp3k.c -- Added direct configuration support, including selection of
parameters based on the "compatible" property from the device tree.
mcp48x1.c -- Same.
ssdfb_spi.c -- Tidied up the FDT integration a bit.
tmp121.c -- Added place-holders for other versions of the chip that we should
support eventually.
Anyway, if you've read this far, congratulations! Now, I need your help. I
don't have all of these machines. I've done a fair bit of testing on evbarm
(FDT) and macppc, but not every model. I need help specifically with:
-- evbarm (ACPI), specifically a system with an I2C MUX (I think jmcneill@ has
such a beast).
-- macppc (variety of models) -- macallan@?
-- sandpoint (any will do; I bought a Synology, but the Ethernet interface
failed after a couple of days, even PPCBOOT complains, boo). rin@?
-- sparc64 -- I don't think Qemu can emulate any of the I2C controllers that
system has. jdc@?
-- SPI devices -- tnn@?
I have some additional changes teed up for "ihidev", but I first need to finish
writing some Arduino code to simulate a I2C HID mouse so I can test it, so I'm
not including any of that bigger change here.
Anyway, comments please!
-- thorpej