193 lines
4.4 KiB
C
193 lines
4.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2024 Rockchip Electronics Co., Ltd.
|
|
*/
|
|
#include <linux/cdev.h>
|
|
#include <linux/io.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/version_compat_defs.h>
|
|
|
|
#include "dsmc-host.h"
|
|
|
|
struct dsmc_cs {
|
|
struct cdev cdev[DSMC_LB_MAX_RGN];
|
|
};
|
|
|
|
static struct dsmc_cs cs_info[DSMC_MAX_SLAVE_NUM];
|
|
static struct class *dsmc_class;
|
|
static dev_t dsmc_devt;
|
|
|
|
static inline int get_cs_index(struct inode *inode)
|
|
{
|
|
return iminor(inode) / DSMC_LB_MAX_RGN;
|
|
}
|
|
|
|
static inline int get_mem_region_index(struct inode *inode)
|
|
{
|
|
return iminor(inode) % DSMC_LB_MAX_RGN;
|
|
}
|
|
|
|
static int dsmc_open(struct inode *inode, struct file *pfile)
|
|
{
|
|
struct rockchip_dsmc_device *dsmc_dev = NULL;
|
|
struct rockchip_dsmc *dsmc = NULL;
|
|
struct dsmc_config_cs *cfg;
|
|
struct dsmc_cs_map *map;
|
|
int cs_index, mem_region_index;
|
|
|
|
cs_index = get_cs_index(inode);
|
|
mem_region_index = get_mem_region_index(inode);
|
|
|
|
dsmc_dev = rockchip_dsmc_find_device_by_compat(rockchip_dsmc_get_compat(0));
|
|
if (dsmc_dev == NULL)
|
|
return -EINVAL;
|
|
|
|
dsmc = &dsmc_dev->dsmc;
|
|
|
|
if (cs_index < DSMC_MAX_SLAVE_NUM)
|
|
cfg = &dsmc->cfg.cs_cfg[cs_index];
|
|
else
|
|
return -EINVAL;
|
|
if ((cfg->device_type == DSMC_UNKNOWN_DEVICE) ||
|
|
(!cfg->slv_rgn[mem_region_index].status))
|
|
return -EINVAL;
|
|
|
|
map = &dsmc->cs_map[cs_index];
|
|
|
|
pfile->private_data = (void *)&map->region_map[mem_region_index];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dsmc_release(struct inode *inode, struct file *pfile)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int dsmc_mmap(struct file *pfile, struct vm_area_struct *vma)
|
|
{
|
|
struct dsmc_map *region = (struct dsmc_map *)pfile->private_data;
|
|
unsigned long pfn;
|
|
unsigned long vm_size = 0;
|
|
|
|
if (!region)
|
|
return -EINVAL;
|
|
|
|
vm_flags_set(vma, VM_PFNMAP | VM_DONTDUMP);
|
|
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
|
|
|
|
vm_size = vma->vm_end - vma->vm_start;
|
|
|
|
pfn = __phys_to_pfn(region->phys);
|
|
|
|
if (remap_pfn_range(vma, vma->vm_start, pfn, vm_size, vma->vm_page_prot))
|
|
return -EAGAIN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations dsmc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = dsmc_open,
|
|
.release = dsmc_release,
|
|
.mmap = dsmc_mmap,
|
|
};
|
|
|
|
int rockchip_dsmc_lb_class_create(const char *name)
|
|
{
|
|
int ret;
|
|
|
|
dsmc_class = class_create(THIS_MODULE, name);
|
|
if (IS_ERR(dsmc_class)) {
|
|
ret = PTR_ERR(dsmc_class);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_dsmc_lb_class_create);
|
|
|
|
int rockchip_dsmc_lb_class_destroy(void)
|
|
{
|
|
if (!dsmc_class)
|
|
return 0;
|
|
|
|
class_destroy(dsmc_class);
|
|
dsmc_class = NULL;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_dsmc_lb_class_destroy);
|
|
|
|
int rockchip_dsmc_register_lb_device(struct device *dev, uint32_t cs)
|
|
{
|
|
int ret, j;
|
|
struct device *device_ret;
|
|
|
|
if (!dev || (cs >= DSMC_MAX_SLAVE_NUM) || (!dsmc_class))
|
|
return -EINVAL;
|
|
|
|
ret = alloc_chrdev_region(&dsmc_devt, 0,
|
|
DSMC_LB_MAX_RGN, "dsmc");
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to alloc dsmc device region\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
for (j = 0; j < DSMC_LB_MAX_RGN; j++) {
|
|
device_ret = device_create(dsmc_class, NULL,
|
|
MKDEV(MAJOR(dsmc_devt), cs * DSMC_LB_MAX_RGN + j),
|
|
NULL, "dsmc/cs%d/region%d", cs, j);
|
|
if (IS_ERR(device_ret)) {
|
|
dev_err(dev, "Failed to create device for cs%d region%d\n", cs, j);
|
|
ret = PTR_ERR(device_ret);
|
|
goto err_device_create;
|
|
}
|
|
cdev_init(&cs_info[cs].cdev[j], &dsmc_fops);
|
|
ret = cdev_add(&cs_info[cs].cdev[j],
|
|
MKDEV(MAJOR(dsmc_devt), cs * DSMC_LB_MAX_RGN + j), 1);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to add cdev for cs%d region%d\n", cs, j);
|
|
goto err_cdev_add;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_cdev_add:
|
|
device_destroy(dsmc_class, MKDEV(MAJOR(dsmc_devt), cs * DSMC_LB_MAX_RGN + j));
|
|
|
|
err_device_create:
|
|
while (j-- > 0) {
|
|
device_destroy(dsmc_class, MKDEV(MAJOR(dsmc_devt), cs * DSMC_LB_MAX_RGN + j));
|
|
cdev_del(&cs_info[cs].cdev[j]);
|
|
}
|
|
unregister_chrdev_region(dsmc_devt, DSMC_LB_MAX_RGN);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_dsmc_register_lb_device);
|
|
|
|
int rockchip_dsmc_unregister_lb_device(struct device *dev, uint32_t cs)
|
|
{
|
|
int j;
|
|
|
|
if (!dev || (cs >= DSMC_MAX_SLAVE_NUM))
|
|
return -EINVAL;
|
|
|
|
for (j = 0; j < DSMC_LB_MAX_RGN; j++) {
|
|
device_destroy(dsmc_class,
|
|
MKDEV(MAJOR(dsmc_devt),
|
|
cs * DSMC_LB_MAX_RGN + j));
|
|
cdev_del(&cs_info->cdev[j]);
|
|
}
|
|
unregister_chrdev_region(dsmc_devt, DSMC_LB_MAX_RGN);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rockchip_dsmc_unregister_lb_device);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Zhihuan He <huan.he@rock-chips.com>");
|
|
MODULE_DESCRIPTION("ROCKCHIP DSMC local bus device");
|