429 lines
9.8 KiB
C
429 lines
9.8 KiB
C
/*
|
|
* 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;
|
|
}
|