Commit | Line | Data |
---|---|---|
6b52c00f DD |
1 | /* |
2 | * This file is subject to the terms and conditions of the GNU General Public | |
3 | * License. See the file "COPYING" in the main directory of this archive | |
4 | * for more details. | |
5 | * | |
6 | * Copyright (C) 2011, 2012 Cavium, Inc. | |
7 | */ | |
8 | ||
6b52c00f DD |
9 | #include <linux/spi/spi.h> |
10 | #include <linux/module.h> | |
11 | #include <linux/delay.h> | |
6b52c00f | 12 | #include <linux/io.h> |
22cc1b6b JG |
13 | |
14 | #include "spi-cavium.h" | |
6b52c00f | 15 | |
6b52c00f DD |
16 | static void octeon_spi_wait_ready(struct octeon_spi *p) |
17 | { | |
18 | union cvmx_mpi_sts mpi_sts; | |
19 | unsigned int loops = 0; | |
20 | ||
21 | do { | |
22 | if (loops++) | |
23 | __delay(500); | |
ee423c53 | 24 | mpi_sts.u64 = readq(p->register_base + OCTEON_SPI_STS(p)); |
6b52c00f DD |
25 | } while (mpi_sts.s.busy); |
26 | } | |
27 | ||
28 | static int octeon_spi_do_transfer(struct octeon_spi *p, | |
29 | struct spi_message *msg, | |
30 | struct spi_transfer *xfer, | |
31 | bool last_xfer) | |
32 | { | |
85fe414d | 33 | struct spi_device *spi = msg->spi; |
6b52c00f DD |
34 | union cvmx_mpi_cfg mpi_cfg; |
35 | union cvmx_mpi_tx mpi_tx; | |
36 | unsigned int clkdiv; | |
6b52c00f DD |
37 | int mode; |
38 | bool cpha, cpol; | |
6b52c00f DD |
39 | const u8 *tx_buf; |
40 | u8 *rx_buf; | |
41 | int len; | |
42 | int i; | |
43 | ||
85fe414d | 44 | mode = spi->mode; |
6b52c00f DD |
45 | cpha = mode & SPI_CPHA; |
46 | cpol = mode & SPI_CPOL; | |
6b52c00f | 47 | |
b9e64763 | 48 | clkdiv = p->sys_freq / (2 * xfer->speed_hz); |
6b52c00f DD |
49 | |
50 | mpi_cfg.u64 = 0; | |
51 | ||
52 | mpi_cfg.s.clkdiv = clkdiv; | |
53 | mpi_cfg.s.cshi = (mode & SPI_CS_HIGH) ? 1 : 0; | |
54 | mpi_cfg.s.lsbfirst = (mode & SPI_LSB_FIRST) ? 1 : 0; | |
55 | mpi_cfg.s.wireor = (mode & SPI_3WIRE) ? 1 : 0; | |
56 | mpi_cfg.s.idlelo = cpha != cpol; | |
57 | mpi_cfg.s.cslate = cpha ? 1 : 0; | |
58 | mpi_cfg.s.enable = 1; | |
59 | ||
9e264f3f AKMA |
60 | if (spi_get_chipselect(spi, 0) < 4) |
61 | p->cs_enax |= 1ull << (12 + spi_get_chipselect(spi, 0)); | |
6b52c00f DD |
62 | mpi_cfg.u64 |= p->cs_enax; |
63 | ||
64 | if (mpi_cfg.u64 != p->last_cfg) { | |
65 | p->last_cfg = mpi_cfg.u64; | |
ee423c53 | 66 | writeq(mpi_cfg.u64, p->register_base + OCTEON_SPI_CFG(p)); |
6b52c00f DD |
67 | } |
68 | tx_buf = xfer->tx_buf; | |
69 | rx_buf = xfer->rx_buf; | |
70 | len = xfer->len; | |
71 | while (len > OCTEON_SPI_MAX_BYTES) { | |
72 | for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) { | |
73 | u8 d; | |
74 | if (tx_buf) | |
75 | d = *tx_buf++; | |
76 | else | |
77 | d = 0; | |
ee423c53 | 78 | writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
6b52c00f DD |
79 | } |
80 | mpi_tx.u64 = 0; | |
9e264f3f | 81 | mpi_tx.s.csid = spi_get_chipselect(spi, 0); |
6b52c00f DD |
82 | mpi_tx.s.leavecs = 1; |
83 | mpi_tx.s.txnum = tx_buf ? OCTEON_SPI_MAX_BYTES : 0; | |
84 | mpi_tx.s.totnum = OCTEON_SPI_MAX_BYTES; | |
ee423c53 | 85 | writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p)); |
6b52c00f DD |
86 | |
87 | octeon_spi_wait_ready(p); | |
88 | if (rx_buf) | |
89 | for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) { | |
ee423c53 | 90 | u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
6b52c00f DD |
91 | *rx_buf++ = (u8)v; |
92 | } | |
93 | len -= OCTEON_SPI_MAX_BYTES; | |
94 | } | |
95 | ||
96 | for (i = 0; i < len; i++) { | |
97 | u8 d; | |
98 | if (tx_buf) | |
99 | d = *tx_buf++; | |
100 | else | |
101 | d = 0; | |
ee423c53 | 102 | writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
6b52c00f DD |
103 | } |
104 | ||
105 | mpi_tx.u64 = 0; | |
9e264f3f | 106 | mpi_tx.s.csid = spi_get_chipselect(spi, 0); |
6b52c00f DD |
107 | if (last_xfer) |
108 | mpi_tx.s.leavecs = xfer->cs_change; | |
109 | else | |
110 | mpi_tx.s.leavecs = !xfer->cs_change; | |
111 | mpi_tx.s.txnum = tx_buf ? len : 0; | |
112 | mpi_tx.s.totnum = len; | |
ee423c53 | 113 | writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p)); |
6b52c00f DD |
114 | |
115 | octeon_spi_wait_ready(p); | |
116 | if (rx_buf) | |
117 | for (i = 0; i < len; i++) { | |
ee423c53 | 118 | u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
6b52c00f DD |
119 | *rx_buf++ = (u8)v; |
120 | } | |
121 | ||
e74dc5c7 | 122 | spi_transfer_delay_exec(xfer); |
6b52c00f DD |
123 | |
124 | return xfer->len; | |
125 | } | |
126 | ||
63d49afe JG |
127 | int octeon_spi_transfer_one_message(struct spi_master *master, |
128 | struct spi_message *msg) | |
6b52c00f DD |
129 | { |
130 | struct octeon_spi *p = spi_master_get_devdata(master); | |
131 | unsigned int total_len = 0; | |
132 | int status = 0; | |
133 | struct spi_transfer *xfer; | |
134 | ||
6b52c00f | 135 | list_for_each_entry(xfer, &msg->transfers, transfer_list) { |
0a4e210e AL |
136 | bool last_xfer = list_is_last(&xfer->transfer_list, |
137 | &msg->transfers); | |
6b52c00f DD |
138 | int r = octeon_spi_do_transfer(p, msg, xfer, last_xfer); |
139 | if (r < 0) { | |
140 | status = r; | |
141 | goto err; | |
142 | } | |
143 | total_len += r; | |
144 | } | |
145 | err: | |
146 | msg->status = status; | |
147 | msg->actual_length = total_len; | |
148 | spi_finalize_current_message(master); | |
149 | return status; | |
150 | } |