/* * sunxi_dspo/dspo_driver.c * * Copyright (c) 2007-2021 Allwinnertech Co., Ltd. * Author: zhengxiaobin * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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 "dspo_driver.h" #include "dspo_reg.h" struct dspo_resource_t dspo_res; int dspo_open(struct inode *inode, struct file *file) { atomic_inc(&dspo_res.user_cnt); return 0; } int dspo_release(struct inode *inode, struct file *file) { atomic_dec(&dspo_res.user_cnt); return 0; } long dspo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { s32 ret = 0; unsigned long karg[2]; unsigned long ubuffer[2] = { 0 }; if (!mutex_trylock(&dspo_res.mutex)) mutex_lock(&dspo_res.mutex); if (copy_from_user((void *)karg, (void __user *)arg, sizeof(unsigned long) * 2)) { pr_err("copy_from_user fail!\n"); ret = -EFAULT; goto OUT; } ubuffer[0] = *(unsigned long *)karg; ubuffer[1] = (*(unsigned long *)(karg + 1)); switch (cmd) { case DSPO_CMD_DEVICE_SWITCH: { struct dspo_config_t *p_cfg = NULL; if (!ubuffer[0]) { if (dspo_res.p_dspo->is_enable(dspo_res.p_dspo)) ret = dspo_res.p_dspo->disable(dspo_res.p_dspo); } else { p_cfg = kmalloc(sizeof(struct dspo_config_t), __GFP_ZERO | GFP_KERNEL); if (copy_from_user(p_cfg, (void __user *)ubuffer[1], sizeof(struct dspo_config_t))) { pr_err("copy_from_user fail 2!\n"); ret = -EFAULT; goto OUT; } if (dspo_res.p_dspo->is_enable(dspo_res.p_dspo)) ret = dspo_res.p_dspo->disable(dspo_res.p_dspo); ret = dspo_res.p_dspo->enable(dspo_res.p_dspo, p_cfg); kfree(p_cfg); } } break; case DSPO_CMD_COMMIT: { struct dspo_dma_info_t info; if (copy_from_user(&info, (void __user *)ubuffer[0], sizeof(struct dspo_dma_info_t))) { ret = -EFAULT; goto OUT; } if (dspo_res.p_dspo->is_enable(dspo_res.p_dspo)) dspo_res.p_dspo->commit(dspo_res.p_dspo, &info); else pr_warn("DSPO is not enabled!\n"); } break; default: ret = -EFAULT; goto OUT; } OUT: mutex_unlock(&dspo_res.mutex); return ret; } int dspo_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long mypfn = vma->vm_pgoff; unsigned long vmsize = vma->vm_end - vma->vm_start; vma->vm_pgoff = 0; vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); if (remap_pfn_range(vma, vma->vm_start, mypfn, vmsize, vma->vm_page_prot)) return -EAGAIN; return 0; } static const struct file_operations dspo_fops = { .owner = THIS_MODULE, .open = dspo_open, .release = dspo_release, .unlocked_ioctl = dspo_ioctl, .mmap = dspo_mmap, }; static const struct of_device_id sunxi_dspo_match[] = { {.compatible = "allwinner,sunxi-dspo",}, {}, }; static ssize_t dspo_info_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t count = 0; if (dspo_res.p_dspo) { struct dspo_config_t *p_cfg = NULL; struct dspo_health_info_t info; p_cfg = dspo_res.p_dspo->get_config(dspo_res.p_dspo); dspo_res.p_dspo->get_health_info(dspo_res.p_dspo, &info); if (p_cfg) { count += sprintf(buf + count, "DSPO INFOMATION:\n"); count += sprintf(buf + count, "Enable status:%u irq cnt:%llu err_cnt:%u fps:%u\n", dspo_res.p_dspo->is_enable(dspo_res.p_dspo), info.irq_cnt, info.err_cnt, info.realtime_fps); if (dspo_res.p_dspo->is_enable(dspo_res.p_dspo)) { count += sprintf(buf + count, " == Output setting: == \n"); count += sprintf( buf + count, "Protocol:%s separate_sync:%u data " "sequence:%u bit_width:%u\n", (p_cfg->protocol == DSPO_BT656) ? "BT656" : "BT1120", p_cfg->separate_sync, p_cfg->data_seq_sel, p_cfg->bit_width); count += sprintf(buf + count, "Dclk setting:%u %u %u\n", p_cfg->dclk_set.dclk_en, p_cfg->dclk_set.dclk_invt, p_cfg->dclk_set.dclk_dly_num); count += sprintf(buf + count, " == Detail timing info: == \n"); count += sprintf(buf + count, "Output mode:%u\n", p_cfg->output_mode); count += sprintf( buf + count, "Resolution:[%ux%u%s@%uHz]", p_cfg->timing_info.x_res, p_cfg->timing_info.y_res, (p_cfg->timing_info.b_interlace) ? "I" : "P", DIV_ROUND_CLOSEST( p_cfg->timing_info.pixel_clk * (p_cfg->timing_info.b_interlace + 1), p_cfg->timing_info.hor_total_time * p_cfg->timing_info.ver_total_time)); count += sprintf(buf + count, "hspw:%u, ht:%u hbp:%u hfp:%u hpol:%u\n", p_cfg->timing_info.hor_sync_time, p_cfg->timing_info.hor_total_time, p_cfg->timing_info.hor_back_porch, p_cfg->timing_info.hor_front_porch, p_cfg->timing_info.hor_sync_polarity); count += sprintf(buf + count, "vspw:%u, vt:%u vbp:%u vfp:%u vpol:%u\n", p_cfg->timing_info.ver_sync_time, p_cfg->timing_info.ver_total_time, p_cfg->timing_info.ver_back_porch, p_cfg->timing_info.ver_front_porch, p_cfg->timing_info.ver_sync_polarity); } count += sprintf(buf + count, " == Hardware resource: == \n"); count += sprintf(buf + count, "reg base:0x%lx irq_no:%u\n", (unsigned long)dspo_res.p_dspo->io, dspo_res.p_dspo->irq); if (dspo_res.p_dspo->clk && dspo_res.p_dspo->clk_parent) count += sprintf( buf + count, "real clk rate:%lu and real clk " "parent rate:%lu, rate to be " "set:%u\n", clk_get_rate(dspo_res.p_dspo->clk), clk_get_rate(dspo_res.p_dspo->clk_parent), p_cfg->timing_info.pixel_clk * 2); else count += sprintf(buf + count, "Get clk info fail!\n"); } else { return sprintf(buf + count, "Get dspo info fail!\n"); } } else return sprintf(buf + count, "NULL dspo device!\n"); return count; } static DEVICE_ATTR(info, 0660, dspo_info_show, NULL); static struct attribute *dspo_attributes[] = { &dev_attr_info.attr, NULL }; static struct attribute_group dspo_attribute_group = { .name = "attr", .attrs = dspo_attributes }; static int init_dspo_device_from_dts(struct device *dev, struct dspo_device_t *p_dspo) { int value = 0, ret = -1; struct dspo_config_t cfg; memset(&cfg, 0, sizeof(struct dspo_config_t)); ret = of_property_read_u32_array(dev->of_node, "enable", &value, 1); if (ret != 0 || value == 0) goto OUT; ret = of_property_read_u32_array(dev->of_node, "protocol", &value, 1); if (ret == 0) cfg.protocol = value; ret = of_property_read_u32_array(dev->of_node, "bit_width", &value, 1); if (ret == 0) cfg.bit_width = value; ret = of_property_read_u32_array(dev->of_node, "separate_sync", &value, 1); if (ret == 0) cfg.separate_sync = value; ret = of_property_read_u32_array(dev->of_node, "data_seq_sel", &value, 1); if (ret == 0) cfg.data_seq_sel = value; ret = of_property_read_u32_array(dev->of_node, "output_mode", &value, 1); if (ret == 0) cfg.output_mode = value; ret = of_property_read_u32_array(dev->of_node, "dclk_dly_en", &value, 1); if (ret == 0) cfg.dclk_set.dclk_en = value; ret = of_property_read_u32_array(dev->of_node, "dclk_dly_num", &value, 1); if (ret == 0) cfg.dclk_set.dclk_dly_num = value; ret = of_property_read_u32_array(dev->of_node, "dclk_invert", &value, 1); if (ret == 0) cfg.dclk_set.dclk_invt = value; if (cfg.output_mode == DSPO_CUSTOM_MODE) { ret = of_property_read_u32_array(dev->of_node, "pixel_clk", &value, 1); if (ret == 0) cfg.timing_info.pixel_clk = value; ret = of_property_read_u32_array(dev->of_node, "x_res", &value, 1); if (ret == 0) cfg.timing_info.x_res = value; ret = of_property_read_u32_array(dev->of_node, "y_res", &value, 1); if (ret == 0) cfg.timing_info.y_res = value; ret = of_property_read_u32_array(dev->of_node, "ht", &value, 1); if (ret == 0) cfg.timing_info.hor_total_time = value; ret = of_property_read_u32_array(dev->of_node, "hbp", &value, 1); if (ret == 0) cfg.timing_info.hor_back_porch = value; ret = of_property_read_u32_array(dev->of_node, "hfp", &value, 1); if (ret == 0) cfg.timing_info.hor_front_porch = value; ret = of_property_read_u32_array(dev->of_node, "hspw", &value, 1); if (ret == 0) cfg.timing_info.hor_sync_time = value; ret = of_property_read_u32_array(dev->of_node, "hpol", &value, 1); if (ret == 0) cfg.timing_info.hor_sync_polarity = value; ret = of_property_read_u32_array(dev->of_node, "vt", &value, 1); if (ret == 0) cfg.timing_info.ver_total_time = value; ret = of_property_read_u32_array(dev->of_node, "vbp", &value, 1); if (ret == 0) cfg.timing_info.ver_back_porch = value; ret = of_property_read_u32_array(dev->of_node, "vfp", &value, 1); if (ret == 0) cfg.timing_info.ver_front_porch = value; ret = of_property_read_u32_array(dev->of_node, "vspw", &value, 1); if (ret == 0) cfg.timing_info.ver_sync_time = value; ret = of_property_read_u32_array(dev->of_node, "vpol", &value, 1); if (ret == 0) cfg.timing_info.ver_sync_polarity = value; ret = of_property_read_u32_array(dev->of_node, "interlace", &value, 1); if (ret == 0) cfg.timing_info.b_interlace = value; } ret = p_dspo->enable(p_dspo, &cfg); OUT: return ret; } static int dspo_probe(struct platform_device *pdev) { int ret = 0; dspo_res.sunxi_dspo_dma_mask = DMA_BIT_MASK(32); platform_set_drvdata(pdev, &dspo_res); pdev->dev.dma_mask = &dspo_res.sunxi_dspo_dma_mask; pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); dspo_res.p_dspo = create_dspo_device(&pdev->dev); if (!dspo_res.p_dspo) { pr_warn("Malloc dspo_device_t fail!\n"); goto OUT; } mutex_init(&dspo_res.mutex); init_dspo_device_from_dts(&pdev->dev, dspo_res.p_dspo); OUT: return ret; } static int dspo_remove(struct platform_device *pdev) { sysfs_remove_group(&dspo_res.dev->kobj, &dspo_attribute_group); return destroy_dspo_device(dspo_res.p_dspo);; } static int dspo_suspend(struct platform_device *pdev, pm_message_t state) { return 0; } static int dspo_resume(struct platform_device *pdev) { return 0; } static struct platform_driver dspo_driver = { .probe = dspo_probe, .remove = dspo_remove, .suspend = dspo_suspend, .resume = dspo_resume, .driver = { .owner = THIS_MODULE, .name = "dspo", .of_match_table = sunxi_dspo_match, }, }; int __init dspo_module_init(void) { int ret = 0; memset(&dspo_res, 0, sizeof(struct dspo_resource_t)); alloc_chrdev_region(&dspo_res.dev_id, 0, 1, "dspo_chrdev"); dspo_res.dspo_cdev = cdev_alloc(); cdev_init(dspo_res.dspo_cdev, &dspo_fops); dspo_res.dspo_cdev->owner = THIS_MODULE; ret = cdev_add(dspo_res.dspo_cdev, dspo_res.dev_id, 1); if (ret) { pr_warn("cdev_add fail:%d\n", ret); goto OUT; } dspo_res.dspo_class = class_create(THIS_MODULE, "dspo"); if (IS_ERR_OR_NULL(dspo_res.dspo_class)) { ret = PTR_ERR(dspo_res.dspo_class); pr_warn("create class error:%d\n", ret); goto OUT; } dspo_res.dev = device_create(dspo_res.dspo_class, NULL, dspo_res.dev_id, NULL, "dspo"); if (IS_ERR_OR_NULL(dspo_res.dev)) { ret = PTR_ERR(dspo_res.dev); pr_warn("device_create dspo fail!:%d\n", ret); } else { ret = platform_driver_register(&dspo_driver); if (ret) pr_warn("platform_driver_register fail:%d\n", ret); else pr_info("module dspo :%d init finish\n", MAJOR(dspo_res.dev_id)); } ret = sysfs_create_group(&dspo_res.dev->kobj, &dspo_attribute_group); if (ret < 0) pr_warn("sysfs_create_file fail!\n"); OUT: return ret; } static void __exit dspo_module_exit(void) { pr_info("dspo_module_exit\n"); platform_driver_unregister(&dspo_driver); device_destroy(dspo_res.dspo_class, dspo_res.dev_id); class_destroy(dspo_res.dspo_class); cdev_del(dspo_res.dspo_cdev); } subsys_initcall_sync(dspo_module_init); module_exit(dspo_module_exit); MODULE_AUTHOR("zxb "); MODULE_DESCRIPTION("dspo driver"); MODULE_LICENSE("GPL");