diff options
Diffstat (limited to 'drivers/misc/mvf_adc.c')
-rw-r--r-- | drivers/misc/mvf_adc.c | 381 |
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); |