/* Copyright (c) 2014-2017, 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mhi_sys.h" #include "mhi.h" #include "mhi_hwio.h" #include "mhi_macros.h" #include "mhi_bhi.h" #include "mhi_trace.h" static int enable_bb_ctxt(struct mhi_device_ctxt *mhi_dev_ctxt, struct mhi_ring *bb_ctxt, int nr_el, int chan, size_t max_payload) { int i; struct mhi_buf_info *mhi_buf_info; bb_ctxt->el_size = sizeof(struct mhi_buf_info); bb_ctxt->len = bb_ctxt->el_size * nr_el; bb_ctxt->base = kzalloc(bb_ctxt->len, GFP_KERNEL); bb_ctxt->wp = bb_ctxt->base; bb_ctxt->rp = bb_ctxt->base; bb_ctxt->ack_rp = bb_ctxt->base; if (!bb_ctxt->base) return -ENOMEM; if (mhi_dev_ctxt->flags.bb_required) { char pool_name[32]; snprintf(pool_name, sizeof(pool_name), "mhi%d_%d", mhi_dev_ctxt->plat_dev->id, chan); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Creating pool %s for chan:%d payload: 0x%lx\n", pool_name, chan, (unsigned long)max_payload); bb_ctxt->dma_pool = dma_pool_create(pool_name, &mhi_dev_ctxt->plat_dev->dev, max_payload, 0, 0); if (unlikely(!bb_ctxt->dma_pool)) goto dma_pool_error; mhi_buf_info = (struct mhi_buf_info *)bb_ctxt->base; for (i = 0; i < nr_el; i++, mhi_buf_info++) { mhi_buf_info->pre_alloc_v_addr = dma_pool_alloc(bb_ctxt->dma_pool, GFP_KERNEL, &mhi_buf_info->pre_alloc_p_addr); if (unlikely(!mhi_buf_info->pre_alloc_v_addr)) goto dma_alloc_error; mhi_buf_info->pre_alloc_len = max_payload; } } return 0; dma_alloc_error: for (--i, --mhi_buf_info; i >= 0; i--, mhi_buf_info--) dma_pool_free(bb_ctxt->dma_pool, mhi_buf_info->pre_alloc_v_addr, mhi_buf_info->pre_alloc_p_addr); dma_pool_destroy(bb_ctxt->dma_pool); bb_ctxt->dma_pool = NULL; dma_pool_error: kfree(bb_ctxt->base); bb_ctxt->base = NULL; return -ENOMEM; } static void mhi_write_db(struct mhi_device_ctxt *mhi_dev_ctxt, void __iomem *io_addr_lower, unsigned int chan, dma_addr_t val) { uintptr_t io_offset = chan * sizeof(u64); void __iomem *io_addr_upper = (void __iomem *)((uintptr_t)io_addr_lower + 4); mhi_reg_write(mhi_dev_ctxt, io_addr_upper, io_offset, (u64)val >> 32); mhi_reg_write(mhi_dev_ctxt, io_addr_lower, io_offset, (u32)val); } static void mhi_update_ctxt(struct mhi_device_ctxt *mhi_dev_ctxt, void *__iomem *io_addr, uintptr_t chan, u64 val) { if (mhi_dev_ctxt->mmio_info.chan_db_addr == io_addr) { mhi_dev_ctxt->dev_space.ring_ctxt.cc_list[chan]. mhi_trb_write_ptr = val; } else if (mhi_dev_ctxt->mmio_info.event_db_addr == io_addr) { if (chan < mhi_dev_ctxt->mmio_info.nr_event_rings) { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "EV ctxt: %ld val 0x%llx WP 0x%llx RP: 0x%llx", chan, val, mhi_dev_ctxt->dev_space.ring_ctxt. ec_list[chan].mhi_event_read_ptr, mhi_dev_ctxt->dev_space.ring_ctxt. ec_list[chan].mhi_event_write_ptr); mhi_dev_ctxt->dev_space.ring_ctxt.ec_list[chan]. mhi_event_write_ptr = val; } else { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Bad EV ring index: %lx\n", chan); } } /* Flush ctxt update to main memory for device visibility */ wmb(); } int mhi_init_pcie_device(struct mhi_device_ctxt *mhi_dev_ctxt) { int ret_val = 0; long int sleep_time = 100; struct pci_dev *pcie_device = mhi_dev_ctxt->pcie_device; struct pcie_core_info *core = &mhi_dev_ctxt->core; do { ret_val = pci_enable_device(pcie_device); if (0 != ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to enable pcie struct device r: %d\n", ret_val); mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Sleeping for ~ %li uS, and retrying.\n", sleep_time); msleep(sleep_time); } } while (ret_val != 0); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Successfully enabled pcie device.\n"); core->bar0_base = ioremap_nocache(pci_resource_start(pcie_device, 0), pci_resource_len(pcie_device, 0)); if (!core->bar0_base) goto mhi_device_list_error; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Device BAR0 address is at 0x%p\n", core->bar0_base); ret_val = pci_request_region(pcie_device, 0, "mhi"); if (ret_val) mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Could not request BAR0 region\n"); core->manufact_id = pcie_device->vendor; core->dev_id = pcie_device->device; return 0; mhi_device_list_error: pci_disable_device(pcie_device); return -EIO; } static void mhi_move_interrupts(struct mhi_device_ctxt *mhi_dev_ctxt, u32 cpu) { u32 irq_to_affin = 0; int i; for (i = 0; i < mhi_dev_ctxt->mmio_info.nr_event_rings; ++i) { if (MHI_ER_DATA_TYPE == GET_EV_PROPS(EV_TYPE, mhi_dev_ctxt->ev_ring_props[i].flags)) { irq_to_affin = mhi_dev_ctxt->ev_ring_props[i].msi_vec; irq_to_affin += mhi_dev_ctxt->core.irq_base; irq_set_affinity(irq_to_affin, get_cpu_mask(cpu)); } } } int mhi_cpu_notifier_cb(struct notifier_block *nfb, unsigned long action, void *hcpu) { uintptr_t cpu = (uintptr_t)hcpu; struct mhi_device_ctxt *mhi_dev_ctxt = container_of(nfb, struct mhi_device_ctxt, mhi_cpu_notifier); switch (action) { case CPU_ONLINE: if (cpu > 0) mhi_move_interrupts(mhi_dev_ctxt, cpu); break; case CPU_DEAD: for_each_online_cpu(cpu) { if (cpu > 0) { mhi_move_interrupts(mhi_dev_ctxt, cpu); break; } } break; default: break; } return NOTIFY_OK; } int get_chan_props(struct mhi_device_ctxt *mhi_dev_ctxt, int chan, struct mhi_chan_info *chan_info) { char dt_prop[MAX_BUF_SIZE]; int r; scnprintf(dt_prop, MAX_BUF_SIZE, "%s%d", "mhi-chan-cfg-", chan); r = of_property_read_u32_array( mhi_dev_ctxt->plat_dev->dev.of_node, dt_prop, (u32 *)chan_info, sizeof(struct mhi_chan_info) / sizeof(u32)); return r; } int mhi_release_chan_ctxt(struct mhi_device_ctxt *mhi_dev_ctxt, struct mhi_chan_ctxt *cc_list, struct mhi_ring *ring) { if (cc_list == NULL || ring == NULL) return -EINVAL; dma_free_coherent(&mhi_dev_ctxt->plat_dev->dev, ring->len, ring->base, cc_list->mhi_trb_ring_base_addr); mhi_init_chan_ctxt(cc_list, 0, 0, 0, 0, 0, ring, MHI_CHAN_STATE_DISABLED, false, MHI_BRSTMODE_DEFAULT); return 0; } void free_tre_ring(struct mhi_device_ctxt *mhi_dev_ctxt, int chan) { struct mhi_chan_ctxt *chan_ctxt; int r; chan_ctxt = &mhi_dev_ctxt->dev_space.ring_ctxt.cc_list[chan]; r = mhi_release_chan_ctxt(mhi_dev_ctxt, chan_ctxt, &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]); if (r) mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to release chan %d ret %d\n", chan, r); } static int populate_tre_ring(struct mhi_client_config *client_config) { dma_addr_t ring_dma_addr; void *ring_local_addr; struct mhi_chan_ctxt *chan_ctxt; struct mhi_device_ctxt *mhi_dev_ctxt = client_config->mhi_dev_ctxt; u32 chan = client_config->chan_info.chan_nr; u32 nr_desc = client_config->chan_info.max_desc; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Entered chan %d requested desc %d\n", chan, nr_desc); chan_ctxt = &mhi_dev_ctxt->dev_space.ring_ctxt.cc_list[chan]; ring_local_addr = dma_alloc_coherent(&mhi_dev_ctxt->plat_dev->dev, nr_desc * sizeof(union mhi_xfer_pkt), &ring_dma_addr, GFP_KERNEL); if (ring_local_addr == NULL) return -ENOMEM; mhi_init_chan_ctxt(chan_ctxt, ring_dma_addr, (uintptr_t)ring_local_addr, nr_desc, GET_CHAN_PROPS(CHAN_DIR, client_config->chan_info.flags), client_config->chan_info.ev_ring, &mhi_dev_ctxt->mhi_local_chan_ctxt[chan], MHI_CHAN_STATE_ENABLED, GET_CHAN_PROPS(PRESERVE_DB_STATE, client_config->chan_info.flags), GET_CHAN_PROPS(BRSTMODE, client_config->chan_info.flags)); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Exited\n"); return 0; } void mhi_notify_client(struct mhi_client_handle *client_handle, enum MHI_CB_REASON reason) { struct mhi_cb_info cb_info = {0}; struct mhi_result result = {0}; struct mhi_client_config *client_config; cb_info.result = NULL; cb_info.cb_reason = reason; if (client_handle == NULL) return; client_config = client_handle->client_config; if (client_config->client_info.mhi_client_cb) { result.user_data = client_config->user_data; cb_info.chan = client_config->chan_info.chan_nr; cb_info.result = &result; mhi_log(client_config->mhi_dev_ctxt, MHI_MSG_INFO, "Calling back for chan %d, reason %d\n", cb_info.chan, reason); client_config->client_info.mhi_client_cb(&cb_info); } } void mhi_notify_clients(struct mhi_device_ctxt *mhi_dev_ctxt, enum MHI_CB_REASON reason) { int i; struct mhi_client_handle *client_handle = NULL; for (i = 0; i < MHI_MAX_CHANNELS; ++i) { if (VALID_CHAN_NR(i)) { client_handle = mhi_dev_ctxt->client_handle_list[i]; mhi_notify_client(client_handle, reason); } } } int mhi_open_channel(struct mhi_client_handle *client_handle) { int ret_val = 0; struct mhi_device_ctxt *mhi_dev_ctxt; struct mhi_ring *chan_ring; int chan; struct mhi_chan_cfg *cfg; struct mhi_cmd_complete_event_pkt cmd_event_pkt; union mhi_cmd_pkt cmd_pkt; enum MHI_EVENT_CCS ev_code; struct mhi_client_config *client_config = client_handle->client_config; if (client_config->magic != MHI_HANDLE_MAGIC) return -EINVAL; mhi_dev_ctxt = client_config->mhi_dev_ctxt; chan = client_config->chan_info.chan_nr; cfg = &mhi_dev_ctxt->mhi_chan_cfg[chan]; chan_ring = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; mutex_lock(&cfg->chan_lock); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Entered: Client opening chan 0x%x\n", chan); if (mhi_dev_ctxt->dev_exec_env < GET_CHAN_PROPS(CHAN_BRINGUP_STAGE, client_config->chan_info.flags)) { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Chan %d, MHI exec_env %d, not ready!\n", chan, mhi_dev_ctxt->dev_exec_env); mutex_unlock(&cfg->chan_lock); return -ENOTCONN; } ret_val = populate_tre_ring(client_config); if (ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to initialize tre ring chan %d ret %d\n", chan, ret_val); goto error_tre_ring; } client_config->event_ring_index = mhi_dev_ctxt->dev_space.ring_ctxt. cc_list[chan].mhi_event_ring_index; client_config->msi_vec = mhi_dev_ctxt->dev_space.ring_ctxt.ec_list[ client_config->event_ring_index].mhi_msi_vector; client_config->intmod_t = mhi_dev_ctxt->dev_space.ring_ctxt.ec_list[ client_config->event_ring_index].mhi_intmodt; init_completion(&cfg->cmd_complete); read_lock_bh(&mhi_dev_ctxt->pm_xfer_lock); if (unlikely(mhi_dev_ctxt->mhi_pm_state == MHI_PM_DISABLE)) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "MHI State is disabled\n"); read_unlock_bh(&mhi_dev_ctxt->pm_xfer_lock); ret_val = -EIO; goto error_pm_state; } mhi_dev_ctxt->assert_wake(mhi_dev_ctxt, false); read_unlock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->runtime_get(mhi_dev_ctxt); ret_val = mhi_send_cmd(client_config->mhi_dev_ctxt, MHI_COMMAND_START_CHAN, chan); if (ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to send start cmd for chan %d ret %d\n", chan, ret_val); goto error_completion; } ret_val = wait_for_completion_timeout(&cfg->cmd_complete, msecs_to_jiffies(MHI_MAX_CMD_TIMEOUT)); if (!ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to receive cmd completion for %d\n", chan); ret_val = -EIO; goto error_completion; } else { ret_val = 0; } spin_lock_irq(&cfg->event_lock); cmd_event_pkt = cfg->cmd_event_pkt; cmd_pkt = cfg->cmd_pkt; spin_unlock_irq(&cfg->event_lock); ev_code = MHI_EV_READ_CODE(EV_TRB_CODE, ((union mhi_event_pkt *)&cmd_event_pkt)); if (ev_code != MHI_EVENT_CC_SUCCESS) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Error to receive event comp. ev_code:0x%x\n", ev_code); ret_val = -EIO; goto error_completion; } spin_lock_irq(&chan_ring->ring_lock); chan_ring->ch_state = MHI_CHAN_STATE_ENABLED; spin_unlock_irq(&chan_ring->ring_lock); client_config->chan_status = 1; read_lock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); read_unlock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->runtime_put(mhi_dev_ctxt); mutex_unlock(&cfg->chan_lock); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "chan:%d opened successfully\n", chan); return 0; error_completion: read_lock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); read_unlock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->runtime_put(mhi_dev_ctxt); error_pm_state: free_tre_ring(mhi_dev_ctxt, chan); error_tre_ring: mutex_unlock(&cfg->chan_lock); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Exited chan 0x%x ret:%d\n", chan, ret_val); return ret_val; } EXPORT_SYMBOL(mhi_open_channel); bool mhi_is_device_ready(const struct device * const dev, const char *node_name) { struct mhi_device_ctxt *itr; const struct device_node *of_node; bool match_found = false; if (!mhi_device_drv) return false; if (dev->of_node == NULL) return false; of_node = of_parse_phandle(dev->of_node, node_name, 0); if (!of_node) return false; mutex_lock(&mhi_device_drv->lock); list_for_each_entry(itr, &mhi_device_drv->head, node) { struct platform_device *pdev = itr->plat_dev; if (pdev->dev.of_node == of_node) { match_found = true; break; } } mutex_unlock(&mhi_device_drv->lock); return match_found; } EXPORT_SYMBOL(mhi_is_device_ready); int mhi_register_channel(struct mhi_client_handle **client_handle, struct mhi_client_info_t *client_info) { struct mhi_device_ctxt *mhi_dev_ctxt = NULL, *itr; const struct device_node *of_node; struct mhi_client_config *client_config; const char *node_name; enum MHI_CLIENT_CHANNEL chan; struct mhi_chan_info chan_info = {0}; int ret; if (!client_info || client_info->dev->of_node == NULL) return -EINVAL; node_name = client_info->node_name; chan = client_info->chan; of_node = of_parse_phandle(client_info->dev->of_node, node_name, 0); if (!of_node || !mhi_device_drv || chan >= MHI_MAX_CHANNELS) return -EINVAL; /* Traverse thru the list */ mutex_lock(&mhi_device_drv->lock); list_for_each_entry(itr, &mhi_device_drv->head, node) { struct platform_device *pdev = itr->plat_dev; if (pdev->dev.of_node == of_node) { mhi_dev_ctxt = itr; break; } } mutex_unlock(&mhi_device_drv->lock); if (!mhi_dev_ctxt) return -EINVAL; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Registering channel 0x%x for client\n", chan); /* check if it's a supported channel by endpoint */ ret = get_chan_props(mhi_dev_ctxt, chan, &chan_info); if (ret) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Client try to register unsupported chan:%d\n", chan); return -EINVAL; } *client_handle = kzalloc(sizeof(struct mhi_client_handle), GFP_KERNEL); if (NULL == *client_handle) return -ENOMEM; (*client_handle)->client_config = kzalloc(sizeof(*(*client_handle)->client_config), GFP_KERNEL); if ((*client_handle)->client_config == NULL) { kfree(*client_handle); *client_handle = NULL; return -ENOMEM; } mhi_dev_ctxt->client_handle_list[chan] = *client_handle; (*client_handle)->dev_id = mhi_dev_ctxt->core.dev_id; (*client_handle)->domain = mhi_dev_ctxt->core.domain; (*client_handle)->bus = mhi_dev_ctxt->core.bus; (*client_handle)->slot = mhi_dev_ctxt->core.slot; (*client_handle)->enabled = false; client_config = (*client_handle)->client_config; client_config->mhi_dev_ctxt = mhi_dev_ctxt; client_config->user_data = client_info->user_data; client_config->magic = MHI_HANDLE_MAGIC; client_config->chan_info.chan_nr = chan; if (NULL != client_info) client_config->client_info = *client_info; if (MHI_CLIENT_IP_HW_0_OUT == chan) client_config->intmod_t = 10; if (MHI_CLIENT_IP_HW_0_IN == chan) client_config->intmod_t = 10; client_config->chan_info = chan_info; ret = enable_bb_ctxt(mhi_dev_ctxt, &mhi_dev_ctxt->chan_bb_list[chan], client_config->chan_info.max_desc, chan, client_config->client_info.max_payload); if (ret) { kfree(mhi_dev_ctxt->client_handle_list[chan]->client_config); kfree(mhi_dev_ctxt->client_handle_list[chan]); mhi_dev_ctxt->client_handle_list[chan] = NULL; return -ENOMEM; } if (mhi_dev_ctxt->dev_exec_env == MHI_EXEC_ENV_AMSS && mhi_dev_ctxt->flags.mhi_initialized) (*client_handle)->enabled = true; mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Successfuly registered chan:%u\n", chan); return 0; } EXPORT_SYMBOL(mhi_register_channel); void mhi_close_channel(struct mhi_client_handle *client_handle) { u32 chan; int ret_val = 0; struct mhi_chan_cfg *cfg; struct mhi_device_ctxt *mhi_dev_ctxt; struct mhi_cmd_complete_event_pkt cmd_event_pkt; union mhi_cmd_pkt cmd_pkt; struct mhi_ring *chan_ring; enum MHI_EVENT_CCS ev_code; struct mhi_client_config *client_config = client_handle->client_config; if (client_config->magic != MHI_HANDLE_MAGIC || !client_config->chan_status) return; mhi_dev_ctxt = client_config->mhi_dev_ctxt; chan = client_config->chan_info.chan_nr; cfg = &mhi_dev_ctxt->mhi_chan_cfg[chan]; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Client attempting to close chan 0x%x\n", chan); chan_ring = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; mutex_lock(&cfg->chan_lock); /* No more processing events for this channel */ spin_lock_irq(&chan_ring->ring_lock); if (chan_ring->ch_state != MHI_CHAN_STATE_ENABLED) { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Chan %d is not enabled, cur state:0x%x\n", chan, chan_ring->ch_state); spin_unlock_irq(&chan_ring->ring_lock); mutex_unlock(&cfg->chan_lock); return; } chan_ring->ch_state = MHI_CHAN_STATE_DISABLED; spin_unlock_irq(&chan_ring->ring_lock); init_completion(&cfg->cmd_complete); read_lock_bh(&mhi_dev_ctxt->pm_xfer_lock); WARN_ON(mhi_dev_ctxt->mhi_pm_state == MHI_PM_DISABLE); mhi_dev_ctxt->assert_wake(mhi_dev_ctxt, false); read_unlock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->runtime_get(mhi_dev_ctxt); ret_val = mhi_send_cmd(mhi_dev_ctxt, MHI_COMMAND_RESET_CHAN, chan); if (ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to send reset cmd for chan %d ret %d\n", chan, ret_val); goto error_completion; } ret_val = wait_for_completion_timeout(&cfg->cmd_complete, msecs_to_jiffies(MHI_MAX_CMD_TIMEOUT)); if (!ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to receive cmd completion for %d\n", chan); goto error_completion; } spin_lock_irq(&cfg->event_lock); cmd_event_pkt = cfg->cmd_event_pkt; cmd_pkt = cfg->cmd_pkt; spin_unlock_irq(&cfg->event_lock); ev_code = MHI_EV_READ_CODE(EV_TRB_CODE, ((union mhi_event_pkt *)&cmd_event_pkt)); if (ev_code != MHI_EVENT_CC_SUCCESS) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Error to receive event completion ev_cod:0x%x\n", ev_code); } error_completion: mhi_reset_chan(mhi_dev_ctxt, chan); read_lock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); read_unlock_bh(&mhi_dev_ctxt->pm_xfer_lock); mhi_dev_ctxt->runtime_put(mhi_dev_ctxt); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "resetting bb_ring for chan 0x%x\n", chan); mhi_dev_ctxt->chan_bb_list[chan].rp = mhi_dev_ctxt->chan_bb_list[chan].base; mhi_dev_ctxt->chan_bb_list[chan].wp = mhi_dev_ctxt->chan_bb_list[chan].base; mhi_dev_ctxt->chan_bb_list[chan].ack_rp = mhi_dev_ctxt->chan_bb_list[chan].base; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Freeing ring for chan 0x%x\n", chan); free_tre_ring(mhi_dev_ctxt, chan); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Chan 0x%x confirmed closed.\n", chan); client_config->chan_status = 0; mutex_unlock(&cfg->chan_lock); } EXPORT_SYMBOL(mhi_close_channel); void mhi_update_chan_db(struct mhi_device_ctxt *mhi_dev_ctxt, u32 chan) { struct mhi_ring *chan_ctxt; u64 db_value; chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; db_value = mhi_v2p_addr(mhi_dev_ctxt, MHI_RING_TYPE_XFER_RING, chan, (uintptr_t) chan_ctxt->wp); chan_ctxt->db_mode.process_db(mhi_dev_ctxt, mhi_dev_ctxt->mmio_info.chan_db_addr, chan, db_value); } static inline int mhi_queue_tre(struct mhi_device_ctxt *mhi_dev_ctxt, u32 chan, enum MHI_RING_TYPE type) { struct mhi_chan_ctxt *chan_ctxt; unsigned long flags = 0; u64 db_value = 0; chan_ctxt = &mhi_dev_ctxt->dev_space.ring_ctxt.cc_list[chan]; if (!MHI_DB_ACCESS_VALID(mhi_dev_ctxt->mhi_pm_state)) return -EACCES; if (likely(type == MHI_RING_TYPE_XFER_RING)) { struct mhi_ring *mhi_ring = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; spin_lock_irqsave(&mhi_ring->ring_lock, flags); mhi_update_chan_db(mhi_dev_ctxt, chan); spin_unlock_irqrestore(&mhi_ring->ring_lock, flags); } else { struct mhi_ring *cmd_ring = &mhi_dev_ctxt-> mhi_local_cmd_ctxt[PRIMARY_CMD_RING]; db_value = mhi_v2p_addr(mhi_dev_ctxt, MHI_RING_TYPE_CMD_RING, PRIMARY_CMD_RING, (uintptr_t)cmd_ring->wp); cmd_ring->db_mode.process_db (mhi_dev_ctxt, mhi_dev_ctxt->mmio_info.cmd_db_addr, 0, db_value); } return 0; } static int create_bb(struct mhi_device_ctxt *mhi_dev_ctxt, int chan, void *buf, size_t buf_len, enum dma_data_direction dir, struct mhi_buf_info **bb) { struct mhi_ring *bb_ctxt = &mhi_dev_ctxt->chan_bb_list[chan]; struct mhi_buf_info *bb_info; int r; uintptr_t bb_index, ctxt_index_wp, ctxt_index_rp; mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Entered chan %d\n", chan); get_element_index(bb_ctxt, bb_ctxt->wp, &bb_index); get_element_index(&mhi_dev_ctxt->mhi_local_chan_ctxt[chan], mhi_dev_ctxt->mhi_local_chan_ctxt[chan].wp, &ctxt_index_wp); get_element_index(&mhi_dev_ctxt->mhi_local_chan_ctxt[chan], mhi_dev_ctxt->mhi_local_chan_ctxt[chan].rp, &ctxt_index_rp); BUG_ON(bb_index != ctxt_index_wp); mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Chan RP index %ld Chan WP index %ld, chan %d\n", ctxt_index_rp, ctxt_index_wp, chan); r = ctxt_add_element(bb_ctxt, (void **)&bb_info); if (r) return r; bb_info->buf_len = buf_len; bb_info->client_buf = buf; bb_info->dir = dir; bb_info->bb_p_addr = dma_map_single( &mhi_dev_ctxt->plat_dev->dev, bb_info->client_buf, bb_info->buf_len, bb_info->dir); bb_info->bb_active = 0; if (!VALID_BUF(bb_info->bb_p_addr, bb_info->buf_len, mhi_dev_ctxt)) { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Buffer outside DMA range 0x%lx, size 0x%zx\n", (uintptr_t)bb_info->bb_p_addr, buf_len); dma_unmap_single(&mhi_dev_ctxt->plat_dev->dev, bb_info->bb_p_addr, bb_info->buf_len, bb_info->dir); if (likely((mhi_dev_ctxt->flags.bb_required && bb_info->pre_alloc_len >= bb_info->buf_len))) { bb_info->bb_p_addr = bb_info->pre_alloc_p_addr; bb_info->bb_v_addr = bb_info->pre_alloc_v_addr; mhi_dev_ctxt->counters.bb_used[chan]++; if (dir == DMA_TO_DEVICE) { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Copying client buf into BB.\n"); memcpy(bb_info->bb_v_addr, buf, bb_info->buf_len); } bb_info->bb_active = 1; } else mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "No BB allocated\n"); } *bb = bb_info; mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Exited chan %d\n", chan); return 0; } static void disable_bb_ctxt(struct mhi_device_ctxt *mhi_dev_ctxt, struct mhi_ring *bb_ctxt) { if (mhi_dev_ctxt->flags.bb_required) { struct mhi_buf_info *bb = (struct mhi_buf_info *)bb_ctxt->base; int nr_el = bb_ctxt->len / bb_ctxt->el_size; int i = 0; for (i = 0; i < nr_el; i++, bb++) dma_pool_free(bb_ctxt->dma_pool, bb->pre_alloc_v_addr, bb->pre_alloc_p_addr); dma_pool_destroy(bb_ctxt->dma_pool); bb_ctxt->dma_pool = NULL; } kfree(bb_ctxt->base); bb_ctxt->base = NULL; } static void free_bounce_buffer(struct mhi_device_ctxt *mhi_dev_ctxt, struct mhi_buf_info *bb) { mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Entered\n"); if (!bb->bb_active) /* This buffer was maped directly to device */ dma_unmap_single(&mhi_dev_ctxt->plat_dev->dev, bb->bb_p_addr, bb->buf_len, bb->dir); bb->bb_active = 0; mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Exited\n"); } static int mhi_queue_dma_xfer( struct mhi_client_config *client_config, dma_addr_t buf, size_t buf_len, enum MHI_FLAGS mhi_flags) { union mhi_xfer_pkt *pkt_loc; int ret_val; enum MHI_CLIENT_CHANNEL chan; struct mhi_device_ctxt *mhi_dev_ctxt; mhi_dev_ctxt = client_config->mhi_dev_ctxt; MHI_ASSERT(VALID_BUF(buf, buf_len, mhi_dev_ctxt), "Client buffer is of invalid length\n"); chan = client_config->chan_info.chan_nr; pkt_loc = mhi_dev_ctxt->mhi_local_chan_ctxt[chan].wp; pkt_loc->data_tx_pkt.buffer_ptr = buf; pkt_loc->type.info = mhi_flags; trace_mhi_tre(pkt_loc, chan, 0); if (likely(client_config->intmod_t)) MHI_TRB_SET_INFO(TX_TRB_BEI, pkt_loc, 1); else MHI_TRB_SET_INFO(TX_TRB_BEI, pkt_loc, 0); MHI_TRB_SET_INFO(TX_TRB_TYPE, pkt_loc, MHI_PKT_TYPE_TRANSFER); MHI_TX_TRB_SET_LEN(TX_TRB_LEN, pkt_loc, buf_len); /* Ensure writes to descriptor are flushed */ wmb(); mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Channel %d Has buf size of %zd and buf addr %lx, flags 0x%x\n", chan, buf_len, (uintptr_t)buf, mhi_flags); /* Add the TRB to the correct transfer ring */ ret_val = ctxt_add_element(&mhi_dev_ctxt->mhi_local_chan_ctxt[chan], (void *)&pkt_loc); if (unlikely(0 != ret_val)) { mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Failed to insert trb in xfer ring\n"); return ret_val; } if (MHI_OUT == GET_CHAN_PROPS(CHAN_DIR, client_config->chan_info.flags)) atomic_inc(&mhi_dev_ctxt->counters.outbound_acks); return ret_val; } int mhi_queue_xfer(struct mhi_client_handle *client_handle, void *buf, size_t buf_len, enum MHI_FLAGS mhi_flags) { int r; enum dma_data_direction dma_dir; struct mhi_buf_info *bb; struct mhi_device_ctxt *mhi_dev_ctxt; u32 chan; unsigned long flags; struct mhi_client_config *client_config; if (!client_handle || !buf || !buf_len) return -EINVAL; client_config = client_handle->client_config; mhi_dev_ctxt = client_config->mhi_dev_ctxt; chan = client_config->chan_info.chan_nr; read_lock_irqsave(&mhi_dev_ctxt->pm_xfer_lock, flags); if (mhi_dev_ctxt->mhi_pm_state == MHI_PM_DISABLE) { read_unlock_irqrestore(&mhi_dev_ctxt->pm_xfer_lock, flags); mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "MHI is not in active state\n"); return -EINVAL; } mhi_dev_ctxt->runtime_get(mhi_dev_ctxt); mhi_dev_ctxt->assert_wake(mhi_dev_ctxt, false); read_unlock_irqrestore(&mhi_dev_ctxt->pm_xfer_lock, flags); if (GET_CHAN_PROPS(CHAN_DIR, client_config->chan_info.flags) == MHI_OUT) dma_dir = DMA_TO_DEVICE; else dma_dir = DMA_FROM_DEVICE; r = create_bb(client_config->mhi_dev_ctxt, client_config->chan_info.chan_nr, buf, buf_len, dma_dir, &bb); if (r) { mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Failed to create BB, chan %d ret %d\n", chan, r); mhi_dev_ctxt->runtime_put(mhi_dev_ctxt); read_lock_irqsave(&mhi_dev_ctxt->pm_xfer_lock, flags); mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); read_unlock_irqrestore(&mhi_dev_ctxt->pm_xfer_lock, flags); return r; } mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Queueing to HW: Client Buf 0x%p, size 0x%zx, DMA %llx, chan %d\n", buf, buf_len, (u64)bb->bb_p_addr, client_config->chan_info.chan_nr); r = mhi_queue_dma_xfer(client_config, bb->bb_p_addr, bb->buf_len, mhi_flags); /* * Assumption: If create_bounce_buffer did not fail, we do not * expect mhi_queue_dma_xfer to fail, if it does, the bb list will be * out of sync with the descriptor list which is problematic. */ BUG_ON(r); read_lock_irqsave(&mhi_dev_ctxt->pm_xfer_lock, flags); mhi_queue_tre(mhi_dev_ctxt, chan, MHI_RING_TYPE_XFER_RING); if (dma_dir == DMA_FROM_DEVICE) { mhi_dev_ctxt->runtime_put(mhi_dev_ctxt); mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); } read_unlock_irqrestore(&mhi_dev_ctxt->pm_xfer_lock, flags); return 0; } EXPORT_SYMBOL(mhi_queue_xfer); int mhi_send_cmd(struct mhi_device_ctxt *mhi_dev_ctxt, enum MHI_COMMAND cmd, u32 chan) { union mhi_cmd_pkt *cmd_pkt = NULL; enum MHI_PKT_TYPE ring_el_type = MHI_PKT_TYPE_NOOP_CMD; int ret_val = 0; unsigned long flags, flags2; struct mhi_ring *mhi_ring = &mhi_dev_ctxt-> mhi_local_cmd_ctxt[PRIMARY_CMD_RING]; if (chan >= MHI_MAX_CHANNELS || cmd >= MHI_COMMAND_MAX_NR) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Invalid channel id, received id: 0x%x", chan); return -EINVAL; } mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Entered, MHI state %s dev_exec_env %d chan %d cmd %d\n", TO_MHI_STATE_STR(mhi_dev_ctxt->mhi_state), mhi_dev_ctxt->dev_exec_env, chan, cmd); switch (cmd) { break; case MHI_COMMAND_RESET_CHAN: ring_el_type = MHI_PKT_TYPE_RESET_CHAN_CMD; break; case MHI_COMMAND_START_CHAN: ring_el_type = MHI_PKT_TYPE_START_CHAN_CMD; break; default: mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Bad command received\n"); return -EINVAL; } spin_lock_irqsave(&mhi_ring->ring_lock, flags); ret_val = ctxt_add_element(mhi_ring, (void *)&cmd_pkt); if (ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Failed to insert element\n"); spin_unlock_irqrestore(&mhi_ring->ring_lock, flags); return ret_val; } MHI_TRB_SET_INFO(CMD_TRB_TYPE, cmd_pkt, ring_el_type); MHI_TRB_SET_INFO(CMD_TRB_CHID, cmd_pkt, chan); read_lock_irqsave(&mhi_dev_ctxt->pm_xfer_lock, flags2); mhi_queue_tre(mhi_dev_ctxt, 0, MHI_RING_TYPE_CMD_RING); read_unlock_irqrestore(&mhi_dev_ctxt->pm_xfer_lock, flags2); spin_unlock_irqrestore(&mhi_ring->ring_lock, flags); mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Sent command 0x%x for chan %d\n", cmd, chan); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Exited ret %d.\n", ret_val); return ret_val; } static void parse_inbound_bb(struct mhi_device_ctxt *mhi_dev_ctxt, struct mhi_ring *bb_ctxt, struct mhi_result *result, size_t bounced_data_size) { struct mhi_buf_info *bb; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Entered\n"); bb = bb_ctxt->rp; bb->filled_size = bounced_data_size; /* Data corruption will occur */ BUG_ON(bb->dir != DMA_FROM_DEVICE); BUG_ON(bb->filled_size > bb->buf_len); if (bb->bb_active) { /* This is coherent memory, no cache management is needed */ memcpy(bb->client_buf, bb->bb_v_addr, bb->filled_size); mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Bounce from BB:0x%p to Client Buf: 0x%p Len 0x%zx\n", bb->client_buf, bb->bb_v_addr, bb->filled_size); } result->buf_addr = bb->client_buf; result->bytes_xferd = bb->filled_size; /* At this point the bounce buffer is no longer necessary * Whatever was received from the device was copied back to the * user buffer. Free up the bounce buffer, but do not move the bb ring * rp, since it can be moved async by mhi_poll_inbound */ free_bounce_buffer(mhi_dev_ctxt, bb); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Exited\n"); } static void parse_outbound_bb(struct mhi_device_ctxt *mhi_dev_ctxt, struct mhi_ring *bb_ctxt, struct mhi_result *result, size_t bounced_data_size) { struct mhi_buf_info *bb; bb = bb_ctxt->rp; mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Entered\n"); BUG_ON(bb->dir != DMA_TO_DEVICE); bb->filled_size = bounced_data_size; BUG_ON(bb->filled_size != bb->buf_len); result->buf_addr = bb->client_buf; result->bytes_xferd = bb->filled_size; result->transaction_status = 0; free_bounce_buffer(mhi_dev_ctxt, bb); mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Exited\n"); } static int parse_outbound(struct mhi_device_ctxt *mhi_dev_ctxt, u32 chan, union mhi_xfer_pkt *local_ev_trb_loc, u16 xfer_len) { struct mhi_result *result = NULL; int ret_val = 0; struct mhi_client_handle *client_handle = NULL; struct mhi_client_config *client_config; struct mhi_ring *local_chan_ctxt = NULL; struct mhi_cb_info cb_info; struct mhi_ring *bb_ctxt = &mhi_dev_ctxt->chan_bb_list[chan]; local_chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; client_handle = mhi_dev_ctxt->client_handle_list[chan]; client_config = client_handle->client_config; /* If ring is empty */ MHI_ASSERT(!unlikely(mhi_dev_ctxt->mhi_local_chan_ctxt[chan].rp == mhi_dev_ctxt->mhi_local_chan_ctxt[chan].wp), "Empty Event Ring\n"); parse_outbound_bb(mhi_dev_ctxt, bb_ctxt, &client_config->result, xfer_len); mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Removing BB from head, chan %d\n", chan); atomic_dec(&mhi_dev_ctxt->counters.outbound_acks); mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); mhi_dev_ctxt->runtime_put(mhi_dev_ctxt); ret_val = ctxt_del_element(&mhi_dev_ctxt->mhi_local_chan_ctxt[chan], NULL); BUG_ON(ret_val); ret_val = ctxt_del_element(bb_ctxt, NULL); BUG_ON(ret_val); result = &client_config->result; if (NULL != (&client_config->client_info.mhi_client_cb)) { client_config->result.user_data = client_config->user_data; cb_info.cb_reason = MHI_CB_XFER; cb_info.result = result; cb_info.chan = chan; client_config->client_info.mhi_client_cb(&cb_info); } mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "Processed outbound ack chan %d Pending acks %d.\n", chan, atomic_read(&mhi_dev_ctxt->counters.outbound_acks)); return 0; } static int parse_inbound(struct mhi_device_ctxt *mhi_dev_ctxt, u32 chan, union mhi_xfer_pkt *local_ev_trb_loc, u16 xfer_len, unsigned ev_ring) { struct mhi_client_handle *client_handle; struct mhi_client_config *client_config; struct mhi_ring *local_chan_ctxt; struct mhi_result *result; struct mhi_cb_info cb_info; struct mhi_ring *bb_ctxt = &mhi_dev_ctxt->chan_bb_list[chan]; bool ev_managed = GET_EV_PROPS(EV_MANAGED, mhi_dev_ctxt->ev_ring_props[ev_ring].flags); int r; uintptr_t bb_index, ctxt_index_rp, ctxt_index_wp; client_handle = mhi_dev_ctxt->client_handle_list[chan]; client_config = client_handle->client_config; local_chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; MHI_ASSERT(!unlikely(mhi_dev_ctxt->mhi_local_chan_ctxt[chan].rp == mhi_dev_ctxt->mhi_local_chan_ctxt[chan].wp), "Empty Event Ring\n"); result = &client_config->result; parse_inbound_bb(mhi_dev_ctxt, bb_ctxt, result, xfer_len); if (ev_managed) { MHI_TX_TRB_SET_LEN(TX_TRB_LEN, local_ev_trb_loc, xfer_len); r = ctxt_del_element(local_chan_ctxt, NULL); BUG_ON(r); r = ctxt_del_element(bb_ctxt, NULL); BUG_ON(r); get_element_index(bb_ctxt, bb_ctxt->rp, &bb_index); get_element_index(&mhi_dev_ctxt->mhi_local_chan_ctxt[chan], mhi_dev_ctxt->mhi_local_chan_ctxt[chan].rp, &ctxt_index_rp); get_element_index(&mhi_dev_ctxt->mhi_local_chan_ctxt[chan], mhi_dev_ctxt->mhi_local_chan_ctxt[chan].wp, &ctxt_index_wp); mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Chan RP index %ld Chan WP index %ld chan %d\n", ctxt_index_rp, ctxt_index_wp, chan); BUG_ON(bb_index != ctxt_index_rp); if (client_config->client_info.mhi_client_cb) { client_config->result.user_data = client_config->user_data; cb_info.cb_reason = MHI_CB_XFER; cb_info.result = &client_config->result; cb_info.chan = chan; client_config->client_info.mhi_client_cb(&cb_info); } else { mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "No client registered chan %d\n", chan); } } else { if (likely(client_handle)) { /* Move the rp for both the descriptor and * the bb rings. The caller will get all the buffer * references in the result structure. We do not need * to keep further track of the user buffer. */ ctxt_del_element(bb_ctxt, NULL); ctxt_del_element(local_chan_ctxt, NULL); get_element_index(bb_ctxt, bb_ctxt->rp, &bb_index); get_element_index( &mhi_dev_ctxt->mhi_local_chan_ctxt[chan], mhi_dev_ctxt->mhi_local_chan_ctxt[chan].rp, &ctxt_index_rp); get_element_index( &mhi_dev_ctxt->mhi_local_chan_ctxt[chan], mhi_dev_ctxt->mhi_local_chan_ctxt[chan].wp, &ctxt_index_wp); mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Chan RP index %ld Chan WP index %ld chan %d\n", ctxt_index_rp, ctxt_index_wp, chan); BUG_ON(bb_index != ctxt_index_rp); } else { BUG(); } } return 0; } static int validate_xfer_el_addr(struct mhi_chan_ctxt *ring, uintptr_t addr) { return (addr < (ring->mhi_trb_ring_base_addr) || addr > (ring->mhi_trb_ring_base_addr) + (ring->mhi_trb_ring_len - 1)) ? -ERANGE : 0; } static void print_tre(struct mhi_device_ctxt *mhi_dev_ctxt, int chan, struct mhi_ring *ring, struct mhi_tx_pkt *tre) { uintptr_t el_index; get_element_index(ring, tre, &el_index); mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Printing TRE 0x%p index %lx for channel %d:\n", tre, el_index, chan); mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Buffer Pointer 0x%llx, len 0x%x, info 0x%x\n", tre->buffer_ptr, tre->buf_len, tre->info); } int parse_xfer_event(struct mhi_device_ctxt *mhi_dev_ctxt, union mhi_event_pkt *event, u32 event_id) { struct mhi_result *result; u32 chan = MHI_MAX_CHANNELS; u16 xfer_len; uintptr_t phy_ev_trb_loc; union mhi_xfer_pkt *local_ev_trb_loc; struct mhi_client_handle *client_handle; struct mhi_client_config *client_config; union mhi_xfer_pkt *local_trb_loc; struct mhi_chan_ctxt *chan_ctxt; u32 nr_trb_to_parse; u32 i = 0; u32 ev_code; struct mhi_ring *local_chan_ctxt; trace_mhi_ev(event); chan = MHI_EV_READ_CHID(EV_CHID, event); local_chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; ev_code = MHI_EV_READ_CODE(EV_TRB_CODE, event); client_handle = mhi_dev_ctxt->client_handle_list[chan]; client_config = client_handle->client_config; client_config->pkt_count++; result = &client_config->result; mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Event Received, chan %d, cc_code %d\n", chan, ev_code); if (ev_code == MHI_EVENT_CC_OVERFLOW) result->transaction_status = -EOVERFLOW; else result->transaction_status = 0; switch (ev_code) { case MHI_EVENT_CC_OVERFLOW: case MHI_EVENT_CC_EOB: case MHI_EVENT_CC_EOT: { dma_addr_t trb_data_loc; u32 ieot_flag; int ret_val; phy_ev_trb_loc = MHI_EV_READ_PTR(EV_PTR, event); chan_ctxt = &mhi_dev_ctxt->dev_space.ring_ctxt.cc_list[chan]; BUG_ON(validate_xfer_el_addr(chan_ctxt, phy_ev_trb_loc)); /* Get the TRB this event points to */ local_ev_trb_loc = (void *)mhi_p2v_addr(mhi_dev_ctxt, MHI_RING_TYPE_XFER_RING, chan, phy_ev_trb_loc); local_trb_loc = (union mhi_xfer_pkt *)local_chan_ctxt->rp; trace_mhi_tre(local_trb_loc, chan, 1); ret_val = get_nr_enclosed_el(local_chan_ctxt, local_trb_loc, local_ev_trb_loc, &nr_trb_to_parse); if (unlikely(ret_val)) { mhi_log(mhi_dev_ctxt, MHI_MSG_CRITICAL, "Failed to get nr available trbs ret: %d.\n", ret_val); return ret_val; } do { u64 phy_buf_loc; MHI_TRB_GET_INFO(TX_TRB_IEOT, local_trb_loc, ieot_flag); phy_buf_loc = local_trb_loc->data_tx_pkt.buffer_ptr; trb_data_loc = (dma_addr_t)phy_buf_loc; if (local_chan_ctxt->dir == MHI_IN) xfer_len = MHI_EV_READ_LEN(EV_LEN, event); else xfer_len = MHI_TX_TRB_GET_LEN(TX_TRB_LEN, local_trb_loc); if (!VALID_BUF(trb_data_loc, xfer_len, mhi_dev_ctxt)) { mhi_log(mhi_dev_ctxt, MHI_MSG_CRITICAL, "Bad buf ptr: %llx.\n", (u64)trb_data_loc); return -EINVAL; } if (local_chan_ctxt->dir == MHI_IN) { parse_inbound(mhi_dev_ctxt, chan, local_ev_trb_loc, xfer_len, event_id); } else { parse_outbound(mhi_dev_ctxt, chan, local_ev_trb_loc, xfer_len); } mhi_dev_ctxt->counters.chan_pkts_xferd[chan]++; if (local_trb_loc == (union mhi_xfer_pkt *)local_chan_ctxt->rp) { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Done. Processed until: %lx.\n", (uintptr_t)trb_data_loc); break; } local_trb_loc = (union mhi_xfer_pkt *)local_chan_ctxt->rp; i++; } while (i < nr_trb_to_parse); break; } /* CC_EOT */ case MHI_EVENT_CC_OOB: case MHI_EVENT_CC_DB_MODE: { u64 db_value = 0; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "DB_MODE/OOB Detected chan %d.\n", chan); local_chan_ctxt->db_mode.db_mode = 1; if (local_chan_ctxt->wp != local_chan_ctxt->rp) { db_value = mhi_v2p_addr(mhi_dev_ctxt, MHI_RING_TYPE_XFER_RING, chan, (uintptr_t) local_chan_ctxt->wp); local_chan_ctxt->db_mode.process_db(mhi_dev_ctxt, mhi_dev_ctxt->mmio_info.chan_db_addr, chan, db_value); } break; } case MHI_EVENT_CC_BAD_TRE: phy_ev_trb_loc = MHI_EV_READ_PTR(EV_PTR, event); local_ev_trb_loc = (void *)mhi_p2v_addr(mhi_dev_ctxt, MHI_RING_TYPE_EVENT_RING, event_id, phy_ev_trb_loc); mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Received BAD TRE event for ring %d, pointer 0x%p\n", chan, local_ev_trb_loc); print_tre(mhi_dev_ctxt, chan, &mhi_dev_ctxt->mhi_local_chan_ctxt[chan], (struct mhi_tx_pkt *)local_ev_trb_loc); BUG(); break; default: mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Unknown TX completion.\n"); break; } /*switch(MHI_EV_READ_CODE(EV_TRB_CODE,event)) */ return 0; } int recycle_trb_and_ring(struct mhi_device_ctxt *mhi_dev_ctxt, struct mhi_ring *ring, enum MHI_RING_TYPE ring_type, u32 ring_index) { int ret_val = 0; u64 db_value = 0; void *removed_element = NULL; void *added_element = NULL; unsigned long flags; struct mhi_ring *mhi_ring = &mhi_dev_ctxt-> mhi_local_event_ctxt[ring_index]; ret_val = ctxt_del_element(ring, &removed_element); if (ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Could not remove element from ring\n"); return ret_val; } ret_val = ctxt_add_element(ring, &added_element); if (ret_val) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "Could not add element to ring\n"); return ret_val; } if (!MHI_DB_ACCESS_VALID(mhi_dev_ctxt->mhi_pm_state)) return -EACCES; read_lock_irqsave(&mhi_dev_ctxt->pm_xfer_lock, flags); db_value = mhi_v2p_addr(mhi_dev_ctxt, ring_type, ring_index, (uintptr_t) ring->wp); mhi_ring->db_mode.process_db(mhi_dev_ctxt, mhi_dev_ctxt->mmio_info.event_db_addr, ring_index, db_value); read_unlock_irqrestore(&mhi_dev_ctxt->pm_xfer_lock, flags); return 0; } void mhi_reset_chan(struct mhi_device_ctxt *mhi_dev_ctxt, int chan) { struct mhi_ring *local_chan_ctxt; struct mhi_ring *ev_ring; struct mhi_chan_ctxt *chan_ctxt; struct mhi_event_ctxt *ev_ctxt = NULL; int pending_el = 0, i; unsigned long flags; union mhi_event_pkt *local_rp = NULL; union mhi_event_pkt *device_rp = NULL; local_chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; chan_ctxt = &mhi_dev_ctxt->dev_space.ring_ctxt.cc_list[chan]; ev_ring = &mhi_dev_ctxt-> mhi_local_event_ctxt[chan_ctxt->mhi_event_ring_index]; ev_ctxt = &mhi_dev_ctxt-> dev_space.ring_ctxt.ec_list[chan_ctxt->mhi_event_ring_index]; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Marking all events for chan:%d as stale\n", chan); /* Clear all stale events related to Channel */ spin_lock_irqsave(&ev_ring->ring_lock, flags); device_rp = (union mhi_event_pkt *)mhi_p2v_addr( mhi_dev_ctxt, MHI_RING_TYPE_EVENT_RING, chan_ctxt->mhi_event_ring_index, ev_ctxt->mhi_event_read_ptr); local_rp = (union mhi_event_pkt *)ev_ring->rp; while (device_rp != local_rp) { if (MHI_TRB_READ_INFO(EV_TRB_TYPE, local_rp) == MHI_PKT_TYPE_TX_EVENT) { u32 ev_chan = MHI_EV_READ_CHID(EV_CHID, local_rp); /* Mark as stale event */ if (ev_chan == chan) MHI_TRB_SET_INFO(EV_TRB_TYPE, local_rp, MHI_PKT_TYPE_STALE_EVENT); } local_rp++; if (local_rp == (ev_ring->base + ev_ring->len)) local_rp = ev_ring->base; } spin_unlock_irqrestore(&ev_ring->ring_lock, flags); /* * If outbound elements are pending, they must be cleared since * they will never be acked after a channel reset. */ local_chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; if (local_chan_ctxt->dir == MHI_OUT) get_nr_enclosed_el(local_chan_ctxt, local_chan_ctxt->rp, local_chan_ctxt->wp, &pending_el); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Decrementing chan %d out acks by %d.\n", chan, pending_el); atomic_sub(pending_el, &mhi_dev_ctxt->counters.outbound_acks); read_lock_bh(&mhi_dev_ctxt->pm_xfer_lock); for (i = 0; i < pending_el; i++) mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); read_unlock_bh(&mhi_dev_ctxt->pm_xfer_lock); for (i = 0; i < pending_el; i++) { mhi_dev_ctxt->runtime_put(mhi_dev_ctxt); } /* Reset the local channel context */ local_chan_ctxt->rp = local_chan_ctxt->base; local_chan_ctxt->wp = local_chan_ctxt->base; local_chan_ctxt->ack_rp = local_chan_ctxt->base; /* Reset the mhi channel context */ chan_ctxt->chstate = MHI_CHAN_STATE_DISABLED; chan_ctxt->mhi_trb_read_ptr = chan_ctxt->mhi_trb_ring_base_addr; chan_ctxt->mhi_trb_write_ptr = chan_ctxt->mhi_trb_ring_base_addr; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Reset complete.\n"); } enum MHI_EVENT_CCS get_cmd_pkt(struct mhi_device_ctxt *mhi_dev_ctxt, union mhi_event_pkt *ev_pkt, union mhi_cmd_pkt **cmd_pkt, u32 event_index) { uintptr_t phy_trb_loc = 0; if (NULL != ev_pkt) phy_trb_loc = (uintptr_t)MHI_EV_READ_PTR(EV_PTR, ev_pkt); else return -EINVAL; *cmd_pkt = (union mhi_cmd_pkt *)mhi_p2v_addr(mhi_dev_ctxt, MHI_RING_TYPE_CMD_RING, event_index, phy_trb_loc); return MHI_EV_READ_CODE(EV_TRB_CODE, ev_pkt); } int mhi_poll_inbound(struct mhi_client_handle *client_handle, struct mhi_result *result) { struct mhi_tx_pkt *pending_trb = 0; struct mhi_device_ctxt *mhi_dev_ctxt = NULL; struct mhi_ring *local_chan_ctxt = NULL; struct mhi_ring *bb_ctxt = NULL; struct mhi_buf_info *bb = NULL; struct mhi_client_config *client_config; int chan = 0, r = -EIO; unsigned long flags; if (!client_handle || !result) return -EINVAL; client_config = client_handle->client_config; mhi_dev_ctxt = client_config->mhi_dev_ctxt; mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Entered\n"); chan = client_config->chan_info.chan_nr; local_chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; bb_ctxt = &mhi_dev_ctxt->chan_bb_list[chan]; spin_lock_irqsave(&local_chan_ctxt->ring_lock, flags); if (local_chan_ctxt->ch_state == MHI_CHAN_STATE_ENABLED) { if (bb_ctxt->rp != bb_ctxt->ack_rp) { pending_trb = (struct mhi_tx_pkt *)(local_chan_ctxt->ack_rp); result->flags = pending_trb->info; bb = bb_ctxt->ack_rp; if (bb->bb_active) { mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Bounce buffer active chan %d, copying data\n", chan); } result->buf_addr = bb->client_buf; result->bytes_xferd = bb->filled_size; result->transaction_status = 0; r = delete_element(local_chan_ctxt, &local_chan_ctxt->ack_rp, &local_chan_ctxt->rp, NULL); WARN_ON(r); r = delete_element(bb_ctxt, &bb_ctxt->ack_rp, &bb_ctxt->rp, NULL); WARN_ON(r); } else { result->buf_addr = 0; result->bytes_xferd = 0; r = -ENODATA; } } spin_unlock_irqrestore(&local_chan_ctxt->ring_lock, flags); mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "Exited Result: Buf addr: 0x%p Bytes xfed 0x%zx chan %d\n", result->buf_addr, result->bytes_xferd, chan); return r; } EXPORT_SYMBOL(mhi_poll_inbound); int validate_ev_el_addr(struct mhi_ring *ring, uintptr_t addr) { return (addr < (uintptr_t)(ring->base) || addr > ((uintptr_t)(ring->base) + (ring->len - 1))) ? -ERANGE : 0; } int validate_ring_el_addr(struct mhi_ring *ring, uintptr_t addr) { return (addr < (uintptr_t)(ring->base) || addr > ((uintptr_t)(ring->base) + (ring->len - 1))) ? -ERANGE : 0; } int mhi_wait_for_mdm(struct mhi_device_ctxt *mhi_dev_ctxt) { u32 j = 0; while (mhi_reg_read(mhi_dev_ctxt->mmio_info.mmio_addr, MHIREGLEN) == 0xFFFFFFFF && j <= MHI_MAX_LINK_RETRIES) { mhi_log(mhi_dev_ctxt, MHI_MSG_CRITICAL, "Could not access device retry %d\n", j); msleep(MHI_LINK_STABILITY_WAIT_MS); if (MHI_MAX_LINK_RETRIES == j) { mhi_log(mhi_dev_ctxt, MHI_MSG_CRITICAL, "Could not access device, FAILING!\n"); return -ETIME; } j++; } return 0; } int mhi_get_max_desc(struct mhi_client_handle *client_handle) { struct mhi_client_config *client_config; if (!client_handle) return -EINVAL; client_config = client_handle->client_config; return client_config->chan_info.max_desc - 1; } EXPORT_SYMBOL(mhi_get_max_desc); int mhi_get_epid(struct mhi_client_handle *client_handle) { return MHI_EPID; } void mhi_master_mode_runtime_get(struct mhi_device_ctxt *mhi_dev_ctxt) { pm_runtime_get(&mhi_dev_ctxt->pcie_device->dev); } void mhi_master_mode_runtime_put(struct mhi_device_ctxt *mhi_dev_ctxt) { pm_runtime_mark_last_busy(&mhi_dev_ctxt->pcie_device->dev); pm_runtime_put_noidle(&mhi_dev_ctxt->pcie_device->dev); } void mhi_slave_mode_runtime_get(struct mhi_device_ctxt *mhi_dev_ctxt) { mhi_dev_ctxt->bus_master_rt_get(mhi_dev_ctxt->pcie_device); } void mhi_slave_mode_runtime_put(struct mhi_device_ctxt *mhi_dev_ctxt) { mhi_dev_ctxt->bus_master_rt_put(mhi_dev_ctxt->pcie_device); } /* * mhi_assert_device_wake - Set WAKE_DB register * force_set - if true, will set bit regardless of counts */ void mhi_assert_device_wake(struct mhi_device_ctxt *mhi_dev_ctxt, bool force_set) { unsigned long flags; if (unlikely(force_set)) { spin_lock_irqsave(&mhi_dev_ctxt->dev_wake_lock, flags); atomic_inc(&mhi_dev_ctxt->counters.device_wake); if (MHI_WAKE_DB_ACCESS_VALID(mhi_dev_ctxt->mhi_pm_state)) mhi_write_db(mhi_dev_ctxt, mhi_dev_ctxt->mmio_info.chan_db_addr, MHI_DEV_WAKE_DB, 1); spin_unlock_irqrestore(&mhi_dev_ctxt->dev_wake_lock, flags); } else { if (likely(atomic_add_unless(&mhi_dev_ctxt-> counters.device_wake, 1, 0))) return; spin_lock_irqsave(&mhi_dev_ctxt->dev_wake_lock, flags); if ((atomic_inc_return(&mhi_dev_ctxt->counters.device_wake) == 1) && MHI_WAKE_DB_ACCESS_VALID(mhi_dev_ctxt->mhi_pm_state)) { mhi_write_db(mhi_dev_ctxt, mhi_dev_ctxt->mmio_info.chan_db_addr, MHI_DEV_WAKE_DB, 1); } spin_unlock_irqrestore(&mhi_dev_ctxt->dev_wake_lock, flags); } } void mhi_deassert_device_wake(struct mhi_device_ctxt *mhi_dev_ctxt) { unsigned long flags; WARN_ON(atomic_read(&mhi_dev_ctxt->counters.device_wake) == 0); if (likely(atomic_add_unless (&mhi_dev_ctxt->counters.device_wake, -1, 1))) return; spin_lock_irqsave(&mhi_dev_ctxt->dev_wake_lock, flags); if ((atomic_dec_return(&mhi_dev_ctxt->counters.device_wake) == 0) && MHI_WAKE_DB_ACCESS_VALID(mhi_dev_ctxt->mhi_pm_state)) mhi_write_db(mhi_dev_ctxt, mhi_dev_ctxt->mmio_info.chan_db_addr, MHI_DEV_WAKE_DB, 0); spin_unlock_irqrestore(&mhi_dev_ctxt->dev_wake_lock, flags); } int mhi_set_lpm(struct mhi_client_handle *client_handle, bool enable_lpm) { struct mhi_client_config *client_config = client_handle->client_config; struct mhi_device_ctxt *mhi_dev_ctxt = client_config->mhi_dev_ctxt; unsigned long flags; read_lock_irqsave(&mhi_dev_ctxt->pm_xfer_lock, flags); /* Disable low power mode by asserting Wake */ if (enable_lpm == false) mhi_dev_ctxt->assert_wake(mhi_dev_ctxt, false); else mhi_dev_ctxt->deassert_wake(mhi_dev_ctxt); read_unlock_irqrestore(&mhi_dev_ctxt->pm_xfer_lock, flags); return 0; } EXPORT_SYMBOL(mhi_set_lpm); int mhi_set_bus_request(struct mhi_device_ctxt *mhi_dev_ctxt, int index) { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Setting bus request to index %d\n", index); return msm_bus_scale_client_update_request(mhi_dev_ctxt->bus_client, index); } int mhi_deregister_channel(struct mhi_client_handle *client_handle) { int ret_val = 0; int chan; struct mhi_client_config *client_config; struct mhi_device_ctxt *mhi_dev_ctxt; if (!client_handle) return -EINVAL; client_config = client_handle->client_config; mhi_dev_ctxt = client_config->mhi_dev_ctxt; chan = client_config->chan_info.chan_nr; client_config->magic = 0; mhi_dev_ctxt->client_handle_list[chan] = NULL; disable_bb_ctxt(mhi_dev_ctxt, &mhi_dev_ctxt->chan_bb_list[chan]); kfree(client_config); kfree(client_handle); return ret_val; } EXPORT_SYMBOL(mhi_deregister_channel); int mhi_register_device(struct mhi_device *mhi_device, const char *node_name, void *user_data) { const struct device_node *of_node; struct mhi_device_ctxt *mhi_dev_ctxt = NULL, *itr; struct pcie_core_info *core_info; struct pci_dev *pci_dev = mhi_device->pci_dev; u32 domain = pci_domain_nr(pci_dev->bus); u32 bus = pci_dev->bus->number; u32 dev_id = pci_dev->device; u32 slot = PCI_SLOT(pci_dev->devfn); int ret, i; char node[32]; struct pcie_core_info *core; of_node = of_parse_phandle(mhi_device->dev->of_node, node_name, 0); if (!of_node) return -EINVAL; if (!mhi_device_drv) return -EPROBE_DEFER; /* Traverse thru the list */ ret = of_machine_is_compatible("qcom,ipq807x"); mutex_lock(&mhi_device_drv->lock); list_for_each_entry(itr, &mhi_device_drv->head, node) { struct platform_device *pdev = itr->plat_dev; core = &itr->core; if (!ret && pdev->dev.of_node == of_node && core->domain == domain && core->bus == bus && core->slot == slot && (core->dev_id == PCI_ANY_ID || (core->dev_id == dev_id))) { /* change default dev_id to current dev_id */ core->dev_id = dev_id; mhi_dev_ctxt = itr; break; } else if ((pdev->dev.of_node == of_node) && ((core->dev_id == PCI_ANY_ID) || (core->dev_id == dev_id))) { core->dev_id = dev_id; mhi_dev_ctxt = itr; break; } } mutex_unlock(&mhi_device_drv->lock); /* perhaps we've not probed yet */ if (!mhi_dev_ctxt) return -EPROBE_DEFER; snprintf(node, sizeof(node), "mhi_%04x_%02u.%02u.%02u", core->dev_id, core->domain, core->bus, core->slot); mhi_dev_ctxt->mhi_ipc_log = ipc_log_context_create(MHI_IPC_LOG_PAGES, node, 0); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Registering Domain:%02u Bus:%04u dev:0x%04x slot:%04u\n", domain, bus, dev_id, slot); /* Set up pcie dev info */ mhi_dev_ctxt->pcie_device = pci_dev; mhi_dev_ctxt->mhi_pm_state = MHI_PM_DISABLE; INIT_WORK(&mhi_dev_ctxt->process_m1_worker, process_m1_transition); INIT_WORK(&mhi_dev_ctxt->st_thread_worker, mhi_state_change_worker); INIT_WORK(&mhi_dev_ctxt->process_sys_err_worker, mhi_sys_err_worker); mutex_init(&mhi_dev_ctxt->pm_lock); rwlock_init(&mhi_dev_ctxt->pm_xfer_lock); spin_lock_init(&mhi_dev_ctxt->dev_wake_lock); init_completion(&mhi_dev_ctxt->cmd_complete); mhi_dev_ctxt->flags.link_up = 1; core_info = &mhi_dev_ctxt->core; core_info->manufact_id = pci_dev->vendor; core_info->pci_master = false; /* Go thru resources and set up */ for (i = 0; i < ARRAY_SIZE(mhi_device->resources); i++) { const struct resource *res = &mhi_device->resources[i]; switch (resource_type(res)) { case IORESOURCE_MEM: /* bus master already mapped it */ core_info->bar0_base = (void __iomem *)res->start; core_info->bar0_end = (void __iomem *)res->end; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "bar mapped to:0x%llx - 0x%llx (virtual)\n", (u64)res->start, (u64)res->end); break; case IORESOURCE_IRQ: core_info->irq_base = (u32)res->start; core_info->max_nr_msis = (u32)resource_size(res); mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "irq mapped to: %u size:%u\n", core_info->irq_base, core_info->max_nr_msis); break; }; } if (!core_info->bar0_base || !core_info->irq_base) return -EINVAL; if (mhi_device->support_rddm && !mhi_device->rddm_size) return -EINVAL; mhi_dev_ctxt->bus_master_rt_get = mhi_device->pm_runtime_get; mhi_dev_ctxt->bus_master_rt_put = mhi_device->pm_runtime_noidle; mhi_dev_ctxt->status_cb = mhi_device->status_cb; mhi_dev_ctxt->priv_data = user_data; if (!mhi_dev_ctxt->bus_master_rt_get || !mhi_dev_ctxt->bus_master_rt_put || !mhi_dev_ctxt->status_cb) return -EINVAL; ret = mhi_ctxt_init(mhi_dev_ctxt); if (ret) { mhi_log(mhi_dev_ctxt, MHI_MSG_ERROR, "MHI Initialization failed, ret %d\n", ret); return ret; } mhi_init_debugfs(mhi_dev_ctxt); /* setup shadow pm functions */ mhi_dev_ctxt->assert_wake = mhi_assert_device_wake; mhi_dev_ctxt->deassert_wake = mhi_deassert_device_wake; mhi_dev_ctxt->runtime_get = mhi_slave_mode_runtime_get; mhi_dev_ctxt->runtime_put = mhi_slave_mode_runtime_put; mhi_device->mhi_dev_ctxt = mhi_dev_ctxt; /* Store RDDM information */ if (mhi_device->support_rddm) { mhi_dev_ctxt->bhi_ctxt.support_rddm = true; mhi_dev_ctxt->bhi_ctxt.rddm_size = mhi_device->rddm_size; mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Device support rddm of size:0x%lx bytes\n", (unsigned long)mhi_dev_ctxt->bhi_ctxt.rddm_size); } /* notify all the registered clients we probed */ for (i = 0; i < MHI_MAX_CHANNELS; i++) { struct mhi_client_handle *client_handle = mhi_dev_ctxt->client_handle_list[i]; if (!client_handle) continue; client_handle->dev_id = core->dev_id; mhi_notify_client(client_handle, MHI_CB_MHI_PROBED); } mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Exit success\n"); return 0; } EXPORT_SYMBOL(mhi_register_device); int mhi_xfer_rddm(struct mhi_device *mhi_device, enum mhi_rddm_segment seg, struct scatterlist **sg_list) { struct mhi_device_ctxt *mhi_dev_ctxt = mhi_device->mhi_dev_ctxt; struct bhi_ctxt_t *bhi_ctxt = &mhi_dev_ctxt->bhi_ctxt; int segments = 0; *sg_list = NULL; switch (seg) { case MHI_RDDM_FW_SEGMENT: *sg_list = bhi_ctxt->fw_table.sg_list; segments = bhi_ctxt->fw_table.segment_count; break; case MHI_RDDM_RD_SEGMENT: *sg_list = bhi_ctxt->rddm_table.sg_list; segments = bhi_ctxt->rddm_table.segment_count; break; } return segments; } EXPORT_SYMBOL(mhi_xfer_rddm); void mhi_process_db_brstmode(struct mhi_device_ctxt *mhi_dev_ctxt, void __iomem *io_addr, unsigned int chan, dma_addr_t val) { struct mhi_ring *ring_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; if (io_addr == mhi_dev_ctxt->mmio_info.chan_db_addr) ring_ctxt = &mhi_dev_ctxt-> mhi_local_chan_ctxt[chan]; else ring_ctxt = &mhi_dev_ctxt-> mhi_local_event_ctxt[chan]; mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "db.set addr: %p io_offset %u val:0x%llx\n", io_addr, chan, (u64)val); mhi_update_ctxt(mhi_dev_ctxt, io_addr, chan, val); if (ring_ctxt->db_mode.db_mode) { mhi_write_db(mhi_dev_ctxt, io_addr, chan, val); ring_ctxt->db_mode.db_mode = 0; } else { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Not ringing xfer db, chan %u, brstmode %d db_mode %d\n", chan, ring_ctxt->db_mode.brstmode, ring_ctxt->db_mode.db_mode); } } void mhi_process_db_brstmode_disable(struct mhi_device_ctxt *mhi_dev_ctxt, void __iomem *io_addr, unsigned int chan, dma_addr_t val) { mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "db.set addr: %p io_offset %u val:0x%llx\n", io_addr, chan, (u64)val); mhi_update_ctxt(mhi_dev_ctxt, io_addr, chan, val); mhi_write_db(mhi_dev_ctxt, io_addr, chan, val); } void mhi_process_db(struct mhi_device_ctxt *mhi_dev_ctxt, void __iomem *io_addr, unsigned int chan, dma_addr_t val) { mhi_log(mhi_dev_ctxt, MHI_MSG_VERBOSE, "db.set addr: %p io_offset %u val:0x%llx\n", io_addr, chan, (u64)val); mhi_update_ctxt(mhi_dev_ctxt, io_addr, chan, val); /* Channel Doorbell and Polling Mode Disabled or Software Channel*/ if (io_addr == mhi_dev_ctxt->mmio_info.chan_db_addr) { struct mhi_ring *chan_ctxt = &mhi_dev_ctxt->mhi_local_chan_ctxt[chan]; if (!(IS_HARDWARE_CHANNEL(chan) && !chan_ctxt->db_mode.db_mode)) { mhi_write_db(mhi_dev_ctxt, io_addr, chan, val); chan_ctxt->db_mode.db_mode = 0; } else { mhi_log(mhi_dev_ctxt, MHI_MSG_INFO, "Not ringing xfer db, chan %u, brstmode %d db_mode %d\n", chan, chan_ctxt->db_mode.brstmode, chan_ctxt->db_mode.db_mode); } /* Event Doorbell and Polling mode Disabled */ } else if (io_addr == mhi_dev_ctxt->mmio_info.event_db_addr) { struct mhi_ring *ev_ctxt = &mhi_dev_ctxt->mhi_local_event_ctxt[chan]; /* Only ring for software channel or db mode*/ if (!(IS_HW_EV_RING(mhi_dev_ctxt, chan) && !ev_ctxt->db_mode.db_mode)) { mhi_write_db(mhi_dev_ctxt, io_addr, chan, val); } } else { mhi_write_db(mhi_dev_ctxt, io_addr, chan, val); } } void mhi_reg_write_field(struct mhi_device_ctxt *mhi_dev_ctxt, void __iomem *io_addr, uintptr_t io_offset, u32 mask, u32 shift, u32 val) { u32 reg_val; reg_val = mhi_reg_read(io_addr, io_offset); reg_val &= ~mask; reg_val = reg_val | (val << shift); mhi_reg_write(mhi_dev_ctxt, io_addr, io_offset, reg_val); } void mhi_reg_write(struct mhi_device_ctxt *mhi_dev_ctxt, void __iomem *io_addr, uintptr_t io_offset, u32 val) { mhi_log(mhi_dev_ctxt, MHI_MSG_RAW, "d.s 0x%p off: 0x%lx 0x%x\n", io_addr, io_offset, val); iowrite32(val, io_addr + io_offset); /* Flush write to device */ wmb(); } u32 mhi_reg_read_field(void __iomem *io_addr, uintptr_t io_offset, u32 mask, u32 shift) { return (mhi_reg_read(io_addr, io_offset) & mask) >> shift; } u32 mhi_reg_read(void __iomem *io_addr, uintptr_t io_offset) { return ioread32(io_addr + io_offset); }