summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/kfuse.c
blob: 9e4b482e4691fc86bf5282eb9871fe283dcc20d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
 * arch/arm/mach-tegra/kfuse.c
 *
 * Copyright (C) 2010-2011 NVIDIA Corporation.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that 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.
 *
 */

/* The kfuse block stores downstream and upstream HDCP keys for use by HDMI
 * module.
 */

#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/clk.h>

#include <mach/iomap.h>
#include <mach/kfuse.h>

#include "clock.h"
#include "apbio.h"

static struct clk *kfuse_clk = NULL;

/* register definition */
#define KFUSE_STATE			0x80
#define  KFUSE_STATE_DONE		(1u << 16)
#define  KFUSE_STATE_CRCPASS		(1u << 17)
#define KFUSE_KEYADDR			0x88
#define  KFUSE_KEYADDR_AUTOINC		(1u << 16)
#define KFUSE_KEYS			0x8c

static inline u32 tegra_kfuse_readl(unsigned long offset)
{
	return tegra_apb_readl(TEGRA_KFUSE_BASE + offset);
}

static inline void tegra_kfuse_writel(u32 value, unsigned long offset)
{
	tegra_apb_writel(value, TEGRA_KFUSE_BASE + offset);
}

static int wait_for_done(void)
{
	u32 reg;
	int retries = 50;
	do {
		reg = tegra_kfuse_readl(KFUSE_STATE);
		if (reg & KFUSE_STATE_DONE)
			return 0;
		msleep(10);
	} while(--retries);
	return -ETIMEDOUT;
}

/* read up to KFUSE_DATA_SZ bytes into dest.
 * always starts at the first kfuse.
 */
int tegra_kfuse_read(void *dest, size_t len)
{
	int err;
	u32 v;
	unsigned cnt;

	if (len > KFUSE_DATA_SZ)
		return -EINVAL;

	if (kfuse_clk == NULL) {
		kfuse_clk = tegra_get_clock_by_name("kfuse");
		if (IS_ERR_OR_NULL(kfuse_clk)) {
			pr_err("kfuse: can't get kfuse clock\n");
			return -EINVAL;
		}
	}

	err = clk_enable(kfuse_clk);
	if (err)
		return err;

	tegra_kfuse_writel(KFUSE_KEYADDR_AUTOINC, KFUSE_KEYADDR);

	err = wait_for_done();
	if (err) {
		pr_err("kfuse: read timeout\n");
		clk_disable(kfuse_clk);
		return err;
	}

	if ((tegra_kfuse_readl(KFUSE_STATE) & KFUSE_STATE_CRCPASS) == 0) {
		pr_err("kfuse: crc failed\n");
		clk_disable(kfuse_clk);
		return -EIO;
	}

	for (cnt = 0; cnt < len; cnt += 4) {
		v = tegra_kfuse_readl(KFUSE_KEYS);
		memcpy(dest + cnt, &v, sizeof v);
	}

	clk_disable(kfuse_clk);

	return 0;
}