fuse-tegra.c 7.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Copyright (c) 2013-2014, 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

18
#include <linux/clk.h>
19 20
#include <linux/device.h>
#include <linux/kobject.h>
21
#include <linux/init.h>
22 23 24 25 26
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>

27
#include <soc/tegra/common.h>
28 29 30 31 32
#include <soc/tegra/fuse.h>

#include "fuse.h"

struct tegra_sku_info tegra_sku_info;
V
Vince Hsu 已提交
33
EXPORT_SYMBOL(tegra_sku_info);
34 35 36 37 38 39 40 41 42 43

static const char *tegra_revision_name[TEGRA_REVISION_MAX] = {
	[TEGRA_REVISION_UNKNOWN] = "unknown",
	[TEGRA_REVISION_A01]     = "A01",
	[TEGRA_REVISION_A02]     = "A02",
	[TEGRA_REVISION_A03]     = "A03",
	[TEGRA_REVISION_A03p]    = "A03 prime",
	[TEGRA_REVISION_A04]     = "A04",
};

44
static u8 fuse_readb(struct tegra_fuse *fuse, unsigned int offset)
45 46 47
{
	u32 val;

48
	val = fuse->read(fuse, round_down(offset, 4));
49 50 51 52 53 54 55
	val >>= (offset % 4) * 8;
	val &= 0xff;

	return val;
}

static ssize_t fuse_read(struct file *fd, struct kobject *kobj,
56 57
			 struct bin_attribute *attr, char *buf,
			 loff_t pos, size_t size)
58
{
59 60
	struct device *dev = kobj_to_dev(kobj);
	struct tegra_fuse *fuse = dev_get_drvdata(dev);
61 62
	int i;

63
	if (pos < 0 || pos >= attr->size)
64 65
		return 0;

66 67
	if (size > attr->size - pos)
		size = attr->size - pos;
68 69

	for (i = 0; i < size; i++)
70
		buf[i] = fuse_readb(fuse, pos + i);
71 72 73 74 75 76 77 78 79

	return i;
}

static struct bin_attribute fuse_bin_attr = {
	.attr = { .name = "fuse", .mode = S_IRUGO, },
	.read = fuse_read,
};

80 81 82 83 84 85 86 87
static int tegra_fuse_create_sysfs(struct device *dev, unsigned int size,
				   const struct tegra_fuse_info *info)
{
	fuse_bin_attr.size = size;

	return device_create_bin_file(dev, &fuse_bin_attr);
}

88 89 90 91 92
static const struct of_device_id car_match[] __initconst = {
	{ .compatible = "nvidia,tegra20-car", },
	{ .compatible = "nvidia,tegra30-car", },
	{ .compatible = "nvidia,tegra114-car", },
	{ .compatible = "nvidia,tegra124-car", },
93
	{ .compatible = "nvidia,tegra132-car", },
94
	{ .compatible = "nvidia,tegra210-car", },
95 96 97
	{},
};

98 99 100 101 102 103
static struct tegra_fuse *fuse = &(struct tegra_fuse) {
	.base = NULL,
	.soc = NULL,
};

static const struct of_device_id tegra_fuse_match[] = {
104 105 106
#ifdef CONFIG_ARCH_TEGRA_210_SOC
	{ .compatible = "nvidia,tegra210-efuse", .data = &tegra210_fuse_soc },
#endif
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
#ifdef CONFIG_ARCH_TEGRA_132_SOC
	{ .compatible = "nvidia,tegra132-efuse", .data = &tegra124_fuse_soc },
#endif
#ifdef CONFIG_ARCH_TEGRA_124_SOC
	{ .compatible = "nvidia,tegra124-efuse", .data = &tegra124_fuse_soc },
#endif
#ifdef CONFIG_ARCH_TEGRA_114_SOC
	{ .compatible = "nvidia,tegra114-efuse", .data = &tegra114_fuse_soc },
#endif
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
	{ .compatible = "nvidia,tegra30-efuse", .data = &tegra30_fuse_soc },
#endif
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
	{ .compatible = "nvidia,tegra20-efuse", .data = &tegra20_fuse_soc },
#endif
	{ /* sentinel */ }
};

static int tegra_fuse_probe(struct platform_device *pdev)
126
{
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
	void __iomem *base = fuse->base;
	struct resource *res;
	int err;

	/* take over the memory region from the early initialization */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	fuse->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(fuse->base))
		return PTR_ERR(fuse->base);

	fuse->clk = devm_clk_get(&pdev->dev, "fuse");
	if (IS_ERR(fuse->clk)) {
		dev_err(&pdev->dev, "failed to get FUSE clock: %ld",
			PTR_ERR(fuse->clk));
		return PTR_ERR(fuse->clk);
	}
143

144 145
	platform_set_drvdata(pdev, fuse);
	fuse->dev = &pdev->dev;
146

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
	if (fuse->soc->probe) {
		err = fuse->soc->probe(fuse);
		if (err < 0)
			return err;
	}

	if (tegra_fuse_create_sysfs(&pdev->dev, fuse->soc->info->size,
				    fuse->soc->info))
		return -ENODEV;

	/* release the early I/O memory mapping */
	iounmap(base);

	return 0;
}

static struct platform_driver tegra_fuse_driver = {
	.driver = {
		.name = "tegra-fuse",
		.of_match_table = tegra_fuse_match,
		.suppress_bind_attrs = true,
	},
	.probe = tegra_fuse_probe,
};
171
builtin_platform_driver(tegra_fuse_driver);
172 173 174 175 176 177 178 179 180 181 182

bool __init tegra_fuse_read_spare(unsigned int spare)
{
	unsigned int offset = fuse->soc->info->spare + spare * 4;

	return fuse->read_early(fuse, offset) & 1;
}

u32 __init tegra_fuse_read_early(unsigned int offset)
{
	return fuse->read_early(fuse, offset);
183 184 185 186
}

int tegra_fuse_readl(unsigned long offset, u32 *value)
{
187
	if (!fuse->read)
188 189
		return -EPROBE_DEFER;

190
	*value = fuse->read(fuse, offset);
191 192 193 194 195

	return 0;
}
EXPORT_SYMBOL(tegra_fuse_readl);

196
static void tegra_enable_fuse_clk(void __iomem *base)
197
{
198
	u32 reg;
199

200 201 202
	reg = readl_relaxed(base + 0x48);
	reg |= 1 << 28;
	writel(reg, base + 0x48);
203

204 205 206 207 208 209 210
	/*
	 * Enable FUSE clock. This needs to be hardcoded because the clock
	 * subsystem is not active during early boot.
	 */
	reg = readl(base + 0x14);
	reg |= 1 << 7;
	writel(reg, base + 0x14);
211 212
}

213
static int __init tegra_init_fuse(void)
214
{
215
	const struct of_device_id *match;
216
	struct device_node *np;
217
	struct resource regs;
218

219 220
	tegra_init_apbmisc();

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	np = of_find_matching_node_and_match(NULL, tegra_fuse_match, &match);
	if (!np) {
		/*
		 * Fall back to legacy initialization for 32-bit ARM only. All
		 * 64-bit ARM device tree files for Tegra are required to have
		 * a FUSE node.
		 *
		 * This is for backwards-compatibility with old device trees
		 * that didn't contain a FUSE node.
		 */
		if (IS_ENABLED(CONFIG_ARM) && soc_is_tegra()) {
			u8 chip = tegra_get_chip_id();

			regs.start = 0x7000f800;
			regs.end = 0x7000fbff;
			regs.flags = IORESOURCE_MEM;

			switch (chip) {
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
			case TEGRA20:
				fuse->soc = &tegra20_fuse_soc;
				break;
#endif

#ifdef CONFIG_ARCH_TEGRA_3x_SOC
			case TEGRA30:
				fuse->soc = &tegra30_fuse_soc;
				break;
#endif

#ifdef CONFIG_ARCH_TEGRA_114_SOC
			case TEGRA114:
				fuse->soc = &tegra114_fuse_soc;
				break;
#endif

#ifdef CONFIG_ARCH_TEGRA_124_SOC
			case TEGRA124:
				fuse->soc = &tegra124_fuse_soc;
				break;
#endif

			default:
				pr_warn("Unsupported SoC: %02x\n", chip);
				break;
			}
		} else {
			/*
			 * At this point we're not running on Tegra, so play
			 * nice with multi-platform kernels.
			 */
			return 0;
		}
274
	} else {
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
		/*
		 * Extract information from the device tree if we've found a
		 * matching node.
		 */
		if (of_address_to_resource(np, 0, &regs) < 0) {
			pr_err("failed to get FUSE register\n");
			return -ENXIO;
		}

		fuse->soc = match->data;
	}

	np = of_find_matching_node(NULL, car_match);
	if (np) {
		void __iomem *base = of_iomap(np, 0);
		if (base) {
			tegra_enable_fuse_clk(base);
			iounmap(base);
		} else {
			pr_err("failed to map clock registers\n");
			return -ENXIO;
		}
	}

	fuse->base = ioremap_nocache(regs.start, resource_size(&regs));
	if (!fuse->base) {
		pr_err("failed to map FUSE registers\n");
302
		return -ENXIO;
303 304
	}

305
	fuse->soc->init(fuse);
306

307
	pr_info("Tegra Revision: %s SKU: %d CPU Process: %d SoC Process: %d\n",
308 309
		tegra_revision_name[tegra_sku_info.revision],
		tegra_sku_info.sku_id, tegra_sku_info.cpu_process_id,
310 311 312
		tegra_sku_info.soc_process_id);
	pr_debug("Tegra CPU Speedo ID %d, SoC Speedo ID %d\n",
		 tegra_sku_info.cpu_speedo_id, tegra_sku_info.soc_speedo_id);
313 314

	return 0;
315
}
316
early_initcall(tegra_init_fuse);