blob: 2a822b2b1b37528772fe9b162880f12503a6275d [file] [log] [blame]
// 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");