| // 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}; |
| |
| 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) { |
| 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"); |
| |
| 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; |
| 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; |
| |
| master_obj.state = CCD_MASTER_EXIT; |
| ccd_master_destroy(ccd, &master_obj); |
| dev_info(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; |
| |
| switch (cmd) { |
| case IOCTL_CCD_MASTER_INIT: |
| dev_dbg(ccd->dev, "enter IOCTL_CCD_MASTER_INIT\n"); |
| memset(&master_obj, 0, sizeof(master_obj)); |
| master_obj.state = CCD_MASTER_ACTIVE; |
| /* TBD: Protect by lock? */ |
| ccd->master_status.state = CCD_MASTER_ACTIVE; |
| |
| ret = copy_to_user(user_addr, &master_obj, |
| sizeof(master_obj)); |
| break; |
| case IOCTL_CCD_MASTER_DESTROY: |
| dev_dbg(ccd->dev, "enter IOCTL_CCD_MASTER_DESTROY\n"); |
| ret = copy_from_user(&master_obj, user_addr, |
| sizeof(master_obj)); |
| /* TBD: Protect by lock? */ |
| ccd->master_status.state = master_obj.state; |
| break; |
| case IOCTL_CCD_MASTER_LISTEN: |
| memset(&listen_obj, 0, sizeof(listen_obj)); |
| ccd_master_listen(ccd, &listen_obj); |
| |
| ret = copy_to_user(user_addr, &listen_obj, |
| sizeof(struct ccd_master_listen_item)); |
| break; |
| case IOCTL_CCD_WORKER_READ: |
| ret = copy_from_user(&work_obj, user_addr, |
| sizeof(struct ccd_worker_item)); |
| |
| ret = ccd_worker_read(ccd, &work_obj); |
| if (ret < 0) |
| break; |
| ret = copy_to_user(user_addr, &work_obj, |
| sizeof(struct ccd_worker_item)); |
| break; |
| case IOCTL_CCD_WORKER_WRITE: |
| ret = copy_from_user(&work_obj, user_addr, |
| sizeof(struct ccd_worker_item)); |
| ccd_worker_write(ccd, &work_obj); |
| break; |
| default: |
| dev_info(ccd->dev, "Unknown ioctl\n"); |
| 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; |
| 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; |
| |
| 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; |
| |
| 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"); |