summaryrefslogtreecommitdiff
path: root/drivers/misc/mvf_adc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/mvf_adc.c')
-rw-r--r--drivers/misc/mvf_adc.c381
1 files changed, 258 insertions, 123 deletions
diff --git a/drivers/misc/mvf_adc.c b/drivers/misc/mvf_adc.c
index 63e0f95804f9..662d76667bdc 100644
--- a/drivers/misc/mvf_adc.c
+++ b/drivers/misc/mvf_adc.c
@@ -1,4 +1,5 @@
/* Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2013 Toradex AG
*
* Freescale Faraday Quad ADC driver
*
@@ -24,13 +25,20 @@
#include <linux/mvf_adc.h>
#include <linux/device.h>
#include <linux/cdev.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
#define DRIVER_NAME "mvf-adc"
-#define DRV_VERSION "1.0"
+#define DRV_VERSION "1.2"
+
+#define MVF_ADC_MAX_DEVICES 4
+#define MVF_ADC_MAX ((1 << 12) - 1)
/*
* wait_event_interruptible(wait_queue_head_t suspendq, int suspend_flag)
*/
+static struct class *adc_class;
+static int mvf_adc_major;
struct adc_client {
struct platform_device *pdev;
@@ -46,27 +54,29 @@ static DECLARE_COMPLETION(adc_tsi);
struct adc_device {
struct platform_device *pdev;
- struct platform_device *owner;
+ struct device *hwmon_dev;
struct clk *clk;
struct adc_client *cur;
void __iomem *regs;
spinlock_t lock;
+ struct device *dev;
+ struct cdev cdev;
+
int irq;
};
+static struct adc_device *adc_devices[MVF_ADC_MAX_DEVICES];
+
struct data {
unsigned int res_value;
bool flag;
};
-struct data data_array[7];
-
-static struct adc_device *adc_dev;
+struct data data_array[32];
#define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg)
-static int res_proc(void);
struct adc_client *adc_register(struct platform_device *pdev,
unsigned char channel);
@@ -95,32 +105,7 @@ static void adc_try(struct adc_device *adc)
}
}
-/* channel and sample */
-int adc_start(struct adc_client *client,
- unsigned int channel, unsigned int nr_samples)
-{
- struct adc_device *adc = adc_dev;
- unsigned long flags;
-
- if (!adc) {
- printk(KERN_ERR "%s: failed to find adc\n", __func__);
- return -EINVAL;
- }
-
-
- spin_lock_irqsave(&adc->lock, flags);
-
- client->channel = channel;
-
- if (!adc->cur)
- adc_try(adc_dev);
-
- spin_unlock_irqrestore(&adc->lock, flags);
-
- return 0;
-}
-
-int adc_initiate(struct adc_device *adc_dev)
+static int adc_initiate(struct adc_device *adc_dev)
{
unsigned long reg, tmp, pin;
struct adc_device *adc = adc_dev;
@@ -176,7 +161,6 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
default:
return -EINVAL;
}
- writel(con, adc->regs+ADC_CFG);
break;
@@ -200,7 +184,6 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
default:
return -EINVAL;
}
- writel(con, adc->regs+ADC_CFG);
break;
@@ -224,7 +207,6 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
default:
return -EINVAL;
}
- writel(con, adc->regs+ADC_CFG);
break;
default:
@@ -249,7 +231,6 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
adc_fea->res_mode);
return -EINVAL;
}
- writel(con, adc->regs+ADC_CFG);
/* Defines the sample time duration */
/* clear 4, 9-8 */
@@ -284,20 +265,17 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
adc_fea->sam_time);
return -EINVAL;
}
- writel(con, adc->regs+ADC_CFG);
/* low power configuration */
/* */
switch (adc_fea->lp_con) {
case ADCIOC_LPOFF_SET:
con &= ~CLEAR_ADLPC_BIT;
- writel(con, adc->regs+ADC_CFG);
break;
case ADCIOC_LPON_SET:
con &= ~CLEAR_ADLPC_BIT;
con |= ADLPC_EN;
- writel(con, adc->regs+ADC_CFG);
break;
default:
return -EINVAL;
@@ -308,34 +286,28 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
case ADCIOC_HSON_SET:
con &= ~CLEAR_ADHSC_BIT;
con |= ADHSC_EN;
- writel(con, adc->regs+ADC_CFG);
break;
case ADCIOC_HSOFF_SET:
con &= ~CLEAR_ADHSC_BIT;
- writel(con, adc->regs+ADC_CFG);
break;
default:
return -EINVAL;
}
-
/* voltage reference*/
switch (adc_fea->vol_ref) {
case ADCIOC_VR_VREF_SET:
con &= ~CLEAR_REFSEL_BIT;
- writel(con, adc->regs+ADC_CFG);
break;
case ADCIOC_VR_VALT_SET:
con &= ~CLEAR_REFSEL_BIT;
con |= REFSEL_VALT;
- writel(con, adc->regs+ADC_CFG);
break;
case ADCIOC_VR_VBG_SET:
con &= ~CLEAR_REFSEL_BIT;
con |= REFSEL_VBG;
- writel(con, adc->regs+ADC_CFG);
break;
default:
return -EINVAL;
@@ -345,13 +317,11 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
switch (adc_fea->tri_sel) {
case ADCIOC_SOFTTS_SET:
con &= ~CLEAR_ADTRG_BIT;
- writel(con, adc->regs+ADC_CFG);
break;
case ADCIOC_HARDTS_SET:
con &= ~CLEAR_ADTRG_BIT;
con |= ADTRG_HARD;
- writel(con, adc->regs+ADC_CFG);
break;
default:
return -EINVAL;
@@ -360,8 +330,8 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
/* hardware average select */
switch (adc_fea->ha_sel) {
case ADCIOC_HA_DIS:
+ con &= ~CLEAR_AVGS_BIT;
res &= ~CLEAR_AVGE_BIT;
- writel(con, adc->regs+ADC_GC);
break;
case ADCIOC_HA_SET:
@@ -383,8 +353,6 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
}
res &= ~CLEAR_AVGE_BIT;
res |= AVGEN;
- writel(con, adc->regs+ADC_CFG);
- writel(res, adc->regs+ADC_GC);
break;
default:
@@ -394,13 +362,11 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
/* data overwrite enable */
switch (adc_fea->do_ena) {
case ADCIOC_DOEON_SET:
- con &= ~CLEAR_OVWREN_BIT;
- writel(con, adc->regs+ADC_CFG);
+ con |= OVWREN;
break;
case ADCIOC_DOEOFF_SET:
- con |= OVWREN;
- writel(con, adc->regs+ADC_CFG);
+ con &= ~CLEAR_OVWREN_BIT;
break;
default:
return -EINVAL;
@@ -410,13 +376,11 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
switch (adc_fea->ac_ena) {
case ADCIOC_ADACKENON_SET:
res &= ~CLEAR_ADACKEN_BIT;
- writel(res, adc->regs+ADC_GC);
+ res |= ADACKEN;
break;
case ADCIOC_ADACKENOFF_SET:
res &= ~CLEAR_ADACKEN_BIT;
- res |= ADACKEN;
- writel(res, adc->regs+ADC_GC);
break;
default:
return -EINVAL;
@@ -426,13 +390,11 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
switch (adc_fea->dma_ena) {
case ADCIDC_DMAON_SET:
res &= ~CLEAR_DMAEN_BIT;
- writel(res, adc->regs+ADC_GC);
+ res |= DMAEN;
break;
case ADCIDC_DMAOFF_SET:
res &= ~CLEAR_DMAEN_BIT;
- res |= DMAEN;
- writel(res, adc->regs+ADC_GC);
break;
default:
return -EINVAL;
@@ -442,13 +404,11 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
switch (adc_fea->cc_ena) {
case ADCIOC_CCEOFF_SET:
res &= ~CLEAR_ADCO_BIT;
- writel(res, adc->regs+ADC_GC);
break;
case ADCIOC_CCEON_SET:
res &= ~CLEAR_ADCO_BIT;
res |= ADCON;
- writel(res, adc->regs+ADC_GC);
break;
default:
return -EINVAL;
@@ -459,12 +419,10 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
case ADCIOC_ACFEON_SET:
res &= ~CLEAR_ACFE_BIT;
res |= ACFE;
- writel(res, adc->regs+ADC_GC);
break;
case ADCIOC_ACFEOFF_SET:
res &= ~CLEAR_ACFE_BIT;
- writel(res, adc->regs+ADC_GC);
break;
default:
return -EINVAL;
@@ -475,12 +433,10 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
case ADCIOC_ACFGTON_SET:
res &= ~CLEAR_ACFGT_BIT;
res |= ACFGT;
- writel(res, adc->regs+ADC_GC);
break;
case ADCIOC_ACFGTOFF_SET:
res &= ~CLEAR_ACFGT_BIT;
- writel(res, adc->regs+ADC_GC);
break;
default:
return -EINVAL;
@@ -491,22 +447,175 @@ static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
case ADCIOC_ACRENON_SET:
res &= ~CLEAR_ACREN_BIT;
res |= ACREN;
- writel(res, adc->regs+ADC_GC);
break;
case ADCIOC_ACRENOFF_SET:
res &= ~CLEAR_ACREN_BIT;
- writel(res, adc->regs+ADC_GC);
break;
default: return -ENOTTY;
}
+
+ /* write register once */
+ writel(con, adc->regs+ADC_CFG);
+ writel(res, adc->regs+ADC_GC);
+
+ return 0;
+}
+
+static int adc_convert_wait(struct adc_device *adc_dev, unsigned char channel)
+{
+ INIT_COMPLETION(adc_tsi);
+ adc_try(adc_dev);
+ wait_for_completion(&adc_tsi);
+
+ if (!data_array[channel].flag)
+ return -EINVAL;
+
+ data_array[channel].flag = 0;
+ return data_array[channel].res_value;
+}
+
+/**
+ * mvf_adc_initiate - Initiate a given ADC converter
+ *
+ * @adc: ADC block to initiate
+ */
+int mvf_adc_initiate(unsigned int adc)
+{
+ return adc_initiate(adc_devices[adc]);
+}
+EXPORT_SYMBOL(mvf_adc_initiate);
+
+/**
+ * mvf_adc_set - Configure a given ADC converter
+ *
+ * @adc: ADC block to configure
+ * @adc_fea: Features to enable
+ *
+ * Returns zero on success, error number otherwise
+ */
+int mvf_adc_set(unsigned int adc, struct adc_feature *adc_fea)
+{
+ return adc_set(adc_devices[adc], adc_fea);
+}
+EXPORT_SYMBOL(mvf_adc_set);
+
+/**
+ * mvf_adc_register_and_convert - Register a client and start a convertion
+ *
+ * @adc: ADC block
+ * @channel: Channel to convert
+ *
+ * Returns converted value or error code
+ */
+int mvf_adc_register_and_convert(unsigned int adc, unsigned char channel)
+{
+ struct adc_client *client;
+ int result;
+
+ /* Register client... */
+ client = adc_register(adc_devices[adc]->pdev, channel);
+ if (!client)
+ return -ENOMEM;
+
+ /* Start convertion */
+ result = adc_convert_wait(adc_devices[adc], channel);
+
+ /* Free client */
+ kfree(client);
+
+ return result;
+}
+EXPORT_SYMBOL(mvf_adc_register_and_convert);
+
+/* Temperature sensor (hwmon) */
+
+static ssize_t adc_show_temp(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct adc_device *adc_dev = dev_get_drvdata(dev);
+ struct adc_client *client;
+ unsigned char channel = 26;
+ int temperature;
+ int ret;
+
+ struct adc_feature feature = {
+ .channel = ADC26,
+ .clk_sel = ADCIOC_BUSCLK_SET,
+ .clk_div_num = 1,
+ .res_mode = 12,
+ .sam_time = 6,
+ .lp_con = ADCIOC_LPOFF_SET,
+ .hs_oper = ADCIOC_HSOFF_SET,
+ .vol_ref = ADCIOC_VR_VREF_SET,
+ .tri_sel = ADCIOC_SOFTTS_SET,
+ .ha_sel = ADCIOC_HA_SET,
+ .ha_sam = 8,
+ .do_ena = ADCIOC_DOEOFF_SET,
+ .ac_ena = ADCIOC_ADACKENOFF_SET,
+ .dma_ena = ADCIDC_DMAOFF_SET,
+ .cc_ena = ADCIOC_CCEOFF_SET,
+ .compare_func_ena = ADCIOC_ACFEOFF_SET,
+ .range_ena = ADCIOC_ACRENOFF_SET,
+ .greater_ena = ADCIOC_ACFGTOFF_SET,
+ .result0 = 0,
+ .result1 = 0,
+ };
+
+ /* Initialize device */
+ adc_initiate(adc_dev);
+ ret = adc_set(adc_dev, &feature);
+ if (ret)
+ return ret;
+
+ /* Register client... */
+ client = adc_register(adc_dev->pdev, channel);
+ if (!client)
+ return -ENOMEM;
+
+ /* Do the ADC convertion of the temperature channel */
+ temperature = adc_convert_wait(adc_dev, channel);
+
+ /*
+ * Calculate in degree celsius times 1000)
+ * Using sensor slope of 1.84 mV/°C and
+ * V at 25°C of 696mv
+ */
+ temperature = 25000 - (temperature - 864) * 1000000 / 1840;
+
+ /* Free client */
+ kfree(client);
+
+ return sprintf(buf, "%d\n", temperature);
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, adc_show_temp, NULL, 0);
+
+static struct attribute *mvf_adc_attributes[] = {
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mvf_adc_group = {
+ .attrs = mvf_adc_attributes,
+};
+
+
+static int adc_open(struct inode *inode, struct file *file)
+{
+ struct adc_device *dev = container_of(inode->i_cdev,
+ struct adc_device, cdev);
+
+ file->private_data = dev;
+
return 0;
}
static long adc_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
+ struct adc_device *adc_dev = file->private_data;
void __user *argp = (void __user *)arg;
struct adc_feature feature;
int channel;
@@ -515,18 +624,19 @@ static long adc_ioctl(struct file *file, unsigned int cmd,
return -ENOTTY;
if (copy_from_user(&feature, (struct adc_feature *)argp,
- sizeof(feature))) {
+ sizeof(feature)))
return -EFAULT;
- }
+
+ if (feature.channel > 31)
+ return -EINVAL;
switch (cmd) {
case ADC_INIT:
- adc_initiate(adc_dev);
+ return adc_initiate(adc_dev);
break;
case ADC_CONFIGURATION:
-
- adc_set(adc_dev, &feature);
+ return adc_set(adc_dev, &feature);
break;
case ADC_REG_CLIENT:
@@ -535,13 +645,8 @@ static long adc_ioctl(struct file *file, unsigned int cmd,
break;
case ADC_CONVERT:
- INIT_COMPLETION(adc_tsi);
- adc_try(adc_dev);
- wait_for_completion_interruptible(&adc_tsi);
- if (data_array[feature.channel].flag) {
- feature.result0 = data_array[feature.channel].res_value;
- data_array[feature.channel].flag = 0;
- }
+ feature.result0 = adc_convert_wait(adc_dev, feature.channel);
+
if (copy_to_user((struct adc_feature *)argp, &feature,
sizeof(feature)))
return -EFAULT;
@@ -581,26 +686,6 @@ struct adc_client *adc_register(struct platform_device *pdev,
return client;
}
-
-/*result process */
-static int res_proc(void)
-{
- int con, res;
- struct adc_device *adc = adc_dev;
- con = readl(adc->regs + ADC_CFG);
-
- if ((con & (1 << 2)) == 0) {
- if ((con & (1 << 3)) == 1)
- res = (0xFFF & readl(adc->regs + ADC_R0));
- else
- res = (0xFF & readl(adc->regs + ADC_R0));
- } else
- res = (0x3FF & readl(adc->regs + ADC_R0));
-
- return readl(adc->regs + ADC_R0);
- return res;
-}
-
static irqreturn_t adc_irq(int irq, void *pw)
{
int coco;
@@ -614,7 +699,8 @@ static irqreturn_t adc_irq(int irq, void *pw)
coco = readl(adc->regs + ADC_HS);
if (coco & 1) {
- data_array[client->channel].res_value = res_proc();
+ data_array[client->channel].res_value =
+ readl(adc->regs + ADC_R0);
data_array[client->channel].flag = 1;
complete(&adc_tsi);
}
@@ -625,8 +711,8 @@ exit:
static const struct file_operations adc_fops = {
.owner = THIS_MODULE,
+ .open = adc_open,
.unlocked_ioctl = adc_ioctl,
- .open = NULL,
.read = NULL,
};
@@ -636,11 +722,8 @@ static int __devinit adc_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct adc_device *adc;
struct resource *regs;
- struct cdev *adc_cdev;
- static struct class *adc_class;
+ dev_t devt;
int ret;
- dev_t id;
-
adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL);
if (adc == NULL) {
@@ -686,34 +769,52 @@ static int __devinit adc_probe(struct platform_device *pdev)
goto err_clk;
}
- /* Obtain device numbers and register char device */
- ret = alloc_chrdev_region(&id, 0, 1, "mvf-adc");
+ /* clk enable */
+ clk_enable(adc->clk);
+
+ /* Save device structure by Platform device ID for touch */
+ adc_devices[pdev->id] = adc;
+
+ /* Register temperature sensor */
+ ret = sysfs_create_group(&pdev->dev.kobj, &mvf_adc_group);
if (ret < 0)
- return ret;
+ goto err_clk;
+
+ adc->hwmon_dev = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(adc->hwmon_dev)) {
+ ret = PTR_ERR(adc->hwmon_dev);
+ dev_err(dev, "class registration failed (%d)\n", ret);
+ goto err_sysfs;
+ }
- adc_cdev = cdev_alloc();
- adc_cdev->ops = &adc_fops;
- adc_cdev->owner = THIS_MODULE;
- ret = cdev_add(adc_cdev, id, 1);
+ /* Create character device for ADC */
+ cdev_init(&adc->cdev, &adc_fops);
+ adc->cdev.owner = THIS_MODULE;
+ devt = MKDEV(mvf_adc_major, pdev->id);
+ ret = cdev_add(&adc->cdev, devt, 1);
if (ret < 0)
- return ret;
+ goto err_sysfs;
- adc_class = class_create(THIS_MODULE, "mvf-adc.0");
- if (IS_ERR(adc_class))
- return -1;
+ adc->dev = device_create(adc_class, &pdev->dev, devt,
+ NULL, "mvf-adc.%d", pdev->id);
+ if (IS_ERR(adc->dev)) {
+ dev_err(dev, "failed to create device\n");
+ goto err_cdev;
+ }
- device_create(adc_class, NULL, id, NULL, "mvf-adc.0");
- /* clk enable */
- clk_enable(adc->clk);
/* Associated structures */
platform_set_drvdata(pdev, adc);
- adc_dev = adc;
-
dev_info(dev, "attached adc driver\n");
return 0;
+err_cdev:
+ cdev_del(&adc->cdev);
+
+err_sysfs:
+ sysfs_remove_group(&pdev->dev.kobj, &mvf_adc_group);
+
err_clk:
clk_put(adc->clk);
@@ -728,12 +829,22 @@ err_alloc:
static int __devexit adc_remove(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
struct adc_device *adc = platform_get_drvdata(pdev);
+ dev_info(dev, "remove adc driver\n");
+
+ hwmon_device_unregister(adc->hwmon_dev);
+ sysfs_remove_group(&pdev->dev.kobj, &mvf_adc_group);
+
+ device_destroy(adc_class, adc->dev->devt);
+ cdev_del(&adc->cdev);
+
iounmap(adc->regs);
free_irq(adc->irq, adc);
clk_disable(adc->clk);
clk_put(adc->clk);
+ adc_devices[pdev->id] = NULL;
kfree(adc);
return 0;
@@ -751,21 +862,45 @@ static struct platform_driver adc_driver = {
static int __init adc_init(void)
{
int ret;
+ dev_t dev;
+
+ adc_class = class_create(THIS_MODULE, "mvf-adc");
+ if (IS_ERR(adc_class)) {
+ ret = PTR_ERR(adc_class);
+ printk(KERN_ERR "%s: can't register mvf-adc class\n",__func__);
+ goto err;
+ }
+
+ /* Obtain device numbers and register char device */
+ ret = alloc_chrdev_region(&dev, 0, MVF_ADC_MAX_DEVICES, "mvf-adc");
+ if (ret)
+ {
+ printk(KERN_ERR "%s: can't register character device\n",
+ __func__);
+ goto err_class;
+ }
+ mvf_adc_major = MAJOR(dev);
+
ret = platform_driver_register(&adc_driver);
if (ret)
printk(KERN_ERR "%s: failed to add adc driver\n", __func__);
+ return 0;
+err_class:
+ class_destroy(adc_class);
+err:
return ret;
}
static void __exit adc_exit(void)
{
platform_driver_unregister(&adc_driver);
+ class_destroy(adc_class);
}
module_init(adc_init);
module_exit(adc_exit);
-MODULE_AUTHOR("Xiaojun Wang");
+MODULE_AUTHOR("Xiaojun Wang, Stefan Agner");
MODULE_DESCRIPTION("Vybrid ADC driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(DRV_VERSION);