#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h> 
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>



struct led_chrdev {
    unsigned int __iomem *va_dr;
    unsigned int __iomem *va_gdir;
    unsigned int __iomem *va_iomuxc_mux;
    unsigned int __iomem *va_ccm_ccgrx;
    unsigned int __iomem *va_iomux_pad;
    unsigned int led_pin;
    unsigned int clock_offset;
    struct cdev dev;
    dev_t my_devno;
};

static struct class *led_class = NULL;

static int led_open(struct inode *inode, struct file *filp) 
{
    uint32_t val = 0;
    struct led_chrdev *my_led_dev = NULL;

    printk("led open %d:%d...\n",MAJOR(inode->i_rdev),MINOR(inode->i_rdev));
    my_led_dev = container_of(inode->i_cdev, struct led_chrdev, dev);

    filp->private_data = my_led_dev;

    val = ioread32(my_led_dev->va_ccm_ccgrx);
    val &= ~(3 << my_led_dev->clock_offset);
    val |= 3 << my_led_dev->clock_offset;

    iowrite32(val, my_led_dev->va_ccm_ccgrx);
    iowrite32(5, my_led_dev->va_iomuxc_mux);
    iowrite32(0x1F838, my_led_dev->va_iomux_pad);

    val = ioread32(my_led_dev->va_gdir);
    val &= ~(1 << my_led_dev->led_pin);
    val |= (1 << my_led_dev->led_pin);
    iowrite32(val, my_led_dev->va_gdir); 

    val = ioread32(my_led_dev->va_dr);
    val |= (0x01 << my_led_dev->led_pin);
    iowrite32(val, my_led_dev->va_dr);
    
    return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return cnt;
}

static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
    uint32_t val = 0;
    uint32_t ret = 0;
    struct led_chrdev *my_led_dev = NULL;

    printk("led write...\n");
    my_led_dev = filp->private_data;

    kstrtoul_from_user(buf,cnt,10,&ret);

    //printk("ret = %u\n",ret);

    val = ioread32(my_led_dev->va_dr);
    
    if (ret)
        val &= ~(0x01 << my_led_dev->led_pin);
    else
        val |= (0x01 << my_led_dev->led_pin);

    iowrite32(val, my_led_dev->va_dr);
    *offt += cnt;
        
    return cnt;

}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static struct file_operations led_fops = {
.owner = THIS_MODULE, 
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};


static struct platform_device_id led_pdev_ids[] = {
    {.name = "led_pdev"},
    {}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);


int led_plat_probe(struct platform_device *pdev)
{
    uint32_t val = 0;
    int ret = 0; 
    dev_t devno = 0;
    struct led_chrdev *my_led_dev = NULL;
    struct device_node *dev_node = NULL;
    char *compatible_str = NULL;

    unsigned int *led_hwinfo;

    dev_node = dev_of_node(&(pdev->dev));

    struct resource *mem_dr;
    struct resource *mem_gdir;
    struct resource *mem_iomuxc_mux;
    struct resource *mem_ccm_ccgrx;
    struct resource *mem_iomux_pad;
    struct resource t_mem_dr;
    struct resource t_mem_gdir;
    struct resource t_mem_iomuxc_mux;
    struct resource t_mem_ccm_ccgrx;
    struct resource t_mem_iomux_pad;
    if(NULL == dev_node){
        printk(KERN_INFO"probe frome normal platform device\n");
    
        led_hwinfo = dev_get_platdata(&(pdev->dev));

        mem_dr = platform_get_resource(pdev,IORESOURCE_MEM,0);
        mem_gdir = platform_get_resource(pdev,IORESOURCE_MEM,1);
        mem_iomuxc_mux = platform_get_resource(pdev,IORESOURCE_MEM,2);
        mem_ccm_ccgrx = platform_get_resource(pdev,IORESOURCE_MEM,3);
        mem_iomux_pad = platform_get_resource(pdev,IORESOURCE_MEM,4);
    }
    else{
        printk(KERN_INFO"probe frome device tree\n");

        led_hwinfo = devm_kzalloc(&(pdev->dev),2*sizeof(unsigned int), GFP_KERNEL);
        if(!led_hwinfo){
            printk(KERN_ERR"devm_kzalloc failed\n");
            return -ENOMEM;
        }
            
        if (0 != of_property_read_u32_array(dev_node, "hw_info", led_hwinfo, 2)){
            printk(KERN_ERR"read hw_info err\n");
            return -ENODATA;
        }

        of_address_to_resource(dev_node,3,&t_mem_dr);
        of_address_to_resource(dev_node,4,&t_mem_gdir);
        of_address_to_resource(dev_node,1,&t_mem_iomuxc_mux);
        of_address_to_resource(dev_node,0,&t_mem_ccm_ccgrx);
        of_address_to_resource(dev_node,2,&t_mem_iomux_pad);
        mem_dr = &t_mem_dr;
        mem_gdir = &t_mem_gdir;
        mem_iomuxc_mux = &t_mem_iomuxc_mux;
        mem_ccm_ccgrx = &t_mem_ccm_ccgrx;
        mem_iomux_pad = &t_mem_iomux_pad;

        if(0 != of_property_read_string(dev_node,"compatible",&compatible_str)){
            printk(KERN_ERR"read compatible err\n");
            return -ENODATA;
        }
        printk(KERN_INFO"compatible = %s\n",compatible_str);
        if(0 == strcmp(compatible_str, "fire,rgb_led_red")){
            printk(KERN_INFO"red led\n");
            pdev->id = 0;
        }
        else if(0 == strcmp(compatible_str, "fire,rgb_led_green")){
            printk(KERN_INFO"green led\n");
            pdev->id = 1;
        }
        else{
            printk(KERN_INFO"blue led\n");
            pdev->id = 2;
        }
    }

    
    my_led_dev = devm_kzalloc(&(pdev->dev),sizeof(struct led_chrdev), GFP_KERNEL);
    if(!my_led_dev)
        return -ENOMEM;

    my_led_dev->led_pin = led_hwinfo[0];
    my_led_dev->clock_offset = led_hwinfo[1];

    my_led_dev->va_ccm_ccgrx = ioremap(mem_ccm_ccgrx->start,4);
    my_led_dev->va_dr = ioremap(mem_dr->start,4);
    my_led_dev->va_gdir = ioremap(mem_gdir->start,4);
    my_led_dev->va_iomuxc_mux = ioremap(mem_iomuxc_mux->start,4);
    my_led_dev->va_iomux_pad = ioremap(mem_iomux_pad->start,4);

    printk(KERN_INFO"hw_info:%u,%d\n",my_led_dev->led_pin,my_led_dev->clock_offset);
    printk(KERN_INFO"reg:0x%x,0x%x,0x%x,0x%x,0x%x,\n",mem_ccm_ccgrx->start,\
                                                      mem_dr->start,\
                                                      mem_gdir->start,\
                                                      mem_iomuxc_mux->start,\
                                                      mem_iomux_pad->start);

    ret = alloc_chrdev_region(&devno, pdev->id, 1, "led");
    if(ret < 0){
        printk(KERN_ERR "chrdev alloc region failed\n");
        return ret;
    }
    printk(KERN_INFO "devno is %u(major:%u,minor:%u)\n",devno,MAJOR(devno), MINOR(devno));
    my_led_dev->my_devno = devno;
    
    cdev_init(&my_led_dev->dev, &led_fops);
	my_led_dev->dev.owner = THIS_MODULE;
    
	ret = cdev_add(&my_led_dev->dev, devno, 1);
    if(ret < 0){
        printk(KERN_ERR "chrdev add failed\n");
        goto add_err;
    }
    device_create(led_class,NULL,devno,NULL,"led_%s",(pdev->id == 0)?"red":((pdev->id == 1)? "green":"blue"));

    platform_set_drvdata(pdev, my_led_dev);
    
    return 0;

add_err:
    unregister_chrdev_region(my_led_dev, 1);
    return ret;

}

int led_plat_remove(struct platform_device *pdev)
{
    dev_t cur_dev;
    struct led_chrdev *my_led_dev = platform_get_drvdata(pdev);

    printk("led platform driver remove\n");

    iounmap(my_led_dev->va_ccm_ccgrx);
    iounmap(my_led_dev->va_dr);
    iounmap(my_led_dev->va_gdir);
    iounmap(my_led_dev->va_iomuxc_mux);
    iounmap(my_led_dev->va_iomux_pad);
    cur_dev = my_led_dev->my_devno;
    cdev_del(&my_led_dev->dev);
    device_destroy(led_class, cur_dev);
    unregister_chrdev_region(cur_dev, 1);

    return 0;
}

static const struct of_device_id rgb_led[] = {
    {.compatible = "fire,rgb_led_red"},
    {.compatible = "fire,rgb_led_green"},
    {.compatible = "fire,rgb_led_blue"},
    {/* sentinel */}
    };


static struct platform_driver led_pdrv = {
    .probe = led_plat_probe,
    .remove = led_plat_remove,
    .driver = {
        .name = "led_pdev",
        .owner = THIS_MODULE,
        .of_match_table = rgb_led,
    },
    .id_table = led_pdev_ids,
};


static int __init led_plat_drv_init(void)
{
    printk("led platform driver init\n");

    
    led_class = class_create(THIS_MODULE, "led_class");
    if(IS_ERR(led_class))
    {
        printk(KERN_ERR" create class faild!/n");
        return -EBUSY;
    }
    platform_driver_register(&led_pdrv);

    return 0;
}

static void __exit led_plat_drv_exit(void)
{

    printk("led platform driver exit\n");

    platform_driver_unregister(&led_pdrv);
    class_destroy(led_class);
}


module_init(led_plat_drv_init);
module_exit(led_plat_drv_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchao");



