/*
  * AMD Cryptographic Coprocessor (CCP) AES crypto API support
  *
- * Copyright (C) 2013 Advanced Micro Devices, Inc.
+ * Copyright (C) 2013,2016 Advanced Micro Devices, Inc.
  *
  * Author: Tom Lendacky <thomas.lendacky@amd.com>
  *
 
 struct ccp_aes_def {
        enum ccp_aes_mode mode;
+       unsigned int version;
        const char *name;
        const char *driver_name;
        unsigned int blocksize;
 static struct ccp_aes_def aes_algs[] = {
        {
                .mode           = CCP_AES_MODE_ECB,
+               .version        = CCP_VERSION(3, 0),
                .name           = "ecb(aes)",
                .driver_name    = "ecb-aes-ccp",
                .blocksize      = AES_BLOCK_SIZE,
        },
        {
                .mode           = CCP_AES_MODE_CBC,
+               .version        = CCP_VERSION(3, 0),
                .name           = "cbc(aes)",
                .driver_name    = "cbc-aes-ccp",
                .blocksize      = AES_BLOCK_SIZE,
        },
        {
                .mode           = CCP_AES_MODE_CFB,
+               .version        = CCP_VERSION(3, 0),
                .name           = "cfb(aes)",
                .driver_name    = "cfb-aes-ccp",
                .blocksize      = AES_BLOCK_SIZE,
        },
        {
                .mode           = CCP_AES_MODE_OFB,
+               .version        = CCP_VERSION(3, 0),
                .name           = "ofb(aes)",
                .driver_name    = "ofb-aes-ccp",
                .blocksize      = 1,
        },
        {
                .mode           = CCP_AES_MODE_CTR,
+               .version        = CCP_VERSION(3, 0),
                .name           = "ctr(aes)",
                .driver_name    = "ctr-aes-ccp",
                .blocksize      = 1,
        },
        {
                .mode           = CCP_AES_MODE_CTR,
+               .version        = CCP_VERSION(3, 0),
                .name           = "rfc3686(ctr(aes))",
                .driver_name    = "rfc3686-ctr-aes-ccp",
                .blocksize      = 1,
 int ccp_register_aes_algs(struct list_head *head)
 {
        int i, ret;
+       unsigned int ccpversion = ccp_version();
 
        for (i = 0; i < ARRAY_SIZE(aes_algs); i++) {
+               if (aes_algs[i].version > ccpversion)
+                       continue;
                ret = ccp_register_aes_alg(head, &aes_algs[i]);
                if (ret)
                        return ret;
 
 /*
  * AMD Cryptographic Coprocessor (CCP) SHA crypto API support
  *
- * Copyright (C) 2013 Advanced Micro Devices, Inc.
+ * Copyright (C) 2013,2016 Advanced Micro Devices, Inc.
  *
  * Author: Tom Lendacky <thomas.lendacky@amd.com>
  *
 }
 
 struct ccp_sha_def {
+       unsigned int version;
        const char *name;
        const char *drv_name;
        enum ccp_sha_type type;
 
 static struct ccp_sha_def sha_algs[] = {
        {
+               .version        = CCP_VERSION(3, 0),
                .name           = "sha1",
                .drv_name       = "sha1-ccp",
                .type           = CCP_SHA_TYPE_1,
                .block_size     = SHA1_BLOCK_SIZE,
        },
        {
+               .version        = CCP_VERSION(3, 0),
                .name           = "sha224",
                .drv_name       = "sha224-ccp",
                .type           = CCP_SHA_TYPE_224,
                .block_size     = SHA224_BLOCK_SIZE,
        },
        {
+               .version        = CCP_VERSION(3, 0),
                .name           = "sha256",
                .drv_name       = "sha256-ccp",
                .type           = CCP_SHA_TYPE_256,
 int ccp_register_sha_algs(struct list_head *head)
 {
        int i, ret;
+       unsigned int ccpversion = ccp_version();
 
        for (i = 0; i < ARRAY_SIZE(sha_algs); i++) {
+               if (sha_algs[i].version > ccpversion)
+                       continue;
                ret = ccp_register_sha_alg(head, &sha_algs[i]);
                if (ret)
                        return ret;
 
 }
 EXPORT_SYMBOL_GPL(ccp_present);
 
+/**
+ * ccp_version - get the version of the CCP device
+ *
+ * Returns the version from the first unit on the list;
+ * otherwise a zero if no CCP device is present
+ */
+unsigned int ccp_version(void)
+{
+       struct ccp_device *dp;
+       unsigned long flags;
+       int ret = 0;
+
+       read_lock_irqsave(&ccp_unit_lock, flags);
+       if (!list_empty(&ccp_units)) {
+               dp = list_first_entry(&ccp_units, struct ccp_device, entry);
+               ret = dp->vdata->version;
+       }
+       read_unlock_irqrestore(&ccp_unit_lock, flags);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(ccp_version);
+
 /**
  * ccp_enqueue_cmd - queue an operation for processing by the CCP
  *
 }
 #endif
 
+struct ccp_vdata ccpv3 = {
+       .version = CCP_VERSION(3, 0),
+};
+
 static int __init ccp_mod_init(void)
 {
 #ifdef CONFIG_X86
 
 #define CCP_ECC_RESULT_OFFSET          60
 #define CCP_ECC_RESULT_SUCCESS         0x0001
 
+/* Structure to hold CCP version-specific values */
+struct ccp_vdata {
+       unsigned int version;
+};
+
+extern struct ccp_vdata ccpv3;
+
 struct ccp_device;
 struct ccp_cmd;
 
 struct ccp_device {
        struct list_head entry;
 
+       struct ccp_vdata *vdata;
        unsigned int ord;
        char name[MAX_CCP_NAME_LEN];
        char rngname[MAX_CCP_NAME_LEN];
 
                goto e_err;
 
        ccp->dev_specific = ccp_pci;
+       ccp->vdata = (struct ccp_vdata *)id->driver_data;
+       if (!ccp->vdata || !ccp->vdata->version) {
+               ret = -ENODEV;
+               dev_err(dev, "missing driver data\n");
+               goto e_err;
+       }
        ccp->get_irq = ccp_get_irqs;
        ccp->free_irq = ccp_free_irqs;
 
 #endif
 
 static const struct pci_device_id ccp_pci_table[] = {
-       { PCI_VDEVICE(AMD, 0x1537), },
+       { PCI_VDEVICE(AMD, 0x1537), (kernel_ulong_t)&ccpv3 },
        /* Last entry must be zero */
        { 0, }
 };
 
        int coherent;
 };
 
+static const struct acpi_device_id ccp_acpi_match[];
+static const struct of_device_id ccp_of_match[];
+
+static struct ccp_vdata *ccp_get_of_version(struct platform_device *pdev)
+{
+#ifdef CONFIG_OF
+       const struct of_device_id *match;
+
+       match = of_match_node(ccp_of_match, pdev->dev.of_node);
+       if (match && match->data)
+               return (struct ccp_vdata *)match->data;
+#endif
+       return 0;
+}
+
+static struct ccp_vdata *ccp_get_acpi_version(struct platform_device *pdev)
+{
+#ifdef CONFIG_ACPI
+       const struct acpi_device_id *match;
+
+       match = acpi_match_device(ccp_acpi_match, &pdev->dev);
+       if (match && match->driver_data)
+               return (struct ccp_vdata *)match->driver_data;
+#endif
+       return 0;
+}
+
 static int ccp_get_irq(struct ccp_device *ccp)
 {
        struct device *dev = ccp->dev;
                goto e_err;
 
        ccp->dev_specific = ccp_platform;
+       ccp->vdata = pdev->dev.of_node ? ccp_get_of_version(pdev)
+                                        : ccp_get_acpi_version(pdev);
+       if (!ccp->vdata || !ccp->vdata->version) {
+               ret = -ENODEV;
+               dev_err(dev, "missing driver data\n");
+               goto e_err;
+       }
        ccp->get_irq = ccp_get_irqs;
        ccp->free_irq = ccp_free_irqs;
 
 
 #ifdef CONFIG_ACPI
 static const struct acpi_device_id ccp_acpi_match[] = {
-       { "AMDI0C00", 0 },
+       { "AMDI0C00", (kernel_ulong_t)&ccpv3 },
        { },
 };
 MODULE_DEVICE_TABLE(acpi, ccp_acpi_match);
 
 #ifdef CONFIG_OF
 static const struct of_device_id ccp_of_match[] = {
-       { .compatible = "amd,ccp-seattle-v1a" },
+       { .compatible = "amd,ccp-seattle-v1a",
+         .data = (const void *)&ccpv3 },
        { },
 };
 MODULE_DEVICE_TABLE(of, ccp_of_match);
 
  */
 int ccp_present(void);
 
+#define        CCP_VSIZE 16
+#define        CCP_VMASK               ((unsigned int)((1 << CCP_VSIZE) - 1))
+#define        CCP_VERSION(v, r)       ((unsigned int)((v << CCP_VSIZE) \
+                                              | (r & CCP_VMASK)))
+
+/**
+ * ccp_version - get the version of the CCP
+ *
+ * Returns a positive version number, or zero if no CCP
+ */
+unsigned int ccp_version(void);
+
 /**
  * ccp_enqueue_cmd - queue an operation for processing by the CCP
  *
        return -ENODEV;
 }
 
+static inline unsigned int ccp_version(void)
+{
+       return 0;
+}
+
 static inline int ccp_enqueue_cmd(struct ccp_cmd *cmd)
 {
        return -ENODEV;