sdk-hwV1.3/lichee/linux-4.9/drivers/char/sunxi_dspo/dspo_driver.c

430 lines
12 KiB
C
Raw Normal View History

2024-05-07 10:09:20 +00:00
/*
* sunxi_dspo/dspo_driver.c
*
* Copyright (c) 2007-2021 Allwinnertech Co., Ltd.
* Author: zhengxiaobin <zhengxiaobin@allwinnertech.com>
*
* 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 <zhengxiaobin@allwinnertech.com>");
MODULE_DESCRIPTION("dspo driver");
MODULE_LICENSE("GPL");