Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 55ddaa0e authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "msm: mdsp_ts: add snapshot of mdsp timestamp driver"

parents b22827e5 3d93f14c
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -630,6 +630,15 @@ config MSM_ADSPRPC
	  applications DSP processor. Say M if you want to enable this
	  module.

config MSM_MDSP_TS
	tristate "Qualcomm Technologies Inc MDSP TimeStamp driver"
	depends on MSM_SMD
	help
	  Provides a high response mechanism that allows for AP to get
	  timestamp from mDSP. It provides the device /dev/vsync_ts_diff
	  for user to read the timestamp, and the timestamp is tranfered
	  through smd between mDSP and AP.

config MSM_RDBG
	tristate "Qualcomm Remote debug driver"
	help
+1 −0
Original line number Diff line number Diff line
@@ -68,4 +68,5 @@ obj-$(CONFIG_MSM_ADSPRPC) += adsprpc.o
ifdef CONFIG_COMPAT
obj-$(CONFIG_MSM_ADSPRPC)       += adsprpc_compat.o
endif
obj-$(CONFIG_MSM_MDSP_TS)       += smd_ts.o
obj-$(CONFIG_MSM_RDBG)		+= rdbg.o

drivers/char/smd_ts.c

0 → 100644
+293 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2016, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
#include <linux/slab.h>
#include <linux/completion.h>
#include <linux/pagemap.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/list.h>
#include <linux/hash.h>
#include <linux/msm_ion.h>
#include <soc/qcom/smd.h>
#include <soc/qcom/subsystem_notif.h>
#include <linux/msm_iommu_domains.h>
#include <linux/scatterlist.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/dma-contiguous.h>
#include <linux/iommu.h>
#include <linux/kref.h>
#include <linux/debugfs.h>

#include "smd_ts.h"

static struct smd_ts_apps ts_diff;
/* called in smd_irq, as irq is off, it doesnot need spinlock */
static void ts_smdl_transfer(struct smd_ts_apps *ts_app)
{
	int read_len;
	int avail_len;

	avail_len = smd_read_avail(ts_app->chan);
	/* avail_len should be aligned to sizeof(uint64_t) */
	if (avail_len <= 0 || avail_len & (sizeof(uint64_t) - 1)) {
		pr_err("invalid avail length %d\n", avail_len);
		return;
	}

	while (avail_len > 0) {
		/* buf_ptr always point to the latest data to write */
		ts_app->buf_ptr--;
		if (ts_app->buf_ptr < ts_app->ts_buf)
			ts_app->buf_ptr = ts_app->ts_buf + ts_app->buf_len - 1;

		read_len = smd_read_from_cb(ts_app->chan, ts_app->buf_ptr,
					sizeof(uint64_t));
		if (read_len != sizeof(uint64_t)) {
			pr_err("invaild read\n");
			return;
		}
		ts_app->ready_buf_len++;

		if (ts_app->ready_buf_len > ts_app->buf_len)
			ts_app->ready_buf_len = ts_app->buf_len;

		avail_len -= sizeof(uint64_t);
	}
	/* complete who wait for me */
	if (!completion_done(&ts_app->work))
		complete(&ts_app->work);
}

/* event handler of timestamp_diff, called in smd_irq */
static void ts_diff_handler(void *priv, unsigned event)
{
	struct completion *work = &ts_diff.work;

	switch (event) {
	case SMD_EVENT_OPEN:
		complete(work);
		break;

	case SMD_EVENT_CLOSE:
		break;

	case SMD_EVENT_DATA:
		ts_smdl_transfer(&ts_diff);
		break;
	}
}

/* open channel and wait for completion */
static int ts_smdl_open(struct smd_ts_apps *ts_app)
{
	int ret;

	if (ts_app->chan)
		return 0;

	/* open timestamp's smd channel */
	ret = smd_named_open_on_edge(ts_app->dev_name, TS_CHANNEL_TYPE,
		&ts_app->chan, ts_app, ts_app->event_handler);
	if (ret) {
		pr_err("open channel %s failed\n", ts_app->dev_name);
		return ret;
	}

	/* wait for DSP---AP finish communication */
	ret = wait_for_completion_timeout(&ts_app->work, 5*HZ);
	if (ret == 0) {
		pr_err("%s wait for completion failed\n", ts_app->dev_name);
		smd_close(ts_app->chan);
		return ret;
	}

	return 0;
}

/* read ts to ts_buf, and copy ts_buf to user_buf */
static int ts_buf_read(struct smd_ts_apps *ts_app, char *user_buf, size_t count)
{
	int ret = 0;
	unsigned int length;
	unsigned long flags;
	uint64_t *buf_tmp;

	if (count == 0)
		return 0;

	spin_lock_irqsave(&ts_app->lock, flags);

	/* if none data ready, wait until at least one ready;
	 * it should release the lock, and get the lock after completion. */
	if (ts_app->ready_buf_len == 0) {
		init_completion(&ts_app->work);
		spin_unlock_irqrestore(&ts_app->lock, flags);
		wait_for_completion(&ts_app->work);
		spin_lock_irqsave(&ts_app->lock, flags);
	}

	if (count > (ts_app->ready_buf_len * sizeof(uint64_t)))
		count = ts_app->ready_buf_len * sizeof(uint64_t);

	ts_app->ready_buf_len -= count / sizeof(uint64_t);

	buf_tmp = ts_app->buf_ptr;
	/* buf structure
	 * ts_buf******buf_ptr******ts_buf+buf_len */
	length = (ts_app->ts_buf + ts_app->buf_len - ts_app->buf_ptr)
			* sizeof(uint64_t);
	if (count <= length) {
		ts_app->buf_ptr += count / sizeof(uint64_t) - 1;
	} else {
		ts_app->buf_ptr = ts_app->ts_buf +
				(count - length) / sizeof(uint64_t) - 1;
	}

	spin_unlock_irqrestore(&ts_app->lock, flags);

	/* do memcpy outof spinlock_irq */
	if (count <= length) {
		ret = copy_to_user(user_buf, buf_tmp, count);
	} else {
		/* copy from buf_ptr */
		ret = copy_to_user(user_buf, ts_app->buf_ptr, length);
		/* copy from the begin of ts_buf */
		ret += copy_to_user(user_buf + length, ts_app->ts_buf,
				(count - length));
	}

	if (ret)
		return -EFAULT;
	return count;
}

static int ts_diff_open(struct inode *inode, struct file *file)
{
	if (inode->i_private)
		file->private_data = inode->i_private;
	return ts_smdl_open(&ts_diff);
}

static ssize_t ts_diff_read(struct file *file, char __user *user_buf,
				     size_t count, loff_t *ppos)
{
	return ts_buf_read(&ts_diff, user_buf, count);
}
static int ts_diff_release(struct inode *inode, struct file *file)
{
	return smd_close(ts_diff.chan);
}

/* alloc dev and open channel */
static int smd_ts_apps_init(struct smd_ts_apps *ts_app)
{
	int ret = -1;
	unsigned int length = sizeof(uint64_t) * ts_app->buf_len;

	/* malloc buf for list_buf */
	ts_app->ts_buf = kmalloc(length, GFP_KERNEL);
	if (!ts_app->ts_buf)
		return -ENOMEM;
	memset(ts_app->ts_buf, 0, sizeof(uint64_t) * ts_app->buf_len);
	ts_app->buf_ptr = ts_app->ts_buf;

	init_completion(&ts_app->work);
	spin_lock_init(&ts_app->lock);

	ret = alloc_chrdev_region(&ts_app->dev_no, 0, 1, ts_app->dev_name);
	if (ret != 0)
		goto alloc_chrdev_fail;
	cdev_init(&ts_app->cdev, ts_app->fops);
	ts_app->cdev.owner = THIS_MODULE;

	ret = cdev_add(&ts_app->cdev, MKDEV(MAJOR(ts_app->dev_no), 0), 1);
	if (ret != 0)
		goto cdev_add_fail;

	ts_app->class = class_create(THIS_MODULE, ts_app->dev_name);
	if (IS_ERR(ts_app->class))
		goto class_create_fail;

	ts_app->dev = device_create(ts_app->class, NULL,
					MKDEV(MAJOR(ts_app->dev_no), 0),
					NULL, ts_app->dev_name);
	if (IS_ERR(ts_app->dev))
		goto device_create_fail;

	return 0;

device_create_fail:
	class_destroy(ts_app->class);
class_create_fail:
	cdev_del(&ts_app->cdev);
cdev_add_fail:
	unregister_chrdev_region(ts_app->dev_no, 1);
alloc_chrdev_fail:
	kfree(ts_app->ts_buf);

	return ret;
}

static const struct file_operations ts_diff_fops = {
	.open = ts_diff_open,
	.read = ts_diff_read,
	.release = ts_diff_release,
};

/* close smd and free device */
static void smd_ts_apps_deinit(struct smd_ts_apps *ts_app)
{
	if (ts_app->chan)
		smd_close(ts_app->chan);
	class_destroy(ts_app->class);
	cdev_del(&ts_app->cdev);
	unregister_chrdev_region(ts_app->dev_no, 1);
	kfree(ts_app->ts_buf);
}

static int __init smd_ts_init(void)
{
	int ret;

	ts_diff.buf_len = TS_DIFF_BUF_NUM,
	ts_diff.dev_name = TS_DIFF_PORT_NAME,
	ts_diff.fops = &ts_diff_fops,
	ts_diff.event_handler = ts_diff_handler,

	ret = smd_ts_apps_init(&ts_diff);
	if (ret != 0) {
		pr_err("failed to init ts_diff\n");
		return ret;
	}

	return 0;
}

static void __exit smd_ts_exit(void)
{
	smd_ts_apps_deinit(&ts_diff);
}

module_init(smd_ts_init);
module_exit(smd_ts_exit);

MODULE_LICENSE("GPL v2");

drivers/char/smd_ts.h

0 → 100644
+70 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2016, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
#ifndef SMD_TS_H
#define SMD_TS_H

#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/list.h>
#include <linux/hash.h>
#include <linux/msm_ion.h>
#include <soc/qcom/smd.h>
#include <soc/qcom/subsystem_notif.h>
#include <linux/msm_iommu_domains.h>
#include <linux/scatterlist.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/dma-contiguous.h>
#include <linux/iommu.h>
#include <linux/kref.h>
#include <linux/debugfs.h>
#include <linux/list.h>

#define TS_DIFF_PORT_NAME	"vsync_ts_diff"
#define TS_CHANNEL_TYPE		SMD_APPS_MODEM
#define TS_FIFO_SIZE		(1024)

#define TS_DIFF_BUF_NUM		33

struct smd_ts_apps {
	spinlock_t lock;
	char *dev_name;
	smd_channel_t *chan;
	struct completion work;

	/* for dev node */
	struct cdev cdev;
	struct device *dev;
	dev_t dev_no;
	struct class *class;
	const struct file_operations *fops;

	/* handler for smd event */
	void *event_handler;

	/* point to the head of buf, const */
	uint64_t *ts_buf;
	/* point to the buf to write, always changed */
	uint64_t *buf_ptr;
	/* the total buf length */
	unsigned int buf_len;
	/* the length of ready buf */
	unsigned int ready_buf_len;
};

#endif