/* * Copyright (c) 2023 DENX Software Engineering GmbH * * SPDX-License-Identifier: Apache-2.0 */ #include "oa_tc6.h" #include LOG_MODULE_REGISTER(oa_tc6, CONFIG_ETHERNET_LOG_LEVEL); /* * When IPv6 support enabled - the minimal size of network buffer * shall be at least 128 bytes (i.e. default value). */ #if defined(CONFIG_NET_IPV6) && (CONFIG_NET_BUF_DATA_SIZE < 128) #error IPv6 requires at least 128 bytes of continuous data to handle headers! #endif int oa_tc6_reg_read(struct oa_tc6 *tc6, const uint32_t reg, uint32_t *val) { uint8_t buf[OA_TC6_HDR_SIZE + 12] = { 0 }; struct spi_buf tx_buf = { .buf = buf, .len = sizeof(buf) }; const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 }; struct spi_buf rx_buf = { .buf = buf, .len = sizeof(buf) }; const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1 }; uint32_t rv, rvn, hdr_bkp, *hdr = (uint32_t *) &buf[0]; int ret = 0; /* * Buffers are allocated for protected (larger) case (by 4 bytes). * When non-protected case - we need to decrase them */ if (!tc6->protected) { tx_buf.len -= sizeof(rvn); rx_buf.len -= sizeof(rvn); } *hdr = FIELD_PREP(OA_CTRL_HDR_DNC, 0) | FIELD_PREP(OA_CTRL_HDR_WNR, 0) | FIELD_PREP(OA_CTRL_HDR_AID, 0) | FIELD_PREP(OA_CTRL_HDR_MMS, reg >> 16) | FIELD_PREP(OA_CTRL_HDR_ADDR, reg) | FIELD_PREP(OA_CTRL_HDR_LEN, 0); /* To read single register len = 0 */ *hdr |= FIELD_PREP(OA_CTRL_HDR_P, oa_tc6_get_parity(*hdr)); hdr_bkp = *hdr; *hdr = sys_cpu_to_be32(*hdr); ret = spi_transceive_dt(tc6->spi, &tx, &rx); if (ret < 0) { return ret; } /* Check if echoed control command header is correct */ rv = sys_be32_to_cpu(*(uint32_t *)&buf[4]); if (hdr_bkp != rv) { LOG_ERR("Header transmission error!"); return -1; } rv = sys_be32_to_cpu(*(uint32_t *)&buf[8]); /* In protected mode read data is followed by its compliment value */ if (tc6->protected) { rvn = sys_be32_to_cpu(*(uint32_t *)&buf[12]); if (rv != ~rvn) { LOG_ERR("Protected mode transmission error!"); return -1; } } *val = rv; return ret; } int oa_tc6_reg_write(struct oa_tc6 *tc6, const uint32_t reg, uint32_t val) { uint8_t buf_tx[OA_TC6_HDR_SIZE + 12] = { 0 }; uint8_t buf_rx[OA_TC6_HDR_SIZE + 12] = { 0 }; struct spi_buf tx_buf = { .buf = buf_tx, .len = sizeof(buf_tx) }; const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 }; struct spi_buf rx_buf = { .buf = buf_rx, .len = sizeof(buf_rx) }; const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1 }; uint32_t rv, rvn, hdr_bkp, *hdr = (uint32_t *) &buf_tx[0]; int ret; /* * Buffers are allocated for protected (larger) case (by 4 bytes). * When non-protected case - we need to decrase them */ if (!tc6->protected) { tx_buf.len -= sizeof(rvn); rx_buf.len -= sizeof(rvn); } *hdr = FIELD_PREP(OA_CTRL_HDR_DNC, 0) | FIELD_PREP(OA_CTRL_HDR_WNR, 1) | FIELD_PREP(OA_CTRL_HDR_AID, 0) | FIELD_PREP(OA_CTRL_HDR_MMS, reg >> 16) | FIELD_PREP(OA_CTRL_HDR_ADDR, reg) | FIELD_PREP(OA_CTRL_HDR_LEN, 0); /* To read single register len = 0 */ *hdr |= FIELD_PREP(OA_CTRL_HDR_P, oa_tc6_get_parity(*hdr)); hdr_bkp = *hdr; *hdr = sys_cpu_to_be32(*hdr); *(uint32_t *)&buf_tx[4] = sys_cpu_to_be32(val); if (tc6->protected) { *(uint32_t *)&buf_tx[8] = sys_be32_to_cpu(~val); } ret = spi_transceive_dt(tc6->spi, &tx, &rx); if (ret < 0) { return ret; } /* Check if echoed control command header is correct */ rv = sys_be32_to_cpu(*(uint32_t *)&buf_rx[4]); if (hdr_bkp != rv) { LOG_ERR("Header transmission error!"); return -1; } /* Check if echoed value is correct */ rv = sys_be32_to_cpu(*(uint32_t *)&buf_rx[8]); if (val != rv) { LOG_ERR("Header transmission error!"); return -1; } /* * In protected mode check if read value is followed by its * compliment value */ if (tc6->protected) { rvn = sys_be32_to_cpu(*(uint32_t *)&buf_rx[12]); if (val != ~rvn) { LOG_ERR("Protected mode transmission error!"); return -1; } } return ret; } int oa_tc6_reg_rmw(struct oa_tc6 *tc6, const uint32_t reg, uint32_t mask, uint32_t val) { uint32_t tmp; int ret; ret = oa_tc6_reg_read(tc6, reg, &tmp); if (ret < 0) { return ret; } tmp &= ~mask; if (val) { tmp |= val; } return oa_tc6_reg_write(tc6, reg, tmp); } int oa_tc6_set_protected_ctrl(struct oa_tc6 *tc6, bool prote) { int ret = oa_tc6_reg_rmw(tc6, OA_CONFIG0, OA_CONFIG0_PROTE, prote ? OA_CONFIG0_PROTE : 0); if (ret < 0) { return ret; } tc6->protected = prote; return 0; } int oa_tc6_send_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt) { uint16_t len = net_pkt_get_len(pkt); uint8_t oa_tx[tc6->cps]; uint32_t hdr, ftr; uint8_t chunks, i; int ret; if (len == 0) { return -ENODATA; } chunks = len / tc6->cps; if (len % tc6->cps) { chunks++; } /* Check if LAN865x has any free internal buffer space */ if (chunks > tc6->txc) { return -EIO; } /* Transform struct net_pkt content into chunks */ for (i = 1; i <= chunks; i++) { hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1) | FIELD_PREP(OA_DATA_HDR_DV, 1) | FIELD_PREP(OA_DATA_HDR_NORX, 1) | FIELD_PREP(OA_DATA_HDR_SWO, 0); if (i == 1) { hdr |= FIELD_PREP(OA_DATA_HDR_SV, 1); } if (i == chunks) { hdr |= FIELD_PREP(OA_DATA_HDR_EBO, len - 1) | FIELD_PREP(OA_DATA_HDR_EV, 1); } hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr)); ret = net_pkt_read(pkt, oa_tx, len > tc6->cps ? tc6->cps : len); if (ret < 0) { return ret; } ret = oa_tc6_chunk_spi_transfer(tc6, NULL, oa_tx, hdr, &ftr); if (ret < 0) { return ret; } len -= tc6->cps; } return 0; } int oa_tc6_check_status(struct oa_tc6 *tc6) { uint32_t sts; if (!tc6->sync) { LOG_ERR("SYNC: Configuration lost, reset IC!"); return -EIO; } if (tc6->exst) { /* * Just clear any pending interrupts. * The RESETC is handled separately as it requires per * device configuration. */ oa_tc6_reg_read(tc6, OA_STATUS0, &sts); if (sts != 0) { oa_tc6_reg_write(tc6, OA_STATUS0, sts); LOG_WRN("EXST: OA_STATUS0: 0x%x", sts); } oa_tc6_reg_read(tc6, OA_STATUS1, &sts); if (sts != 0) { oa_tc6_reg_write(tc6, OA_STATUS1, sts); LOG_WRN("EXST: OA_STATUS1: 0x%x", sts); } } return 0; } static int oa_tc6_update_status(struct oa_tc6 *tc6, uint32_t ftr) { if (oa_tc6_get_parity(ftr)) { LOG_DBG("OA Status Update: Footer parity error!"); return -EIO; } tc6->exst = FIELD_GET(OA_DATA_FTR_EXST, ftr); tc6->sync = FIELD_GET(OA_DATA_FTR_SYNC, ftr); tc6->rca = FIELD_GET(OA_DATA_FTR_RCA, ftr); tc6->txc = FIELD_GET(OA_DATA_FTR_TXC, ftr); return 0; } int oa_tc6_chunk_spi_transfer(struct oa_tc6 *tc6, uint8_t *buf_rx, uint8_t *buf_tx, uint32_t hdr, uint32_t *ftr) { struct spi_buf tx_buf[2]; struct spi_buf rx_buf[2]; struct spi_buf_set tx; struct spi_buf_set rx; int ret; hdr = sys_cpu_to_be32(hdr); tx_buf[0].buf = &hdr; tx_buf[0].len = sizeof(hdr); tx_buf[1].buf = buf_tx; tx_buf[1].len = tc6->cps; tx.buffers = tx_buf; tx.count = ARRAY_SIZE(tx_buf); rx_buf[0].buf = buf_rx; rx_buf[0].len = tc6->cps; rx_buf[1].buf = ftr; rx_buf[1].len = sizeof(*ftr); rx.buffers = rx_buf; rx.count = ARRAY_SIZE(rx_buf); ret = spi_transceive_dt(tc6->spi, &tx, &rx); if (ret < 0) { return ret; } *ftr = sys_be32_to_cpu(*ftr); return oa_tc6_update_status(tc6, *ftr); } int oa_tc6_read_status(struct oa_tc6 *tc6, uint32_t *ftr) { uint32_t hdr; hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1) | FIELD_PREP(OA_DATA_HDR_DV, 0) | FIELD_PREP(OA_DATA_HDR_NORX, 1); hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr)); return oa_tc6_chunk_spi_transfer(tc6, NULL, NULL, hdr, ftr); } int oa_tc6_read_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt) { const uint16_t buf_rx_size = CONFIG_NET_BUF_DATA_SIZE; struct net_buf *buf_rx = NULL; uint32_t buf_rx_used = 0; uint32_t hdr, ftr; uint8_t sbo, ebo; int ret; /* * Special case - append already received data (extracted from previous * chunk) to new packet. * * This code is NOT used when OA_CONFIG0 RFA [13:12] is set to 01 * (ZAREFE) - so received ethernet frames will always start on the * beginning of new chunks. */ if (tc6->concat_buf) { net_pkt_append_buffer(pkt, tc6->concat_buf); tc6->concat_buf = NULL; } do { if (!buf_rx) { buf_rx = net_pkt_get_frag(pkt, buf_rx_size, OA_TC6_BUF_ALLOC_TIMEOUT); if (!buf_rx) { LOG_ERR("OA RX: Can't allocate RX buffer fordata!"); return -ENOMEM; } } hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1); hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr)); ret = oa_tc6_chunk_spi_transfer(tc6, buf_rx->data + buf_rx_used, NULL, hdr, &ftr); if (ret < 0) { LOG_ERR("OA RX: transmission error: %d!", ret); goto unref_buf; } ret = -EIO; if (oa_tc6_get_parity(ftr)) { LOG_ERR("OA RX: Footer parity error!"); goto unref_buf; } if (!FIELD_GET(OA_DATA_FTR_SYNC, ftr)) { LOG_ERR("OA RX: Configuration not SYNC'ed!"); goto unref_buf; } if (!FIELD_GET(OA_DATA_FTR_DV, ftr)) { LOG_DBG("OA RX: Data chunk not valid, skip!"); goto unref_buf; } sbo = FIELD_GET(OA_DATA_FTR_SWO, ftr) * sizeof(uint32_t); ebo = FIELD_GET(OA_DATA_FTR_EBO, ftr) + 1; if (FIELD_GET(OA_DATA_FTR_SV, ftr)) { /* * Adjust beginning of the buffer with SWO only when * we DO NOT have two frames concatenated together * in one chunk. */ if (!(FIELD_GET(OA_DATA_FTR_EV, ftr) && (ebo <= sbo))) { if (sbo) { net_buf_pull(buf_rx, sbo); } } } if (FIELD_GET(OA_DATA_FTR_EV, ftr)) { /* * Check if received frame shall be dropped - i.e. MAC has * detected error condition, which shall result in frame drop * by the SPI host. */ if (FIELD_GET(OA_DATA_FTR_FD, ftr)) { ret = -EIO; goto unref_buf; } /* * Concatenation of frames in a single chunk - one frame ends * and second one starts just afterwards (ebo == sbo). */ if (FIELD_GET(OA_DATA_FTR_SV, ftr) && (ebo <= sbo)) { tc6->concat_buf = net_buf_clone(buf_rx, OA_TC6_BUF_ALLOC_TIMEOUT); if (!tc6->concat_buf) { LOG_ERR("OA RX: Can't allocate RX buffer for data!"); ret = -ENOMEM; goto unref_buf; } net_buf_pull(tc6->concat_buf, sbo); } /* Set final size of the buffer */ buf_rx_used += ebo; buf_rx->len = buf_rx_used; net_pkt_append_buffer(pkt, buf_rx); /* * Exit when complete packet is read and added to * struct net_pkt */ break; } else { buf_rx_used += tc6->cps; if ((buf_rx_size - buf_rx_used) < tc6->cps) { net_pkt_append_buffer(pkt, buf_rx); buf_rx->len = buf_rx_used; buf_rx_used = 0; buf_rx = NULL; } } } while (tc6->rca > 0); return 0; unref_buf: net_buf_unref(buf_rx); return ret; }