430 lines
12 KiB
C
430 lines
12 KiB
C
|
/*
|
||
|
* 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");
|