driver: eth: Implementation of Open Alliance's TC6 T1S communication

Those files provide generic functions to handle transmission between chip
conforming OA TC6 standard and Zephyr's network stack represented by
struct net_pkt.

The communication is performed via SPI and is focused on reduced memory
footprint (works with SOC equipped with 32 KiB of RAM) and robustness of
operation.

Signed-off-by: Lukasz Majewski <lukma@denx.de>
This commit is contained in:
Lukasz Majewski 2023-08-08 13:00:30 +02:00 committed by Carles Cufí
parent 345f079e49
commit 1cef7f3250
3 changed files with 627 additions and 0 deletions

View file

@ -305,6 +305,7 @@
/drivers/ethernet/*xlnx_gem* @ibirnbaum
/drivers/ethernet/*smsc91x* @sgrrzhf
/drivers/ethernet/*adin2111* @GeorgeCGV
/drivers/ethernet/*oa_tc6* @lmajewski
/drivers/ethernet/phy/ @rlubos @tbursztyka @arvinf @jukkar
/drivers/ethernet/phy/*adin2111* @GeorgeCGV
/drivers/mdio/ @rlubos @tbursztyka @arvinf

393
drivers/ethernet/oa_tc6.c Normal file
View file

@ -0,0 +1,393 @@
/*
* 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_set_protected_ctrl(struct oa_tc6 *tc6, bool prote)
{
uint32_t val;
int ret;
ret = oa_tc6_reg_read(tc6, OA_CONFIG0, &val);
if (ret < 0) {
return ret;
}
if (prote) {
val |= OA_CONFIG0_PROTE;
} else {
val &= ~OA_CONFIG0_PROTE;
}
ret = oa_tc6_reg_write(tc6, OA_CONFIG0, val);
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;
chunks = (len / tc6->cps) + 1;
/* 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;
}
static void oa_tc6_update_status(struct oa_tc6 *tc6, uint32_t ftr)
{
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);
}
int oa_tc6_update_buf_info(struct oa_tc6 *tc6)
{
uint32_t val;
int ret;
ret = oa_tc6_reg_read(tc6, OA_BUFSTS, &val);
if (ret < 0) {
return ret;
}
tc6->rca = FIELD_GET(OA_BUFSTS_RCA, val);
tc6->txc = FIELD_GET(OA_BUFSTS_TXC, val);
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);
oa_tc6_update_status(tc6, *ftr);
return 0;
}
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;
uint8_t chunks, sbo, ebo;
uint32_t hdr, ftr;
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;
}
for (chunks = tc6->rca; chunks; chunks--) {
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_ERR("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;
}
}
return 0;
unref_buf:
net_buf_unref(buf_rx);
return ret;
}

233
drivers/ethernet/oa_tc6.h Normal file
View file

@ -0,0 +1,233 @@
/*
* Copyright (c) 2023 DENX Software Engineering GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef OA_TC6_CFG_H__
#define OA_TC6_CFG_H__
#include <stdint.h>
#include <stdbool.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/net/net_pkt.h>
#define MMS_REG(m, r) ((((m) & GENMASK(3, 0)) << 16) | ((r) & GENMASK(15, 0)))
/* Memory Map Sector (MMS) 0 */
#define OA_ID MMS_REG(0x0, 0x000) /* expect 0x11 */
#define OA_PHYID MMS_REG(0x0, 0x001)
#define OA_RESET MMS_REG(0x0, 0x003)
#define OA_RESET_SWRESET BIT(0)
#define OA_CONFIG0 MMS_REG(0x0, 0x004)
#define OA_CONFIG0_SYNC BIT(15)
#define OA_CONFIG0_PROTE BIT(5)
#define OA_STATUS0 MMS_REG(0x0, 0x008)
#define OA_STATUS0_RESETC BIT(6)
#define OA_STATUS1 MMS_REG(0x0, 0x009)
#define OA_BUFSTS MMS_REG(0x0, 0x00B)
#define OA_BUFSTS_TXC GENMASK(15, 8)
#define OA_BUFSTS_RCA GENMASK(7, 0)
#define OA_IMASK0 MMS_REG(0x0, 0x00C)
#define OA_IMASK0_TXPEM BIT(0)
#define OA_IMASK0_TXBOEM BIT(1)
#define OA_IMASK0_TXBUEM BIT(2)
#define OA_IMASK0_RXBOEM BIT(3)
#define OA_IMASK0_LOFEM BIT(4)
#define OA_IMASK0_HDREM BIT(5)
#define OA_IMASK1 MMS_REG(0x0, 0x00D)
#define OA_IMASK0_UV18M BIT(19)
/* OA Control header */
#define OA_CTRL_HDR_DNC BIT(31)
#define OA_CTRL_HDR_HDRB BIT(30)
#define OA_CTRL_HDR_WNR BIT(29)
#define OA_CTRL_HDR_AID BIT(28)
#define OA_CTRL_HDR_MMS GENMASK(27, 24)
#define OA_CTRL_HDR_ADDR GENMASK(23, 8)
#define OA_CTRL_HDR_LEN GENMASK(7, 1)
#define OA_CTRL_HDR_P BIT(0)
/* OA Data header */
#define OA_DATA_HDR_DNC BIT(31)
#define OA_DATA_HDR_SEQ BIT(30)
#define OA_DATA_HDR_NORX BIT(29)
#define OA_DATA_HDR_DV BIT(21)
#define OA_DATA_HDR_SV BIT(20)
#define OA_DATA_HDR_SWO GENMASK(19, 16)
#define OA_DATA_HDR_EV BIT(14)
#define OA_DATA_HDR_EBO GENMASK(13, 8)
#define OA_DATA_HDR_P BIT(0)
/* OA Data footer */
#define OA_DATA_FTR_EXST BIT(31)
#define OA_DATA_FTR_HDRB BIT(30)
#define OA_DATA_FTR_SYNC BIT(29)
#define OA_DATA_FTR_RCA GENMASK(28, 24)
#define OA_DATA_FTR_DV BIT(21)
#define OA_DATA_FTR_SV BIT(20)
#define OA_DATA_FTR_SWO GENMASK(19, 16)
#define OA_DATA_FTR_FD BIT(15)
#define OA_DATA_FTR_EV BIT(14)
#define OA_DATA_FTR_EBO GENMASK(13, 8)
#define OA_DATA_FTR_TXC GENMASK(5, 1)
#define OA_DATA_FTR_P BIT(0)
#define OA_TC6_HDR_SIZE 4
#define OA_TC6_FTR_SIZE 4
#define OA_TC6_BUF_ALLOC_TIMEOUT K_MSEC(10)
/**
* @brief OA TC6 data.
*/
struct oa_tc6 {
/** Pointer to SPI device */
const struct spi_dt_spec *spi;
/** OA data payload (chunk) size */
uint8_t cps;
/**
* Number of available chunks buffers in OA TC6 device to store
* data for transmission
*/
uint8_t txc;
/** Number of available chunks to read from OA TC6 device */
uint8_t rca;
/** Indication of pending interrupt in OA TC6 device */
bool exst;
/** Indication of OA TC6 device being ready for transmission */
bool sync;
/** Indication of protected control transmission mode */
bool protected;
/** Pointer to network buffer concatenated from received chunk */
struct net_buf *concat_buf;
};
typedef struct {
uint32_t address;
uint32_t value;
} oa_mem_map_t;
/**
* @brief Calculate parity bit from data
*
* @param x data to calculate parity
*
* @return 0 if number of ones is odd, 1 otherwise.
*/
static inline bool oa_tc6_get_parity(const uint32_t x)
{
uint32_t y;
y = x ^ (x >> 1);
y = y ^ (y >> 2);
y = y ^ (y >> 4);
y = y ^ (y >> 8);
y = y ^ (y >> 16);
return !(y & 1);
}
/**
* @brief Read OA TC6 compliant device single register
*
* @param tc6 OA TC6 specific data
*
* @param reg register to read
* @param val pointer to variable to store read value
*
* @return 0 if read was successful, <0 otherwise.
*/
int oa_tc6_reg_read(struct oa_tc6 *tc6, const uint32_t reg, uint32_t *val);
/**
* @brief Write to OA TC6 compliant device a single register
*
* @param tc6 OA TC6 specific data
*
* @param reg register to read
* @param val data to send to device
*
* @return 0 if write was successful, <0 otherwise.
*/
int oa_tc6_reg_write(struct oa_tc6 *tc6, const uint32_t reg, uint32_t val);
/**
* @brief Enable or disable the protected mode for control transactions
*
* @param tc6 OA TC6 specific data
*
* @param prote enable or disable protected control transactions
*
* @return 0 if operation was successful, <0 otherwise.
*/
int oa_tc6_set_protected_ctrl(struct oa_tc6 *tc6, bool prote);
/**
* @brief Send OA TC6 data chunks to the device
*
* @param tc6 OA TC6 specific data
*
* @param pkt network packet to be send
*
* @return 0 if data send was successful, <0 otherwise.
*/
int oa_tc6_send_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt);
/**
* @brief Read data chunks from OA TC6 device
*
* @param tc6 OA TC6 specific data
*
* @param pkt network packet to store received data
*
* @return 0 if read was successful, <0 otherwise.
*/
int oa_tc6_read_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt);
/**
* @brief Perform SPI transfer of single chunk from/to OA TC6 device
*
* @param tc6 OA TC6 specific data
*
* @param buf_rx buffer to store read data
*
* @param buf_tx buffer with data to send
*
* @param hdr OA TC6 data transmission header value
*
* @param ftr poniter to OA TC6 data received footer
*
* @return 0 if transmission was successful, <0 otherwise.
*/
int oa_tc6_chunk_spi_transfer(struct oa_tc6 *tc6, uint8_t *buf_rx, uint8_t *buf_tx,
uint32_t hdr, uint32_t *ftr);
/**
* @brief Read status from OA TC6 device
*
* @param tc6 OA TC6 specific data
*
* @param ftr poniter to OA TC6 data received footer
*
* @return 0 if successful, <0 otherwise.
*/
int oa_tc6_read_status(struct oa_tc6 *tc6, uint32_t *ftr);
/**
* @brief Read from OA TC6 device and update buffer information
*
* @param tc6 OA TC6 specific data
*
* @return 0 if successful, <0 otherwise.
*/
int oa_tc6_update_buf_info(struct oa_tc6 *tc6);
#endif /* OA_TC6_CFG_H__ */