diff mbox

[RFC] ssb: Generic SPROM override for devices without SPROM

Message ID 200911201212.19588.mb@bu3sch.de (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Michael Buesch Nov. 20, 2009, 11:12 a.m. UTC
None
diff mbox

Patch

Index: wireless-testing/drivers/ssb/pci.c
===================================================================
--- wireless-testing.orig/drivers/ssb/pci.c	2009-11-19 18:34:42.000000000 +0100
+++ wireless-testing/drivers/ssb/pci.c	2009-11-19 18:37:27.000000000 +0100
@@ -252,6 +252,9 @@  static int sprom_do_read(struct ssb_bus 
 {
 	int i;
 
+	if (!bus->sprom_size)
+		return -ENODEV;
+
 	for (i = 0; i < bus->sprom_size; i++)
 		sprom[i] = ioread16(bus->mmio + SSB_SPROM_BASE + (i * 2));
 
@@ -265,6 +268,9 @@  static int sprom_do_write(struct ssb_bus
 	u32 spromctl;
 	u16 size = bus->sprom_size;
 
+	if (!size)
+		return -ENODEV;
+
 	ssb_printk(KERN_NOTICE PFX "Writing SPROM. Do NOT turn off the power! Please stand by...\n");
 	err = pci_read_config_dword(pdev, SSB_SPROMCTL, &spromctl);
 	if (err)
@@ -616,10 +622,17 @@  static int sprom_extract(struct ssb_bus 
 static int ssb_pci_sprom_get(struct ssb_bus *bus,
 			     struct ssb_sprom *sprom)
 {
-	const struct ssb_sprom *fallback;
-	int err = -ENOMEM;
+	int err;
 	u16 *buf;
 
+	bus->sprom_size = 0;
+	err = ssb_find_sprom_override(bus, sprom);
+	if (!err) {
+		ssb_printk(KERN_INFO PFX "Overriding SPROM image\n");
+		return 0;
+	}
+
+	err = -ENOMEM;
 	buf = kcalloc(SSB_SPROMSIZE_WORDS_R123, sizeof(u16), GFP_KERNEL);
 	if (!buf)
 		goto out;
@@ -637,22 +650,12 @@  static int ssb_pci_sprom_get(struct ssb_
 		sprom_do_read(bus, buf);
 		err = sprom_check_crc(buf, bus->sprom_size);
 		if (err) {
-			/* All CRC attempts failed.
-			 * Maybe there is no SPROM on the device?
-			 * If we have a fallback, use that. */
-			fallback = ssb_get_fallback_sprom();
-			if (fallback) {
-				memcpy(sprom, fallback, sizeof(*sprom));
-				err = 0;
-				goto out_free;
-			}
 			ssb_printk(KERN_WARNING PFX "WARNING: Invalid"
 				   " SPROM CRC (corrupt SPROM)\n");
 		}
 	}
 	err = sprom_extract(bus, sprom, buf, bus->sprom_size);
 
-out_free:
 	kfree(buf);
 out:
 	return err;
Index: wireless-testing/drivers/ssb/sprom.c
===================================================================
--- wireless-testing.orig/drivers/ssb/sprom.c	2009-11-19 18:34:42.000000000 +0100
+++ wireless-testing/drivers/ssb/sprom.c	2009-11-19 18:37:27.000000000 +0100
@@ -13,8 +13,13 @@ 
 
 #include "ssb_private.h"
 
+#include <linux/list.h>
+#include <linux/spinlock.h>
 
-static const struct ssb_sprom *fallback_sprom;
+
+/* List of registered SPROM overrides. */
+static LIST_HEAD(override_list);
+static DEFINE_SPINLOCK(override_list_lock);
 
 
 static int sprom2hex(const u16 *sprom, char *buf, size_t buf_len,
@@ -135,35 +140,34 @@  out:
 	return err ? err : count;
 }
 
-/**
- * ssb_arch_set_fallback_sprom - Set a fallback SPROM for use if no SPROM is found.
- *
- * @sprom: The SPROM data structure to register.
- *
- * With this function the architecture implementation may register a fallback
- * SPROM data structure. The fallback is only used for PCI based SSB devices,
- * where no valid SPROM can be found in the shadow registers.
- *
- * This function is useful for weird architectures that have a half-assed SSB device
- * hardwired to their PCI bus.
- *
- * Note that it does only work with PCI attached SSB devices. PCMCIA devices currently
- * don't use this fallback.
- * Architectures must provide the SPROM for native SSB devices anyway,
- * so the fallback also isn't used for native devices.
- *
- * This function is available for architecture code, only. So it is not exported.
- */
-int ssb_arch_set_fallback_sprom(const struct ssb_sprom *sprom)
-{
-	if (fallback_sprom)
-		return -EEXIST;
-	fallback_sprom = sprom;
+void ssb_register_sprom_override(struct ssb_sprom_override *ovr)
+{
+	spin_lock(&override_list_lock);
+	list_add_tail(&ovr->list, &override_list);
+	spin_unlock(&override_list_lock);
+}
+EXPORT_SYMBOL(ssb_register_sprom_override);
 
-	return 0;
+void ssb_unregister_sprom_override(struct ssb_sprom_override *ovr)
+{
+	spin_lock(&override_list_lock);
+	list_del(&ovr->list);
+	spin_unlock(&override_list_lock);
 }
+EXPORT_SYMBOL(ssb_unregister_sprom_override);
 
-const struct ssb_sprom *ssb_get_fallback_sprom(void)
+int ssb_find_sprom_override(struct ssb_bus *bus, struct ssb_sprom *buf)
 {
-	return fallback_sprom;
+	struct ssb_sprom_override *ovr;
+	int err = -ENODEV;
+
+	spin_lock(&override_list_lock);
+	list_for_each_entry(ovr, &override_list, list) {
+		err = ovr->probe(bus, buf);
+		if (!err)
+			break;
+	}
+	spin_unlock(&override_list_lock);
+
+	return err;
 }
Index: wireless-testing/drivers/ssb/ssb_private.h
===================================================================
--- wireless-testing.orig/drivers/ssb/ssb_private.h	2009-11-19 18:34:42.000000000 +0100
+++ wireless-testing/drivers/ssb/ssb_private.h	2009-11-19 18:37:27.000000000 +0100
@@ -171,7 +171,7 @@  ssize_t ssb_attr_sprom_store(struct ssb_
 			     const char *buf, size_t count,
 			     int (*sprom_check_crc)(const u16 *sprom, size_t size),
 			     int (*sprom_write)(struct ssb_bus *bus, const u16 *sprom));
-extern const struct ssb_sprom *ssb_get_fallback_sprom(void);
+extern int ssb_find_sprom_override(struct ssb_bus *bus, struct ssb_sprom *buf);
 
 
 /* core.c */
Index: wireless-testing/include/linux/ssb/ssb.h
===================================================================
--- wireless-testing.orig/include/linux/ssb/ssb.h	2009-11-19 18:34:42.000000000 +0100
+++ wireless-testing/include/linux/ssb/ssb.h	2009-11-19 18:37:27.000000000 +0100
@@ -394,9 +394,20 @@  extern int ssb_bus_sdiobus_register(stru
 
 extern void ssb_bus_unregister(struct ssb_bus *bus);
 
-/* Set a fallback SPROM.
- * See kdoc at the function definition for complete documentation. */
-extern int ssb_arch_set_fallback_sprom(const struct ssb_sprom *sprom);
+/** struct ssb_sprom_override - SPROM override handler
+ * @probe: Callback function used to probe for a SPROM override.
+ *	Puts the override image into "buf" and returns 0.
+ *	If there's no need to override the image, nonzero is returned.
+ *	This callback runs in atomic context.
+ * @list: Used internally in ssb. Do not use in the device driver.
+ */
+struct ssb_sprom_override {
+	int (*probe)(struct ssb_bus *bus, struct ssb_sprom *buf);
+	struct list_head list;
+};
+
+extern void ssb_register_sprom_override(struct ssb_sprom_override *ovr);
+extern void ssb_unregister_sprom_override(struct ssb_sprom_override *ovr);
 
 /* Suspend a SSB bus.
  * Call this from the parent bus suspend routine. */
Index: wireless-testing/drivers/ssb/b43_pci_bridge.c
===================================================================
--- wireless-testing.orig/drivers/ssb/b43_pci_bridge.c	2009-11-19 18:34:42.000000000 +0100
+++ wireless-testing/drivers/ssb/b43_pci_bridge.c	2009-11-20 12:04:09.000000000 +0100
@@ -5,13 +5,15 @@ 
  * because of its small size we include it in the SSB core
  * instead of creating a standalone module.
  *
- * Copyright 2007  Michael Buesch <mb@bu3sch.de>
+ * Copyright 2007-2009  Michael Buesch <mb@bu3sch.de>
  *
  * Licensed under the GNU/GPL. See COPYING for details.
  */
 
 #include <linux/pci.h>
 #include <linux/ssb/ssb.h>
+#include <linux/etherdevice.h>
+#include <linux/jhash.h>
 
 #include "ssb_private.h"
 
@@ -36,6 +38,76 @@  static const struct pci_device_id b43_pc
 };
 MODULE_DEVICE_TABLE(pci, b43_pci_bridge_tbl);
 
+
+static void pcidev_deduce_mac_address(struct pci_dev *pdev,
+				      struct ssb_sprom *sprom,
+				      const char *oui)
+{
+	u32 hash = 0x63E72B6D;
+
+	hash = jhash(&pdev->device, sizeof(pdev->device), hash);
+	hash = jhash(&pdev->subsystem_device, sizeof(pdev->subsystem_device), hash);
+	hash = jhash(&pdev->devfn, sizeof(pdev->devfn), hash);
+	//TODO: Need machine specific seed
+
+	sprom->il0mac[3] = hash;
+	sprom->il0mac[4] = hash >> 8;
+	sprom->il0mac[5] = hash >> 16;
+	memcpy(sprom->il0mac, oui, 3);
+	memcpy(sprom->et0mac, sprom->il0mac, ETH_ALEN);
+	memcpy(sprom->et1mac, sprom->il0mac, ETH_ALEN);
+}
+
+#define IS_PDEV(pdev, _vendor, _device, _subvendor, _subdevice)		( \
+	(pdev->vendor == PCI_VENDOR_ID_##_vendor) &&			\
+	(pdev->device == _device) &&					\
+	(pdev->subsystem_vendor == PCI_VENDOR_ID_##_subvendor) &&	\
+	(pdev->subsystem_device == _subdevice)				)
+
+static int b43_sprom_override_probe(struct ssb_bus *bus,
+				    struct ssb_sprom *sprom)
+{
+	struct pci_dev *pdev;
+
+	if (bus->bustype != SSB_BUSTYPE_PCI)
+		return -ENODEV;
+	pdev = bus->host_pci;
+
+	if (IS_PDEV(pdev, BROADCOM, 0x4315, FOXCONN, 0xE01B)) {
+		static const struct ssb_sprom image = {
+			.revision		= 0x02,
+			.board_rev		= 0x17,
+			.country_code		= 0x0,
+			.ant_available_bg 	= 0x3,
+			.pa0b0			= 0x15ae,
+			.pa0b1			= 0xfa85,
+			.pa0b2			= 0xfe8d,
+			.pa1b0			= 0xffff,
+			.pa1b1			= 0xffff,
+			.pa1b2			= 0xffff,
+			.gpio0			= 0xff,
+			.gpio1			= 0xff,
+			.gpio2			= 0xff,
+			.gpio3			= 0xff,
+			.maxpwr_bg		= 0x004c,
+			.itssi_bg		= 0x00,
+			.boardflags_lo		= 0x2848,
+			.boardflags_hi		= 0x0000,
+		};//FIXME This image is not the right one.
+
+		memcpy(sprom, &image, sizeof(*sprom));
+		pcidev_deduce_mac_address(pdev, sprom, "\x00\x15\x58");
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+static struct ssb_sprom_override b43_sprom_override = {
+	.probe		= b43_sprom_override_probe,
+};
+
 static struct pci_driver b43_pci_bridge_driver = {
 	.name = "b43-pci-bridge",
 	.id_table = b43_pci_bridge_tbl,
@@ -44,10 +116,20 @@  static struct pci_driver b43_pci_bridge_
 
 int __init b43_pci_ssb_bridge_init(void)
 {
-	return ssb_pcihost_register(&b43_pci_bridge_driver);
+	int err;
+
+	ssb_register_sprom_override(&b43_sprom_override);
+	err = ssb_pcihost_register(&b43_pci_bridge_driver);
+	if (err) {
+		ssb_unregister_sprom_override(&b43_sprom_override);
+		return err;
+	}
+
+	return 0;
 }
 
 void __exit b43_pci_ssb_bridge_exit(void)
 {
 	ssb_pcihost_unregister(&b43_pci_bridge_driver);
+	ssb_unregister_sprom_override(&b43_sprom_override);
 }