summaryrefslogtreecommitdiffstats
path: root/drivers/char/hw_random/st-rng.c
blob: 1d35363d23c51929eff4d1924bed08684a6587cc (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
140
141
142
143
144
145
146
147
148
149
150
151
/*
 * ST Random Number Generator Driver ST's Platforms
 *
 * Author: Pankaj Dev: <pankaj.dev@st.com>
 *         Lee Jones <lee.jones@linaro.org>
 *
 * Copyright (C) 2015 STMicroelectronics (R&D) Limited
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

/* Registers */
#define ST_RNG_STATUS_REG		0x20
#define ST_RNG_DATA_REG			0x24

/* Registers fields */
#define ST_RNG_STATUS_BAD_SEQUENCE	BIT(0)
#define ST_RNG_STATUS_BAD_ALTERNANCE	BIT(1)
#define ST_RNG_STATUS_FIFO_FULL		BIT(5)

#define ST_RNG_SAMPLE_SIZE		2 /* 2 Byte (16bit) samples */
#define ST_RNG_FIFO_DEPTH		4
#define ST_RNG_FIFO_SIZE		(ST_RNG_FIFO_DEPTH * ST_RNG_SAMPLE_SIZE)

/*
 * Samples are documented to be available every 0.667us, so in theory
 * the 4 sample deep FIFO should take 2.668us to fill.  However, during
 * thorough testing, it became apparent that filling the FIFO actually
 * takes closer to 12us.  We then multiply by 2 in order to account for
 * the lack of udelay()'s reliability, suggested by Russell King.
 */
#define ST_RNG_FILL_FIFO_TIMEOUT	(12 * 2)

struct st_rng_data {
	void __iomem	*base;
	struct clk	*clk;
	struct hwrng	ops;
};

static int st_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
	struct st_rng_data *ddata = (struct st_rng_data *)rng->priv;
	u32 status;
	int i;

	if (max < sizeof(u16))
		return -EINVAL;

	/* Wait until FIFO is full - max 4uS*/
	for (i = 0; i < ST_RNG_FILL_FIFO_TIMEOUT; i++) {
		status = readl_relaxed(ddata->base + ST_RNG_STATUS_REG);
		if (status & ST_RNG_STATUS_FIFO_FULL)
			break;
		udelay(1);
	}

	if (i == ST_RNG_FILL_FIFO_TIMEOUT)
		return 0;

	for (i = 0; i < ST_RNG_FIFO_SIZE && i < max; i += 2)
		*(u16 *)(data + i) =
			readl_relaxed(ddata->base + ST_RNG_DATA_REG);

	return i;	/* No of bytes read */
}

static int st_rng_probe(struct platform_device *pdev)
{
	struct st_rng_data *ddata;
	struct resource *res;
	struct clk *clk;
	void __iomem *base;
	int ret;

	ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
	if (!ddata)
		return -ENOMEM;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base))
		return PTR_ERR(base);

	clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(clk))
		return PTR_ERR(clk);

	ret = clk_prepare_enable(clk);
	if (ret)
		return ret;

	ddata->ops.priv	= (unsigned long)ddata;
	ddata->ops.read	= st_rng_read;
	ddata->ops.name	= pdev->name;
	ddata->base	= base;
	ddata->clk	= clk;

	dev_set_drvdata(&pdev->dev, ddata);

	ret = hwrng_register(&ddata->ops);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register HW RNG\n");
		return ret;
	}

	dev_info(&pdev->dev, "Successfully registered HW RNG\n");

	return 0;
}

static int st_rng_remove(struct platform_device *pdev)
{
	struct st_rng_data *ddata = dev_get_drvdata(&pdev->dev);

	hwrng_unregister(&ddata->ops);

	clk_disable_unprepare(ddata->clk);

	return 0;
}

static const struct of_device_id st_rng_match[] = {
	{ .compatible = "st,rng" },
	{},
};
MODULE_DEVICE_TABLE(of, st_rng_match);

static struct platform_driver st_rng_driver = {
	.driver = {
		.name = "st-hwrandom",
		.of_match_table = of_match_ptr(st_rng_match),
	},
	.probe = st_rng_probe,
	.remove = st_rng_remove
};

module_platform_driver(st_rng_driver);

MODULE_AUTHOR("Pankaj Dev <pankaj.dev@st.com>");
MODULE_LICENSE("GPL v2");