| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (c) 2019, The Linux Foundation. All rights reserved.*/ |
| |
| #include <linux/async.h> |
| #include <linux/device.h> |
| #include <linux/dma-direction.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/errno.h> |
| #include <linux/ipc_logging.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of_device.h> |
| #include <linux/rpmsg.h> |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| #include <linux/uaccess.h> |
| #include <linux/wait.h> |
| #include <linux/mhi.h> |
| |
| #define MHI_SAT_DRIVER_NAME "mhi_satellite" |
| |
| /* logging macros */ |
| #define IPC_LOG_PAGES (10) |
| #define IPC_LOG_LVL (MHI_MSG_LVL_INFO) |
| #define KLOG_LVL (MHI_MSG_LVL_ERROR) |
| |
| #define MHI_SUBSYS_LOG(fmt, ...) do { \ |
| if (!subsys) \ |
| break; \ |
| if (mhi_sat_driver.klog_lvl <= MHI_MSG_LVL_INFO) \ |
| pr_info("[I][%s][%s] " fmt, __func__, subsys->name, \ |
| ##__VA_ARGS__);\ |
| if (subsys->ipc_log && mhi_sat_driver.ipc_log_lvl <= \ |
| MHI_MSG_LVL_INFO) \ |
| ipc_log_string(subsys->ipc_log, "[I][%s] " fmt, __func__, \ |
| ##__VA_ARGS__); \ |
| } while (0) |
| |
| #define MHI_SAT_LOG(fmt, ...) do { \ |
| if (!subsys || !sat_cntrl) \ |
| break; \ |
| if (mhi_sat_driver.klog_lvl <= MHI_MSG_LVL_INFO) \ |
| pr_info("[I][%s][%s][%x] " fmt, __func__, subsys->name, \ |
| sat_cntrl->dev_id, ##__VA_ARGS__);\ |
| if (subsys->ipc_log && mhi_sat_driver.ipc_log_lvl <= \ |
| MHI_MSG_LVL_INFO) \ |
| ipc_log_string(subsys->ipc_log, "[I][%s][%x] " fmt, __func__, \ |
| sat_cntrl->dev_id, ##__VA_ARGS__); \ |
| } while (0) |
| |
| #define MHI_SAT_ERR(fmt, ...) do { \ |
| if (!subsys || !sat_cntrl) \ |
| break; \ |
| if (mhi_sat_driver.klog_lvl <= MHI_MSG_LVL_ERROR) \ |
| pr_err("[E][%s][%s][%x] " fmt, __func__, subsys->name, \ |
| sat_cntrl->dev_id, ##__VA_ARGS__); \ |
| if (subsys->ipc_log && mhi_sat_driver.ipc_log_lvl <= \ |
| MHI_MSG_LVL_ERROR) \ |
| ipc_log_string(subsys->ipc_log, "[E][%s][%x] " fmt, __func__, \ |
| sat_cntrl->dev_id, ##__VA_ARGS__); \ |
| } while (0) |
| |
| #define MHI_SAT_ASSERT(cond, msg) do { \ |
| if (cond) \ |
| panic(msg); \ |
| } while (0) |
| |
| /* mhi sys error command */ |
| #define MHI_TRE_CMD_SYS_ERR_PTR (0) |
| #define MHI_TRE_CMD_SYS_ERR_D0 (0) |
| #define MHI_TRE_CMD_SYS_ERR_D1 (MHI_PKT_TYPE_SYS_ERR_CMD << 16) |
| |
| /* mhi state change event */ |
| #define MHI_TRE_EVT_MHI_STATE_PTR (0) |
| #define MHI_TRE_EVT_MHI_STATE_D0(state) (state << 24) |
| #define MHI_TRE_EVT_MHI_STATE_D1 (MHI_PKT_TYPE_STATE_CHANGE_EVENT << 16) |
| |
| /* mhi exec env change event */ |
| #define MHI_TRE_EVT_EE_PTR (0) |
| #define MHI_TRE_EVT_EE_D0(ee) (ee << 24) |
| #define MHI_TRE_EVT_EE_D1 (MHI_PKT_TYPE_EE_EVENT << 16) |
| |
| /* mhi config event */ |
| #define MHI_TRE_EVT_CFG_PTR(base_addr) (base_addr) |
| #define MHI_TRE_EVT_CFG_D0(er_base, num) ((er_base << 16) | (num & 0xFFFF)) |
| #define MHI_TRE_EVT_CFG_D1 (MHI_PKT_TYPE_CFG_EVENT << 16) |
| |
| /* command completion event */ |
| #define MHI_TRE_EVT_CMD_COMPLETION_PTR(ptr) (ptr) |
| #define MHI_TRE_EVT_CMD_COMPLETION_D0(code) (code << 24) |
| #define MHI_TRE_EVT_CMD_COMPLETION_D1 (MHI_PKT_TYPE_CMD_COMPLETION_EVENT << 16) |
| |
| /* packet parser macros */ |
| #define MHI_TRE_GET_PTR(tre) ((tre)->ptr) |
| #define MHI_TRE_GET_SIZE(tre) ((tre)->dword[0]) |
| #define MHI_TRE_GET_CCS(tre) (((tre)->dword[0] >> 24) & 0xFF) |
| #define MHI_TRE_GET_ID(tre) (((tre)->dword[1] >> 24) & 0xFF) |
| #define MHI_TRE_GET_TYPE(tre) (((tre)->dword[1] >> 16) & 0xFF) |
| #define MHI_TRE_IS_ER_CTXT_TYPE(tre) (((tre)->dword[1]) & 0x1) |
| |
| /* creates unique device ID based on connection topology */ |
| #define MHI_SAT_CREATE_DEVICE_ID(dev, domain, bus, slot) \ |
| ((dev & 0xFFFF) << 16 | (domain & 0xF) << 12 | (bus & 0xFF) << 4 | \ |
| (slot & 0xF)) |
| |
| /* mhi core definitions */ |
| #define MHI_CTXT_TYPE_GENERIC (0xA) |
| |
| struct __packed mhi_generic_ctxt { |
| u32 reserved0; |
| u32 type; |
| u32 reserved1; |
| u64 ctxt_base; |
| u64 ctxt_size; |
| u64 reserved[2]; |
| }; |
| |
| enum mhi_pkt_type { |
| MHI_PKT_TYPE_INVALID = 0x0, |
| MHI_PKT_TYPE_RESET_CHAN_CMD = 0x10, |
| MHI_PKT_TYPE_STOP_CHAN_CMD = 0x11, |
| MHI_PKT_TYPE_START_CHAN_CMD = 0x12, |
| MHI_PKT_TYPE_STATE_CHANGE_EVENT = 0x20, |
| MHI_PKT_TYPE_CMD_COMPLETION_EVENT = 0x21, |
| MHI_PKT_TYPE_EE_EVENT = 0x40, |
| MHI_PKT_TYPE_CTXT_UPDATE_CMD = 0x64, |
| MHI_PKT_TYPE_IOMMU_MAP_CMD = 0x65, |
| MHI_PKT_TYPE_CFG_EVENT = 0x6E, |
| MHI_PKT_TYPE_SYS_ERR_CMD = 0xFF, |
| }; |
| |
| enum mhi_cmd_type { |
| MHI_CMD_TYPE_RESET = 0x10, |
| MHI_CMD_TYPE_STOP = 0x11, |
| MHI_CMD_TYPE_START = 0x12, |
| }; |
| |
| /* mhi event completion codes */ |
| enum mhi_ev_ccs { |
| MHI_EV_CC_INVALID = 0x0, |
| MHI_EV_CC_SUCCESS = 0x1, |
| MHI_EV_CC_BAD_TRE = 0x11, |
| }; |
| |
| /* satellite subsystem definitions */ |
| enum subsys_id { |
| SUBSYS_ADSP, |
| SUBSYS_SLPI, |
| SUBSYS_MAX, |
| }; |
| |
| static const char * const subsys_names[SUBSYS_MAX] = { |
| [SUBSYS_ADSP] = "adsp", |
| [SUBSYS_SLPI] = "slpi", |
| }; |
| |
| struct mhi_sat_subsys { |
| const char *name; |
| |
| struct rpmsg_device *rpdev; /* rpmsg device */ |
| |
| /* |
| * acquire either mutex or spinlock to walk controller list |
| * acquire both when modifying list |
| */ |
| struct list_head cntrl_list; /* controllers list */ |
| struct mutex cntrl_mutex; /* mutex to walk/modify controllers list */ |
| spinlock_t cntrl_lock; /* lock to walk/modify controllers list */ |
| |
| void *ipc_log; |
| }; |
| |
| /* satellite IPC definitions */ |
| #define SAT_MAJOR_VERSION (1) |
| #define SAT_MINOR_VERSION (0) |
| #define SAT_RESERVED_SEQ_NUM (0xFFFF) |
| #define SAT_MSG_SIZE(n) (sizeof(struct sat_header) + \ |
| (n * sizeof(struct sat_tre))) |
| #define SAT_TRE_SIZE(msg_size) (msg_size - sizeof(struct sat_header)) |
| #define SAT_TRE_OFFSET(msg) (msg + sizeof(struct sat_header)) |
| #define SAT_TRE_NUM_PKTS(payload_size) ((payload_size) / sizeof(struct sat_tre)) |
| |
| /* satellite IPC msg type */ |
| enum sat_msg_id { |
| SAT_MSG_ID_ACK = 0xA, |
| SAT_MSG_ID_CMD = 0xC, |
| SAT_MSG_ID_EVT = 0xE, |
| }; |
| |
| /* satellite IPC context type */ |
| enum sat_ctxt_type { |
| SAT_CTXT_TYPE_CHAN = 0x0, |
| SAT_CTXT_TYPE_EVENT = 0x1, |
| SAT_CTXT_TYPE_MAX, |
| }; |
| |
| /* satellite IPC context string */ |
| #define TO_SAT_CTXT_TYPE_STR(type) (type >= SAT_CTXT_TYPE_MAX ? "INVALID" : \ |
| sat_ctxt_str[type]) |
| |
| const char * const sat_ctxt_str[SAT_CTXT_TYPE_MAX] = { |
| [SAT_CTXT_TYPE_CHAN] = "CCA", |
| [SAT_CTXT_TYPE_EVENT] = "ECA", |
| }; |
| |
| /* satellite IPC transfer ring element */ |
| struct __packed sat_tre { |
| u64 ptr; |
| u32 dword[2]; |
| }; |
| |
| /* satellite IPC header */ |
| struct __packed sat_header { |
| u16 major_ver; |
| u16 minor_ver; |
| u16 msg_id; |
| u16 seq; |
| u16 reply_seq; |
| u16 payload_size; |
| u32 dev_id; |
| u8 reserved[8]; |
| }; |
| |
| /* satellite driver definitions */ |
| struct mhi_sat_packet { |
| struct list_head node; |
| |
| struct mhi_sat_cntrl *cntrl; /* satellite controller reference */ |
| void *msg; /* incoming message */ |
| }; |
| |
| enum mhi_sat_state { |
| SAT_READY, /* initial state when device is presented to driver */ |
| SAT_RUNNING, /* subsystem can communicate with the device */ |
| SAT_DISCONNECTED, /* rpmsg link is down */ |
| SAT_FATAL_DETECT, /* device is down as fatal error was detected early */ |
| SAT_ERROR, /* device is down after error or graceful shutdown */ |
| SAT_DISABLED, /* no further processing: wait for device removal */ |
| }; |
| |
| #define MHI_SAT_ACTIVE(cntrl) (cntrl->state == SAT_RUNNING) |
| #define MHI_SAT_IN_ERROR_STATE(cntrl) (cntrl->state >= SAT_FATAL_DETECT) |
| #define MHI_SAT_ALLOW_CONNECTION(cntrl) (cntrl->state == SAT_READY || \ |
| cntrl->state == SAT_DISCONNECTED) |
| #define MHI_SAT_ALLOW_SYS_ERR(cntrl) (cntrl->state == SAT_RUNNING || \ |
| cntrl->state == SAT_FATAL_DETECT) |
| |
| struct mhi_sat_cntrl { |
| struct list_head node; |
| |
| struct mhi_controller *mhi_cntrl; /* device MHI controller reference */ |
| struct mhi_sat_subsys *subsys; |
| |
| struct list_head dev_list; |
| struct list_head addr_map_list; /* IOMMU mapped addresses list */ |
| struct mutex list_mutex; /* mutex for devices and address map lists */ |
| |
| struct list_head packet_list; |
| spinlock_t pkt_lock; /* lock to walk/modify received packets list */ |
| |
| struct work_struct connect_work; /* subsystem connection worker */ |
| struct work_struct process_work; /* incoming packets processor */ |
| async_cookie_t error_cookie; /* synchronize device error handling */ |
| |
| /* mhi core/controller configurations */ |
| u32 dev_id; /* unique device ID with BDF as per connection topology */ |
| int er_base; /* event rings base index */ |
| int er_max; /* event rings max index */ |
| int num_er; /* total number of event rings */ |
| |
| /* satellite controller function counts */ |
| int num_devices; /* mhi devices current count */ |
| int max_devices; /* count of maximum devices for subsys/controller */ |
| u16 seq; /* internal sequence number for all outgoing packets */ |
| enum mhi_sat_state state; /* controller state manager */ |
| spinlock_t state_lock; /* lock to change controller state */ |
| |
| /* command completion variables */ |
| u16 last_cmd_seq; /* sequence number of last sent command packet */ |
| enum mhi_ev_ccs last_cmd_ccs; /* last command completion event code */ |
| struct completion completion; /* command completion event wait */ |
| struct mutex cmd_wait_mutex; /* command completion wait mutex */ |
| }; |
| |
| struct mhi_sat_device { |
| struct list_head node; |
| |
| struct mhi_device *mhi_dev; /* mhi device pointer */ |
| struct mhi_sat_cntrl *cntrl; /* parent controller */ |
| |
| bool chan_started; |
| }; |
| |
| struct mhi_sat_driver { |
| enum MHI_DEBUG_LEVEL ipc_log_lvl; /* IPC log level */ |
| enum MHI_DEBUG_LEVEL klog_lvl; /* klog/dmesg levels */ |
| |
| struct mhi_sat_subsys *subsys; /* pointer to subsystem array */ |
| unsigned int num_subsys; |
| }; |
| |
| static struct mhi_sat_driver mhi_sat_driver; |
| |
| static struct mhi_sat_subsys *find_subsys_by_name(const char *name) |
| { |
| int i; |
| struct mhi_sat_subsys *subsys = mhi_sat_driver.subsys; |
| |
| for (i = 0; i < mhi_sat_driver.num_subsys; i++, subsys++) { |
| if (!strcmp(name, subsys->name)) |
| return subsys; |
| } |
| |
| return NULL; |
| } |
| |
| static struct mhi_sat_cntrl *find_sat_cntrl_by_id(struct mhi_sat_subsys *subsys, |
| u32 dev_id) |
| { |
| struct mhi_sat_cntrl *sat_cntrl; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&subsys->cntrl_lock, flags); |
| list_for_each_entry(sat_cntrl, &subsys->cntrl_list, node) { |
| if (sat_cntrl->dev_id == dev_id) { |
| spin_unlock_irqrestore(&subsys->cntrl_lock, flags); |
| return sat_cntrl; |
| } |
| } |
| spin_unlock_irqrestore(&subsys->cntrl_lock, flags); |
| |
| return NULL; |
| } |
| |
| static struct mhi_sat_device *find_sat_dev_by_id( |
| struct mhi_sat_cntrl *sat_cntrl, int id, |
| enum sat_ctxt_type evt) |
| { |
| struct mhi_sat_device *sat_dev; |
| int compare_id; |
| |
| mutex_lock(&sat_cntrl->list_mutex); |
| list_for_each_entry(sat_dev, &sat_cntrl->dev_list, node) { |
| compare_id = (evt == SAT_CTXT_TYPE_EVENT) ? |
| sat_dev->mhi_dev->dl_event_id : |
| sat_dev->mhi_dev->dl_chan_id; |
| |
| if (compare_id == id) { |
| mutex_unlock(&sat_cntrl->list_mutex); |
| return sat_dev; |
| } |
| } |
| mutex_unlock(&sat_cntrl->list_mutex); |
| |
| return NULL; |
| } |
| |
| static bool mhi_sat_isvalid_header(struct sat_header *hdr, int len) |
| { |
| /* validate payload size */ |
| if (len >= sizeof(*hdr) && (len != hdr->payload_size + sizeof(*hdr))) |
| return false; |
| |
| /* validate SAT IPC version */ |
| if (hdr->major_ver != SAT_MAJOR_VERSION || |
| hdr->minor_ver != SAT_MINOR_VERSION) |
| return false; |
| |
| /* validate msg ID */ |
| if (hdr->msg_id != SAT_MSG_ID_CMD && hdr->msg_id != SAT_MSG_ID_EVT) |
| return false; |
| |
| return true; |
| } |
| |
| static int mhi_sat_wait_cmd_completion(struct mhi_sat_cntrl *sat_cntrl) |
| { |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| int ret; |
| |
| reinit_completion(&sat_cntrl->completion); |
| |
| MHI_SAT_LOG("Wait for command completion\n"); |
| ret = wait_for_completion_timeout(&sat_cntrl->completion, |
| msecs_to_jiffies(sat_cntrl->mhi_cntrl->timeout_ms)); |
| if (!ret || sat_cntrl->last_cmd_ccs != MHI_EV_CC_SUCCESS) { |
| MHI_SAT_ERR("Command completion failure:seq:%u:ret:%d:ccs:%d\n", |
| sat_cntrl->last_cmd_seq, ret, sat_cntrl->last_cmd_ccs); |
| return -EIO; |
| } |
| |
| MHI_SAT_LOG("Command completion successful for seq:%u\n", |
| sat_cntrl->last_cmd_seq); |
| |
| return 0; |
| } |
| |
| static int mhi_sat_send_msg(struct mhi_sat_cntrl *sat_cntrl, |
| enum sat_msg_id type, u16 reply_seq, |
| void *msg, u16 msg_size) |
| { |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| struct sat_header *hdr = msg; |
| |
| /* create sequence number for controller */ |
| sat_cntrl->seq++; |
| if (sat_cntrl->seq == SAT_RESERVED_SEQ_NUM) |
| sat_cntrl->seq = 0; |
| |
| /* populate header */ |
| hdr->major_ver = SAT_MAJOR_VERSION; |
| hdr->minor_ver = SAT_MINOR_VERSION; |
| hdr->msg_id = type; |
| hdr->seq = sat_cntrl->seq; |
| hdr->reply_seq = reply_seq; |
| hdr->payload_size = SAT_TRE_SIZE(msg_size); |
| hdr->dev_id = sat_cntrl->dev_id; |
| |
| /* save last sent command sequence number for completion event */ |
| if (type == SAT_MSG_ID_CMD) |
| sat_cntrl->last_cmd_seq = sat_cntrl->seq; |
| |
| return rpmsg_send(subsys->rpdev->ept, msg, msg_size); |
| } |
| |
| static void mhi_sat_process_cmds(struct mhi_sat_cntrl *sat_cntrl, |
| struct sat_header *hdr, struct sat_tre *pkt) |
| { |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| int num_pkts = SAT_TRE_NUM_PKTS(hdr->payload_size), i; |
| |
| for (i = 0; i < num_pkts; i++, pkt++) { |
| enum mhi_ev_ccs code = MHI_EV_CC_INVALID; |
| |
| switch (MHI_TRE_GET_TYPE(pkt)) { |
| case MHI_PKT_TYPE_IOMMU_MAP_CMD: |
| { |
| struct mhi_buf *buf; |
| struct mhi_controller *mhi_cntrl = sat_cntrl->mhi_cntrl; |
| dma_addr_t iova = DMA_ERROR_CODE; |
| |
| buf = kmalloc(sizeof(*buf), GFP_ATOMIC); |
| if (!buf) |
| goto iommu_map_cmd_completion; |
| |
| buf->phys_addr = MHI_TRE_GET_PTR(pkt); |
| buf->len = MHI_TRE_GET_SIZE(pkt); |
| |
| iova = dma_map_resource(mhi_cntrl->dev, buf->phys_addr, |
| buf->len, DMA_BIDIRECTIONAL, 0); |
| if (dma_mapping_error(mhi_cntrl->dev, iova)) { |
| kfree(buf); |
| goto iommu_map_cmd_completion; |
| } |
| |
| buf->dma_addr = iova; |
| |
| mutex_lock(&sat_cntrl->list_mutex); |
| list_add_tail(&buf->node, |
| &sat_cntrl->addr_map_list); |
| mutex_unlock(&sat_cntrl->list_mutex); |
| |
| code = MHI_EV_CC_SUCCESS; |
| |
| iommu_map_cmd_completion: |
| MHI_SAT_LOG("IOMMU MAP 0x%llx len:%d CMD %s:%llx\n", |
| MHI_TRE_GET_PTR(pkt), MHI_TRE_GET_SIZE(pkt), |
| (code == MHI_EV_CC_SUCCESS) ? "successful" : |
| "failed", iova); |
| |
| pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(iova); |
| pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0(code); |
| pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; |
| break; |
| } |
| case MHI_PKT_TYPE_CTXT_UPDATE_CMD: |
| { |
| u64 ctxt_ptr = MHI_TRE_GET_PTR(pkt); |
| u64 ctxt_size = MHI_TRE_GET_SIZE(pkt); |
| int id = MHI_TRE_GET_ID(pkt); |
| enum sat_ctxt_type evt = MHI_TRE_IS_ER_CTXT_TYPE(pkt); |
| struct mhi_generic_ctxt gen_ctxt; |
| struct mhi_buf buf; |
| struct mhi_sat_device *sat_dev = find_sat_dev_by_id( |
| sat_cntrl, id, evt); |
| int ret; |
| |
| MHI_SAT_ASSERT(!sat_dev, |
| "No device with given chan/evt ID"); |
| |
| memset(&gen_ctxt, 0, sizeof(gen_ctxt)); |
| memset(&buf, 0, sizeof(buf)); |
| |
| gen_ctxt.type = MHI_CTXT_TYPE_GENERIC; |
| gen_ctxt.ctxt_base = ctxt_ptr; |
| gen_ctxt.ctxt_size = ctxt_size; |
| |
| buf.buf = &gen_ctxt; |
| buf.len = sizeof(gen_ctxt); |
| buf.name = TO_SAT_CTXT_TYPE_STR(evt); |
| |
| ret = mhi_device_configure(sat_dev->mhi_dev, |
| DMA_BIDIRECTIONAL, &buf, 1); |
| if (!ret) |
| code = MHI_EV_CC_SUCCESS; |
| |
| MHI_SAT_LOG("CTXT UPDATE CMD %s:%d %s\n", buf.name, id, |
| (code == MHI_EV_CC_SUCCESS) ? "successful" : |
| "failed"); |
| |
| pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(0); |
| pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0(code); |
| pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; |
| break; |
| } |
| case MHI_PKT_TYPE_START_CHAN_CMD: |
| { |
| int id = MHI_TRE_GET_ID(pkt); |
| struct mhi_sat_device *sat_dev = find_sat_dev_by_id( |
| sat_cntrl, id, |
| SAT_CTXT_TYPE_CHAN); |
| int ret; |
| |
| MHI_SAT_ASSERT(!sat_dev, |
| "No device with given channel ID\n"); |
| |
| MHI_SAT_ASSERT(sat_dev->chan_started, |
| "Channel already started!"); |
| |
| ret = mhi_prepare_for_transfer(sat_dev->mhi_dev); |
| if (!ret) { |
| sat_dev->chan_started = true; |
| code = MHI_EV_CC_SUCCESS; |
| } |
| |
| MHI_SAT_LOG("START CHANNEL %d CMD %s\n", id, |
| (code == MHI_EV_CC_SUCCESS) ? "successful" : |
| "failure"); |
| |
| pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(0); |
| pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0(code); |
| pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; |
| break; |
| } |
| case MHI_PKT_TYPE_RESET_CHAN_CMD: |
| { |
| int id = MHI_TRE_GET_ID(pkt); |
| struct mhi_sat_device *sat_dev = |
| find_sat_dev_by_id(sat_cntrl, id, |
| SAT_CTXT_TYPE_CHAN); |
| |
| MHI_SAT_ASSERT(!sat_dev, |
| "No device with given channel ID\n"); |
| |
| MHI_SAT_ASSERT(!sat_dev->chan_started, |
| "Resetting unstarted channel!"); |
| |
| mhi_unprepare_from_transfer(sat_dev->mhi_dev); |
| sat_dev->chan_started = false; |
| |
| MHI_SAT_LOG("RESET CHANNEL %d CMD successful\n", id); |
| |
| pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(0); |
| pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0( |
| MHI_EV_CC_SUCCESS); |
| pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; |
| break; |
| } |
| default: |
| MHI_SAT_ASSERT(1, "Unhandled command!"); |
| break; |
| } |
| } |
| } |
| |
| /* send sys_err command to subsystem if device asserts or is powered off */ |
| static void mhi_sat_send_sys_err(struct mhi_sat_cntrl *sat_cntrl) |
| { |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| struct sat_tre *pkt; |
| void *msg; |
| int ret; |
| |
| /* flush all pending work */ |
| flush_work(&sat_cntrl->connect_work); |
| flush_work(&sat_cntrl->process_work); |
| |
| msg = kmalloc(SAT_MSG_SIZE(1), GFP_KERNEL); |
| |
| MHI_SAT_ASSERT(!msg, "Unable to malloc for SYS_ERR message!\n"); |
| if (!msg) |
| return; |
| |
| pkt = SAT_TRE_OFFSET(msg); |
| pkt->ptr = MHI_TRE_CMD_SYS_ERR_PTR; |
| pkt->dword[0] = MHI_TRE_CMD_SYS_ERR_D0; |
| pkt->dword[1] = MHI_TRE_CMD_SYS_ERR_D1; |
| |
| mutex_lock(&sat_cntrl->cmd_wait_mutex); |
| |
| ret = mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_CMD, |
| SAT_RESERVED_SEQ_NUM, msg, |
| SAT_MSG_SIZE(1)); |
| kfree(msg); |
| if (ret) { |
| MHI_SAT_ERR("Failed to notify SYS_ERR cmd\n"); |
| mutex_unlock(&sat_cntrl->cmd_wait_mutex); |
| return; |
| } |
| |
| MHI_SAT_LOG("SYS_ERR command sent\n"); |
| |
| /* blocking call to wait for command completion event */ |
| mhi_sat_wait_cmd_completion(sat_cntrl); |
| |
| mutex_unlock(&sat_cntrl->cmd_wait_mutex); |
| } |
| |
| static void mhi_sat_error_worker(void *data, async_cookie_t cookie) |
| { |
| struct mhi_sat_cntrl *sat_cntrl = data; |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| struct sat_tre *pkt; |
| void *msg; |
| int ret; |
| |
| MHI_SAT_LOG("Entered\n"); |
| |
| /* flush all pending work */ |
| flush_work(&sat_cntrl->connect_work); |
| flush_work(&sat_cntrl->process_work); |
| |
| msg = kmalloc(SAT_MSG_SIZE(1), GFP_KERNEL); |
| |
| MHI_SAT_ASSERT(!msg, "Unable to malloc for SYS_ERR message!\n"); |
| if (!msg) |
| return; |
| |
| pkt = SAT_TRE_OFFSET(msg); |
| pkt->ptr = MHI_TRE_EVT_MHI_STATE_PTR; |
| pkt->dword[0] = MHI_TRE_EVT_MHI_STATE_D0(MHI_STATE_SYS_ERR); |
| pkt->dword[1] = MHI_TRE_EVT_MHI_STATE_D1; |
| |
| ret = mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_EVT, |
| SAT_RESERVED_SEQ_NUM, msg, |
| SAT_MSG_SIZE(1)); |
| kfree(msg); |
| |
| MHI_SAT_LOG("SYS_ERROR state change event send %s!\n", ret ? "failure" : |
| "success"); |
| } |
| |
| static void mhi_sat_process_worker(struct work_struct *work) |
| { |
| struct mhi_sat_cntrl *sat_cntrl = container_of(work, |
| struct mhi_sat_cntrl, process_work); |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| struct mhi_sat_packet *packet, *tmp; |
| struct sat_header *hdr; |
| struct sat_tre *pkt; |
| LIST_HEAD(head); |
| |
| MHI_SAT_LOG("Entered\n"); |
| |
| spin_lock_irq(&sat_cntrl->pkt_lock); |
| list_splice_tail_init(&sat_cntrl->packet_list, &head); |
| spin_unlock_irq(&sat_cntrl->pkt_lock); |
| |
| list_for_each_entry_safe(packet, tmp, &head, node) { |
| hdr = packet->msg; |
| pkt = SAT_TRE_OFFSET(packet->msg); |
| |
| list_del(&packet->node); |
| |
| if (!MHI_SAT_ACTIVE(sat_cntrl)) |
| goto process_next; |
| |
| mhi_sat_process_cmds(sat_cntrl, hdr, pkt); |
| |
| /* send response event(s) */ |
| mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_EVT, hdr->seq, |
| packet->msg, |
| SAT_MSG_SIZE(SAT_TRE_NUM_PKTS( |
| hdr->payload_size))); |
| |
| process_next: |
| kfree(packet); |
| } |
| |
| MHI_SAT_LOG("Exited\n"); |
| } |
| |
| static void mhi_sat_connect_worker(struct work_struct *work) |
| { |
| struct mhi_sat_cntrl *sat_cntrl = container_of(work, |
| struct mhi_sat_cntrl, connect_work); |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| enum mhi_sat_state prev_state; |
| struct sat_tre *pkt; |
| void *msg; |
| int ret; |
| |
| spin_lock_irq(&sat_cntrl->state_lock); |
| if (!subsys->rpdev || sat_cntrl->max_devices != sat_cntrl->num_devices |
| || !(MHI_SAT_ALLOW_CONNECTION(sat_cntrl))) { |
| spin_unlock_irq(&sat_cntrl->state_lock); |
| return; |
| } |
| prev_state = sat_cntrl->state; |
| sat_cntrl->state = SAT_RUNNING; |
| spin_unlock_irq(&sat_cntrl->state_lock); |
| |
| MHI_SAT_LOG("Entered\n"); |
| |
| msg = kmalloc(SAT_MSG_SIZE(3), GFP_ATOMIC); |
| if (!msg) |
| goto error_connect_work; |
| |
| pkt = SAT_TRE_OFFSET(msg); |
| |
| /* prepare #1 MHI_CFG HELLO event */ |
| pkt->ptr = MHI_TRE_EVT_CFG_PTR(sat_cntrl->mhi_cntrl->base_addr); |
| pkt->dword[0] = MHI_TRE_EVT_CFG_D0(sat_cntrl->er_base, |
| sat_cntrl->num_er); |
| pkt->dword[1] = MHI_TRE_EVT_CFG_D1; |
| pkt++; |
| |
| /* prepare M0 event */ |
| pkt->ptr = MHI_TRE_EVT_MHI_STATE_PTR; |
| pkt->dword[0] = MHI_TRE_EVT_MHI_STATE_D0(MHI_STATE_M0); |
| pkt->dword[1] = MHI_TRE_EVT_MHI_STATE_D1; |
| pkt++; |
| |
| /* prepare AMSS event */ |
| pkt->ptr = MHI_TRE_EVT_EE_PTR; |
| pkt->dword[0] = MHI_TRE_EVT_EE_D0(MHI_EE_AMSS); |
| pkt->dword[1] = MHI_TRE_EVT_EE_D1; |
| |
| ret = mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_EVT, SAT_RESERVED_SEQ_NUM, |
| msg, SAT_MSG_SIZE(3)); |
| kfree(msg); |
| if (ret) { |
| MHI_SAT_ERR("Failed to send hello packet:%d\n", ret); |
| goto error_connect_work; |
| } |
| |
| MHI_SAT_LOG("Device 0x%x sent hello packet\n", sat_cntrl->dev_id); |
| |
| return; |
| |
| error_connect_work: |
| spin_lock_irq(&sat_cntrl->state_lock); |
| if (MHI_SAT_ACTIVE(sat_cntrl)) |
| sat_cntrl->state = prev_state; |
| spin_unlock_irq(&sat_cntrl->state_lock); |
| } |
| |
| static void mhi_sat_process_events(struct mhi_sat_cntrl *sat_cntrl, |
| struct sat_header *hdr, struct sat_tre *pkt) |
| { |
| int num_pkts = SAT_TRE_NUM_PKTS(hdr->payload_size); |
| int i; |
| |
| for (i = 0; i < num_pkts; i++, pkt++) { |
| if (MHI_TRE_GET_TYPE(pkt) == |
| MHI_PKT_TYPE_CMD_COMPLETION_EVENT) { |
| if (hdr->reply_seq != sat_cntrl->last_cmd_seq) |
| continue; |
| |
| sat_cntrl->last_cmd_ccs = MHI_TRE_GET_CCS(pkt); |
| complete(&sat_cntrl->completion); |
| } |
| } |
| } |
| |
| static int mhi_sat_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, |
| void *priv, u32 src) |
| { |
| struct mhi_sat_subsys *subsys = dev_get_drvdata(&rpdev->dev); |
| struct sat_header *hdr = data; |
| struct sat_tre *pkt = SAT_TRE_OFFSET(data); |
| struct mhi_sat_cntrl *sat_cntrl; |
| struct mhi_sat_packet *packet; |
| unsigned long flags; |
| |
| MHI_SAT_ASSERT(!mhi_sat_isvalid_header(hdr, len), "Invalid header!\n"); |
| |
| /* find controller packet was sent for */ |
| sat_cntrl = find_sat_cntrl_by_id(subsys, hdr->dev_id); |
| |
| MHI_SAT_ASSERT(!sat_cntrl, "Packet for unknown device!\n"); |
| |
| /* handle events directly regardless of controller active state */ |
| if (hdr->msg_id == SAT_MSG_ID_EVT) { |
| mhi_sat_process_events(sat_cntrl, hdr, pkt); |
| return 0; |
| } |
| |
| /* Inactive controller cannot process incoming commands */ |
| if (unlikely(!MHI_SAT_ACTIVE(sat_cntrl))) { |
| MHI_SAT_ERR("Message for inactive controller!\n"); |
| return 0; |
| } |
| |
| /* offload commands to process worker */ |
| packet = kmalloc(sizeof(*packet) + len, GFP_ATOMIC); |
| if (!packet) |
| return 0; |
| |
| packet->cntrl = sat_cntrl; |
| packet->msg = packet + 1; |
| memcpy(packet->msg, data, len); |
| |
| spin_lock_irqsave(&sat_cntrl->pkt_lock, flags); |
| list_add_tail(&packet->node, &sat_cntrl->packet_list); |
| spin_unlock_irqrestore(&sat_cntrl->pkt_lock, flags); |
| |
| schedule_work(&sat_cntrl->process_work); |
| |
| return 0; |
| } |
| |
| static void mhi_sat_rpmsg_remove(struct rpmsg_device *rpdev) |
| { |
| struct mhi_sat_subsys *subsys = dev_get_drvdata(&rpdev->dev); |
| struct mhi_sat_cntrl *sat_cntrl; |
| struct mhi_sat_device *sat_dev; |
| struct mhi_buf *buf, *tmp; |
| |
| MHI_SUBSYS_LOG("Enter\n"); |
| |
| /* unprepare each controller/device from transfer */ |
| mutex_lock(&subsys->cntrl_mutex); |
| list_for_each_entry(sat_cntrl, &subsys->cntrl_list, node) { |
| async_synchronize_cookie(sat_cntrl->error_cookie + 1); |
| |
| spin_lock_irq(&sat_cntrl->state_lock); |
| /* |
| * move to disabled state if early error fatal is detected |
| * and rpmsg link goes down before device remove call from |
| * mhi is received |
| */ |
| if (MHI_SAT_IN_ERROR_STATE(sat_cntrl)) { |
| sat_cntrl->state = SAT_DISABLED; |
| spin_unlock_irq(&sat_cntrl->state_lock); |
| continue; |
| } |
| sat_cntrl->state = SAT_DISCONNECTED; |
| spin_unlock_irq(&sat_cntrl->state_lock); |
| |
| flush_work(&sat_cntrl->connect_work); |
| flush_work(&sat_cntrl->process_work); |
| |
| mutex_lock(&sat_cntrl->list_mutex); |
| list_for_each_entry(sat_dev, &sat_cntrl->dev_list, node) { |
| if (sat_dev->chan_started) { |
| mhi_unprepare_from_transfer(sat_dev->mhi_dev); |
| sat_dev->chan_started = false; |
| } |
| } |
| |
| list_for_each_entry_safe(buf, tmp, &sat_cntrl->addr_map_list, |
| node) { |
| dma_unmap_resource(sat_cntrl->mhi_cntrl->dev, |
| buf->dma_addr, buf->len, |
| DMA_BIDIRECTIONAL, 0); |
| list_del(&buf->node); |
| kfree(buf); |
| } |
| mutex_unlock(&sat_cntrl->list_mutex); |
| |
| MHI_SAT_LOG("Removed RPMSG link\n"); |
| } |
| subsys->rpdev = NULL; |
| mutex_unlock(&subsys->cntrl_mutex); |
| } |
| |
| static int mhi_sat_rpmsg_probe(struct rpmsg_device *rpdev) |
| { |
| struct mhi_sat_subsys *subsys; |
| struct mhi_sat_cntrl *sat_cntrl; |
| const char *subsys_name; |
| int ret; |
| |
| ret = of_property_read_string(rpdev->dev.parent->of_node, "label", |
| &subsys_name); |
| if (ret) |
| return ret; |
| |
| /* find which subsystem has probed */ |
| subsys = find_subsys_by_name(subsys_name); |
| if (!subsys) |
| return -EINVAL; |
| |
| mutex_lock(&subsys->cntrl_mutex); |
| |
| MHI_SUBSYS_LOG("Received RPMSG probe\n"); |
| |
| dev_set_drvdata(&rpdev->dev, subsys); |
| |
| subsys->rpdev = rpdev; |
| |
| /* schedule work for each controller as GLINK has connected */ |
| spin_lock_irq(&subsys->cntrl_lock); |
| list_for_each_entry(sat_cntrl, &subsys->cntrl_list, node) |
| schedule_work(&sat_cntrl->connect_work); |
| spin_unlock_irq(&subsys->cntrl_lock); |
| |
| mutex_unlock(&subsys->cntrl_mutex); |
| |
| return 0; |
| } |
| |
| static struct rpmsg_device_id mhi_sat_rpmsg_match_table[] = { |
| { .name = "mhi_sat" }, |
| { }, |
| }; |
| |
| static struct rpmsg_driver mhi_sat_rpmsg_driver = { |
| .id_table = mhi_sat_rpmsg_match_table, |
| .probe = mhi_sat_rpmsg_probe, |
| .remove = mhi_sat_rpmsg_remove, |
| .callback = mhi_sat_rpmsg_cb, |
| .drv = { |
| .name = "mhi,sat_rpmsg", |
| }, |
| }; |
| |
| static void mhi_sat_dev_status_cb(struct mhi_device *mhi_dev, |
| enum MHI_CB mhi_cb) |
| { |
| struct mhi_sat_device *sat_dev = mhi_device_get_devdata(mhi_dev); |
| struct mhi_sat_cntrl *sat_cntrl = sat_dev->cntrl; |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| unsigned long flags; |
| |
| if (mhi_cb != MHI_CB_FATAL_ERROR) |
| return; |
| |
| MHI_SAT_LOG("Device fatal error detected\n"); |
| spin_lock_irqsave(&sat_cntrl->state_lock, flags); |
| if (MHI_SAT_ACTIVE(sat_cntrl)) { |
| sat_cntrl->error_cookie = async_schedule(mhi_sat_error_worker, |
| sat_cntrl); |
| sat_cntrl->state = SAT_FATAL_DETECT; |
| } else { |
| /* rpmsg link down or HELLO not sent or an error occurred */ |
| sat_cntrl->state = SAT_DISABLED; |
| } |
| |
| spin_unlock_irqrestore(&sat_cntrl->state_lock, flags); |
| } |
| |
| static void mhi_sat_dev_remove(struct mhi_device *mhi_dev) |
| { |
| struct mhi_sat_device *sat_dev = mhi_device_get_devdata(mhi_dev); |
| struct mhi_sat_cntrl *sat_cntrl = sat_dev->cntrl; |
| struct mhi_sat_subsys *subsys = sat_cntrl->subsys; |
| struct mhi_buf *buf, *tmp; |
| bool send_sys_err = false; |
| |
| /* remove device node from probed list */ |
| mutex_lock(&sat_cntrl->list_mutex); |
| list_del(&sat_dev->node); |
| mutex_unlock(&sat_cntrl->list_mutex); |
| |
| sat_cntrl->num_devices--; |
| |
| mutex_lock(&subsys->cntrl_mutex); |
| |
| async_synchronize_cookie(sat_cntrl->error_cookie + 1); |
| |
| /* send sys_err if first device is removed */ |
| spin_lock_irq(&sat_cntrl->state_lock); |
| if (MHI_SAT_ALLOW_SYS_ERR(sat_cntrl)) |
| send_sys_err = true; |
| sat_cntrl->state = SAT_ERROR; |
| spin_unlock_irq(&sat_cntrl->state_lock); |
| |
| if (send_sys_err) |
| mhi_sat_send_sys_err(sat_cntrl); |
| |
| /* exit if some devices are still present */ |
| if (sat_cntrl->num_devices) { |
| mutex_unlock(&subsys->cntrl_mutex); |
| return; |
| } |
| |
| /* |
| * cancel any pending work as it is possible that work gets queued |
| * when rpmsg probe comes in before controller is removed |
| */ |
| cancel_work_sync(&sat_cntrl->connect_work); |
| cancel_work_sync(&sat_cntrl->process_work); |
| |
| /* remove address mappings */ |
| mutex_lock(&sat_cntrl->list_mutex); |
| list_for_each_entry_safe(buf, tmp, &sat_cntrl->addr_map_list, node) { |
| dma_unmap_resource(sat_cntrl->mhi_cntrl->dev, buf->dma_addr, |
| buf->len, DMA_BIDIRECTIONAL, 0); |
| list_del(&buf->node); |
| kfree(buf); |
| } |
| mutex_unlock(&sat_cntrl->list_mutex); |
| |
| /* remove controller */ |
| spin_lock_irq(&subsys->cntrl_lock); |
| list_del(&sat_cntrl->node); |
| spin_unlock_irq(&subsys->cntrl_lock); |
| |
| mutex_destroy(&sat_cntrl->cmd_wait_mutex); |
| mutex_destroy(&sat_cntrl->list_mutex); |
| MHI_SAT_LOG("Satellite controller node removed\n"); |
| kfree(sat_cntrl); |
| |
| mutex_unlock(&subsys->cntrl_mutex); |
| } |
| |
| static int mhi_sat_dev_probe(struct mhi_device *mhi_dev, |
| const struct mhi_device_id *id) |
| { |
| struct mhi_sat_device *sat_dev; |
| struct mhi_sat_cntrl *sat_cntrl; |
| struct device_node *of_node = mhi_dev->dev.of_node; |
| struct mhi_sat_subsys *subsys = &mhi_sat_driver.subsys[id->driver_data]; |
| u32 dev_id = MHI_SAT_CREATE_DEVICE_ID(mhi_dev->dev_id, mhi_dev->domain, |
| mhi_dev->bus, mhi_dev->slot); |
| int ret; |
| |
| /* find controller with unique device ID based on topology */ |
| sat_cntrl = find_sat_cntrl_by_id(subsys, dev_id); |
| if (!sat_cntrl) { |
| sat_cntrl = kzalloc(sizeof(*sat_cntrl), GFP_KERNEL); |
| if (!sat_cntrl) |
| return -ENOMEM; |
| |
| /* |
| * max_devices will be read from device tree node. Set it to |
| * -1 before it is populated to avoid false positive when |
| * RPMSG probe schedules connect worker but no device has |
| * probed in which case num_devices and max_devices are both |
| * zero. |
| */ |
| sat_cntrl->max_devices = -1; |
| sat_cntrl->dev_id = dev_id; |
| sat_cntrl->er_base = mhi_dev->dl_event_id; |
| sat_cntrl->mhi_cntrl = mhi_dev->mhi_cntrl; |
| sat_cntrl->last_cmd_seq = SAT_RESERVED_SEQ_NUM; |
| sat_cntrl->subsys = subsys; |
| init_completion(&sat_cntrl->completion); |
| mutex_init(&sat_cntrl->list_mutex); |
| mutex_init(&sat_cntrl->cmd_wait_mutex); |
| spin_lock_init(&sat_cntrl->pkt_lock); |
| spin_lock_init(&sat_cntrl->state_lock); |
| INIT_WORK(&sat_cntrl->connect_work, mhi_sat_connect_worker); |
| INIT_WORK(&sat_cntrl->process_work, mhi_sat_process_worker); |
| INIT_LIST_HEAD(&sat_cntrl->dev_list); |
| INIT_LIST_HEAD(&sat_cntrl->addr_map_list); |
| INIT_LIST_HEAD(&sat_cntrl->packet_list); |
| |
| mutex_lock(&subsys->cntrl_mutex); |
| spin_lock_irq(&subsys->cntrl_lock); |
| list_add(&sat_cntrl->node, &subsys->cntrl_list); |
| spin_unlock_irq(&subsys->cntrl_lock); |
| mutex_unlock(&subsys->cntrl_mutex); |
| |
| MHI_SAT_LOG("Controller allocated for 0x%x\n", dev_id); |
| } |
| |
| /* set maximum devices for subsystem from device tree */ |
| if (of_node) { |
| ret = of_property_read_u32(of_node, "mhi,max-devices", |
| &sat_cntrl->max_devices); |
| if (ret) { |
| MHI_SAT_ERR("Could not find max-devices in DT node\n"); |
| return -EINVAL; |
| } |
| } |
| |
| /* get event ring base and max indexes */ |
| sat_cntrl->er_base = min(sat_cntrl->er_base, mhi_dev->dl_event_id); |
| sat_cntrl->er_max = max(sat_cntrl->er_base, mhi_dev->dl_event_id); |
| |
| sat_dev = devm_kzalloc(&mhi_dev->dev, sizeof(*sat_dev), GFP_KERNEL); |
| if (!sat_dev) |
| return -ENOMEM; |
| |
| sat_dev->mhi_dev = mhi_dev; |
| sat_dev->cntrl = sat_cntrl; |
| |
| mutex_lock(&sat_cntrl->list_mutex); |
| list_add(&sat_dev->node, &sat_cntrl->dev_list); |
| mutex_unlock(&sat_cntrl->list_mutex); |
| |
| mhi_device_set_devdata(mhi_dev, sat_dev); |
| |
| sat_cntrl->num_devices++; |
| |
| /* schedule connect worker if all devices for controller have probed */ |
| if (sat_cntrl->num_devices == sat_cntrl->max_devices) { |
| /* number of event rings is 1 more than difference in IDs */ |
| sat_cntrl->num_er = (sat_cntrl->er_max - sat_cntrl->er_base) + |
| 1; |
| MHI_SAT_LOG("All satellite channels probed!\n"); |
| schedule_work(&sat_cntrl->connect_work); |
| } |
| |
| return 0; |
| } |
| |
| /* .driver_data stores subsys id */ |
| static const struct mhi_device_id mhi_sat_dev_match_table[] = { |
| /* ADSP */ |
| { .chan = "ADSP_0", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_1", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_2", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_3", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_4", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_5", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_6", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_7", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_8", .driver_data = SUBSYS_ADSP }, |
| { .chan = "ADSP_9", .driver_data = SUBSYS_ADSP }, |
| /* SLPI */ |
| { .chan = "SLPI_0", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_1", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_2", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_3", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_4", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_5", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_6", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_7", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_8", .driver_data = SUBSYS_SLPI }, |
| { .chan = "SLPI_9", .driver_data = SUBSYS_SLPI }, |
| {}, |
| }; |
| |
| static struct mhi_driver mhi_sat_dev_driver = { |
| .id_table = mhi_sat_dev_match_table, |
| .probe = mhi_sat_dev_probe, |
| .remove = mhi_sat_dev_remove, |
| .status_cb = mhi_sat_dev_status_cb, |
| .driver = { |
| .name = MHI_SAT_DRIVER_NAME, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int mhi_sat_init(void) |
| { |
| struct mhi_sat_subsys *subsys; |
| int i, ret; |
| |
| subsys = kcalloc(SUBSYS_MAX, sizeof(*subsys), GFP_KERNEL); |
| if (!subsys) |
| return -ENOMEM; |
| |
| mhi_sat_driver.subsys = subsys; |
| mhi_sat_driver.num_subsys = SUBSYS_MAX; |
| mhi_sat_driver.klog_lvl = KLOG_LVL; |
| mhi_sat_driver.ipc_log_lvl = IPC_LOG_LVL; |
| |
| for (i = 0; i < mhi_sat_driver.num_subsys; i++, subsys++) { |
| char log[32]; |
| |
| subsys->name = subsys_names[i]; |
| mutex_init(&subsys->cntrl_mutex); |
| spin_lock_init(&subsys->cntrl_lock); |
| INIT_LIST_HEAD(&subsys->cntrl_list); |
| scnprintf(log, sizeof(log), "mhi_sat_%s", subsys->name); |
| subsys->ipc_log = ipc_log_context_create(IPC_LOG_PAGES, log, 0); |
| } |
| |
| ret = register_rpmsg_driver(&mhi_sat_rpmsg_driver); |
| if (ret) |
| goto error_sat_init; |
| |
| ret = mhi_driver_register(&mhi_sat_dev_driver); |
| if (ret) |
| goto error_sat_register; |
| |
| return 0; |
| |
| error_sat_register: |
| unregister_rpmsg_driver(&mhi_sat_rpmsg_driver); |
| |
| error_sat_init: |
| subsys = mhi_sat_driver.subsys; |
| for (i = 0; i < mhi_sat_driver.num_subsys; i++, subsys++) { |
| ipc_log_context_destroy(subsys->ipc_log); |
| mutex_destroy(&subsys->cntrl_mutex); |
| } |
| kfree(mhi_sat_driver.subsys); |
| mhi_sat_driver.subsys = NULL; |
| |
| return ret; |
| } |
| |
| module_init(mhi_sat_init); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("MHI_SATELLITE"); |
| MODULE_DESCRIPTION("MHI SATELLITE DRIVER"); |