xref: /openbmc/linux/drivers/spi/spi-fsi.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1bbb6b2f9SEddie James // SPDX-License-Identifier: GPL-2.0-or-later
2bbb6b2f9SEddie James // Copyright (C) IBM Corporation 2020
3bbb6b2f9SEddie James 
4bbb6b2f9SEddie James #include <linux/bitfield.h>
5bbb6b2f9SEddie James #include <linux/bits.h>
6bbb6b2f9SEddie James #include <linux/fsi.h>
7bbb6b2f9SEddie James #include <linux/jiffies.h>
8bbb6b2f9SEddie James #include <linux/kernel.h>
9bbb6b2f9SEddie James #include <linux/module.h>
10bbb6b2f9SEddie James #include <linux/of.h>
11bbb6b2f9SEddie James #include <linux/spi/spi.h>
12bbb6b2f9SEddie James 
13bbb6b2f9SEddie James #define FSI_ENGID_SPI			0x23
14bbb6b2f9SEddie James #define FSI_MBOX_ROOT_CTRL_8		0x2860
159211a441SEddie James #define  FSI_MBOX_ROOT_CTRL_8_SPI_MUX	 0xf0000000
16bbb6b2f9SEddie James 
17bbb6b2f9SEddie James #define FSI2SPI_DATA0			0x00
18bbb6b2f9SEddie James #define FSI2SPI_DATA1			0x04
19bbb6b2f9SEddie James #define FSI2SPI_CMD			0x08
20bbb6b2f9SEddie James #define  FSI2SPI_CMD_WRITE		 BIT(31)
21bbb6b2f9SEddie James #define FSI2SPI_RESET			0x18
22bbb6b2f9SEddie James #define FSI2SPI_STATUS			0x1c
23bbb6b2f9SEddie James #define  FSI2SPI_STATUS_ANY_ERROR	 BIT(31)
24bbb6b2f9SEddie James #define FSI2SPI_IRQ			0x20
25bbb6b2f9SEddie James 
26bbb6b2f9SEddie James #define SPI_FSI_BASE			0x70000
2740308f96SEddie James #define SPI_FSI_TIMEOUT_MS		1000
2834d34a56SEddie James #define SPI_FSI_MAX_RX_SIZE		8
2934d34a56SEddie James #define SPI_FSI_MAX_TX_SIZE		40
30bbb6b2f9SEddie James 
31bbb6b2f9SEddie James #define SPI_FSI_ERROR			0x0
32bbb6b2f9SEddie James #define SPI_FSI_COUNTER_CFG		0x1
33bbb6b2f9SEddie James #define SPI_FSI_CFG1			0x2
34bbb6b2f9SEddie James #define SPI_FSI_CLOCK_CFG		0x3
35bbb6b2f9SEddie James #define  SPI_FSI_CLOCK_CFG_MM_ENABLE	 BIT_ULL(32)
36bbb6b2f9SEddie James #define  SPI_FSI_CLOCK_CFG_ECC_DISABLE	 (BIT_ULL(35) | BIT_ULL(33))
37bbb6b2f9SEddie James #define  SPI_FSI_CLOCK_CFG_RESET1	 (BIT_ULL(36) | BIT_ULL(38))
38bbb6b2f9SEddie James #define  SPI_FSI_CLOCK_CFG_RESET2	 (BIT_ULL(37) | BIT_ULL(39))
39bbb6b2f9SEddie James #define  SPI_FSI_CLOCK_CFG_MODE		 (BIT_ULL(41) | BIT_ULL(42))
40bbb6b2f9SEddie James #define  SPI_FSI_CLOCK_CFG_SCK_RECV_DEL	 GENMASK_ULL(51, 44)
41bbb6b2f9SEddie James #define   SPI_FSI_CLOCK_CFG_SCK_NO_DEL	  BIT_ULL(51)
42bbb6b2f9SEddie James #define  SPI_FSI_CLOCK_CFG_SCK_DIV	 GENMASK_ULL(63, 52)
43bbb6b2f9SEddie James #define SPI_FSI_MMAP			0x4
44bbb6b2f9SEddie James #define SPI_FSI_DATA_TX			0x5
45bbb6b2f9SEddie James #define SPI_FSI_DATA_RX			0x6
46bbb6b2f9SEddie James #define SPI_FSI_SEQUENCE		0x7
47bbb6b2f9SEddie James #define  SPI_FSI_SEQUENCE_STOP		 0x00
48bbb6b2f9SEddie James #define  SPI_FSI_SEQUENCE_SEL_SLAVE(x)	 (0x10 | ((x) & 0xf))
49bbb6b2f9SEddie James #define  SPI_FSI_SEQUENCE_SHIFT_OUT(x)	 (0x30 | ((x) & 0xf))
50bbb6b2f9SEddie James #define  SPI_FSI_SEQUENCE_SHIFT_IN(x)	 (0x40 | ((x) & 0xf))
51bbb6b2f9SEddie James #define  SPI_FSI_SEQUENCE_COPY_DATA_TX	 0xc0
52bbb6b2f9SEddie James #define  SPI_FSI_SEQUENCE_BRANCH(x)	 (0xe0 | ((x) & 0xf))
53bbb6b2f9SEddie James #define SPI_FSI_STATUS			0x8
54bbb6b2f9SEddie James #define  SPI_FSI_STATUS_ERROR		 \
55bbb6b2f9SEddie James 	(GENMASK_ULL(31, 21) | GENMASK_ULL(15, 12))
56bbb6b2f9SEddie James #define  SPI_FSI_STATUS_SEQ_STATE	 GENMASK_ULL(55, 48)
57bbb6b2f9SEddie James #define   SPI_FSI_STATUS_SEQ_STATE_IDLE	  BIT_ULL(48)
58bbb6b2f9SEddie James #define  SPI_FSI_STATUS_TDR_UNDERRUN	 BIT_ULL(57)
59bbb6b2f9SEddie James #define  SPI_FSI_STATUS_TDR_OVERRUN	 BIT_ULL(58)
60bbb6b2f9SEddie James #define  SPI_FSI_STATUS_TDR_FULL	 BIT_ULL(59)
61bbb6b2f9SEddie James #define  SPI_FSI_STATUS_RDR_UNDERRUN	 BIT_ULL(61)
62bbb6b2f9SEddie James #define  SPI_FSI_STATUS_RDR_OVERRUN	 BIT_ULL(62)
63bbb6b2f9SEddie James #define  SPI_FSI_STATUS_RDR_FULL	 BIT_ULL(63)
64bbb6b2f9SEddie James #define  SPI_FSI_STATUS_ANY_ERROR	 \
657909eebbSBrad Bishop 	(SPI_FSI_STATUS_ERROR | \
66bbb6b2f9SEddie James 	 SPI_FSI_STATUS_TDR_OVERRUN | SPI_FSI_STATUS_RDR_UNDERRUN | \
67bbb6b2f9SEddie James 	 SPI_FSI_STATUS_RDR_OVERRUN)
68bbb6b2f9SEddie James #define SPI_FSI_PORT_CTRL		0x9
69bbb6b2f9SEddie James 
70e954af13SEddie James struct fsi2spi {
71e954af13SEddie James 	struct fsi_device *fsi; /* FSI2SPI CFAM engine device */
72e954af13SEddie James 	struct mutex lock; /* lock access to the device */
73e954af13SEddie James };
74e954af13SEddie James 
75bbb6b2f9SEddie James struct fsi_spi {
76bbb6b2f9SEddie James 	struct device *dev;	/* SPI controller device */
77e954af13SEddie James 	struct fsi2spi *bridge; /* FSI2SPI device */
78bbb6b2f9SEddie James 	u32 base;
79bbb6b2f9SEddie James };
80bbb6b2f9SEddie James 
81bbb6b2f9SEddie James struct fsi_spi_sequence {
82bbb6b2f9SEddie James 	int bit;
83bbb6b2f9SEddie James 	u64 data;
84bbb6b2f9SEddie James };
85bbb6b2f9SEddie James 
fsi_spi_check_mux(struct fsi_device * fsi,struct device * dev)869211a441SEddie James static int fsi_spi_check_mux(struct fsi_device *fsi, struct device *dev)
879211a441SEddie James {
889211a441SEddie James 	int rc;
899211a441SEddie James 	u32 root_ctrl_8;
909211a441SEddie James 	__be32 root_ctrl_8_be;
919211a441SEddie James 
929211a441SEddie James 	rc = fsi_slave_read(fsi->slave, FSI_MBOX_ROOT_CTRL_8, &root_ctrl_8_be,
939211a441SEddie James 			    sizeof(root_ctrl_8_be));
949211a441SEddie James 	if (rc)
959211a441SEddie James 		return rc;
969211a441SEddie James 
979211a441SEddie James 	root_ctrl_8 = be32_to_cpu(root_ctrl_8_be);
989211a441SEddie James 	dev_dbg(dev, "Root control register 8: %08x\n", root_ctrl_8);
999211a441SEddie James 	if ((root_ctrl_8 & FSI_MBOX_ROOT_CTRL_8_SPI_MUX) ==
1009211a441SEddie James 	     FSI_MBOX_ROOT_CTRL_8_SPI_MUX)
1019211a441SEddie James 		return 0;
1029211a441SEddie James 
1039211a441SEddie James 	return -ENOLINK;
1049211a441SEddie James }
1059211a441SEddie James 
fsi_spi_check_status(struct fsi_spi * ctx)106bbb6b2f9SEddie James static int fsi_spi_check_status(struct fsi_spi *ctx)
107bbb6b2f9SEddie James {
108bbb6b2f9SEddie James 	int rc;
109bbb6b2f9SEddie James 	u32 sts;
110bbb6b2f9SEddie James 	__be32 sts_be;
111bbb6b2f9SEddie James 
112e954af13SEddie James 	rc = fsi_device_read(ctx->bridge->fsi, FSI2SPI_STATUS, &sts_be,
113bbb6b2f9SEddie James 			     sizeof(sts_be));
114bbb6b2f9SEddie James 	if (rc)
115bbb6b2f9SEddie James 		return rc;
116bbb6b2f9SEddie James 
117bbb6b2f9SEddie James 	sts = be32_to_cpu(sts_be);
118bbb6b2f9SEddie James 	if (sts & FSI2SPI_STATUS_ANY_ERROR) {
119bbb6b2f9SEddie James 		dev_err(ctx->dev, "Error with FSI2SPI interface: %08x.\n", sts);
120bbb6b2f9SEddie James 		return -EIO;
121bbb6b2f9SEddie James 	}
122bbb6b2f9SEddie James 
123bbb6b2f9SEddie James 	return 0;
124bbb6b2f9SEddie James }
125bbb6b2f9SEddie James 
fsi_spi_read_reg(struct fsi_spi * ctx,u32 offset,u64 * value)126bbb6b2f9SEddie James static int fsi_spi_read_reg(struct fsi_spi *ctx, u32 offset, u64 *value)
127bbb6b2f9SEddie James {
128e954af13SEddie James 	int rc = 0;
129bbb6b2f9SEddie James 	__be32 cmd_be;
130bbb6b2f9SEddie James 	__be32 data_be;
131bbb6b2f9SEddie James 	u32 cmd = offset + ctx->base;
132e954af13SEddie James 	struct fsi2spi *bridge = ctx->bridge;
133bbb6b2f9SEddie James 
134bbb6b2f9SEddie James 	*value = 0ULL;
135bbb6b2f9SEddie James 
136bbb6b2f9SEddie James 	if (cmd & FSI2SPI_CMD_WRITE)
137bbb6b2f9SEddie James 		return -EINVAL;
138bbb6b2f9SEddie James 
139e954af13SEddie James 	rc = mutex_lock_interruptible(&bridge->lock);
140bbb6b2f9SEddie James 	if (rc)
141bbb6b2f9SEddie James 		return rc;
142bbb6b2f9SEddie James 
143e954af13SEddie James 	cmd_be = cpu_to_be32(cmd);
144e954af13SEddie James 	rc = fsi_device_write(bridge->fsi, FSI2SPI_CMD, &cmd_be,
145e954af13SEddie James 			      sizeof(cmd_be));
146e954af13SEddie James 	if (rc)
147e954af13SEddie James 		goto unlock;
148e954af13SEddie James 
149bbb6b2f9SEddie James 	rc = fsi_spi_check_status(ctx);
150bbb6b2f9SEddie James 	if (rc)
151e954af13SEddie James 		goto unlock;
152bbb6b2f9SEddie James 
153e954af13SEddie James 	rc = fsi_device_read(bridge->fsi, FSI2SPI_DATA0, &data_be,
154bbb6b2f9SEddie James 			     sizeof(data_be));
155bbb6b2f9SEddie James 	if (rc)
156e954af13SEddie James 		goto unlock;
157bbb6b2f9SEddie James 
158bbb6b2f9SEddie James 	*value |= (u64)be32_to_cpu(data_be) << 32;
159bbb6b2f9SEddie James 
160e954af13SEddie James 	rc = fsi_device_read(bridge->fsi, FSI2SPI_DATA1, &data_be,
161bbb6b2f9SEddie James 			     sizeof(data_be));
162bbb6b2f9SEddie James 	if (rc)
163e954af13SEddie James 		goto unlock;
164bbb6b2f9SEddie James 
165bbb6b2f9SEddie James 	*value |= (u64)be32_to_cpu(data_be);
166bbb6b2f9SEddie James 	dev_dbg(ctx->dev, "Read %02x[%016llx].\n", offset, *value);
167bbb6b2f9SEddie James 
168e954af13SEddie James unlock:
169e954af13SEddie James 	mutex_unlock(&bridge->lock);
170e954af13SEddie James 	return rc;
171bbb6b2f9SEddie James }
172bbb6b2f9SEddie James 
fsi_spi_write_reg(struct fsi_spi * ctx,u32 offset,u64 value)173bbb6b2f9SEddie James static int fsi_spi_write_reg(struct fsi_spi *ctx, u32 offset, u64 value)
174bbb6b2f9SEddie James {
175e954af13SEddie James 	int rc = 0;
176bbb6b2f9SEddie James 	__be32 cmd_be;
177bbb6b2f9SEddie James 	__be32 data_be;
178bbb6b2f9SEddie James 	u32 cmd = offset + ctx->base;
179e954af13SEddie James 	struct fsi2spi *bridge = ctx->bridge;
180bbb6b2f9SEddie James 
181bbb6b2f9SEddie James 	if (cmd & FSI2SPI_CMD_WRITE)
182bbb6b2f9SEddie James 		return -EINVAL;
183bbb6b2f9SEddie James 
184e954af13SEddie James 	rc = mutex_lock_interruptible(&bridge->lock);
185e954af13SEddie James 	if (rc)
186e954af13SEddie James 		return rc;
187e954af13SEddie James 
188bbb6b2f9SEddie James 	dev_dbg(ctx->dev, "Write %02x[%016llx].\n", offset, value);
189bbb6b2f9SEddie James 
190bbb6b2f9SEddie James 	data_be = cpu_to_be32(upper_32_bits(value));
191e954af13SEddie James 	rc = fsi_device_write(bridge->fsi, FSI2SPI_DATA0, &data_be,
192bbb6b2f9SEddie James 			      sizeof(data_be));
193bbb6b2f9SEddie James 	if (rc)
194e954af13SEddie James 		goto unlock;
195bbb6b2f9SEddie James 
196bbb6b2f9SEddie James 	data_be = cpu_to_be32(lower_32_bits(value));
197e954af13SEddie James 	rc = fsi_device_write(bridge->fsi, FSI2SPI_DATA1, &data_be,
198bbb6b2f9SEddie James 			      sizeof(data_be));
199bbb6b2f9SEddie James 	if (rc)
200e954af13SEddie James 		goto unlock;
201bbb6b2f9SEddie James 
202bbb6b2f9SEddie James 	cmd_be = cpu_to_be32(cmd | FSI2SPI_CMD_WRITE);
203e954af13SEddie James 	rc = fsi_device_write(bridge->fsi, FSI2SPI_CMD, &cmd_be,
204e954af13SEddie James 			      sizeof(cmd_be));
205bbb6b2f9SEddie James 	if (rc)
206e954af13SEddie James 		goto unlock;
207bbb6b2f9SEddie James 
208e954af13SEddie James 	rc = fsi_spi_check_status(ctx);
209e954af13SEddie James 
210e954af13SEddie James unlock:
211e954af13SEddie James 	mutex_unlock(&bridge->lock);
212e954af13SEddie James 	return rc;
213bbb6b2f9SEddie James }
214bbb6b2f9SEddie James 
fsi_spi_data_in(u64 in,u8 * rx,int len)215bbb6b2f9SEddie James static int fsi_spi_data_in(u64 in, u8 *rx, int len)
216bbb6b2f9SEddie James {
217bbb6b2f9SEddie James 	int i;
218bbb6b2f9SEddie James 	int num_bytes = min(len, 8);
219bbb6b2f9SEddie James 
220bbb6b2f9SEddie James 	for (i = 0; i < num_bytes; ++i)
221bbb6b2f9SEddie James 		rx[i] = (u8)(in >> (8 * ((num_bytes - 1) - i)));
222bbb6b2f9SEddie James 
223bbb6b2f9SEddie James 	return num_bytes;
224bbb6b2f9SEddie James }
225bbb6b2f9SEddie James 
fsi_spi_data_out(u64 * out,const u8 * tx,int len)226bbb6b2f9SEddie James static int fsi_spi_data_out(u64 *out, const u8 *tx, int len)
227bbb6b2f9SEddie James {
228bbb6b2f9SEddie James 	int i;
229bbb6b2f9SEddie James 	int num_bytes = min(len, 8);
230bbb6b2f9SEddie James 	u8 *out_bytes = (u8 *)out;
231bbb6b2f9SEddie James 
232bbb6b2f9SEddie James 	/* Unused bytes of the tx data should be 0. */
233bbb6b2f9SEddie James 	*out = 0ULL;
234bbb6b2f9SEddie James 
235bbb6b2f9SEddie James 	for (i = 0; i < num_bytes; ++i)
236bbb6b2f9SEddie James 		out_bytes[8 - (i + 1)] = tx[i];
237bbb6b2f9SEddie James 
238bbb6b2f9SEddie James 	return num_bytes;
239bbb6b2f9SEddie James }
240bbb6b2f9SEddie James 
fsi_spi_reset(struct fsi_spi * ctx)241bbb6b2f9SEddie James static int fsi_spi_reset(struct fsi_spi *ctx)
242bbb6b2f9SEddie James {
243bbb6b2f9SEddie James 	int rc;
244bbb6b2f9SEddie James 
245bbb6b2f9SEddie James 	dev_dbg(ctx->dev, "Resetting SPI controller.\n");
246bbb6b2f9SEddie James 
247bbb6b2f9SEddie James 	rc = fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG,
248bbb6b2f9SEddie James 			       SPI_FSI_CLOCK_CFG_RESET1);
249bbb6b2f9SEddie James 	if (rc)
250bbb6b2f9SEddie James 		return rc;
251bbb6b2f9SEddie James 
25249c9fc1dSEddie James 	rc = fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG,
253bbb6b2f9SEddie James 			       SPI_FSI_CLOCK_CFG_RESET2);
25449c9fc1dSEddie James 	if (rc)
25549c9fc1dSEddie James 		return rc;
25649c9fc1dSEddie James 
25749c9fc1dSEddie James 	return fsi_spi_write_reg(ctx, SPI_FSI_STATUS, 0ULL);
258bbb6b2f9SEddie James }
259bbb6b2f9SEddie James 
fsi_spi_status(struct fsi_spi * ctx,u64 * status,const char * dir)26048a78c66SEddie James static int fsi_spi_status(struct fsi_spi *ctx, u64 *status, const char *dir)
26148a78c66SEddie James {
26248a78c66SEddie James 	int rc = fsi_spi_read_reg(ctx, SPI_FSI_STATUS, status);
26348a78c66SEddie James 
26448a78c66SEddie James 	if (rc)
26548a78c66SEddie James 		return rc;
26648a78c66SEddie James 
26748a78c66SEddie James 	if (*status & SPI_FSI_STATUS_ANY_ERROR) {
268e954af13SEddie James 		dev_err(ctx->dev, "%s error: %016llx\n", dir, *status);
26948a78c66SEddie James 
27048a78c66SEddie James 		rc = fsi_spi_reset(ctx);
27148a78c66SEddie James 		if (rc)
27248a78c66SEddie James 			return rc;
27348a78c66SEddie James 
27448a78c66SEddie James 		return -EREMOTEIO;
27548a78c66SEddie James 	}
27648a78c66SEddie James 
27748a78c66SEddie James 	return 0;
27848a78c66SEddie James }
27948a78c66SEddie James 
fsi_spi_sequence_add(struct fsi_spi_sequence * seq,u8 val)28034d34a56SEddie James static void fsi_spi_sequence_add(struct fsi_spi_sequence *seq, u8 val)
281bbb6b2f9SEddie James {
282bbb6b2f9SEddie James 	/*
283bbb6b2f9SEddie James 	 * Add the next byte of instruction to the 8-byte sequence register.
284bbb6b2f9SEddie James 	 * Then decrement the counter so that the next instruction will go in
28549c9fc1dSEddie James 	 * the right place. Return the index of the slot we just filled in the
28649c9fc1dSEddie James 	 * sequence register.
287bbb6b2f9SEddie James 	 */
288bbb6b2f9SEddie James 	seq->data |= (u64)val << seq->bit;
289bbb6b2f9SEddie James 	seq->bit -= 8;
290bbb6b2f9SEddie James }
291bbb6b2f9SEddie James 
fsi_spi_sequence_init(struct fsi_spi_sequence * seq)292bbb6b2f9SEddie James static void fsi_spi_sequence_init(struct fsi_spi_sequence *seq)
293bbb6b2f9SEddie James {
294bbb6b2f9SEddie James 	seq->bit = 56;
295bbb6b2f9SEddie James 	seq->data = 0ULL;
296bbb6b2f9SEddie James }
297bbb6b2f9SEddie James 
fsi_spi_transfer_data(struct fsi_spi * ctx,struct spi_transfer * transfer)298bbb6b2f9SEddie James static int fsi_spi_transfer_data(struct fsi_spi *ctx,
299bbb6b2f9SEddie James 				 struct spi_transfer *transfer)
300bbb6b2f9SEddie James {
30140308f96SEddie James 	int loops;
302bbb6b2f9SEddie James 	int rc = 0;
30389b35e3fSEddie James 	unsigned long end;
304bbb6b2f9SEddie James 	u64 status = 0ULL;
305bbb6b2f9SEddie James 
306bbb6b2f9SEddie James 	if (transfer->tx_buf) {
307bbb6b2f9SEddie James 		int nb;
308bbb6b2f9SEddie James 		int sent = 0;
309bbb6b2f9SEddie James 		u64 out = 0ULL;
310bbb6b2f9SEddie James 		const u8 *tx = transfer->tx_buf;
311bbb6b2f9SEddie James 
312bbb6b2f9SEddie James 		while (transfer->len > sent) {
313bbb6b2f9SEddie James 			nb = fsi_spi_data_out(&out, &tx[sent],
314bbb6b2f9SEddie James 					      (int)transfer->len - sent);
315bbb6b2f9SEddie James 
316bbb6b2f9SEddie James 			rc = fsi_spi_write_reg(ctx, SPI_FSI_DATA_TX, out);
317bbb6b2f9SEddie James 			if (rc)
318bbb6b2f9SEddie James 				return rc;
319bbb6b2f9SEddie James 
32040308f96SEddie James 			loops = 0;
32140308f96SEddie James 			end = jiffies + msecs_to_jiffies(SPI_FSI_TIMEOUT_MS);
322bbb6b2f9SEddie James 			do {
32340308f96SEddie James 				if (loops++ && time_after(jiffies, end))
32461bf40efSEddie James 					return -ETIMEDOUT;
32561bf40efSEddie James 
32648a78c66SEddie James 				rc = fsi_spi_status(ctx, &status, "TX");
327bbb6b2f9SEddie James 				if (rc)
328bbb6b2f9SEddie James 					return rc;
329bbb6b2f9SEddie James 			} while (status & SPI_FSI_STATUS_TDR_FULL);
330bbb6b2f9SEddie James 
331bbb6b2f9SEddie James 			sent += nb;
332bbb6b2f9SEddie James 		}
333bbb6b2f9SEddie James 	} else if (transfer->rx_buf) {
334bbb6b2f9SEddie James 		int recv = 0;
335bbb6b2f9SEddie James 		u64 in = 0ULL;
336bbb6b2f9SEddie James 		u8 *rx = transfer->rx_buf;
337bbb6b2f9SEddie James 
338bbb6b2f9SEddie James 		while (transfer->len > recv) {
33940308f96SEddie James 			loops = 0;
34040308f96SEddie James 			end = jiffies + msecs_to_jiffies(SPI_FSI_TIMEOUT_MS);
341bbb6b2f9SEddie James 			do {
34240308f96SEddie James 				if (loops++ && time_after(jiffies, end))
34361bf40efSEddie James 					return -ETIMEDOUT;
34461bf40efSEddie James 
34548a78c66SEddie James 				rc = fsi_spi_status(ctx, &status, "RX");
346bbb6b2f9SEddie James 				if (rc)
347bbb6b2f9SEddie James 					return rc;
348bbb6b2f9SEddie James 			} while (!(status & SPI_FSI_STATUS_RDR_FULL));
349bbb6b2f9SEddie James 
350bbb6b2f9SEddie James 			rc = fsi_spi_read_reg(ctx, SPI_FSI_DATA_RX, &in);
351bbb6b2f9SEddie James 			if (rc)
352bbb6b2f9SEddie James 				return rc;
353bbb6b2f9SEddie James 
354bbb6b2f9SEddie James 			recv += fsi_spi_data_in(in, &rx[recv],
355bbb6b2f9SEddie James 						(int)transfer->len - recv);
356bbb6b2f9SEddie James 		}
357bbb6b2f9SEddie James 	}
358bbb6b2f9SEddie James 
359bbb6b2f9SEddie James 	return 0;
360bbb6b2f9SEddie James }
361bbb6b2f9SEddie James 
fsi_spi_transfer_init(struct fsi_spi * ctx)362bbb6b2f9SEddie James static int fsi_spi_transfer_init(struct fsi_spi *ctx)
363bbb6b2f9SEddie James {
36440308f96SEddie James 	int loops = 0;
365bbb6b2f9SEddie James 	int rc;
366bbb6b2f9SEddie James 	bool reset = false;
367bbb6b2f9SEddie James 	unsigned long end;
368bbb6b2f9SEddie James 	u64 seq_state;
369bbb6b2f9SEddie James 	u64 clock_cfg = 0ULL;
370bbb6b2f9SEddie James 	u64 status = 0ULL;
371bbb6b2f9SEddie James 	u64 wanted_clock_cfg = SPI_FSI_CLOCK_CFG_ECC_DISABLE |
372bbb6b2f9SEddie James 		SPI_FSI_CLOCK_CFG_SCK_NO_DEL |
3730b546bbeSBrad Bishop 		FIELD_PREP(SPI_FSI_CLOCK_CFG_SCK_DIV, 19);
374bbb6b2f9SEddie James 
37540308f96SEddie James 	end = jiffies + msecs_to_jiffies(SPI_FSI_TIMEOUT_MS);
376bbb6b2f9SEddie James 	do {
37740308f96SEddie James 		if (loops++ && time_after(jiffies, end))
378bbb6b2f9SEddie James 			return -ETIMEDOUT;
379bbb6b2f9SEddie James 
380bbb6b2f9SEddie James 		rc = fsi_spi_read_reg(ctx, SPI_FSI_STATUS, &status);
381bbb6b2f9SEddie James 		if (rc)
382bbb6b2f9SEddie James 			return rc;
383bbb6b2f9SEddie James 
384bbb6b2f9SEddie James 		seq_state = status & SPI_FSI_STATUS_SEQ_STATE;
385bbb6b2f9SEddie James 
386bbb6b2f9SEddie James 		if (status & (SPI_FSI_STATUS_ANY_ERROR |
387bbb6b2f9SEddie James 			      SPI_FSI_STATUS_TDR_FULL |
388bbb6b2f9SEddie James 			      SPI_FSI_STATUS_RDR_FULL)) {
38948a78c66SEddie James 			if (reset) {
39048a78c66SEddie James 				dev_err(ctx->dev,
39148a78c66SEddie James 					"Initialization error: %08llx\n",
39248a78c66SEddie James 					status);
393bbb6b2f9SEddie James 				return -EIO;
39448a78c66SEddie James 			}
395bbb6b2f9SEddie James 
396bbb6b2f9SEddie James 			rc = fsi_spi_reset(ctx);
397bbb6b2f9SEddie James 			if (rc)
398bbb6b2f9SEddie James 				return rc;
399bbb6b2f9SEddie James 
400bbb6b2f9SEddie James 			reset = true;
401bbb6b2f9SEddie James 			continue;
402bbb6b2f9SEddie James 		}
403bbb6b2f9SEddie James 	} while (seq_state && (seq_state != SPI_FSI_STATUS_SEQ_STATE_IDLE));
404bbb6b2f9SEddie James 
40534d34a56SEddie James 	rc = fsi_spi_write_reg(ctx, SPI_FSI_COUNTER_CFG, 0ULL);
40634d34a56SEddie James 	if (rc)
40734d34a56SEddie James 		return rc;
40834d34a56SEddie James 
409bbb6b2f9SEddie James 	rc = fsi_spi_read_reg(ctx, SPI_FSI_CLOCK_CFG, &clock_cfg);
410bbb6b2f9SEddie James 	if (rc)
411bbb6b2f9SEddie James 		return rc;
412bbb6b2f9SEddie James 
413bbb6b2f9SEddie James 	if ((clock_cfg & (SPI_FSI_CLOCK_CFG_MM_ENABLE |
414bbb6b2f9SEddie James 			  SPI_FSI_CLOCK_CFG_ECC_DISABLE |
415bbb6b2f9SEddie James 			  SPI_FSI_CLOCK_CFG_MODE |
416bbb6b2f9SEddie James 			  SPI_FSI_CLOCK_CFG_SCK_RECV_DEL |
417bbb6b2f9SEddie James 			  SPI_FSI_CLOCK_CFG_SCK_DIV)) != wanted_clock_cfg)
418bbb6b2f9SEddie James 		rc = fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG,
419bbb6b2f9SEddie James 				       wanted_clock_cfg);
420bbb6b2f9SEddie James 
421bbb6b2f9SEddie James 	return rc;
422bbb6b2f9SEddie James }
423bbb6b2f9SEddie James 
fsi_spi_transfer_one_message(struct spi_controller * ctlr,struct spi_message * mesg)424bbb6b2f9SEddie James static int fsi_spi_transfer_one_message(struct spi_controller *ctlr,
425bbb6b2f9SEddie James 					struct spi_message *mesg)
426bbb6b2f9SEddie James {
4279211a441SEddie James 	int rc;
4289e264f3fSAmit Kumar Mahapatra via Alsa-devel 	u8 seq_slave = SPI_FSI_SEQUENCE_SEL_SLAVE(spi_get_chipselect(mesg->spi, 0) + 1);
42934d34a56SEddie James 	unsigned int len;
430bbb6b2f9SEddie James 	struct spi_transfer *transfer;
431bbb6b2f9SEddie James 	struct fsi_spi *ctx = spi_controller_get_devdata(ctlr);
432bbb6b2f9SEddie James 
433e954af13SEddie James 	rc = fsi_spi_check_mux(ctx->bridge->fsi, ctx->dev);
4349211a441SEddie James 	if (rc)
435ee4ad5d0SEddie James 		goto error;
4369211a441SEddie James 
437bbb6b2f9SEddie James 	list_for_each_entry(transfer, &mesg->transfers, transfer_list) {
438bbb6b2f9SEddie James 		struct fsi_spi_sequence seq;
439bbb6b2f9SEddie James 		struct spi_transfer *next = NULL;
440bbb6b2f9SEddie James 
441bbb6b2f9SEddie James 		/* Sequencer must do shift out (tx) first. */
44234d34a56SEddie James 		if (!transfer->tx_buf || transfer->len > SPI_FSI_MAX_TX_SIZE) {
443bbb6b2f9SEddie James 			rc = -EINVAL;
444bbb6b2f9SEddie James 			goto error;
445bbb6b2f9SEddie James 		}
446bbb6b2f9SEddie James 
447bbb6b2f9SEddie James 		dev_dbg(ctx->dev, "Start tx of %d bytes.\n", transfer->len);
448bbb6b2f9SEddie James 
449bbb6b2f9SEddie James 		rc = fsi_spi_transfer_init(ctx);
450bbb6b2f9SEddie James 		if (rc < 0)
451bbb6b2f9SEddie James 			goto error;
452bbb6b2f9SEddie James 
453bbb6b2f9SEddie James 		fsi_spi_sequence_init(&seq);
454bbb6b2f9SEddie James 		fsi_spi_sequence_add(&seq, seq_slave);
455bbb6b2f9SEddie James 
45634d34a56SEddie James 		len = transfer->len;
45734d34a56SEddie James 		while (len > 8) {
45834d34a56SEddie James 			fsi_spi_sequence_add(&seq,
45934d34a56SEddie James 					     SPI_FSI_SEQUENCE_SHIFT_OUT(8));
46034d34a56SEddie James 			len -= 8;
46134d34a56SEddie James 		}
46234d34a56SEddie James 		fsi_spi_sequence_add(&seq, SPI_FSI_SEQUENCE_SHIFT_OUT(len));
463bbb6b2f9SEddie James 
464bbb6b2f9SEddie James 		if (!list_is_last(&transfer->transfer_list,
465bbb6b2f9SEddie James 				  &mesg->transfers)) {
466bbb6b2f9SEddie James 			next = list_next_entry(transfer, transfer_list);
467bbb6b2f9SEddie James 
468bbb6b2f9SEddie James 			/* Sequencer can only do shift in (rx) after tx. */
469bbb6b2f9SEddie James 			if (next->rx_buf) {
47034d34a56SEddie James 				u8 shift;
47134d34a56SEddie James 
47234d34a56SEddie James 				if (next->len > SPI_FSI_MAX_RX_SIZE) {
473bbb6b2f9SEddie James 					rc = -EINVAL;
474bbb6b2f9SEddie James 					goto error;
475bbb6b2f9SEddie James 				}
476bbb6b2f9SEddie James 
477bbb6b2f9SEddie James 				dev_dbg(ctx->dev, "Sequence rx of %d bytes.\n",
478bbb6b2f9SEddie James 					next->len);
479bbb6b2f9SEddie James 
48034d34a56SEddie James 				shift = SPI_FSI_SEQUENCE_SHIFT_IN(next->len);
48134d34a56SEddie James 				fsi_spi_sequence_add(&seq, shift);
482bbb6b2f9SEddie James 			} else {
483bbb6b2f9SEddie James 				next = NULL;
484bbb6b2f9SEddie James 			}
485bbb6b2f9SEddie James 		}
486bbb6b2f9SEddie James 
487bbb6b2f9SEddie James 		fsi_spi_sequence_add(&seq, SPI_FSI_SEQUENCE_SEL_SLAVE(0));
488bbb6b2f9SEddie James 
489bbb6b2f9SEddie James 		rc = fsi_spi_write_reg(ctx, SPI_FSI_SEQUENCE, seq.data);
490bbb6b2f9SEddie James 		if (rc)
491bbb6b2f9SEddie James 			goto error;
492bbb6b2f9SEddie James 
493bbb6b2f9SEddie James 		rc = fsi_spi_transfer_data(ctx, transfer);
494bbb6b2f9SEddie James 		if (rc)
495bbb6b2f9SEddie James 			goto error;
496bbb6b2f9SEddie James 
497bbb6b2f9SEddie James 		if (next) {
498bbb6b2f9SEddie James 			rc = fsi_spi_transfer_data(ctx, next);
499bbb6b2f9SEddie James 			if (rc)
500bbb6b2f9SEddie James 				goto error;
501bbb6b2f9SEddie James 
502bbb6b2f9SEddie James 			transfer = next;
503bbb6b2f9SEddie James 		}
504bbb6b2f9SEddie James 	}
505bbb6b2f9SEddie James 
506bbb6b2f9SEddie James error:
507bbb6b2f9SEddie James 	mesg->status = rc;
508bbb6b2f9SEddie James 	spi_finalize_current_message(ctlr);
509bbb6b2f9SEddie James 
510bbb6b2f9SEddie James 	return rc;
511bbb6b2f9SEddie James }
512bbb6b2f9SEddie James 
fsi_spi_max_transfer_size(struct spi_device * spi)513bbb6b2f9SEddie James static size_t fsi_spi_max_transfer_size(struct spi_device *spi)
514bbb6b2f9SEddie James {
51534d34a56SEddie James 	return SPI_FSI_MAX_RX_SIZE;
516bbb6b2f9SEddie James }
517bbb6b2f9SEddie James 
fsi_spi_probe(struct device * dev)518bbb6b2f9SEddie James static int fsi_spi_probe(struct device *dev)
519bbb6b2f9SEddie James {
520bbb6b2f9SEddie James 	int rc;
521bbb6b2f9SEddie James 	struct device_node *np;
522bbb6b2f9SEddie James 	int num_controllers_registered = 0;
523e954af13SEddie James 	struct fsi2spi *bridge;
524bbb6b2f9SEddie James 	struct fsi_device *fsi = to_fsi_dev(dev);
525bbb6b2f9SEddie James 
5269211a441SEddie James 	rc = fsi_spi_check_mux(fsi, dev);
527bbb6b2f9SEddie James 	if (rc)
528bbb6b2f9SEddie James 		return -ENODEV;
529bbb6b2f9SEddie James 
530e954af13SEddie James 	bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
531e954af13SEddie James 	if (!bridge)
532e954af13SEddie James 		return -ENOMEM;
533e954af13SEddie James 
534e954af13SEddie James 	bridge->fsi = fsi;
535e954af13SEddie James 	mutex_init(&bridge->lock);
536e954af13SEddie James 
537bbb6b2f9SEddie James 	for_each_available_child_of_node(dev->of_node, np) {
538bbb6b2f9SEddie James 		u32 base;
539bbb6b2f9SEddie James 		struct fsi_spi *ctx;
540bbb6b2f9SEddie James 		struct spi_controller *ctlr;
541bbb6b2f9SEddie James 
542bbb6b2f9SEddie James 		if (of_property_read_u32(np, "reg", &base))
543bbb6b2f9SEddie James 			continue;
544bbb6b2f9SEddie James 
545*d40f10d0SYang Yingliang 		ctlr = spi_alloc_host(dev, sizeof(*ctx));
54624b5515aSChristophe JAILLET 		if (!ctlr) {
54724b5515aSChristophe JAILLET 			of_node_put(np);
548bbb6b2f9SEddie James 			break;
54924b5515aSChristophe JAILLET 		}
550bbb6b2f9SEddie James 
551bbb6b2f9SEddie James 		ctlr->dev.of_node = np;
552bbb6b2f9SEddie James 		ctlr->num_chipselect = of_get_available_child_count(np) ?: 1;
553bbb6b2f9SEddie James 		ctlr->flags = SPI_CONTROLLER_HALF_DUPLEX;
554bbb6b2f9SEddie James 		ctlr->max_transfer_size = fsi_spi_max_transfer_size;
555bbb6b2f9SEddie James 		ctlr->transfer_one_message = fsi_spi_transfer_one_message;
556bbb6b2f9SEddie James 
557bbb6b2f9SEddie James 		ctx = spi_controller_get_devdata(ctlr);
558bbb6b2f9SEddie James 		ctx->dev = &ctlr->dev;
559e954af13SEddie James 		ctx->bridge = bridge;
560bbb6b2f9SEddie James 		ctx->base = base + SPI_FSI_BASE;
561bbb6b2f9SEddie James 
562bbb6b2f9SEddie James 		rc = devm_spi_register_controller(dev, ctlr);
563bbb6b2f9SEddie James 		if (rc)
564bbb6b2f9SEddie James 			spi_controller_put(ctlr);
565bbb6b2f9SEddie James 		else
566bbb6b2f9SEddie James 			num_controllers_registered++;
567bbb6b2f9SEddie James 	}
568bbb6b2f9SEddie James 
569bbb6b2f9SEddie James 	if (!num_controllers_registered)
570bbb6b2f9SEddie James 		return -ENODEV;
571bbb6b2f9SEddie James 
572bbb6b2f9SEddie James 	return 0;
573bbb6b2f9SEddie James }
574bbb6b2f9SEddie James 
575bbb6b2f9SEddie James static const struct fsi_device_id fsi_spi_ids[] = {
576bbb6b2f9SEddie James 	{ FSI_ENGID_SPI, FSI_VERSION_ANY },
577bbb6b2f9SEddie James 	{ }
578bbb6b2f9SEddie James };
579bbb6b2f9SEddie James MODULE_DEVICE_TABLE(fsi, fsi_spi_ids);
580bbb6b2f9SEddie James 
581bbb6b2f9SEddie James static struct fsi_driver fsi_spi_driver = {
582bbb6b2f9SEddie James 	.id_table = fsi_spi_ids,
583bbb6b2f9SEddie James 	.drv = {
584bbb6b2f9SEddie James 		.name = "spi-fsi",
585bbb6b2f9SEddie James 		.bus = &fsi_bus_type,
586bbb6b2f9SEddie James 		.probe = fsi_spi_probe,
587bbb6b2f9SEddie James 	},
588bbb6b2f9SEddie James };
589bbb6b2f9SEddie James module_fsi_driver(fsi_spi_driver);
590bbb6b2f9SEddie James 
591bbb6b2f9SEddie James MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
592bbb6b2f9SEddie James MODULE_DESCRIPTION("FSI attached SPI controller");
593bbb6b2f9SEddie James MODULE_LICENSE("GPL");
594