bb44dc4862
In this commit, create SPI controller on p10 chip and connect cs irq. The QOM tree of pnv-spi and seeprom are. /machine (powernv10-machine) /chip[0] (power10_v2.0-pnv-chip) /pib_spic[2] (pnv-spi) /pnv-spi-bus.2 (SSI) /xscom-spi[0] (memory-region) /machine (powernv10-machine) /peripheral-anon (container) /device[0] (25csm04) /WP#[0] (irq) /ssi-gpio-cs[0] (irq) (qemu) qom-get /machine/peripheral-anon /device[76] "parent_bus" "/machine/chip[0]/pib_spic[2]/pnv-spi-bus.2" Signed-off-by: Chalapathi V <chalapathi.v@linux.ibm.com> Reviewed-by: Glenn Miles <milesg@linux.ibm.com> Reviewed-by: Cédric Le Goater <clg@kaod.org> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
1269 lines
49 KiB
C
1269 lines
49 KiB
C
/*
|
|
* QEMU PowerPC SPI model
|
|
*
|
|
* Copyright (c) 2024, IBM Corporation.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/ppc/pnv_xscom.h"
|
|
#include "hw/ssi/pnv_spi.h"
|
|
#include "hw/ssi/pnv_spi_regs.h"
|
|
#include "hw/ssi/ssi.h"
|
|
#include <libfdt.h>
|
|
#include "hw/irq.h"
|
|
#include "trace.h"
|
|
|
|
#define PNV_SPI_OPCODE_LO_NIBBLE(x) (x & 0x0F)
|
|
#define PNV_SPI_MASKED_OPCODE(x) (x & 0xF0)
|
|
|
|
/*
|
|
* Macro from include/hw/ppc/fdt.h
|
|
* fdt.h cannot be included here as it contain ppc target specific dependency.
|
|
*/
|
|
#define _FDT(exp) \
|
|
do { \
|
|
int _ret = (exp); \
|
|
if (_ret < 0) { \
|
|
qemu_log_mask(LOG_GUEST_ERROR, \
|
|
"error creating device tree: %s: %s", \
|
|
#exp, fdt_strerror(_ret)); \
|
|
exit(1); \
|
|
} \
|
|
} while (0)
|
|
|
|
/* PnvXferBuffer */
|
|
typedef struct PnvXferBuffer {
|
|
|
|
uint32_t len;
|
|
uint8_t *data;
|
|
|
|
} PnvXferBuffer;
|
|
|
|
/* pnv_spi_xfer_buffer_methods */
|
|
static PnvXferBuffer *pnv_spi_xfer_buffer_new(void)
|
|
{
|
|
PnvXferBuffer *payload = g_malloc0(sizeof(*payload));
|
|
|
|
return payload;
|
|
}
|
|
|
|
static void pnv_spi_xfer_buffer_free(PnvXferBuffer *payload)
|
|
{
|
|
free(payload->data);
|
|
free(payload);
|
|
}
|
|
|
|
static uint8_t *pnv_spi_xfer_buffer_write_ptr(PnvXferBuffer *payload,
|
|
uint32_t offset, uint32_t length)
|
|
{
|
|
if (payload->len < (offset + length)) {
|
|
payload->len = offset + length;
|
|
payload->data = g_realloc(payload->data, payload->len);
|
|
}
|
|
return &payload->data[offset];
|
|
}
|
|
|
|
static bool does_rdr_match(PnvSpi *s)
|
|
{
|
|
/*
|
|
* According to spec, the mask bits that are 0 are compared and the
|
|
* bits that are 1 are ignored.
|
|
*/
|
|
uint16_t rdr_match_mask = GETFIELD(SPI_MM_RDR_MATCH_MASK,
|
|
s->regs[SPI_MM_REG]);
|
|
uint16_t rdr_match_val = GETFIELD(SPI_MM_RDR_MATCH_VAL,
|
|
s->regs[SPI_MM_REG]);
|
|
|
|
if ((~rdr_match_mask & rdr_match_val) == ((~rdr_match_mask) &
|
|
GETFIELD(PPC_BITMASK(48, 63), s->regs[SPI_RCV_DATA_REG]))) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint8_t get_from_offset(PnvSpi *s, uint8_t offset)
|
|
{
|
|
uint8_t byte;
|
|
|
|
/*
|
|
* Offset is an index between 0 and PNV_SPI_REG_SIZE - 1
|
|
* Check the offset before using it.
|
|
*/
|
|
if (offset < PNV_SPI_REG_SIZE) {
|
|
byte = (s->regs[SPI_XMIT_DATA_REG] >> (56 - offset * 8)) & 0xFF;
|
|
} else {
|
|
/*
|
|
* Log an error and return a 0xFF since we have to assign something
|
|
* to byte before returning.
|
|
*/
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Invalid offset = %d used to get byte "
|
|
"from TDR\n", offset);
|
|
byte = 0xff;
|
|
}
|
|
return byte;
|
|
}
|
|
|
|
static uint8_t read_from_frame(PnvSpi *s, uint8_t *read_buf, uint8_t nr_bytes,
|
|
uint8_t ecc_count, uint8_t shift_in_count)
|
|
{
|
|
uint8_t byte;
|
|
int count = 0;
|
|
|
|
while (count < nr_bytes) {
|
|
shift_in_count++;
|
|
if ((ecc_count != 0) &&
|
|
(shift_in_count == (PNV_SPI_REG_SIZE + ecc_count))) {
|
|
shift_in_count = 0;
|
|
} else {
|
|
byte = read_buf[count];
|
|
trace_pnv_spi_shift_rx(byte, count);
|
|
s->regs[SPI_RCV_DATA_REG] = (s->regs[SPI_RCV_DATA_REG] << 8) | byte;
|
|
}
|
|
count++;
|
|
} /* end of while */
|
|
return shift_in_count;
|
|
}
|
|
|
|
static void spi_response(PnvSpi *s, int bits, PnvXferBuffer *rsp_payload)
|
|
{
|
|
uint8_t ecc_count;
|
|
uint8_t shift_in_count;
|
|
|
|
/*
|
|
* Processing here must handle:
|
|
* - Which bytes in the payload we should move to the RDR
|
|
* - Explicit mode counter configuration settings
|
|
* - RDR full and RDR overrun status
|
|
*/
|
|
|
|
/*
|
|
* First check that the response payload is the exact same
|
|
* number of bytes as the request payload was
|
|
*/
|
|
if (rsp_payload->len != (s->N1_bytes + s->N2_bytes)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Invalid response payload size in "
|
|
"bytes, expected %d, got %d\n",
|
|
(s->N1_bytes + s->N2_bytes), rsp_payload->len);
|
|
} else {
|
|
uint8_t ecc_control;
|
|
trace_pnv_spi_rx_received(rsp_payload->len);
|
|
trace_pnv_spi_log_Ncounts(s->N1_bits, s->N1_bytes, s->N1_tx,
|
|
s->N1_rx, s->N2_bits, s->N2_bytes, s->N2_tx, s->N2_rx);
|
|
/*
|
|
* Adding an ECC count let's us know when we have found a payload byte
|
|
* that was shifted in but cannot be loaded into RDR. Bits 29-30 of
|
|
* clock_config_reset_control register equal to either 0b00 or 0b10
|
|
* indicate that we are taking in data with ECC and either applying
|
|
* the ECC or discarding it.
|
|
*/
|
|
ecc_count = 0;
|
|
ecc_control = GETFIELD(SPI_CLK_CFG_ECC_CTRL, s->regs[SPI_CLK_CFG_REG]);
|
|
if (ecc_control == 0 || ecc_control == 2) {
|
|
ecc_count = 1;
|
|
}
|
|
/*
|
|
* Use the N1_rx and N2_rx counts to control shifting data from the
|
|
* payload into the RDR. Keep an overall count of the number of bytes
|
|
* shifted into RDR so we can discard every 9th byte when ECC is
|
|
* enabled.
|
|
*/
|
|
shift_in_count = 0;
|
|
/* Handle the N1 portion of the frame first */
|
|
if (s->N1_rx != 0) {
|
|
trace_pnv_spi_rx_read_N1frame();
|
|
shift_in_count = read_from_frame(s, &rsp_payload->data[0],
|
|
s->N1_bytes, ecc_count, shift_in_count);
|
|
}
|
|
/* Handle the N2 portion of the frame */
|
|
if (s->N2_rx != 0) {
|
|
trace_pnv_spi_rx_read_N2frame();
|
|
shift_in_count = read_from_frame(s,
|
|
&rsp_payload->data[s->N1_bytes], s->N2_bytes,
|
|
ecc_count, shift_in_count);
|
|
}
|
|
if ((s->N1_rx + s->N2_rx) > 0) {
|
|
/*
|
|
* Data was received so handle RDR status.
|
|
* It is easier to handle RDR_full and RDR_overrun status here
|
|
* since the RDR register's shift_byte_in method is called
|
|
* multiple times in a row. Controlling RDR status is done here
|
|
* instead of in the RDR scoped methods for that reason.
|
|
*/
|
|
if (GETFIELD(SPI_STS_RDR_FULL, s->status) == 1) {
|
|
/*
|
|
* Data was shifted into the RDR before having been read
|
|
* causing previous data to have been overrun.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_RDR_OVERRUN, s->status, 1);
|
|
} else {
|
|
/*
|
|
* Set status to indicate that the received data register is
|
|
* full. This flag is only cleared once the RDR is unloaded.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_RDR_FULL, s->status, 1);
|
|
}
|
|
}
|
|
} /* end of else */
|
|
} /* end of spi_response() */
|
|
|
|
static void transfer(PnvSpi *s, PnvXferBuffer *payload)
|
|
{
|
|
uint32_t tx;
|
|
uint32_t rx;
|
|
PnvXferBuffer *rsp_payload = NULL;
|
|
|
|
rsp_payload = pnv_spi_xfer_buffer_new();
|
|
for (int offset = 0; offset < payload->len; offset += s->transfer_len) {
|
|
tx = 0;
|
|
for (int i = 0; i < s->transfer_len; i++) {
|
|
if ((offset + i) >= payload->len) {
|
|
tx <<= 8;
|
|
} else {
|
|
tx = (tx << 8) | payload->data[offset + i];
|
|
}
|
|
}
|
|
rx = ssi_transfer(s->ssi_bus, tx);
|
|
for (int i = 0; i < s->transfer_len; i++) {
|
|
if ((offset + i) >= payload->len) {
|
|
break;
|
|
}
|
|
*(pnv_spi_xfer_buffer_write_ptr(rsp_payload, rsp_payload->len, 1)) =
|
|
(rx >> (8 * (s->transfer_len - 1) - i * 8)) & 0xFF;
|
|
}
|
|
}
|
|
if (rsp_payload != NULL) {
|
|
spi_response(s, s->N1_bits, rsp_payload);
|
|
}
|
|
}
|
|
|
|
static inline uint8_t get_seq_index(PnvSpi *s)
|
|
{
|
|
return GETFIELD(SPI_STS_SEQ_INDEX, s->status);
|
|
}
|
|
|
|
static inline void next_sequencer_fsm(PnvSpi *s)
|
|
{
|
|
uint8_t seq_index = get_seq_index(s);
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, (seq_index + 1));
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_INDEX_INCREMENT);
|
|
}
|
|
|
|
/*
|
|
* Calculate the N1 counters based on passed in opcode and
|
|
* internal register values.
|
|
* The method assumes that the opcode is a Shift_N1 opcode
|
|
* and doesn't test it.
|
|
* The counters returned are:
|
|
* N1 bits: Number of bits in the payload data that are significant
|
|
* to the responder.
|
|
* N1_bytes: Total count of payload bytes for the N1 (portion of the) frame.
|
|
* N1_tx: Total number of bytes taken from TDR for N1
|
|
* N1_rx: Total number of bytes taken from the payload for N1
|
|
*/
|
|
static void calculate_N1(PnvSpi *s, uint8_t opcode)
|
|
{
|
|
/*
|
|
* Shift_N1 opcode form: 0x3M
|
|
* Implicit mode:
|
|
* If M != 0 the shift count is M bytes and M is the number of tx bytes.
|
|
* Forced Implicit mode:
|
|
* M is the shift count but tx and rx is determined by the count control
|
|
* register fields. Note that we only check for forced Implicit mode when
|
|
* M != 0 since the mode doesn't make sense when M = 0.
|
|
* Explicit mode:
|
|
* If M == 0 then shift count is number of bits defined in the
|
|
* Counter Configuration Register's shift_count_N1 field.
|
|
*/
|
|
if (PNV_SPI_OPCODE_LO_NIBBLE(opcode) == 0) {
|
|
/* Explicit mode */
|
|
s->N1_bits = GETFIELD(SPI_CTR_CFG_N1, s->regs[SPI_CTR_CFG_REG]);
|
|
s->N1_bytes = (s->N1_bits + 7) / 8;
|
|
s->N1_tx = 0;
|
|
s->N1_rx = 0;
|
|
/* If tx count control for N1 is set, load the tx value */
|
|
if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B2, s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
s->N1_tx = s->N1_bytes;
|
|
}
|
|
/* If rx count control for N1 is set, load the rx value */
|
|
if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B3, s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
s->N1_rx = s->N1_bytes;
|
|
}
|
|
} else {
|
|
/* Implicit mode/Forced Implicit mode, use M field from opcode */
|
|
s->N1_bytes = PNV_SPI_OPCODE_LO_NIBBLE(opcode);
|
|
s->N1_bits = s->N1_bytes * 8;
|
|
/*
|
|
* Assume that we are going to transmit the count
|
|
* (pure Implicit only)
|
|
*/
|
|
s->N1_tx = s->N1_bytes;
|
|
s->N1_rx = 0;
|
|
/* Let Forced Implicit mode have an effect on the counts */
|
|
if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B1, s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
/*
|
|
* If Forced Implicit mode and count control doesn't
|
|
* indicate transmit then reset the tx count to 0
|
|
*/
|
|
if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B2,
|
|
s->regs[SPI_CTR_CFG_REG]) == 0) {
|
|
s->N1_tx = 0;
|
|
}
|
|
/* If rx count control for N1 is set, load the rx value */
|
|
if (GETFIELD(SPI_CTR_CFG_N1_CTRL_B3,
|
|
s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
s->N1_rx = s->N1_bytes;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Enforce an upper limit on the size of N1 that is equal to the known size
|
|
* of the shift register, 64 bits or 72 bits if ECC is enabled.
|
|
* If the size exceeds 72 bits it is a user error so log an error,
|
|
* cap the size at a max of 64 bits or 72 bits and set the sequencer FSM
|
|
* error bit.
|
|
*/
|
|
uint8_t ecc_control = GETFIELD(SPI_CLK_CFG_ECC_CTRL,
|
|
s->regs[SPI_CLK_CFG_REG]);
|
|
if (ecc_control == 0 || ecc_control == 2) {
|
|
if (s->N1_bytes > (PNV_SPI_REG_SIZE + 1)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size when "
|
|
"ECC enabled, bytes = 0x%x, bits = 0x%x\n",
|
|
s->N1_bytes, s->N1_bits);
|
|
s->N1_bytes = PNV_SPI_REG_SIZE + 1;
|
|
s->N1_bits = s->N1_bytes * 8;
|
|
}
|
|
} else if (s->N1_bytes > PNV_SPI_REG_SIZE) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Unsupported N1 shift size, "
|
|
"bytes = 0x%x, bits = 0x%x\n",
|
|
s->N1_bytes, s->N1_bits);
|
|
s->N1_bytes = PNV_SPI_REG_SIZE;
|
|
s->N1_bits = s->N1_bytes * 8;
|
|
}
|
|
} /* end of calculate_N1 */
|
|
|
|
/*
|
|
* Shift_N1 operation handler method
|
|
*/
|
|
static bool operation_shiftn1(PnvSpi *s, uint8_t opcode,
|
|
PnvXferBuffer **payload, bool send_n1_alone)
|
|
{
|
|
uint8_t n1_count;
|
|
bool stop = false;
|
|
|
|
/*
|
|
* If there isn't a current payload left over from a stopped sequence
|
|
* create a new one.
|
|
*/
|
|
if (*payload == NULL) {
|
|
*payload = pnv_spi_xfer_buffer_new();
|
|
}
|
|
/*
|
|
* Use a combination of N1 counters to build the N1 portion of the
|
|
* transmit payload.
|
|
* We only care about transmit at this time since the request payload
|
|
* only represents data going out on the controller output line.
|
|
* Leave mode specific considerations in the calculate function since
|
|
* all we really care about are counters that tell use exactly how
|
|
* many bytes are in the payload and how many of those bytes to
|
|
* include from the TDR into the payload.
|
|
*/
|
|
calculate_N1(s, opcode);
|
|
trace_pnv_spi_log_Ncounts(s->N1_bits, s->N1_bytes, s->N1_tx,
|
|
s->N1_rx, s->N2_bits, s->N2_bytes, s->N2_tx, s->N2_rx);
|
|
/*
|
|
* Zero out the N2 counters here in case there is no N2 operation following
|
|
* the N1 operation in the sequencer. This keeps leftover N2 information
|
|
* from interfering with spi_response logic.
|
|
*/
|
|
s->N2_bits = 0;
|
|
s->N2_bytes = 0;
|
|
s->N2_tx = 0;
|
|
s->N2_rx = 0;
|
|
/*
|
|
* N1_bytes is the overall size of the N1 portion of the frame regardless of
|
|
* whether N1 is used for tx, rx or both. Loop over the size to build a
|
|
* payload that is N1_bytes long.
|
|
* N1_tx is the count of bytes to take from the TDR and "shift" into the
|
|
* frame which means append those bytes to the payload for the N1 portion
|
|
* of the frame.
|
|
* If N1_tx is 0 or if the count exceeds the size of the TDR append 0xFF to
|
|
* the frame until the overall N1 count is reached.
|
|
*/
|
|
n1_count = 0;
|
|
while (n1_count < s->N1_bytes) {
|
|
/*
|
|
* Assuming that if N1_tx is not equal to 0 then it is the same as
|
|
* N1_bytes.
|
|
*/
|
|
if ((s->N1_tx != 0) && (n1_count < PNV_SPI_REG_SIZE)) {
|
|
|
|
if (GETFIELD(SPI_STS_TDR_FULL, s->status) == 1) {
|
|
/*
|
|
* Note that we are only appending to the payload IF the TDR
|
|
* is full otherwise we don't touch the payload because we are
|
|
* going to NOT send the payload and instead tell the sequencer
|
|
* that called us to stop and wait for a TDR write so we have
|
|
* data to load into the payload.
|
|
*/
|
|
uint8_t n1_byte = 0x00;
|
|
n1_byte = get_from_offset(s, n1_count);
|
|
trace_pnv_spi_tx_append("n1_byte", n1_byte, n1_count);
|
|
*(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1)) =
|
|
n1_byte;
|
|
} else {
|
|
/*
|
|
* We hit a shift_n1 opcode TX but the TDR is empty, tell the
|
|
* sequencer to stop and break this loop.
|
|
*/
|
|
trace_pnv_spi_sequencer_stop_requested("Shift N1"
|
|
"set for transmit but TDR is empty");
|
|
stop = true;
|
|
break;
|
|
}
|
|
} else {
|
|
/*
|
|
* Cases here:
|
|
* - we are receiving during the N1 frame segment and the RDR
|
|
* is full so we need to stop until the RDR is read
|
|
* - we are transmitting and we don't care about RDR status
|
|
* since we won't be loading RDR during the frame segment.
|
|
* - we are receiving and the RDR is empty so we allow the operation
|
|
* to proceed.
|
|
*/
|
|
if ((s->N1_rx != 0) && (GETFIELD(SPI_STS_RDR_FULL,
|
|
s->status) == 1)) {
|
|
trace_pnv_spi_sequencer_stop_requested("shift N1"
|
|
"set for receive but RDR is full");
|
|
stop = true;
|
|
break;
|
|
} else {
|
|
trace_pnv_spi_tx_append_FF("n1_byte");
|
|
*(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1))
|
|
= 0xff;
|
|
}
|
|
}
|
|
n1_count++;
|
|
} /* end of while */
|
|
/*
|
|
* If we are not stopping due to an empty TDR and we are doing an N1 TX
|
|
* and the TDR is full we need to clear the TDR_full status.
|
|
* Do this here instead of up in the loop above so we don't log the message
|
|
* in every loop iteration.
|
|
* Ignore the send_n1_alone flag, all that does is defer the TX until the N2
|
|
* operation, which was found immediately after the current opcode. The TDR
|
|
* was unloaded and will be shifted so we have to clear the TDR_full status.
|
|
*/
|
|
if (!stop && (s->N1_tx != 0) &&
|
|
(GETFIELD(SPI_STS_TDR_FULL, s->status) == 1)) {
|
|
s->status = SETFIELD(SPI_STS_TDR_FULL, s->status, 0);
|
|
}
|
|
/*
|
|
* There are other reasons why the shifter would stop, such as a TDR empty
|
|
* or RDR full condition with N1 set to receive. If we haven't stopped due
|
|
* to either one of those conditions then check if the send_n1_alone flag is
|
|
* equal to False, indicating the next opcode is an N2 operation, AND if
|
|
* the N2 counter reload switch (bit 0 of the N2 count control field) is
|
|
* set. This condition requires a pacing write to "kick" off the N2
|
|
* shift which includes the N1 shift as well when send_n1_alone is False.
|
|
*/
|
|
if (!stop && !send_n1_alone &&
|
|
(GETFIELD(SPI_CTR_CFG_N2_CTRL_B0, s->regs[SPI_CTR_CFG_REG]) == 1)) {
|
|
trace_pnv_spi_sequencer_stop_requested("N2 counter reload "
|
|
"active, stop N1 shift, TDR_underrun set to 1");
|
|
stop = true;
|
|
s->status = SETFIELD(SPI_STS_TDR_UNDERRUN, s->status, 1);
|
|
}
|
|
/*
|
|
* If send_n1_alone is set AND we have a full TDR then this is the first and
|
|
* last payload to send and we don't have an N2 frame segment to add to the
|
|
* payload.
|
|
*/
|
|
if (send_n1_alone && !stop) {
|
|
/* We have a TX and a full TDR or an RX and an empty RDR */
|
|
trace_pnv_spi_tx_request("Shifting N1 frame", (*payload)->len);
|
|
transfer(s, *payload);
|
|
/* The N1 frame shift is complete so reset the N1 counters */
|
|
s->N2_bits = 0;
|
|
s->N2_bytes = 0;
|
|
s->N2_tx = 0;
|
|
s->N2_rx = 0;
|
|
pnv_spi_xfer_buffer_free(*payload);
|
|
*payload = NULL;
|
|
}
|
|
return stop;
|
|
} /* end of operation_shiftn1() */
|
|
|
|
/*
|
|
* Calculate the N2 counters based on passed in opcode and
|
|
* internal register values.
|
|
* The method assumes that the opcode is a Shift_N2 opcode
|
|
* and doesn't test it.
|
|
* The counters returned are:
|
|
* N2 bits: Number of bits in the payload data that are significant
|
|
* to the responder.
|
|
* N2_bytes: Total count of payload bytes for the N2 frame.
|
|
* N2_tx: Total number of bytes taken from TDR for N2
|
|
* N2_rx: Total number of bytes taken from the payload for N2
|
|
*/
|
|
static void calculate_N2(PnvSpi *s, uint8_t opcode)
|
|
{
|
|
/*
|
|
* Shift_N2 opcode form: 0x4M
|
|
* Implicit mode:
|
|
* If M!=0 the shift count is M bytes and M is the number of rx bytes.
|
|
* Forced Implicit mode:
|
|
* M is the shift count but tx and rx is determined by the count control
|
|
* register fields. Note that we only check for Forced Implicit mode when
|
|
* M != 0 since the mode doesn't make sense when M = 0.
|
|
* Explicit mode:
|
|
* If M==0 then shift count is number of bits defined in the
|
|
* Counter Configuration Register's shift_count_N1 field.
|
|
*/
|
|
if (PNV_SPI_OPCODE_LO_NIBBLE(opcode) == 0) {
|
|
/* Explicit mode */
|
|
s->N2_bits = GETFIELD(SPI_CTR_CFG_N2, s->regs[SPI_CTR_CFG_REG]);
|
|
s->N2_bytes = (s->N2_bits + 7) / 8;
|
|
s->N2_tx = 0;
|
|
s->N2_rx = 0;
|
|
/* If tx count control for N2 is set, load the tx value */
|
|
if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B2, s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
s->N2_tx = s->N2_bytes;
|
|
}
|
|
/* If rx count control for N2 is set, load the rx value */
|
|
if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B3, s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
s->N2_rx = s->N2_bytes;
|
|
}
|
|
} else {
|
|
/* Implicit mode/Forced Implicit mode, use M field from opcode */
|
|
s->N2_bytes = PNV_SPI_OPCODE_LO_NIBBLE(opcode);
|
|
s->N2_bits = s->N2_bytes * 8;
|
|
/* Assume that we are going to receive the count */
|
|
s->N2_rx = s->N2_bytes;
|
|
s->N2_tx = 0;
|
|
/* Let Forced Implicit mode have an effect on the counts */
|
|
if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B1, s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
/*
|
|
* If Forced Implicit mode and count control doesn't
|
|
* indicate a receive then reset the rx count to 0
|
|
*/
|
|
if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B3,
|
|
s->regs[SPI_CTR_CFG_REG]) == 0) {
|
|
s->N2_rx = 0;
|
|
}
|
|
/* If tx count control for N2 is set, load the tx value */
|
|
if (GETFIELD(SPI_CTR_CFG_N2_CTRL_B2,
|
|
s->regs[SPI_CTR_CFG_REG]) == 1) {
|
|
s->N2_tx = s->N2_bytes;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Enforce an upper limit on the size of N1 that is equal to the
|
|
* known size of the shift register, 64 bits or 72 bits if ECC
|
|
* is enabled.
|
|
* If the size exceeds 72 bits it is a user error so log an error,
|
|
* cap the size at a max of 64 bits or 72 bits and set the sequencer FSM
|
|
* error bit.
|
|
*/
|
|
uint8_t ecc_control = GETFIELD(SPI_CLK_CFG_ECC_CTRL,
|
|
s->regs[SPI_CLK_CFG_REG]);
|
|
if (ecc_control == 0 || ecc_control == 2) {
|
|
if (s->N2_bytes > (PNV_SPI_REG_SIZE + 1)) {
|
|
/* Unsupported N2 shift size when ECC enabled */
|
|
s->N2_bytes = PNV_SPI_REG_SIZE + 1;
|
|
s->N2_bits = s->N2_bytes * 8;
|
|
}
|
|
} else if (s->N2_bytes > PNV_SPI_REG_SIZE) {
|
|
/* Unsupported N2 shift size */
|
|
s->N2_bytes = PNV_SPI_REG_SIZE;
|
|
s->N2_bits = s->N2_bytes * 8;
|
|
}
|
|
} /* end of calculate_N2 */
|
|
|
|
/*
|
|
* Shift_N2 operation handler method
|
|
*/
|
|
|
|
static bool operation_shiftn2(PnvSpi *s, uint8_t opcode,
|
|
PnvXferBuffer **payload)
|
|
{
|
|
uint8_t n2_count;
|
|
bool stop = false;
|
|
|
|
/*
|
|
* If there isn't a current payload left over from a stopped sequence
|
|
* create a new one.
|
|
*/
|
|
if (*payload == NULL) {
|
|
*payload = pnv_spi_xfer_buffer_new();
|
|
}
|
|
/*
|
|
* Use a combination of N2 counters to build the N2 portion of the
|
|
* transmit payload.
|
|
*/
|
|
calculate_N2(s, opcode);
|
|
trace_pnv_spi_log_Ncounts(s->N1_bits, s->N1_bytes, s->N1_tx,
|
|
s->N1_rx, s->N2_bits, s->N2_bytes, s->N2_tx, s->N2_rx);
|
|
/*
|
|
* The only difference between this code and the code for shift N1 is
|
|
* that this code has to account for the possible presence of N1 transmit
|
|
* bytes already taken from the TDR.
|
|
* If there are bytes to be transmitted for the N2 portion of the frame
|
|
* and there are still bytes in TDR that have not been copied into the
|
|
* TX data of the payload, this code will handle transmitting those
|
|
* remaining bytes.
|
|
* If for some reason the transmit count(s) add up to more than the size
|
|
* of the TDR we will just append 0xFF to the transmit payload data until
|
|
* the payload is N1 + N2 bytes long.
|
|
*/
|
|
n2_count = 0;
|
|
while (n2_count < s->N2_bytes) {
|
|
/*
|
|
* If the RDR is full and we need to RX just bail out, letting the
|
|
* code continue will end up building the payload twice in the same
|
|
* buffer since RDR full causes a sequence stop and restart.
|
|
*/
|
|
if ((s->N2_rx != 0) &&
|
|
(GETFIELD(SPI_STS_RDR_FULL, s->status) == 1)) {
|
|
trace_pnv_spi_sequencer_stop_requested("shift N2 set"
|
|
"for receive but RDR is full");
|
|
stop = true;
|
|
break;
|
|
}
|
|
if ((s->N2_tx != 0) && ((s->N1_tx + n2_count) <
|
|
PNV_SPI_REG_SIZE)) {
|
|
/* Always append data for the N2 segment if it is set for TX */
|
|
uint8_t n2_byte = 0x00;
|
|
n2_byte = get_from_offset(s, (s->N1_tx + n2_count));
|
|
trace_pnv_spi_tx_append("n2_byte", n2_byte, (s->N1_tx + n2_count));
|
|
*(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1))
|
|
= n2_byte;
|
|
} else {
|
|
/*
|
|
* Regardless of whether or not N2 is set for TX or RX, we need
|
|
* the number of bytes in the payload to match the overall length
|
|
* of the operation.
|
|
*/
|
|
trace_pnv_spi_tx_append_FF("n2_byte");
|
|
*(pnv_spi_xfer_buffer_write_ptr(*payload, (*payload)->len, 1))
|
|
= 0xff;
|
|
}
|
|
n2_count++;
|
|
} /* end of while */
|
|
if (!stop) {
|
|
/* We have a TX and a full TDR or an RX and an empty RDR */
|
|
trace_pnv_spi_tx_request("Shifting N2 frame", (*payload)->len);
|
|
transfer(s, *payload);
|
|
/*
|
|
* If we are doing an N2 TX and the TDR is full we need to clear the
|
|
* TDR_full status. Do this here instead of up in the loop above so we
|
|
* don't log the message in every loop iteration.
|
|
*/
|
|
if ((s->N2_tx != 0) &&
|
|
(GETFIELD(SPI_STS_TDR_FULL, s->status) == 1)) {
|
|
s->status = SETFIELD(SPI_STS_TDR_FULL, s->status, 0);
|
|
}
|
|
/*
|
|
* The N2 frame shift is complete so reset the N2 counters.
|
|
* Reset the N1 counters also in case the frame was a combination of
|
|
* N1 and N2 segments.
|
|
*/
|
|
s->N2_bits = 0;
|
|
s->N2_bytes = 0;
|
|
s->N2_tx = 0;
|
|
s->N2_rx = 0;
|
|
s->N1_bits = 0;
|
|
s->N1_bytes = 0;
|
|
s->N1_tx = 0;
|
|
s->N1_rx = 0;
|
|
pnv_spi_xfer_buffer_free(*payload);
|
|
*payload = NULL;
|
|
}
|
|
return stop;
|
|
} /* end of operation_shiftn2()*/
|
|
|
|
static void operation_sequencer(PnvSpi *s)
|
|
{
|
|
/*
|
|
* Loop through each sequencer operation ID and perform the requested
|
|
* operations.
|
|
* Flag for indicating if we should send the N1 frame or wait to combine
|
|
* it with a preceding N2 frame.
|
|
*/
|
|
bool send_n1_alone = true;
|
|
bool stop = false; /* Flag to stop the sequencer */
|
|
uint8_t opcode = 0;
|
|
uint8_t masked_opcode = 0;
|
|
|
|
/*
|
|
* PnvXferBuffer for containing the payload of the SPI frame.
|
|
* This is a static because there are cases where a sequence has to stop
|
|
* and wait for the target application to unload the RDR. If this occurs
|
|
* during a sequence where N1 is not sent alone and instead combined with
|
|
* N2 since the N1 tx length + the N2 tx length is less than the size of
|
|
* the TDR.
|
|
*/
|
|
static PnvXferBuffer *payload;
|
|
|
|
if (payload == NULL) {
|
|
payload = pnv_spi_xfer_buffer_new();
|
|
}
|
|
/*
|
|
* Clear the sequencer FSM error bit - general_SPI_status[3]
|
|
* before starting a sequence.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_GEN_STATUS_B3, s->status, 0);
|
|
/*
|
|
* If the FSM is idle set the sequencer index to 0
|
|
* (new/restarted sequence)
|
|
*/
|
|
if (GETFIELD(SPI_STS_SEQ_FSM, s->status) == SEQ_STATE_IDLE) {
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, 0);
|
|
}
|
|
/*
|
|
* There are only 8 possible operation IDs to iterate through though
|
|
* some operations may cause more than one frame to be sequenced.
|
|
*/
|
|
while (get_seq_index(s) < NUM_SEQ_OPS) {
|
|
opcode = s->seq_op[get_seq_index(s)];
|
|
/* Set sequencer state to decode */
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_DECODE);
|
|
/*
|
|
* Only the upper nibble of the operation ID is needed to know what
|
|
* kind of operation is requested.
|
|
*/
|
|
masked_opcode = PNV_SPI_MASKED_OPCODE(opcode);
|
|
switch (masked_opcode) {
|
|
/*
|
|
* Increment the operation index in each case instead of just
|
|
* once at the end in case an operation like the branch
|
|
* operation needs to change the index.
|
|
*/
|
|
case SEQ_OP_STOP:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
/* A stop operation in any position stops the sequencer */
|
|
trace_pnv_spi_sequencer_op("STOP", get_seq_index(s));
|
|
|
|
stop = true;
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_IDLE);
|
|
s->loop_counter_1 = 0;
|
|
s->loop_counter_2 = 0;
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_IDLE);
|
|
break;
|
|
|
|
case SEQ_OP_SELECT_SLAVE:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
trace_pnv_spi_sequencer_op("SELECT_SLAVE", get_seq_index(s));
|
|
/*
|
|
* This device currently only supports a single responder
|
|
* connection at position 0. De-selecting a responder is fine
|
|
* and expected at the end of a sequence but selecting any
|
|
* responder other than 0 should cause an error.
|
|
*/
|
|
s->responder_select = PNV_SPI_OPCODE_LO_NIBBLE(opcode);
|
|
if (s->responder_select == 0) {
|
|
trace_pnv_spi_shifter_done();
|
|
qemu_set_irq(s->cs_line[0], 1);
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status,
|
|
(get_seq_index(s) + 1));
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_DONE);
|
|
} else if (s->responder_select != 1) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Slave selection other than 1 "
|
|
"not supported, select = 0x%x\n",
|
|
s->responder_select);
|
|
trace_pnv_spi_sequencer_stop_requested("invalid "
|
|
"responder select");
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_IDLE);
|
|
stop = true;
|
|
} else {
|
|
/*
|
|
* Only allow an FSM_START state when a responder is
|
|
* selected
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_START);
|
|
trace_pnv_spi_shifter_stating();
|
|
qemu_set_irq(s->cs_line[0], 0);
|
|
/*
|
|
* A Shift_N2 operation is only valid after a Shift_N1
|
|
* according to the spec. The spec doesn't say if that means
|
|
* immediately after or just after at any point. We will track
|
|
* the occurrence of a Shift_N1 to enforce this requirement in
|
|
* the most generic way possible by assuming that the rule
|
|
* applies once a valid responder select has occurred.
|
|
*/
|
|
s->shift_n1_done = false;
|
|
next_sequencer_fsm(s);
|
|
}
|
|
break;
|
|
|
|
case SEQ_OP_SHIFT_N1:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
trace_pnv_spi_sequencer_op("SHIFT_N1", get_seq_index(s));
|
|
/*
|
|
* Only allow a shift_n1 when the state is not IDLE or DONE.
|
|
* In either of those two cases the sequencer is not in a proper
|
|
* state to perform shift operations because the sequencer has:
|
|
* - processed a responder deselect (DONE)
|
|
* - processed a stop opcode (IDLE)
|
|
* - encountered an error (IDLE)
|
|
*/
|
|
if ((GETFIELD(SPI_STS_SHIFTER_FSM, s->status) == FSM_IDLE) ||
|
|
(GETFIELD(SPI_STS_SHIFTER_FSM, s->status) == FSM_DONE)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Shift_N1 not allowed in "
|
|
"shifter state = 0x%llx", GETFIELD(
|
|
SPI_STS_SHIFTER_FSM, s->status));
|
|
/*
|
|
* Set sequencer FSM error bit 3 (general_SPI_status[3])
|
|
* in status reg.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_GEN_STATUS_B3, s->status, 1);
|
|
trace_pnv_spi_sequencer_stop_requested("invalid shifter state");
|
|
stop = true;
|
|
} else {
|
|
/*
|
|
* Look for the special case where there is a shift_n1 set for
|
|
* transmit and it is followed by a shift_n2 set for transmit
|
|
* AND the combined transmit length of the two operations is
|
|
* less than or equal to the size of the TDR register. In this
|
|
* case we want to use both this current shift_n1 opcode and the
|
|
* following shift_n2 opcode to assemble the frame for
|
|
* transmission to the responder without requiring a refill of
|
|
* the TDR between the two operations.
|
|
*/
|
|
if (PNV_SPI_MASKED_OPCODE(s->seq_op[get_seq_index(s) + 1])
|
|
== SEQ_OP_SHIFT_N2) {
|
|
send_n1_alone = false;
|
|
}
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status,
|
|
FSM_SHIFT_N1);
|
|
stop = operation_shiftn1(s, opcode, &payload, send_n1_alone);
|
|
if (stop) {
|
|
/*
|
|
* The operation code says to stop, this can occur if:
|
|
* (1) RDR is full and the N1 shift is set for receive
|
|
* (2) TDR was empty at the time of the N1 shift so we need
|
|
* to wait for data.
|
|
* (3) Neither 1 nor 2 are occurring and we aren't sending
|
|
* N1 alone and N2 counter reload is set (bit 0 of the N2
|
|
* counter reload field). In this case TDR_underrun will
|
|
* will be set and the Payload has been loaded so it is
|
|
* ok to advance the sequencer.
|
|
*/
|
|
if (GETFIELD(SPI_STS_TDR_UNDERRUN, s->status)) {
|
|
s->shift_n1_done = true;
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status,
|
|
FSM_SHIFT_N2);
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status,
|
|
(get_seq_index(s) + 1));
|
|
} else {
|
|
/*
|
|
* This is case (1) or (2) so the sequencer needs to
|
|
* wait and NOT go to the next sequence yet.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status,
|
|
FSM_WAIT);
|
|
}
|
|
} else {
|
|
/* Ok to move on to the next index */
|
|
s->shift_n1_done = true;
|
|
next_sequencer_fsm(s);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SEQ_OP_SHIFT_N2:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
trace_pnv_spi_sequencer_op("SHIFT_N2", get_seq_index(s));
|
|
if (!s->shift_n1_done) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Shift_N2 is not allowed if a "
|
|
"Shift_N1 is not done, shifter state = 0x%llx",
|
|
GETFIELD(SPI_STS_SHIFTER_FSM, s->status));
|
|
/*
|
|
* In case the sequencer actually stops if an N2 shift is
|
|
* requested before any N1 shift is done. Set sequencer FSM
|
|
* error bit 3 (general_SPI_status[3]) in status reg.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_GEN_STATUS_B3, s->status, 1);
|
|
trace_pnv_spi_sequencer_stop_requested("shift_n2 "
|
|
"w/no shift_n1 done");
|
|
stop = true;
|
|
} else {
|
|
/* Ok to do a Shift_N2 */
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status,
|
|
FSM_SHIFT_N2);
|
|
stop = operation_shiftn2(s, opcode, &payload);
|
|
/*
|
|
* If the operation code says to stop set the shifter state to
|
|
* wait and stop
|
|
*/
|
|
if (stop) {
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status,
|
|
FSM_WAIT);
|
|
} else {
|
|
/* Ok to move on to the next index */
|
|
next_sequencer_fsm(s);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SEQ_OP_BRANCH_IFNEQ_RDR:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_RDR", get_seq_index(s));
|
|
/*
|
|
* The memory mapping register RDR match value is compared against
|
|
* the 16 rightmost bytes of the RDR (potentially with masking).
|
|
* Since this comparison is performed against the contents of the
|
|
* RDR then a receive must have previously occurred otherwise
|
|
* there is no data to compare and the operation cannot be
|
|
* completed and will stop the sequencer until RDR full is set to
|
|
* 1.
|
|
*/
|
|
if (GETFIELD(SPI_STS_RDR_FULL, s->status) == 1) {
|
|
bool rdr_matched = false;
|
|
rdr_matched = does_rdr_match(s);
|
|
if (rdr_matched) {
|
|
trace_pnv_spi_RDR_match("success");
|
|
/* A match occurred, increment the sequencer index. */
|
|
next_sequencer_fsm(s);
|
|
} else {
|
|
trace_pnv_spi_RDR_match("failed");
|
|
/*
|
|
* Branch the sequencer to the index coded into the op
|
|
* code.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status,
|
|
PNV_SPI_OPCODE_LO_NIBBLE(opcode));
|
|
}
|
|
/*
|
|
* Regardless of where the branch ended up we want the
|
|
* sequencer to continue shifting so we have to clear
|
|
* RDR_full.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_RDR_FULL, s->status, 0);
|
|
} else {
|
|
trace_pnv_spi_sequencer_stop_requested("RDR not"
|
|
"full for 0x6x opcode");
|
|
stop = true;
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_WAIT);
|
|
}
|
|
break;
|
|
|
|
case SEQ_OP_TRANSFER_TDR:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
qemu_log_mask(LOG_GUEST_ERROR, "Transfer TDR is not supported\n");
|
|
next_sequencer_fsm(s);
|
|
break;
|
|
|
|
case SEQ_OP_BRANCH_IFNEQ_INC_1:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_INC_1", get_seq_index(s));
|
|
/*
|
|
* The spec says the loop should execute count compare + 1 times.
|
|
* However we learned from engineering that we really only loop
|
|
* count_compare times, count compare = 0 makes this op code a
|
|
* no-op
|
|
*/
|
|
if (s->loop_counter_1 !=
|
|
GETFIELD(SPI_CTR_CFG_CMP1, s->regs[SPI_CTR_CFG_REG])) {
|
|
/*
|
|
* Next index is the lower nibble of the branch operation ID,
|
|
* mask off all but the first three bits so we don't try to
|
|
* access beyond the sequencer_operation_reg boundary.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status,
|
|
PNV_SPI_OPCODE_LO_NIBBLE(opcode));
|
|
s->loop_counter_1++;
|
|
} else {
|
|
/* Continue to next index if loop counter is reached */
|
|
next_sequencer_fsm(s);
|
|
}
|
|
break;
|
|
|
|
case SEQ_OP_BRANCH_IFNEQ_INC_2:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
trace_pnv_spi_sequencer_op("BRANCH_IFNEQ_INC_2", get_seq_index(s));
|
|
uint8_t condition2 = GETFIELD(SPI_CTR_CFG_CMP2,
|
|
s->regs[SPI_CTR_CFG_REG]);
|
|
/*
|
|
* The spec says the loop should execute count compare + 1 times.
|
|
* However we learned from engineering that we really only loop
|
|
* count_compare times, count compare = 0 makes this op code a
|
|
* no-op
|
|
*/
|
|
if (s->loop_counter_2 != condition2) {
|
|
/*
|
|
* Next index is the lower nibble of the branch operation ID,
|
|
* mask off all but the first three bits so we don't try to
|
|
* access beyond the sequencer_operation_reg boundary.
|
|
*/
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX,
|
|
s->status, PNV_SPI_OPCODE_LO_NIBBLE(opcode));
|
|
s->loop_counter_2++;
|
|
} else {
|
|
/* Continue to next index if loop counter is reached */
|
|
next_sequencer_fsm(s);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_EXECUTE);
|
|
/* Ignore unsupported operations. */
|
|
next_sequencer_fsm(s);
|
|
break;
|
|
} /* end of switch */
|
|
/*
|
|
* If we used all 8 opcodes without seeing a 00 - STOP in the sequence
|
|
* we need to go ahead and end things as if there was a STOP at the
|
|
* end.
|
|
*/
|
|
if (get_seq_index(s) == NUM_SEQ_OPS) {
|
|
/* All 8 opcodes completed, sequencer idling */
|
|
s->status = SETFIELD(SPI_STS_SHIFTER_FSM, s->status, FSM_IDLE);
|
|
s->status = SETFIELD(SPI_STS_SEQ_INDEX, s->status, 0);
|
|
s->loop_counter_1 = 0;
|
|
s->loop_counter_2 = 0;
|
|
s->status = SETFIELD(SPI_STS_SEQ_FSM, s->status, SEQ_STATE_IDLE);
|
|
break;
|
|
}
|
|
/* Break the loop if a stop was requested */
|
|
if (stop) {
|
|
break;
|
|
}
|
|
} /* end of while */
|
|
return;
|
|
} /* end of operation_sequencer() */
|
|
|
|
/*
|
|
* The SPIC engine and its internal sequencer can be interrupted and reset by
|
|
* a hardware signal, the sbe_spicst_hard_reset bits from Pervasive
|
|
* Miscellaneous Register of sbe_register_bo device.
|
|
* Reset immediately aborts any SPI transaction in progress and returns the
|
|
* sequencer and state machines to idle state.
|
|
* The configuration register values are not changed. The status register is
|
|
* not reset. The engine registers are not reset.
|
|
* The SPIC engine reset does not have any affect on the attached devices.
|
|
* Reset handling of any attached devices is beyond the scope of the engine.
|
|
*/
|
|
static void do_reset(DeviceState *dev)
|
|
{
|
|
PnvSpi *s = PNV_SPI(dev);
|
|
DeviceState *ssi_dev;
|
|
|
|
trace_pnv_spi_reset();
|
|
|
|
/* Connect cs irq */
|
|
ssi_dev = ssi_get_cs(s->ssi_bus, 0);
|
|
if (ssi_dev) {
|
|
qemu_irq cs_line = qdev_get_gpio_in_named(ssi_dev, SSI_GPIO_CS, 0);
|
|
qdev_connect_gpio_out_named(DEVICE(s), "cs", 0, cs_line);
|
|
}
|
|
|
|
/* Reset all N1 and N2 counters, and other constants */
|
|
s->N2_bits = 0;
|
|
s->N2_bytes = 0;
|
|
s->N2_tx = 0;
|
|
s->N2_rx = 0;
|
|
s->N1_bits = 0;
|
|
s->N1_bytes = 0;
|
|
s->N1_tx = 0;
|
|
s->N1_rx = 0;
|
|
s->loop_counter_1 = 0;
|
|
s->loop_counter_2 = 0;
|
|
/* Disconnected from responder */
|
|
qemu_set_irq(s->cs_line[0], 1);
|
|
}
|
|
|
|
static uint64_t pnv_spi_xscom_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
PnvSpi *s = PNV_SPI(opaque);
|
|
uint32_t reg = addr >> 3;
|
|
uint64_t val = ~0ull;
|
|
|
|
switch (reg) {
|
|
case ERROR_REG:
|
|
case SPI_CTR_CFG_REG:
|
|
case CONFIG_REG1:
|
|
case SPI_CLK_CFG_REG:
|
|
case SPI_MM_REG:
|
|
case SPI_XMIT_DATA_REG:
|
|
val = s->regs[reg];
|
|
break;
|
|
case SPI_RCV_DATA_REG:
|
|
val = s->regs[reg];
|
|
trace_pnv_spi_read_RDR(val);
|
|
s->status = SETFIELD(SPI_STS_RDR_FULL, s->status, 0);
|
|
if (GETFIELD(SPI_STS_SHIFTER_FSM, s->status) == FSM_WAIT) {
|
|
trace_pnv_spi_start_sequencer();
|
|
operation_sequencer(s);
|
|
}
|
|
break;
|
|
case SPI_SEQ_OP_REG:
|
|
val = 0;
|
|
for (int i = 0; i < PNV_SPI_REG_SIZE; i++) {
|
|
val = (val << 8) | s->seq_op[i];
|
|
}
|
|
break;
|
|
case SPI_STS_REG:
|
|
val = s->status;
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi_regs: Invalid xscom "
|
|
"read at 0x%" PRIx32 "\n", reg);
|
|
}
|
|
|
|
trace_pnv_spi_read(addr, val);
|
|
return val;
|
|
}
|
|
|
|
static void pnv_spi_xscom_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
PnvSpi *s = PNV_SPI(opaque);
|
|
uint32_t reg = addr >> 3;
|
|
|
|
trace_pnv_spi_write(addr, val);
|
|
|
|
switch (reg) {
|
|
case ERROR_REG:
|
|
case SPI_CTR_CFG_REG:
|
|
case CONFIG_REG1:
|
|
case SPI_MM_REG:
|
|
case SPI_RCV_DATA_REG:
|
|
s->regs[reg] = val;
|
|
break;
|
|
case SPI_CLK_CFG_REG:
|
|
/*
|
|
* To reset the SPI controller write the sequence 0x5 0xA to
|
|
* reset_control field
|
|
*/
|
|
if ((GETFIELD(SPI_CLK_CFG_RST_CTRL, s->regs[SPI_CLK_CFG_REG]) == 0x5)
|
|
&& (GETFIELD(SPI_CLK_CFG_RST_CTRL, val) == 0xA)) {
|
|
/* SPI controller reset sequence completed, resetting */
|
|
s->regs[reg] = SPI_CLK_CFG_HARD_RST;
|
|
} else {
|
|
s->regs[reg] = val;
|
|
}
|
|
break;
|
|
case SPI_XMIT_DATA_REG:
|
|
/*
|
|
* Writing to the transmit data register causes the transmit data
|
|
* register full status bit in the status register to be set. Writing
|
|
* when the transmit data register full status bit is already set
|
|
* causes a "Resource Not Available" condition. This is not possible
|
|
* in the model since writes to this register are not asynchronous to
|
|
* the operation sequence like it would be in hardware.
|
|
*/
|
|
s->regs[reg] = val;
|
|
trace_pnv_spi_write_TDR(val);
|
|
s->status = SETFIELD(SPI_STS_TDR_FULL, s->status, 1);
|
|
s->status = SETFIELD(SPI_STS_TDR_UNDERRUN, s->status, 0);
|
|
trace_pnv_spi_start_sequencer();
|
|
operation_sequencer(s);
|
|
break;
|
|
case SPI_SEQ_OP_REG:
|
|
for (int i = 0; i < PNV_SPI_REG_SIZE; i++) {
|
|
s->seq_op[i] = (val >> (56 - i * 8)) & 0xFF;
|
|
}
|
|
break;
|
|
case SPI_STS_REG:
|
|
/* other fields are ignore_write */
|
|
s->status = SETFIELD(SPI_STS_RDR_OVERRUN, s->status,
|
|
GETFIELD(SPI_STS_RDR, val));
|
|
s->status = SETFIELD(SPI_STS_TDR_OVERRUN, s->status,
|
|
GETFIELD(SPI_STS_TDR, val));
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "pnv_spi_regs: Invalid xscom "
|
|
"write at 0x%" PRIx32 "\n", reg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static const MemoryRegionOps pnv_spi_xscom_ops = {
|
|
.read = pnv_spi_xscom_read,
|
|
.write = pnv_spi_xscom_write,
|
|
.valid.min_access_size = 8,
|
|
.valid.max_access_size = 8,
|
|
.impl.min_access_size = 8,
|
|
.impl.max_access_size = 8,
|
|
.endianness = DEVICE_BIG_ENDIAN,
|
|
};
|
|
|
|
static Property pnv_spi_properties[] = {
|
|
DEFINE_PROP_UINT32("spic_num", PnvSpi, spic_num, 0),
|
|
DEFINE_PROP_UINT8("transfer_len", PnvSpi, transfer_len, 4),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void pnv_spi_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
PnvSpi *s = PNV_SPI(dev);
|
|
g_autofree char *name = g_strdup_printf(TYPE_PNV_SPI_BUS ".%d",
|
|
s->spic_num);
|
|
s->ssi_bus = ssi_create_bus(dev, name);
|
|
s->cs_line = g_new0(qemu_irq, 1);
|
|
qdev_init_gpio_out_named(DEVICE(s), s->cs_line, "cs", 1);
|
|
|
|
/* spi scoms */
|
|
pnv_xscom_region_init(&s->xscom_spic_regs, OBJECT(s), &pnv_spi_xscom_ops,
|
|
s, "xscom-spi", PNV10_XSCOM_PIB_SPIC_SIZE);
|
|
}
|
|
|
|
static int pnv_spi_dt_xscom(PnvXScomInterface *dev, void *fdt,
|
|
int offset)
|
|
{
|
|
PnvSpi *s = PNV_SPI(dev);
|
|
g_autofree char *name;
|
|
int s_offset;
|
|
const char compat[] = "ibm,power10-spi";
|
|
uint32_t spic_pcba = PNV10_XSCOM_PIB_SPIC_BASE +
|
|
s->spic_num * PNV10_XSCOM_PIB_SPIC_SIZE;
|
|
uint32_t reg[] = {
|
|
cpu_to_be32(spic_pcba),
|
|
cpu_to_be32(PNV10_XSCOM_PIB_SPIC_SIZE)
|
|
};
|
|
name = g_strdup_printf("pnv_spi@%x", spic_pcba);
|
|
s_offset = fdt_add_subnode(fdt, offset, name);
|
|
_FDT(s_offset);
|
|
|
|
_FDT(fdt_setprop(fdt, s_offset, "reg", reg, sizeof(reg)));
|
|
_FDT(fdt_setprop(fdt, s_offset, "compatible", compat, sizeof(compat)));
|
|
_FDT((fdt_setprop_cell(fdt, s_offset, "spic_num#", s->spic_num)));
|
|
return 0;
|
|
}
|
|
|
|
static void pnv_spi_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
PnvXScomInterfaceClass *xscomc = PNV_XSCOM_INTERFACE_CLASS(klass);
|
|
|
|
xscomc->dt_xscom = pnv_spi_dt_xscom;
|
|
|
|
dc->desc = "PowerNV SPI";
|
|
dc->realize = pnv_spi_realize;
|
|
dc->reset = do_reset;
|
|
device_class_set_props(dc, pnv_spi_properties);
|
|
}
|
|
|
|
static const TypeInfo pnv_spi_info = {
|
|
.name = TYPE_PNV_SPI,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(PnvSpi),
|
|
.class_init = pnv_spi_class_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ TYPE_PNV_XSCOM_INTERFACE },
|
|
{ }
|
|
}
|
|
};
|
|
|
|
static void pnv_spi_register_types(void)
|
|
{
|
|
type_register_static(&pnv_spi_info);
|
|
}
|
|
|
|
type_init(pnv_spi_register_types);
|