summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThierry Reding <treding@nvidia.com>2015-04-23 16:06:27 +0200
committerThierry Reding <treding@nvidia.com>2016-02-15 19:53:26 +0100
commit4b7f0428d774feabb6c0059fe9e37aab3f986b91 (patch)
tree530e8b9695d2c9814820e8393e38c5397e69a5c2
parentdeb5876450964e027a725646b3ef3eaf1ea8796a (diff)
downloadlinux-next-4b7f0428d774feabb6c0059fe9e37aab3f986b91.tar.gz
soc/tegra: Add KFUSE support
The KFUSE module found on NVIDIA Tegra SoCs stores encrypted HDCP keys which are used to protect audio/video content over an HDMI or DP link. Signed-off-by: Thierry Reding <treding@nvidia.com>
-rw-r--r--Documentation/devicetree/bindings/fuse/nvidia,tegra20-kfuse.txt31
-rw-r--r--drivers/soc/tegra/Makefile1
-rw-r--r--drivers/soc/tegra/kfuse.c218
-rw-r--r--include/soc/tegra/kfuse.h22
4 files changed, 272 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/fuse/nvidia,tegra20-kfuse.txt b/Documentation/devicetree/bindings/fuse/nvidia,tegra20-kfuse.txt
new file mode 100644
index 000000000000..d3cf70519812
--- /dev/null
+++ b/Documentation/devicetree/bindings/fuse/nvidia,tegra20-kfuse.txt
@@ -0,0 +1,31 @@
+NVIDIA Tegra KFUSE block
+
+The KFUSE module stores encrypted HDCP keys in a large array of fuses.
+These keys are used to protect audio/video content over an HDMI or DP
+connection.
+
+Required properties:
+- compatible: Should be one of the following:
+ - "nvidia,tegra20-kfuse": for Tegra20 and Tegra30
+ - "nvidia,tegra114-kfuse": for Tegra114, Tegra124 and Tegra132
+ - "nvidia,tegra210-kfuse": for Tegra210
+- reg: Should contain a single entry that specifies the physical address and
+ length of the KFUSE registers.
+- clocks: Must contain an entry for each entry in the clock-names property.
+ See ../clocks/clock-bindings.txt for details.
+- clock-names: Must include the following entries:
+ - "kfuse"
+- resets: Must contain an entry for each entry in the reset-names property.
+- reset-names: Must include the following entries:
+ - "kfuse"
+
+Example:
+
+ kfuse@0,7000fc00 {
+ compatible = "nvidia,tegra210-kfuse";
+ reg = <0x0 0x7000fc00 0x0 0x400>;
+ clocks = <&tegra_car TEGRA210_CLK_KFUSE>;
+ clock-names = "kfuse";
+ resets = <&tegra_car 40>;
+ reset-names = "kfuse";
+ };
diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile
index ae857ff7d53d..74151796acb7 100644
--- a/drivers/soc/tegra/Makefile
+++ b/drivers/soc/tegra/Makefile
@@ -1,4 +1,5 @@
obj-y += fuse/
obj-y += common.o
+obj-y += kfuse.o
obj-y += pmc.o
diff --git a/drivers/soc/tegra/kfuse.c b/drivers/soc/tegra/kfuse.c
new file mode 100644
index 000000000000..e3e4c0e2dc65
--- /dev/null
+++ b/drivers/soc/tegra/kfuse.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <soc/tegra/kfuse.h>
+
+#define KFUSE_STATE 0x80
+#define KFUSE_STATE_CRCPASS (1 << 17)
+#define KFUSE_STATE_DONE (1 << 16)
+
+#define KFUSE_ERRCOUNT 0x84
+
+#define KFUSE_KEYADDR 0x88
+#define KFUSE_KEYADDR_AUTOINC (1 << 16)
+#define KFUSE_KEYADDR_ADDR(x) (((x) & 0xff) << 0)
+
+#define KFUSE_KEYS 0x8c
+
+static const struct of_device_id tegra_kfuse_match[] = {
+ { .compatible = "nvidia,tegra210-kfuse" },
+ { /* sentinel */ }
+};
+
+struct tegra_kfuse {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct reset_control *rst;
+
+ size_t size;
+};
+
+static int tegra_kfuse_wait_for_decode(struct tegra_kfuse *kfuse,
+ unsigned long timeout)
+{
+ u32 value;
+
+ timeout = jiffies + msecs_to_jiffies(timeout);
+
+ while (time_before(jiffies, timeout)) {
+ value = readl(kfuse->base + KFUSE_STATE);
+ if (value & KFUSE_STATE_DONE)
+ return 0;
+
+ usleep_range(100, 1000);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int tegra_kfuse_wait_for_crc(struct tegra_kfuse *kfuse,
+ unsigned long timeout)
+{
+ u32 value;
+
+ timeout = jiffies + msecs_to_jiffies(timeout);
+
+ while (time_before(jiffies, timeout)) {
+ value = readl(kfuse->base + KFUSE_STATE);
+ if (value & KFUSE_STATE_CRCPASS)
+ return 0;
+
+ usleep_range(100, 1000);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int tegra_kfuse_probe(struct platform_device *pdev)
+{
+ struct tegra_kfuse *kfuse;
+ struct resource *regs;
+ int err = 0;
+
+ kfuse = devm_kzalloc(&pdev->dev, sizeof(*kfuse), GFP_KERNEL);
+ if (!kfuse)
+ return -ENOMEM;
+
+ kfuse->dev = &pdev->dev;
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ kfuse->base = devm_ioremap_resource(&pdev->dev, regs);
+ if (IS_ERR(kfuse->base))
+ return PTR_ERR(kfuse->base);
+
+ kfuse->clk = devm_clk_get(&pdev->dev, "kfuse");
+ if (IS_ERR(kfuse->clk)) {
+ dev_err(&pdev->dev, "failed to get clock: %ld\n",
+ PTR_ERR(kfuse->clk));
+ return PTR_ERR(kfuse->clk);
+ }
+
+ err = clk_prepare_enable(kfuse->clk);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to enable clock: %d\n", err);
+ return err;
+ }
+
+ kfuse->rst = devm_reset_control_get(&pdev->dev, "kfuse");
+ if (IS_ERR(kfuse->rst)) {
+ err = PTR_ERR(kfuse->rst);
+ dev_err(&pdev->dev, "failed to get reset control: %d\n", err);
+ goto disable;
+ }
+
+ err = reset_control_deassert(kfuse->rst);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to deassert reset: %d\n", err);
+ goto disable;
+ }
+
+ err = tegra_kfuse_wait_for_decode(kfuse, 100);
+ if (err < 0) {
+ dev_err(&pdev->dev, "error waiting for decode: %d\n", err);
+ goto reset;
+ }
+
+ err = tegra_kfuse_wait_for_crc(kfuse, 100);
+ if (err < 0) {
+ dev_err(&pdev->dev, "error waiting for CRC check: %d\n", err);
+ goto reset;
+ }
+
+ /*
+ * The ECC-decoded keyglob data is 144 32-bit words (576 bytes).
+ */
+ kfuse->size = 576;
+
+ platform_set_drvdata(pdev, kfuse);
+
+ return 0;
+
+reset:
+ reset_control_assert(kfuse->rst);
+disable:
+ clk_disable_unprepare(kfuse->clk);
+ return err;
+}
+
+static int tegra_kfuse_remove(struct platform_device *pdev)
+{
+ struct tegra_kfuse *kfuse = platform_get_drvdata(pdev);
+ int err = 0;
+
+ reset_control_assert(kfuse->rst);
+ clk_disable_unprepare(kfuse->clk);
+
+ dev_info(&pdev->dev, "< %s() = %d\n", __func__, err);
+ return err;
+}
+
+static struct platform_driver tegra_kfuse_driver = {
+ .driver = {
+ .name = "tegra-kfuse",
+ .of_match_table = tegra_kfuse_match,
+ },
+ .probe = tegra_kfuse_probe,
+ .remove = tegra_kfuse_remove,
+};
+module_platform_driver(tegra_kfuse_driver);
+
+struct tegra_kfuse *tegra_kfuse_find_by_of_node(struct device_node *np)
+{
+ struct device *dev;
+
+ dev = driver_find_device(&tegra_kfuse_driver.driver, NULL, np,
+ of_device_match);
+ if (!dev)
+ return NULL;
+
+ return dev_get_drvdata(dev);
+}
+EXPORT_SYMBOL(tegra_kfuse_find_by_of_node);
+
+ssize_t tegra_kfuse_read(struct tegra_kfuse *kfuse, void *buffer, size_t size)
+{
+ size_t offset;
+ u32 value;
+
+ if (!buffer && size == 0)
+ return kfuse->size;
+
+ if (size > kfuse->size)
+ size = kfuse->size;
+
+ value = KFUSE_KEYADDR_AUTOINC | KFUSE_KEYADDR_ADDR(0);
+ writel(value, kfuse->base + KFUSE_KEYADDR);
+
+ for (offset = 0; offset < size; offset += 4) {
+ value = readl(kfuse->base + KFUSE_KEYS);
+ memcpy(buffer + offset, &value, 4);
+ }
+
+ return offset;
+}
+EXPORT_SYMBOL(tegra_kfuse_read);
+
+MODULE_DESCRIPTION("NVIDIA Tegra KFUSE driver");
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/soc/tegra/kfuse.h b/include/soc/tegra/kfuse.h
new file mode 100644
index 000000000000..fb44df5ad64b
--- /dev/null
+++ b/include/soc/tegra/kfuse.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef SOC_TEGRA_KFUSE_H
+#define SOC_TEGRA_KFUSE_H
+
+struct tegra_kfuse;
+
+struct tegra_kfuse *tegra_kfuse_find_by_of_node(struct device_node *np);
+ssize_t tegra_kfuse_read(struct tegra_kfuse *kfuse, void *buffer, size_t size);
+
+#endif