799 lines
18 KiB
C
799 lines
18 KiB
C
/*
|
|
* Copyright (c) 2024 Nordic Semiconductor ASA.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <zephyr/debug/mipi_stp_decoder.h>
|
|
#include <string.h>
|
|
|
|
#if defined(CONFIG_CPU_CORTEX_M) && \
|
|
!defined(CONFIG_CPU_CORTEX_M0) && \
|
|
!defined(CONFIG_CPU_CORTEX_M0PLUS)
|
|
#define UNALIGNED_ACCESS_SUPPORTED 1
|
|
#else
|
|
#define UNALIGNED_ACCESS_SUPPORTED 0
|
|
#endif
|
|
|
|
enum stp_state {
|
|
STP_STATE_OP,
|
|
STP_STATE_DATA,
|
|
STP_STATE_TS,
|
|
STP_STATE_OUT_OF_SYNC,
|
|
};
|
|
|
|
enum stp_id {
|
|
STP_NULL,
|
|
STP_M8,
|
|
STP_MERR,
|
|
STP_C8,
|
|
STP_D8,
|
|
STP_D16,
|
|
STP_D32,
|
|
STP_D64,
|
|
STP_D8MTS,
|
|
STP_D16MTS,
|
|
STP_D32MTS,
|
|
STP_D64MTS,
|
|
STP_D4,
|
|
STP_D4MTS,
|
|
STP_FLAG_TS,
|
|
STP_VERSION,
|
|
STP_TAG_3NIBBLE_OP = STP_VERSION,
|
|
STP_NULL_TS,
|
|
STP_USER,
|
|
STP_USER_TS,
|
|
STP_TIME,
|
|
STP_TIME_TS,
|
|
STP_TRIG,
|
|
STP_TRIG_TS,
|
|
STP_FREQ,
|
|
STP_FREQ_TS,
|
|
STP_XSYNC,
|
|
STP_XSYNC_TS,
|
|
STP_FREQ_40,
|
|
STP_TAG_4NIBBLE_OP = STP_FREQ_40,
|
|
STP_FREQ_40_TS,
|
|
STP_DIP,
|
|
STP_M16,
|
|
STP_TAG_2NIBBLE_OP = STP_M16,
|
|
STP_GERR,
|
|
STP_C16,
|
|
STP_D8TS,
|
|
STP_D16TS,
|
|
STP_D32TS,
|
|
STP_D64TS,
|
|
STP_D8M,
|
|
STP_D16M,
|
|
STP_D32M,
|
|
STP_D64M,
|
|
STP_D4TS,
|
|
STP_D4M,
|
|
STP_FLAG,
|
|
STP_ASYNC,
|
|
STP_INVALID,
|
|
STP_OP_MAX
|
|
};
|
|
|
|
#define STP_LONG_OP_ID 0xF
|
|
#define STP_2B_OP_ID 0xF0
|
|
|
|
#define STP_VAR_DATA 0xff
|
|
|
|
typedef void (*stp_cb)(uint64_t data, uint64_t ts);
|
|
|
|
struct stp_item {
|
|
const char *name;
|
|
enum stp_id type;
|
|
uint8_t id[3];
|
|
uint8_t id_ncnt;
|
|
uint8_t d_ncnt;
|
|
bool has_ts;
|
|
stp_cb cb;
|
|
};
|
|
|
|
#define STP_ITEM(_type, _id, _id_ncnt, _d_ncnt, _has_ts, _cb) \
|
|
{ \
|
|
.name = STRINGIFY(_type), .type = _type, .id = {__DEBRACKET _id}, \
|
|
.id_ncnt = _id_ncnt, .d_ncnt = _d_ncnt, \
|
|
.has_ts = _has_ts, .cb = (stp_cb)_cb \
|
|
}
|
|
|
|
static struct mipi_stp_decoder_config cfg;
|
|
static uint64_t prev_ts;
|
|
static uint64_t base_ts;
|
|
static enum stp_state state;
|
|
static size_t ntotal;
|
|
static size_t ncnt;
|
|
static size_t noff;
|
|
static uint16_t curr_ch;
|
|
|
|
static void data4_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA4, d, NULL, false);
|
|
}
|
|
|
|
static void data8_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA8, d, NULL, false);
|
|
}
|
|
|
|
static void data16_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA16, d, NULL, false);
|
|
}
|
|
|
|
static void data32_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA32, d, NULL, false);
|
|
}
|
|
|
|
static void data64_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA64, d, NULL, false);
|
|
}
|
|
|
|
static void data4_m_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA4, d, NULL, true);
|
|
}
|
|
|
|
static void data8_m_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA8, d, NULL, true);
|
|
}
|
|
|
|
static void data16_m_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA16, d, NULL, true);
|
|
}
|
|
|
|
static void data32_m_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA32, d, NULL, true);
|
|
}
|
|
|
|
static void data64_m_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA64, d, NULL, true);
|
|
}
|
|
|
|
static void data4_ts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA4, d, &ts, false);
|
|
}
|
|
|
|
static void data8_ts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA8, d, &ts, false);
|
|
}
|
|
|
|
static void data16_ts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA16, d, &ts, false);
|
|
}
|
|
|
|
static void data32_ts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA32, d, &ts, false);
|
|
}
|
|
|
|
static void data64_ts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA64, d, &ts, false);
|
|
}
|
|
|
|
static void data4_mts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA4, d, &ts, true);
|
|
}
|
|
|
|
static void data8_mts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA8, d, &ts, true);
|
|
}
|
|
|
|
static void data16_mts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA16, d, &ts, true);
|
|
}
|
|
|
|
static void data32_mts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA32, d, &ts, true);
|
|
}
|
|
|
|
static void data64_mts_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data d = {.data = data};
|
|
|
|
cfg.cb(STP_DATA64, d, &ts, true);
|
|
}
|
|
|
|
static void major_cb(uint64_t id, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
uint16_t m_id = (uint16_t)id;
|
|
union mipi_stp_decoder_data data = {.id = m_id};
|
|
|
|
curr_ch = 0;
|
|
|
|
cfg.cb(STP_DECODER_MAJOR, data, NULL, false);
|
|
}
|
|
|
|
static void channel16_cb(uint64_t id, uint64_t ts)
|
|
{
|
|
uint16_t ch = (uint16_t)id;
|
|
|
|
curr_ch = 0xFF00 & ch;
|
|
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data data = {.id = ch};
|
|
|
|
cfg.cb(STP_DECODER_CHANNEL, data, NULL, false);
|
|
}
|
|
static void channel_cb(uint64_t id, uint64_t ts)
|
|
{
|
|
uint16_t ch = (uint16_t)id;
|
|
|
|
ch |= curr_ch;
|
|
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data data = {.id = ch};
|
|
|
|
cfg.cb(STP_DECODER_CHANNEL, data, NULL, false);
|
|
}
|
|
|
|
static void merror_cb(uint64_t err, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data data = {.err = (uint32_t)err};
|
|
|
|
cfg.cb(STP_DECODER_MERROR, data, NULL, false);
|
|
}
|
|
|
|
static void gerror_cb(uint64_t err, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
union mipi_stp_decoder_data data = {.err = (uint32_t)err};
|
|
|
|
cfg.cb(STP_DECODER_GERROR, data, NULL, false);
|
|
}
|
|
|
|
static void flag_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
ARG_UNUSED(data);
|
|
union mipi_stp_decoder_data dummy = {.dummy = 0};
|
|
|
|
cfg.cb(STP_DECODER_FLAG, dummy, NULL, false);
|
|
}
|
|
|
|
static void flag_ts_cb(uint64_t unused, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(unused);
|
|
union mipi_stp_decoder_data data = {.dummy = 0};
|
|
|
|
cfg.cb(STP_DECODER_FLAG, data, &ts, false);
|
|
}
|
|
|
|
static void version_cb(uint64_t version, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
|
|
curr_ch = 0;
|
|
|
|
union mipi_stp_decoder_data data = {.ver = version};
|
|
|
|
cfg.cb(STP_DECODER_VERSION, data, NULL, false);
|
|
}
|
|
|
|
static void notsup_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
ARG_UNUSED(data);
|
|
|
|
union mipi_stp_decoder_data dummy = {.dummy = 0};
|
|
|
|
cfg.cb(STP_DECODER_NOT_SUPPORTED, dummy, NULL, false);
|
|
}
|
|
|
|
static void freq_cb(uint64_t freq, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
|
|
union mipi_stp_decoder_data data = {.freq = freq};
|
|
|
|
cfg.cb(STP_DECODER_FREQ, data, NULL, false);
|
|
}
|
|
|
|
static void freq_ts_cb(uint64_t freq, uint64_t ts)
|
|
{
|
|
union mipi_stp_decoder_data data = {.freq = freq};
|
|
|
|
cfg.cb(STP_DECODER_FREQ, data, &ts, false);
|
|
}
|
|
|
|
static void null_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
ARG_UNUSED(data);
|
|
|
|
union mipi_stp_decoder_data dummy = {.dummy = 0};
|
|
|
|
cfg.cb(STP_DECODER_NULL, dummy, NULL, false);
|
|
}
|
|
|
|
static void async_cb(uint64_t data, uint64_t ts)
|
|
{
|
|
ARG_UNUSED(ts);
|
|
ARG_UNUSED(data);
|
|
|
|
union mipi_stp_decoder_data dummy = {.dummy = 0};
|
|
|
|
cfg.cb(STP_DECODER_ASYNC, dummy, NULL, false);
|
|
}
|
|
|
|
static const struct stp_item items[] = {
|
|
STP_ITEM(STP_NULL, (0x0), 1, 0, false, null_cb),
|
|
STP_ITEM(STP_M8, (0x1), 1, 2, false, major_cb),
|
|
STP_ITEM(STP_MERR, (0x2), 1, 2, false, merror_cb),
|
|
STP_ITEM(STP_C8, (0x3), 1, 2, false, channel_cb),
|
|
STP_ITEM(STP_D8, (0x4), 1, 2, false, data8_cb),
|
|
STP_ITEM(STP_D16, (0x5), 1, 4, false, data16_cb),
|
|
STP_ITEM(STP_D32, (0x6), 1, 8, false, data32_cb),
|
|
STP_ITEM(STP_D64, (0x7), 1, 16, false, data64_cb),
|
|
STP_ITEM(STP_D8MTS, (0x8), 1, 2, true, data8_mts_cb),
|
|
STP_ITEM(STP_D16MTS, (0x9), 1, 4, true, data16_mts_cb),
|
|
STP_ITEM(STP_D32MTS, (0xa), 1, 8, true, data32_mts_cb),
|
|
STP_ITEM(STP_D64MTS, (0xb), 1, 16, true, data64_mts_cb),
|
|
STP_ITEM(STP_D4, (0xc), 1, 1, false, data4_cb),
|
|
STP_ITEM(STP_D4MTS, (0xd), 1, 1, true, data4_mts_cb),
|
|
STP_ITEM(STP_FLAG_TS, (0xe), 1, 0, true, flag_ts_cb),
|
|
STP_ITEM(STP_VERSION, (0xf0, 0x00), 3, 1, false, version_cb),
|
|
STP_ITEM(STP_NULL_TS, (0xf0, 0x01), 3, 0, true, notsup_cb),
|
|
STP_ITEM(STP_USER, (0xf0, 0x02), 3, 0, false, notsup_cb),
|
|
STP_ITEM(STP_USER_TS, (0xf0, 0x03), 3, 0, true, notsup_cb),
|
|
STP_ITEM(STP_TIME, (0xf0, 0x04), 3, 0, false, notsup_cb),
|
|
STP_ITEM(STP_TIME_TS, (0xf0, 0x05), 3, 0, true, notsup_cb),
|
|
STP_ITEM(STP_TRIG, (0xf0, 0x06), 3, 0, false, notsup_cb),
|
|
STP_ITEM(STP_TRIG_TS, (0xf0, 0x07), 3, 0, true, notsup_cb),
|
|
STP_ITEM(STP_FREQ, (0xf0, 0x08), 3, 8, false, freq_cb),
|
|
STP_ITEM(STP_FREQ_TS, (0xf0, 0x09), 3, 8, true, freq_ts_cb),
|
|
STP_ITEM(STP_XSYNC, (0xf0, 0x0a), 3, 0, false, notsup_cb),
|
|
STP_ITEM(STP_XSYNC_TS, (0xf0, 0x0b), 3, 0, true, notsup_cb),
|
|
STP_ITEM(STP_FREQ_40, (0xf0, 0xf0), 4, 10, false, freq_cb),
|
|
STP_ITEM(STP_FREQ_40_TS, (0xf0, 0xf1), 4, 0, true, notsup_cb),
|
|
STP_ITEM(STP_DIP, (0xf0, 0xf2), 4, 0, false, notsup_cb),
|
|
STP_ITEM(STP_M16, (0xf1), 2, 4, false, major_cb),
|
|
STP_ITEM(STP_GERR, (0xf2), 2, 2, false, gerror_cb),
|
|
STP_ITEM(STP_C16, (0xf3), 2, 4, false, channel16_cb),
|
|
STP_ITEM(STP_D8TS, (0xf4), 2, 2, true, data8_ts_cb),
|
|
STP_ITEM(STP_D16TS, (0xf5), 2, 4, true, data16_ts_cb),
|
|
STP_ITEM(STP_D32TS, (0xf6), 2, 8, true, data32_ts_cb),
|
|
STP_ITEM(STP_D64TS, (0xf7), 2, 16, true, data64_ts_cb),
|
|
STP_ITEM(STP_D8M, (0xf8), 2, 2, false, data8_m_cb),
|
|
STP_ITEM(STP_D16M, (0xf9), 2, 4, false, data16_m_cb),
|
|
STP_ITEM(STP_D32M, (0xfa), 2, 8, false, data32_m_cb),
|
|
STP_ITEM(STP_D64M, (0xfb), 2, 16, false, data64_m_cb),
|
|
STP_ITEM(STP_D4TS, (0xfc), 2, 1, true, data4_ts_cb),
|
|
STP_ITEM(STP_D4M, (0xfd), 2, 1, false, data4_m_cb),
|
|
STP_ITEM(STP_FLAG, (0xfe), 2, 0, false, flag_cb),
|
|
STP_ITEM(STP_ASYNC, (0xff, 0xff, 0xff), 6, 16, false, async_cb),
|
|
STP_ITEM(STP_INVALID, (0x0), 0, 0, false, NULL),
|
|
};
|
|
|
|
/** @brief Decode a nibble and read opcode from the stream.
|
|
*
|
|
* Function reads a nibble and continues or starts decoding of a STP opcode.
|
|
*
|
|
* @param data Pointer to the stream.
|
|
* @param[in,out] noff Offset (in nibbles).
|
|
* @param nlen Length (in nibbles).
|
|
* @param[in,out] ncnt Current number of nibbles
|
|
* @param[in,out] ntotal Number of nibbles in the opcode.
|
|
*
|
|
* @retval STP_INVALID Opcode decoding is in progress.
|
|
* @retval opcode Decoded opcode.
|
|
*/
|
|
static inline uint8_t get_nibble(const uint8_t *data, size_t noff)
|
|
{
|
|
uint8_t ret = data[noff / 2];
|
|
|
|
if (noff & 0x1UL) {
|
|
ret >>= 4;
|
|
}
|
|
|
|
ret &= 0x0F;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void get_nibbles64(const uint8_t *src, size_t src_noff, uint8_t *dst, size_t dst_noff,
|
|
size_t nlen)
|
|
{
|
|
bool src_ba = (src_noff & 0x1UL) == 0;
|
|
bool dst_ba = (dst_noff & 0x1UL) == 0;
|
|
uint32_t *src32 = (uint32_t *)&src[src_noff / 2];
|
|
uint32_t *dst32 = (uint32_t *)&dst[dst_noff / 2];
|
|
|
|
if (nlen == 16) {
|
|
/* dst must be aligned. */
|
|
if (src_ba) {
|
|
dst32[0] = src32[0];
|
|
dst32[1] = src32[1];
|
|
} else {
|
|
uint64_t src0 = src32[0] | ((uint64_t)src32[1] << 32);
|
|
uint64_t src1 = src32[2] | ((uint64_t)src32[3] << 32);
|
|
uint64_t part_a = src0 >> 4;
|
|
uint64_t part_b = src1 << 60;
|
|
uint64_t out = part_a | part_b;
|
|
|
|
dst32[0] = (uint32_t)out;
|
|
dst32[1] = (uint32_t)(out >> 32);
|
|
}
|
|
return;
|
|
}
|
|
|
|
uint64_t src0 = src32[0] | ((uint64_t)src32[1] << 32);
|
|
uint64_t mask = BIT64_MASK(nlen * 4) << (src_ba ? 0 : 4);
|
|
uint64_t src_d = src0 & mask;
|
|
|
|
if (((src_noff ^ dst_noff) & 0x1UL) == 0) {
|
|
/* nothing */
|
|
} else if (dst_ba) {
|
|
src_d >>= 4;
|
|
} else {
|
|
src_d <<= 4;
|
|
}
|
|
|
|
dst32[0] |= (uint32_t)src_d;
|
|
dst32[1] |= (uint32_t)(src_d >> 32);
|
|
}
|
|
|
|
/* Function performs getting nibbles in less efficient way but does not use unaligned
|
|
* access which may not be supported by some platforms.
|
|
*/
|
|
static void get_nibbles_unaligned(const uint8_t *src, size_t src_noff, uint8_t *dst,
|
|
size_t dst_noff, size_t nlen)
|
|
{
|
|
for (size_t i = 0; i < nlen; i++) {
|
|
size_t idx = (src_noff + i) / 2;
|
|
size_t ni = (src_noff + i) & 0x1;
|
|
uint8_t n = src[idx] >> (ni ? 4 : 0);
|
|
size_t d_idx = (dst_noff + i) / 2;
|
|
size_t dni = (dst_noff + i) & 0x1;
|
|
|
|
if (dni == 0) {
|
|
dst[d_idx] = n;
|
|
} else {
|
|
dst[d_idx] |= n << 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void get_nibbles(const uint8_t *src, size_t src_noff, uint8_t *dst, size_t dst_noff,
|
|
size_t nlen)
|
|
{
|
|
if (!UNALIGNED_ACCESS_SUPPORTED) {
|
|
get_nibbles_unaligned(src, src_noff, dst, dst_noff, nlen);
|
|
return;
|
|
}
|
|
|
|
bool src_ba = (src_noff & 0x1UL) == 0;
|
|
bool dst_ba = (dst_noff & 0x1UL) == 0;
|
|
uint32_t *src32 = (uint32_t *)&src[src_noff / 2];
|
|
uint32_t *dst32 = (uint32_t *)&dst[dst_noff / 2];
|
|
|
|
if (nlen > 8) {
|
|
get_nibbles64(src, src_noff, dst, dst_noff, nlen);
|
|
return;
|
|
} else if (nlen == 8) {
|
|
/* dst must be aligned. */
|
|
if (src_ba) {
|
|
dst32[0] = src32[0];
|
|
} else {
|
|
uint32_t part_a = src32[0] >> 4;
|
|
uint32_t part_b = src32[1] << 28;
|
|
|
|
dst32[0] = part_a | part_b;
|
|
}
|
|
return;
|
|
}
|
|
|
|
uint32_t mask = BIT_MASK(nlen * 4) << (src_ba ? 0 : 4);
|
|
uint32_t src_d = src32[0] & mask;
|
|
|
|
if (((src_noff ^ dst_noff) & 0x1UL) == 0) {
|
|
dst32[0] |= src_d;
|
|
} else if (dst_ba) {
|
|
dst32[0] |= (src_d >> 4);
|
|
} else {
|
|
dst32[0] |= (src_d << 4);
|
|
}
|
|
}
|
|
|
|
/* Function swaps nibbles in a byte. */
|
|
static uint8_t swap8(uint8_t byte)
|
|
{
|
|
return (byte << 4) | (byte >> 4);
|
|
}
|
|
|
|
/* Function swaps nibbles in a 16 bit variable. */
|
|
static uint16_t swap16(uint16_t halfword)
|
|
{
|
|
halfword = __builtin_bswap16(halfword);
|
|
uint16_t d1 = (halfword & 0xf0f0) >> 4;
|
|
uint16_t d2 = (halfword & 0x0f0f) << 4;
|
|
|
|
return d1 | d2;
|
|
}
|
|
|
|
/* Function swaps nibbles in a 32 bit word. */
|
|
static uint32_t swap32(uint32_t word)
|
|
{
|
|
word = __builtin_bswap32(word);
|
|
uint32_t d1 = (word & 0xf0f0f0f0) >> 4;
|
|
uint32_t d2 = (word & 0x0f0f0f0f) << 4;
|
|
|
|
return d1 | d2;
|
|
}
|
|
|
|
/* Function swaps nibbles in a 64 bit word. */
|
|
static uint64_t swap64(uint64_t dword)
|
|
{
|
|
uint32_t l = (uint32_t)dword;
|
|
uint32_t u = (uint32_t)(dword >> 32);
|
|
|
|
return ((uint64_t)swap32(l) << 32) | (uint64_t)swap32(u);
|
|
}
|
|
|
|
static void swap_n(uint8_t *data, uint32_t n)
|
|
{
|
|
switch (n) {
|
|
case 2:
|
|
*data = swap8(*data);
|
|
break;
|
|
case 4:
|
|
*(uint16_t *)data = swap16(*(uint16_t *)data);
|
|
break;
|
|
case 8:
|
|
*(uint32_t *)data = swap32(*(uint32_t *)data);
|
|
break;
|
|
case 16:
|
|
*(uint64_t *)data = swap64(*(uint64_t *)data);
|
|
break;
|
|
default:
|
|
*(uint64_t *)data = swap64(*(uint64_t *)data);
|
|
*(uint64_t *)data >>= (4 * (16 - n));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static enum stp_id get_op(const uint8_t *data, size_t *noff, size_t *nlen, size_t *ncnt,
|
|
size_t *ntotal)
|
|
{
|
|
uint8_t op = 0;
|
|
|
|
op = get_nibble(data, *noff);
|
|
|
|
*noff += 1;
|
|
*ncnt += 1;
|
|
if (*ntotal == 0 && *ncnt == 1) {
|
|
/* Starting to read op. */
|
|
/* op code has only 1 nibble. */
|
|
if (op != 0xF) {
|
|
return (enum stp_id)op;
|
|
}
|
|
} else if (*ncnt == 2) {
|
|
if (op == 0xF) {
|
|
/* ASYNC*/
|
|
*ntotal = 6;
|
|
} else if (op != 0) {
|
|
return (enum stp_id)(STP_TAG_2NIBBLE_OP - 1 + op);
|
|
}
|
|
} else if (*ncnt == 3) {
|
|
if (op != 0xf) {
|
|
return (enum stp_id)(STP_TAG_3NIBBLE_OP + op);
|
|
} else if (*ntotal == 0) {
|
|
*ntotal = 4;
|
|
}
|
|
} else if (*ncnt == *ntotal) {
|
|
if (*ntotal == 4) {
|
|
return (enum stp_id)(STP_TAG_4NIBBLE_OP + op);
|
|
} else {
|
|
return STP_ASYNC;
|
|
}
|
|
}
|
|
|
|
return STP_INVALID;
|
|
}
|
|
|
|
void mipi_stp_decoder_sync_loss(void)
|
|
{
|
|
state = STP_STATE_OUT_OF_SYNC;
|
|
ncnt = 0;
|
|
ntotal = 0;
|
|
}
|
|
|
|
int mipi_stp_decoder_decode(const uint8_t *data, size_t len)
|
|
{
|
|
static enum stp_id curr_id = STP_INVALID;
|
|
static uint8_t data_buf[8] __aligned(sizeof(uint64_t));
|
|
static uint8_t ts_buf[8] __aligned(sizeof(uint64_t));
|
|
uint64_t *data64 = (uint64_t *)data_buf;
|
|
uint64_t *ts64 = (uint64_t *)ts_buf;
|
|
size_t nlen = 2 * len;
|
|
|
|
do {
|
|
switch (state) {
|
|
case STP_STATE_OUT_OF_SYNC: {
|
|
uint8_t b = get_nibble(data, noff);
|
|
|
|
noff++;
|
|
if (ncnt < 21 && b == 0xF) {
|
|
ncnt++;
|
|
} else if (ncnt == 21 && b == 0) {
|
|
curr_id = STP_INVALID;
|
|
ncnt = 0;
|
|
|
|
items[STP_ASYNC].cb(0, 0);
|
|
state = STP_STATE_OP;
|
|
} else {
|
|
ncnt = 0;
|
|
}
|
|
break;
|
|
}
|
|
case STP_STATE_OP: {
|
|
curr_id = get_op(data, &noff, &nlen, &ncnt, &ntotal);
|
|
if (curr_id != STP_INVALID) {
|
|
ntotal = items[curr_id].d_ncnt;
|
|
ncnt = 0;
|
|
if (ntotal > 0) {
|
|
state = STP_STATE_DATA;
|
|
data64[0] = 0;
|
|
} else if (items[curr_id].has_ts) {
|
|
state = STP_STATE_TS;
|
|
} else {
|
|
/* item without data and ts, notify. */
|
|
items[curr_id].cb(0, 0);
|
|
curr_id = STP_INVALID;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case STP_STATE_DATA: {
|
|
size_t ncpy = MIN(ntotal - ncnt, nlen - noff);
|
|
|
|
get_nibbles(data, noff, data_buf, ncnt, ncpy);
|
|
|
|
ncnt += ncpy;
|
|
noff += ncpy;
|
|
if (ncnt == ntotal) {
|
|
swap_n(data_buf, ntotal);
|
|
ncnt = 0;
|
|
if (items[curr_id].has_ts) {
|
|
ncnt = 0;
|
|
ntotal = 0;
|
|
state = STP_STATE_TS;
|
|
} else {
|
|
items[curr_id].cb(*data64, 0);
|
|
curr_id = STP_INVALID;
|
|
state = STP_STATE_OP;
|
|
ntotal = 0;
|
|
ncnt = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case STP_STATE_TS:
|
|
if (ntotal == 0 && ncnt == 0) {
|
|
/* TS to be read but length is unknown yet */
|
|
*ts64 = 0;
|
|
ntotal = get_nibble(data, noff);
|
|
noff++;
|
|
/* Values up to 12 represents number of nibbles on which
|
|
* timestamp is encoded. Above are the exceptions:
|
|
* - 13 => 14 nibbles
|
|
* - 14 => 16 nibbles
|
|
*/
|
|
if (ntotal > 12) {
|
|
if (ntotal == 13) {
|
|
ntotal = 14;
|
|
base_ts = ~BIT64_MASK(4 * ntotal) & prev_ts;
|
|
} else {
|
|
ntotal = 16;
|
|
base_ts = 0;
|
|
}
|
|
} else {
|
|
base_ts = ~BIT64_MASK(4 * ntotal) & prev_ts;
|
|
}
|
|
|
|
} else {
|
|
size_t ncpy = MIN(ntotal - ncnt, nlen - noff);
|
|
|
|
get_nibbles(data, noff, ts_buf, ncnt, ncpy);
|
|
ncnt += ncpy;
|
|
noff += ncpy;
|
|
if (ncnt == ntotal) {
|
|
swap_n(ts_buf, ntotal);
|
|
prev_ts = base_ts | *ts64;
|
|
items[curr_id].cb(*data64, prev_ts);
|
|
curr_id = STP_INVALID;
|
|
state = STP_STATE_OP;
|
|
ntotal = 0;
|
|
ncnt = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} while (noff < nlen);
|
|
|
|
noff = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mipi_stp_decoder_init(const struct mipi_stp_decoder_config *config)
|
|
{
|
|
state = config->start_out_of_sync ? STP_STATE_OUT_OF_SYNC : STP_STATE_OP;
|
|
ntotal = 0;
|
|
ncnt = 0;
|
|
cfg = *config;
|
|
prev_ts = 0;
|
|
base_ts = 0;
|
|
noff = 0;
|
|
|
|
return 0;
|
|
}
|