177 lines
4.0 KiB
C
177 lines
4.0 KiB
C
|
/*
|
||
|
* 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 <linux/module.h>
|
||
|
#include <linux/rtc.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/adc-core.h>
|
||
|
|
||
|
|
||
|
#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);
|
||
|
}
|
||
|
|