/* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(msg) "bgcom: %s: " msg, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bgcom.h" #include "bgrsb.h" #include "bgcom_interface.h" #define BG_SPI_WORD_SIZE (0x04) #define BG_SPI_READ_LEN (0x04) #define BG_SPI_WRITE_CMND_LEN (0x01) #define BG_SPI_FIFO_READ_CMD (0x41) #define BG_SPI_FIFO_WRITE_CMD (0x40) #define BG_SPI_AHB_READ_CMD (0x43) #define BG_SPI_AHB_WRITE_CMD (0x42) #define BG_SPI_AHB_CMD_LEN (0x05) #define BG_SPI_AHB_READ_CMD_LEN (0x08) #define BG_STATUS_REG (0x05) #define BG_CMND_REG (0x14) #define BG_SPI_MAX_WORDS (0x3FFFFFFD) #define BG_SPI_MAX_REGS (0x0A) #define HED_EVENT_ID_LEN (0x02) #define HED_EVENT_SIZE_LEN (0x02) #define HED_EVENT_DATA_STRT_LEN (0x05) #define CMA_BFFR_POOL_SIZE (128*1024) #define MAX_RETRY 100 enum bgcom_state { /*BGCOM Staus ready*/ BGCOM_PROB_SUCCESS = 0, BGCOM_PROB_WAIT = 1, BGCOM_STATE_SUSPEND = 2, BGCOM_STATE_ACTIVE = 3 }; enum bgcom_req_type { /*BGCOM local requests*/ BGCOM_READ_REG = 0, BGCOM_READ_FIFO = 1, BGCOM_READ_AHB = 2, BGCOM_WRITE_REG = 3, }; struct bg_spi_priv { struct spi_device *spi; /* Transaction related */ struct mutex xfer_mutex; void *lhandle; /* Message for single transfer */ struct spi_message msg1; struct spi_transfer xfer1; int irq_lock; enum bgcom_state bg_state; }; struct cb_data { void *priv; void *handle; void (*bgcom_notification_cb)(void *handle, void *priv, enum bgcom_event_type event, union bgcom_event_data_type *event_data); struct list_head list; }; struct bg_context { struct bg_spi_priv *bg_spi; enum bgcom_state state; struct cb_data *cb; }; struct event_list { struct event *evnt; struct list_head list; }; static void *bg_com_drv; static uint32_t g_slav_status_reg; /* BGCOM client callbacks set-up */ static void send_input_events(struct work_struct *work); static struct list_head cb_head = LIST_HEAD_INIT(cb_head); static struct list_head pr_lst_hd = LIST_HEAD_INIT(pr_lst_hd); static DEFINE_SPINLOCK(lst_setup_lock); static enum bgcom_spi_state spi_state; static struct workqueue_struct *wq; static DECLARE_WORK(input_work, send_input_events); static struct mutex bg_resume_mutex; static atomic_t bg_is_spi_active; static int bg_irq; static uint8_t *fxd_mem_buffer; static struct mutex cma_buffer_lock; static struct spi_device *get_spi_device(void) { struct bg_spi_priv *bg_spi = container_of(bg_com_drv, struct bg_spi_priv, lhandle); struct spi_device *spi = bg_spi->spi; return spi; } static void augmnt_fifo(uint8_t *data, int pos) { data[pos] = '\0'; } static void send_input_events(struct work_struct *work) { struct list_head *temp; struct list_head *pos; struct event_list *node; struct event *evnt; if (list_empty(&pr_lst_hd)) return; list_for_each_safe(pos, temp, &pr_lst_hd) { node = list_entry(pos, struct event_list, list); evnt = node->evnt; bgrsb_send_input(evnt); kfree(evnt); spin_lock(&lst_setup_lock); list_del(&node->list); spin_unlock(&lst_setup_lock); kfree(node); } } int bgcom_set_spi_state(enum bgcom_spi_state state) { struct bg_spi_priv *bg_spi = container_of(bg_com_drv, struct bg_spi_priv, lhandle); const struct device spi_dev = bg_spi->spi->master->dev; ktime_t time_start, delta; s64 time_elapsed; if (state < 0 || state > 1) return -EINVAL; if (state == spi_state) return 0; mutex_lock(&bg_spi->xfer_mutex); if (state == BGCOM_SPI_BUSY) { time_start = ktime_get(); while (!pm_runtime_status_suspended(spi_dev.parent)) { delta = ktime_sub(ktime_get(), time_start); time_elapsed = ktime_to_ms(delta); WARN_ON(time_elapsed > 5 * MSEC_PER_SEC); msleep(100); } } spi_state = state; mutex_unlock(&bg_spi->xfer_mutex); return 0; } EXPORT_SYMBOL(bgcom_set_spi_state); static inline void add_to_irq_list(struct cb_data *data) { list_add_tail(&data->list, &cb_head); } static bool is_bgcom_ready(void) { return (bg_com_drv != NULL ? true : false); } static void bg_spi_reinit_xfer(struct spi_transfer *xfer) { xfer->tx_buf = NULL; xfer->rx_buf = NULL; xfer->delay_usecs = 0; xfer->len = 0; } static int read_bg_locl(enum bgcom_req_type req_type, uint32_t no_of_words, void *buf) { struct bg_context clnt_handle; struct bg_spi_priv *spi = container_of(bg_com_drv, struct bg_spi_priv, lhandle); int ret = 0; if (!buf) return -EINVAL; clnt_handle.bg_spi = spi; switch (req_type) { case BGCOM_READ_REG: ret = bgcom_reg_read(&clnt_handle, BG_STATUS_REG, no_of_words, buf); break; case BGCOM_READ_FIFO: ret = bgcom_fifo_read(&clnt_handle, no_of_words, buf); break; case BGCOM_WRITE_REG: ret = bgcom_reg_write(&clnt_handle, BG_CMND_REG, no_of_words, buf); break; case BGCOM_READ_AHB: break; } return ret; } static int bgcom_transfer(void *handle, uint8_t *tx_buf, uint8_t *rx_buf, uint32_t txn_len) { struct spi_transfer *tx_xfer; struct bg_spi_priv *bg_spi; struct bg_context *cntx; struct spi_device *spi; int ret; if (!handle || !tx_buf) return -EINVAL; cntx = (struct bg_context *)handle; if (cntx->state == BGCOM_PROB_WAIT) { if (!is_bgcom_ready()) return -ENODEV; cntx->bg_spi = container_of(bg_com_drv, struct bg_spi_priv, lhandle); cntx->state = BGCOM_PROB_SUCCESS; } bg_spi = cntx->bg_spi; if (!bg_spi) return -ENODEV; tx_xfer = &bg_spi->xfer1; spi = bg_spi->spi; if (!atomic_read(&bg_is_spi_active)) return -ECANCELED; mutex_lock(&bg_spi->xfer_mutex); bg_spi_reinit_xfer(tx_xfer); tx_xfer->tx_buf = tx_buf; if (rx_buf) tx_xfer->rx_buf = rx_buf; tx_xfer->len = txn_len; pm_runtime_get_sync(bg_spi->spi->controller->dev.parent); ret = spi_sync(spi, &bg_spi->msg1); pm_runtime_put_sync_suspend(bg_spi->spi->controller->dev.parent); mutex_unlock(&bg_spi->xfer_mutex); if (ret) pr_err("SPI transaction failed: %d\n", ret); return ret; } /* BG-COM Interrupt handling */ static inline void send_event(enum bgcom_event_type event, void *data) { struct list_head *pos; struct cb_data *cb; /* send interrupt notification for each * registered call-back */ list_for_each(pos, &cb_head) { cb = list_entry(pos, struct cb_data, list); cb->bgcom_notification_cb(cb->handle, cb->priv, event, data); } } void bgcom_bgdown_handler(void) { send_event(BGCOM_EVENT_RESET_OCCURRED, NULL); g_slav_status_reg = 0; } EXPORT_SYMBOL(bgcom_bgdown_handler); static void parse_fifo(uint8_t *data, union bgcom_event_data_type *event_data) { uint16_t p_len; uint8_t sub_id; uint32_t evnt_tm; uint16_t event_id; void *evnt_data; struct event *evnt; struct event_list *data_list; while (*data != '\0') { event_id = *((uint16_t *) data); data = data + HED_EVENT_ID_LEN; p_len = *((uint16_t *) data); data = data + HED_EVENT_SIZE_LEN; if (event_id == 0xFFFE) { sub_id = *data; evnt_tm = *((uint32_t *)(data+1)); evnt = kmalloc(sizeof(*evnt), GFP_KERNEL); evnt->sub_id = sub_id; evnt->evnt_tm = evnt_tm; evnt->evnt_data = *(int16_t *)(data + HED_EVENT_DATA_STRT_LEN); data_list = kmalloc(sizeof(*data_list), GFP_KERNEL); data_list->evnt = evnt; spin_lock(&lst_setup_lock); list_add_tail(&data_list->list, &pr_lst_hd); spin_unlock(&lst_setup_lock); } else if (event_id == 0x0001) { evnt_data = kmalloc(p_len, GFP_KERNEL); if (evnt_data != NULL) { memcpy(evnt_data, data, p_len); event_data->fifo_data.to_master_fifo_used = p_len/BG_SPI_WORD_SIZE; event_data->fifo_data.data = evnt_data; send_event(BGCOM_EVENT_TO_MASTER_FIFO_USED, event_data); } } data = data + p_len; } if (!list_empty(&pr_lst_hd)) queue_work(wq, &input_work); } static void send_back_notification(uint32_t slav_status_reg, uint32_t slav_status_auto_clear_reg, uint32_t fifo_fill_reg, uint32_t fifo_size_reg) { uint16_t master_fifo_used; uint16_t slave_fifo_free; uint32_t *ptr; int ret; union bgcom_event_data_type event_data = { .fifo_data = {0} }; master_fifo_used = (uint16_t)fifo_fill_reg; slave_fifo_free = (uint16_t)(fifo_fill_reg >> 16); if (slav_status_auto_clear_reg & BIT(31)) send_event(BGCOM_EVENT_RESET_OCCURRED, NULL); if (slav_status_auto_clear_reg & BIT(30)) send_event(BGCOM_EVENT_ERROR_WRITE_FIFO_OVERRUN, NULL); if (slav_status_auto_clear_reg & BIT(29)) send_event(BGCOM_EVENT_ERROR_WRITE_FIFO_BUS_ERR, NULL); if (slav_status_auto_clear_reg & BIT(28)) send_event(BGCOM_EVENT_ERROR_WRITE_FIFO_ACCESS, NULL); if (slav_status_auto_clear_reg & BIT(27)) send_event(BGCOM_EVENT_ERROR_READ_FIFO_UNDERRUN, NULL); if (slav_status_auto_clear_reg & BIT(26)) send_event(BGCOM_EVENT_ERROR_READ_FIFO_BUS_ERR, NULL); if (slav_status_auto_clear_reg & BIT(25)) send_event(BGCOM_EVENT_ERROR_READ_FIFO_ACCESS, NULL); if (slav_status_auto_clear_reg & BIT(24)) send_event(BGCOM_EVENT_ERROR_TRUNCATED_READ, NULL); if (slav_status_auto_clear_reg & BIT(23)) send_event(BGCOM_EVENT_ERROR_TRUNCATED_WRITE, NULL); if (slav_status_auto_clear_reg & BIT(22)) send_event(BGCOM_EVENT_ERROR_AHB_ILLEGAL_ADDRESS, NULL); if (slav_status_auto_clear_reg & BIT(21)) send_event(BGCOM_EVENT_ERROR_AHB_BUS_ERR, NULL); /* check if BG status is changed */ if (g_slav_status_reg ^ slav_status_reg) { if (slav_status_reg & BIT(30)) { event_data.application_running = true; send_event(BGCOM_EVENT_APPLICATION_RUNNING, &event_data); } if (slav_status_reg & BIT(29)) { event_data.to_slave_fifo_ready = true; send_event(BGCOM_EVENT_TO_SLAVE_FIFO_READY, &event_data); } if (slav_status_reg & BIT(28)) { event_data.to_master_fifo_ready = true; send_event(BGCOM_EVENT_TO_MASTER_FIFO_READY, &event_data); } if (slav_status_reg & BIT(27)) { event_data.ahb_ready = true; send_event(BGCOM_EVENT_AHB_READY, &event_data); } } if (master_fifo_used > 0) { ptr = kzalloc(master_fifo_used*BG_SPI_WORD_SIZE + 1, GFP_KERNEL | GFP_ATOMIC); if (ptr != NULL) { ret = read_bg_locl(BGCOM_READ_FIFO, master_fifo_used, ptr); if (!ret) { augmnt_fifo((uint8_t *)ptr, master_fifo_used*BG_SPI_WORD_SIZE); parse_fifo((uint8_t *)ptr, &event_data); } kfree(ptr); } } event_data.to_slave_fifo_free = slave_fifo_free; send_event(BGCOM_EVENT_TO_SLAVE_FIFO_FREE, &event_data); } static void bg_irq_tasklet_hndlr_l(void) { uint32_t slave_status_reg; uint32_t glink_isr_reg; uint32_t slav_status_auto_clear_reg; uint32_t fifo_fill_reg; uint32_t fifo_size_reg; int ret = 0; uint32_t irq_buf[5] = {0}; ret = read_bg_locl(BGCOM_READ_REG, 5, &irq_buf[0]); if (ret) return; /* save current state */ slave_status_reg = irq_buf[0]; glink_isr_reg = irq_buf[1]; slav_status_auto_clear_reg = irq_buf[2]; fifo_fill_reg = irq_buf[3]; fifo_size_reg = irq_buf[4]; send_back_notification(slave_status_reg, slav_status_auto_clear_reg, fifo_fill_reg, fifo_size_reg); g_slav_status_reg = slave_status_reg; } int bgcom_ahb_read(void *handle, uint32_t ahb_start_addr, uint32_t num_words, void *read_buf) { uint32_t txn_len; uint8_t *tx_buf; uint8_t *rx_buf; uint32_t size; int ret; uint8_t cmnd = 0; uint32_t ahb_addr = 0; if (!handle || !read_buf || num_words == 0 || num_words > BG_SPI_MAX_WORDS) { pr_err("Invalid param\n"); return -EINVAL; } if (!is_bgcom_ready()) return -ENODEV; if (spi_state == BGCOM_SPI_BUSY) { pr_err("Device busy\n"); return -EBUSY; } if (bgcom_resume(handle)) { pr_err("Failed to resume\n"); return -EBUSY; } size = num_words*BG_SPI_WORD_SIZE; txn_len = BG_SPI_AHB_READ_CMD_LEN + size; tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC); if (!tx_buf) return -ENOMEM; rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC); if (!rx_buf) { kfree(tx_buf); return -ENOMEM; } cmnd |= BG_SPI_AHB_READ_CMD; ahb_addr |= ahb_start_addr; memcpy(tx_buf, &cmnd, sizeof(cmnd)); memcpy(tx_buf+sizeof(cmnd), &ahb_addr, sizeof(ahb_addr)); ret = bgcom_transfer(handle, tx_buf, rx_buf, txn_len); if (!ret) memcpy(read_buf, rx_buf+BG_SPI_AHB_READ_CMD_LEN, size); kfree(tx_buf); kfree(rx_buf); return ret; } EXPORT_SYMBOL(bgcom_ahb_read); int bgcom_ahb_write(void *handle, uint32_t ahb_start_addr, uint32_t num_words, void *write_buf) { dma_addr_t dma_hndl; uint32_t txn_len; uint8_t *tx_buf; uint32_t size; int ret; bool is_cma_used = false; uint8_t cmnd = 0; uint32_t ahb_addr = 0; struct spi_device *spi = get_spi_device(); if (!handle || !write_buf || num_words == 0 || num_words > BG_SPI_MAX_WORDS) { pr_err("Invalid param\n"); return -EINVAL; } if (!is_bgcom_ready()) return -ENODEV; if (spi_state == BGCOM_SPI_BUSY) { pr_err("Device busy\n"); return -EBUSY; } if (bgcom_resume(handle)) { pr_err("Failed to resume\n"); return -EBUSY; } mutex_lock(&cma_buffer_lock); size = num_words*BG_SPI_WORD_SIZE; txn_len = BG_SPI_AHB_CMD_LEN + size; if (fxd_mem_buffer != NULL && txn_len <= CMA_BFFR_POOL_SIZE) { memset(fxd_mem_buffer, 0, txn_len); tx_buf = fxd_mem_buffer; is_cma_used = true; } else { pr_info("DMA memory used for size[%d]\n", txn_len); tx_buf = dma_zalloc_coherent(&spi->dev, txn_len, &dma_hndl, GFP_KERNEL); } if (!tx_buf) { mutex_unlock(&cma_buffer_lock); return -ENOMEM; } cmnd |= BG_SPI_AHB_WRITE_CMD; ahb_addr |= ahb_start_addr; memcpy(tx_buf, &cmnd, sizeof(cmnd)); memcpy(tx_buf+sizeof(cmnd), &ahb_addr, sizeof(ahb_addr)); memcpy(tx_buf+BG_SPI_AHB_CMD_LEN, write_buf, size); ret = bgcom_transfer(handle, tx_buf, NULL, txn_len); if (!is_cma_used) dma_free_coherent(&spi->dev, txn_len, tx_buf, dma_hndl); mutex_unlock(&cma_buffer_lock); return ret; } EXPORT_SYMBOL(bgcom_ahb_write); int bgcom_fifo_write(void *handle, uint32_t num_words, void *write_buf) { uint32_t txn_len; uint8_t *tx_buf; uint32_t size; int ret; uint8_t cmnd = 0; if (!handle || !write_buf || num_words == 0 || num_words > BG_SPI_MAX_WORDS) { pr_err("Invalid param\n"); return -EINVAL; } if (!is_bgcom_ready()) return -ENODEV; if (spi_state == BGCOM_SPI_BUSY) { pr_err("Device busy\n"); return -EBUSY; } if (bgcom_resume(handle)) { pr_err("Failed to resume\n"); return -EBUSY; } size = num_words*BG_SPI_WORD_SIZE; txn_len = BG_SPI_WRITE_CMND_LEN + size; tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC); if (!tx_buf) return -ENOMEM; cmnd |= BG_SPI_FIFO_WRITE_CMD; memcpy(tx_buf, &cmnd, sizeof(cmnd)); memcpy(tx_buf+sizeof(cmnd), write_buf, size); ret = bgcom_transfer(handle, tx_buf, NULL, txn_len); kfree(tx_buf); return ret; } EXPORT_SYMBOL(bgcom_fifo_write); int bgcom_fifo_read(void *handle, uint32_t num_words, void *read_buf) { uint32_t txn_len; uint8_t *tx_buf; uint8_t *rx_buf; uint32_t size; uint8_t cmnd = 0; int ret = 0; if (!handle || !read_buf || num_words == 0 || num_words > BG_SPI_MAX_WORDS) { pr_err("Invalid param\n"); return -EINVAL; } if (!is_bgcom_ready()) return -ENODEV; if (spi_state == BGCOM_SPI_BUSY) { pr_err("Device busy\n"); return -EBUSY; } if (bgcom_resume(handle)) { pr_err("Failed to resume\n"); return -EBUSY; } size = num_words*BG_SPI_WORD_SIZE; txn_len = BG_SPI_READ_LEN + size; tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC); if (!tx_buf) return -ENOMEM; rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC); if (!rx_buf) { kfree(tx_buf); return -ENOMEM; } cmnd |= BG_SPI_FIFO_READ_CMD; memcpy(tx_buf, &cmnd, sizeof(cmnd)); ret = bgcom_transfer(handle, tx_buf, rx_buf, txn_len); if (!ret) memcpy(read_buf, rx_buf+BG_SPI_READ_LEN, size); kfree(tx_buf); kfree(rx_buf); return ret; } EXPORT_SYMBOL(bgcom_fifo_read); int bgcom_reg_write(void *handle, uint8_t reg_start_addr, uint8_t num_regs, void *write_buf) { uint32_t txn_len; uint8_t *tx_buf; uint32_t size; uint8_t cmnd = 0; int ret = 0; if (!handle || !write_buf || num_regs == 0 || num_regs > BG_SPI_MAX_REGS) { pr_err("Invalid param\n"); return -EINVAL; } if (!is_bgcom_ready()) return -ENODEV; if (spi_state == BGCOM_SPI_BUSY) { pr_err("Device busy\n"); return -EBUSY; } if (bgcom_resume(handle)) { pr_err("Failed to resume\n"); return -EBUSY; } size = num_regs*BG_SPI_WORD_SIZE; txn_len = BG_SPI_WRITE_CMND_LEN + size; tx_buf = kzalloc(txn_len, GFP_KERNEL); if (!tx_buf) return -ENOMEM; cmnd |= reg_start_addr; memcpy(tx_buf, &cmnd, sizeof(cmnd)); memcpy(tx_buf+sizeof(cmnd), write_buf, size); ret = bgcom_transfer(handle, tx_buf, NULL, txn_len); kfree(tx_buf); return ret; } EXPORT_SYMBOL(bgcom_reg_write); int bgcom_reg_read(void *handle, uint8_t reg_start_addr, uint32_t num_regs, void *read_buf) { uint32_t txn_len; uint8_t *tx_buf; uint8_t *rx_buf; uint32_t size; int ret; uint8_t cmnd = 0; if (!handle || !read_buf || num_regs == 0 || num_regs > BG_SPI_MAX_REGS) { pr_err("Invalid param\n"); return -EINVAL; } if (!is_bgcom_ready()) return -ENODEV; if (spi_state == BGCOM_SPI_BUSY) { pr_err("Device busy\n"); return -EBUSY; } size = num_regs*BG_SPI_WORD_SIZE; txn_len = BG_SPI_READ_LEN + size; tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC); if (!tx_buf) return -ENOMEM; rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC); if (!rx_buf) { kfree(tx_buf); return -ENOMEM; } cmnd |= reg_start_addr; memcpy(tx_buf, &cmnd, sizeof(cmnd)); ret = bgcom_transfer(handle, tx_buf, rx_buf, txn_len); if (!ret) memcpy(read_buf, rx_buf+BG_SPI_READ_LEN, size); kfree(tx_buf); kfree(rx_buf); return ret; } EXPORT_SYMBOL(bgcom_reg_read); static int is_bg_resume(void *handle) { uint32_t txn_len; int ret; uint8_t tx_buf[8] = {0}; uint8_t rx_buf[8] = {0}; uint32_t cmnd_reg = 0; if (spi_state == BGCOM_SPI_BUSY) { printk_ratelimited("SPI is held by TZ\n"); goto ret_err; } txn_len = 0x08; tx_buf[0] = 0x05; ret = bgcom_transfer(handle, tx_buf, rx_buf, txn_len); if (!ret) memcpy(&cmnd_reg, rx_buf+BG_SPI_READ_LEN, 0x04); ret_err: return cmnd_reg & BIT(31); } int bgcom_resume(void *handle) { struct bg_spi_priv *bg_spi; struct bg_context *cntx; int retry = 0; if (handle == NULL) return -EINVAL; if (!atomic_read(&bg_is_spi_active)) return -ECANCELED; cntx = (struct bg_context *)handle; /* if client is outside bgcom scope and * handle is provided before BGCOM probed */ if (cntx->state == BGCOM_PROB_WAIT) { pr_info("handle is provided before BGCOM probed\n"); if (!is_bgcom_ready()) return -EAGAIN; cntx->bg_spi = container_of(bg_com_drv, struct bg_spi_priv, lhandle); cntx->state = BGCOM_PROB_SUCCESS; } bg_spi = cntx->bg_spi; mutex_lock(&bg_resume_mutex); if (bg_spi->bg_state == BGCOM_STATE_ACTIVE) goto unlock; enable_irq(bg_irq); do { if (is_bg_resume(handle)) { bg_spi->bg_state = BGCOM_STATE_ACTIVE; break; } udelay(1000); ++retry; } while (retry < MAX_RETRY); unlock: mutex_unlock(&bg_resume_mutex); if (retry == MAX_RETRY) { /* BG failed to resume. Trigger BG soft reset. */ pr_err("BG failed to resume\n"); pr_err("%s: gpio#95 value is: %d\n", __func__, gpio_get_value(95)); pr_err("%s: gpio#97 value is: %d\n", __func__, gpio_get_value(97)); BUG(); bg_soft_reset(); return -ETIMEDOUT; } return 0; } EXPORT_SYMBOL(bgcom_resume); int bgcom_suspend(void *handle) { if (!handle) return -EINVAL; return 0; } EXPORT_SYMBOL(bgcom_suspend); void *bgcom_open(struct bgcom_open_config_type *open_config) { struct bg_spi_priv *spi; struct cb_data *irq_notification; struct bg_context *clnt_handle = kzalloc(sizeof(*clnt_handle), GFP_KERNEL); if (!clnt_handle) return NULL; /* Client handle Set-up */ if (!is_bgcom_ready()) { clnt_handle->bg_spi = NULL; clnt_handle->state = BGCOM_PROB_WAIT; } else { spi = container_of(bg_com_drv, struct bg_spi_priv, lhandle); clnt_handle->bg_spi = spi; clnt_handle->state = BGCOM_PROB_SUCCESS; } clnt_handle->cb = NULL; /* Interrupt callback Set-up */ if (open_config && open_config->bgcom_notification_cb) { irq_notification = kzalloc(sizeof(*irq_notification), GFP_KERNEL); if (!irq_notification) goto error_ret; /* set irq node */ irq_notification->handle = clnt_handle; irq_notification->priv = open_config->priv; irq_notification->bgcom_notification_cb = open_config->bgcom_notification_cb; add_to_irq_list(irq_notification); clnt_handle->cb = irq_notification; } return clnt_handle; error_ret: kfree(clnt_handle); return NULL; } EXPORT_SYMBOL(bgcom_open); int bgcom_close(void **handle) { struct bg_context *lhandle; struct cb_data *cb = NULL; if (*handle == NULL) return -EINVAL; lhandle = *handle; cb = lhandle->cb; if (cb) list_del(&cb->list); kfree(*handle); *handle = NULL; return 0; } EXPORT_SYMBOL(bgcom_close); static irqreturn_t bg_irq_tasklet_hndlr(int irq, void *device) { struct bg_spi_priv *bg_spi = device; /* check if call-back exists */ if (!atomic_read(&bg_is_spi_active)) { pr_debug("Interrupt received in suspend state\n"); return IRQ_HANDLED; } else if (list_empty(&cb_head)) { pr_debug("No callback registered\n"); return IRQ_HANDLED; } else if (spi_state == BGCOM_SPI_BUSY) { /* delay for SPI to be freed */ msleep(50); return IRQ_HANDLED; } else if (!bg_spi->irq_lock) { bg_spi->irq_lock = 1; bg_irq_tasklet_hndlr_l(); bg_spi->irq_lock = 0; } return IRQ_HANDLED; } static void bg_spi_init(struct bg_spi_priv *bg_spi) { if (!bg_spi) { pr_err("device not found\n"); return; } /* BGCOM SPI set-up */ mutex_init(&bg_spi->xfer_mutex); spi_message_init(&bg_spi->msg1); spi_message_add_tail(&bg_spi->xfer1, &bg_spi->msg1); /* BGCOM IRQ set-up */ bg_spi->irq_lock = 0; spi_state = BGCOM_SPI_FREE; wq = create_singlethread_workqueue("input_wq"); bg_spi->bg_state = BGCOM_STATE_ACTIVE; bg_com_drv = &bg_spi->lhandle; mutex_init(&bg_resume_mutex); fxd_mem_buffer = kmalloc(CMA_BFFR_POOL_SIZE, GFP_KERNEL | GFP_ATOMIC); mutex_init(&cma_buffer_lock); } static int bg_spi_probe(struct spi_device *spi) { struct bg_spi_priv *bg_spi; struct device_node *node; int irq_gpio = 0; int ret; bg_spi = devm_kzalloc(&spi->dev, sizeof(*bg_spi), GFP_KERNEL | GFP_ATOMIC); pr_info("%s started\n", __func__); if (!bg_spi) return -ENOMEM; bg_spi->spi = spi; spi_set_drvdata(spi, bg_spi); bg_spi_init(bg_spi); /* BGCOM Interrupt probe */ node = spi->dev.of_node; irq_gpio = of_get_named_gpio(node, "qcom,irq-gpio", 0); if (!gpio_is_valid(irq_gpio)) { pr_err("gpio %d found is not valid\n", irq_gpio); goto err_ret; } ret = gpio_request(irq_gpio, "bgcom_gpio"); if (ret) { pr_err("gpio %d request failed\n", irq_gpio); goto err_ret; } ret = gpio_direction_input(irq_gpio); if (ret) { pr_err("gpio_direction_input not set: %d\n", ret); goto err_ret; } bg_irq = gpio_to_irq(irq_gpio); ret = request_threaded_irq(bg_irq, NULL, bg_irq_tasklet_hndlr, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "qcom-bg_spi", bg_spi); if (ret) goto err_ret; atomic_set(&bg_is_spi_active, 1); dma_set_coherent_mask(&spi->dev, DMA_BIT_MASK(64)); pr_info("%s success\n", __func__); pr_info("Bgcom Probed successfully\n"); return ret; err_ret: bg_com_drv = NULL; mutex_destroy(&bg_spi->xfer_mutex); spi_set_drvdata(spi, NULL); return -ENODEV; } static int bg_spi_remove(struct spi_device *spi) { struct bg_spi_priv *bg_spi = spi_get_drvdata(spi); bg_com_drv = NULL; mutex_destroy(&bg_spi->xfer_mutex); devm_kfree(&spi->dev, bg_spi); spi_set_drvdata(spi, NULL); if (fxd_mem_buffer != NULL) kfree(fxd_mem_buffer); mutex_destroy(&cma_buffer_lock); return 0; } static void bg_spi_shutdown(struct spi_device *spi) { bg_spi_remove(spi); } static int bgcom_pm_suspend(struct device *dev) { uint32_t cmnd_reg = 0; struct spi_device *s_dev = to_spi_device(dev); struct bg_spi_priv *bg_spi = spi_get_drvdata(s_dev); int ret = 0; if (bg_spi->bg_state == BGCOM_STATE_SUSPEND) return 0; cmnd_reg |= BIT(31); ret = read_bg_locl(BGCOM_WRITE_REG, 1, &cmnd_reg); if (ret == 0) { bg_spi->bg_state = BGCOM_STATE_SUSPEND; atomic_set(&bg_is_spi_active, 0); disable_irq(bg_irq); } pr_info("suspended with : %d\n", ret); return ret; } static int bgcom_pm_resume(struct device *dev) { struct bg_context clnt_handle; int ret; struct bg_spi_priv *spi = container_of(bg_com_drv, struct bg_spi_priv, lhandle); clnt_handle.bg_spi = spi; atomic_set(&bg_is_spi_active, 1); ret = bgcom_resume(&clnt_handle); pr_info("Bgcom resumed with : %d\n", ret); return ret; } static const struct dev_pm_ops bgcom_pm = { .suspend = bgcom_pm_suspend, .resume = bgcom_pm_resume, }; static const struct of_device_id bg_spi_of_match[] = { { .compatible = "qcom,bg-spi", }, { } }; MODULE_DEVICE_TABLE(of, bg_spi_of_match); static struct spi_driver bg_spi_driver = { .driver = { .name = "bg-spi", .of_match_table = bg_spi_of_match, .pm = &bgcom_pm, }, .probe = bg_spi_probe, .remove = bg_spi_remove, .shutdown = bg_spi_shutdown, }; module_spi_driver(bg_spi_driver); MODULE_DESCRIPTION("bg SPI driver"); MODULE_LICENSE("GPL v2");