529 lines
12 KiB
C
529 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// Copyright (c) 2018 MediaTek Inc.
|
|
|
|
#include <linux/compat.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/remoteproc.h>
|
|
#include <linux/platform_data/mtk_ccd.h>
|
|
#include <linux/remoteproc/mtk_ccd_mem.h>
|
|
#include <linux/rpmsg/mtk_ccd_rpmsg.h>
|
|
#include <uapi/linux/mtk_ccd_controls.h>
|
|
|
|
#include "remoteproc_internal.h"
|
|
|
|
#define CCD_DEV_NAME "mtk_ccd"
|
|
#define MAX_CODE_SIZE 0x500000
|
|
|
|
char ccd_firmware[100] = {0};
|
|
|
|
static struct mutex g_ccd_open_mutex;
|
|
static int ccd_user_cnt;
|
|
|
|
DECLARE_BUILTIN_FIRMWARE("remoteproc_scp", ccd_firmware);
|
|
|
|
struct platform_device *ccd_get_pdev(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *ccd_node;
|
|
struct platform_device *ccd_pdev;
|
|
|
|
ccd_node = of_parse_phandle(dev->of_node, "mediatek,scp", 0);
|
|
if (!ccd_node) {
|
|
dev_info(dev, "can't get ccd node\n");
|
|
return NULL;
|
|
}
|
|
|
|
ccd_pdev = of_find_device_by_node(ccd_node);
|
|
if (WARN_ON(!ccd_pdev)) {
|
|
dev_info(dev, "ccd pdev failed\n");
|
|
of_node_put(ccd_node);
|
|
return NULL;
|
|
}
|
|
|
|
return ccd_pdev;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ccd_get_pdev);
|
|
|
|
void ccd_wdt_handler(struct mtk_ccd *ccd)
|
|
{
|
|
rproc_report_crash(ccd->rproc, RPROC_WATCHDOG);
|
|
}
|
|
|
|
void ccd_init_ipi_handler(void *data, unsigned int len, void *priv)
|
|
{
|
|
//struct mtk_ccd *ccd = (struct mtk_ccd *)priv;
|
|
}
|
|
|
|
int ccd_ipi_init(struct mtk_ccd *ccd)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ccd_load(struct rproc *rproc, const struct firmware *fw)
|
|
{
|
|
const struct mtk_ccd *ccd = rproc->priv;
|
|
struct device *dev = ccd->dev;
|
|
int ret = 0;
|
|
|
|
dev_info(dev, "remote_ccd loaded!\n");
|
|
return ret;
|
|
}
|
|
|
|
static int ccd_start(struct rproc *rproc)
|
|
{
|
|
struct mtk_ccd *ccd = (struct mtk_ccd *)rproc->priv;
|
|
struct device *dev = ccd->dev;
|
|
int ret = 0;
|
|
|
|
dev_info(dev, "ccd started: %p\n", dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void *ccd_da_to_va(struct rproc *rproc, u64 da, int len)
|
|
{
|
|
struct mtk_ccd *ccd = (struct mtk_ccd *)rproc->priv;
|
|
|
|
dev_info(ccd->dev, "%s: %p\n", __func__, ccd->dev);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int ccd_stop(struct rproc *rproc)
|
|
{
|
|
struct mtk_ccd *ccd = (struct mtk_ccd *)rproc->priv;
|
|
int ret = 0;
|
|
|
|
dev_info(ccd->dev, "%s\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct rproc_ops ccd_ops = {
|
|
.start = ccd_start,
|
|
.stop = ccd_stop,
|
|
.load = ccd_load,
|
|
};
|
|
|
|
void *ccd_mapping_dm_addr(struct platform_device *pdev, u32 mem_addr)
|
|
{
|
|
struct mtk_ccd *ccd = platform_get_drvdata(pdev);
|
|
void *ptr = ccd_da_to_va(ccd->rproc, mem_addr, 0);
|
|
|
|
if (!ptr)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return ptr;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ccd_mapping_dm_addr);
|
|
|
|
static struct mtk_ccd_rpmsg_ops ccd_rpmsg_ops = {
|
|
.ccd_send = rpmsg_ccd_ipi_send,
|
|
};
|
|
|
|
static void ccd_add_rpmsg_subdev(struct mtk_ccd *ccd)
|
|
{
|
|
ccd->rpmsg_subdev =
|
|
mtk_rpmsg_create_rproc_subdev(to_platform_device(ccd->dev),
|
|
&ccd_rpmsg_ops);
|
|
if (ccd->rpmsg_subdev)
|
|
rproc_add_subdev(ccd->rproc, ccd->rpmsg_subdev);
|
|
}
|
|
|
|
static void ccd_remove_rpmsg_subdev(struct mtk_ccd *ccd)
|
|
{
|
|
if (ccd->rpmsg_subdev) {
|
|
mtk_rpmsg_destroy_rpmsgdev(ccd->rpmsg_subdev);
|
|
|
|
rproc_remove_subdev(ccd->rproc, ccd->rpmsg_subdev);
|
|
mtk_rpmsg_destroy_rproc_subdev(ccd->rpmsg_subdev);
|
|
ccd->rpmsg_subdev = NULL;
|
|
}
|
|
}
|
|
|
|
static ssize_t ccd_debug_read(struct file *filp,
|
|
char __user *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char buf[256];
|
|
u32 len = 0;
|
|
|
|
len = snprintf(buf, sizeof(buf), "ccu_debug_read\n");
|
|
if (len >= sizeof(buf))
|
|
pr_info("%s: snprintf fail\n");
|
|
|
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
|
}
|
|
|
|
static ssize_t ccd_debug_write(struct file *filp,
|
|
const char __user *buffer,
|
|
size_t count, loff_t *data)
|
|
{
|
|
char desc[64];
|
|
s32 len = 0;
|
|
|
|
len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1);
|
|
if (copy_from_user(desc, buffer, len))
|
|
return 0;
|
|
|
|
return count;
|
|
}
|
|
|
|
static int ccd_open(struct inode *inode,
|
|
struct file *filp)
|
|
{
|
|
int ret = 0;
|
|
struct mtk_ccd *ccd = container_of(inode->i_cdev,
|
|
struct mtk_ccd,
|
|
ccd_cdev);
|
|
filp->private_data = ccd;
|
|
|
|
mutex_lock(&g_ccd_open_mutex);
|
|
|
|
ccd_user_cnt++;
|
|
if (ccd_user_cnt > 1) {
|
|
dev_info(ccd->dev, "%s: ccd_user_cnt(%d) > 1. Do nothing.\n",
|
|
__func__, ccd_user_cnt);
|
|
mutex_unlock(&g_ccd_open_mutex);
|
|
return 0;
|
|
}
|
|
mutex_unlock(&g_ccd_open_mutex);
|
|
|
|
dev_dbg(ccd->dev, "%s: %p\n", __func__, ccd);
|
|
return ret;
|
|
}
|
|
|
|
static int ccd_release(struct inode *inode,
|
|
struct file *filp)
|
|
{
|
|
int ret = 0;
|
|
struct ccd_master_status_item master_obj;
|
|
struct mtk_ccd *ccd = (struct mtk_ccd *)filp->private_data;
|
|
|
|
mutex_lock(&g_ccd_open_mutex);
|
|
|
|
ccd_user_cnt--;
|
|
if (ccd_user_cnt > 0) {
|
|
dev_info(ccd->dev, "%s: ccd_user_cnt(%d) > 0. Do nothing.\n",
|
|
__func__, ccd_user_cnt);
|
|
mutex_unlock(&g_ccd_open_mutex);
|
|
return 0;
|
|
}
|
|
|
|
master_obj.state = CCD_MASTER_EXIT;
|
|
ccd_master_destroy(ccd, &master_obj);
|
|
mutex_unlock(&g_ccd_open_mutex);
|
|
|
|
dev_dbg(ccd->dev, "%s: %p\n", __func__, ccd);
|
|
return ret;
|
|
}
|
|
|
|
static long ccd_unlocked_ioctl(struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
long ret = 0;
|
|
struct mtk_ccd *ccd = (struct mtk_ccd *)filp->private_data;
|
|
unsigned char *user_addr = (unsigned char *)arg;
|
|
struct ccd_master_listen_item listen_obj;
|
|
struct ccd_worker_item work_obj;
|
|
struct ccd_master_status_item master_obj;
|
|
memset(&work_obj, 0, sizeof(work_obj));
|
|
memset(&listen_obj, 0, sizeof(listen_obj));
|
|
memset(&master_obj, 0, sizeof(master_obj));
|
|
|
|
switch (cmd) {
|
|
case IOCTL_CCD_MASTER_INIT:
|
|
dev_dbg(ccd->dev, "enter IOCTL_CCD_MASTER_INIT\n");
|
|
master_obj.state = CCD_MASTER_ACTIVE;
|
|
/* TBD: Protect by lock? */
|
|
ccd->master_status.state = CCD_MASTER_ACTIVE;
|
|
|
|
if (copy_to_user(user_addr, &master_obj, sizeof(master_obj)))
|
|
ret = -EFAULT;
|
|
break;
|
|
case IOCTL_CCD_MASTER_DESTROY:
|
|
dev_dbg(ccd->dev, "enter IOCTL_CCD_MASTER_DESTROY\n");
|
|
if (copy_from_user(&master_obj, user_addr, sizeof(master_obj))) {
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
/* TBD: Protect by lock? */
|
|
ccd->master_status.state = master_obj.state;
|
|
break;
|
|
case IOCTL_CCD_MASTER_LISTEN:
|
|
ccd_master_listen(ccd, &listen_obj);
|
|
|
|
if (copy_to_user(user_addr, &listen_obj,
|
|
sizeof(struct ccd_master_listen_item)))
|
|
ret = -EFAULT;
|
|
break;
|
|
case IOCTL_CCD_WORKER_READ:
|
|
if (copy_from_user(&work_obj, user_addr,
|
|
sizeof(struct ccd_worker_item))) {
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
ret = ccd_worker_read(ccd, &work_obj);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
if (copy_to_user(user_addr, &work_obj,
|
|
sizeof(struct ccd_worker_item)))
|
|
ret = -EFAULT;
|
|
break;
|
|
case IOCTL_CCD_WORKER_WRITE:
|
|
if (copy_from_user(&work_obj, user_addr,
|
|
sizeof(struct ccd_worker_item))) {
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
ccd_worker_write(ccd, &work_obj);
|
|
break;
|
|
default:
|
|
dev_info(ccd->dev, "Unknown ioctl\n");
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static long ccd_ioctl_compat(struct file *filp,
|
|
unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
long ret = 0;
|
|
|
|
if (!filp->f_op || !filp->f_op->unlocked_ioctl)
|
|
return -ENOTTY;
|
|
|
|
ret = filp->f_op->unlocked_ioctl(filp,
|
|
cmd,
|
|
(unsigned long)compat_ptr(arg));
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static const struct file_operations ccd_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = ccd_open,
|
|
.release = ccd_release,
|
|
.read = ccd_debug_read,
|
|
.write = ccd_debug_write,
|
|
.unlocked_ioctl = ccd_unlocked_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = ccd_ioctl_compat,
|
|
#endif
|
|
};
|
|
|
|
static int ccd_regcdev(struct mtk_ccd *ccd)
|
|
{
|
|
int ret = 0;
|
|
struct device *dev;
|
|
|
|
ret = alloc_chrdev_region(&ccd->ccd_devno, 0, 1, CCD_DEV_NAME);
|
|
if (ret < 0) {
|
|
pr_debug("alloc_chrdev_region failed, %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Attatch file operation. */
|
|
cdev_init(&ccd->ccd_cdev, &ccd_fops);
|
|
ccd->ccd_cdev.owner = THIS_MODULE;
|
|
/* Add to system */
|
|
ret = cdev_add(&ccd->ccd_cdev, ccd->ccd_devno, 1);
|
|
if (ret < 0) {
|
|
pr_debug("Attach file operation failed, %d\n", ret);
|
|
goto err_cdev_add;
|
|
}
|
|
|
|
/* Create class register */
|
|
ccd->ccd_class = class_create(THIS_MODULE, "mtk_ccd");
|
|
if (IS_ERR(ccd->ccd_class)) {
|
|
ret = PTR_ERR(ccd->ccd_class);
|
|
pr_debug("Unable to create class, err = %d\n", ret);
|
|
goto err_class_create;
|
|
}
|
|
|
|
dev = device_create(ccd->ccd_class, NULL,
|
|
ccd->ccd_devno, NULL,
|
|
CCD_DEV_NAME);
|
|
if (IS_ERR(dev)) {
|
|
ret = PTR_ERR(dev);
|
|
pr_debug("Failed to create device: /dev/%s, err = %d\n",
|
|
CCD_DEV_NAME,
|
|
ret);
|
|
goto err_device_create;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err_device_create:
|
|
device_destroy(ccd->ccd_class, ccd->ccd_devno);
|
|
|
|
err_class_create:
|
|
class_destroy(ccd->ccd_class);
|
|
ccd->ccd_class = NULL;
|
|
|
|
err_cdev_add:
|
|
cdev_del(&ccd->ccd_cdev);
|
|
unregister_chrdev_region(ccd->ccd_devno, 1);
|
|
return ret;
|
|
}
|
|
|
|
static void ccd_unregcdev(struct mtk_ccd *ccd)
|
|
{
|
|
/* Release char driver */
|
|
if (ccd->ccd_class) {
|
|
device_destroy(ccd->ccd_class, ccd->ccd_devno);
|
|
class_destroy(ccd->ccd_class);
|
|
ccd->ccd_class = NULL;
|
|
}
|
|
|
|
cdev_del(&ccd->ccd_cdev);
|
|
unregister_chrdev_region(ccd->ccd_devno, 1);
|
|
}
|
|
|
|
static int ccd_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct mtk_ccd *ccd;
|
|
struct resource *res;
|
|
struct rproc *rproc;
|
|
char *fw_name = "remoteproc_scp";
|
|
int ret;
|
|
u32 i;
|
|
|
|
rproc = rproc_alloc(dev,
|
|
np->name,
|
|
&ccd_ops,
|
|
fw_name,
|
|
sizeof(*ccd));
|
|
if (!rproc) {
|
|
dev_info(dev, "unable to allocate remoteproc\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(34)))
|
|
dev_info(dev, "No suitable DMA available\n");
|
|
|
|
if (!dev->dma_parms) {
|
|
dev->dma_parms =
|
|
devm_kzalloc(dev, sizeof(*dev->dma_parms), GFP_KERNEL);
|
|
if (!dev->dma_parms)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (dev->dma_parms) {
|
|
ret = dma_set_max_seg_size(dev, UINT_MAX);
|
|
if (ret)
|
|
dev_info(dev, "Failed to set DMA segment size\n");
|
|
}
|
|
|
|
ccd = (struct mtk_ccd *)rproc->priv;
|
|
ccd->rproc = rproc;
|
|
ccd->dev = dev;
|
|
ccd->ccd_open_mutex = &g_ccd_open_mutex;
|
|
|
|
platform_set_drvdata(pdev, ccd);
|
|
ccd_regcdev(ccd);
|
|
dev_info(ccd->dev, "ccd is created: %p\n", ccd);
|
|
|
|
for (i = 0; i < CCD_MAP_HW_REG_NUM; i++) {
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
|
|
if (res == NULL) {
|
|
dev_info(dev, "No memory resource got\n");
|
|
continue;
|
|
}
|
|
ccd->map_base[i].base = res->start;
|
|
ccd->map_base[i].len = resource_size(res);
|
|
dev_info(dev, "Reg baseaddr [%d]: 0x%lx 0x%lx", i,
|
|
ccd->map_base[i].base,
|
|
ccd->map_base[i].len);
|
|
}
|
|
|
|
/* register SCP initialization IPI */
|
|
ret = ccd_ipi_register(pdev,
|
|
CCD_IPI_INIT,
|
|
ccd_init_ipi_handler,
|
|
ccd);
|
|
if (ret) {
|
|
dev_info(dev, "Failed to register IPI_SCP_INIT\n");
|
|
goto free_rproc;
|
|
}
|
|
|
|
ccd_add_rpmsg_subdev(ccd);
|
|
|
|
ccd->ccd_memory = mtk_ccd_mem_init(ccd->dev);
|
|
|
|
ret = rproc_add(rproc);
|
|
if (ret)
|
|
goto remove_subdev;
|
|
|
|
mtk_create_client_msgdevice(ccd->rpmsg_subdev);
|
|
|
|
return 0;
|
|
|
|
remove_subdev:
|
|
ccd_remove_rpmsg_subdev(ccd);
|
|
free_rproc:
|
|
rproc_free(rproc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ccd_remove(struct platform_device *pdev)
|
|
{
|
|
struct mtk_ccd *ccd = platform_get_drvdata(pdev);
|
|
|
|
mtk_ccd_mem_release(ccd);
|
|
ccd_unregcdev(ccd);
|
|
ccd_remove_rpmsg_subdev(ccd);
|
|
rproc_del(ccd->rproc);
|
|
rproc_free(ccd->rproc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id mtk_ccd_of_match[] = {
|
|
{ .compatible = "mediatek,ccd"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mtk_ccd_of_match);
|
|
|
|
static struct platform_driver mtk_ccd_driver = {
|
|
.probe = ccd_probe,
|
|
.remove = ccd_remove,
|
|
.driver = {
|
|
.name = CCD_DEV_NAME,
|
|
.of_match_table = of_match_ptr(mtk_ccd_of_match),
|
|
},
|
|
};
|
|
|
|
static int __init ccd_init(void)
|
|
{
|
|
int ret;
|
|
|
|
mutex_init(&g_ccd_open_mutex);
|
|
|
|
ret = platform_driver_register(&mtk_ccd_driver);
|
|
return ret;
|
|
}
|
|
|
|
static void __exit ccd_exit(void)
|
|
{
|
|
platform_driver_unregister(&mtk_ccd_driver);
|
|
}
|
|
|
|
late_initcall(ccd_init);
|
|
module_exit(ccd_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MediaTek CCD driver");
|