#include #include #include #include #include #include #include #include #include #include #include #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; u16 val16; 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; if (node->parent->lio_io_width == 0) { for (i = 0; i < size; i++) { kbuf[i] = ioread8(ptr + i); } } else { for (i = 0; i < size; i += 2) { val16 = ioread16(ptr + i); kbuf[i+1] = val16 >> 8; kbuf[i] = val16 & 0xff; } } // 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");