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.c771
1 files changed, 771 insertions, 0 deletions
diff --git a/drivers/misc/mvf_adc.c b/drivers/misc/mvf_adc.c
new file mode 100644
index 000000000000..63e0f95804f9
--- /dev/null
+++ b/drivers/misc/mvf_adc.c
@@ -0,0 +1,771 @@
+/* Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * Freescale Faraday Quad ADC driver
+ *
+ * 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.
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mvf_adc.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+
+#define DRIVER_NAME "mvf-adc"
+#define DRV_VERSION "1.0"
+
+/*
+ * wait_event_interruptible(wait_queue_head_t suspendq, int suspend_flag)
+ */
+
+struct adc_client {
+ struct platform_device *pdev;
+ struct list_head list;
+ wait_queue_head_t *wait;
+
+ unsigned int result;
+ unsigned int channel;
+};
+
+static LIST_HEAD(client_list);/* Initialization client list head */
+static DECLARE_COMPLETION(adc_tsi);
+
+struct adc_device {
+ struct platform_device *pdev;
+ struct platform_device *owner;
+ struct clk *clk;
+ struct adc_client *cur;
+ void __iomem *regs;
+ spinlock_t lock;
+
+ int irq;
+};
+
+struct data {
+ unsigned int res_value;
+ bool flag;
+};
+
+struct data data_array[7];
+
+static struct adc_device *adc_dev;
+
+#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);
+
+/* select channel and enable conversion */
+static inline void adc_convert(struct adc_device *adc,
+ struct adc_client *client)
+{
+
+ unsigned con = readl(adc->regs + ADC_HC0);
+ con = ADCHC0(client->channel);
+ con |= IRQ_EN;
+ writel(con, adc->regs + ADC_HC0);
+}
+
+/* find next conversion client */
+static void adc_try(struct adc_device *adc)
+{
+ struct adc_client *next = adc->cur;
+ if (!list_empty(&client_list)) {
+ next = list_entry(client_list.next,
+ struct adc_client, list);
+ list_del(client_list.next);
+
+ adc->cur = next;
+ adc_convert(adc, adc->cur);
+ }
+}
+
+/* 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)
+{
+ unsigned long reg, tmp, pin;
+ struct adc_device *adc = adc_dev;
+
+
+ /* PCTL: pin control 5 for Sliding rheostat */
+ pin = readl(adc->regs+ADC_PCTL);
+ pin &= ~CLEPCTL23;
+ pin &= SETPCTL5;
+ writel(pin, adc->regs+ADC_PCTL);
+
+ /* CFG: Feature set */
+ reg = readl(adc->regs+ADC_CFG);
+ reg &= ~CLECFG16;
+ reg |= SETCFG;
+ writel(reg, adc->regs+ADC_CFG);
+
+ /* GC: software trigger */
+ tmp = readl(adc->regs+ADC_GC);
+ tmp = 0;
+ writel(tmp, adc->regs+ADC_GC);
+
+ return 0;
+}
+
+static int adc_set(struct adc_device *adc_dev, struct adc_feature *adc_fea)
+{
+ struct adc_device *adc = adc_dev;
+ int con, res;
+ con = readl(adc->regs+ADC_CFG);
+ res = readl(adc->regs+ADC_GC);
+
+ /* clock select and clock devide */
+ switch (adc_fea->clk_sel) {
+ case ADCIOC_BUSCLK_SET:
+ /* clear 1-0,6-5 */
+ con &= ~CLEAR_CLK_BIT;
+ switch (adc_fea->clk_div_num) {
+ case 1:
+ break;
+ case 2:
+ con |= CLK_DIV2;
+ break;
+ case 4:
+ con |= CLK_DIV4;
+ break;
+ case 8:
+ con |= CLK_DIV8;
+ break;
+ case 16:
+ con |= BUSCLK2_SEL | CLK_DIV8;
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(con, adc->regs+ADC_CFG);
+
+ break;
+
+ case ADCIOC_ALTCLK_SET:
+ /* clear 1-0,6-5 */
+ con &= ~CLEAR_CLK_BIT;
+
+ con |= ALTCLK_SEL;
+ switch (adc_fea->clk_div_num) {
+ case 1:
+ break;
+ case 2:
+ con |= CLK_DIV2;
+ break;
+ case 4:
+ con |= CLK_DIV4;
+ break;
+ case 8:
+ con |= CLK_DIV8;
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(con, adc->regs+ADC_CFG);
+
+ break;
+
+ case ADCIOC_ADACK_SET:
+ /* clear 1-0,6-5 */
+ con &= ~CLEAR_CLK_BIT;
+
+ con |= ADACK_SEL;
+ switch (adc_fea->clk_div_num) {
+ case 1:
+ break;
+ case 2:
+ con |= CLK_DIV2;
+ break;
+ case 4:
+ con |= CLK_DIV4;
+ break;
+ case 8:
+ con |= CLK_DIV8;
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(con, adc->regs+ADC_CFG);
+ break;
+
+ default:
+ pr_debug("adc_ioctl: unsupported ioctl command 0x%x\n",
+ adc_fea->clk_sel);
+ return -EINVAL;
+ }
+
+ /* resolution mode,clear 3-2 */
+ con &= ~CLEAR_MODE_BIT;
+ switch (adc_fea->res_mode) {
+ case 8:
+ break;
+ case 10:
+ con |= BIT10;
+ break;
+ case 12:
+ con |= BIT12;
+ break;
+ default:
+ pr_debug("adc_ioctl: unsupported ioctl resolution mode 0x%x\n",
+ adc_fea->res_mode);
+ return -EINVAL;
+ }
+ writel(con, adc->regs+ADC_CFG);
+
+ /* Defines the sample time duration */
+ /* clear 4, 9-8 */
+ con &= ~CLEAR_LSTC_BIT;
+ switch (adc_fea->sam_time) {
+ case 2:
+ break;
+ case 4:
+ con |= ADSTS_SHORT;
+ break;
+ case 6:
+ con |= ADSTS_NORMAL;
+ break;
+ case 8:
+ con |= ADSTS_LONG;
+ break;
+ case 12:
+ con |= ADLSMP_LONG;
+ break;
+ case 16:
+ con |= ADLSMP_LONG | ADSTS_SHORT;
+ break;
+ case 20:
+ con |= ADLSMP_LONG | ADSTS_NORMAL;
+ break;
+ case 24:
+ con |= ADLSMP_LONG | ADSTS_LONG;
+ break;
+ default:
+ pr_debug("adc_ioctl: unsupported ioctl sample"
+ "time duration 0x%x\n",
+ 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;
+
+ }
+ /* high speed operation */
+ switch (adc_fea->hs_oper) {
+ 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;
+ }
+
+ /* trigger select */
+ 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;
+ }
+
+ /* hardware average select */
+ switch (adc_fea->ha_sel) {
+ case ADCIOC_HA_DIS:
+ res &= ~CLEAR_AVGE_BIT;
+ writel(con, adc->regs+ADC_GC);
+ break;
+
+ case ADCIOC_HA_SET:
+ con &= ~CLEAR_AVGS_BIT;
+ switch (adc_fea->ha_sam) {
+ case 4:
+ break;
+ case 8:
+ con |= AVGS_8;
+ break;
+ case 16:
+ con |= AVGS_16;
+ break;
+ case 32:
+ con |= AVGS_32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ res &= ~CLEAR_AVGE_BIT;
+ res |= AVGEN;
+ writel(con, adc->regs+ADC_CFG);
+ writel(res, adc->regs+ADC_GC);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* data overwrite enable */
+ switch (adc_fea->do_ena) {
+ case ADCIOC_DOEON_SET:
+ con &= ~CLEAR_OVWREN_BIT;
+ writel(con, adc->regs+ADC_CFG);
+ break;
+
+ case ADCIOC_DOEOFF_SET:
+ con |= OVWREN;
+ writel(con, adc->regs+ADC_CFG);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Asynchronous clock output enable */
+ switch (adc_fea->ac_ena) {
+ case ADCIOC_ADACKENON_SET:
+ res &= ~CLEAR_ADACKEN_BIT;
+ writel(res, adc->regs+ADC_GC);
+ break;
+
+ case ADCIOC_ADACKENOFF_SET:
+ res &= ~CLEAR_ADACKEN_BIT;
+ res |= ADACKEN;
+ writel(res, adc->regs+ADC_GC);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* dma enable */
+ switch (adc_fea->dma_ena) {
+ case ADCIDC_DMAON_SET:
+ res &= ~CLEAR_DMAEN_BIT;
+ writel(res, adc->regs+ADC_GC);
+ break;
+
+ case ADCIDC_DMAOFF_SET:
+ res &= ~CLEAR_DMAEN_BIT;
+ res |= DMAEN;
+ writel(res, adc->regs+ADC_GC);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* continue function enable */
+ 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;
+ }
+
+ /* compare function enable */
+ switch (adc_fea->compare_func_ena) {
+ 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;
+ }
+
+ /* greater than enable */
+ switch (adc_fea->greater_ena) {
+ 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;
+ }
+
+ /* range enable */
+ switch (adc_fea->range_ena) {
+ 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;
+ }
+ return 0;
+}
+
+static long adc_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct adc_feature feature;
+ int channel;
+
+ if (_IOC_TYPE(cmd) != 'p')
+ return -ENOTTY;
+
+ if (copy_from_user(&feature, (struct adc_feature *)argp,
+ sizeof(feature))) {
+ return -EFAULT;
+ }
+
+ switch (cmd) {
+ case ADC_INIT:
+ adc_initiate(adc_dev);
+ break;
+
+ case ADC_CONFIGURATION:
+
+ adc_set(adc_dev, &feature);
+ break;
+
+ case ADC_REG_CLIENT:
+ channel = feature.channel;
+ adc_register(adc_dev->pdev, channel);
+ 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;
+ }
+ if (copy_to_user((struct adc_feature *)argp, &feature,
+ sizeof(feature)))
+ return -EFAULT;
+
+ kfree(adc_dev->cur);
+ break;
+
+ default:
+ pr_debug("adc_ioctl: unsupported ioctl command 0x%x\n", cmd);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* client register */
+struct adc_client *adc_register(struct platform_device *pdev,
+ unsigned char channel)
+{
+ struct adc_client *client = kzalloc(sizeof(struct adc_client),
+ GFP_KERNEL);
+
+ WARN_ON(!pdev);
+
+ if (!pdev)
+ return ERR_PTR(-EINVAL);
+
+
+ if (!client) {
+ dev_err(&pdev->dev, "no memory for adc client\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ client->pdev = pdev;
+ client->channel = channel;
+ list_add(&client->list, &client_list);
+
+ 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;
+ struct adc_device *adc = pw;
+ struct adc_client *client = adc->cur;
+
+ if (!client) {
+ dev_warn(&adc->pdev->dev, "%s: no adc pending\n", __func__);
+ goto exit;
+ }
+
+ coco = readl(adc->regs + ADC_HS);
+ if (coco & 1) {
+ data_array[client->channel].res_value = res_proc();
+ data_array[client->channel].flag = 1;
+ complete(&adc_tsi);
+ }
+
+exit:
+ return IRQ_NONE;
+}
+
+static const struct file_operations adc_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = adc_ioctl,
+ .open = NULL,
+ .read = NULL,
+};
+
+/* probe */
+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;
+ int ret;
+ dev_t id;
+
+
+ adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL);
+ if (adc == NULL) {
+ dev_err(dev, "failed to allocate adc_device\n");
+ return -ENOMEM;
+ }
+ adc->pdev = pdev;
+
+ /* Initialize spin_lock */
+ spin_lock_init(&adc->lock);
+
+ adc->irq = platform_get_irq(pdev, 0);
+ if (adc->irq <= 0) {
+ dev_err(dev, "failed to get adc irq\n");
+ ret = -EINVAL;
+ goto err_alloc;
+ }
+
+ ret = request_irq(adc->irq, adc_irq, 0, dev_name(dev), adc);
+ if (ret < 0) {
+ dev_err(dev, "failed to attach adc irq\n");
+ goto err_alloc;
+ }
+
+ adc->clk = clk_get(dev, NULL);
+ if (IS_ERR(adc->clk)) {
+ dev_err(dev, "failed to get adc clock\n");
+ ret = PTR_ERR(adc->clk);
+ goto err_irq;
+ }
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs) {
+ dev_err(dev, "failed to find registers\n");
+ ret = -ENXIO;
+ goto err_clk;
+ }
+
+ adc->regs = ioremap(regs->start, resource_size(regs));
+ if (!adc->regs) {
+ dev_err(dev, "failed to map registers\n");
+ ret = -ENXIO;
+ goto err_clk;
+ }
+
+ /* Obtain device numbers and register char device */
+ ret = alloc_chrdev_region(&id, 0, 1, "mvf-adc");
+ if (ret < 0)
+ return ret;
+
+ adc_cdev = cdev_alloc();
+ adc_cdev->ops = &adc_fops;
+ adc_cdev->owner = THIS_MODULE;
+ ret = cdev_add(adc_cdev, id, 1);
+ if (ret < 0)
+ return ret;
+
+ adc_class = class_create(THIS_MODULE, "mvf-adc.0");
+ if (IS_ERR(adc_class))
+ return -1;
+
+ 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_clk:
+ clk_put(adc->clk);
+
+err_irq:
+ free_irq(adc->irq, adc);
+
+err_alloc:
+ kfree(adc);
+
+ return ret;
+}
+
+static int __devexit adc_remove(struct platform_device *pdev)
+{
+ struct adc_device *adc = platform_get_drvdata(pdev);
+
+ iounmap(adc->regs);
+ free_irq(adc->irq, adc);
+ clk_disable(adc->clk);
+ clk_put(adc->clk);
+ kfree(adc);
+
+ return 0;
+}
+
+static struct platform_driver adc_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = adc_probe,
+ .remove = __devexit_p(adc_remove),
+};
+
+static int __init adc_init(void)
+{
+ int ret;
+ ret = platform_driver_register(&adc_driver);
+ if (ret)
+ printk(KERN_ERR "%s: failed to add adc driver\n", __func__);
+
+ return ret;
+}
+
+static void __exit adc_exit(void)
+{
+ platform_driver_unregister(&adc_driver);
+}
+module_init(adc_init);
+module_exit(adc_exit);
+
+MODULE_AUTHOR("Xiaojun Wang");
+MODULE_DESCRIPTION("Vybrid ADC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRV_VERSION);