/* * GSC3280 SoC adc Controller Driver * * Copyright (C) 2013 BLX IC Design Corp.,Ltd. * Author: Davied, apple_guet@126.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ #include #include #include #include #ifdef CONFIG_ADC_DEV_DEBUG #define DBG(msg...) do { \ printk(KERN_INFO msg); \ } while (0) #else #define DBG(msg...) do { } while(0) #endif #define ADC_DEV_MAX 16 /* 16 ADCs should be enough for everyone... */ #define ADC_DEV_BUSY 0 #define ADC_DEV_IOC_MAGIC 'a' #define ADC_DEV_IOC_MAXNR 2 #define ADC_DEV_CON_PBAT _IOR(ADC_DEV_IOC_MAGIC, 0, int) #define ADC_DEV_CON_CHX _IOWR(ADC_DEV_IOC_MAGIC, 1, int) static dev_t adc_devt; static int adc_dev_open(struct inode *inode, struct file *file) { int err; struct adc_core_dev *adc = container_of(inode->i_cdev, struct adc_core_dev, char_dev); if (test_and_set_bit_lock(ADC_DEV_BUSY, &adc->flags)) return -EBUSY; file->private_data = adc; err = adc->ops->open ? adc->ops->open(adc->dev.parent) : 0; if (err == 0) { return 0; } /* something has gone wrong */ clear_bit_unlock(ADC_DEV_BUSY, &adc->flags); return err; } static long adc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0, channel = 0; unsigned short adc_cmd = 0; struct adc_core_dev *adc = file->private_data; void __user *argp = (void __user *)arg; int __user *p = argp; err = mutex_lock_interruptible(&adc->ops_lock); if (err) return err; if ((_IOC_TYPE(cmd) != ADC_DEV_IOC_MAGIC) || (_IOC_NR(cmd) > ADC_DEV_IOC_MAXNR)) { err = -ENOTTY; goto exit; } switch(cmd) { case ADC_DEV_CON_PBAT: adc_cmd = CMD_AD_CON_PBAT; break; case ADC_DEV_CON_CHX: if (get_user(channel, p)) { err = -EFAULT; goto exit; } switch (channel) { case 0: adc_cmd = CMD_AD_CON_CH0; break; case 1: adc_cmd = CMD_AD_CON_CH1; break; case 2: adc_cmd = CMD_AD_CON_CH2; break; case 3: adc_cmd = CMD_AD_CON_CH3; break; } break; default: err = -ENOTTY; goto exit; } DBG("adc_cmd = 0x%x\n", adc_cmd); put_user(adc->ops->convert(adc_cmd), p); exit: mutex_unlock(&adc->ops_lock); return err; } static int adc_dev_release(struct inode *inode, struct file *file) { struct adc_core_dev *adc = file->private_data; if (adc->ops->release) adc->ops->release(adc->dev.parent); clear_bit_unlock(ADC_DEV_BUSY, &adc->flags); return 0; } static const struct file_operations adc_dev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .unlocked_ioctl = adc_dev_ioctl, .open = adc_dev_open, .release = adc_dev_release, }; void adc_dev_prepare(struct adc_core_dev *adc) { if (!adc_devt) { DBG("!!!!!!adc_devt = 0!!!!!!\n"); return; } if (adc->id >= ADC_DEV_MAX) { DBG("!!!!!!adc dev prepare error,id too many!!!!!!\n"); pr_debug("%s: too many ADC devices\n", adc->name); return; } adc->dev.devt = MKDEV(MAJOR(adc_devt), adc->id); cdev_init(&adc->char_dev, &adc_dev_fops); adc->char_dev.owner = adc->owner; } void adc_dev_add_device(struct adc_core_dev *adc) { if (cdev_add(&adc->char_dev, adc->dev.devt, 1)) { DBG("!!!!!!cdev add error.!!!!!!\n"); printk(KERN_WARNING "%s: failed to add char device %d:%d\n", adc->name, MAJOR(adc_devt), adc->id); } else { pr_debug("%s: dev (%d:%d)\n", adc->name, MAJOR(adc_devt), adc->id); } } void adc_dev_del_device(struct adc_core_dev *adc) { if (adc->dev.devt) cdev_del(&adc->char_dev); } void __init adc_dev_init(void) { int err; err = alloc_chrdev_region(&adc_devt, 0, ADC_DEV_MAX, "adc"); if (err < 0) { DBG("!!!!!!alloc chrdev region error!!!!!!\n"); printk(KERN_ERR "%s: failed to allocate char dev region\n", __FILE__); } } void __exit adc_dev_exit(void) { if (adc_devt) unregister_chrdev_region(adc_devt, ADC_DEV_MAX); }