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 
octeon_spi_wait_ready(struct octeon_spi * p)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);
24 		mpi_sts.u64 = readq(p->register_base + OCTEON_SPI_STS(p));
25 	} while (mpi_sts.s.busy);
26 }
27 
octeon_spi_do_transfer(struct octeon_spi * p,struct spi_message * msg,struct spi_transfer * xfer,bool last_xfer)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, 0) < 4)
61 		p->cs_enax |= 1ull << (12 + spi_get_chipselect(spi, 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(mpi_cfg.u64, 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(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
79 		}
80 		mpi_tx.u64 = 0;
81 		mpi_tx.s.csid = spi_get_chipselect(spi, 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(mpi_tx.u64, 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(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(d, 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, 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(mpi_tx.u64, 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(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
119 			*rx_buf++ = (u8)v;
120 		}
121 
122 	spi_transfer_delay_exec(xfer);
123 
124 	return xfer->len;
125 }
126 
octeon_spi_transfer_one_message(struct spi_controller * ctlr,struct spi_message * msg)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(&xfer->transfer_list,
137 					      &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