| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright 2015 Broadcom Corporation |
| * |
| * The Broadcom GPS SPI driver |
| * |
| */ |
| |
| /* TODO: Use dev_*() calls instead */ |
| #define pr_fmt(fmt) "GPSREGS: " fmt |
| |
| #include <linux/module.h> |
| #include <linux/ioport.h> |
| #include <linux/device.h> |
| #include <linux/init.h> |
| #include <linux/sysrq.h> |
| #include <linux/console.h> |
| #include <linux/delay.h> |
| #include <linux/spi/spi.h> |
| #include <linux/spi/spidev.h> |
| #include <linux/kthread.h> |
| #include <linux/circ_buf.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/of_gpio.h> |
| #include <linux/poll.h> |
| #include <linux/uaccess.h> |
| #include <linux/suspend.h> |
| #include <linux/kernel.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/miscdevice.h> |
| #include <linux/timekeeping.h> |
| #include <linux/io.h> |
| #include <asm/irq.h> |
| #include <linux/kernel_stat.h> |
| #include <linux/pm_runtime.h> |
| |
| #include "bbd.h" |
| #include "bcm_gps_spi.h" |
| |
| /* 0 - Half Duplex, 1 - Full Duplex */ |
| #define SSI_MODE 1 |
| |
| /* 1 = 1B, 2 = 2B */ |
| #define SSI_LEN 2 |
| |
| /* |
| * TODO: Need to read bitrate from bus driver spi.c. |
| * Just for startup info notification. |
| */ |
| #define BCM_BITRATE 12000 |
| |
| static void bcm_on_packet_received( |
| void *_priv, unsigned char *data, unsigned int size); |
| |
| |
| static ssize_t nstandby_show( |
| struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int value = 0; |
| struct spi_device *spi = to_spi_device(dev); |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| value = gpio_get_value(priv->nstandby); |
| |
| return scnprintf(buf, PAGE_SIZE, "%d\n", value); |
| } |
| |
| static ssize_t nstandby_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| |
| #ifdef DEBUG_1HZ_STAT |
| dev_dbg(dev, "nstandby, buf is %s\n", buf); |
| #endif |
| |
| if (buf[0] == '0') |
| gpio_set_value(priv->nstandby, 0); |
| else |
| gpio_set_value(priv->nstandby, 1); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR_RW(nstandby); |
| |
| static ssize_t sspmcureq_show( |
| struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| int value = 0; |
| struct spi_device *spi = to_spi_device(dev); |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| value = gpio_get_value(priv->mcu_req); |
| |
| return scnprintf(buf, PAGE_SIZE, "%d\n", value); |
| } |
| |
| static ssize_t sspmcureq_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| |
| dev_dbg(dev, "sspmcureq, buf is %s\n", buf); |
| |
| if (buf[0] == '0') |
| gpio_set_value(priv->mcu_req, 0); |
| else |
| gpio_set_value(priv->mcu_req, 1); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR_RW(sspmcureq); |
| |
| #ifdef CONFIG_TRANSFER_STAT |
| void bcm_ssi_clear_trans_stat(struct bcm_spi_priv *priv) |
| { |
| memset(priv->trans_stat, 0, sizeof(priv->trans_stat)); |
| } |
| |
| void bcm_ssi_print_trans_stat(struct bcm_spi_priv *priv) |
| { |
| struct bcm_spi_transfer_stat *trans = &priv->trans_stat[0]; |
| |
| dev_info(&priv->spi->dev, "DBG SPI @ TX: <255B = %d, <1K = %d, <2K = %d, <4K = %d, <8K = %d, <16K = %d, <32K = %d, <64K = %d, total = %ld, min = %ld, max = %ld", |
| trans->len_255, trans->len_1K, trans->len_2K, |
| trans->len_4K, trans->len_8K, trans->len_16K, |
| trans->len_32K, trans->len_64K, trans->len_total, |
| trans->len_min, trans->len_max); |
| |
| trans = &priv->trans_stat[1]; |
| dev_info(&priv->spi->dev, "DBG SPI @ RX: <255B = %d, <1K = %d, <2K = %d, <4K = %d, <8K = %d, <16K = %d, <32K = %d, <64K = %d, total = %ld, min = %ld, max = %ld", |
| trans->len_255, trans->len_1K, trans->len_2K, |
| trans->len_4K, trans->len_8K, trans->len_16K, |
| trans->len_32K, trans->len_64K, trans->len_total, |
| trans->len_min, trans->len_max); |
| |
| dev_info(&priv->spi->dev, "DBG SPI @ PZC: retries = %d, delays = %d", |
| priv->ssi_tx_pzc_retries, priv->ssi_tx_pzc_retry_delays); |
| } |
| |
| static void bcm_ssi_calc_trans_stat( |
| struct bcm_spi_transfer_stat *trans, unsigned short length) |
| { |
| if (length <= 255) |
| trans->len_255++; |
| else if (length <= 1024) |
| trans->len_1K++; |
| else if (length <= (2 * 1024)) |
| trans->len_2K++; |
| else if (length <= (4 * 1024)) |
| trans->len_4K++; |
| else if (length <= (8 * 1024)) |
| trans->len_8K++; |
| else if (length <= (16 * 1024)) |
| trans->len_16K++; |
| else if (length <= (32 * 1024)) |
| trans->len_32K++; |
| else |
| trans->len_64K++; |
| |
| if (length > trans->len_max) |
| trans->len_max = length; |
| |
| if (trans->len_min == 0 || length < trans->len_min) |
| trans->len_min = length; |
| |
| trans->len_total += length; |
| } |
| #endif /* CONFIG_TRANSFER_STAT */ |
| |
| static const unsigned long m_ulRxBufferBlockSize[4] = {32, 256, 1024 * 2, 1024 * 16}; |
| |
| static unsigned long bcm_ssi_chk_pzc(struct bcm_spi_priv *priv, |
| unsigned char stat_byte, bool bprint) |
| { |
| unsigned long rx_buffer_blk_bytes = |
| m_ulRxBufferBlockSize[( |
| stat_byte & HSI_F_MOSI_CTRL_SZE_MASK) >> |
| HSI_F_MOSI_CTRL_SZE_SHIFT]; |
| unsigned long rx_buffer_blk_counts = |
| (unsigned long)((stat_byte & HSI_F_MOSI_CTRL_CNT_MASK) >> |
| HSI_F_MOSI_CTRL_CNT_SHIFT); |
| |
| priv->rx_buffer_avail_bytes = rx_buffer_blk_bytes * rx_buffer_blk_counts; |
| |
| if (!bprint) |
| return priv->rx_buffer_avail_bytes; |
| |
| if (stat_byte & HSI_F_MOSI_CTRL_PE_MASK) { |
| dev_dbg(&priv->spi->dev, "DBG SPI @ PZC: rx stat 0x%02x%s avail %lu", |
| stat_byte, |
| stat_byte & HSI_F_MOSI_CTRL_PE_MASK ? "(PE)" : " ", |
| priv->rx_buffer_avail_bytes); |
| } |
| |
| #ifdef CONFIG_REG_IO |
| if (stat_byte & HSI_F_MOSI_CTRL_PE_MASK) { |
| u8 regval; |
| |
| bcm_dreg_read(priv, "HSI_ERROR_STATUS(R) ", |
| HSI_ERROR_STATUS, ®val, 1); |
| |
| if (regval & HSI_ERROR_STATUS_STRM_FIFO_OVFL) |
| dev_err(&priv->spi->dev, "(rx_strm_fifo_ovfl)"); |
| |
| regval = HSI_ERROR_STATUS_ALL_ERRORS; |
| bcm_dreg_write(priv, "HSI_ERROR_STATUS(W) ", |
| HSI_ERROR_STATUS, ®val, 1); |
| } |
| #endif /* CONFIG_REG_IO */ |
| |
| return priv->rx_buffer_avail_bytes; |
| } |
| |
| |
| /********************************** |
| * |
| * File Operations |
| * |
| **********************************/ |
| static int bcm_spi_open(struct inode *inode, struct file *filp) |
| { |
| /* |
| * Initially, file->private_data points device itself |
| * and we can get our priv structs from it. |
| */ |
| struct bcm_spi_priv *priv = container_of(filp->private_data, |
| struct bcm_spi_priv, misc); |
| struct bcm_spi_strm_protocol *strm; |
| unsigned long flags; |
| unsigned char fc_mask, len_mask, duplex_mask; |
| |
| #ifdef CONFIG_REG_IO |
| u8 regval8[2]; |
| u32 regval32[16]; |
| #endif |
| |
| if (priv->busy) |
| return -EBUSY; |
| |
| priv->busy = true; |
| |
| /* Reset circ buffer */ |
| priv->read_buf.head = priv->read_buf.tail = 0; |
| priv->write_buf.head = priv->write_buf.tail = 0; |
| |
| priv->packet_received = 0; |
| |
| /* Enable irq */ |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| if (!atomic_xchg(&priv->irq_enabled, 1)) |
| enable_irq(priv->spi->irq); |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| priv->irq_wakeup_enabled = (enable_irq_wake(priv->spi->irq) == 0); |
| |
| filp->private_data = priv; |
| #ifdef DEBUG_1HZ_STAT |
| bbd_enable_stat(priv->bbd); |
| #endif |
| |
| strm = &priv->tx_strm; |
| strm->pckt_len = SSI_LEN == 2 ? 2 : 1; |
| len_mask = SSI_LEN == 2 ? SSI_PCKT_2B_LENGTH : SSI_PCKT_1B_LENGTH; |
| duplex_mask = SSI_MODE != 0 ? SSI_MODE_FULL_DUPLEX : SSI_MODE_HALF_DUPLEX; |
| |
| fc_mask = SSI_FLOW_CONTROL_DISABLED; |
| strm->fc_len = 0; |
| if (SSI_MODE == 0) { |
| /* SSI_MODE_HALF_DUPLEX; */ |
| strm->pckt_len = 0; |
| } |
| /* 1 for tx cmd byte */ |
| strm->ctrl_len = strm->pckt_len + strm->fc_len + 1; |
| |
| strm->frame_len = SSI_LEN == 2 ? MAX_SPI_FRAME_LEN : MAX_SPI_DREG_FRAME_LEN; |
| strm->ctrl_byte = duplex_mask | SSI_MODE_STREAM | |
| len_mask | SSI_WRITE_TRANS | fc_mask; |
| |
| /* TX SPI Streaming Protocol in details */ |
| #ifdef DEBUG_1HZ_STAT |
| dev_info(&priv->spi->dev, "tx ctrl %02X: total %d = len %d + fc %d + cmd 1", |
| strm->ctrl_byte, strm->ctrl_len, |
| strm->pckt_len, strm->fc_len); |
| #endif |
| |
| strm = &priv->rx_strm; |
| strm->pckt_len = SSI_LEN == 2 ? 2 : 1; |
| strm->fc_len = 0; |
| /* 1 for rx stat byte */ |
| strm->ctrl_len = strm->pckt_len + strm->fc_len + 1; |
| strm->frame_len = SSI_LEN == 2 ? MAX_SPI_FRAME_LEN : MAX_SPI_DREG_FRAME_LEN; |
| strm->ctrl_byte = duplex_mask | SSI_MODE_STREAM | len_mask | |
| (SSI_MODE == SSI_MODE_FULL_DUPLEX ? |
| SSI_WRITE_TRANS : SSI_READ_TRANS); |
| |
| /* RX SPI Streaming Protocol in details */ |
| #ifdef DEBUG_1HZ_STAT |
| dev_info(&priv->spi->dev, "rx ctrl %02X: total %d = len %d + fc %d + stat 1\n", |
| strm->ctrl_byte, strm->ctrl_len, |
| strm->pckt_len, strm->fc_len); |
| |
| dev_info(&priv->spi->dev, "SPI @ %d: %s Duplex Strm mode, %dB Len, w/o FC, Frame Len %u : tx ctrl %02X, rx ctrl %02X\n", |
| BCM_BITRATE, |
| SSI_MODE != 0 ? "Full" : "Half", |
| SSI_LEN == 2 ? 2 : 1, |
| strm->frame_len, |
| priv->tx_strm.ctrl_byte, |
| priv->rx_strm.ctrl_byte); |
| #endif |
| |
| #ifdef CONFIG_REG_IO |
| bcm_dreg_read(priv, "HSI_STATUS ", |
| HSI_STATUS, regval8, 1); |
| bcm_dreg_read(priv, "HSI_ERROR_STATUS(R) ", |
| HSI_ERROR_STATUS, regval8, 1); |
| bcm_ireg_read(priv, "INTR_MASK/STAT ", |
| HSI_INTR_MASK, regval32, 2); |
| bcm_ireg_read(priv, "RNGDMA_RX ", |
| HSI_RNGDMA_RX_BASE_ADDR, regval32, 8); |
| bcm_ireg_read(priv, "RNGDMA_TX ", |
| HSI_RNGDMA_TX_BASE_ADDR, regval32, 8); |
| bcm_ireg_read(priv, "HSI_CTRL ", |
| HSI_CTRL, regval32, 4); |
| bcm_ireg_read(priv, "ADL_ABR ", |
| HSI_ADL_ABR_CONTROL, regval32, 4); |
| bcm_ireg_read(priv, "RSTN/STBY/EN ", |
| HSI_RESETN, regval32, 4); |
| bcm_ireg_read(priv, "STRM/CMND ", |
| HSI_STRM_FIFO_STATUS, regval32, 2); |
| #endif |
| |
| #ifdef CONFIG_TRANSFER_STAT |
| bcm_ssi_print_trans_stat(priv); |
| bcm_ssi_clear_trans_stat(priv); |
| #endif |
| priv->ssi_tx_fail = 0; |
| priv->ssi_tx_pzc_retries = 0; |
| priv->ssi_tx_pzc_retry_delays = 0; |
| priv->ssi_pm_semaphore = 0; |
| priv->rx_buffer_avail_bytes = HSI_PZC_MAX_RX_BUFFER; |
| |
| return 0; |
| } |
| |
| static int bcm_spi_release(struct inode *inode, struct file *filp) |
| { |
| struct bcm_spi_priv *priv = filp->private_data; |
| unsigned long flags; |
| |
| priv->busy = false; |
| |
| #ifdef CONFIG_TRANSFER_STAT |
| bcm_ssi_print_trans_stat(priv); |
| #endif |
| |
| |
| #ifdef DEBUG_1HZ_STAT |
| bbd_disable_stat(priv->bbd); |
| #endif |
| /* Disable irq */ |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| if (atomic_xchg(&priv->irq_enabled, 0)) |
| disable_irq_nosync(priv->spi->irq); |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| if (priv->irq_wakeup_enabled) |
| disable_irq_wake(priv->spi->irq); |
| |
| return 0; |
| } |
| |
| static ssize_t bcm_spi_read( |
| struct file *filp, char __user *buf, size_t size, loff_t *ppos) |
| { |
| struct bcm_spi_priv *priv = filp->private_data; |
| struct circ_buf *circ = &priv->read_buf; |
| size_t rd_size = 0; |
| |
| mutex_lock(&priv->rlock); |
| |
| /* |
| * Copy from circ buffer to user |
| * We may require 2 copies from [tail..end] and [end..head] |
| */ |
| do { |
| size_t cnt_to_end = CIRC_CNT_TO_END( |
| circ->head, circ->tail, BCM_SPI_READ_BUF_SIZE); |
| size_t copied = min(cnt_to_end, size); |
| |
| if (copy_to_user(buf + rd_size, |
| circ->buf + circ->tail, copied)){ |
| dev_err(&priv->spi->dev, "failed to copy to user.\n"); |
| mutex_unlock(&priv->rlock); |
| return -EFAULT; |
| } |
| size -= copied; |
| rd_size += copied; |
| circ->tail = (circ->tail + copied) & (BCM_SPI_READ_BUF_SIZE-1); |
| |
| } while (size > 0 && CIRC_CNT(circ->head, |
| circ->tail, BCM_SPI_READ_BUF_SIZE)); |
| mutex_unlock(&priv->rlock); |
| |
| #ifdef DEBUG_1HZ_STAT |
| bbd_update_stat(priv->bbd, STAT_RX_LHD, rd_size); |
| #endif |
| |
| return rd_size; |
| } |
| |
| static ssize_t bcm_spi_write( |
| struct file *filp, const char __user *buf, |
| size_t size, loff_t *ppos) |
| { |
| struct bcm_spi_priv *priv = filp->private_data; |
| struct circ_buf *circ = &priv->write_buf; |
| size_t wr_size = 0; |
| |
| mutex_lock(&priv->wlock); |
| /* |
| * Copy from user into circ buffer |
| * We may require 2 copies from [tail..end] and [end..head] |
| */ |
| do { |
| size_t space_to_end = CIRC_SPACE_TO_END(circ->head, |
| circ->tail, BCM_SPI_WRITE_BUF_SIZE); |
| size_t copied = min(space_to_end, size); |
| |
| if (copy_from_user(circ->buf + circ->head, |
| buf + wr_size, copied)){ |
| dev_err(&priv->spi->dev, "failed to copy from user.\n"); |
| mutex_unlock(&priv->wlock); |
| return -EFAULT; |
| } |
| size -= copied; |
| wr_size += copied; |
| circ->head = (circ->head + copied) & |
| (BCM_SPI_WRITE_BUF_SIZE - 1); |
| } while (size > 0 && CIRC_SPACE(circ->head, circ->tail, |
| BCM_SPI_WRITE_BUF_SIZE)); |
| mutex_unlock(&priv->wlock); |
| |
| /* |
| * kick start rxtx thread |
| * we don't want to queue work in suspending and shutdown |
| */ |
| if (!atomic_read(&priv->suspending)) |
| queue_work(priv->serial_wq, |
| (struct work_struct *)&priv->rxtx_work); |
| |
| #ifdef DEBUG_1HZ_STAT |
| bbd_update_stat(priv->bbd, STAT_TX_LHD, wr_size); |
| #endif |
| return wr_size; |
| } |
| |
| static unsigned int bcm_spi_poll(struct file *filp, poll_table *wait) |
| { |
| struct bcm_spi_priv *priv = filp->private_data; |
| struct circ_buf *rd_circ = &priv->read_buf; |
| struct circ_buf *wr_circ = &priv->write_buf; |
| unsigned int mask = 0; |
| |
| poll_wait(filp, &priv->poll_wait, wait); |
| |
| if (CIRC_CNT(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE)) |
| mask |= POLLIN; |
| |
| if (CIRC_SPACE(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE)) |
| mask |= POLLOUT; |
| |
| return mask; |
| } |
| |
| |
| static const struct file_operations bcm_spi_fops = { |
| .owner = THIS_MODULE, |
| .open = bcm_spi_open, |
| .release = bcm_spi_release, |
| .read = bcm_spi_read, |
| .write = bcm_spi_write, |
| .poll = bcm_spi_poll, |
| }; |
| |
| |
| |
| /* Misc. functions */ |
| |
| unsigned long bcm_clock_get_ms(void) |
| { |
| struct timespec64 t; |
| unsigned long now; |
| static unsigned long init_time; |
| |
| ktime_get_real_ts64(&t); |
| now = t.tv_nsec / 1000000 + t.tv_sec * 1000; |
| if (init_time == 0) |
| init_time = now; |
| |
| return now - init_time; |
| } |
| |
| void wait1secDelay(unsigned int count) |
| { |
| if (count <= 100) |
| usleep_range(1000, 2000); |
| else |
| usleep_range(20000, 30000); |
| } |
| |
| #ifdef CONFIG_MCU_WAKEUP |
| /** |
| * bcm4773_hello - wakeup chip by toggling mcu_req |
| * while monitoring mcu_resp to check if awake |
| */ |
| static bool bcm477x_hello(struct bcm_spi_priv *priv) |
| { |
| int count = 0; |
| #define MAX_RESP_CHECK_COUNT 100 /* 100 msec */ |
| |
| unsigned long start_time, delta; |
| |
| start_time = bcm_clock_get_ms(); |
| gpio_set_value(priv->mcu_req, 1); |
| while (!gpio_get_value(priv->mcu_resp)) { |
| if (count++ > MAX_RESP_CHECK_COUNT) { |
| gpio_set_value(priv->mcu_req, 0); |
| #ifdef DEBUG_1HZ_STAT |
| dev_err(&priv->spi->dev, " MCU_REQ_RESP timeout. MCU_RESP(gpio%d) not responding to MCU_REQ(gpio%d)\n", |
| priv->mcu_resp, priv->mcu_req); |
| #endif |
| return false; |
| } |
| |
| wait1secDelay(count); |
| |
| /*if awake, done */ |
| if (gpio_get_value(priv->mcu_resp)) |
| break; |
| |
| if (count % 20 == 0) { |
| gpio_set_value(priv->mcu_req, 0); |
| usleep_range(1000, 2000); |
| gpio_set_value(priv->mcu_req, 1); |
| usleep_range(1000, 2000); |
| } |
| } |
| |
| delta = bcm_clock_get_ms() - start_time; |
| |
| if (count > 100) |
| dev_err(&priv->spi->dev, "hello consumed %lu = clock_get_ms() - start_time; msec", |
| delta); |
| |
| return true; |
| } |
| #endif |
| |
| /** |
| * bcm4773_bye - set mcu_req low to let chip go to sleep |
| */ |
| static void bcm477x_bye(struct bcm_spi_priv *priv) |
| { |
| gpio_set_value(priv->mcu_req, 0); |
| } |
| |
| static void pk_log(struct bcm_spi_priv *priv, char *dir, |
| unsigned char *data, int len) |
| { |
| const char ic = 'D'; |
| |
| if (likely(!priv->ssi_dbg)) |
| return; |
| |
| /* |
| * TODO: There is print issue. Printing 7 digits instead of 6 |
| * when clock is over 1000000. "% 1000000" added |
| * E.g. |
| * #999829D w 0x68, 1: A2 |
| * #999829D r 0x68, 34: 8D 00 01 52 5F B0 01 B0 00 8E 00 01 53 8B |
| * B0 01 B0 00 8F 00 01 54 61 B0 01 B0 00 90 00 01 55 B5 |
| * r B0 01 |
| * #1000001D w 0x68, 1: A1 |
| * #1000001D r 0x68, 1: 00 |
| */ |
| dev_info(&priv->spi->dev, "#%06ld%c %2s,\t %5d: ", |
| bcm_clock_get_ms() % 1000000, ic, dir, len); |
| |
| print_hex_dump(KERN_INFO, dir[0] == 'r' ? "r " : "w ", |
| DUMP_PREFIX_NONE, 32, 1, data, len, false); |
| } |
| |
| void bcm_ssi_debug(struct device *dev, int type, bool value) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| |
| switch (type) { |
| case 0: |
| priv->ssi_dbg = value; |
| break; |
| case 1: |
| priv->ssi_dbg_pzc = value; |
| break; |
| case 2: |
| priv->ssi_dbg_rng = value; |
| break; |
| } |
| } |
| EXPORT_SYMBOL_GPL(bcm_ssi_debug); |
| |
| /* SSI tx/rx functions */ |
| |
| static unsigned short bcm_ssi_get_len( |
| unsigned char ctrl_byte, unsigned char *data) |
| { |
| if (ctrl_byte & SSI_PCKT_2B_LENGTH) |
| return ((unsigned short)data[0] + |
| ((unsigned short)data[1] << 8)); |
| |
| return (unsigned short)data[0]; |
| } |
| |
| static void bcm_ssi_set_len( |
| unsigned char ctrl_byte, unsigned char *data, unsigned short len) |
| { |
| if (ctrl_byte & SSI_PCKT_2B_LENGTH) { |
| data[0] = (unsigned char)(len & 0xff); |
| data[1] = (unsigned char)((len >> 8) & 0xff); |
| } else { |
| data[0] = (unsigned char)len; |
| } |
| } |
| |
| static void bcm_ssi_clr_len(unsigned char ctrl_byte, unsigned char *data) |
| { |
| bcm_ssi_set_len(ctrl_byte, data, 0); |
| } |
| |
| |
| int bcm_spi_sync(struct bcm_spi_priv *priv, void *tx_buf, |
| void *rx_buf, int len, int bits_per_word) |
| { |
| struct spi_message msg; |
| struct spi_transfer xfer; |
| int ret; |
| |
| /* Init */ |
| spi_message_init(&msg); |
| memset(&xfer, 0, sizeof(xfer)); |
| spi_message_add_tail(&xfer, &msg); |
| |
| /* Setup */ |
| msg.spi = priv->spi; |
| xfer.len = len; |
| xfer.tx_buf = tx_buf; |
| xfer.rx_buf = rx_buf; |
| xfer.bits_per_word = bits_per_word; |
| |
| /* Sync */ |
| pk_log(priv, "w", (unsigned char *)xfer.tx_buf, len); |
| ret = spi_sync(msg.spi, &msg); |
| pk_log(priv, "r", (unsigned char *)xfer.rx_buf, len); |
| |
| if (ret) |
| dev_err(&priv->spi->dev, "spi_sync error for cmd:0x%x, return=%d\n", |
| ((struct bcm_ssi_tx_frame *)xfer.tx_buf)->cmd, ret); |
| |
| return ret; |
| } |
| |
| |
| static int bcm_ssi_tx(struct bcm_spi_priv *priv, int length) |
| { |
| struct bcm_ssi_tx_frame *tx = priv->tx_buf; |
| struct bcm_ssi_rx_frame *rx = priv->rx_buf; |
| struct bcm_spi_strm_protocol *strm = &priv->tx_strm; |
| int bits_per_word = (length + strm->ctrl_len >= MIN_DMA_SIZE) ? |
| CONFIG_SPI_DMA_BITS_PER_WORD : 8; |
| int ret; |
| unsigned short m_write; |
| unsigned short bytes_to_write = (unsigned short)length; |
| unsigned short bytes_written = 0; |
| unsigned short n_read = 0; /* for Full Duplex only */ |
| unsigned short frame_data_size = strm->frame_len - strm->ctrl_len; |
| |
| m_write = bytes_to_write; |
| |
| tx->cmd = strm->ctrl_byte; /* SSI_WRITE_HD etc. */ |
| |
| bytes_to_write = max(m_write, n_read); |
| |
| if (strm->pckt_len != 0) |
| bcm_ssi_set_len(strm->ctrl_byte, tx->data, m_write); |
| |
| /* ctrl_len is for tx len + fc */ |
| ret = bcm_spi_sync(priv, tx, rx, bytes_to_write + |
| strm->ctrl_len, bits_per_word); |
| |
| if (ret) { |
| priv->ssi_tx_fail++; |
| return ret; |
| /* TODO: failure, operation should gets 0 to continue */ |
| } |
| |
| if (strm->pckt_len != 0) { |
| unsigned short m_write = |
| bcm_ssi_get_len(strm->ctrl_byte, tx->data); |
| /* Just for understanding SPI Streaming Protocol */ |
| if (m_write > frame_data_size) { |
| /* The h/w malfunctioned ? */ |
| dev_err(&priv->spi->dev, "@ TX m_write %d is h/w overflowed of frame %d...Fail\n", |
| m_write, frame_data_size); |
| } |
| } |
| |
| if (strm->ctrl_byte & SSI_MODE_FULL_DUPLEX) { |
| unsigned char *data_p = rx->data + strm->pckt_len; |
| |
| n_read = bcm_ssi_get_len(strm->ctrl_byte, rx->data); |
| if (n_read > frame_data_size) { |
| dev_err(&priv->spi->dev, "@ FD n_read %d is h/w overflowed of frame %d...Fail\n", |
| n_read, frame_data_size); |
| n_read = frame_data_size; |
| } |
| |
| if (m_write < n_read) { |
| /* Call BBD */ |
| bcm_on_packet_received(priv, data_p, m_write); |
| /* 1/2 bytes for len */ |
| n_read -= m_write; |
| bytes_to_write -= m_write; |
| data_p += (m_write + strm->fc_len); |
| } else { |
| bytes_to_write = n_read; |
| /* No data available next time */ |
| n_read = 0; |
| } |
| |
| /* Call BBD */ |
| if (bytes_to_write != 0) { |
| bcm_on_packet_received( |
| priv, data_p, bytes_to_write); |
| } |
| } |
| |
| bytes_written += bytes_to_write; |
| |
| #ifdef CONFIG_TRANSFER_STAT |
| bcm_ssi_calc_trans_stat(&priv->trans_stat[0], bytes_written); |
| #endif |
| |
| bcm_ssi_chk_pzc(priv, rx->status, priv->ssi_dbg_pzc); |
| |
| return ret; |
| } |
| |
| static int bcm_ssi_rx(struct bcm_spi_priv *priv, size_t *length) |
| { |
| struct bcm_ssi_tx_frame *tx = priv->tx_buf; |
| struct bcm_ssi_rx_frame *rx = priv->rx_buf; |
| struct circ_buf *rd_circ = &priv->read_buf; |
| struct bcm_spi_strm_protocol *strm = &priv->rx_strm; |
| unsigned short ctrl_len = strm->pckt_len + 1; /* +1 for rx status */ |
| unsigned short payload_len; |
| int bits_per_word = 8; |
| size_t sz_to_recv = 0; |
| |
| #ifdef CONFIG_REG_IO |
| if (likely(priv->ssi_dbg_rng) && |
| (priv->packet_received > CONFIG_PACKET_RECEIVED)) { |
| u32 regval32[8]; |
| |
| bcm_ireg_read(priv, "RNGDMA_TX ", |
| HSI_RNGDMA_TX_SW_ADDR_OFFSET, regval32, 3); |
| } |
| #endif |
| |
| /* TODO:: Check 1B or 2B mode */ |
| |
| bcm_ssi_clr_len(strm->ctrl_byte, tx->data); |
| /* tx and rx ctrl_byte(s) are same */ |
| tx->cmd = strm->ctrl_byte; |
| /* SSI_READ_HD etc. */ |
| rx->status = 0; |
| |
| if (bcm_spi_sync(priv, tx, rx, ctrl_len, 8)) |
| return -1; |
| |
| bcm_ssi_chk_pzc(priv, rx->status, priv->ssi_dbg_pzc); |
| |
| payload_len = bcm_ssi_get_len(strm->ctrl_byte, rx->data); |
| |
| if (payload_len == 0) { |
| /* |
| * TODO: payload_len = MIN_SPI_FRAME_LEN; |
| * Needn't to use MAX_SPI_FRAME_LEN because don't |
| * know how many bytes is ready to really read |
| */ |
| dev_err(&priv->spi->dev, "@ RX length is still read to 0. Set %d\n", payload_len); |
| return -1; |
| } |
| |
| *length = min((unsigned short)(strm->frame_len - ctrl_len), payload_len); |
| |
| /* SWGNSSGLL-24487 : slowing down read speed if buffer is half full */ |
| if (CIRC_CNT(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE) > |
| BCM_SPI_READ_BUF_SIZE / 2) { |
| msleep(DELAY_FOR_SYSTEM_OVERLOADED_MS); |
| if (*length >= READ_SIZE_FOR_SYSTEM_OVERLOADED) |
| *length = READ_SIZE_FOR_SYSTEM_OVERLOADED; |
| } |
| |
| sz_to_recv = *length + ctrl_len; |
| if (sz_to_recv >= MIN_DMA_SIZE) { |
| bits_per_word = CONFIG_SPI_DMA_BITS_PER_WORD; |
| if (sz_to_recv & (CONFIG_SPI_DMA_BYTES_PER_WORD - 1)) |
| *length = (sz_to_recv & ~(CONFIG_SPI_DMA_BYTES_PER_WORD - 1)) |
| - ctrl_len; |
| } |
| memset(tx->data, 0, *length + ctrl_len - 1); /* -1 for status byte */ |
| |
| if (bcm_spi_sync(priv, tx, rx, *length+ctrl_len, bits_per_word)) |
| return -1; |
| |
| payload_len = bcm_ssi_get_len(strm->ctrl_byte, rx->data); |
| if (payload_len < *length) |
| *length = payload_len; |
| |
| return 0; |
| } |
| |
| static void bcm_check_overrun(struct bcm_spi_priv *priv, size_t avail) |
| { |
| const long THRESHOLD_MS = 100; |
| unsigned long curr_tick = bcm_clock_get_ms(); |
| |
| if (!avail) |
| return; |
| |
| if (curr_tick - priv->last_tick < THRESHOLD_MS) { |
| priv->skip_count++; |
| return; |
| } |
| |
| if (priv->skip_count) |
| dev_err(&priv->spi->dev, "%ld messages are skipped!\n", priv->skip_count); |
| |
| dev_err(&priv->spi->dev, "input overrun error by %zu bytes.\n", avail); |
| priv->skip_count = 0; |
| priv->last_tick = curr_tick; |
| |
| } |
| |
| static void bcm_on_packet_received(void *_priv, unsigned char *data, |
| unsigned int size) |
| { |
| struct bcm_spi_priv *priv = (struct bcm_spi_priv *)_priv; |
| struct circ_buf *rd_circ = &priv->read_buf; |
| size_t written = 0, avail = size; |
| |
| #ifdef DEBUG_1HZ_STAT |
| bbd_update_stat(priv->bbd, STAT_RX_SSI, size); |
| #endif |
| #ifdef CONFIG_TRANSFER_STAT |
| bcm_ssi_calc_trans_stat(&priv->trans_stat[1], size); |
| #endif |
| |
| /* Copy into circ buffer */ |
| mutex_lock(&priv->rlock); |
| do { |
| size_t space_to_end = CIRC_SPACE_TO_END( |
| rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE); |
| size_t copied = min(space_to_end, avail); |
| |
| memcpy(rd_circ->buf + rd_circ->head, |
| data + written, copied); |
| avail -= copied; |
| written += copied; |
| rd_circ->head = (rd_circ->head + copied) & |
| (BCM_SPI_READ_BUF_SIZE - 1); |
| } while (avail > 0 && CIRC_SPACE(rd_circ->head, |
| rd_circ->tail, BCM_SPI_READ_BUF_SIZE)); |
| |
| priv->packet_received += size; |
| mutex_unlock(&priv->rlock); |
| wake_up(&priv->poll_wait); |
| bcm_check_overrun(priv, avail); |
| } |
| |
| #ifdef DEBUG_1HZ_STAT |
| void bcm477x_debug_info(struct bcm_spi_priv *priv) |
| { |
| int pin_ttyBCM, pin_MCU_REQ, pin_MCU_RESP; |
| int irq_enabled; |
| |
| if (!priv) |
| return; |
| |
| pin_ttyBCM = gpio_get_value(priv->host_req); |
| pin_MCU_REQ = gpio_get_value(priv->mcu_req); |
| pin_MCU_RESP = gpio_get_value(priv->mcu_resp); |
| |
| irq_enabled = atomic_read(&priv->irq_enabled); |
| |
| dev_info(&priv->spi->dev, "pin_ttyBCM:%d, pin_MCU_REQ:%d, pin_MCU_RESP:%d\n", |
| pin_ttyBCM, pin_MCU_REQ, pin_MCU_RESP); |
| dev_info(&priv->spi->dev, "irq_enabled:%d\n", irq_enabled); |
| } |
| #endif |
| |
| static void bcm_rxtx_work_func(struct work_struct *work) |
| { |
| struct bcm_spi_priv *priv = container_of(work, |
| struct bcm_spi_priv, rxtx_work); |
| struct circ_buf *rd_circ = &priv->read_buf; |
| struct circ_buf *wr_circ = &priv->write_buf; |
| struct bcm_spi_strm_protocol *strm = &priv->tx_strm; |
| unsigned short rx_pckt_len = priv->rx_strm.pckt_len; |
| int wait_for_pzc = 0; |
| unsigned long flags; |
| |
| #ifdef DEBUG_1HZ_STAT |
| u64 ts_rx_start = 0; |
| u64 ts_rx_end = 0; |
| struct timespec64 ts; |
| struct bbd_device *bbd = priv->bbd; |
| #endif |
| |
| #ifdef CONFIG_MCU_WAKEUP |
| if (!bcm477x_hello(priv)) { |
| #ifdef DEBUG_1HZ_STAT |
| dev_err(&priv->spi->dev, "hello timeout!!\n"); |
| bcm477x_debug_info(priv); |
| #endif |
| return; |
| } |
| #endif |
| |
| do { |
| int ret = 0; |
| size_t avail = 0; |
| size_t written = 0; |
| size_t sz_to_send = 0; |
| |
| /* Read first */ |
| if (!gpio_is_valid(priv->host_req)) { |
| dev_err(&priv->spi->dev, "gpio host_req is invalid, return\n"); |
| return; |
| } |
| ret = gpio_get_value(priv->host_req); |
| |
| if (ret || wait_for_pzc) { |
| wait_for_pzc = 0; |
| #ifdef DEBUG_1HZ_STAT |
| if (bbd->stat1hz.ts_irq) { |
| ts = ktime_to_timespec64(ktime_get_boottime()); |
| ts_rx_start = ts.tv_sec * 1000000000ULL |
| + ts.tv_nsec; |
| } |
| #endif |
| |
| /* Receive SSI frame */ |
| if (bcm_ssi_rx(priv, &avail)) |
| break; |
| |
| #ifdef DEBUG_1HZ_STAT |
| if (ts_rx_start && !gpio_get_value(priv->host_req)) { |
| ts = ktime_to_timespec64(ktime_get_boottime()); |
| ts_rx_end = ts.tv_sec * 1000000000ULL |
| + ts.tv_nsec; |
| } |
| #endif |
| /* Call BBD */ |
| bcm_on_packet_received(priv, |
| priv->rx_buf->data + rx_pckt_len, avail); |
| } |
| |
| /* Next, write */ |
| avail = CIRC_CNT(wr_circ->head, wr_circ->tail, |
| BCM_SPI_WRITE_BUF_SIZE); |
| |
| if(!avail) |
| continue; |
| |
| mutex_lock(&priv->wlock); |
| /* |
| * For big packet, we should align xfer size to |
| * DMA word size and burst size. |
| * That is, SSI payload + one byte command should be |
| * multiple of (DMA word size * burst size) |
| */ |
| |
| if (avail > (strm->frame_len - strm->ctrl_len)) |
| avail = strm->frame_len - strm->ctrl_len; |
| |
| ret = 0; |
| |
| /* |
| * SWGNSSGLL-15521 : Sometimes LHD does not write data |
| * because the following code blocks sending data to MCU |
| * Code is commented out because |
| * 'rx_buffer_avail_bytes'(PZC) is calculated in |
| * bcm_ssi_tx() inside loop in work queue |
| * (bcm_rxtx_work_func) below this code. |
| * It means 'rx_buffer_avail_bytes' doesn't reflect |
| * real available bytes in RX DMA RING buffer when |
| * work queue will be restarted |
| * because MCU is working independently from host. |
| * The 'rx_buffer_avail_bytes' can be tested inside |
| * bcm_ssi_tx but it may not guarantee correct |
| * condition also. |
| * SWGNSSGLL-16290 : FC detecting was broken when buffer |
| * is overflow Using PZC for a software workaround to |
| * not get into fifo overflow condition. |
| */ |
| if (avail > priv->rx_buffer_avail_bytes) { |
| priv->rx_buffer_avail_bytes ? priv->ssi_tx_pzc_retries++ : |
| priv->ssi_tx_pzc_retry_delays++; |
| dev_dbg(&priv->spi->dev, "%d PZC %s, wr CIRC_CNT %lu, RNGDMA_RX %lu\n", |
| priv->rx_buffer_avail_bytes ? |
| priv->ssi_tx_pzc_retries : priv->ssi_tx_pzc_retry_delays, |
| priv->rx_buffer_avail_bytes ? "writes":"delays", |
| avail, |
| priv->rx_buffer_avail_bytes); |
| if (priv->rx_buffer_avail_bytes == 0) { |
| /* |
| *RNGDMA_RX is full ? |
| * If it's YES keep reading |
| */ |
| u32 regval32[8]; |
| |
| bcm_ireg_read(priv, "RNGDMA_RX ", |
| HSI_RNGDMA_RX_SW_ADDR_OFFSET, |
| regval32, 3); |
| } |
| avail = priv->rx_buffer_avail_bytes; |
| usleep_range(1000, 2000); |
| /* |
| * TODO: increase delay for waiting for |
| * draining RNGDMA_RX on MCU side ? |
| */ |
| wait_for_pzc = 1; |
| /* |
| * This case is for when RNGDMA_RX is |
| * full and HOST_REQ is low |
| */ |
| } |
| |
| /* we should align xfer size to DMA word size. */ |
| sz_to_send = avail + strm->ctrl_len; |
| if (sz_to_send >= MIN_DMA_SIZE && |
| sz_to_send & (CONFIG_SPI_DMA_BYTES_PER_WORD - 1)) |
| avail = (sz_to_send & ~(CONFIG_SPI_DMA_BYTES_PER_WORD - 1)) |
| - strm->ctrl_len; |
| |
| /* Copy from wr_circ the data */ |
| while (avail > 0) { |
| size_t cnt_to_end = CIRC_CNT_TO_END( |
| wr_circ->head, wr_circ->tail, |
| BCM_SPI_WRITE_BUF_SIZE); |
| size_t copied = min(cnt_to_end, avail); |
| |
| memcpy(priv->tx_buf->data + strm->pckt_len + |
| written, wr_circ->buf + wr_circ->tail, copied); |
| avail -= copied; |
| written += copied; |
| wr_circ->tail = (wr_circ->tail + copied) & |
| (BCM_SPI_WRITE_BUF_SIZE - 1); |
| } |
| |
| /* Transmit SSI frame */ |
| if (written) |
| ret = bcm_ssi_tx(priv, written); |
| |
| mutex_unlock(&priv->wlock); |
| |
| if (ret) |
| break; |
| |
| /* |
| * SWGNSSAND-2159 While looping, |
| * wake up lhd only if rx ring is more than 12.5% full |
| */ |
| if (CIRC_CNT(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE) > |
| BCM_SPI_READ_BUF_SIZE / 8) { |
| wake_up(&priv->poll_wait); |
| } |
| #ifdef DEBUG_1HZ_STAT |
| bbd_update_stat(bbd, STAT_TX_SSI, written); |
| #endif |
| |
| } while (!atomic_read(&priv->suspending) && |
| (gpio_get_value(priv->host_req) || |
| CIRC_CNT(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE))); |
| |
| bcm477x_bye(priv); |
| |
| wake_up(&priv->poll_wait); |
| |
| /* Enable irq */ |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| |
| /* we don't want to enable irq when going to suspending */ |
| if (!atomic_read(&priv->suspending)) |
| if (!atomic_xchg(&priv->irq_enabled, 1)) |
| enable_irq(priv->spi->irq); |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| #ifdef DEBUG_1HZ_STAT |
| if (bbd->stat1hz.ts_irq && ts_rx_start && ts_rx_end) { |
| u64 lat = ts_rx_start - bbd->stat1hz.ts_irq; |
| u64 dur = ts_rx_end - ts_rx_start; |
| |
| bbd->stat1hz.min_rx_lat = (lat < bbd->stat1hz.min_rx_lat) ? |
| lat : bbd->stat1hz.min_rx_lat; |
| bbd->stat1hz.max_rx_lat = (lat > bbd->stat1hz.max_rx_lat) ? |
| lat : bbd->stat1hz.max_rx_lat; |
| bbd->stat1hz.min_rx_dur = (dur < bbd->stat1hz.min_rx_dur) ? |
| dur : bbd->stat1hz.min_rx_dur; |
| bbd->stat1hz.max_rx_dur = (dur > bbd->stat1hz.max_rx_dur) ? |
| dur : bbd->stat1hz.max_rx_dur; |
| bbd->stat1hz.ts_irq = 0; |
| } |
| #endif |
| } |
| |
| |
| /* IRQ Handler */ |
| static irqreturn_t bcm_irq_handler(int irq, void *pdata) |
| { |
| struct bcm_spi_priv *priv = (struct bcm_spi_priv *) pdata; |
| |
| if (!gpio_get_value(priv->host_req)) |
| return IRQ_HANDLED; |
| #ifdef DEBUG_1HZ_STAT |
| { |
| struct bbd_device *bbd = priv->bbd; |
| |
| struct timespec64 ts; |
| |
| ts = ktime_to_timespec64(ktime_get_boottime()); |
| bbd->stat1hz.ts_irq = ts.tv_sec * 1000000000ULL + ts.tv_nsec; |
| } |
| #endif |
| /* Disable irq */ |
| spin_lock(&priv->irq_lock); |
| if (atomic_xchg(&priv->irq_enabled, 0)) |
| disable_irq_nosync(priv->spi->irq); |
| |
| spin_unlock(&priv->irq_lock); |
| |
| /* we don't want to queue work in suspending and shutdown */ |
| if (!atomic_read(&priv->suspending)) |
| queue_work(priv->serial_wq, |
| (struct work_struct *)&priv->rxtx_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int gps_initialize_pinctrl(struct bcm_spi_priv *data) |
| { |
| int ret = 0; |
| struct device *dev = &data->spi->dev; |
| |
| /* Get pinctrl if target uses pinctrl */ |
| data->ts_pinctrl = devm_pinctrl_get(dev); |
| if (IS_ERR_OR_NULL(data->ts_pinctrl)) { |
| dev_err(&data->spi->dev, "Target does not use pinctrl\n"); |
| ret = PTR_ERR(data->ts_pinctrl); |
| data->ts_pinctrl = NULL; |
| return ret; |
| } |
| |
| data->gpio_state_active |
| = pinctrl_lookup_state(data->ts_pinctrl, "gps_active"); |
| if (IS_ERR_OR_NULL(data->gpio_state_active)) { |
| dev_err(&data->spi->dev, "Can not get ts default pinstate\n"); |
| ret = PTR_ERR(data->gpio_state_active); |
| data->ts_pinctrl = NULL; |
| return ret; |
| } |
| |
| data->gpio_state_suspend |
| = pinctrl_lookup_state(data->ts_pinctrl, "gps_suspend"); |
| if (IS_ERR_OR_NULL(data->gpio_state_suspend)) { |
| dev_err(&data->spi->dev, "Can not get ts sleep pinstate\n"); |
| ret = PTR_ERR(data->gpio_state_suspend); |
| data->ts_pinctrl = NULL; |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int gps_pinctrl_select(struct bcm_spi_priv *data, bool on) |
| { |
| int ret = 0; |
| struct pinctrl_state *pins_state; |
| |
| pins_state = on ? data->gpio_state_active : data->gpio_state_suspend; |
| if (!IS_ERR_OR_NULL(pins_state)) { |
| ret = pinctrl_select_state(data->ts_pinctrl, pins_state); |
| if (ret) { |
| dev_err(&data->spi->dev, "can not set %s pins\n", |
| on ? "gps_active" : "gps_suspend"); |
| return ret; |
| } |
| } else { |
| dev_err(&data->spi->dev, "not a valid '%s' pinstate\n", |
| on ? "gps_active" : "gps_suspend"); |
| } |
| |
| return ret; |
| } |
| |
| |
| |
| /* SPI driver operations */ |
| |
| static int bcm_spi_suspend(struct device *dev) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| unsigned long flags; |
| |
| atomic_set(&priv->suspending, 1); |
| |
| /* Disable irq */ |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| if (atomic_xchg(&priv->irq_enabled, 0)) |
| disable_irq_nosync(spi->irq); |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| if (priv->serial_wq) |
| flush_workqueue(priv->serial_wq); |
| |
| priv->ssi_pm_semaphore++; |
| return 0; |
| } |
| |
| static int bcm_spi_resume(struct device *dev) |
| { |
| struct spi_device *spi = to_spi_device(dev); |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| unsigned long flags; |
| |
| atomic_set(&priv->suspending, 0); |
| |
| /* Enable irq */ |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| if (!atomic_xchg(&priv->irq_enabled, 1)) |
| enable_irq(spi->irq); |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| priv->ssi_pm_semaphore--; |
| return 0; |
| } |
| |
| static void bcm_spi_shutdown(struct spi_device *spi) |
| { |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| unsigned long flags; |
| |
| #ifdef CONFIG_TRANSFER_STAT |
| bcm_ssi_print_trans_stat(priv); |
| #endif |
| |
| atomic_set(&priv->suspending, 1); |
| |
| /* Disable irq */ |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| if (atomic_xchg(&priv->irq_enabled, 0)) |
| disable_irq_nosync(spi->irq); |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| flush_workqueue(priv->serial_wq); |
| destroy_workqueue(priv->serial_wq); |
| priv->serial_wq = NULL; |
| } |
| |
| static int bcm_spi_probe(struct spi_device *spi) |
| { |
| int host_req, mcu_req, mcu_resp; |
| int gps_power; |
| int nstandby; |
| struct bcm_spi_priv *priv; |
| bool skip_validity_check; |
| bool legacy_patch = false; |
| int ret; |
| int error = 0; |
| |
| /* Check GPIO# */ |
| #ifndef CONFIG_OF |
| dev_err(&spi->dev, "Check platform_data for bcm device\n"); |
| #else |
| if (!spi->dev.of_node) { |
| dev_err(&spi->dev, "Failed to find of_node\n"); |
| goto err_exit; |
| } |
| #endif |
| |
| host_req = of_get_named_gpio(spi->dev.of_node, "host-req-gpios", 0); |
| mcu_req = of_get_named_gpio(spi->dev.of_node, "mcu-req-gpios", 0); |
| mcu_resp = of_get_named_gpio(spi->dev.of_node, "mcu-resp-gpios", 0); |
| nstandby = of_get_named_gpio(spi->dev.of_node, "nstandby-gpios", 0); |
| skip_validity_check = of_property_read_bool(spi->dev.of_node, |
| "ssp-skip-validity-check"); |
| #ifdef CONFIG_SENSORS_BBD_LEGACY_PATCH |
| legacy_patch = of_property_read_bool(spi->dev.of_node, |
| "ssp-legacy-patch"); |
| #endif |
| |
| dev_info(&spi->dev, "ssp-host-req=%d, ssp-mcu_req=%d, ssp-mcu-resp=%d nstandby=%d \n", |
| host_req, mcu_req, mcu_resp, nstandby); |
| if (host_req < 0 || mcu_req < 0 || mcu_resp < 0 || nstandby < 0) { |
| dev_err(&spi->dev, "GPIO value not correct\n"); |
| goto err_exit; |
| } |
| |
| /* Check IRQ# */ |
| ret = gpio_request(host_req, "HOST REQ"); |
| if (ret) { |
| dev_err(&spi->dev, "failed to request HOST REQ, ret:%d", ret); |
| goto err_exit; |
| } |
| spi->irq = gpio_to_irq(host_req); |
| if (spi->irq < 0) { |
| dev_err(&spi->dev, "irq=%d for host_req=%d not correct\n", |
| spi->irq, host_req); |
| goto err_exit; |
| } |
| |
| /* Config GPIO */ |
| ret = gpio_request(mcu_req, "MCU REQ"); |
| if (ret) { |
| dev_err(&spi->dev, "failed to request MCU REQ, ret:%d", ret); |
| goto err_exit; |
| } |
| ret = gpio_direction_output(mcu_req, 0); |
| if (ret) { |
| dev_err(&spi->dev, "failed set MCU REQ as input mode, ret:%d", |
| ret); |
| goto err_exit; |
| } |
| ret = gpio_request(mcu_resp, "MCU RESP"); |
| if (ret) { |
| dev_err(&spi->dev, "failed to request MCU RESP, ret:%d", ret); |
| goto err_exit; |
| } |
| ret = gpio_direction_input(mcu_resp); |
| if (ret) { |
| dev_err(&spi->dev, "failed set MCU RESP as input mode, ret:%d", |
| ret); |
| goto err_exit; |
| } |
| |
| ret = gpio_request(nstandby, "GPS NSTANDBY"); |
| if (ret) { |
| dev_err(&spi->dev, "failed to request GPS NSTANDBY, ret:%d", ret); |
| goto err_exit; |
| } |
| ret = gpio_direction_output(nstandby, 0); |
| if (ret) { |
| dev_err(&spi->dev, "failed set GPS NSTANDBY as out mode, ret:%d", |
| ret); |
| goto err_exit; |
| } |
| |
| /* enable gps_power */ |
| gps_power = of_get_named_gpio(spi->dev.of_node, "gps-power-enable", 0); |
| if (gps_power >= 0) { |
| ret = gpio_request(gps_power, "GPS POWER"); |
| if (ret) { |
| dev_err(&spi->dev, "failed to request GPS POWER, ret:%d", ret); |
| goto err_exit; |
| } |
| ret = gpio_direction_output(gps_power, 1); |
| if (ret) { |
| dev_err(&spi->dev, "failed set GPS POWER as out mode, ret:%d", ret); |
| goto err_exit; |
| } |
| } |
| |
| /* Alloc everything */ |
| priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| goto err_exit; |
| |
| priv->skip_validity_check = skip_validity_check; |
| priv->spi = spi; |
| priv->tx_buf = devm_kmalloc(&spi->dev, |
| sizeof(struct bcm_ssi_tx_frame), GFP_KERNEL); |
| priv->rx_buf = devm_kmalloc(&spi->dev, |
| sizeof(struct bcm_ssi_rx_frame), GFP_KERNEL); |
| if (!priv->tx_buf || !priv->rx_buf) |
| goto err_exit; |
| |
| priv->serial_wq = alloc_workqueue("bcm477x_wq", |
| WQ_HIGHPRI|WQ_UNBOUND|WQ_MEM_RECLAIM, 1); |
| if (!priv->serial_wq) { |
| dev_err(&spi->dev, "Failed to allocate workqueue\n"); |
| goto err_exit; |
| } |
| /* Init - pinctrl */ |
| error = gps_initialize_pinctrl(priv); |
| |
| /* Register misc device */ |
| priv->misc.minor = MISC_DYNAMIC_MINOR; |
| priv->misc.name = "ttyBCM"; |
| priv->misc.fops = &bcm_spi_fops; |
| |
| ret = misc_register(&priv->misc); |
| if (ret) { |
| dev_err(&spi->dev, "Failed to register bcm_gps_spi's misc dev. err=%d\n", ret); |
| goto free_wq; |
| } |
| |
| /* Set driver data */ |
| spi_set_drvdata(spi, priv); |
| |
| /* Init - miscdev stuff */ |
| init_waitqueue_head(&priv->poll_wait); |
| priv->read_buf.buf = priv->_read_buf; |
| priv->write_buf.buf = priv->_write_buf; |
| mutex_init(&priv->rlock); |
| mutex_init(&priv->wlock); |
| priv->busy = false; |
| |
| /* Init - work */ |
| INIT_WORK((struct work_struct *)&priv->rxtx_work, bcm_rxtx_work_func); |
| |
| /* Init - irq stuff */ |
| spin_lock_init(&priv->irq_lock); |
| atomic_set(&priv->irq_enabled, 0); |
| atomic_set(&priv->suspending, 0); |
| |
| /* Init - gpios */ |
| priv->host_req = host_req; |
| priv->mcu_req = mcu_req; |
| priv->mcu_resp = mcu_resp; |
| priv->nstandby = nstandby; |
| |
| /* Init BBD & SSP */ |
| priv->bbd = bbd_init(&spi->dev, legacy_patch); |
| if (priv->bbd == NULL) |
| goto free_wq; |
| |
| if (device_create_file(&priv->spi->dev, &dev_attr_nstandby)) |
| dev_err(&spi->dev, "Unable to create sysfs 4775 nstandby entry"); |
| |
| if (device_create_file(&priv->spi->dev, &dev_attr_sspmcureq)) |
| dev_err(&spi->dev, "Unable to create sysfs 4775 sspmcureq entry"); |
| |
| /* Request IRQ */ |
| ret = devm_request_irq(&spi->dev, spi->irq, bcm_irq_handler, |
| IRQF_TRIGGER_HIGH | IRQF_NO_AUTOEN, "ttyBCM", priv); |
| if (ret) { |
| dev_err(&spi->dev, "Failed to register BCM477x SPI TTY IRQ %d.\n", |
| spi->irq); |
| goto free_wq; |
| } |
| |
| dev_info(&spi->dev, "Probe OK. ssp-host-req=%d, irq=%d, priv=0x%pK\n", |
| host_req, spi->irq, priv); |
| |
| return 0; |
| |
| free_wq: |
| if (priv->serial_wq) |
| destroy_workqueue(priv->serial_wq); |
| err_exit: |
| return -ENODEV; |
| } |
| |
| |
| static int bcm_spi_remove(struct spi_device *spi) |
| { |
| struct bcm_spi_priv *priv = spi_get_drvdata(spi); |
| unsigned long flags; |
| |
| atomic_set(&priv->suspending, 1); |
| |
| /* Disable irq */ |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| if (atomic_xchg(&priv->irq_enabled, 0)) |
| disable_irq_nosync(spi->irq); |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| /* Flush work */ |
| flush_workqueue(priv->serial_wq); |
| destroy_workqueue(priv->serial_wq); |
| if (priv->ts_pinctrl) { |
| if (gps_pinctrl_select(priv, false) < 0) |
| dev_err(&priv->spi->dev, "Cannot get idle pinctrl state\n"); |
| } |
| |
| /* Free everything */ |
| bbd_exit(&spi->dev); |
| |
| device_remove_file(&priv->spi->dev, &dev_attr_nstandby); |
| device_remove_file(&priv->spi->dev, &dev_attr_sspmcureq); |
| return 0; |
| } |
| |
| static const struct spi_device_id bcm_spi_id[] = { |
| {"ssp-spi", 0}, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(spi, bcm_spi_id); |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id match_table[] = { |
| { .compatible = "ssp-spi,bcm4775",}, |
| {}, |
| }; |
| #endif |
| |
| static const struct dev_pm_ops bcm_spi_pm_ops = { |
| .suspend = bcm_spi_suspend, |
| .resume = bcm_spi_resume, |
| }; |
| |
| static struct spi_driver bcm_spi_driver = { |
| .id_table = bcm_spi_id, |
| .probe = bcm_spi_probe, |
| .remove = bcm_spi_remove, |
| .shutdown = bcm_spi_shutdown, |
| .driver = { |
| .name = "brcm gps spi", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_OF |
| .of_match_table = match_table, |
| #endif |
| .pm = &bcm_spi_pm_ops, |
| }, |
| }; |
| |
| |
| /* Module init/exit */ |
| static int __init bcm_spi_init(void) |
| { |
| return spi_register_driver(&bcm_spi_driver); |
| } |
| |
| static void __exit bcm_spi_exit(void) |
| { |
| spi_unregister_driver(&bcm_spi_driver); |
| } |
| |
| module_init(bcm_spi_init); |
| module_exit(bcm_spi_exit); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("BCM SPI/SSI Driver"); |