| /* |
| * Copyright (c) 2023 DENX Software Engineering GmbH |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "oa_tc6.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(oa_tc6, CONFIG_ETHERNET_LOG_LEVEL); |
| |
| 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) |
| { |
| struct net_buf *buf_rx = NULL; |
| uint32_t hdr, ftr; |
| uint8_t sbo, ebo; |
| int ret; |
| |
| /* |
| * Special case - append already received data (extracted from previous |
| * chunk) to new packet. |
| */ |
| if (tc6->concat_buf) { |
| net_pkt_append_buffer(pkt, tc6->concat_buf); |
| tc6->concat_buf = NULL; |
| } |
| |
| do { |
| buf_rx = net_pkt_get_frag(pkt, tc6->cps, 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, 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); |
| } |
| } |
| } |
| |
| net_pkt_append_buffer(pkt, buf_rx); |
| buf_rx->len = tc6->cps; |
| |
| 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->len = ebo; |
| /* |
| * Exit when complete packet is read and added to |
| * struct net_pkt |
| */ |
| break; |
| } |
| } while (tc6->rca > 0); |
| |
| return 0; |
| |
| unref_buf: |
| net_buf_unref(buf_rx); |
| return ret; |
| } |