1 | /* |
2 | * B53 register access through SPI |
3 | * |
4 | * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org> |
5 | * |
6 | * Permission to use, copy, modify, and/or distribute this software for any |
7 | * purpose with or without fee is hereby granted, provided that the above |
8 | * copyright notice and this permission notice appear in all copies. |
9 | * |
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | */ |
18 | |
19 | #include <asm/unaligned.h> |
20 | |
21 | #include <linux/delay.h> |
22 | #include <linux/kernel.h> |
23 | #include <linux/module.h> |
24 | #include <linux/spi/spi.h> |
25 | #include <linux/platform_data/b53.h> |
26 | |
27 | #include "b53_priv.h" |
28 | |
29 | #define B53_SPI_DATA 0xf0 |
30 | |
31 | #define B53_SPI_STATUS 0xfe |
32 | #define B53_SPI_CMD_SPIF BIT(7) |
33 | #define B53_SPI_CMD_RACK BIT(5) |
34 | |
35 | #define B53_SPI_CMD_READ 0x00 |
36 | #define B53_SPI_CMD_WRITE 0x01 |
37 | #define B53_SPI_CMD_NORMAL 0x60 |
38 | #define B53_SPI_CMD_FAST 0x10 |
39 | |
40 | #define B53_SPI_PAGE_SELECT 0xff |
41 | |
42 | static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val, |
43 | unsigned int len) |
44 | { |
45 | u8 txbuf[2]; |
46 | |
47 | txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ; |
48 | txbuf[1] = reg; |
49 | |
50 | return spi_write_then_read(spi, txbuf, n_tx: 2, rxbuf: val, n_rx: len); |
51 | } |
52 | |
53 | static inline int b53_spi_clear_status(struct spi_device *spi) |
54 | { |
55 | unsigned int i; |
56 | u8 rxbuf; |
57 | int ret; |
58 | |
59 | for (i = 0; i < 10; i++) { |
60 | ret = b53_spi_read_reg(spi, B53_SPI_STATUS, val: &rxbuf, len: 1); |
61 | if (ret) |
62 | return ret; |
63 | |
64 | if (!(rxbuf & B53_SPI_CMD_SPIF)) |
65 | break; |
66 | |
67 | mdelay(1); |
68 | } |
69 | |
70 | if (i == 10) |
71 | return -EIO; |
72 | |
73 | return 0; |
74 | } |
75 | |
76 | static inline int b53_spi_set_page(struct spi_device *spi, u8 page) |
77 | { |
78 | u8 txbuf[3]; |
79 | |
80 | txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; |
81 | txbuf[1] = B53_SPI_PAGE_SELECT; |
82 | txbuf[2] = page; |
83 | |
84 | return spi_write(spi, buf: txbuf, len: sizeof(txbuf)); |
85 | } |
86 | |
87 | static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page) |
88 | { |
89 | int ret = b53_spi_clear_status(spi); |
90 | |
91 | if (ret) |
92 | return ret; |
93 | |
94 | return b53_spi_set_page(spi, page); |
95 | } |
96 | |
97 | static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg) |
98 | { |
99 | u8 rxbuf; |
100 | int retry_count; |
101 | int ret; |
102 | |
103 | ret = b53_spi_read_reg(spi, reg, val: &rxbuf, len: 1); |
104 | if (ret) |
105 | return ret; |
106 | |
107 | for (retry_count = 0; retry_count < 10; retry_count++) { |
108 | ret = b53_spi_read_reg(spi, B53_SPI_STATUS, val: &rxbuf, len: 1); |
109 | if (ret) |
110 | return ret; |
111 | |
112 | if (rxbuf & B53_SPI_CMD_RACK) |
113 | break; |
114 | |
115 | mdelay(1); |
116 | } |
117 | |
118 | if (retry_count == 10) |
119 | return -EIO; |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data, |
125 | unsigned int len) |
126 | { |
127 | struct spi_device *spi = dev->priv; |
128 | int ret; |
129 | |
130 | ret = b53_prepare_reg_access(spi, page); |
131 | if (ret) |
132 | return ret; |
133 | |
134 | ret = b53_spi_prepare_reg_read(spi, reg); |
135 | if (ret) |
136 | return ret; |
137 | |
138 | return b53_spi_read_reg(spi, B53_SPI_DATA, val: data, len); |
139 | } |
140 | |
141 | static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) |
142 | { |
143 | return b53_spi_read(dev, page, reg, data: val, len: 1); |
144 | } |
145 | |
146 | static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) |
147 | { |
148 | __le16 value; |
149 | int ret; |
150 | |
151 | ret = b53_spi_read(dev, page, reg, data: (u8 *)&value, len: 2); |
152 | |
153 | if (!ret) |
154 | *val = le16_to_cpu(value); |
155 | |
156 | return ret; |
157 | } |
158 | |
159 | static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) |
160 | { |
161 | __le32 value; |
162 | int ret; |
163 | |
164 | ret = b53_spi_read(dev, page, reg, data: (u8 *)&value, len: 4); |
165 | |
166 | if (!ret) |
167 | *val = le32_to_cpu(value); |
168 | |
169 | return ret; |
170 | } |
171 | |
172 | static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) |
173 | { |
174 | __le64 value; |
175 | int ret; |
176 | |
177 | *val = 0; |
178 | ret = b53_spi_read(dev, page, reg, data: (u8 *)&value, len: 6); |
179 | if (!ret) |
180 | *val = le64_to_cpu(value); |
181 | |
182 | return ret; |
183 | } |
184 | |
185 | static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) |
186 | { |
187 | __le64 value; |
188 | int ret; |
189 | |
190 | ret = b53_spi_read(dev, page, reg, data: (u8 *)&value, len: 8); |
191 | |
192 | if (!ret) |
193 | *val = le64_to_cpu(value); |
194 | |
195 | return ret; |
196 | } |
197 | |
198 | static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) |
199 | { |
200 | struct spi_device *spi = dev->priv; |
201 | int ret; |
202 | u8 txbuf[3]; |
203 | |
204 | ret = b53_prepare_reg_access(spi, page); |
205 | if (ret) |
206 | return ret; |
207 | |
208 | txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; |
209 | txbuf[1] = reg; |
210 | txbuf[2] = value; |
211 | |
212 | return spi_write(spi, buf: txbuf, len: sizeof(txbuf)); |
213 | } |
214 | |
215 | static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value) |
216 | { |
217 | struct spi_device *spi = dev->priv; |
218 | int ret; |
219 | u8 txbuf[4]; |
220 | |
221 | ret = b53_prepare_reg_access(spi, page); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; |
226 | txbuf[1] = reg; |
227 | put_unaligned_le16(val: value, p: &txbuf[2]); |
228 | |
229 | return spi_write(spi, buf: txbuf, len: sizeof(txbuf)); |
230 | } |
231 | |
232 | static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value) |
233 | { |
234 | struct spi_device *spi = dev->priv; |
235 | int ret; |
236 | u8 txbuf[6]; |
237 | |
238 | ret = b53_prepare_reg_access(spi, page); |
239 | if (ret) |
240 | return ret; |
241 | |
242 | txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; |
243 | txbuf[1] = reg; |
244 | put_unaligned_le32(val: value, p: &txbuf[2]); |
245 | |
246 | return spi_write(spi, buf: txbuf, len: sizeof(txbuf)); |
247 | } |
248 | |
249 | static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value) |
250 | { |
251 | struct spi_device *spi = dev->priv; |
252 | int ret; |
253 | u8 txbuf[10]; |
254 | |
255 | ret = b53_prepare_reg_access(spi, page); |
256 | if (ret) |
257 | return ret; |
258 | |
259 | txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; |
260 | txbuf[1] = reg; |
261 | put_unaligned_le64(val: value, p: &txbuf[2]); |
262 | |
263 | return spi_write(spi, buf: txbuf, len: sizeof(txbuf) - 2); |
264 | } |
265 | |
266 | static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value) |
267 | { |
268 | struct spi_device *spi = dev->priv; |
269 | int ret; |
270 | u8 txbuf[10]; |
271 | |
272 | ret = b53_prepare_reg_access(spi, page); |
273 | if (ret) |
274 | return ret; |
275 | |
276 | txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; |
277 | txbuf[1] = reg; |
278 | put_unaligned_le64(val: value, p: &txbuf[2]); |
279 | |
280 | return spi_write(spi, buf: txbuf, len: sizeof(txbuf)); |
281 | } |
282 | |
283 | static const struct b53_io_ops b53_spi_ops = { |
284 | .read8 = b53_spi_read8, |
285 | .read16 = b53_spi_read16, |
286 | .read32 = b53_spi_read32, |
287 | .read48 = b53_spi_read48, |
288 | .read64 = b53_spi_read64, |
289 | .write8 = b53_spi_write8, |
290 | .write16 = b53_spi_write16, |
291 | .write32 = b53_spi_write32, |
292 | .write48 = b53_spi_write48, |
293 | .write64 = b53_spi_write64, |
294 | }; |
295 | |
296 | static int b53_spi_probe(struct spi_device *spi) |
297 | { |
298 | struct b53_device *dev; |
299 | int ret; |
300 | |
301 | dev = b53_switch_alloc(base: &spi->dev, ops: &b53_spi_ops, priv: spi); |
302 | if (!dev) |
303 | return -ENOMEM; |
304 | |
305 | if (spi->dev.platform_data) |
306 | dev->pdata = spi->dev.platform_data; |
307 | |
308 | ret = b53_switch_register(dev); |
309 | if (ret) |
310 | return ret; |
311 | |
312 | spi_set_drvdata(spi, data: dev); |
313 | |
314 | return 0; |
315 | } |
316 | |
317 | static void b53_spi_remove(struct spi_device *spi) |
318 | { |
319 | struct b53_device *dev = spi_get_drvdata(spi); |
320 | |
321 | if (dev) |
322 | b53_switch_remove(dev); |
323 | } |
324 | |
325 | static void b53_spi_shutdown(struct spi_device *spi) |
326 | { |
327 | struct b53_device *dev = spi_get_drvdata(spi); |
328 | |
329 | if (dev) |
330 | b53_switch_shutdown(dev); |
331 | |
332 | spi_set_drvdata(spi, NULL); |
333 | } |
334 | |
335 | static const struct of_device_id b53_spi_of_match[] = { |
336 | { .compatible = "brcm,bcm5325" }, |
337 | { .compatible = "brcm,bcm5365" }, |
338 | { .compatible = "brcm,bcm5395" }, |
339 | { .compatible = "brcm,bcm5397" }, |
340 | { .compatible = "brcm,bcm5398" }, |
341 | { .compatible = "brcm,bcm53115" }, |
342 | { .compatible = "brcm,bcm53125" }, |
343 | { .compatible = "brcm,bcm53128" }, |
344 | { /* sentinel */ } |
345 | }; |
346 | MODULE_DEVICE_TABLE(of, b53_spi_of_match); |
347 | |
348 | static const struct spi_device_id b53_spi_ids[] = { |
349 | { .name = "bcm5325" }, |
350 | { .name = "bcm5365" }, |
351 | { .name = "bcm5395" }, |
352 | { .name = "bcm5397" }, |
353 | { .name = "bcm5398" }, |
354 | { .name = "bcm53115" }, |
355 | { .name = "bcm53125" }, |
356 | { .name = "bcm53128" }, |
357 | { /* sentinel */ } |
358 | }; |
359 | MODULE_DEVICE_TABLE(spi, b53_spi_ids); |
360 | |
361 | static struct spi_driver b53_spi_driver = { |
362 | .driver = { |
363 | .name = "b53-switch" , |
364 | .of_match_table = b53_spi_of_match, |
365 | }, |
366 | .probe = b53_spi_probe, |
367 | .remove = b53_spi_remove, |
368 | .shutdown = b53_spi_shutdown, |
369 | .id_table = b53_spi_ids, |
370 | }; |
371 | |
372 | module_spi_driver(b53_spi_driver); |
373 | |
374 | MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>" ); |
375 | MODULE_DESCRIPTION("B53 SPI access driver" ); |
376 | MODULE_LICENSE("Dual BSD/GPL" ); |
377 | |