123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991 |
- // SPDX-License-Identifier: GPL-2.0-only
- /* Copyright (c) 2018-2020, 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 <audio/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");
|