+/*
+ * Handling of "partitions"
+ */
+
+static int smu_read_datablock(u8 *dest, unsigned int addr, unsigned int len)
+{
+ DECLARE_COMPLETION(comp);
+ unsigned int chunk;
+ struct smu_cmd cmd;
+ int rc;
+ u8 params[8];
+
+ /* We currently use a chunk size of 0xe. We could check the
+ * SMU firmware version and use bigger sizes though
+ */
+ chunk = 0xe;
+
+ while (len) {
+ unsigned int clen = min(len, chunk);
+
+ cmd.cmd = SMU_CMD_MISC_ee_COMMAND;
+ cmd.data_len = 7;
+ cmd.data_buf = params;
+ cmd.reply_len = chunk;
+ cmd.reply_buf = dest;
+ cmd.done = smu_done_complete;
+ cmd.misc = ∁
+ params[0] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC;
+ params[1] = 0x4;
+ *((u32 *)¶ms[2]) = addr;
+ params[6] = clen;
+
+ rc = smu_queue_cmd(&cmd);
+ if (rc)
+ return rc;
+ wait_for_completion(&comp);
+ if (cmd.status != 0)
+ return rc;
+ if (cmd.reply_len != clen) {
+ printk(KERN_DEBUG "SMU: short read in "
+ "smu_read_datablock, got: %d, want: %d\n",
+ cmd.reply_len, clen);
+ return -EIO;
+ }
+ len -= clen;
+ addr += clen;
+ dest += clen;
+ }
+ return 0;
+}
+
+static struct smu_sdbp_header *smu_create_sdb_partition(int id)
+{
+ DECLARE_COMPLETION(comp);
+ struct smu_simple_cmd cmd;
+ unsigned int addr, len, tlen;
+ struct smu_sdbp_header *hdr;
+ struct property *prop;
+
+ /* First query the partition info */
+ DPRINTK("SMU: Query partition infos ... (irq=%d)\n", smu->db_irq);
+ smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2,
+ smu_done_complete, &comp,
+ SMU_CMD_PARTITION_LATEST, id);
+ wait_for_completion(&comp);
+ DPRINTK("SMU: done, status: %d, reply_len: %d\n",
+ cmd.cmd.status, cmd.cmd.reply_len);
+
+ /* Partition doesn't exist (or other error) */
+ if (cmd.cmd.status != 0 || cmd.cmd.reply_len != 6)
+ return NULL;
+
+ /* Fetch address and length from reply */
+ addr = *((u16 *)cmd.buffer);
+ len = cmd.buffer[3] << 2;
+ /* Calucluate total length to allocate, including the 17 bytes
+ * for "sdb-partition-XX" that we append at the end of the buffer
+ */
+ tlen = sizeof(struct property) + len + 18;
+
+ prop = kcalloc(tlen, 1, GFP_KERNEL);
+ if (prop == NULL)
+ return NULL;
+ hdr = (struct smu_sdbp_header *)(prop + 1);
+ prop->name = ((char *)prop) + tlen - 18;
+ sprintf(prop->name, "sdb-partition-%02x", id);
+ prop->length = len;
+ prop->value = (unsigned char *)hdr;
+ prop->next = NULL;
+
+ /* Read the datablock */
+ if (smu_read_datablock((u8 *)hdr, addr, len)) {
+ printk(KERN_DEBUG "SMU: datablock read failed while reading "
+ "partition %02x !\n", id);
+ goto failure;
+ }
+
+ /* Got it, check a few things and create the property */
+ if (hdr->id != id) {
+ printk(KERN_DEBUG "SMU: Reading partition %02x and got "
+ "%02x !\n", id, hdr->id);
+ goto failure;
+ }
+ if (prom_add_property(smu->of_node, prop)) {
+ printk(KERN_DEBUG "SMU: Failed creating sdb-partition-%02x "
+ "property !\n", id);
+ goto failure;
+ }
+
+ return hdr;
+ failure:
+ kfree(prop);
+ return NULL;
+}
+
+/* Note: Only allowed to return error code in pointers (using ERR_PTR)
+ * when interruptible is 1
+ */
+struct smu_sdbp_header *__smu_get_sdb_partition(int id, unsigned int *size,
+ int interruptible)
+{
+ char pname[32];
+ struct smu_sdbp_header *part;
+
+ if (!smu)
+ return NULL;
+
+ sprintf(pname, "sdb-partition-%02x", id);
+
+ DPRINTK("smu_get_sdb_partition(%02x)\n", id);
+
+ if (interruptible) {
+ int rc;
+ rc = down_interruptible(&smu_part_access);
+ if (rc)
+ return ERR_PTR(rc);
+ } else
+ down(&smu_part_access);
+
+ part = (struct smu_sdbp_header *)get_property(smu->of_node,
+ pname, size);
+ if (part == NULL) {
+ DPRINTK("trying to extract from SMU ...\n");
+ part = smu_create_sdb_partition(id);
+ if (part != NULL && size)
+ *size = part->len << 2;
+ }
+ up(&smu_part_access);
+ return part;
+}
+
+struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size)
+{
+ return __smu_get_sdb_partition(id, size, 0);
+}
+EXPORT_SYMBOL(smu_get_sdb_partition);