firewire: add CSR PRIORITY_BUDGET support

If supported by the OHCI controller, implement the PRIORITY_BUDGET
register, which is required for nodes that can use asynchronous
priority arbitration.

To allow the core to determine what features the lowlevel device
supports, add a new card driver callback.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
diff --git a/drivers/firewire/core-transaction.c b/drivers/firewire/core-transaction.c
index 8146133..a61eb3f 100644
--- a/drivers/firewire/core-transaction.c
+++ b/drivers/firewire/core-transaction.c
@@ -1126,6 +1126,20 @@
 			rcode = RCODE_TYPE_ERROR;
 		break;
 
+	case CSR_PRIORITY_BUDGET:
+		if (!(card->driver->get_features(card) &
+						FEATURE_PRIORITY_BUDGET))
+			rcode = RCODE_ADDRESS_ERROR;
+		else if (tcode == TCODE_READ_QUADLET_REQUEST)
+			*data = cpu_to_be32(card->driver->
+				read_csr_reg(card, CSR_PRIORITY_BUDGET));
+		else if (tcode == TCODE_WRITE_QUADLET_REQUEST)
+			card->driver->write_csr_reg(card, CSR_PRIORITY_BUDGET,
+						    be32_to_cpu(*data));
+		else
+			rcode = RCODE_TYPE_ERROR;
+		break;
+
 	case CSR_BROADCAST_CHANNEL:
 		if (tcode == TCODE_READ_QUADLET_REQUEST)
 			*data = cpu_to_be32(card->broadcast_channel);
diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h
index efcdeb2..3b8c0f0 100644
--- a/drivers/firewire/core.h
+++ b/drivers/firewire/core.h
@@ -38,6 +38,8 @@
 #define BROADCAST_CHANNEL_INITIAL	(1 << 31 | 31)
 #define BROADCAST_CHANNEL_VALID		(1 << 30)
 
+#define FEATURE_PRIORITY_BUDGET		0x01
+
 struct fw_card_driver {
 	/*
 	 * Enable the given card with the given initial config rom.
@@ -78,6 +80,8 @@
 	u32 (*read_csr_reg)(struct fw_card *card, int csr_offset);
 	void (*write_csr_reg)(struct fw_card *card, int csr_offset, u32 value);
 
+	unsigned int (*get_features)(struct fw_card *card);
+
 	struct fw_iso_context *
 	(*allocate_iso_context)(struct fw_card *card,
 				int type, int channel, size_t header_size);
diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c
index 9c588fd..0e54135 100644
--- a/drivers/firewire/ohci.c
+++ b/drivers/firewire/ohci.c
@@ -170,6 +170,7 @@
 	int generation;
 	int request_generation;	/* for timestamping incoming requests */
 	unsigned quirks;
+	unsigned int pri_req_max;
 	u32 bus_time;
 
 	/*
@@ -1738,6 +1739,11 @@
 	reg_write(ohci, OHCI1394_IsochronousCycleTimer, seconds << 25);
 	ohci->bus_time = seconds & ~0x3f;
 
+	/* Get implemented bits of the priority arbitration request counter. */
+	reg_write(ohci, OHCI1394_FairnessControl, 0x3f);
+	ohci->pri_req_max = reg_read(ohci, OHCI1394_FairnessControl) & 0x3f;
+	reg_write(ohci, OHCI1394_FairnessControl, 0);
+
 	ar_context_run(&ohci->ar_request_ctx);
 	ar_context_run(&ohci->ar_response_ctx);
 
@@ -2028,6 +2034,10 @@
 		value = reg_read(ohci, OHCI1394_ATRetries);
 		return (value >> 4) & 0x0ffff00f;
 
+	case CSR_PRIORITY_BUDGET:
+		return (reg_read(ohci, OHCI1394_FairnessControl) & 0x3f) |
+			(ohci->pri_req_max << 8);
+
 	default:
 		WARN_ON(1);
 		return 0;
@@ -2065,12 +2075,28 @@
 		flush_writes(ohci);
 		break;
 
+	case CSR_PRIORITY_BUDGET:
+		reg_write(ohci, OHCI1394_FairnessControl, value & 0x3f);
+		flush_writes(ohci);
+		break;
+
 	default:
 		WARN_ON(1);
 		break;
 	}
 }
 
+static unsigned int ohci_get_features(struct fw_card *card)
+{
+	struct fw_ohci *ohci = fw_ohci(card);
+	unsigned int features = 0;
+
+	if (ohci->pri_req_max != 0)
+		features |= FEATURE_PRIORITY_BUDGET;
+
+	return features;
+}
+
 static void copy_iso_headers(struct iso_context *ctx, void *p)
 {
 	int i = ctx->header_length;
@@ -2510,6 +2536,7 @@
 	.enable_phys_dma	= ohci_enable_phys_dma,
 	.read_csr_reg		= ohci_read_csr_reg,
 	.write_csr_reg		= ohci_write_csr_reg,
+	.get_features		= ohci_get_features,
 
 	.allocate_iso_context	= ohci_allocate_iso_context,
 	.free_iso_context	= ohci_free_iso_context,