summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-mxs/ocotp.c
blob: 86e63dc7deb38b29d80bfc27145caf18d704c976 (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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * ocotp.c - barebox driver for the On-Chip One Time Programmable for MXS
 *
 * Copyright (C) 2012 by Wolfram Sang, Pengutronix e.K.
 * based on the kernel driver which is
 * Copyright 2010 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <common.h>
#include <init.h>
#include <driver.h>
#include <xfuncs.h>
#include <errno.h>
#include <param.h>
#include <fcntl.h>
#include <malloc.h>
#include <io.h>
#include <clock.h>

#include <mach/generic.h>
#include <mach/ocotp.h>
#include <mach/imx-regs.h>

#define DRIVERNAME "ocotp"

#define OCOTP_WORD_OFFSET		0x20

#define BM_OCOTP_CTRL_BUSY		(1 << 8)
#define BM_OCOTP_CTRL_ERROR		(1 << 9)
#define BM_OCOTP_CTRL_RD_BANK_OPEN	(1 << 12)

struct ocotp_priv {
	struct cdev cdev;
	void __iomem *base;
};

static ssize_t mxs_ocotp_cdev_read(struct cdev *cdev, void *buf, size_t count,
		loff_t offset, ulong flags)
{
	struct ocotp_priv *priv = cdev->priv;
	void __iomem *base = priv->base;
	size_t size = min((loff_t)count, cdev->size - offset);
	uint64_t start;
	int i;

	/*
	 * clk_enable(hbus_clk) for ocotp can be skipped
	 * as it must be on when system is running.
	 */

	/* try to clear ERROR bit */
	writel(BM_OCOTP_CTRL_ERROR, base + BIT_CLR);

	/* check both BUSY and ERROR cleared */
	start = get_time_ns();
	while (readl(base) & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR))
		if (is_timeout(start, MSECOND))
			return -ETIMEDOUT;

	/* open OCOTP banks for read */
	writel(BM_OCOTP_CTRL_RD_BANK_OPEN, base + BIT_SET);

	/* approximately wait 32 hclk cycles */
	udelay(1);

	/* poll BUSY bit becoming cleared */
	start = get_time_ns();
	while (readl(base) & BM_OCOTP_CTRL_BUSY)
		if (is_timeout(start, MSECOND))
			return -ETIMEDOUT;

	for (i = 0; i < size; i++)
		/* When reading bytewise, we need to hop over the SET/CLR/TGL regs */
		((u8 *)buf)[i] = readb(base + OCOTP_WORD_OFFSET +
				(((i + offset) & 0xfc) << 2) + ((i + offset) & 3));

	/* close banks for power saving */
	writel(BM_OCOTP_CTRL_RD_BANK_OPEN, base + BIT_CLR);

	return size;
}

static struct file_operations mxs_ocotp_ops = {
	.read	= mxs_ocotp_cdev_read,
	.lseek	= dev_lseek_default,
};

static int mxs_ocotp_probe(struct device_d *dev)
{
	int err;
	struct ocotp_priv *priv = xzalloc(sizeof (*priv));

	priv->base = dev_request_mem_region(dev, 0);
	priv->cdev.dev = dev;
	priv->cdev.ops = &mxs_ocotp_ops;
	priv->cdev.priv = priv;
	priv->cdev.size = cpu_is_mx23() ? 128 : 160;
	priv->cdev.name = DRIVERNAME;

	err = devfs_create(&priv->cdev);
	if (err < 0)
		return err;

	return 0;
}

static struct driver_d mxs_ocotp_driver = {
	.name	= DRIVERNAME,
	.probe	= mxs_ocotp_probe,
};

static int mxs_ocotp_init(void)
{
	register_driver(&mxs_ocotp_driver);

	return 0;
}
coredevice_initcall(mxs_ocotp_init);

int mxs_ocotp_read(void *buf, int count, int offset)
{
	struct cdev *cdev;
	int ret;

	cdev = cdev_open(DRIVERNAME, O_RDONLY);
	if (!cdev)
		return -ENODEV;

	ret = cdev_read(cdev, buf, count, offset, 0);

	cdev_close(cdev);

	return ret;
}