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 | |
9 | #include <linux/spi/spi.h> |
10 | #include <linux/module.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/io.h> |
13 | |
14 | #include "spi-cavium.h" |
15 | |
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(loops: 500); |
24 | mpi_sts.u64 = readq(addr: p->register_base + OCTEON_SPI_STS(p)); |
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 | { |
33 | struct spi_device *spi = msg->spi; |
34 | union cvmx_mpi_cfg mpi_cfg; |
35 | union cvmx_mpi_tx mpi_tx; |
36 | unsigned int clkdiv; |
37 | int mode; |
38 | bool cpha, cpol; |
39 | const u8 *tx_buf; |
40 | u8 *rx_buf; |
41 | int len; |
42 | int i; |
43 | |
44 | mode = spi->mode; |
45 | cpha = mode & SPI_CPHA; |
46 | cpol = mode & SPI_CPOL; |
47 | |
48 | clkdiv = p->sys_freq / (2 * xfer->speed_hz); |
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 | |
60 | if (spi_get_chipselect(spi, idx: 0) < 4) |
61 | p->cs_enax |= 1ull << (12 + spi_get_chipselect(spi, idx: 0)); |
62 | mpi_cfg.u64 |= p->cs_enax; |
63 | |
64 | if (mpi_cfg.u64 != p->last_cfg) { |
65 | p->last_cfg = mpi_cfg.u64; |
66 | writeq(val: mpi_cfg.u64, addr: p->register_base + OCTEON_SPI_CFG(p)); |
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; |
78 | writeq(val: d, addr: p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
79 | } |
80 | mpi_tx.u64 = 0; |
81 | mpi_tx.s.csid = spi_get_chipselect(spi, idx: 0); |
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; |
85 | writeq(val: mpi_tx.u64, addr: p->register_base + OCTEON_SPI_TX(p)); |
86 | |
87 | octeon_spi_wait_ready(p); |
88 | if (rx_buf) |
89 | for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) { |
90 | u64 v = readq(addr: p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
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; |
102 | writeq(val: d, addr: p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
103 | } |
104 | |
105 | mpi_tx.u64 = 0; |
106 | mpi_tx.s.csid = spi_get_chipselect(spi, idx: 0); |
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; |
113 | writeq(val: mpi_tx.u64, addr: p->register_base + OCTEON_SPI_TX(p)); |
114 | |
115 | octeon_spi_wait_ready(p); |
116 | if (rx_buf) |
117 | for (i = 0; i < len; i++) { |
118 | u64 v = readq(addr: p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); |
119 | *rx_buf++ = (u8)v; |
120 | } |
121 | |
122 | spi_transfer_delay_exec(t: xfer); |
123 | |
124 | return xfer->len; |
125 | } |
126 | |
127 | int octeon_spi_transfer_one_message(struct spi_controller *ctlr, |
128 | struct spi_message *msg) |
129 | { |
130 | struct octeon_spi *p = spi_controller_get_devdata(ctlr); |
131 | unsigned int total_len = 0; |
132 | int status = 0; |
133 | struct spi_transfer *xfer; |
134 | |
135 | list_for_each_entry(xfer, &msg->transfers, transfer_list) { |
136 | bool last_xfer = list_is_last(list: &xfer->transfer_list, |
137 | head: &msg->transfers); |
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(ctlr); |
149 | return status; |
150 | } |
151 | |