mmc: bcm2835-host: let firmware manage the clock divisor

Newer firmware can manage the SDCDIV clock divisor register, allowing
the divisor to scale with the core as necessary.

Leverage this ability if the firmware supports it.

Adapted from the following raspberrypi Linux kernel commit:

  bcm2835-sdhost: Firmware manages the clock divisor
  https://github.com/raspberrypi/linux/commit/08532d242d7702ae0add95096aa49c5e96e066e2

Signed-off-by: Vincent Fazio <vfazio@xes-inc.com>
Signed-off-by: Peter Robinson <pbrobinson@gmail.com>
diff --git a/arch/arm/mach-bcm283x/include/mach/mbox.h b/arch/arm/mach-bcm283x/include/mach/mbox.h
index 7dcac58..490664f 100644
--- a/arch/arm/mach-bcm283x/include/mach/mbox.h
+++ b/arch/arm/mach-bcm283x/include/mach/mbox.h
@@ -252,6 +252,22 @@
 	} body;
 };
 
+#define BCM2835_MBOX_TAG_SET_SDHOST_CLOCK	0x00038042
+
+struct bcm2835_mbox_tag_set_sdhost_clock {
+	struct bcm2835_mbox_tag_hdr tag_hdr;
+	union {
+		struct {
+			u32 rate_hz;
+		} req;
+		struct {
+			u32 rate_hz;
+			u32 rate_1;
+			u32 rate_2;
+		} resp;
+	} body;
+};
+
 #define BCM2835_MBOX_TAG_ALLOCATE_BUFFER	0x00040001
 
 struct bcm2835_mbox_tag_allocate_buffer {
diff --git a/arch/arm/mach-bcm283x/include/mach/msg.h b/arch/arm/mach-bcm283x/include/mach/msg.h
index eb3da93..e54da86 100644
--- a/arch/arm/mach-bcm283x/include/mach/msg.h
+++ b/arch/arm/mach-bcm283x/include/mach/msg.h
@@ -23,6 +23,16 @@
 int bcm2835_get_mmc_clock(u32 clock_id);
 
 /**
+ * bcm2835_set_sdhost_clock() - determine if firmware controls sdhost cdiv
+ *
+ * @rate_hz: Input clock frequency
+ * @rate_1: Returns a clock frequency
+ * @rate_2: Returns a clock frequency
+ * @return 0 of OK, -EIO on error
+ */
+int bcm2835_set_sdhost_clock(u32 rate_hz, u32 *rate_1, u32 *rate_2);
+
+/**
  * bcm2835_get_video_size() - get the current display size
  *
  * @widthp: Returns the width in pixels
diff --git a/arch/arm/mach-bcm283x/msg.c b/arch/arm/mach-bcm283x/msg.c
index 68b2773..2188b38 100644
--- a/arch/arm/mach-bcm283x/msg.c
+++ b/arch/arm/mach-bcm283x/msg.c
@@ -21,6 +21,12 @@
 	u32 end_tag;
 };
 
+struct msg_set_sdhost_clock {
+	struct bcm2835_mbox_hdr hdr;
+	struct bcm2835_mbox_tag_set_sdhost_clock set_sdhost_clock;
+	u32 end_tag;
+};
+
 struct msg_query {
 	struct bcm2835_mbox_hdr hdr;
 	struct bcm2835_mbox_tag_physical_w_h physical_w_h;
@@ -110,6 +116,28 @@
 	return clock_rate;
 }
 
+int bcm2835_set_sdhost_clock(u32 rate_hz, u32 *rate_1, u32 *rate_2)
+{
+	ALLOC_CACHE_ALIGN_BUFFER(struct msg_set_sdhost_clock, msg_sdhost_clk, 1);
+	int ret;
+
+	BCM2835_MBOX_INIT_HDR(msg_sdhost_clk);
+	BCM2835_MBOX_INIT_TAG(&msg_sdhost_clk->set_sdhost_clock, SET_SDHOST_CLOCK);
+
+	msg_sdhost_clk->set_sdhost_clock.body.req.rate_hz = rate_hz;
+
+	ret = bcm2835_mbox_call_prop(BCM2835_MBOX_PROP_CHAN, &msg_sdhost_clk->hdr);
+	if (ret) {
+		printf("bcm2835: Could not query sdhost clock rate\n");
+		return -EIO;
+	}
+
+	*rate_1 = msg_sdhost_clk->set_sdhost_clock.body.resp.rate_1;
+	*rate_2 = msg_sdhost_clk->set_sdhost_clock.body.resp.rate_2;
+
+	return 0;
+}
+
 int bcm2835_get_video_size(int *widthp, int *heightp)
 {
 	ALLOC_CACHE_ALIGN_BUFFER(struct msg_query, msg_query, 1);