]>
Commit | Line | Data |
---|---|---|
aa5eb9a3 MB |
1 | /* |
2 | * I2C Driver for Atmel ATSHA204 over I2C | |
3 | * | |
4 | * Copyright (C) 2014 Josh Datko, Cryptotronix, jbd@cryptotronix.com | |
0cf207ec | 5 | * 2016 Tomas Hlavacek, CZ.NIC, tmshlvck@gmail.com |
61143f74 | 6 | * 2017 Marek Behún, CZ.NIC, kabel@kernel.org |
aa5eb9a3 MB |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | */ | |
12 | ||
13 | #include <common.h> | |
14 | #include <dm.h> | |
15 | #include <i2c.h> | |
16 | #include <errno.h> | |
17 | #include <atsha204a-i2c.h> | |
f7ae49fc | 18 | #include <log.h> |
401d1c4f | 19 | #include <asm/global_data.h> |
c05ed00a | 20 | #include <linux/delay.h> |
467f0c4d | 21 | #include <linux/bitrev.h> |
3db71108 | 22 | #include <u-boot/crc.h> |
aa5eb9a3 | 23 | |
73d88cf9 PA |
24 | #define ATSHA204A_TWLO_US 60 |
25 | #define ATSHA204A_TWHI_US 2500 | |
aa5eb9a3 MB |
26 | #define ATSHA204A_TRANSACTION_TIMEOUT 100000 |
27 | #define ATSHA204A_TRANSACTION_RETRY 5 | |
28 | #define ATSHA204A_EXECTIME 5000 | |
29 | ||
30 | DECLARE_GLOBAL_DATA_PTR; | |
31 | ||
467f0c4d | 32 | static inline u16 atsha204a_crc16(const u8 *buffer, size_t len) |
aa5eb9a3 | 33 | { |
467f0c4d | 34 | return bitrev16(crc16(0, buffer, len)); |
aa5eb9a3 MB |
35 | } |
36 | ||
37 | static int atsha204a_send(struct udevice *dev, const u8 *buf, u8 len) | |
38 | { | |
39 | fdt_addr_t *priv = dev_get_priv(dev); | |
40 | struct i2c_msg msg; | |
41 | ||
42 | msg.addr = *priv; | |
43 | msg.flags = I2C_M_STOP; | |
44 | msg.len = len; | |
45 | msg.buf = (u8 *) buf; | |
46 | ||
47 | return dm_i2c_xfer(dev, &msg, 1); | |
48 | } | |
49 | ||
50 | static int atsha204a_recv(struct udevice *dev, u8 *buf, u8 len) | |
51 | { | |
52 | fdt_addr_t *priv = dev_get_priv(dev); | |
53 | struct i2c_msg msg; | |
54 | ||
55 | msg.addr = *priv; | |
56 | msg.flags = I2C_M_RD | I2C_M_STOP; | |
57 | msg.len = len; | |
58 | msg.buf = (u8 *) buf; | |
59 | ||
60 | return dm_i2c_xfer(dev, &msg, 1); | |
61 | } | |
62 | ||
63 | static int atsha204a_recv_resp(struct udevice *dev, | |
64 | struct atsha204a_resp *resp) | |
65 | { | |
66 | int res; | |
67 | u16 resp_crc, computed_crc; | |
68 | u8 *p = (u8 *) resp; | |
69 | ||
70 | res = atsha204a_recv(dev, p, 4); | |
71 | if (res) | |
72 | return res; | |
73 | ||
74 | if (resp->length > 4) { | |
75 | if (resp->length > sizeof(*resp)) | |
76 | return -EMSGSIZE; | |
77 | ||
78 | res = atsha204a_recv(dev, p + 4, resp->length - 4); | |
79 | if (res) | |
80 | return res; | |
81 | } | |
82 | ||
83 | resp_crc = (u16) p[resp->length - 2] | |
84 | | (((u16) p[resp->length - 1]) << 8); | |
85 | computed_crc = atsha204a_crc16(p, resp->length - 2); | |
86 | ||
87 | if (resp_crc != computed_crc) { | |
88 | debug("Invalid checksum in ATSHA204A response\n"); | |
89 | return -EBADMSG; | |
90 | } | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | int atsha204a_wakeup(struct udevice *dev) | |
96 | { | |
97 | u8 req[4]; | |
98 | struct atsha204a_resp resp; | |
c4841ae4 | 99 | int res; |
aa5eb9a3 MB |
100 | |
101 | debug("Waking up ATSHA204A\n"); | |
102 | ||
c4841ae4 MB |
103 | /* |
104 | * The device ignores any levels or transitions on the SCL pin | |
105 | * when the device is idle, asleep or during waking up. | |
106 | * Don't check for error when waking up the device. | |
107 | */ | |
108 | memset(req, 0, 4); | |
109 | atsha204a_send(dev, req, 4); | |
aa5eb9a3 | 110 | |
c4841ae4 | 111 | udelay(ATSHA204A_TWLO_US + ATSHA204A_TWHI_US); |
aa5eb9a3 | 112 | |
c4841ae4 MB |
113 | res = atsha204a_recv_resp(dev, &resp); |
114 | if (res) { | |
115 | debug("failed on receiving response, ending\n"); | |
116 | return res; | |
117 | } | |
aa5eb9a3 | 118 | |
c4841ae4 MB |
119 | if (resp.code != ATSHA204A_STATUS_AFTER_WAKE) { |
120 | debug("failed (response code = %02x), ending\n", resp.code); | |
121 | return -EBADMSG; | |
aa5eb9a3 MB |
122 | } |
123 | ||
c4841ae4 MB |
124 | debug("success\n"); |
125 | return 0; | |
aa5eb9a3 MB |
126 | } |
127 | ||
128 | int atsha204a_idle(struct udevice *dev) | |
129 | { | |
130 | int res; | |
131 | u8 req = ATSHA204A_FUNC_IDLE; | |
132 | ||
133 | res = atsha204a_send(dev, &req, 1); | |
134 | if (res) | |
135 | debug("Failed putting ATSHA204A idle\n"); | |
136 | return res; | |
137 | } | |
138 | ||
139 | int atsha204a_sleep(struct udevice *dev) | |
140 | { | |
141 | int res; | |
6e0d4a7e | 142 | u8 req = ATSHA204A_FUNC_SLEEP; |
aa5eb9a3 MB |
143 | |
144 | res = atsha204a_send(dev, &req, 1); | |
145 | if (res) | |
146 | debug("Failed putting ATSHA204A to sleep\n"); | |
147 | return res; | |
148 | } | |
149 | ||
150 | static int atsha204a_transaction(struct udevice *dev, struct atsha204a_req *req, | |
151 | struct atsha204a_resp *resp) | |
152 | { | |
153 | int res, timeout = ATSHA204A_TRANSACTION_TIMEOUT; | |
154 | ||
155 | res = atsha204a_send(dev, (u8 *) req, req->length + 1); | |
156 | if (res) { | |
157 | debug("ATSHA204A transaction send failed\n"); | |
158 | return -EBUSY; | |
159 | } | |
160 | ||
161 | do { | |
e4662716 | 162 | udelay(ATSHA204A_EXECTIME); |
aa5eb9a3 MB |
163 | res = atsha204a_recv_resp(dev, resp); |
164 | if (!res || res == -EMSGSIZE || res == -EBADMSG) | |
165 | break; | |
166 | ||
167 | debug("ATSHA204A transaction polling for response " | |
168 | "(timeout = %d)\n", timeout); | |
169 | ||
aa5eb9a3 MB |
170 | timeout -= ATSHA204A_EXECTIME; |
171 | } while (timeout > 0); | |
172 | ||
173 | if (timeout <= 0) { | |
174 | debug("ATSHA204A transaction timed out\n"); | |
175 | return -ETIMEDOUT; | |
176 | } | |
177 | ||
178 | return res; | |
179 | } | |
180 | ||
181 | static void atsha204a_req_crc32(struct atsha204a_req *req) | |
182 | { | |
183 | u8 *p = (u8 *) req; | |
184 | u16 computed_crc; | |
185 | u16 *crc_ptr = (u16 *) &p[req->length - 1]; | |
186 | ||
187 | /* The buffer to crc16 starts at byte 1, not 0 */ | |
188 | computed_crc = atsha204a_crc16(p + 1, req->length - 2); | |
189 | ||
190 | *crc_ptr = cpu_to_le16(computed_crc); | |
191 | } | |
192 | ||
193 | int atsha204a_read(struct udevice *dev, enum atsha204a_zone zone, bool read32, | |
194 | u16 addr, u8 *buffer) | |
195 | { | |
196 | int res, retry = ATSHA204A_TRANSACTION_RETRY; | |
197 | struct atsha204a_req req; | |
198 | struct atsha204a_resp resp; | |
199 | ||
200 | req.function = ATSHA204A_FUNC_COMMAND; | |
201 | req.length = 7; | |
202 | req.command = ATSHA204A_CMD_READ; | |
203 | ||
204 | req.param1 = (u8) zone; | |
205 | if (read32) | |
206 | req.param1 |= 0x80; | |
207 | ||
208 | req.param2 = cpu_to_le16(addr); | |
209 | ||
210 | atsha204a_req_crc32(&req); | |
211 | ||
212 | do { | |
213 | res = atsha204a_transaction(dev, &req, &resp); | |
214 | if (!res) | |
215 | break; | |
216 | ||
217 | debug("ATSHA204A read retry (%d)\n", retry); | |
218 | retry--; | |
219 | atsha204a_wakeup(dev); | |
220 | } while (retry >= 0); | |
0a50b3c9 | 221 | |
aa5eb9a3 MB |
222 | if (res) { |
223 | debug("ATSHA204A read failed\n"); | |
224 | return res; | |
225 | } | |
226 | ||
227 | if (resp.length != (read32 ? 32 : 4) + 3) { | |
228 | debug("ATSHA204A read bad response length (%d)\n", | |
229 | resp.length); | |
230 | return -EBADMSG; | |
231 | } | |
232 | ||
233 | memcpy(buffer, ((u8 *) &resp) + 1, read32 ? 32 : 4); | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
238 | int atsha204a_get_random(struct udevice *dev, u8 *buffer, size_t max) | |
239 | { | |
240 | int res; | |
241 | struct atsha204a_req req; | |
242 | struct atsha204a_resp resp; | |
243 | ||
244 | req.function = ATSHA204A_FUNC_COMMAND; | |
245 | req.length = 7; | |
246 | req.command = ATSHA204A_CMD_RANDOM; | |
247 | ||
248 | req.param1 = 1; | |
249 | req.param2 = 0; | |
250 | ||
251 | /* We do not have to compute the checksum dynamically */ | |
252 | req.data[0] = 0x27; | |
253 | req.data[1] = 0x47; | |
254 | ||
255 | res = atsha204a_transaction(dev, &req, &resp); | |
256 | if (res) { | |
257 | debug("ATSHA204A random transaction failed\n"); | |
258 | return res; | |
259 | } | |
260 | ||
261 | memcpy(buffer, ((u8 *) &resp) + 1, max >= 32 ? 32 : max); | |
262 | return 0; | |
263 | } | |
264 | ||
d1998a9f | 265 | static int atsha204a_of_to_plat(struct udevice *dev) |
aa5eb9a3 MB |
266 | { |
267 | fdt_addr_t *priv = dev_get_priv(dev); | |
268 | fdt_addr_t addr; | |
269 | ||
532a5b29 | 270 | addr = dev_read_addr(dev); |
aa5eb9a3 MB |
271 | if (addr == FDT_ADDR_T_NONE) { |
272 | debug("Can't get ATSHA204A I2C base address\n"); | |
273 | return -ENXIO; | |
274 | } | |
275 | ||
276 | *priv = addr; | |
277 | return 0; | |
278 | } | |
279 | ||
280 | static const struct udevice_id atsha204a_ids[] = { | |
89eabd2f | 281 | { .compatible = "atmel,atsha204" }, |
aa5eb9a3 MB |
282 | { .compatible = "atmel,atsha204a" }, |
283 | { } | |
284 | }; | |
285 | ||
286 | U_BOOT_DRIVER(atsha204) = { | |
287 | .name = "atsha204", | |
288 | .id = UCLASS_MISC, | |
289 | .of_match = atsha204a_ids, | |
d1998a9f | 290 | .of_to_plat = atsha204a_of_to_plat, |
41575d8e | 291 | .priv_auto = sizeof(fdt_addr_t), |
aa5eb9a3 | 292 | }; |