/**************************************************************************
 *                     I.MX6 driver goto 使用
 * 说明:
 *     在绝大多数地方,我们都被告诉尽可能不要用goto,甚至都没学过goto,但
 * 这种语法却在内核驱动中普遍使用。
 *
 *                                        2016-4-13 深圳 南山平山村 曾剑锋
 *************************************************************************/


#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>

#define SABRESD_VO_PIN            IMX_GPIO_NR(1, 4)
#define SABRESD_AMPE_PIN          IMX_GPIO_NR(1, 5)
#define SABRESD_SD_PIN            IMX_GPIO_NR(1, 19)
#define SABRESD_DT_PIN            IMX_GPIO_NR(3, 20)

#define SPK_HEIGHT              66
#define SPK_LOW                 67
#define AMP_HEIGHT              68
#define AMP_LOW                 69
#define SD_HEIGHT               70
#define SD_LOW                  71
#define DETECT                  72

#define GPIO_CTRL_DEBUG
#ifdef GPIO_CTRL_DEBUG
    #define mDebug(format, ...) printk("File:%s, Function:%s, Line:%d  "format, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
    #define mDebug(format, ...)
#endif

static int gpioCtrl_open(struct inode *inode, struct file *file)
{
    mDebug("Dev open.\n");

    return 0;
}

static int gpioCtrl_close(struct inode *inode, struct file *file)
{
    mDebug("Dev close.\n");

    return 0;
}

static ssize_t gpioCtrl_read(struct file *file,
                        char __user *buf,
                        size_t count,
                        loff_t *pos)
{
    mDebug("Read data.\n");

    return 0;
}

static long gpioCtrl_ioctl(struct file * file, unsigned int cmd, unsigned long arg) {

    int ret = 0;

    switch ( cmd ) {
    case SPK_HEIGHT :
        gpio_set_value(SABRESD_VO_PIN, 1);
        mDebug("SPK_HEIGHT.\n");
        break;
    case SPK_LOW :
        gpio_set_value(SABRESD_VO_PIN, 0);
        mDebug("SPK_LOW.\n");
        break;
    case AMP_HEIGHT :
        gpio_set_value(SABRESD_AMPE_PIN, 1);
        mDebug("AMP_HEIGHT.\n");
        break;
    case AMP_LOW :
        gpio_set_value(SABRESD_AMPE_PIN, 0);
        mDebug("AMP_LOW.\n");
        break;
    case SD_HEIGHT :
        gpio_set_value(SABRESD_SD_PIN, 1);
        mDebug("SD_HEIGHT.\n");
        break;
    case SD_LOW :
        gpio_set_value(SABRESD_SD_PIN, 0);
        mDebug("SD_LOW.\n");
        break;
    case DETECT :
        ret = gpio_get_value(SABRESD_DT_PIN);
        (*(int *)arg) = ret;
        mDebug("DETECT ret = %d.\n", ret);

        ret = 0;
        break;
    default :
        mDebug("gpioCtrl control error.\n");
        ret =  -1;
        break;
    }
    return ret;
}


struct file_operations fops = {
    .owner      = THIS_MODULE,
    .open       = gpioCtrl_open,
    .release    = gpioCtrl_close,
    .read       = gpioCtrl_read,
    .unlocked_ioctl = gpioCtrl_ioctl,
};

struct miscdevice misc = {
    .minor  = MISC_DYNAMIC_MINOR,
    .name   = "gpioCtrl",
    .fops   = &fops,
};

int __init gpioCtrl_init(void)
{
    int ret;

    ret = gpio_request(SABRESD_VO_PIN, "SABRESD_VO_PIN");
    if ( ret ) {
        mDebug("get SABRESD_VO_PIN gpio FAILED!\n");
        return ret;
    }

    ret = gpio_request(SABRESD_AMPE_PIN, "SABRESD_AMPE_PIN");
    if ( ret ) {
        mDebug("get SABRESD_AMPE_PIN gpio FAILED!\n");
        goto fail1;
    }

    ret = gpio_request(SABRESD_SD_PIN, "SABRESD_SD_PIN");
    if ( ret ) {
        mDebug("get SABRESD_SD_PIN gpio FAILED!\n");
        goto fail2;
    }

    ret = gpio_request(SABRESD_DT_PIN, "SABRESD_DT_PIN");
    if ( ret ) {
        mDebug("get SABRESD_DETECT gpio FAILED!\n");
        goto fail3;
    }

    gpio_direction_output(SABRESD_VO_PIN, 0);
    gpio_direction_output(SABRESD_AMPE_PIN, 1);
    gpio_direction_output(SABRESD_SD_PIN, 1);
    gpio_direction_input(SABRESD_DT_PIN);

    ret = misc_register(&misc);
    if(ret) {
        mDebug("gpioCtrl_misc_register FAILED!\n");
        goto fail4;
    }

    mDebug("gpioCtrl_misc_register over!\n");
    return ret;

fail4:
    gpio_free(SABRESD_DT_PIN);
fail3:
    gpio_free(SABRESD_SD_PIN);
fail2:
    gpio_free(SABRESD_AMPE_PIN);
fail1:
    gpio_free(SABRESD_VO_PIN);
    
    return ret;
}

void __exit gpioCtrl_exit(void)
{
    gpio_set_value(SABRESD_VO_PIN, 0);
    gpio_set_value(SABRESD_AMPE_PIN, 0);
    gpio_set_value(SABRESD_SD_PIN, 0);

    gpio_free(SABRESD_VO_PIN);
    gpio_free(SABRESD_AMPE_PIN);
    gpio_free(SABRESD_SD_PIN);
    gpio_free(SABRESD_DT_PIN);

    misc_deregister(&misc);
}

module_init(gpioCtrl_init);
module_exit(gpioCtrl_exit);

MODULE_LICENSE("GPL");