/**************************************************************************** * drivers/modem/altair/altmdm_spi.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include "altmdm_sys.h" #include "altmdm_pm.h" #include "altmdm_pm_state.h" #if defined(CONFIG_MODEM_ALTMDM) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define EVENT_BIT(b) (1<<(b)) #define EVENT_NONE (0) #define EVENT_TXREQ EVENT_BIT(0) #define EVENT_RXREQ EVENT_BIT(1) #define EVENT_CANCEL EVENT_BIT(2) #define EVENT_EXIT EVENT_BIT(3) #define EVENT_RXRDY EVENT_BIT(4) #define EVENT_RXBUFRDY EVENT_BIT(5) #define EVENT_TX_DONE EVENT_BIT(6) #define EVENT_RX_DONE EVENT_BIT(7) #define EVENT_SLEEPREQ EVENT_BIT(8) #define EVENT_SLEEP_DONE EVENT_BIT(9) #define EVENT_SV_TIMER_EXP EVENT_BIT(10) #define EVENT_XFERRDY EVENT_BIT(11) #define EVENT_REQMASK (EVENT_TXREQ | EVENT_RXREQ | EVENT_SLEEPREQ | \ EVENT_SV_TIMER_EXP) #define EVENT_TRANS_WAIT (EVENT_TXREQ | EVENT_RXREQ | EVENT_RXBUFRDY | \ EVENT_SLEEPREQ | EVENT_SV_TIMER_EXP | EVENT_EXIT) #if defined(CONFIG_MODEM_ALTMDM_XFER_TASK_PRIORITY) # define XFER_TASK_PRI (CONFIG_MODEM_ALTMDM_XFER_TASK_PRIORITY) #else # define XFER_TASK_PRI (170) #endif #define XFER_TASK_STKSIZE (1536) #define XFER_TASK_NAME "altmdm_xfer_task" #if defined(CONFIG_MODEM_ALTMDM_MAX_PACKET_SIZE) # if ((CONFIG_MODEM_ALTMDM_MAX_PACKET_SIZE & 0x3) != 0) # error MODEM_ALTMDM_MAX_PACKET_SIZE must be aligned 0x4 (4B) # endif # define MAX_PKT_SIZE (CONFIG_MODEM_ALTMDM_MAX_PACKET_SIZE) #else # define MAX_PKT_SIZE (2064) #endif #define UNIT_SIZE (4) #define RX_ENABLE (0) #define RX_DISABLE (1) #define SLEEP_OK (0) #define SLEEP_NG (-EBUSY) /* Timeout is counted in units of millisecond. */ #define WAIT_RXREQ_TIMEOUT (5) #define WAIT_RXREQ_HEADER_TIMEOUT (5 * 1000) #define WAIT_XFERRDY_TIMEOUT (25 * 1000) #if defined(CONFIG_MODEM_ALTMDM_SLEEP_TIMER_VAL) # if (CONFIG_MODEM_ALTMDM_SLEEP_TIMER_VAL < 20) # error MODEM_ALTMDM_SLEEP_TIMER_VAL too small # endif # define SV_TIMER_TIMOUT_VAL (CONFIG_MODEM_ALTMDM_SLEEP_TIMER_VAL) #else # define SV_TIMER_TIMOUT_VAL (20) #endif #define WRITE_WAIT_TIMEOUT (ALTMDM_SYS_FLAG_TMOFEVR) #define SREQ_WAIT_TIMEOUT (ALTMDM_SYS_FLAG_TMOFEVR) #define YIELD_TASK_NOBUFF (100 * 1000) /* microsecond */ /* Defines for transfer mode */ #define MODE_RXDATA (0) /* Data receive mode. */ #define MODE_TXDATA (1) /* Data send mode. */ #define MODE_TRXDATA (2) /* Data send and receive mode. */ #define MODE_RXDATANOBUFF (3) /* Data receive mode when there is no * receiving buffer. */ #define MODE_TRXDATANOBUFF (4) /* Data send and receive mode when * there is no receiving buffer. */ #define MODE_RXINVALID (5) /* Data receive mode when receiving * header is wrong. */ #define MODE_TRXHEADERFAILTXREQ (6) /* Data send and receive mode when * header transfer fails. Send request * triggered. */ #define MODE_TRXHEADERFAILRXREQ (7) /* Data receive mode when header * transfer fails. Receive request * triggered. */ #define MODE_RXRESET (8) /* Reset packet receive mode. */ #define MODE_TRXRESET (9) /* Data send and reset packet receive * mode. */ /* Defines for transfer result code */ #define TRANS_OK (1) /* OK. */ #define TRANS_OK_RXDATANOBUFF (-1) /* OK when MODE_RXDATANOBUFF mode. */ #define TRANS_OK_TRXDATANORXBUFF (-2) /* OK when MODE_TRXDATANOBUFF mode. */ #define TRANS_RXINVALID (-3) /* NG when MODE_RXINVALID mode. */ #define TRANS_WAITRCVRTMO (-4) /* NG when receiver ready timeout. */ #define TRANS_OK_RCVBUFFUL (-5) /* OK when receiver is buffer full. */ #define RESET_BOOTSTAT_BOOTING (0) /* Boot stat: booting */ #define RESET_BOOTSTAT_UPDATING (1) /* Boot stat: updating */ #define STAT_INF_GET_BOOTSTAT (1 << 1) /* Status info: Get bootstatus bit */ #define STAT_INF_RESET (1 << 2) /* Status info: Reset bit */ #define STAT_INF_BUFFFULL (1 << 3) /* Status info: Buffer full bit */ #define STAT_INF_IS_RESET(info) ((info & STAT_INF_RESET) >> 2) #define STAT_INF_IS_BUFF_FULL(info) ((info & STAT_INF_BUFFFULL) >> 3) /**************************************************************************** * Private Data ****************************************************************************/ static struct altmdm_dev_s *g_privdata = NULL; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: svtimer_handler * * Description: * Timeout handler for supervisory timer. * ****************************************************************************/ static void svtimer_handler(int signo, FAR siginfo_t * info, FAR void *ucontext) { FAR struct altmdm_dev_s *priv = (FAR struct altmdm_dev_s *)(info->si_value.sival_ptr); struct altmdm_sys_flagstate_s flag_status; altmdm_sys_referflag(&priv->spidev.xfer_flag, &flag_status); if (!(flag_status.flag_pattern & EVENT_SV_TIMER_EXP)) { altmdm_sys_setflag(&priv->spidev.xfer_flag, EVENT_SV_TIMER_EXP); } } /**************************************************************************** * Name: init_svtimer * * Description: * Initialize supervisory timer. * ****************************************************************************/ static int init_svtimer(FAR struct altmdm_dev_s *priv) { priv->spidev.sleep_param.sv_timerid = NULL; return 0; } /**************************************************************************** * Name: delete_svtimer * * Description: * Delete supervisory timer. * ****************************************************************************/ static int delete_svtimer(FAR struct altmdm_dev_s *priv) { altmdm_sys_stoptimer(priv->spidev.sleep_param.sv_timerid); priv->spidev.sleep_param.sv_timerid = NULL; return 0; } /**************************************************************************** * Name: start_svtimer * * Description: * Start supervisory timer. * ****************************************************************************/ static int start_svtimer(FAR struct altmdm_dev_s *priv) { timer_t timerid; timerid = altmdm_sys_starttimer(SV_TIMER_TIMOUT_VAL, SV_TIMER_TIMOUT_VAL, svtimer_handler, 0, priv); if (timerid == NULL) { return -1; } priv->spidev.sleep_param.sv_timerid = timerid; return 0; } /**************************************************************************** * Name: stop_svtimer * * Description: * Stop supervisory timer. * ****************************************************************************/ static int stop_svtimer(FAR struct altmdm_dev_s *priv) { altmdm_sys_stoptimer(priv->spidev.sleep_param.sv_timerid); priv->spidev.sleep_param.sv_timerid = NULL; return 0; } /**************************************************************************** * Name: do_dmaxfer * * Description: * Execute DMA transfer. * ****************************************************************************/ static int do_dmaxfer(FAR struct altmdm_dev_s *priv, FAR void *tx_buffer, FAR void *rx_buffer, size_t dma_size) { SPI_EXCHANGE(priv->spi, tx_buffer, rx_buffer, dma_size); return 0; } /**************************************************************************** * Name: get_dmasize * * Description: * Get the data size used for DMA transfer. * ****************************************************************************/ static int get_dmasize(FAR struct altmdm_dev_s *priv, int data_size) { return data_size; } /**************************************************************************** * Name: wait_receiverready * * Description: * Wait until receiver is ready. * ****************************************************************************/ static int wait_receiverready(FAR struct altmdm_dev_s *priv, uint32_t timeout_ms) { int ret; uint32_t ptn; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; ret = altmdm_sys_waitflag(&spidev->xfer_flag, EVENT_RXREQ, ALTMDM_SYS_FLAG_WMODEOR, &ptn, timeout_ms); if (ret != 0) { m_err("receiver ready timeout.\n"); return TRANS_WAITRCVRTMO; } return 0; } /**************************************************************************** * Name: wait_xferready * * Description: * Wait until xfer is ready. * ****************************************************************************/ static int wait_xferready(FAR struct altmdm_dev_s *priv) { int ret; uint32_t ptn; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; ret = altmdm_sys_waitflag(&spidev->xferready_flag, EVENT_XFERRDY, ALTMDM_SYS_FLAG_WMODEOR, &ptn, WAIT_XFERRDY_TIMEOUT); if (ret != 0) { m_err("xfer ready timeout.\n"); /* Make it ready to transfer. * It is assumed that modem does not implement reset packet. */ if (!spidev->is_xferready) { spidev->is_xferready = true; m_info("ready to xfer\n"); } return -1; } return 0; } /**************************************************************************** * Name: notify_xferready * * Description: * Notify xfer is ready. * ****************************************************************************/ static int notify_xferready(FAR struct altmdm_dev_s *priv) { int ret; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; ret = altmdm_sys_setflag(&spidev->xferready_flag, EVENT_XFERRDY); return ret; } /**************************************************************************** * Name: init_rxbuffer * * Description: * Initialize the receive buffer used for data transfer. * ****************************************************************************/ static void init_rxbuffer(FAR struct altmdm_dev_s *priv, FAR struct altmdm_spi_rxbuff_s **rxbuff, uint32_t unit_size) { FAR struct altmdm_spi_rxbuff_s *l_buff = NULL; l_buff = (FAR struct altmdm_spi_rxbuff_s *)kmm_malloc (sizeof(struct altmdm_spi_rxbuff_s)); if (l_buff == NULL) { m_err("cannot allocate memory for received buffer\n"); } else { memset(l_buff, 0x00, sizeof(struct altmdm_spi_rxbuff_s)); l_buff->buff_addr = (char *)kmm_malloc(unit_size); if (l_buff->buff_addr == NULL) { m_err("cannot allocate memory for received buffer\n"); kmm_free(l_buff); l_buff = NULL; } else { l_buff->buff_size = unit_size; } } *rxbuff = l_buff; } /**************************************************************************** * Name: uninit_rxbuffer * * Description: * Uninitialize the receive buffer used for data transfer. * ****************************************************************************/ static void uninit_rxbuffer(FAR struct altmdm_dev_s *priv, FAR struct altmdm_spi_rxbuff_s *rxbuff) { if (rxbuff != NULL) { if (rxbuff->buff_addr != NULL) { kmm_free(rxbuff->buff_addr); } kmm_free(rxbuff); } } /**************************************************************************** * Name: alloc_rxbuffer * * Description: * Get the allocated receive buffer. * ****************************************************************************/ static void alloc_rxbuffer(FAR struct altmdm_dev_s *priv, FAR struct altmdm_spi_rxbuff_s **rxbuff, uint32_t size) { if (priv->spidev.rxbuffinfo.free_buff == NULL) { m_err("cannot allocate free rx buffer\n"); } else { *rxbuff = priv->spidev.rxbuffinfo.free_buff; priv->spidev.rxbuffinfo.free_buff = NULL; } } /**************************************************************************** * Name: free_rxbuffer * * Description: * Release allocated receive buffer. * ****************************************************************************/ static void free_rxbuffer(FAR struct altmdm_dev_s *priv, FAR struct altmdm_spi_rxbuff_s *rxbuff) { if (priv->spidev.rxbuffinfo.free_buff != NULL) { m_err("cannot free rx buffer\n"); } else { priv->spidev.rxbuffinfo.free_buff = rxbuff; } } /**************************************************************************** * Name: create_rxbufffifo * * Description: * Create receiving FIFO. This fifo is used when copying to the buffer * specified by the user. * ****************************************************************************/ static void create_rxbufffifo(FAR struct altmdm_dev_s *priv) { FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo; init_rxbuffer(priv, &priv->spidev.rxbuffinfo.free_buff, MAX_PKT_SIZE); fifo->head = NULL; fifo->tail = NULL; altmdm_sys_initcsem(&fifo->csem); } /**************************************************************************** * Name: destroy_rxbufffifo * * Description: * Destroy receiving FIFO. * ****************************************************************************/ static void destroy_rxbufffifo(FAR struct altmdm_dev_s *priv) { FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo; uninit_rxbuffer(priv, priv->spidev.rxbuffinfo.free_buff); altmdm_sys_deletecsem(&fifo->csem); } /**************************************************************************** * Name: put_rxbufffifo * * Description: * Put date into the receiving FIFO. * ****************************************************************************/ static void put_rxbufffifo(FAR struct altmdm_dev_s *priv, FAR struct altmdm_spi_rxbuff_s *rxbuff) { FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo; irqstate_t flags; flags = enter_critical_section(); if (fifo->head == NULL) { fifo->head = rxbuff; fifo->tail = rxbuff; rxbuff->next = NULL; } else { fifo->tail->next = rxbuff; fifo->tail = rxbuff; rxbuff->next = NULL; } leave_critical_section(flags); altmdm_sys_postcsem(&fifo->csem); } /**************************************************************************** * Name: get_rxbufffifo * * Description: * Get date from the receiving FIFO. If the FIFO is empty, * it is kept waiting until data is put in the FIFO. * ****************************************************************************/ static void get_rxbufffifo(FAR struct altmdm_dev_s *priv, FAR struct altmdm_spi_rxbuff_s **rxbuff) { FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo; irqstate_t flags; *rxbuff = NULL; altmdm_sys_waitcsem(&fifo->csem); flags = enter_critical_section(); if (fifo->head != NULL) { *rxbuff = fifo->head; fifo->head = fifo->head->next; } leave_critical_section(flags); } /**************************************************************************** * Name: abort_get_rxbufffifo * * Description: * If waiting for data to be put in the FIFO, then abort it. * ****************************************************************************/ static void abort_get_rxbufffifo(FAR struct altmdm_dev_s *priv) { int val; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; FAR struct altmdm_spi_rxbufffifo_s *fifo = &priv->spidev.rxbuffinfo.fifo; if (!altmdm_sys_getcsemvalue(&fifo->csem, &val)) { if (val < 0) { spidev->rx_param.rxabort = true; altmdm_sys_postcsem(&fifo->csem); } } } /**************************************************************************** * Name: clear_txheader * * Description: * Clear the header for transmission. * ****************************************************************************/ static void clear_txheader(FAR struct altmdm_dev_s *priv) { memset(&priv->spidev.tx_param.header, 0x00, sizeof(struct altmdm_spi_xferhdr_s)); } /**************************************************************************** * Name: clear_rxheader * * Description: * Clear the header for reception. * ****************************************************************************/ static void clear_rxheader(FAR struct altmdm_dev_s *priv) { memset(&priv->spidev.rx_param.header, 0x00, sizeof(struct altmdm_spi_xferhdr_s)); } /**************************************************************************** * Name: set_txheader_datasize * * Description: * Set transmission data size in the header. * ****************************************************************************/ static void set_txheader_datasize(FAR struct altmdm_dev_s *priv, int total_size, int actual_size) { FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header; tx_header->header[0] = (tx_header->header[0] & 0xf0) + ((total_size >> 10) & 0x0000000f); tx_header->header[1] = (total_size >> 2) & 0x000000ff; tx_header->header[2] = ((total_size & 0x00000003) << 6) + ((actual_size >> 8) & 0x0000003f); tx_header->header[3] = (actual_size & 0x000000ff); } /**************************************************************************** * Name: set_txheader_possibleofrx * * Description: * Set flag in the header. The flag indicates that master can receive data. * ****************************************************************************/ static void set_txheader_possibleofrx(FAR struct altmdm_dev_s *priv, bool possible) { FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header; if (!possible) { tx_header->header[0] = 0x80 + (tx_header->header[0] & 0x7f); } else { tx_header->header[0] = 0x00 + (tx_header->header[0] & 0x7f); } } /**************************************************************************** * Name: set_txheader_sleepreq * * Description: * Set flag in the header. The flag indicates that sleep request. * ****************************************************************************/ static void set_txheader_sleepreq(FAR struct altmdm_dev_s *priv) { FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header; tx_header->header[0] = 0x10 + (tx_header->header[0] & 0xef); } /**************************************************************************** * Name: show_txheader * * Description: * Show header for transmission. * ****************************************************************************/ static void show_txheader(FAR struct altmdm_dev_s *priv) { #ifdef MODEM_ALTMDM_DEBUG FAR struct altmdm_spi_xferhdr_s *tx_header = &priv->spidev.tx_param.header; m_info("[TXHDR]=%02x,%02x,%02x,%02x\n", tx_header->header[0], tx_header->header[1], tx_header->header[2], tx_header->header[3]); #endif } /**************************************************************************** * Name: parse_rxheader * * Description: * Parse header for receiving. * ****************************************************************************/ static void parse_rxheader(FAR struct altmdm_dev_s *priv, FAR int32_t *total_size, FAR int32_t *actual_size, FAR int *is_reset, FAR int32_t *is_bufful) { FAR struct altmdm_spi_xferhdr_s *rx_header = &priv->spidev.rx_param.header; m_info("[RXHDR]%02x,%02x,%02x,%02x\n", rx_header->header[0], rx_header->header[1], rx_header->header[2], rx_header->header[3]); *total_size = ((rx_header->header[0] & 0x0f) << 10) + ((rx_header->header[1] & 0xff) << 2) + (rx_header->header[2] >> 6); *actual_size = ((rx_header->header[2] & 0x3f) << 8) + (rx_header->header[3] & 0xff); priv->spidev.rx_param.status_info = (rx_header->header[0] & 0xf0) >> 4; *is_reset = STAT_INF_IS_RESET(priv->spidev.rx_param.status_info); *is_bufful = STAT_INF_IS_BUFF_FULL(priv->spidev.rx_param.status_info); m_info("t=%d a=%d r=%d b=%d\n", *total_size, *actual_size, *is_reset, *is_bufful); return; } /**************************************************************************** * Name: verify_rxheader * * Description: * Verify header for receiving. * ****************************************************************************/ static int verify_rxheader(FAR struct altmdm_dev_s *priv, int total_size, int actual_size) { int calc_total_size; if ((total_size == 0) || (actual_size == 0)) { return -1; } if ((total_size > MAX_PKT_SIZE) || (actual_size > MAX_PKT_SIZE)) { return -1; } if (total_size % UNIT_SIZE) { return -1; } if (total_size != actual_size) { calc_total_size = ((actual_size / UNIT_SIZE) + 1) * UNIT_SIZE; if (total_size != calc_total_size) { return -1; } } return 0; } /**************************************************************************** * Name: do_xferheader * * Description: * Execute header transfer. * ****************************************************************************/ static int do_xferheader(FAR struct altmdm_dev_s *priv, uint32_t is_rxreq, uint32_t is_txreq, uint32_t is_sleepreq, uint32_t is_rcvrready) { int ret; int dma_xfer_size; struct altmdm_spi_dev_s *spidev = &priv->spidev; bool possibleofrx = true; /* Make transfer header */ clear_txheader(priv); if ((is_sleepreq) || (is_txreq)) { DEBUGASSERT(priv->lower); priv->lower->master_request(true); if (is_sleepreq) { set_txheader_sleepreq(priv); } else { set_txheader_datasize(priv, spidev->tx_param.total_size, spidev->tx_param.actual_size); } } if (!is_sleepreq) { alloc_rxbuffer(priv, &spidev->rx_param.rxbuff, MAX_PKT_SIZE); if (spidev->rx_param.rxbuff == NULL) { possibleofrx = false; } set_txheader_possibleofrx(priv, possibleofrx); } show_txheader(priv); /* Wait for Receiver Ready to receive. */ if ((!is_rxreq) && (!is_rcvrready)) { ret = wait_receiverready(priv, WAIT_RXREQ_HEADER_TIMEOUT); if (ret < 0) { goto trans_header_error; } } /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, sizeof(struct altmdm_spi_xferhdr_s)); ret = do_dmaxfer(priv, (FAR void *)&spidev->tx_param.header, (FAR void *)&spidev->rx_param.header, dma_xfer_size); if (ret < 0) { goto trans_header_error; } return ret; trans_header_error: m_err("ERR:%04d Transfer Header Failed. ret = %d.\n", __LINE__, ret); /* Clear Header */ clear_rxheader(priv); set_txheader_datasize(priv, 0, 0); return ret; } /**************************************************************************** * Name: do_receivedata * * Description: * Executes the receive only transfer mode. * ****************************************************************************/ static int do_receivedata(FAR struct altmdm_dev_s *priv) { int ret; int dma_xfer_size; FAR void *rxbuff; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, spidev->rx_param.total_size); rxbuff = spidev->rx_param.rxbuff->buff_addr; /* Do DMA transfer */ ret = do_dmaxfer(priv, NULL, rxbuff, dma_xfer_size); } if (ret < 0) { m_err("Rcv Data Failed. ret = %d.\n", ret); clear_rxheader(priv); } return ret; } /**************************************************************************** * Name: do_senddata * * Description: * Executes the transmission only transfer mode. * ****************************************************************************/ static int do_senddata(FAR struct altmdm_dev_s *priv) { int ret; int dma_xfer_size; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, spidev->tx_param.total_size); /* Do DMA transfer */ ret = do_dmaxfer(priv, (FAR void *)spidev->tx_param.buff_addr, NULL, dma_xfer_size); } if (ret < 0) { m_err("ERR:%04d Snd Data Failed. ret = %d.\n", __LINE__, ret); clear_txheader(priv); } return ret; } /**************************************************************************** * Name: do_trxdata * * Description: * Executes the transmission and receive transfer mode. * ****************************************************************************/ static int do_trxdata(FAR struct altmdm_dev_s *priv) { int ret; int xfer_size; int dma_xfer_size; FAR void *rxbuff; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Choose the larger one. */ if (spidev->tx_param.total_size < spidev->rx_param.total_size) { xfer_size = spidev->rx_param.total_size; } else { xfer_size = spidev->tx_param.total_size; } rxbuff = spidev->rx_param.rxbuff->buff_addr; /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, xfer_size); /* Do DMA transfer */ ret = do_dmaxfer(priv, (FAR void *)spidev->tx_param.buff_addr, rxbuff, dma_xfer_size); } if (ret < 0) { m_err("ERR:%04d Trx Data Failed. ret = %d.\n", __LINE__, ret); clear_txheader(priv); clear_rxheader(priv); } return ret; } /**************************************************************************** * Name: do_receivedata_nobuff * * Description: * Executes the receive only transfer mode. When receiving buffer cannot * be prepared. * ****************************************************************************/ static int do_receivedata_nobuff(FAR struct altmdm_dev_s *priv) { int ret; int dma_xfer_size; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, spidev->rx_param.total_size); /* Do DMA transfer */ ret = do_dmaxfer(priv, NULL, NULL, dma_xfer_size); } if (ret < 0) { m_err("ERR:%04d Rcv Data Nobuff Failed. ret = %d.\n", __LINE__, ret); clear_rxheader(priv); } else { ret = TRANS_OK_RXDATANOBUFF; } return ret; } /**************************************************************************** * Name: do_trxdata_norxbuff * * Description: * Executes the transmission and receive transfer mode. When receiving * buffer cannot be prepared. * ****************************************************************************/ static int do_trxdata_norxbuff(FAR struct altmdm_dev_s *priv) { int ret; int xfer_size; int dma_xfer_size; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Choose the larger one. */ if (spidev->tx_param.total_size < spidev->rx_param.total_size) { xfer_size = spidev->rx_param.total_size; } else { xfer_size = spidev->tx_param.total_size; } /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, xfer_size); /* Do DMA transfer */ ret = do_dmaxfer(priv, (FAR void *)spidev->tx_param.buff_addr, NULL, dma_xfer_size); } if (ret < 0) { m_err("ERR:%04d Trx Data Norxbuff Failed. ret = %d.\n", __LINE__, ret); clear_txheader(priv); clear_rxheader(priv); } else { ret = TRANS_OK_TRXDATANORXBUFF; } return ret; } /**************************************************************************** * Name: do_receivesleepdata * * Description: * Executes the sleep data transfer mode. * ****************************************************************************/ static int do_receivesleepdata(FAR struct altmdm_dev_s *priv, FAR int *resp) { int ret; int dma_xfer_size; char rxbuff[UNIT_SIZE]; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, UNIT_SIZE); /* Do DMA transfer */ ret = do_dmaxfer(priv, NULL, (FAR void *)rxbuff, dma_xfer_size); } if (ret < 0) { m_err("ERR:%04d Rcv Sleep Resp Data Failed. ret = %d.\n", __LINE__, ret); } else { m_info("[SRESP] 0x%02x,0x%02x,0x%02x,0x%02x\n", rxbuff[0], rxbuff[1], rxbuff[2], rxbuff[3]); if (!memcmp(rxbuff, "OK", 2)) { *resp = SLEEP_OK; } else { *resp = SLEEP_NG; } } return ret; } /**************************************************************************** * Name: do_receivereset * * Description: * Executes the reset packet receive only transfer mode. * ****************************************************************************/ static int do_receivereset(FAR struct altmdm_dev_s *priv) { int ret; int dma_xfer_size; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; char rxbuff[UNIT_SIZE]; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, spidev->rx_param.total_size); /* Do DMA transfer */ ret = do_dmaxfer(priv, NULL, (FAR void *)rxbuff, dma_xfer_size); } if (ret < 0) { m_err("ERR:%04d Rcv Reset Failed. ret = %d.\n", __LINE__, ret); clear_rxheader(priv); } else { if ((STAT_INF_GET_BOOTSTAT | STAT_INF_RESET) == (spidev->rx_param. status_info & (STAT_INF_GET_BOOTSTAT | STAT_INF_RESET))) { switch (rxbuff[0]) { case RESET_BOOTSTAT_BOOTING: altmdm_pm_set_bootstatus(priv, MODEM_PM_ERR_RESET_BOOTSTAT_BOOTING); break; case RESET_BOOTSTAT_UPDATING: altmdm_pm_set_bootstatus(priv, MODEM_PM_ERR_RESET_BOOTSTAT_UPDATING); break; default: m_err ("ERR:%04d Invalid payload of reset packet. " \ "%02x,%02x,%02x,%02x\n", __LINE__, rxbuff[0], rxbuff[1], rxbuff[2], rxbuff[3]); break; } } else if (STAT_INF_RESET == (spidev->rx_param.status_info & STAT_INF_RESET)) { altmdm_pm_set_bootstatus(priv, MODEM_PM_ERR_RESET_BOOTSTAT_DONE); } else { m_err("ERR:%04d Unexpected status info. %04x\n", __LINE__, spidev->rx_param.status_info); } } return ret; } /**************************************************************************** * Name: do_trxreset * * Description: * Executes the transmission and receive reset packet transfer mode. * ****************************************************************************/ static int do_trxreset(FAR struct altmdm_dev_s *priv) { int ret; int xfer_size; int dma_xfer_size; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; /* Wait for Receiver Ready to receive. */ ret = wait_receiverready(priv, WAIT_RXREQ_TIMEOUT); if (ret >= 0) { /* Even in the case of a conflict between a reset packet * and a send packet, the send size is compared and the * larger size is sent as the payload. * If the send packet that conflicts with the reset packet * is in ALTCOM command format, the modem may assert. * If it is not in the ALTCOM command format, it will be * discarded by the modem, so dummy data will be sent to * avoid assertion. */ if (spidev->tx_param.total_size < spidev->rx_param.total_size) { xfer_size = spidev->rx_param.total_size; } else { xfer_size = spidev->tx_param.total_size; } /* Get DMA transfer size */ dma_xfer_size = get_dmasize(priv, xfer_size); /* Do DMA transfer */ ret = do_dmaxfer(priv, NULL, NULL, dma_xfer_size); } if (ret < 0) { m_err("ERR:%04d Trx Reset Failed. ret = %d.\n", __LINE__, ret); clear_txheader(priv); clear_rxheader(priv); } return ret; } /**************************************************************************** * Name: do_xfersleep * * Description: * Executes the sleep request. * ****************************************************************************/ static int do_xfersleep(FAR struct altmdm_dev_s *priv, uint32_t is_rcvrready) { int ret; int resp = 0; int is_reset = 0; int32_t total_size; int32_t actual_size; int32_t is_bufful; /* Transfer header for sleep request */ ret = do_xferheader(priv, 0, 0, 1, is_rcvrready); if (ret >= 0) { parse_rxheader(priv, &total_size, &actual_size, &is_reset, &is_bufful); /* Transfer data for sleep request */ ret = do_receivesleepdata(priv, &resp); if (ret >= 0) { ret = resp; } } if (ret < 0) { ret = SLEEP_NG; } DEBUGASSERT(priv && priv->lower); priv->lower->master_request(false); if (is_reset) { if (!priv->spidev.is_xferready) { priv->spidev.is_xferready = true; m_info("ready to xfer\n"); notify_xferready(priv); } altmdm_pm_notify_reset(priv); } return ret; } /**************************************************************************** * Name: decide_xfermode * * Description: * Decide transfer mode. * ****************************************************************************/ static uint32_t decide_xfermode(FAR struct altmdm_dev_s *priv, uint32_t is_rxreq, uint32_t is_txreq, int ret) { int retval; int is_reset; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; uint32_t mode = MODE_RXINVALID; if (ret < 0) { if (is_txreq) { mode = MODE_TRXHEADERFAILTXREQ; } else { mode = MODE_TRXHEADERFAILRXREQ; } if (spidev->rx_param.rxbuff != NULL) { free_rxbuffer(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } } else { parse_rxheader(priv, &spidev->rx_param.total_size, &spidev->rx_param.actual_size, &is_reset, &spidev->tx_param.is_bufful); if (is_rxreq) { retval = verify_rxheader(priv, spidev->rx_param.total_size, spidev->rx_param.actual_size); if (retval != 0) { m_info("RX header:total=0x%02x, actual=0x%02x.\n", spidev->rx_param.total_size, spidev->rx_param.actual_size); if (spidev->rx_param.rxbuff != NULL) { free_rxbuffer(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } if (!is_txreq) { return mode; } is_rxreq = 0; m_info("RX Header invalid. But Send will be done.\n"); } } else if ((spidev->rx_param.actual_size != 0) || (spidev->rx_param.total_size != 0)) { retval = verify_rxheader(priv, spidev->rx_param.total_size, spidev->rx_param.actual_size); if (retval == 0) { is_rxreq = 1; } } /* Diceide transfer mode here. */ if (is_txreq) { if (is_rxreq) { if (is_reset) { if (spidev->rx_param.rxbuff != NULL) { free_rxbuffer(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } mode = MODE_TRXRESET; } else { if (spidev->rx_param.rxbuff == NULL) { mode = MODE_TRXDATANOBUFF; } else { mode = MODE_TRXDATA; spidev->rx_param.rxbuff->rx_size = spidev->rx_param.actual_size; m_info("received size = %d.\n", spidev->rx_param.rxbuff->rx_size); } } } else { if (spidev->rx_param.rxbuff != NULL) { free_rxbuffer(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } mode = MODE_TXDATA; } } else { if (is_reset) { if (spidev->rx_param.rxbuff != NULL) { free_rxbuffer(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } mode = MODE_RXRESET; } else { if (spidev->rx_param.rxbuff == NULL) { mode = MODE_RXDATANOBUFF; } else { mode = MODE_RXDATA; spidev->rx_param.rxbuff->rx_size = spidev->rx_param.actual_size; m_info("received size = %d.\n", spidev->rx_param.rxbuff->rx_size); } } } } clear_rxheader(priv); clear_txheader(priv); return mode; } /**************************************************************************** * Name: done_xfer * * Description: * Notify that transfer has completed. * ****************************************************************************/ static void done_xfer(FAR struct altmdm_dev_s *priv, uint32_t xfer_mode, int ret) { FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; switch (xfer_mode) { case MODE_RXDATA: if (ret < 0) { free_rxbuffer(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } else { put_rxbufffifo(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } break; case MODE_TXDATA: if (spidev->tx_param.is_bufful) { spidev->tx_param.is_bufful = 0; spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL; } else { spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK; } altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE); break; case MODE_TRXDATA: if (spidev->tx_param.is_bufful) { spidev->tx_param.is_bufful = 0; spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL; } else { spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK; } altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE); if (ret < 0) { free_rxbuffer(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } else { put_rxbufffifo(priv, spidev->rx_param.rxbuff); spidev->rx_param.rxbuff = NULL; } break; case MODE_TRXDATANOBUFF: if (spidev->tx_param.is_bufful) { spidev->tx_param.is_bufful = 0; spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL; } else { spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK; } altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE); /* Yield the CPU so that the upper layer can execute * the receiving process. */ nxsig_usleep(YIELD_TASK_NOBUFF); break; case MODE_TRXHEADERFAILTXREQ: spidev->tx_param.result = ret; altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE); break; case MODE_RXRESET: if (!spidev->is_xferready) { spidev->is_xferready = true; m_info("ready to xfer\n"); notify_xferready(priv); } altmdm_pm_notify_reset(priv); break; case MODE_TRXRESET: if (!spidev->is_xferready) { spidev->is_xferready = true; m_info("ready to xfer\n"); notify_xferready(priv); } altmdm_pm_notify_reset(priv); if (spidev->tx_param.is_bufful) { spidev->tx_param.is_bufful = 0; spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK_RCVBUFFUL; } else { spidev->tx_param.result = (ret < 0) ? ret : TRANS_OK; } altmdm_sys_setflag(&spidev->tx_param.done_flag, EVENT_TX_DONE); break; case MODE_RXDATANOBUFF: /* Yield the CPU so that the upper layer can execute * the receiving process. */ nxsig_usleep(YIELD_TASK_NOBUFF); break; case MODE_TRXHEADERFAILRXREQ: case MODE_RXINVALID: break; default: break; } } /**************************************************************************** * Name: done_sleep * * Description: * Notify that sleep request has completed. * ****************************************************************************/ static void done_sleep(FAR struct altmdm_dev_s *priv, int ret) { FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; spidev->sleep_param.result = ret; altmdm_sys_setflag(&spidev->sleep_param.done_flag, EVENT_SLEEP_DONE); } /**************************************************************************** * Name: xfer_task_init * * Description: * Initialize SPI transfer task. * ****************************************************************************/ static void xfer_task_init(FAR struct altmdm_dev_s *priv) { sigset_t mask; sigfillset(&mask); nxsig_procmask(SIG_SETMASK, &mask, NULL); init_svtimer(priv); } /**************************************************************************** * Name: xfer_task * * Description: * ALTMDM SPI transfer task. * ****************************************************************************/ static int xfer_task(int argc, char *argv[]) { int ret; int sleep_result; int res_code; uint32_t is_txreq; uint32_t is_rxreq; uint32_t is_sleepreq; uint32_t is_timerexp; uint32_t do_sleep; uint32_t ptn; uint32_t xfer_mode; uint32_t modem_state; uint32_t is_rcvrready = 0; FAR struct altmdm_dev_s *priv = g_privdata; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; xfer_task_init(priv); while (!spidev->is_not_run) { ret = altmdm_sys_waitflag(&spidev->xfer_flag, EVENT_TRANS_WAIT, ALTMDM_SYS_FLAG_WMODEOR, &ptn, ALTMDM_SYS_FLAG_TMOFEVR); if (ret != 0) { m_err("wait flag failed:%d.\n", ret); continue; } m_info("ptn:%x.\n", ptn); if (ptn & EVENT_REQMASK) { is_txreq = ptn & EVENT_TXREQ; is_rxreq = ptn & EVENT_RXREQ; is_sleepreq = ptn & EVENT_SLEEPREQ; is_timerexp = ptn & EVENT_SV_TIMER_EXP; do_sleep = 0; sleep_result = SLEEP_NG; /* Sleep transition event received. Check if it can sleep. */ if (is_sleepreq || is_timerexp) { /* If data transfer required at the same time, cannot sleep. */ if (!(is_txreq || is_rxreq)) { if (altmdm_pm_cansleep(priv)) { do_sleep = 1; } else if (is_timerexp) { /* Case where modem spontaneously enters sleep state * and timer is not stopped. */ modem_state = altmdm_pm_getinternalstate(); if (modem_state == MODEM_PM_INTERNAL_STATE_SLEEP) { stop_svtimer(priv); } } } } if (do_sleep) { stop_svtimer(priv); /* Transfer sleep packet */ sleep_result = do_xfersleep(priv, 0); if (sleep_result == SLEEP_OK) { altmdm_pm_sleepmodem(priv); } } if (is_sleepreq) { /* Send sleep response */ done_sleep(priv, sleep_result); } /* Receive data transfer request */ if (is_txreq || is_rxreq) { stop_svtimer(priv); /* Wakeup modem before data transfer */ if (is_rxreq) { res_code = altmdm_pm_wakeup(priv, NULL); } else { res_code = altmdm_pm_wakeup(priv, wait_receiverready); } if (res_code == MODEM_PM_WAKEUP_FAIL) { done_xfer(priv, MODE_TXDATA, TRANS_WAITRCVRTMO); start_svtimer(priv); continue; } else if (res_code == MODEM_PM_WAKEUP_DONE) { is_rcvrready = 1; } /* transfer header */ ret = do_xferheader(priv, is_rxreq, is_txreq, 0, is_rcvrready); if (is_rcvrready) { is_rcvrready = 0; } xfer_mode = decide_xfermode(priv, is_rxreq, is_txreq, ret); switch (xfer_mode) { case MODE_RXDATA: ret = do_receivedata(priv); break; case MODE_TXDATA: ret = do_senddata(priv); break; case MODE_TRXDATA: ret = do_trxdata(priv); break; case MODE_RXDATANOBUFF: ret = do_receivedata_nobuff(priv); break; case MODE_TRXDATANOBUFF: ret = do_trxdata_norxbuff(priv); break; case MODE_RXRESET: ret = do_receivereset(priv); break; case MODE_TRXRESET: ret = do_trxreset(priv); break; case MODE_RXINVALID: ret = TRANS_RXINVALID; break; case MODE_TRXHEADERFAILTXREQ: case MODE_TRXHEADERFAILRXREQ: break; default: m_err("ERR:%04d Unknown decision of transfer: %d.\n", __LINE__, xfer_mode); break; } if (is_txreq) { DEBUGASSERT(priv->lower); priv->lower->master_request(false); } m_info("m=%d\n", xfer_mode); done_xfer(priv, xfer_mode, ret); start_svtimer(priv); } } if (ptn & EVENT_EXIT) { spidev->is_not_run = true; } } delete_svtimer(priv); task_delete(0); return 0; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: altmdm_spi_gpioreadyisr * * Description: * Interrupt handler for SLAVE_REQUEST GPIO line. * ****************************************************************************/ int altmdm_spi_gpioreadyisr(int irq, FAR void *context, FAR void *arg) { FAR struct altmdm_dev_s *priv = g_privdata; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; altmdm_sys_setflag(&spidev->xfer_flag, EVENT_RXREQ); return 0; } /**************************************************************************** * Name: altmdm_spi_init * * Description: * Initialize ALTMDM driver. * ****************************************************************************/ int altmdm_spi_init(FAR struct altmdm_dev_s *priv) { int ret = 0; g_privdata = priv; /* Initialize modem power management driver */ altmdm_pm_init(priv); memset(&priv->spidev, 0, sizeof(struct altmdm_spi_dev_s)); priv->spidev.is_not_run = false; priv->spidev.is_xferready = false; altmdm_sys_initlock(&priv->spidev.tx_param.lock); altmdm_sys_initlock(&priv->spidev.rx_param.lock); altmdm_sys_initlock(&priv->spidev.sleep_param.lock); altmdm_sys_initflag(&priv->spidev.xfer_flag); altmdm_sys_initflag(&priv->spidev.xferready_flag); altmdm_sys_initflag(&priv->spidev.tx_param.done_flag); altmdm_sys_initflag(&priv->spidev.sleep_param.done_flag); create_rxbufffifo(priv); DEBUGASSERT(priv->lower); /* SPI settings */ SPI_LOCK(priv->spi, true); SPI_SETMODE(priv->spi, SPIDEV_MODE0); SPI_SETBITS(priv->spi, 8); SPI_SETFREQUENCY(priv->spi, priv->lower->spi_maxfreq()); SPI_LOCK(priv->spi, false); priv->lower->sready_irqattach(true, altmdm_spi_gpioreadyisr); priv->spidev.task_id = task_create(XFER_TASK_NAME, XFER_TASK_PRI, XFER_TASK_STKSIZE, xfer_task, NULL); if (priv->spidev.task_id == ERROR) { m_err("Failed to create xfer task\n"); } return ret; } /**************************************************************************** * Name: altmdm_spi_uninit * * Description: * Uninitialize ALTMDM driver. * ****************************************************************************/ int altmdm_spi_uninit(FAR struct altmdm_dev_s *priv) { altmdm_sys_setflag(&priv->spidev.xfer_flag, EVENT_EXIT); /* check transfer task is deleted or not */ while (1) { if (priv->spidev.is_not_run) { break; } nxsig_usleep(10); } altmdm_sys_deletelock(&priv->spidev.tx_param.lock); altmdm_sys_deletelock(&priv->spidev.rx_param.lock); altmdm_sys_deletelock(&priv->spidev.sleep_param.lock); altmdm_sys_deleteflag(&priv->spidev.xfer_flag); altmdm_sys_deleteflag(&priv->spidev.tx_param.done_flag); altmdm_sys_deleteflag(&priv->spidev.sleep_param.done_flag); if (priv->spidev.rx_param.rxbuff != NULL) { free_rxbuffer(priv, priv->spidev.rx_param.rxbuff); priv->spidev.rx_param.rxbuff = NULL; } destroy_rxbufffifo(priv); DEBUGASSERT(priv->lower); priv->lower->sready_irqattach(false, NULL); /* Uninitialize modem power management driver */ altmdm_pm_uninit(priv); return 0; } /**************************************************************************** * Name: altmdm_spi_enable * * Description: * Enable ALTMDM SPI driver. * ****************************************************************************/ int altmdm_spi_enable(FAR struct altmdm_dev_s *priv) { DEBUGASSERT(priv && priv->lower); priv->lower->sready_irqenable(true); return 0; } /**************************************************************************** * Name: altmdm_spi_disable * * Description: * Disable ALTMDM SPI driver. * ****************************************************************************/ int altmdm_spi_disable(FAR struct altmdm_dev_s *priv) { FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; DEBUGASSERT(priv->lower); priv->lower->sready_irqenable(false); spidev->is_xferready = false; return 0; } /**************************************************************************** * Name: altmdm_spi_read * * Description: * ALTMDM SPI driver read method. * ****************************************************************************/ ssize_t altmdm_spi_read(FAR struct altmdm_dev_s * priv, FAR const char *buffer, size_t readlen) { FAR struct altmdm_spi_dev_s *spidev; FAR struct altmdm_spi_rxbuff_s *rbuff; ssize_t rsize = readlen; /* Check argument */ if ((priv == NULL) || (buffer == NULL)) { return -EINVAL; } if (!readlen || readlen > MAX_PKT_SIZE) { m_err("Invalid read length:%d.\n", readlen); return -EINVAL; } spidev = &priv->spidev; altmdm_sys_lock(&spidev->rx_param.lock); get_rxbufffifo(priv, &rbuff); if (spidev->rx_param.rxabort) { spidev->rx_param.rxabort = false; if (rbuff != NULL) { m_info("rx buffer discard because of abort.%d\n", __LINE__); free_rxbuffer(priv, rbuff); } rsize = -ECONNABORTED; } else { if (rbuff == NULL) { m_err("get rx buffer failed.\n"); rsize = -EIO; } else if (rbuff->rx_size > readlen) { m_info("get rx buffer.%d\n", __LINE__); rsize = readlen; memcpy((void *)buffer, rbuff->buff_addr, rsize); free_rxbuffer(priv, rbuff); } else { m_info("get rx buffer.%d\n", __LINE__); rsize = rbuff->rx_size; memcpy((void *)buffer, rbuff->buff_addr, rsize); free_rxbuffer(priv, rbuff); } } altmdm_sys_unlock(&spidev->rx_param.lock); return rsize; } /**************************************************************************** * Name: altmdm_spi_write * * Description: * ALTMDM SPI driver write method. * ****************************************************************************/ ssize_t altmdm_spi_write(FAR struct altmdm_dev_s * priv, FAR const char *buffer, size_t witelen) { int ret; int remainder; uint32_t ptn; FAR struct altmdm_spi_dev_s *spidev; ssize_t wsize = witelen; /* Check argument */ if ((priv == NULL) || (buffer == NULL)) { return -EINVAL; } if (!witelen || witelen > MAX_PKT_SIZE) { m_err("Invalid write length:%d.\n", witelen); return -EINVAL; } spidev = &priv->spidev; if (!spidev->is_xferready) { wait_xferready(priv); } altmdm_sys_lock(&spidev->tx_param.lock); again: spidev->tx_param.buff_addr = (void *)buffer; spidev->tx_param.actual_size = witelen; remainder = witelen % UNIT_SIZE; if (remainder == 0) { spidev->tx_param.total_size = spidev->tx_param.actual_size; } else { spidev->tx_param.total_size = ((spidev->tx_param.actual_size / UNIT_SIZE) + 1) * UNIT_SIZE; } spidev->tx_param.result = 0; altmdm_sys_setflag(&spidev->xfer_flag, EVENT_TXREQ); ret = altmdm_sys_waitflag(&spidev->tx_param.done_flag, EVENT_TX_DONE, ALTMDM_SYS_FLAG_WMODEOR, &ptn, WRITE_WAIT_TIMEOUT); if (ret != OK) { m_err("wait failed:%d\n", ret); wsize = -ETIME; } else { switch (spidev->tx_param.result) { case TRANS_OK: case TRANS_OK_TRXDATANORXBUFF: wsize = witelen; break; case TRANS_RXINVALID: case TRANS_WAITRCVRTMO: wsize = -EIO; break; case TRANS_OK_RCVBUFFUL: nxsig_usleep(100); goto again; break; default: m_err("Unexpected situation. tx result = %d.\n", spidev->tx_param.result); wsize = -EIO; break; } m_info("%s: tx result: %d.\n", __func__, spidev->tx_param.result); m_info("%s: write size: %d.\n", __func__, wsize); } altmdm_sys_unlock(&spidev->tx_param.lock); return wsize; } /**************************************************************************** * Name: spicom_read_abort * * Description: * Abort the read process. * ****************************************************************************/ int altmdm_spi_readabort(FAR struct altmdm_dev_s *priv) { /* Check argument */ if (priv == NULL) { return -EINVAL; } abort_get_rxbufffifo(priv); return OK; } /**************************************************************************** * Name: altmdm_spi_sleepmodem * * Description: * Make ALTMDM sleep. * ****************************************************************************/ int altmdm_spi_sleepmodem(FAR struct altmdm_dev_s *priv) { int ret; bool sleep_requested; uint32_t ptn; FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; altmdm_sys_lock(&spidev->sleep_param.lock); sleep_requested = spidev->sleep_param.requested; if (!sleep_requested) { spidev->sleep_param.requested = true; } altmdm_sys_unlock(&spidev->sleep_param.lock); if (sleep_requested) { ret = -EBUSY; } else { spidev->sleep_param.result = 0; altmdm_sys_setflag(&spidev->xfer_flag, EVENT_SLEEPREQ); ret = altmdm_sys_waitflag(&spidev->sleep_param.done_flag, EVENT_SLEEP_DONE, ALTMDM_SYS_FLAG_WMODEOR, &ptn, SREQ_WAIT_TIMEOUT); if (ret != OK) { m_err("wait failed:%d\n", ret); } else { ret = spidev->sleep_param.result; m_info("%s: sleep result: %d.\n", __func__, spidev->sleep_param.result); } spidev->sleep_param.requested = false; } return ret; } /**************************************************************************** * Name: altmdm_spi_setreceiverready * * Description: * Set receiver ready notification. * ****************************************************************************/ int altmdm_spi_setreceiverready(FAR struct altmdm_dev_s *priv) { FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; altmdm_sys_setflag(&spidev->xfer_flag, EVENT_RXREQ); return 0; } /**************************************************************************** * Name: altmdm_spi_isreceiverready * * Description: * Check already notified or not by altmdm_spi_setreceiverready. * ****************************************************************************/ int altmdm_spi_isreceiverready(FAR struct altmdm_dev_s *priv) { FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; struct altmdm_sys_flagstate_s flag_status; altmdm_sys_referflag(&spidev->xfer_flag, &flag_status); if (flag_status.flag_pattern & EVENT_RXREQ) { return 1; } return 0; } /**************************************************************************** * Name: altmdm_spi_clearreceiverready * * Description: * Clear receiver ready notification. * ****************************************************************************/ int altmdm_spi_clearreceiverready(FAR struct altmdm_dev_s *priv) { FAR struct altmdm_spi_dev_s *spidev = &priv->spidev; altmdm_sys_clearflag(&spidev->xfer_flag, EVENT_RXREQ); return 0; } #endif /* CONFIG_MODEM_ALTMDM */