From 1cef7f3250efef663b0f0ce33b7b4768ee97c1b9 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Tue, 8 Aug 2023 13:00:30 +0200 Subject: [PATCH] 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 --- CODEOWNERS | 1 + drivers/ethernet/oa_tc6.c | 393 ++++++++++++++++++++++++++++++++++++++ drivers/ethernet/oa_tc6.h | 233 ++++++++++++++++++++++ 3 files changed, 627 insertions(+) create mode 100644 drivers/ethernet/oa_tc6.c create mode 100644 drivers/ethernet/oa_tc6.h diff --git a/CODEOWNERS b/CODEOWNERS index f339c8958fe..f3d2bb732c9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/drivers/ethernet/oa_tc6.c b/drivers/ethernet/oa_tc6.c new file mode 100644 index 00000000000..0fa1f105581 --- /dev/null +++ b/drivers/ethernet/oa_tc6.c @@ -0,0 +1,393 @@ +/* + * 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); + +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; +} diff --git a/drivers/ethernet/oa_tc6.h b/drivers/ethernet/oa_tc6.h new file mode 100644 index 00000000000..7a9337e7c5d --- /dev/null +++ b/drivers/ethernet/oa_tc6.h @@ -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 +#include +#include +#include +#include +#include + +#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__ */