/* * wm831x-auxadc.c -- AUXADC for Wolfson WM831x PMICs * * Copyright 2009-2011 Wolfson Microelectronics PLC. * * Author: Mark Brown <broonie@opensource.wolfsonmicro.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/kernel.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/mfd/core.h> #include <linux/slab.h> #include <linux/list.h> #include <linux/mfd/wm831x/core.h> #include <linux/mfd/wm831x/pdata.h> #include <linux/mfd/wm831x/irq.h> #include <linux/mfd/wm831x/auxadc.h> #include <linux/mfd/wm831x/otp.h> #include <linux/mfd/wm831x/regulator.h> struct wm831x_auxadc_req { struct list_head list; enum wm831x_auxadc input; int val; struct completion done; }; static int wm831x_auxadc_read_irq(struct wm831x *wm831x, enum wm831x_auxadc input) { struct wm831x_auxadc_req *req; int ret; bool ena = false; req = kzalloc(sizeof(*req), GFP_KERNEL); if (!req) return -ENOMEM; init_completion(&req->done); req->input = input; req->val = -ETIMEDOUT; mutex_lock(&wm831x->auxadc_lock); /* Enqueue the request */ list_add(&req->list, &wm831x->auxadc_pending); ena = !wm831x->auxadc_active; if (ena) { ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, WM831X_AUX_ENA); if (ret != 0) { dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret); goto out; } } /* Enable the conversion if not already running */ if (!(wm831x->auxadc_active & (1 << input))) { ret = wm831x_set_bits(wm831x, WM831X_AUXADC_SOURCE, 1 << input, 1 << input); if (ret != 0) { dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret); goto out; } wm831x->auxadc_active |= 1 << input; } /* We convert at the fastest rate possible */ if (ena) { ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_CVT_ENA | WM831X_AUX_RATE_MASK, WM831X_AUX_CVT_ENA | WM831X_AUX_RATE_MASK); if (ret != 0) { dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret); goto out; } } mutex_unlock(&wm831x->auxadc_lock); /* Wait for an interrupt */ wait_for_completion_timeout(&req->done, msecs_to_jiffies(500)); mutex_lock(&wm831x->auxadc_lock); list_del(&req->list); ret = req->val; out: mutex_unlock(&wm831x->auxadc_lock); kfree(req); return ret; } static irqreturn_t wm831x_auxadc_irq(int irq, void *irq_data) { struct wm831x *wm831x = irq_data; struct wm831x_auxadc_req *req; int ret, input, val; ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); if (ret < 0) { dev_err(wm831x->dev, "Failed to read AUXADC data: %d\n", ret); return IRQ_NONE; } input = ((ret & WM831X_AUX_DATA_SRC_MASK) >> WM831X_AUX_DATA_SRC_SHIFT) - 1; if (input == 14) input = WM831X_AUX_CAL; val = ret & WM831X_AUX_DATA_MASK; mutex_lock(&wm831x->auxadc_lock); /* Disable this conversion, we're about to complete all users */ wm831x_set_bits(wm831x, WM831X_AUXADC_SOURCE, 1 << input, 0); wm831x->auxadc_active &= ~(1 << input); /* Turn off the entire convertor if idle */ if (!wm831x->auxadc_active) wm831x_reg_write(wm831x, WM831X_AUXADC_CONTROL, 0); /* Wake up any threads waiting for this request */ list_for_each_entry(req, &wm831x->auxadc_pending, list) { if (req->input == input) { req->val = val; complete(&req->done); } } mutex_unlock(&wm831x->auxadc_lock); return IRQ_HANDLED; } static int wm831x_auxadc_read_polled(struct wm831x *wm831x, enum wm831x_auxadc input) { int ret, src, timeout; mutex_lock(&wm831x->auxadc_lock); ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, WM831X_AUX_ENA); if (ret < 0) { dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret); goto out; } /* We force a single source at present */ src = input; ret = wm831x_reg_write(wm831x, WM831X_AUXADC_SOURCE, 1 << src); if (ret < 0) { dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret); goto out; } ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA); if (ret < 0) { dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret); goto disable; } /* If we're not using interrupts then poll the * interrupt status register */ timeout = 5; while (timeout) { msleep(1); ret = wm831x_reg_read(wm831x, WM831X_INTERRUPT_STATUS_1); if (ret < 0) { dev_err(wm831x->dev, "ISR 1 read failed: %d\n", ret); goto disable; } /* Did it complete? */ if (ret & WM831X_AUXADC_DATA_EINT) { wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1, WM831X_AUXADC_DATA_EINT); break; } else { dev_err(wm831x->dev, "AUXADC conversion timeout\n"); ret = -EBUSY; goto disable; } } ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA); if (ret < 0) { dev_err(wm831x->dev, "Failed to read AUXADC data: %d\n", ret); goto disable; } src = ((ret & WM831X_AUX_DATA_SRC_MASK) >> WM831X_AUX_DATA_SRC_SHIFT) - 1; if (src == 14) src = WM831X_AUX_CAL; if (src != input) { dev_err(wm831x->dev, "Data from source %d not %d\n", src, input); ret = -EINVAL; } else { ret &= WM831X_AUX_DATA_MASK; } disable: wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, 0); out: mutex_unlock(&wm831x->auxadc_lock); return ret; } /** * wm831x_auxadc_read: Read a value from the WM831x AUXADC * * @wm831x: Device to read from. * @input: AUXADC input to read. */ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input) { return wm831x->auxadc_read(wm831x, input); } EXPORT_SYMBOL_GPL(wm831x_auxadc_read); /** * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC * * @wm831x: Device to read from. * @input: AUXADC input to read. */ int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input) { int ret; ret = wm831x_auxadc_read(wm831x, input); if (ret < 0) return ret; ret *= 1465; return ret; } EXPORT_SYMBOL_GPL(wm831x_auxadc_read_uv); void wm831x_auxadc_init(struct wm831x *wm831x) { int ret; mutex_init(&wm831x->auxadc_lock); INIT_LIST_HEAD(&wm831x->auxadc_pending); if (wm831x->irq) { wm831x->auxadc_read = wm831x_auxadc_read_irq; ret = request_threaded_irq(wm831x_irq(wm831x, WM831X_IRQ_AUXADC_DATA), NULL, wm831x_auxadc_irq, 0, "auxadc", wm831x); if (ret < 0) { dev_err(wm831x->dev, "AUXADC IRQ request failed: %d\n", ret); wm831x->auxadc_read = NULL; } } if (!wm831x->auxadc_read) wm831x->auxadc_read = wm831x_auxadc_read_polled; }