mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-25 12:47:11 +08:00
lscanfd.ko is in driver/net/can/ls_can. usage: insmod lscanfd.ko insmod lscanfd_platform.ko
606 lines
12 KiB
C
606 lines
12 KiB
C
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/io.h>
|
|
#include <linux/uio.h>
|
|
|
|
#define LIO_NODE_MAX 3
|
|
|
|
#define MINOR_BASE 0
|
|
#define DRIVER_NAME "lio"
|
|
#define DEVICE_NAME_FREFIX "lio"
|
|
#define CLASS_NAME "lio"
|
|
|
|
struct property_s {
|
|
char name[32];
|
|
int shift;
|
|
int bitwidth;
|
|
};
|
|
|
|
static struct property_s g_lio_prop[] =
|
|
#if defined(CONFIG_LOONGSON_2K500)
|
|
{
|
|
{"lio-rom-width", 28, 1},
|
|
{"lio-rom-count-init", 23, 5},
|
|
{"lio-clk-period", 21, 2},
|
|
};
|
|
#elif defined(CONFIG_LOONGSON_2K1000)
|
|
{
|
|
{"lio-big-mem", 63, 1},
|
|
{"lio-io-width", 61, 1},
|
|
{"lio-io-count-init", 56, 5},
|
|
{"lio-rom-width", 55, 1},
|
|
{"lio-rom-count-init", 50, 5},
|
|
{"lio-clk-period", 48, 2},
|
|
};
|
|
#else
|
|
{
|
|
};
|
|
#endif
|
|
|
|
/**
|
|
* lio_dev
|
|
* |-------+-------.
|
|
* V V V
|
|
* node0 node1 node2
|
|
* 0x1C10 0x1FD0 0x1FF0
|
|
* 0000 0000 0000
|
|
*/
|
|
|
|
struct lio_dev_s {
|
|
const char* name; // name from pdev
|
|
u8 lio_big_mem;
|
|
u8 lio_io_width; // 0:8bit, 1:16bit
|
|
u8 lio_io_count_init;
|
|
u8 lio_rom_width; // 0:8bit, 1:16bit
|
|
u8 lio_rom_count_init;
|
|
u8 lio_clk_period;
|
|
|
|
struct class *class;
|
|
int node_num;
|
|
struct list_head lio_nodes;
|
|
};
|
|
|
|
struct lio_node {
|
|
struct resource* resource;
|
|
void __iomem *base;
|
|
u32 size;
|
|
|
|
dev_t dev_num;
|
|
struct cdev cdev;
|
|
struct device *dev;
|
|
int index;
|
|
struct list_head list;
|
|
|
|
struct lio_dev_s* parent;
|
|
};
|
|
|
|
static struct lio_dev_s g_lio_dev = {0};
|
|
|
|
static int lio_select_node(struct inode *inode, struct file* file)
|
|
{
|
|
struct lio_node* node;
|
|
struct lio_dev_s* lio_dev = &g_lio_dev;
|
|
|
|
list_for_each_entry(node, &lio_dev->lio_nodes, list) {
|
|
if (node->dev_num == inode->i_rdev)
|
|
{
|
|
// printk("open dev %d:%d\n",
|
|
// MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
|
|
file->private_data = node;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
printk("[%s] find lio node fail\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
static int lio_open (struct inode *inode, struct file *file)
|
|
{
|
|
//printk("%s:%d\n", __FUNCTION__, __LINE__);
|
|
return lio_select_node(inode, file);
|
|
}
|
|
|
|
static loff_t lio_llseek (struct file *file, loff_t offset, int whence)
|
|
{
|
|
loff_t ret = -EINVAL;
|
|
struct lio_node* node = (struct lio_node*)(file->private_data);
|
|
|
|
//printk("%s:%d offset:0x%llx whence:%d\n", __FUNCTION__, __LINE__, offset, whence);
|
|
if (offset >= node->size) {
|
|
pr_info("The max size is: 0x%x, offset:0x%llx\n",
|
|
node->size, offset);
|
|
return EFAULT;
|
|
}
|
|
|
|
switch(whence) {
|
|
case SEEK_END:
|
|
case SEEK_DATA:
|
|
case SEEK_HOLE:
|
|
/* unsupported */
|
|
return -EINVAL;
|
|
case SEEK_CUR:
|
|
if (offset == 0)
|
|
return file->f_pos;
|
|
|
|
offset += file->f_pos;
|
|
break;
|
|
case SEEK_SET:
|
|
break;
|
|
}
|
|
|
|
if (offset != file->f_pos) {
|
|
file->f_pos = offset;
|
|
file->f_version = 0;
|
|
ret = offset;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t lio_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
|
|
{
|
|
unsigned long res;
|
|
u8 *kbuf;
|
|
void *ptr;
|
|
int i;
|
|
struct lio_node* node = (struct lio_node*)(file->private_data);
|
|
|
|
//printk("%s:%d size:%ld, offset:0x%llx\n", __FUNCTION__, __LINE__, size, *offset);
|
|
|
|
if ((*offset + size) >= node->size) {
|
|
pr_info("The max size is: 0x%x, size:0x%lx, offset:0x%llx\n",
|
|
node->size, size, *offset);
|
|
return EFAULT;
|
|
}
|
|
|
|
kbuf = kmalloc(size+1, GFP_KERNEL);
|
|
if (!kbuf) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ptr = node->base + *offset;
|
|
for (i = 0; i < size; i++) {
|
|
kbuf[i] = ioread8(ptr + i);
|
|
}
|
|
// pr_info("lio kernel read: 0x%x\n", kbuf[0]);
|
|
res = copy_to_user(buf, kbuf, size);
|
|
if (res != 0) {
|
|
printk("lio copy to user fail.\n");
|
|
goto fail;
|
|
}
|
|
|
|
return size;
|
|
fail:
|
|
kfree(kbuf);
|
|
return -EFAULT;
|
|
}
|
|
|
|
static ssize_t lio_write (struct file *file,
|
|
const char __user *buf, size_t size, loff_t *offset)
|
|
{
|
|
unsigned long res;
|
|
u8 *kbuf;
|
|
void *ptr;
|
|
int i;
|
|
struct lio_node* node = (struct lio_node*)(file->private_data);
|
|
|
|
//printk("%s:%d.\n", __FUNCTION__, __LINE__);
|
|
if ((*offset + size) >= node->size) {
|
|
pr_info("The max size is: 0x%x, size:0x%lx, offset:0x%llx\n",
|
|
node->size, size, *offset);
|
|
return EFAULT;
|
|
}
|
|
|
|
kbuf = kmalloc(size+1, GFP_KERNEL);
|
|
if (!kbuf) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
res = copy_from_user(kbuf, buf, size);
|
|
if (res != 0) {
|
|
printk("lio copy fail.\n");
|
|
goto fail;
|
|
}
|
|
|
|
ptr = node->base + *offset;
|
|
if (node->parent->lio_io_width == 0) {
|
|
for (i = 0; i < size; i++) {
|
|
iowrite8(kbuf[i], ptr+i);
|
|
}
|
|
} else {
|
|
for (i = 0; i < size; i += 2) {
|
|
iowrite16((kbuf[i+1] << 8 | kbuf[i]), ptr+i);
|
|
}
|
|
}
|
|
|
|
return size;
|
|
fail:
|
|
kfree(kbuf);
|
|
return -EFAULT;
|
|
}
|
|
|
|
static int lio_release (struct inode *indoe, struct file *file)
|
|
{
|
|
//printk("%s:%d\n", __FUNCTION__, __LINE__);
|
|
return 0;
|
|
}
|
|
|
|
static struct file_operations fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = lio_open,
|
|
.read = lio_read,
|
|
.write = lio_write,
|
|
.llseek = lio_llseek,
|
|
.release = lio_release
|
|
};
|
|
|
|
static int lio_sync_config_to_reg(struct device_node *of_node,
|
|
struct lio_dev_s* lio_dev)
|
|
{
|
|
#if defined(CONFIG_LOONGSON_2K500)
|
|
#define CHIP_CONF 0x1fe10104
|
|
u32 val = readl((void __iomem *)TO_UNCAC(CHIP_CONF));
|
|
u32 mask;
|
|
u32 tmp;
|
|
#elif defined(CONFIG_LOONGSON_2K1000)
|
|
#define CHIP_CONF 0x1fe00420
|
|
u64 val = readq((void __iomem *)TO_UNCAC(CHIP_CONF));
|
|
u64 mask;
|
|
u64 tmp;
|
|
#else
|
|
u32 val = 0;
|
|
u32 mask;
|
|
u32 tmp;
|
|
#endif
|
|
int i;
|
|
u32 prop;
|
|
int shift;
|
|
int bitwidth;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(g_lio_prop); i++)
|
|
{
|
|
if (of_property_read_u32(of_node, g_lio_prop[i].name, &prop) == 0)
|
|
{
|
|
tmp = prop; // u32 << 63~32 will get 0
|
|
shift = g_lio_prop[i].shift;
|
|
bitwidth = g_lio_prop[i].bitwidth;
|
|
mask = GENMASK(shift + bitwidth - 1, shift);
|
|
val = (val & (~mask)) | ((tmp << shift) & mask);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_LOONGSON_2K500)
|
|
writel(val, (void __iomem *)TO_UNCAC(CHIP_CONF));
|
|
#elif defined(CONFIG_LOONGSON_2K1000)
|
|
writeq(val, (void __iomem *)TO_UNCAC(CHIP_CONF));
|
|
#else
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int lio_record_config(struct device_node *of_node,
|
|
struct lio_dev_s* lio_dev)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
|
|
ret = of_property_read_u32(of_node, "lio-big-mem", &val);
|
|
if (ret < 0) {
|
|
pr_warn("lio lio-big-mem not find.\n");
|
|
val = 0;
|
|
}
|
|
lio_dev->lio_big_mem = val;
|
|
|
|
ret = of_property_read_u32(of_node, "lio-io-width", &val);
|
|
if (ret < 0) {
|
|
pr_warn("lio lio-io-width not find.\n");
|
|
val = 0;
|
|
}
|
|
lio_dev->lio_io_width = val;
|
|
|
|
ret = of_property_read_u32(of_node, "lio-io-count-init", &val);
|
|
if (ret < 0) {
|
|
pr_warn("lio lio-io-count-init not find.\n");
|
|
val = 0;
|
|
}
|
|
lio_dev->lio_io_count_init = val;
|
|
|
|
ret = of_property_read_u32(of_node, "lio-rom-width", &val);
|
|
if (ret < 0) {
|
|
pr_warn("lio lio-rom-width not find.\n");
|
|
val = 0;
|
|
}
|
|
lio_dev->lio_rom_width = val;
|
|
|
|
ret = of_property_read_u32(of_node, "lio-rom-count-init", &val);
|
|
if (ret < 0) {
|
|
pr_warn("lio-rom-count-init not find.\n");
|
|
val = 0;
|
|
}
|
|
lio_dev->lio_rom_count_init = val;
|
|
|
|
ret = of_property_read_u32(of_node, "lio-clk-period", &val);
|
|
if (ret < 0) {
|
|
pr_warn("lio-clk-period not find.\n");
|
|
val = 0;
|
|
}
|
|
lio_dev->lio_clk_period = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lio_release_resource(struct lio_dev_s* lio_dev)
|
|
{
|
|
struct lio_node* node;
|
|
list_for_each_entry(node, &lio_dev->lio_nodes, list) {
|
|
if (node->base != NULL)
|
|
iounmap(node->base);
|
|
|
|
if (node->resource != NULL)
|
|
release_mem_region(node->resource->start,
|
|
resource_size(node->resource));
|
|
|
|
kfree(node);
|
|
}
|
|
|
|
list_del(&lio_dev->lio_nodes);
|
|
|
|
lio_dev = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ls2k1000_lionode_append(struct lio_dev_s* lio_dev, struct resource * mem)
|
|
{
|
|
struct lio_node* node = kzalloc(sizeof(*node), GFP_KERNEL);
|
|
if (node == NULL)
|
|
goto FAIL;
|
|
|
|
node->resource = request_mem_region(mem->start,
|
|
resource_size(mem), lio_dev->name);
|
|
if (node->resource == NULL)
|
|
goto KFREE_NODE;
|
|
|
|
node->base = ioremap(node->resource->start,
|
|
resource_size(node->resource));
|
|
if (node->base == NULL)
|
|
goto RELEASE_REGION;
|
|
|
|
node->size = resource_size(mem);
|
|
node->index = lio_dev->node_num;
|
|
node->parent = lio_dev;
|
|
|
|
list_add_tail(&node->list, &lio_dev->lio_nodes);
|
|
lio_dev->node_num++;
|
|
|
|
printk("[%s] mem_region <0x%llx, 0x%llx> OK\n", __func__,
|
|
mem->start, resource_size(mem));
|
|
return 0;
|
|
RELEASE_REGION:
|
|
printk("[%s] ioremap <0x%llx, 0x%llx> fail\n", __func__,
|
|
node->resource->start, resource_size(node->resource));
|
|
release_mem_region(node->resource->start,
|
|
resource_size(node->resource));
|
|
KFREE_NODE:
|
|
printk("[%s] mem_region <0x%llx, 0x%llx> fail\n", __func__,
|
|
mem->start, resource_size(mem));
|
|
kfree(node);
|
|
FAIL:
|
|
printk("[%s] kzalloc fail\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
static int lio_aquire_resource(struct platform_device* pdev,
|
|
struct lio_dev_s* lio_dev)
|
|
{
|
|
int ret;
|
|
struct resource *mem;
|
|
int i;
|
|
|
|
INIT_LIST_HEAD(&lio_dev->lio_nodes);
|
|
lio_dev->name = pdev->name;
|
|
|
|
for (i = 0; i < LIO_NODE_MAX; i++)
|
|
{
|
|
mem = platform_get_resource(pdev, IORESOURCE_MEM, i);
|
|
if (mem == NULL)
|
|
goto CHECK_NUM;
|
|
|
|
ret = ls2k1000_lionode_append(lio_dev, mem);
|
|
if (ret != 0)
|
|
goto FAIL;
|
|
}
|
|
|
|
return 0;
|
|
|
|
CHECK_NUM:
|
|
printk("[%s] resource empty\n", __func__);
|
|
if (lio_dev->node_num == 0)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
|
|
FAIL:
|
|
lio_release_resource(lio_dev);
|
|
return -1;
|
|
|
|
}
|
|
|
|
static int lio_free_device_region(struct lio_dev_s* lio_dev)
|
|
{
|
|
struct lio_node* node;
|
|
list_for_each_entry(node, &lio_dev->lio_nodes, list) {
|
|
// if (DEV_T_VALID(node->dev_num))
|
|
unregister_chrdev_region(node->dev_num, 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int lio_alloc_device_region(struct lio_dev_s* lio_dev)
|
|
{
|
|
int ret = 0;
|
|
struct lio_node* node;
|
|
list_for_each_entry(node, &lio_dev->lio_nodes, list) {
|
|
ret = alloc_chrdev_region(&node->dev_num,
|
|
MINOR_BASE, 1, DRIVER_NAME);
|
|
if (ret != 0)
|
|
{
|
|
printk("[%s] chrdev fail <%d = %d:%d>\n", __func__,
|
|
ret, MAJOR(node->dev_num), MINOR(node->dev_num));
|
|
goto FAIL;
|
|
}
|
|
printk("alloc chrdev region success.%d:%d\n",
|
|
MAJOR(node->dev_num), MINOR(node->dev_num));
|
|
}
|
|
|
|
return 0;
|
|
FAIL:
|
|
lio_free_device_region(lio_dev);
|
|
return ret;
|
|
}
|
|
|
|
static int lio_remove_device_node(struct lio_dev_s* lio_dev)
|
|
{
|
|
struct lio_node* node;
|
|
|
|
if (lio_dev->class == NULL)
|
|
return 0;
|
|
|
|
list_for_each_entry(node, &lio_dev->lio_nodes, list) {
|
|
if (node->dev != NULL)
|
|
device_destroy(lio_dev->class, node->dev_num);
|
|
|
|
cdev_del(&node->cdev);
|
|
}
|
|
|
|
class_destroy(lio_dev->class);
|
|
lio_dev->class = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lio_create_device_node(struct lio_dev_s* lio_dev)
|
|
{
|
|
int ret;
|
|
struct lio_node* node;
|
|
char name[16] = {0};
|
|
|
|
lio_dev->class = class_create(THIS_MODULE, CLASS_NAME);
|
|
if (lio_dev->class == NULL)
|
|
{
|
|
printk("[%s] class create fail\n", __func__);
|
|
goto FAIL;
|
|
}
|
|
|
|
list_for_each_entry(node, &lio_dev->lio_nodes, list) {
|
|
cdev_init(&node->cdev, &fops);
|
|
ret = cdev_add(&node->cdev, node->dev_num, 1);
|
|
if (ret != 0)
|
|
{
|
|
printk("[%s] cdev_add fail <%d = %d:%d>\n", __func__,
|
|
ret, MAJOR(node->dev_num), MINOR(node->dev_num));
|
|
goto FAIL;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "%s%d",
|
|
DEVICE_NAME_FREFIX, node->index);
|
|
node->dev = device_create(lio_dev->class,
|
|
NULL, node->dev_num, NULL, name);
|
|
if (node->dev == NULL)
|
|
{
|
|
printk("[%s] device_create fail <%d = %s %d:%d>\n", __func__,
|
|
ret, name,
|
|
MAJOR(node->dev_num), MINOR(node->dev_num));
|
|
goto FAIL;
|
|
}
|
|
printk("device create success.%s %d:%d\n",
|
|
name, MAJOR(node->dev_num), MINOR(node->dev_num));
|
|
}
|
|
|
|
return 0;
|
|
|
|
FAIL:
|
|
lio_remove_device_node(lio_dev);
|
|
return -1;
|
|
}
|
|
|
|
static int lio_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct lio_dev_s* lio_dev = &g_lio_dev;
|
|
|
|
lio_record_config(pdev->dev.of_node, lio_dev);
|
|
lio_sync_config_to_reg(pdev->dev.of_node, lio_dev);
|
|
|
|
ret = lio_aquire_resource(pdev, lio_dev);
|
|
if (ret != 0)
|
|
goto FAIL;
|
|
|
|
ret = lio_alloc_device_region(lio_dev);
|
|
if (ret != 0)
|
|
goto RELEASE_RES;
|
|
|
|
ret = lio_create_device_node(lio_dev);
|
|
if (ret != 0)
|
|
goto FREE_DEVREG;
|
|
|
|
return 0;
|
|
|
|
FREE_DEVREG:
|
|
printk("[%s] create device node fail\n", __func__);
|
|
lio_free_device_region(lio_dev);
|
|
RELEASE_RES:
|
|
printk("[%s] alloc device region fail\n", __func__);
|
|
lio_release_resource(lio_dev);
|
|
FAIL:
|
|
printk("[%s] acquire fail\n", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
int lio_remove(struct platform_device *pdev)
|
|
{
|
|
struct lio_dev_s* lio_dev = &g_lio_dev;
|
|
|
|
lio_remove_device_node(lio_dev);
|
|
lio_free_device_region(lio_dev);
|
|
lio_release_resource(lio_dev);
|
|
return 0;
|
|
}
|
|
|
|
const struct of_device_id lio_id_table[] = {
|
|
{.compatible = "loongson,lio"},
|
|
{},
|
|
};
|
|
|
|
struct platform_driver lio_driver = {
|
|
.probe = lio_probe,
|
|
.remove = lio_remove,
|
|
.driver = {
|
|
.name = "ls2k-lio",
|
|
.of_match_table = of_match_ptr(lio_id_table)
|
|
},
|
|
};
|
|
|
|
static int __init lio_init(void)
|
|
{
|
|
return platform_driver_register(&lio_driver);
|
|
}
|
|
|
|
static void __exit lio_exit(void)
|
|
{
|
|
platform_driver_unregister(&lio_driver);
|
|
}
|
|
|
|
module_init(lio_init);
|
|
module_exit(lio_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|