// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAILBOX_PHY_ADDR 0x90d80000 #define OTA_PHY_ADDR 0x90e00000 #define SAIL_S1_MAGIC 0xD0000000U #define SAIL_S2_MAGIC 0xD0020000U #define SAIL_SUSPEND_MAGIC 0xD0030000U #define SAIL_IPC_SIGNAL BIT(23) #define SAIL_SUSPEND_ACK_TIMEOUT_MS 2000 #define SAIL_HANDSHAKE_DEFAULT_DELAY_US 50000 struct mmap_data { unsigned long mmap_phy_addr; unsigned long mmap_size; }; struct sail_irq_data { unsigned int irq; unsigned int client_id; unsigned int signal_id; char *irq_name; struct eventfd_ctx *event_fd_sig; }; struct sail_mbox_data { struct mbox_client mbox_client; struct mbox_chan *mchan; unsigned int client_id; unsigned int signal_id; }; struct sail_mailbox { struct device *dev; struct cdev sailmb_cdev; struct class *sailmb_driver_class; int sailmb_dev_major; dev_t sailmb_dev_num; wait_queue_head_t event; struct mutex dev_lock; struct sail_mbox_data *mbox_data; struct sail_irq_data *irq_data; int mbox_count; int irq_count; struct mmap_data pr_data; bool mmap_ioctl_set; void __iomem *req_tcsr_reg; void __iomem *resp_tcsr_reg; void __iomem *ipc_reg; unsigned int sail_handshake_delay; }; static struct sail_mailbox *sailmb_dev; static struct mbox_chan *get_mchan_from_signal(const struct sail_mbox_data *mbox_data, const int mbox_count, const unsigned int sig) { int i; for (i = 0; i < mbox_count; i++) { if (mbox_data[i].signal_id == sig) return mbox_data[i].mchan; } return NULL; } static int sail_send_interrupt(struct sail_mailbox *sailmb_dev, const unsigned int sig) { int ret; struct mbox_chan *mchan; mchan = get_mchan_from_signal(sailmb_dev->mbox_data, sailmb_dev->mbox_count, sig); if (mchan == NULL) { dev_err(sailmb_dev->dev, "unable to find mchan corresponding to signal %d\n", sig); return -EFAULT; } ret = mbox_send_message(mchan, NULL); if (ret < 0) { dev_err(sailmb_dev->dev, "mbox send message failed for signal %d, err=%d\n", sig, ret); return ret; } mbox_client_txdone(mchan, 0); return 0; } static int get_irq_from_signal(const struct sail_irq_data *irq_data, const int irq_count, unsigned int signal, unsigned int *irq) { int i; for (i = 0; i < irq_count; i++) { if (irq_data[i].signal_id == signal) { *irq = irq_data[i].irq; return 0; } } return -ENOENT; } static int set_eventfd_for_signal(struct sail_irq_data *irq_data, const int irq_count, const unsigned int signal, int32_t *event_fd) { int i, ret; for (i = 0; i < irq_count; i++) { if (irq_data[i].signal_id == signal) { irq_data[i].event_fd_sig = eventfd_ctx_fdget(*event_fd); if (IS_ERR(irq_data[i].event_fd_sig)) { ret = (int) PTR_ERR(irq_data[i].event_fd_sig); irq_data[i].event_fd_sig = NULL; return ret; } return 0; } } return -ENOENT; } static int release_eventfd_for_signal(struct sail_irq_data *irq_data, const int irq_count, const unsigned int signal) { int i; for (i = 0; i < irq_count; i++) { if ((irq_data[i].signal_id == signal) && (irq_data[i].event_fd_sig)) { eventfd_ctx_put(irq_data[i].event_fd_sig); irq_data[i].event_fd_sig = NULL; return 0; } } return -ENOENT; } static struct eventfd_ctx *get_eventfd_from_irq(const unsigned int irq) { int i; struct sail_irq_data *irq_data = sailmb_dev->irq_data; for (i = 0; i < sailmb_dev->irq_count; i++) { if (irq_data[i].irq == irq) return irq_data[i].event_fd_sig; } return NULL; } static irqreturn_t sailmb_intr_handler(int irq, void *data) { struct eventfd_ctx *event_fd; event_fd = get_eventfd_from_irq(irq); if (event_fd == NULL) { dev_err(sailmb_dev->dev, "could not retrieve eventfd corresponding to %d\n", irq); return IRQ_NONE; } eventfd_signal(event_fd, 1); return IRQ_HANDLED; } static long sail_mb_ioctl_call(struct file *f, unsigned int cmd, unsigned long arg) { int ret = 0; switch (cmd) { case SEND_INTERRUPT: { struct sailmb_data data; mutex_lock(&sailmb_dev->dev_lock); if (copy_from_user(&data, (struct sailmb_data *)arg, sizeof(struct sailmb_data))) { dev_err(sailmb_dev->dev, "copy from user failed, cmd=%x\n", cmd); mutex_unlock(&sailmb_dev->dev_lock); return -EFAULT; } if (data.sailmb_client.tx_mode == 1) { dev_dbg(sailmb_dev->dev, "sending irq %d\n", data.sailmb_client.signal_num); ret = sail_send_interrupt(sailmb_dev, data.sailmb_client.signal_num); if (ret < 0) { mutex_unlock(&sailmb_dev->dev_lock); return ret; } } else { ret = -EPERM; dev_err(sailmb_dev->dev, "send interrupt not permitted on RX channel\n"); } mutex_unlock(&sailmb_dev->dev_lock); break; } case SET_EVENT_FD: { unsigned int irq; struct sailmb_data data; mutex_lock(&sailmb_dev->dev_lock); if (copy_from_user(&data, (struct sailmb_data *)arg, sizeof(struct sailmb_data))) { dev_err(sailmb_dev->dev, "copy from user failed, cmd=%x\n", cmd); mutex_unlock(&sailmb_dev->dev_lock); return -EFAULT; } if (data.sailmb_client.rx_mode == 1) { dev_dbg(sailmb_dev->dev, "setting eventfd for irq %d\n", data.sailmb_client.signal_num); ret = set_eventfd_for_signal(sailmb_dev->irq_data, sailmb_dev->irq_count, data.sailmb_client.signal_num, &data.sailmb_client.event_fd); if (ret) { dev_err(sailmb_dev->dev, "set eventfd failed %d\n", ret); mutex_unlock(&sailmb_dev->dev_lock); return ret; } dev_dbg(sailmb_dev->dev, "set eventfd successful, enabling irq\n"); ret = get_irq_from_signal(sailmb_dev->irq_data, sailmb_dev->irq_count, data.sailmb_client.signal_num, &irq); if (ret) { dev_err(sailmb_dev->dev, "error %d getting irq for signal %d\n", ret, data.sailmb_client.signal_num); mutex_unlock(&sailmb_dev->dev_lock); return ret; } enable_irq(irq); } else { ret = -EPERM; dev_err(sailmb_dev->dev, "cannot set eventfd for TX channel\n"); } mutex_unlock(&sailmb_dev->dev_lock); break; } case DISABLE_INTERRUPT: { unsigned int irq; struct sailmb_data data; mutex_lock(&sailmb_dev->dev_lock); if (copy_from_user(&data, (struct sailmb_data *)arg, sizeof(struct sailmb_data))) { dev_err(sailmb_dev->dev, "copy from user failed\n"); mutex_unlock(&sailmb_dev->dev_lock); return -EFAULT; } if (data.sailmb_client.rx_mode == 1) { ret = get_irq_from_signal(sailmb_dev->irq_data, sailmb_dev->irq_count, data.sailmb_client.signal_num, &irq); if (ret) { dev_err(sailmb_dev->dev, "error %d getting irq for signal %d\n", ret, data.sailmb_client.signal_num); mutex_unlock(&sailmb_dev->dev_lock); return ret; } ret = release_eventfd_for_signal(sailmb_dev->irq_data, sailmb_dev->irq_count, data.sailmb_client.signal_num); if (ret) { dev_err(sailmb_dev->dev, "failed to reset eventfd %d\n", ret); mutex_unlock(&sailmb_dev->dev_lock); return ret; } disable_irq(irq); } else { ret = -EPERM; dev_err(sailmb_dev->dev, "cannot disable interrupt for TX channel\n"); } mutex_unlock(&sailmb_dev->dev_lock); break; } case SET_MAILBOX_ADDR: { mutex_lock(&sailmb_dev->dev_lock); if (sailmb_dev->mmap_ioctl_set) { dev_err(sailmb_dev->dev, "invalid driver state to perform mmap\n"); mutex_unlock(&sailmb_dev->dev_lock); return -EPERM; } sailmb_dev->pr_data.mmap_phy_addr = MAILBOX_PHY_ADDR; sailmb_dev->pr_data.mmap_size = MAILBOX_SIZE; sailmb_dev->mmap_ioctl_set = true; mutex_unlock(&sailmb_dev->dev_lock); break; } case SET_OTA_ADDR: { mutex_lock(&sailmb_dev->dev_lock); if (sailmb_dev->mmap_ioctl_set) { dev_err(sailmb_dev->dev, "invalid driver state to perform mmap\n"); mutex_unlock(&sailmb_dev->dev_lock); return -EPERM; } sailmb_dev->pr_data.mmap_phy_addr = OTA_PHY_ADDR; sailmb_dev->pr_data.mmap_size = OTA_SIZE; sailmb_dev->mmap_ioctl_set = true; mutex_unlock(&sailmb_dev->dev_lock); break; } default: dev_err(sailmb_dev->dev, "invalid ioctl call, cmd=%x\n", cmd); ret = -EPERM; } return ret; } static int sail_mb_mmap(struct file *f, struct vm_area_struct *vma) { int ret; unsigned long phy_addr; unsigned long size; unsigned long page_offset; mutex_lock(&sailmb_dev->dev_lock); phy_addr = sailmb_dev->pr_data.mmap_phy_addr; size = sailmb_dev->pr_data.mmap_size; page_offset = phy_addr & ~PAGE_MASK; if (!sailmb_dev->mmap_ioctl_set) { dev_err(sailmb_dev->dev, "invalid driver state to perform mmap\n"); mutex_unlock(&sailmb_dev->dev_lock); return -EPERM; } if (page_offset) { phy_addr &= PAGE_MASK; phy_addr += PAGE_SIZE; size += page_offset; } if (size > sailmb_dev->pr_data.mmap_size) dev_err(sailmb_dev->dev, "requested size 0x%x greater than mmap_size 0x%x\n", size, sailmb_dev->pr_data.mmap_size); vma->vm_page_prot = pgprot_writecombine(pgprot_noncached(vma->vm_page_prot)); ret = remap_pfn_range(vma, vma->vm_start, phy_addr >> PAGE_SHIFT, size, vma->vm_page_prot); if (ret) { dev_err(sailmb_dev->dev, "error while remapping %d\n", ret); mutex_unlock(&sailmb_dev->dev_lock); return ret; } sailmb_dev->mmap_ioctl_set = false; mutex_unlock(&sailmb_dev->dev_lock); return 0; } static const struct file_operations sailmb_fops = { .unlocked_ioctl = sail_mb_ioctl_call, .mmap = sail_mb_mmap, }; static int sail_handshake(void __iomem *tcsr_reg, void __iomem *ipc_reg, const unsigned int magic) { int tcsr_write_value; unsigned int i; for (i = 0; i < 4; i++) { tcsr_write_value = (((magic & (0xFFU << (8U * i))) >> (8U * i)) & 0xFFU); writel_relaxed(tcsr_write_value, tcsr_reg); tcsr_reg = tcsr_reg + 4; } isb(); /* ensure TSCR register writes go through before triggering IPC to SAIL */ mb(); writel_relaxed(SAIL_IPC_SIGNAL, ipc_reg); return 0; } static int do_sailmb_init_handshakes(void __iomem *tcsr_reg, void __iomem *ipc_reg) { int ret; ret = sail_handshake(tcsr_reg, ipc_reg, SAIL_S1_MAGIC); if (ret) { dev_err(sailmb_dev->dev, "sail_s1_handshake returned error %d\n", ret); return ret; } if (sailmb_dev->sail_handshake_delay <= 20000) usleep_range(sailmb_dev->sail_handshake_delay-100, sailmb_dev->sail_handshake_delay); else msleep(sailmb_dev->sail_handshake_delay/1000); ret = sail_handshake(tcsr_reg, ipc_reg, SAIL_S2_MAGIC); if (ret) { dev_err(sailmb_dev->dev, "sail_s2_handshake returned error %d\n", ret); return ret; } return ret; } static int sail_suspend_ack(void __iomem *tcsr_reg) { unsigned int i; unsigned int magic = 0U; for (i = 0; i < SAIL_SUSPEND_ACK_TIMEOUT_MS; i++) { magic = 0U; magic |= readl_relaxed(tcsr_reg) & (0xFFU); magic |= (readl_relaxed(tcsr_reg + 4) & (0xFFU)) << 8U; magic |= (readl_relaxed(tcsr_reg + 8) & (0xFFU)) << 16U; magic |= (readl_relaxed(tcsr_reg + 12) & (0xFFU)) << 24U; if (magic == SAIL_SUSPEND_MAGIC) { dev_dbg(sailmb_dev->dev, "magic number matched\n"); return 0; } usleep_range(900, 1000); } return -ETIMEDOUT; } static int sail_suspend_handshake(void __iomem *req_tcsr_reg, void __iomem *ipc_reg, void __iomem *resp_tcsr_reg) { int ret; ret = sail_handshake(req_tcsr_reg, ipc_reg, SAIL_SUSPEND_MAGIC); if (ret) { dev_err(sailmb_dev->dev, "sail suspend handshake returned error %d\n", ret); return ret; } ret = sail_suspend_ack(resp_tcsr_reg); if (ret) { dev_err(sailmb_dev->dev, "sail failed to acknowledge suspend call\n"); return ret; } return 0; } static int populate_irq_from_dt(struct platform_device *pdev, struct sail_mailbox *sailmb_dev) { int i; int ret; int count = 0; const char *intr_name = "sailmb_irq"; const size_t irq_name_size = strlen(intr_name) + 1; sailmb_dev->irq_count = of_property_count_elems_of_size(pdev->dev.of_node, "interrupts", (3 * sizeof(u32))); if (sailmb_dev->irq_count <= 0) { dev_err(sailmb_dev->dev, "invalid interrupt count %d\n", sailmb_dev->irq_count); return -EFAULT; } sailmb_dev->irq_data = devm_kzalloc(&pdev->dev, sailmb_dev->irq_count * sizeof(*sailmb_dev->irq_data), GFP_KERNEL); if (!sailmb_dev->irq_data) return -ENOMEM; for (i = 0; i < (3 * sailmb_dev->irq_count); i += 3) { ret = of_property_read_u32_index(pdev->dev.of_node, "interrupts", i, &sailmb_dev->irq_data[count].client_id); if (ret) { dev_err(sailmb_dev->dev, "failed to get client_id for irq %d, err=%d\n", count, ret); return ret; } ret = of_property_read_u32_index(pdev->dev.of_node, "interrupts", i+1, &sailmb_dev->irq_data[count].signal_id); if (ret) { dev_err(sailmb_dev->dev, "failed to get signal_id for irq %d, err=%d\n", count, ret); return ret; } sailmb_dev->irq_data[count].irq = platform_get_irq(pdev, count); sailmb_dev->irq_data[count].irq_name = devm_kzalloc(&pdev->dev, irq_name_size, GFP_KERNEL); strscpy(sailmb_dev->irq_data[count].irq_name, intr_name, irq_name_size); dev_dbg(sailmb_dev->dev, "requesting irq %d\n", sailmb_dev->irq_data[count].irq); ret = devm_request_irq(&pdev->dev, sailmb_dev->irq_data[count].irq, sailmb_intr_handler, 0, sailmb_dev->irq_data[count].irq_name, sailmb_dev); if (ret) { dev_err(sailmb_dev->dev, "failed to request interrupt for irq %d, err=%d\n", sailmb_dev->irq_data[count].irq, ret); return ret; } disable_irq_nosync(sailmb_dev->irq_data[count].irq); count++; } dev_dbg(sailmb_dev->dev, "irq initialization successful\n"); return 0; } static int populate_mbox_from_dt(struct platform_device *pdev, struct sail_mailbox *sailmb_dev) { int i; int ret; int count = 0; sailmb_dev->mbox_count = of_property_count_elems_of_size(pdev->dev.of_node, "mboxes", (3 * sizeof(u32))); if (sailmb_dev->mbox_count <= 0) { dev_err(sailmb_dev->dev, "invalid mbox count %d\n", sailmb_dev->mbox_count); return -EFAULT; } sailmb_dev->mbox_data = devm_kzalloc(&pdev->dev, sailmb_dev->mbox_count * sizeof(*sailmb_dev->mbox_data), GFP_KERNEL); if (!sailmb_dev->mbox_data) return -ENOMEM; for (i = 1; i < (3 * sailmb_dev->mbox_count); i += 3) { ret = of_property_read_u32_index(pdev->dev.of_node, "mboxes", i, &sailmb_dev->mbox_data[count].client_id); if (ret) { dev_err(sailmb_dev->dev, "failed to get client_id for mbox %d, err=%d\n", count, ret); return ret; } ret = of_property_read_u32_index(pdev->dev.of_node, "mboxes", i+1, &sailmb_dev->mbox_data[count].signal_id); if (ret) { dev_err(sailmb_dev->dev, "failed to get signal_id for mbox %d, err=%d\n", count, ret); return ret; } dev_dbg(&pdev->dev, "requesting mbox %d\n", count); sailmb_dev->mbox_data[count].mbox_client.dev = &pdev->dev; sailmb_dev->mbox_data[count].mbox_client.knows_txdone = true; sailmb_dev->mbox_data[count].mchan = mbox_request_channel(&sailmb_dev->mbox_data[count].mbox_client, count); if (IS_ERR(sailmb_dev->mbox_data[count].mchan)) { dev_err(sailmb_dev->dev, "failed to get ipc mailbox %ld\n", PTR_ERR(sailmb_dev->mbox_data[count].mchan)); ret = PTR_ERR(sailmb_dev->mbox_data[count].mchan); return ret; } if (sailmb_dev->mbox_data[count].mchan == NULL) { dev_err(sailmb_dev->dev, "mailbox mchan is NULL\n"); return -EINVAL; } count++; } dev_dbg(sailmb_dev->dev, "mbox initialization successful\n"); return 0; } static int sailmb_probe(struct platform_device *pdev) { int ret; struct device *dev; struct resource *req_tcsr_res; struct resource *resp_tcsr_res; struct resource *ipc_res; unsigned int delay; sailmb_dev = devm_kzalloc(&pdev->dev, sizeof(*sailmb_dev), GFP_KERNEL); if (!sailmb_dev) return -ENOMEM; sailmb_dev->dev = &pdev->dev; req_tcsr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!req_tcsr_res) { dev_err(sailmb_dev->dev, "failed to acquire tcsr io resource\n"); return -EINVAL; } sailmb_dev->req_tcsr_reg = devm_ioremap_resource(&pdev->dev, req_tcsr_res); if (IS_ERR(sailmb_dev->req_tcsr_reg)) { dev_err(sailmb_dev->dev, "ioremap of tcsr register failed\n"); return PTR_ERR(sailmb_dev->req_tcsr_reg); } resp_tcsr_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!resp_tcsr_res) { dev_err(sailmb_dev->dev, "failed to acquire tcsr io resource\n"); return -EINVAL; } sailmb_dev->resp_tcsr_reg = devm_ioremap_resource(&pdev->dev, resp_tcsr_res); if (IS_ERR(sailmb_dev->resp_tcsr_reg)) { dev_err(sailmb_dev->dev, "ioremap of tcsr register failed\n"); return PTR_ERR(sailmb_dev->resp_tcsr_reg); } ipc_res = platform_get_resource(pdev, IORESOURCE_MEM, 2); if (!ipc_res) { dev_err(sailmb_dev->dev, "failed to acquire ipc io resource\n"); return -EINVAL; } sailmb_dev->ipc_reg = devm_ioremap_resource(&pdev->dev, ipc_res); if (IS_ERR(sailmb_dev->ipc_reg)) { dev_err(sailmb_dev->dev, "Ioremap of ipc register failed\n"); return PTR_ERR(sailmb_dev->ipc_reg); } ret = alloc_chrdev_region(&sailmb_dev->sailmb_dev_num, 0, 1, "sailMB"); if (ret) { dev_err(sailmb_dev->dev, "failed to alloc char dev for the driver %d\n", ret); goto failed_chrdev_alloc; } cdev_init(&sailmb_dev->sailmb_cdev, &sailmb_fops); ret = cdev_add(&sailmb_dev->sailmb_cdev, sailmb_dev->sailmb_dev_num, 1); if (ret) { dev_err(sailmb_dev->dev, "char device driver add failed %d\n", ret); goto failed_cdev_add; } sailmb_dev->sailmb_dev_major = MAJOR(sailmb_dev->sailmb_dev_num); sailmb_dev->sailmb_driver_class = class_create(THIS_MODULE, "sailMB"); if (IS_ERR_OR_NULL(sailmb_dev->sailmb_driver_class)) { dev_err(sailmb_dev->dev, "char device driver class create error\n"); ret = -EFAULT; goto failed_class_create; } dev = device_create(sailmb_dev->sailmb_driver_class, NULL, sailmb_dev->sailmb_dev_num, NULL, "sailMB"); if (IS_ERR_OR_NULL(dev)) { dev_err(sailmb_dev->dev, "device create error\n"); ret = -EFAULT; goto failed_device_create; } ret = populate_mbox_from_dt(pdev, sailmb_dev); if (ret) goto out; ret = populate_irq_from_dt(pdev, sailmb_dev); if (ret) goto out; mutex_init(&sailmb_dev->dev_lock); ret = of_property_read_u32(sailmb_dev->dev->of_node, "sail-handshake-delay", &delay); if (ret) sailmb_dev->sail_handshake_delay = SAIL_HANDSHAKE_DEFAULT_DELAY_US; else sailmb_dev->sail_handshake_delay = delay; if (sailmb_dev->sail_handshake_delay < 100) sailmb_dev->sail_handshake_delay = 100; ret = do_sailmb_init_handshakes(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg); if (ret) { dev_err(sailmb_dev->dev, "sail-mailbox handshake failed %d\n", ret); goto out; } dev_dbg(sailmb_dev->dev, "sail mailbox ready\n"); return 0; out: device_destroy(sailmb_dev->sailmb_driver_class, sailmb_dev->sailmb_dev_num); failed_device_create: class_destroy(sailmb_dev->sailmb_driver_class); failed_class_create: cdev_del(&sailmb_dev->sailmb_cdev); failed_cdev_add: unregister_chrdev_region(sailmb_dev->sailmb_dev_num, 1); failed_chrdev_alloc: return ret; } static int sailmb_remove(struct platform_device *pdev) { device_destroy(sailmb_dev->sailmb_driver_class, sailmb_dev->sailmb_dev_num); class_destroy(sailmb_dev->sailmb_driver_class); cdev_del(&sailmb_dev->sailmb_cdev); unregister_chrdev_region(sailmb_dev->sailmb_dev_num, 1); return 0; } static int sailmb_suspend(struct device *dev) { int ret; ret = sail_suspend_handshake(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg, sailmb_dev->resp_tcsr_reg); if (ret) { dev_err(sailmb_dev->dev, "sail_suspend_handshake returned error %d\n", ret); goto suspend_fail; } return 0; suspend_fail: do_sailmb_init_handshakes(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg); return ret; } static int sailmb_resume(struct device *dev) { int ret; dev_dbg(sailmb_dev->dev, "sail-mailbox resuming, sending handshake signals\n"); ret = do_sailmb_init_handshakes(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg); return ret; } static const struct dev_pm_ops sailmb_pm_ops = { .suspend = sailmb_suspend, .resume = sailmb_resume, }; static const struct of_device_id sailmb_dt_match[] = { { .compatible = "qcom,sail-mailbox" }, {} }; MODULE_DEVICE_TABLE(of, sailmb_dt_match); static struct platform_driver sailmb_driver = { .driver = { .name = "sail_mailbox", .of_match_table = sailmb_dt_match, .suppress_bind_attrs = true, .pm = &sailmb_pm_ops, }, .probe = sailmb_probe, .remove = sailmb_remove, }; module_platform_driver(sailmb_driver); MODULE_DESCRIPTION("Qualcomm Technologies, Inc. sail mailbox driver"); MODULE_LICENSE("GPL");