// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ */ #include #include #include #include int spi_mem_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) { unsigned int pos = 0; const u8 *tx_buf = NULL; u8 *rx_buf = NULL; u8 *op_buf; int op_len; u32 flag; int ret; int i; if (op->data.nbytes) { if (op->data.dir == SPI_MEM_DATA_IN) rx_buf = op->data.buf.in; else tx_buf = op->data.buf.out; } op_len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; op_buf = calloc(1, op_len); ret = spi_claim_bus(slave); if (ret < 0) return ret; op_buf[pos++] = op->cmd.opcode; if (op->addr.nbytes) { for (i = 0; i < op->addr.nbytes; i++) op_buf[pos + i] = op->addr.val >> (8 * (op->addr.nbytes - i - 1)); pos += op->addr.nbytes; } if (op->dummy.nbytes) memset(op_buf + pos, 0xff, op->dummy.nbytes); /* 1st transfer: opcode + address + dummy cycles */ flag = SPI_XFER_BEGIN; /* Make sure to set END bit if no tx or rx data messages follow */ if (!tx_buf && !rx_buf) flag |= SPI_XFER_END; ret = spi_xfer(slave, op_len * 8, op_buf, NULL, flag); if (ret) return ret; /* 2nd transfer: rx or tx data path */ if (tx_buf || rx_buf) { ret = spi_xfer(slave, op->data.nbytes * 8, tx_buf, rx_buf, SPI_XFER_END); if (ret) return ret; } spi_release_bus(slave); for (i = 0; i < pos; i++) debug("%02x ", op_buf[i]); debug("| [%dB %s] ", tx_buf || rx_buf ? op->data.nbytes : 0, tx_buf || rx_buf ? (tx_buf ? "out" : "in") : "-"); for (i = 0; i < op->data.nbytes; i++) debug("%02x ", tx_buf ? tx_buf[i] : rx_buf[i]); debug("[ret %d]\n", ret); free(op_buf); if (ret < 0) return ret; return 0; } int spi_mem_adjust_op_size(struct spi_slave *slave, struct spi_mem_op *op) { unsigned int len; len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; if (slave->max_write_size && len > slave->max_write_size) return -EINVAL; if (op->data.dir == SPI_MEM_DATA_IN) { if (slave->max_read_size) op->data.nbytes = min(op->data.nbytes, slave->max_read_size); } else if (slave->max_write_size) { op->data.nbytes = min(op->data.nbytes, slave->max_write_size - len); } if (!op->data.nbytes) return -EINVAL; return 0; } static int spi_check_buswidth_req(struct spi_slave *slave, u8 buswidth, bool tx) { u32 mode = slave->mode; switch (buswidth) { case 1: return 0; case 2: if ((tx && (mode & (SPI_TX_DUAL | SPI_TX_QUAD))) || (!tx && (mode & (SPI_RX_DUAL | SPI_RX_QUAD)))) return 0; break; case 4: if ((tx && (mode & SPI_TX_QUAD)) || (!tx && (mode & SPI_RX_QUAD))) return 0; break; case 8: if ((tx && (mode & SPI_TX_OCTAL)) || (!tx && (mode & SPI_RX_OCTAL))) return 0; break; default: break; } return -ENOTSUPP; } bool spi_mem_supports_op(struct spi_slave *slave, const struct spi_mem_op *op) { if (spi_check_buswidth_req(slave, op->cmd.buswidth, true)) return false; if (op->addr.nbytes && spi_check_buswidth_req(slave, op->addr.buswidth, true)) return false; if (op->dummy.nbytes && spi_check_buswidth_req(slave, op->dummy.buswidth, true)) return false; if (op->data.nbytes && spi_check_buswidth_req(slave, op->data.buswidth, op->data.dir == SPI_MEM_DATA_OUT)) return false; if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) return false; if (op->cmd.nbytes != 1) return false; return true; }