162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
362306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
462306a36Sopenharmony_ci * for more details.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2011, 2012 Cavium, Inc.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/spi/spi.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/delay.h>
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "spi-cavium.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic void octeon_spi_wait_ready(struct octeon_spi *p)
1762306a36Sopenharmony_ci{
1862306a36Sopenharmony_ci	union cvmx_mpi_sts mpi_sts;
1962306a36Sopenharmony_ci	unsigned int loops = 0;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci	do {
2262306a36Sopenharmony_ci		if (loops++)
2362306a36Sopenharmony_ci			__delay(500);
2462306a36Sopenharmony_ci		mpi_sts.u64 = readq(p->register_base + OCTEON_SPI_STS(p));
2562306a36Sopenharmony_ci	} while (mpi_sts.s.busy);
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic int octeon_spi_do_transfer(struct octeon_spi *p,
2962306a36Sopenharmony_ci				  struct spi_message *msg,
3062306a36Sopenharmony_ci				  struct spi_transfer *xfer,
3162306a36Sopenharmony_ci				  bool last_xfer)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	struct spi_device *spi = msg->spi;
3462306a36Sopenharmony_ci	union cvmx_mpi_cfg mpi_cfg;
3562306a36Sopenharmony_ci	union cvmx_mpi_tx mpi_tx;
3662306a36Sopenharmony_ci	unsigned int clkdiv;
3762306a36Sopenharmony_ci	int mode;
3862306a36Sopenharmony_ci	bool cpha, cpol;
3962306a36Sopenharmony_ci	const u8 *tx_buf;
4062306a36Sopenharmony_ci	u8 *rx_buf;
4162306a36Sopenharmony_ci	int len;
4262306a36Sopenharmony_ci	int i;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	mode = spi->mode;
4562306a36Sopenharmony_ci	cpha = mode & SPI_CPHA;
4662306a36Sopenharmony_ci	cpol = mode & SPI_CPOL;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	clkdiv = p->sys_freq / (2 * xfer->speed_hz);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	mpi_cfg.u64 = 0;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	mpi_cfg.s.clkdiv = clkdiv;
5362306a36Sopenharmony_ci	mpi_cfg.s.cshi = (mode & SPI_CS_HIGH) ? 1 : 0;
5462306a36Sopenharmony_ci	mpi_cfg.s.lsbfirst = (mode & SPI_LSB_FIRST) ? 1 : 0;
5562306a36Sopenharmony_ci	mpi_cfg.s.wireor = (mode & SPI_3WIRE) ? 1 : 0;
5662306a36Sopenharmony_ci	mpi_cfg.s.idlelo = cpha != cpol;
5762306a36Sopenharmony_ci	mpi_cfg.s.cslate = cpha ? 1 : 0;
5862306a36Sopenharmony_ci	mpi_cfg.s.enable = 1;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (spi_get_chipselect(spi, 0) < 4)
6162306a36Sopenharmony_ci		p->cs_enax |= 1ull << (12 + spi_get_chipselect(spi, 0));
6262306a36Sopenharmony_ci	mpi_cfg.u64 |= p->cs_enax;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (mpi_cfg.u64 != p->last_cfg) {
6562306a36Sopenharmony_ci		p->last_cfg = mpi_cfg.u64;
6662306a36Sopenharmony_ci		writeq(mpi_cfg.u64, p->register_base + OCTEON_SPI_CFG(p));
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci	tx_buf = xfer->tx_buf;
6962306a36Sopenharmony_ci	rx_buf = xfer->rx_buf;
7062306a36Sopenharmony_ci	len = xfer->len;
7162306a36Sopenharmony_ci	while (len > OCTEON_SPI_MAX_BYTES) {
7262306a36Sopenharmony_ci		for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) {
7362306a36Sopenharmony_ci			u8 d;
7462306a36Sopenharmony_ci			if (tx_buf)
7562306a36Sopenharmony_ci				d = *tx_buf++;
7662306a36Sopenharmony_ci			else
7762306a36Sopenharmony_ci				d = 0;
7862306a36Sopenharmony_ci			writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
7962306a36Sopenharmony_ci		}
8062306a36Sopenharmony_ci		mpi_tx.u64 = 0;
8162306a36Sopenharmony_ci		mpi_tx.s.csid = spi_get_chipselect(spi, 0);
8262306a36Sopenharmony_ci		mpi_tx.s.leavecs = 1;
8362306a36Sopenharmony_ci		mpi_tx.s.txnum = tx_buf ? OCTEON_SPI_MAX_BYTES : 0;
8462306a36Sopenharmony_ci		mpi_tx.s.totnum = OCTEON_SPI_MAX_BYTES;
8562306a36Sopenharmony_ci		writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p));
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci		octeon_spi_wait_ready(p);
8862306a36Sopenharmony_ci		if (rx_buf)
8962306a36Sopenharmony_ci			for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) {
9062306a36Sopenharmony_ci				u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
9162306a36Sopenharmony_ci				*rx_buf++ = (u8)v;
9262306a36Sopenharmony_ci			}
9362306a36Sopenharmony_ci		len -= OCTEON_SPI_MAX_BYTES;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	for (i = 0; i < len; i++) {
9762306a36Sopenharmony_ci		u8 d;
9862306a36Sopenharmony_ci		if (tx_buf)
9962306a36Sopenharmony_ci			d = *tx_buf++;
10062306a36Sopenharmony_ci		else
10162306a36Sopenharmony_ci			d = 0;
10262306a36Sopenharmony_ci		writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	mpi_tx.u64 = 0;
10662306a36Sopenharmony_ci	mpi_tx.s.csid = spi_get_chipselect(spi, 0);
10762306a36Sopenharmony_ci	if (last_xfer)
10862306a36Sopenharmony_ci		mpi_tx.s.leavecs = xfer->cs_change;
10962306a36Sopenharmony_ci	else
11062306a36Sopenharmony_ci		mpi_tx.s.leavecs = !xfer->cs_change;
11162306a36Sopenharmony_ci	mpi_tx.s.txnum = tx_buf ? len : 0;
11262306a36Sopenharmony_ci	mpi_tx.s.totnum = len;
11362306a36Sopenharmony_ci	writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p));
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	octeon_spi_wait_ready(p);
11662306a36Sopenharmony_ci	if (rx_buf)
11762306a36Sopenharmony_ci		for (i = 0; i < len; i++) {
11862306a36Sopenharmony_ci			u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i));
11962306a36Sopenharmony_ci			*rx_buf++ = (u8)v;
12062306a36Sopenharmony_ci		}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	spi_transfer_delay_exec(xfer);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return xfer->len;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ciint octeon_spi_transfer_one_message(struct spi_master *master,
12862306a36Sopenharmony_ci				    struct spi_message *msg)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	struct octeon_spi *p = spi_master_get_devdata(master);
13162306a36Sopenharmony_ci	unsigned int total_len = 0;
13262306a36Sopenharmony_ci	int status = 0;
13362306a36Sopenharmony_ci	struct spi_transfer *xfer;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
13662306a36Sopenharmony_ci		bool last_xfer = list_is_last(&xfer->transfer_list,
13762306a36Sopenharmony_ci					      &msg->transfers);
13862306a36Sopenharmony_ci		int r = octeon_spi_do_transfer(p, msg, xfer, last_xfer);
13962306a36Sopenharmony_ci		if (r < 0) {
14062306a36Sopenharmony_ci			status = r;
14162306a36Sopenharmony_ci			goto err;
14262306a36Sopenharmony_ci		}
14362306a36Sopenharmony_ci		total_len += r;
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_cierr:
14662306a36Sopenharmony_ci	msg->status = status;
14762306a36Sopenharmony_ci	msg->actual_length = total_len;
14862306a36Sopenharmony_ci	spi_finalize_current_message(master);
14962306a36Sopenharmony_ci	return status;
15062306a36Sopenharmony_ci}
151