// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "wcd_spi_ctl_v01.h" #define WCD_SPI_AC_PFS_ENTRY_MAX_LEN 16 #define WCD_SPI_AC_WRITE_CMD_MIN_SIZE \ (sizeof(struct wcd_spi_ac_write_cmd)) #define WCD_SPI_AC_WRITE_CMD_MAX_SIZE \ (WCD_SPI_AC_WRITE_CMD_MIN_SIZE + \ (WCD_SPI_AC_MAX_BUFFERS * \ sizeof(struct wcd_spi_ac_buf_data))) #define WCD_SPI_AC_MUTEX_LOCK(dev, lock) \ { \ dev_dbg(dev, "%s: mutex_lock(%s)\n", \ __func__, __stringify_1(lock)); \ mutex_lock(&lock); \ } #define WCD_SPI_AC_MUTEX_UNLOCK(dev, lock) \ { \ dev_dbg(dev, "%s: mutex_unlock(%s)\n", \ __func__, __stringify_1(lock)); \ mutex_unlock(&lock); \ } /* * All bits of status should be cleared for SPI access * to be released. */ #define WCD_SPI_AC_STATUS_RELEASE_ACCESS 0x00 #define WCD_SPI_AC_LOCAL_ACCESS 0x00 #define WCD_SPI_AC_REMOTE_ACCESS 0x01 #define WCD_SPI_CTL_INS_ID 0 #define WCD_SPI_AC_QMI_TIMEOUT_MS 100 struct wcd_spi_ac_priv { /* Pointer to device for this driver */ struct device *dev; /* Pointer to parent's device */ struct device *parent; /* char dev related */ struct class *cls; struct device *chardev; struct cdev cdev; dev_t cdev_num; /* proc entry related */ struct proc_dir_entry *pfs_root; struct proc_dir_entry *pfs_status; /* service status related */ u8 svc_offline; u8 svc_offline_change; wait_queue_head_t svc_poll_wait; struct mutex status_lock; /* state maintenence related */ u32 state; struct mutex state_lock; u8 current_access; /* qmi related */ struct qmi_handle *qmi_hdl; struct work_struct svc_arr_work; struct work_struct svc_exit_work; struct notifier_block nb; struct mutex svc_lock; struct workqueue_struct *qmi_wq; struct work_struct recv_msg_work; }; static void wcd_spi_ac_status_change(struct wcd_spi_ac_priv *ac, u8 online) { WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock); ac->svc_offline = !online; /* Make sure the write is complete */ wmb(); xchg(&ac->svc_offline_change, 1); wake_up_interruptible(&ac->svc_poll_wait); dev_dbg(ac->dev, "%s request %u offline %u off_change %u\n", __func__, online, ac->svc_offline, ac->svc_offline_change); WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock); } static int wcd_spi_ac_status_open(struct inode *inode, struct file *file) { struct wcd_spi_ac_priv *ac = PDE_DATA(inode); file->private_data = ac; return 0; } static ssize_t wcd_spi_ac_status_read(struct file *file, char __user *buffer, size_t count, loff_t *offset) { struct wcd_spi_ac_priv *ac; char buf[WCD_SPI_AC_PFS_ENTRY_MAX_LEN]; int len, ret; u8 offline; ac = (struct wcd_spi_ac_priv *) file->private_data; if (!ac) { pr_err("%s: Invalid private data for status\n", __func__); return -EINVAL; } WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock); offline = ac->svc_offline; /* Make sure the read is complete */ rmb(); dev_dbg(ac->dev, "%s: offline = %sline\n", __func__, offline ? "off" : "on"); len = snprintf(buf, sizeof(buf), "%s\n", offline ? "OFFLINE" : "ONLINE"); ret = simple_read_from_buffer(buffer, count, offset, buf, len); WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock); return ret; } static unsigned int wcd_spi_ac_status_poll(struct file *file, poll_table *wait) { struct wcd_spi_ac_priv *ac; unsigned int ret = 0; ac = (struct wcd_spi_ac_priv *) file->private_data; if (!ac) { pr_err("%s: Invalid private data for status\n", __func__); return -EINVAL; } dev_dbg(ac->dev, "%s: Poll wait, svc = %s\n", __func__, ac->svc_offline ? "offline" : "online"); poll_wait(file, &ac->svc_poll_wait, wait); dev_dbg(ac->dev, "%s: Woken up Poll wait, svc = %s\n", __func__, ac->svc_offline ? "offline" : "online"); WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock); if (xchg(&ac->svc_offline_change, 0)) ret = POLLIN | POLLPRI | POLLRDNORM; dev_dbg(ac->dev, "%s: ret (%d) from poll_wait\n", __func__, ret); WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock); return ret; } static const struct file_operations wcd_spi_ac_status_ops = { .owner = THIS_MODULE, .open = wcd_spi_ac_status_open, .read = wcd_spi_ac_status_read, .poll = wcd_spi_ac_status_poll, }; static int wcd_spi_ac_procfs_init(struct wcd_spi_ac_priv *ac) { int ret = 0; ac->pfs_root = proc_mkdir(WCD_SPI_AC_PROCFS_DIR_NAME, NULL); if (!ac->pfs_root) { dev_err(ac->dev, "%s: proc_mkdir failed\n", __func__); return -EINVAL; } ac->pfs_status = proc_create_data(WCD_SPI_AC_PROCFS_STATE_NAME, 0444, ac->pfs_root, &wcd_spi_ac_status_ops, ac); if (!ac->pfs_status) { dev_err(ac->dev, "%s: proc_create_data failed\n", __func__); ret = -EINVAL; goto rmdir_root; } proc_set_size(ac->pfs_status, WCD_SPI_AC_PFS_ENTRY_MAX_LEN); return 0; rmdir_root: proc_remove(ac->pfs_root); return ret; } static void wcd_spi_ac_procfs_deinit(struct wcd_spi_ac_priv *ac) { proc_remove(ac->pfs_status); proc_remove(ac->pfs_root); } static int wcd_spi_ac_request_access(struct wcd_spi_ac_priv *ac, bool is_svc_locked) { struct wcd_spi_req_access_msg_v01 req; struct wcd_spi_req_access_resp_v01 rsp; struct msg_desc req_desc, rsp_desc; int ret = 0; dev_dbg(ac->dev, "%s: is_svc_locked = %s\n", __func__, is_svc_locked ? "true" : "false"); memset(&req, 0, sizeof(req)); memset(&rsp, 0, sizeof(rsp)); req.reason_valid = 1; req.reason = ac->state & 0x03; req_desc.max_msg_len = WCD_SPI_REQ_ACCESS_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = WCD_SPI_REQ_ACCESS_MSG_V01; req_desc.ei_array = wcd_spi_req_access_msg_v01_ei; rsp_desc.max_msg_len = WCD_SPI_REQ_ACCESS_RESP_V01_MAX_MSG_LEN; rsp_desc.msg_id = WCD_SPI_REQ_ACCESS_RESP_V01; rsp_desc.ei_array = wcd_spi_req_access_resp_v01_ei; if (!is_svc_locked) WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock); ret = qmi_send_req_wait(ac->qmi_hdl, &req_desc, &req, sizeof(req), &rsp_desc, &rsp, sizeof(rsp), WCD_SPI_AC_QMI_TIMEOUT_MS); if (ret) { dev_err(ac->dev, "%s: msg send failed %d\n", __func__, ret); goto done; } if (rsp.resp.result != QMI_RESULT_SUCCESS_V01) { ret = -EIO; dev_err(ac->dev, "%s: qmi resp error %d\n", __func__, rsp.resp.result); } done: if (!is_svc_locked) WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock); return ret; } static int wcd_spi_ac_release_access(struct wcd_spi_ac_priv *ac, bool is_svc_locked) { struct wcd_spi_rel_access_msg_v01 req; struct wcd_spi_rel_access_resp_v01 rsp; struct msg_desc req_desc, rsp_desc; int ret = 0; dev_dbg(ac->dev, "%s: is_svc_locked = %s\n", __func__, is_svc_locked ? "true" : "false"); memset(&req, 0, sizeof(req)); memset(&rsp, 0, sizeof(rsp)); req_desc.max_msg_len = WCD_SPI_REL_ACCESS_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = WCD_SPI_REL_ACCESS_MSG_V01; req_desc.ei_array = wcd_spi_rel_access_msg_v01_ei; rsp_desc.max_msg_len = WCD_SPI_REL_ACCESS_RESP_V01_MAX_MSG_LEN; rsp_desc.msg_id = WCD_SPI_REL_ACCESS_RESP_V01; rsp_desc.ei_array = wcd_spi_rel_access_resp_v01_ei; if (!is_svc_locked) WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock); ret = qmi_send_req_wait(ac->qmi_hdl, &req_desc, &req, sizeof(req), &rsp_desc, &rsp, sizeof(rsp), WCD_SPI_AC_QMI_TIMEOUT_MS); if (ret) { dev_err(ac->dev, "%s: msg send failed %d\n", __func__, ret); goto done; } if (rsp.resp.result != QMI_RESULT_SUCCESS_V01) { ret = -EIO; dev_err(ac->dev, "%s: qmi resp error %d\n", __func__, rsp.resp.result); } done: if (!is_svc_locked) WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock); return ret; } static int wcd_spi_ac_buf_msg( struct wcd_spi_ac_priv *ac, u8 *data, int data_sz) { struct wcd_spi_ac_buf_data *buf_data; struct wcd_spi_buff_msg_v01 req; struct wcd_spi_buff_resp_v01 rsp; struct msg_desc req_desc, rsp_desc; int ret = 0; memset(&req, 0, sizeof(req)); memset(&rsp, 0, sizeof(rsp)); buf_data = (struct wcd_spi_ac_buf_data *) data; memcpy(req.buff_addr_1, buf_data, sizeof(*buf_data)); if (data_sz - sizeof(*buf_data) != 0) { req.buff_addr_2_valid = 1; buf_data++; memcpy(req.buff_addr_2, buf_data, sizeof(*buf_data)); } req_desc.max_msg_len = WCD_SPI_BUFF_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = WCD_SPI_BUFF_MSG_V01; req_desc.ei_array = wcd_spi_buff_msg_v01_ei; rsp_desc.max_msg_len = WCD_SPI_BUFF_RESP_V01_MAX_MSG_LEN; rsp_desc.msg_id = WCD_SPI_BUFF_RESP_V01; rsp_desc.ei_array = wcd_spi_buff_resp_v01_ei; WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock); ret = qmi_send_req_wait(ac->qmi_hdl, &req_desc, &req, sizeof(req), &rsp_desc, &rsp, sizeof(rsp), WCD_SPI_AC_QMI_TIMEOUT_MS); if (ret) { dev_err(ac->dev, "%s: msg send failed %d\n", __func__, ret); goto done; } if (rsp.resp.result != QMI_RESULT_SUCCESS_V01) { ret = -EIO; dev_err(ac->dev, "%s: qmi resp error %d\n", __func__, rsp.resp.result); } done: WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock); return ret; } /* * wcd_spi_ac_set_sync: Sets the current status of the SPI * bus and requests access if not * already accesible. * @ac: pointer to the drivers private data * @value: value to be set in the status mask * @is_svc_locked: flag to indicate if svc_lock is acquired by caller */ static int wcd_spi_ac_set_sync(struct wcd_spi_ac_priv *ac, u32 value, bool is_svc_locked) { int ret = 0; WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->state_lock); ac->state |= value; /* any non-zero state indicates us to request SPI access */ wmb(); dev_dbg(ac->dev, "%s: current state = 0x%x, current access 0x%x\n", __func__, ac->state, ac->current_access); if (ac->current_access == WCD_SPI_AC_REMOTE_ACCESS) { dev_dbg(ac->dev, "%s: requesting access, state = 0x%x\n", __func__, ac->state); ret = wcd_spi_ac_request_access(ac, is_svc_locked); if (!ret) ac->current_access = WCD_SPI_AC_LOCAL_ACCESS; } WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->state_lock); return ret; } /* * wcd_spi_ac_clear_sync: Clears the current status of the SPI * bus and releases access if applicable * @ac: pointer to the drivers private data * @value: value to be cleared in the status mask * @is_svc_locked: flag to indicate if svc_lock is acquired by caller */ static int wcd_spi_ac_clear_sync(struct wcd_spi_ac_priv *ac, u32 value, bool is_svc_locked) { int ret = 0; WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->state_lock); ac->state &= ~(value); /* make sure value is written before read */ wmb(); dev_dbg(ac->dev, "%s: current state = 0x%x, current access 0x%x\n", __func__, ac->state, ac->current_access); /* state should be zero to release SPI access */ if (!ac->state && ac->current_access == WCD_SPI_AC_LOCAL_ACCESS) { dev_dbg(ac->dev, "%s: releasing access, state = 0x%x\n", __func__, ac->state); ret = wcd_spi_ac_release_access(ac, is_svc_locked); if (!ret) ac->current_access = WCD_SPI_AC_REMOTE_ACCESS; } WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->state_lock); return ret; } /* * wcd_spi_access_ctl: API to request/release the access * to wcd-spi bus. * @dev: handle to the wcd-spi-ac device * @request: enum to indicate access request or access release * @reason: reason for request/release. Must be one of the * valid reasons. * Returns success if the access handover was sucessful, * negative error code otherwise. */ int wcd_spi_access_ctl(struct device *dev, enum wcd_spi_acc_req request, u32 reason) { struct wcd_spi_ac_priv *ac; int ret = 0; if (!dev) { pr_err("%s: invalid device\n", __func__); return -EINVAL; } /* only data_transfer and remote_down are valid reasons */ if (reason != WCD_SPI_AC_DATA_TRANSFER && reason != WCD_SPI_AC_REMOTE_DOWN) { pr_err("%s: Invalid reason 0x%x\n", __func__, reason); return -EINVAL; } ac = (struct wcd_spi_ac_priv *) dev_get_drvdata(dev); if (!ac) { dev_err(dev, "%s: invalid driver data\n", __func__); return -EINVAL; } dev_dbg(dev, "%s: request = 0x%x, reason = 0x%x\n", __func__, request, reason); switch (request) { case WCD_SPI_ACCESS_REQUEST: ret = wcd_spi_ac_set_sync(ac, reason, false); if (ret) dev_err(dev, "%s: set_sync(0x%x) failed %d\n", __func__, reason, ret); break; case WCD_SPI_ACCESS_RELEASE: ret = wcd_spi_ac_clear_sync(ac, reason, false); if (ret) dev_err(dev, "%s: clear_sync(0x%x) failed %d\n", __func__, reason, ret); break; default: dev_err(dev, "%s: invalid request 0x%x\n", __func__, request); break; } return ret; } EXPORT_SYMBOL(wcd_spi_access_ctl); static int wcd_spi_ac_cdev_open(struct inode *inode, struct file *file) { struct wcd_spi_ac_priv *ac; int ret = 0; ac = container_of(inode->i_cdev, struct wcd_spi_ac_priv, cdev); if (!ac) { pr_err("%s: Invalid private data\n", __func__); return -EINVAL; } WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock); if (ac->svc_offline) { dev_err(ac->dev, "%s: SVC is not online, cannot open driver\n", __func__); ret = -ENODEV; goto done; } file->private_data = ac; done: WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock); return ret; } static ssize_t wcd_spi_ac_cdev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct wcd_spi_ac_priv *ac; struct wcd_spi_ac_write_cmd *cmd_buf; int ret = 0; int data_sz; ac = (struct wcd_spi_ac_priv *) file->private_data; if (!ac) { pr_err("%s: Invalid private data\n", __func__); return -EINVAL; } if (count < WCD_SPI_AC_WRITE_CMD_MIN_SIZE || count > WCD_SPI_AC_WRITE_CMD_MAX_SIZE) { dev_err(ac->dev, "%s: Invalid write count %zd\n", __func__, count); return -EINVAL; } cmd_buf = kzalloc(count, GFP_KERNEL); if (!cmd_buf) return -ENOMEM; if (get_user(cmd_buf->cmd_type, buf)) { dev_err(ac->dev, "%s: get_user failed\n", __func__); ret = -EFAULT; goto free_cmd_buf; } dev_dbg(ac->dev, "%s: write cmd type 0x%x\n", __func__, cmd_buf->cmd_type); switch (cmd_buf->cmd_type) { case WCD_SPI_AC_CMD_CONC_BEGIN: ret = wcd_spi_ac_set_sync(ac, WCD_SPI_AC_CONCURRENCY, false); if (ret) { dev_err(ac->dev, "%s: set_sync(CONC) fail %d\n", __func__, ret); goto free_cmd_buf; } break; case WCD_SPI_AC_CMD_CONC_END: ret = wcd_spi_ac_clear_sync(ac, WCD_SPI_AC_CONCURRENCY, false); if (ret) { dev_err(ac->dev, "%s: clear_sync(CONC) fail %d\n", __func__, ret); goto free_cmd_buf; } break; case WCD_SPI_AC_CMD_BUF_DATA: /* Read the buffer details and send to service */ data_sz = count - sizeof(cmd_buf->cmd_type); if (!data_sz || (data_sz % sizeof(struct wcd_spi_ac_buf_data))) { dev_err(ac->dev, "%s: size %d not multiple of %ld\n", __func__, data_sz, sizeof(struct wcd_spi_ac_buf_data)); goto free_cmd_buf; } if (data_sz / sizeof(struct wcd_spi_ac_buf_data) > WCD_SPI_AC_MAX_BUFFERS) { dev_err(ac->dev, "%s: invalid size %d\n", __func__, data_sz); goto free_cmd_buf; } if (copy_from_user(cmd_buf->payload, buf + sizeof(cmd_buf->cmd_type), data_sz)) { dev_err(ac->dev, "%s: copy_from_user failed\n", __func__); ret = -EFAULT; goto free_cmd_buf; } ret = wcd_spi_ac_buf_msg(ac, cmd_buf->payload, data_sz); if (ret) { dev_err(ac->dev, "%s: _buf_msg failed %d\n", __func__, ret); goto free_cmd_buf; } ret = wcd_spi_ac_clear_sync(ac, WCD_SPI_AC_UNINITIALIZED, false); if (ret) { dev_err(ac->dev, "%s: clear_sync 0x%lx failed %d\n", __func__, WCD_SPI_AC_UNINITIALIZED, ret); goto free_cmd_buf; } break; default: dev_err(ac->dev, "%s: Invalid cmd_type 0x%x\n", __func__, cmd_buf->cmd_type); ret = -EINVAL; goto free_cmd_buf; } free_cmd_buf: kfree(cmd_buf); if (!ret) ret = count; return ret; } static int wcd_spi_ac_cdev_release(struct inode *inode, struct file *file) { struct wcd_spi_ac_priv *ac; int ret = 0; ac = (struct wcd_spi_ac_priv *) file->private_data; if (!ac) { pr_err("%s: Invalid private data\n", __func__); return -EINVAL; } ret = wcd_spi_ac_set_sync(ac, WCD_SPI_AC_UNINITIALIZED, false); if (ret) dev_err(ac->dev, "%s: set_sync(UNINITIALIZED) failed %d\n", __func__, ret); return ret; } static const struct file_operations wcd_spi_ac_cdev_fops = { .owner = THIS_MODULE, .open = wcd_spi_ac_cdev_open, .write = wcd_spi_ac_cdev_write, .release = wcd_spi_ac_cdev_release, }; static int wcd_spi_ac_reg_chardev(struct wcd_spi_ac_priv *ac) { int ret; ret = alloc_chrdev_region(&ac->cdev_num, 0, 1, WCD_SPI_AC_CLIENT_CDEV_NAME); if (ret) { dev_err(ac->dev, "%s: alloc_chrdev_region failed %d\n", __func__, ret); return ret; } ac->cls = class_create(THIS_MODULE, WCD_SPI_AC_CLIENT_CDEV_NAME); if (IS_ERR(ac->cls)) { ret = PTR_ERR(ac->cls); dev_err(ac->dev, "%s: class_create failed %d\n", __func__, ret); goto unregister_chrdev; } ac->chardev = device_create(ac->cls, NULL, ac->cdev_num, NULL, WCD_SPI_AC_CLIENT_CDEV_NAME); if (IS_ERR(ac->chardev)) { ret = PTR_ERR(ac->chardev); dev_err(ac->dev, "%s: device_create failed %d\n", __func__, ret); goto destroy_class; } cdev_init(&ac->cdev, &wcd_spi_ac_cdev_fops); ret = cdev_add(&ac->cdev, ac->cdev_num, 1); if (ret) { dev_err(ac->dev, "%s: cdev_add failed %d\n", __func__, ret); goto destroy_device; } return 0; destroy_device: device_destroy(ac->cls, ac->cdev_num); destroy_class: class_destroy(ac->cls); unregister_chrdev: unregister_chrdev_region(0, 1); return ret; } static int wcd_spi_ac_unreg_chardev(struct wcd_spi_ac_priv *ac) { cdev_del(&ac->cdev); device_destroy(ac->cls, ac->cdev_num); class_destroy(ac->cls); unregister_chrdev_region(0, 1); return 0; } static void wcd_spi_ac_recv_msg(struct work_struct *work) { struct wcd_spi_ac_priv *ac; int rc = 0; ac = container_of(work, struct wcd_spi_ac_priv, recv_msg_work); if (!ac) { pr_err("%s: Invalid private data\n", __func__); return; } do { dev_dbg(ac->dev, "%s: msg received, rc = %d\n", __func__, rc); } while ((rc = qmi_recv_msg(ac->qmi_hdl)) == 0); if (rc != -ENOMSG) dev_err(ac->dev, "%s: qmi_recv_msg failed %d\n", __func__, rc); } static void wcd_spi_ac_clnt_notify(struct qmi_handle *hdl, enum qmi_event_type event, void *priv_data) { struct wcd_spi_ac_priv *ac; if (!priv_data) { pr_err("%s: Invalid private data\n", __func__); return; } ac = (struct wcd_spi_ac_priv *) priv_data; switch (event) { case QMI_RECV_MSG: queue_work(ac->qmi_wq, &ac->recv_msg_work); break; default: break; } } static void wcd_spi_ac_svc_arrive(struct work_struct *work) { struct wcd_spi_ac_priv *ac; int ret; ac = container_of(work, struct wcd_spi_ac_priv, svc_arr_work); if (!ac) { pr_err("%s: Invalid private data\n", __func__); return; } WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock); ac->qmi_hdl = qmi_handle_create(wcd_spi_ac_clnt_notify, ac); if (!ac->qmi_hdl) { dev_err(ac->dev, "%s: qmi_handle_create failed\n", __func__); goto done; } ret = qmi_connect_to_service(ac->qmi_hdl, WCD_SPI_CTL_SERVICE_ID_V01, WCD_SPI_CTL_SERVICE_VERS_V01, WCD_SPI_CTL_INS_ID); if (ret) { dev_err(ac->dev, "%s, cant connect to service, error %d\n", __func__, ret); qmi_handle_destroy(ac->qmi_hdl); ac->qmi_hdl = NULL; goto done; } /* Mark service as online */ wcd_spi_ac_status_change(ac, 1); /* * update the state and clear the WCD_SPI_AC_SVC_OFFLINE * bit to indicate that the service is now online. */ ret = wcd_spi_ac_clear_sync(ac, WCD_SPI_AC_SVC_OFFLINE, true); if (ret) dev_err(ac->dev, "%s: clear_sync(SVC_OFFLINE) failed %d\n", __func__, ret); done: WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock); } static void wcd_spi_ac_svc_exit(struct work_struct *work) { struct wcd_spi_ac_priv *ac; int ret = 0; ac = container_of(work, struct wcd_spi_ac_priv, svc_exit_work); if (!ac) { pr_err("%s: Invalid private data\n", __func__); return; } WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock); ret = wcd_spi_ac_set_sync(ac, WCD_SPI_AC_SVC_OFFLINE, true); if (ret) dev_err(ac->dev, "%s: set_sync(SVC_OFFLINE) failed %d\n", __func__, ret); qmi_handle_destroy(ac->qmi_hdl); ac->qmi_hdl = NULL; wcd_spi_ac_status_change(ac, 0); WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock); } static int wcd_spi_ac_svc_event(struct notifier_block *this, unsigned long event, void *data) { struct wcd_spi_ac_priv *ac; ac = container_of(this, struct wcd_spi_ac_priv, nb); if (!ac) { pr_err("%s: Invalid private data\n", __func__); return -EINVAL; } dev_dbg(ac->dev, "%s: event = 0x%lx", __func__, event); switch (event) { case QMI_SERVER_ARRIVE: schedule_work(&ac->svc_arr_work); break; case QMI_SERVER_EXIT: schedule_work(&ac->svc_exit_work); break; default: dev_err(ac->dev, "%s unhandled event %ld\n", __func__, event); break; } return 0; } static int wcd_spi_ac_probe(struct platform_device *pdev) { struct wcd_spi_ac_priv *ac; struct device *parent = pdev->dev.parent; int ret = 0; ac = devm_kzalloc(&pdev->dev, sizeof(*ac), GFP_KERNEL); if (!ac) return -ENOMEM; ac->dev = &pdev->dev; ac->parent = parent; ret = wcd_spi_ac_reg_chardev(ac); if (ret) return ret; ret = wcd_spi_ac_procfs_init(ac); if (ret) goto unreg_chardev; mutex_init(&ac->status_lock); mutex_init(&ac->state_lock); mutex_init(&ac->svc_lock); init_waitqueue_head(&ac->svc_poll_wait); ac->svc_offline = 1; ac->state = (WCD_SPI_AC_SVC_OFFLINE | WCD_SPI_AC_UNINITIALIZED); ac->current_access = WCD_SPI_AC_LOCAL_ACCESS; ac->nb.notifier_call = wcd_spi_ac_svc_event; INIT_WORK(&ac->svc_arr_work, wcd_spi_ac_svc_arrive); INIT_WORK(&ac->svc_exit_work, wcd_spi_ac_svc_exit); INIT_WORK(&ac->recv_msg_work, wcd_spi_ac_recv_msg); ac->qmi_wq = create_singlethread_workqueue("qmi_wq"); if (!ac->qmi_wq) { dev_err(&pdev->dev, "%s: create_singlethread_workqueue failed\n", __func__); goto deinit_procfs; } dev_set_drvdata(&pdev->dev, ac); ret = qmi_svc_event_notifier_register( WCD_SPI_CTL_SERVICE_ID_V01, WCD_SPI_CTL_SERVICE_VERS_V01, WCD_SPI_CTL_INS_ID, &ac->nb); if (ret) { dev_err(&pdev->dev, "%s: qmi_svc_event_notifier_register failed %d\n", __func__, ret); goto destroy_wq; } return 0; destroy_wq: destroy_workqueue(ac->qmi_wq); dev_set_drvdata(&pdev->dev, NULL); deinit_procfs: wcd_spi_ac_procfs_deinit(ac); mutex_destroy(&ac->status_lock); mutex_destroy(&ac->state_lock); mutex_destroy(&ac->svc_lock); unreg_chardev: wcd_spi_ac_unreg_chardev(ac); return ret; } static int wcd_spi_ac_remove(struct platform_device *pdev) { struct wcd_spi_ac_priv *ac; ac = dev_get_drvdata(&pdev->dev); qmi_svc_event_notifier_unregister( WCD_SPI_CTL_SERVICE_ID_V01, WCD_SPI_CTL_SERVICE_VERS_V01, WCD_SPI_CTL_INS_ID, &ac->nb); if (ac->qmi_wq) destroy_workqueue(ac->qmi_wq); wcd_spi_ac_unreg_chardev(ac); wcd_spi_ac_procfs_deinit(ac); mutex_destroy(&ac->status_lock); mutex_destroy(&ac->state_lock); mutex_destroy(&ac->svc_lock); return 0; } static const struct of_device_id wcd_spi_ac_of_match[] = { { .compatible = "qcom,wcd-spi-ac" }, { }, }; MODULE_DEVICE_TABLE(of, wcd_spi_ac_of_match); static struct platform_driver wcd_spi_ac_driver = { .driver = { .name = "qcom,wcd-spi-ac", .of_match_table = wcd_spi_ac_of_match, .suppress_bind_attrs = true, }, .probe = wcd_spi_ac_probe, .remove = wcd_spi_ac_remove, }; module_platform_driver(wcd_spi_ac_driver); MODULE_DESCRIPTION("WCD SPI access control driver"); MODULE_LICENSE("GPL v2");