Files
openloongarch-linux-5.10/drivers/misc/ls2x-lio.c
T
tao89959 b212174b4f Add lscanfd.ko (6965e5fa1627)
lscanfd.ko is in driver/net/can/ls_can.

usage:
insmod lscanfd.ko
insmod lscanfd_platform.ko
2024-08-12 20:22:20 +08:00

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");