| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/cdev.h> |
| #include <linux/proc_fs.h> |
| #include <linux/poll.h> |
| #include <linux/slab.h> |
| #include <linux/notifier.h> |
| #include <linux/wcd-spi-ac-params.h> |
| #include <soc/wcd-spi-ac.h> |
| #include <soc/qcom/msm_qmi_interface.h> |
| |
| #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"); |