i2c/powermac: Improve detection of devices from device-tree
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>
Mon, 18 Jun 2012 02:00:50 +0000 (12:00 +1000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Wed, 11 Jul 2012 08:22:46 +0000 (18:22 +1000)
This patch adds a number of workarounds for broken Apple device-trees
mostly around sound chips. It handles creating the missing audio codec
devices and works around various issues with missing addresses or
missing compatible properties.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
drivers/i2c/busses/i2c-powermac.c

index 31c47e18d83c5f9fdf3bdb8927c57bc3ef12cd84..5285f8565de4d14c1e8265fc12c5ee18b0c670ae 100644 (file)
@@ -227,28 +227,138 @@ static int __devexit i2c_powermac_remove(struct platform_device *dev)
        return 0;
 }
 
+static u32 __devinit i2c_powermac_get_addr(struct i2c_adapter *adap,
+                                          struct pmac_i2c_bus *bus,
+                                          struct device_node *node)
+{
+       const __be32 *prop;
+       int len;
+
+       /* First check for valid "reg" */
+       prop = of_get_property(node, "reg", &len);
+       if (prop && (len >= sizeof(int)))
+               return (be32_to_cpup(prop) & 0xff) >> 1;
+
+       /* Then check old-style "i2c-address" */
+       prop = of_get_property(node, "i2c-address", &len);
+       if (prop && (len >= sizeof(int)))
+               return (be32_to_cpup(prop) & 0xff) >> 1;
+
+       /* Now handle some devices with missing "reg" properties */
+       if (!strcmp(node->name, "cereal"))
+               return 0x60;
+       else if (!strcmp(node->name, "deq"))
+               return 0x34;
+
+       dev_warn(&adap->dev, "No i2c address for %s\n", node->full_name);
+
+       return 0xffffffff;
+}
+
+static void __devinit i2c_powermac_create_one(struct i2c_adapter *adap,
+                                             const char *type,
+                                             u32 addr)
+{
+       struct i2c_board_info info = {};
+       struct i2c_client *newdev;
+
+       strncpy(info.type, type, sizeof(info.type));
+       info.addr = addr;
+       newdev = i2c_new_device(adap, &info);
+       if (!newdev)
+               dev_err(&adap->dev,
+                       "i2c-powermac: Failure to register missing %s\n",
+                       type);
+}
+
+static void __devinit i2c_powermac_add_missing(struct i2c_adapter *adap,
+                                              struct pmac_i2c_bus *bus,
+                                              bool found_onyx)
+{
+       struct device_node *busnode = pmac_i2c_get_bus_node(bus);
+       int rc;
+
+       /* Check for the onyx audio codec */
+#define ONYX_REG_CONTROL               67
+       if (of_device_is_compatible(busnode, "k2-i2c") && !found_onyx) {
+               union i2c_smbus_data data;
+
+               rc = i2c_smbus_xfer(adap, 0x46, 0, I2C_SMBUS_READ,
+                                   ONYX_REG_CONTROL, I2C_SMBUS_BYTE_DATA,
+                                   &data);
+               if (rc >= 0)
+                       i2c_powermac_create_one(adap, "MAC,pcm3052", 0x46);
+
+               rc = i2c_smbus_xfer(adap, 0x47, 0, I2C_SMBUS_READ,
+                                   ONYX_REG_CONTROL, I2C_SMBUS_BYTE_DATA,
+                                   &data);
+               if (rc >= 0)
+                       i2c_powermac_create_one(adap, "MAC,pcm3052", 0x47);
+       }
+}
+
+static bool __devinit i2c_powermac_get_type(struct i2c_adapter *adap,
+                                           struct device_node *node,
+                                           u32 addr, char *type, int type_size)
+{
+       char tmp[16];
+
+       /* Note: we to _NOT_ want the standard
+        * i2c drivers to match with any of our powermac stuff
+        * unless they have been specifically modified to handle
+        * it on a case by case basis. For example, for thermal
+        * control, things like lm75 etc... shall match with their
+        * corresponding windfarm drivers, _NOT_ the generic ones,
+        * so we force a prefix of AAPL, onto the modalias to
+        * make that happen
+        */
+
+       /* First try proper modalias */
+       if (of_modalias_node(node, tmp, sizeof(tmp)) >= 0) {
+               snprintf(type, type_size, "MAC,%s", tmp);
+               return true;
+       }
+
+       /* Now look for known workarounds */
+       if (!strcmp(node->name, "deq")) {
+               /* Apple uses address 0x34 for TAS3001 and 0x35 for TAS3004 */
+               if (addr == 0x34) {
+                       snprintf(type, type_size, "MAC,tas3001");
+                       return true;
+               } else if (addr == 0x35) {
+                       snprintf(type, type_size, "MAC,tas3004");
+                       return true;
+               }
+       }
+
+       dev_err(&adap->dev, "i2c-powermac: modalias failure"
+               " on %s\n", node->full_name);
+       return false;
+}
+
 static void __devinit i2c_powermac_register_devices(struct i2c_adapter *adap,
                                                    struct pmac_i2c_bus *bus)
 {
        struct i2c_client *newdev;
        struct device_node *node;
+       bool found_onyx = 0;
+
+       /*
+        * In some cases we end up with the via-pmu node itself, in this
+        * case we skip this function completely as the device-tree will
+        * not contain anything useful.
+        */
+       if (!strcmp(adap->dev.of_node->name, "via-pmu"))
+               return;
 
        for_each_child_of_node(adap->dev.of_node, node) {
                struct i2c_board_info info = {};
-               struct dev_archdata dev_ad = {};
-               const __be32 *reg;
-               char tmp[16];
                u32 addr;
-               int len;
 
                /* Get address & channel */
-               reg = of_get_property(node, "reg", &len);
-               if (!reg || (len < sizeof(int))) {
-                       dev_err(&adap->dev, "i2c-powermac: invalid reg on %s\n",
-                               node->full_name);
+               addr = i2c_powermac_get_addr(adap, bus, node);
+               if (addr == 0xffffffff)
                        continue;
-               }
-               addr = be32_to_cpup(reg);
 
                /* Multibus setup, check channel */
                if (!pmac_i2c_match_adapter(node, adap))
@@ -257,27 +367,23 @@ static void __devinit i2c_powermac_register_devices(struct i2c_adapter *adap,
                dev_dbg(&adap->dev, "i2c-powermac: register %s\n",
                        node->full_name);
 
-               /* Make up a modalias. Note: we to _NOT_ want the standard
-                * i2c drivers to match with any of our powermac stuff
-                * unless they have been specifically modified to handle
-                * it on a case by case basis. For example, for thermal
-                * control, things like lm75 etc... shall match with their
-                * corresponding windfarm drivers, _NOT_ the generic ones,
-                * so we force a prefix of AAPL, onto the modalias to
-                * make that happen
+               /*
+                * Keep track of some device existence to handle
+                * workarounds later.
                 */
-               if (of_modalias_node(node, tmp, sizeof(tmp)) < 0) {
-                       dev_err(&adap->dev, "i2c-powermac: modalias failure"
-                               " on %s\n", node->full_name);
+               if (of_device_is_compatible(node, "pcm3052"))
+                       found_onyx = true;
+
+               /* Make up a modalias */
+               if (!i2c_powermac_get_type(adap, node, addr,
+                                          info.type, sizeof(info.type))) {
                        continue;
                }
-               snprintf(info.type, sizeof(info.type), "MAC,%s", tmp);
 
                /* Fill out the rest of the info structure */
-               info.addr = (addr & 0xff) >> 1;
+               info.addr = addr;
                info.irq = irq_of_parse_and_map(node, 0);
                info.of_node = of_node_get(node);
-               info.archdata = &dev_ad;
 
                newdev = i2c_new_device(adap, &info);
                if (!newdev) {
@@ -292,6 +398,9 @@ static void __devinit i2c_powermac_register_devices(struct i2c_adapter *adap,
                        continue;
                }
        }
+
+       /* Additional workarounds */
+       i2c_powermac_add_missing(adap, bus, found_onyx);
 }
 
 static int __devinit i2c_powermac_probe(struct platform_device *dev)