| /****************************************************************************** |
| |
| Copyright (c) 2006-2015 Lantiq Deutschland GmbH |
| Copyright (c) 2015 Lantiq Beteiligungs-GmbH & Co.KG |
| Copyright 2018, Intel Corporation. |
| |
| For licensing information, see the file 'LICENSE' in the root folder of |
| this software module. |
| |
| ******************************************************************************/ |
| |
| /** |
| \file dxs_access.c |
| Implementation of low level access functions. |
| */ |
| |
| /* ========================================================================== */ |
| /* Includes */ |
| /* ========================================================================== */ |
| #include <stdio.h> |
| #include <semaphore.h> |
| |
| #include "dxs_lib.h" |
| #include "dxs_errno.h" |
| #include "dxs_error.h" |
| #include "dxs_spi.h" |
| #include "dxs_access.h" |
| #include "dxs_init.h" |
| #ifdef EVENT_LOGGER_DEBUG |
| #include <sys/ioctl.h> |
| #endif |
| |
| #include "dxslic_spi.h" |
| /* ========================================================================== */ |
| /* Macro definitions */ |
| /* ========================================================================== */ |
| /* SPI header length in bytes */ |
| #define DXS_SPI_HDR_LENGTH 2 /* in bytes */ |
| |
| /* ========================================================================== */ |
| /* Type definitions */ |
| /* ========================================================================== */ |
| |
| /* ========================================================================== */ |
| /* Global variables */ |
| /* ========================================================================== */ |
| |
| /* ========================================================================== */ |
| /* Function prototypes */ |
| /* ========================================================================== */ |
| #if 0 |
| //debug |
| static int32_t dxs_spi_write( |
| int fd, |
| uint8_t offset, |
| uint16_t *pbuf, |
| uint32_t len); |
| |
| static int32_t dxs_spi_read( |
| int fd, |
| uint8_t offset, |
| uint16_t *pbuf, |
| uint32_t len); |
| |
| #endif |
| |
| /* ========================================================================== */ |
| /* Function implementation */ |
| /* ========================================================================== */ |
| #ifdef EVENT_LOGGER_DEBUG |
| static void dxs_el_trace_reg_wr(DXS_DEVICE_t *pDev, |
| int32_t reg_offset, |
| uint16_t *reg_data, |
| int32_t data_count) |
| { |
| EL_IoctlAddLog_t stLog; |
| stLog.nLogType = EL_LOG_TYPE_REG_WR; |
| stLog.nOrygLogType = EL_MAX_LOG_TYPE; |
| stLog.nDevType = DXS_LIB_DEV_TYPE; |
| stLog.nDevNum = pDev->nDevNum; |
| stLog.nChNum = 0; |
| stLog.uLogDetails.stReg_Wr.nReg_offset = (reg_offset); |
| stLog.uLogDetails.stReg_Wr.pReg_data = (uint8_t *)(reg_data); |
| stLog.uLogDetails.stReg_Wr.nDataLength = (data_count); |
| stLog.uLogDetails.stReg_Wr.nCount = (data_count); |
| ioctl(pDev->el_fd, EL_ADD_LOG, (int32_t)&stLog); |
| } |
| |
| static void dxs_el_trace_reg_rd(DXS_DEVICE_t *pDev, |
| int32_t reg_offset, |
| uint16_t *reg_data, |
| int32_t data_count) |
| { |
| EL_IoctlAddLog_t stLog; |
| stLog.nLogType = EL_LOG_TYPE_REG_RD; |
| stLog.nOrygLogType = EL_MAX_LOG_TYPE; |
| stLog.nDevType = DXS_LIB_DEV_TYPE; |
| stLog.nDevNum = pDev->nDevNum; |
| stLog.nChNum = 0; |
| stLog.uLogDetails.stReg_Rd.nReg_offset = (reg_offset); |
| stLog.uLogDetails.stReg_Rd.pReg_data = (uint8_t *)(reg_data); |
| stLog.uLogDetails.stReg_Rd.nDataLength = (data_count); |
| stLog.uLogDetails.stReg_Rd.nCount = (data_count); |
| ioctl(pDev->el_fd, EL_ADD_LOG, (int32_t)&stLog); |
| } |
| #endif |
| |
| static uint32_t dxs_spi_max_frag_size_get() |
| { |
| DXS_DEVICE_t *pDev = dxs_get_dev(0); |
| |
| return (pDev->access == DXS_ACCESS_CSI) ? |
| SPI_MAX_BUFF_SIZE_CSI : SPI_MAXBYTES_SIZE; |
| } |
| |
| /** |
| Write a value to one chip register. |
| |
| \param pDev Pointer to device struct. |
| \param offset Register offset (address). |
| \param pValue 16-bit word value to write. |
| |
| \return |
| Error code from dxs_spi_write(). |
| */ |
| int32_t DXS_RegWrite( DXS_DEVICE_t *pDev, |
| uint8_t offset, |
| uint16_t nValue) |
| { |
| int32_t ret; |
| |
| if (pDev->access == DXS_ACCESS_CSI) |
| { |
| uint16_t tmp[2]; |
| |
| tmp[0] = tmp[1] = nValue; |
| ret = dxs_spi_write(pDev->spidev, offset, tmp, sizeof(tmp)); |
| } |
| else |
| { |
| uint16_t tmp = nValue; |
| |
| ret = dxs_spi_write(pDev->spidev, offset, &tmp, 2); |
| } |
| |
| #ifdef EVENT_LOGGER_DEBUG |
| dxs_el_trace_reg_wr(pDev, offset, &nValue, 2); |
| #endif |
| |
| /* fprintf (stderr, "[rw] %02X: %04X\n", offset, tmp); */ |
| |
| DXS_RETURN(ret); |
| } |
| |
| /** |
| Read a value from one chip register. |
| |
| \param pDev Pointer to device struct. |
| \param offset Register offset (address). |
| \param pValue Pointer to variable where to return |
| the 16-bit word value. |
| |
| \return |
| Error code from dxs_spi_read(). |
| */ |
| int32_t DXS_RegRead( DXS_DEVICE_t *pDev, |
| uint8_t offset, |
| uint16_t *pValue) |
| { |
| int32_t ret; |
| |
| if (pDev->access == DXS_ACCESS_CSI) |
| { |
| uint16_t tmp[2]; |
| |
| ret = dxs_spi_read(pDev->spidev, offset, tmp, sizeof(tmp)); |
| *pValue = tmp[1]; |
| } |
| else |
| { |
| ret = dxs_spi_read(pDev->spidev, offset, pValue, 2); |
| } |
| |
| #ifdef EVENT_LOGGER_DEBUG |
| dxs_el_trace_reg_rd(pDev, offset, pValue, 2); |
| #endif |
| |
| /* fprintf (stderr, "[rr] %02X: %04X\n", offset, *pValue); */ |
| |
| DXS_RETURN(ret); |
| } |
| |
| /** |
| Write multiple consecutive chip registers. |
| |
| \param pDev Pointer to device struct. |
| \param offset Register offset (address). |
| \param pValue Pointer to array with 16-bit word values to write. |
| \param count Number of 16-bit words to write. |
| |
| \return |
| Error code from dxs_spi_write(). |
| */ |
| int32_t DXS_RegWriteMulti( |
| DXS_DEVICE_t *pDev, |
| uint8_t offset, |
| uint16_t *nValue, |
| uint8_t count) |
| { |
| int32_t ret; |
| |
| ret = dxs_spi_write(pDev->spidev, offset, nValue, (2*count)); |
| |
| #ifdef EVENT_LOGGER_DEBUG |
| dxs_el_trace_reg_wr(pDev, offset, nValue, (2*count)); |
| #endif |
| |
| DXS_RETURN(ret); |
| } |
| |
| /** |
| Read multiple consecutive chip registers. |
| |
| \param pDev Pointer to device struct. |
| \param offset Register offset (address). |
| \param pValue Pointer to array where to return the 16-bit word values. |
| \param count Number of 16-bit words to read. |
| |
| \return |
| Error code from dxs_spi_read(). |
| */ |
| int32_t DXS_RegReadMulti( |
| DXS_DEVICE_t *pDev, |
| uint8_t offset, |
| uint16_t *pValue, |
| uint8_t count) |
| { |
| int32_t ret; |
| |
| ret = dxs_spi_read(pDev->spidev, offset, pValue, (2*count)); |
| |
| #ifdef EVENT_LOGGER_DEBUG |
| dxs_el_trace_reg_rd(pDev, offset, pValue, (2*count)); |
| #endif |
| |
| DXS_RETURN(ret); |
| } |
| |
| |
| /* ************************ endianess correct copy ****************************/ |
| |
| /** |
| Copy a 16-bit-word buffer into a byte buffer respecting the endianess. |
| |
| Odd and even offsets and length are handled by this function. |
| In place copy is possible but otherwise the buffers must not overlap. |
| |
| \param pBbuf Pointer to byte buffer. (Destination) |
| \param pWbuf Pointer to 16-bit-word buffer. (Source) |
| \param nWoffset Offset in bytes within the source buffer. |
| \param nB Number of bytes to be copied. |
| */ |
| void DXS_cpw2b ( uint8_t *pBbuf, |
| const uint16_t * const pWbuf, |
| const uint32_t nWoffset, |
| const uint32_t nB) |
| { |
| uint32_t i; |
| uint16_t nWord; |
| |
| for (i = 0; i < nB; i++) |
| { |
| nWord = pWbuf[(nWoffset + i) >> 1]; |
| pBbuf[i] = (uint8_t) |
| ((((nWoffset+i) % 2) ? nWord : nWord >> 8) & 0xFF); |
| } |
| } |
| |
| /** |
| Copy a byte buffer into a 16-bit-word buffer respecting the endianness. |
| |
| In place copy is possible but otherwise the buffers must not overlap. |
| When an odd number of bytes is to be copied the missing byte in the |
| 16-bit-word is filled with zero. |
| |
| \param pWbuf Word buffer. |
| \param pBbuf Byte buffer. |
| \param nB Number of Bytes to be copied. |
| */ |
| void DXS_cpb2w ( uint16_t *pWbuf, |
| const uint8_t * const pBbuf, |
| uint32_t nB) |
| { |
| uint32_t i; |
| uint16_t nWord; |
| |
| for (i=0; i < nB; i+=2) |
| { |
| /* Copy each byte separately into the word buffer. Make sure that when |
| an index out of range is addressed zero is copied instead. */ |
| nWord = (uint16_t)pBbuf[i+0] << 8; |
| if ((i+1) < nB) |
| { |
| /* LSB is only added when index is within range. */ |
| nWord |= (uint16_t)pBbuf[i+1]; |
| } |
| pWbuf[i>>1] = nWord; |
| } |
| } |
| |
| |
| /** |
| Copy a byte buffer into a 32-bit-word buffer respecting the endianness. |
| |
| In place copy is possible but otherwise the buffers must not overlap. |
| When an odd number of bytes is to be copied the missing bytes in the |
| 32-bit-word is filled with zero. |
| |
| \param pWbuf Word buffer. |
| \param pBbuf Byte buffer. |
| \param nB Number of Bytes to be copied. |
| */ |
| void DXS_cpb2dw ( uint32_t *pWbuf, |
| const uint8_t * const pBbuf, |
| uint32_t nB) |
| { |
| uint32_t i; |
| uint32_t nWord; |
| |
| for (i=0; i < nB; i+=4) |
| { |
| /* Copy each byte separately into the word buffer. Make sure that when |
| an index out of range is addressed zero is copied instead. */ |
| nWord = (uint32_t)pBbuf[i+0] << 24; |
| if ((i+1) < nB) |
| nWord |= (uint32_t)pBbuf[i+1] << 16; |
| if ((i+2) < nB) |
| nWord |= (uint32_t)pBbuf[i+2] << 8; |
| if ((i+3) < nB) |
| nWord |= (uint32_t)pBbuf[i+3]; |
| pWbuf[i>>2] = nWord; |
| } |
| } |
| |
| /***************************** SPI bus adaption ******************************/ |
| |
| /** |
| Write a number of bytes to a given DXS register using SPI. |
| |
| If the length of the data exceeds the SPI_MAXBYTES_SIZE this function |
| fragments the data into multiple blocks and calls the SPI write interface |
| repeatedly until all data is transmitted. |
| |
| \param fd Filedescriptor of the SPI driver. |
| \param offset DXS register address. |
| \param pbuf Pointer to buffer with data to be written. |
| \param len Number of bytes to write. |
| |
| \return |
| DXS_statusOk or error code. |
| */ |
| int32_t dxs_spi_write(int fd, uint8_t offset, uint16_t *pbuf, uint32_t len) |
| { |
| uint8_t tx_buf[SPI_MAXBYTES_SIZE]; |
| uint32_t tx_buf_length, |
| written, |
| fragment_length; |
| int32_t ret = DXS_statusOk; |
| |
| uint32_t max_frag_sz = dxs_spi_max_frag_size_get(); |
| |
| /* TODO: len and offset assertion */ |
| /* TODO: setup SPI */ |
| |
| /* This variable counts the number of bytes already written. */ |
| written = 0; |
| /* Build SPI header (16-bit word) */ |
| #if SPI_IOC_WR_BITS_PER_WORD_8BITS |
| /* SPI_IOC_WR_BITS_PER_WORD is 8bits */ |
| tx_buf[0] = 0x7E; |
| tx_buf[1] = offset; |
| |
| if (offset == DXS_REG_CSI_M_CFG || offset == DXS_REG_CSI_M_STAT) |
| { |
| tx_buf[1] <<= 1; |
| } |
| #else |
| /* SPI_IOC_WR_BITS_PER_WORD is 16bits */ |
| tx_buf[0] = offset; |
| tx_buf[1] = 0x7E; |
| |
| if (offset == DXS_REG_CSI_M_CFG || offset == DXS_REG_CSI_M_STAT) |
| { |
| tx_buf[0] <<= 1; |
| } |
| #endif |
| |
| /* The tx_buf now contains the SPI header. */ |
| tx_buf_length = DXS_SPI_HDR_LENGTH; |
| |
| while ((ret == DXS_statusOk) && (written < len)) |
| { |
| /* Calculate the amount of payload bytes that can be sent during one |
| write call. */ |
| fragment_length = len - written; /* remaining bytes to transfer */ |
| if (fragment_length > (max_frag_sz - tx_buf_length)) |
| { |
| /* Limit the transfer to the maximum the SPI can transmit. */ |
| fragment_length = (max_frag_sz - tx_buf_length); |
| } |
| #if SPI_IOC_WR_BITS_PER_WORD_8BITS |
| /* SPI_IOC_WR_BITS_PER_WORD is 8bits */ |
| |
| /* Copy the payload into the write buffer with correct endianess. */ |
| DXS_cpw2b(tx_buf+tx_buf_length, pbuf, written, fragment_length); |
| #else |
| /* SPI_IOC_WR_BITS_PER_WORD is 16bits */ |
| memcpy(tx_buf+tx_buf_length, (uint8_t *)pbuf+written, fragment_length); |
| #endif |
| /* Increment by the number of payload bytes written. */ |
| tx_buf_length += fragment_length; |
| written += fragment_length; |
| |
| /* Access SPI for writing (rx buffer is NULL). */ |
| ret = dxs_spi_linux_read_write(fd, tx_buf, NULL, tx_buf_length); |
| /* ret is evaluated before the next iteration. */ |
| |
| /* Preserve and reuse the SPI header for the next fragment. */ |
| tx_buf_length = DXS_SPI_HDR_LENGTH; |
| } |
| |
| DXS_RETURN(ret); |
| } |
| |
| /** |
| Read a number of bytes from a given DXS register using SPI. |
| |
| If the length of the data to be read exceeds the SPI_MAXBYTES_SIZE this |
| function calls the SPI read function repeatedly until all data is read |
| and reassembles the data in the return buffer. |
| |
| \param fd Filedescriptor of the SPI driver. |
| \param offset DXS register address. |
| \param pbuf Pointer to buffer where to return the data. |
| \param len Number of bytes to be read. |
| |
| \return |
| DXS_statusOk or error code. |
| */ |
| int32_t dxs_spi_read(int fd, uint8_t offset, uint16_t *pbuf, uint32_t len) |
| { |
| uint8_t tx_buf[SPI_MAXBYTES_SIZE] = {0}, |
| rx_buf[SPI_MAXBYTES_SIZE] = {0}; |
| uint32_t tx_buf_length, |
| read, |
| fragment_length; |
| int32_t ret = DXS_statusOk; |
| |
| uint32_t max_frag_sz = dxs_spi_max_frag_size_get(); |
| |
| /* TODO: len and offset assertion */ |
| /* TODO: setup SPI */ |
| |
| /* tx_buf is the buffer for writing the read request. Because SPI in general |
| is full duplex the complete buffer is set to zero to avoid sending random |
| data after the SPI header that we set below. */ |
| /* Build SPI header (16-bit word) */ |
| #if SPI_IOC_WR_BITS_PER_WORD_8BITS |
| /* SPI_IOC_WR_BITS_PER_WORD is 8bits */ |
| tx_buf[0] = 0xBE; |
| tx_buf[1] = offset; |
| |
| if (offset == DXS_REG_CSI_M_CFG || offset == DXS_REG_CSI_M_STAT) |
| { |
| tx_buf[1] <<= 1; |
| } |
| #else |
| /* SPI_IOC_WR_BITS_PER_WORD is 16bits */ |
| tx_buf[0] = offset; |
| tx_buf[1] = 0xBE; |
| |
| if (offset == DXS_REG_CSI_M_CFG || offset == DXS_REG_CSI_M_STAT) |
| { |
| tx_buf[0] <<= 1; |
| } |
| #endif |
| |
| /* The tx_buf contains now the SPI header. */ |
| tx_buf_length = DXS_SPI_HDR_LENGTH; |
| |
| /* Loop while more data needs to be read. */ |
| for (read = 0; (ret == DXS_statusOk) && (read < len); read += fragment_length) |
| { |
| /* Calculate the amount of payload bytes that can be read during one |
| read call. */ |
| fragment_length = len - read; /* remaining bytes to read */ |
| if (fragment_length > (max_frag_sz - tx_buf_length)) |
| { |
| /* Limit the transfer to the maximum the SPI can transmit. */ |
| fragment_length = (max_frag_sz - tx_buf_length); |
| } |
| |
| /* Access SPI for writing and reading. */ |
| ret = dxs_spi_linux_read_write(fd, tx_buf, rx_buf, |
| tx_buf_length + fragment_length); |
| if (ret == DXS_statusOk) |
| { |
| #if SPI_IOC_WR_BITS_PER_WORD_8BITS |
| /* SPI_IOC_WR_BITS_PER_WORD is 8bits */ |
| |
| /* Copy the byte buffer into the 16-bit-word buffer respecting |
| the endianess. */ |
| DXS_cpb2w (pbuf + (read/2), |
| rx_buf + DXS_SPI_HDR_LENGTH, fragment_length); |
| #else |
| /* SPI_IOC_WR_BITS_PER_WORD is 16bits */ |
| memcpy((uint8_t *)pbuf+read, rx_buf + DXS_SPI_HDR_LENGTH, fragment_length); |
| #endif |
| } |
| |
| /* ret is evaluated before the next iteration. */ |
| } |
| |
| DXS_RETURN(ret); |
| } |
| |
| |
| /** |
| Return the number of payload bytes that can be transferred over SPI |
| without being fragmented into multiple SPI transfers. |
| |
| \return |
| Maximum number of bytes for a single SPI transfer. |
| */ |
| uint32_t DXS_spi_blocksize_get(DXS_DEVICE_t *pDev) |
| { |
| return (pDev->access == DXS_ACCESS_CSI) ? |
| (SPI_MAX_BUFF_SIZE_CSI - DXS_SPI_HDR_LENGTH) : |
| (SPI_MAXBYTES_SIZE - DXS_SPI_HDR_LENGTH); |
| } |
| |
| /** |
| Get device interrupt status. |
| |
| \return |
| none |
| */ |
| void DXS_StatusGet(DXS_DEVICE_t *pDev) |
| { |
| uint16_t nRegInt1 = 0, nRegInt2 = 0; |
| |
| if (pDev->flags & DXS_DEV_ACCESS_INITIALIZED) |
| { |
| DXS_RegRead(pDev, DXS_REG_INT1, &nRegInt1); |
| if (nRegInt1 & DXS_REG_INT1_ERR) |
| { |
| DXS_RegRead(pDev, DXS_REG_INT2, &nRegInt2); |
| if (nRegInt2 & DXS_REG_INT2_CBO_UFL) |
| { |
| fprintf (stderr, "Out-Box Underflow, dev%d\n", pDev->nDevNum); |
| /* acknowledge the interrupt */ |
| DXS_RegWrite(pDev, DXS_REG_INT2, nRegInt2); |
| } |
| if (nRegInt2 & DXS_REG_INT2_CBI_OFL) |
| { |
| fprintf (stderr, "In-Box Overflow, dev%d\n", pDev->nDevNum); |
| /* acknowledge the interrupt */ |
| DXS_RegWrite(pDev, DXS_REG_INT2, nRegInt2); |
| } |
| } |
| |
| /* new data in the outbox */ |
| if ((nRegInt1 & DXS_REG_INT1_CBO_RDY) && |
| (pDev->flags & DXS_DEV_OBX_HND_INITIALIZED)) |
| sem_post(&pDev->obxSemaphore); |
| } |
| } |