#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 "led.h"

static dev_t devno = 0;
static uint32_t led_major = LED_MAJOR;
static uint32_t led_minor = 0;

static struct class *led_class;


static struct led_chrdev led_cdev[DEV_CNT] = {
    {.pa_dr = 0x0209C000,.pa_gdir = 0x0209C004,.pa_iomuxc_mux =
    0x20E006C,.pa_ccm_ccgrx = 0x20C406C,.pa_iomux_pad =
    0x20E02F8,.led_pin = 4,.clock_offset = 26},
    {.pa_dr = 0x20A8000,.pa_gdir = 0x20A8004,.pa_iomuxc_mux =
    0x20E01E0,.pa_ccm_ccgrx = 0x20C4074,.pa_iomux_pad =
    0x20E046C,.led_pin = 20,.clock_offset = 12},
    {.pa_dr = 0x20A8000,.pa_gdir = 0x20A8004,.pa_iomuxc_mux =
    0x20E01DC,.pa_ccm_ccgrx = 0x20C4074,.pa_iomux_pad =
    0x20E0468,.led_pin = 19,.clock_offset = 12},
};

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

    PDEBUG("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;

    my_led_dev->va_ccm_ccgrx = ioremap(my_led_dev->pa_ccm_ccgrx,4);
    my_led_dev->va_dr = ioremap(my_led_dev->pa_dr,4);
    my_led_dev->va_gdir = ioremap(my_led_dev->pa_gdir,4);
    my_led_dev->va_iomuxc_mux = ioremap(my_led_dev->pa_iomuxc_mux,4);
    my_led_dev->va_iomux_pad = ioremap(my_led_dev->pa_iomux_pad,4);

    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;

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

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

    PDEBUG("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)
{
    struct led_chrdev *my_led_dev = NULL;

    my_led_dev = container_of(inode->i_cdev, struct led_chrdev, dev);


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

    return 0;
}

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


static int __init my_led_init(void)
{
    uint32_t ret = 0;
    uint32_t i = 0;
    dev_t cur_dev = 0;
    printk(KERN_INFO "led  Module Init start\n");

    if(led_major){
        devno = MKDEV(led_major,led_minor);
        ret = register_chrdev_region(devno, DEV_CNT, "led");
    }
    else{

        ret = alloc_chrdev_region(&devno, 0, DEV_CNT, "led");
        led_major = MAJOR(devno);
    }
    if(ret < 0){
        printk(KERN_ERR "chrdev alloc region failed\n");
        return ret;
    }

    printk(KERN_INFO "devno is %u(0x%x)(major:%u,minor:%u)\n",devno,devno,led_major,led_minor);


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

    for(i = 0; i< DEV_CNT; i++)
    {
        cdev_init(&led_cdev[i].dev, &led_fops);
	    led_cdev[i].dev.owner = THIS_MODULE;
        cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);
	    cdev_add(&led_cdev[i].dev, cur_dev, 1);
        device_create(led_class,NULL,cur_dev,NULL,"led_%s",(i == 0)?"red":((i == 1)? "green":"blue"));
    }

    return ret;
}

static void __exit my_led_exit(void)
{
    uint32_t i = 0;
    dev_t cur_dev = 0;

    for(i =0 ; i < DEV_CNT; i++){
        cur_dev = MKDEV(MAJOR(devno), MINOR(devno) + i);
        device_destroy(led_class,cur_dev);
        cdev_del(&led_cdev[i].dev);
    }
    class_destroy(led_class);
    
    
    if(!devno)
        unregister_chrdev_region(devno, DEV_CNT);
    
    printk("LED  Module Exit\n");
}

module_init(my_led_init);
module_exit(my_led_exit);


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

