summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>2012-02-09 22:35:24 +0100
committerUwe Kleine-König <u.kleine-koenig@pengutronix.de>2014-10-05 10:29:40 +0200
commitcff80a8be4b1ca6f731c4055a8345382c5afd9a2 (patch)
tree549cbc03e90c3224c797c163adf9a3a0a6f0c9b4
parentef91697ce8ccb174e1d317dbd2aa93716f203e18 (diff)
downloadlinux-cff80a8be4b1ca6f731c4055a8345382c5afd9a2.tar.gz
linux-cff80a8be4b1ca6f731c4055a8345382c5afd9a2.tar.xz
hwmon/efm32-adc: new driver
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/efm32-adc.c321
3 files changed, 332 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index f00d048aa583..ac207d2f94e5 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -391,6 +391,16 @@ config SENSORS_DA9055
This driver can also be built as a module. If so, the module
will be called da9055-hwmon.
+config SENSORS_EFM32_ADC
+ tristate "Energy Micro EFM32 ADC"
+ depends on OF && ARM && (ARCH_EFM32 || COMPILE_TEST)
+ help
+ If you say yes here you get support for Energy Micro's ADC
+ build into their EFM32 SoCs
+
+ This driver can also be built as a module. If so, the module
+ will be called efm32-adc.
+
config SENSORS_I5K_AMB
tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
depends on PCI
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index be28152c9848..a82502dff716 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
obj-$(CONFIG_SENSORS_DME1737) += dme1737.o
obj-$(CONFIG_SENSORS_DS620) += ds620.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
+obj-$(CONFIG_SENSORS_EFM32_ADC) += efm32-adc.o
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
diff --git a/drivers/hwmon/efm32-adc.c b/drivers/hwmon/efm32-adc.c
new file mode 100644
index 000000000000..0b8c4f7ffe7f
--- /dev/null
+++ b/drivers/hwmon/efm32-adc.c
@@ -0,0 +1,321 @@
+#define DEBUG
+/*
+ * Energy Micro EFM32 adc
+ *
+ * Copyright (C) 2012 Uwe Kleine-Koenig for Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#include <linux/platform_device.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/err.h>
+
+#define DRIVER_NAME "efm32-adc"
+
+#define ADC_CTRL 0x000
+
+#define ADC_CMD 0x004
+#define ADC_CMD_SINGLESTART 0x00000001
+#define ADC_CMD_SINGLESTOP 0x00000002
+#define ADC_CMD_SCANSTART 0x00000004
+#define ADC_CMD_SCANSTOP 0x00000008
+
+#define ADC_STATUS 0x008
+#define ADC_STATUS_SINGLEDV 0x00010000
+#define ADC_SINGLECTRL 0x00c
+#define ADC_SINGLEDATA 0x024
+
+#define ADC_IEN 0x014
+#define ADC_IF 0x018
+#define ADC_IFC 0x020
+#define ADC_IF_SINGLE 0x00000001
+
+struct efm32_adc_ddata {
+ struct device *hwmondev;
+ void __iomem *base;
+ struct clk *clk;
+ unsigned int irq;
+ spinlock_t lock;
+ unsigned int busy;
+};
+
+static void efm32_adc_write32(struct efm32_adc_ddata *ddata,
+ u32 value, unsigned offset)
+{
+ writel_relaxed(value, ddata->base + offset);
+}
+
+static u32 efm32_adc_read32(struct efm32_adc_ddata *ddata, unsigned offset)
+{
+ return readl_relaxed(ddata->base + offset);
+}
+
+static ssize_t efm32_adc_show_name(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ return sprintf(buf, "efm32\n");
+}
+
+struct efm32_adc_irqdata {
+ struct efm32_adc_ddata *ddata;
+ struct completion done;
+};
+
+static irqreturn_t efm32_adc_irq(int irq, void *data)
+{
+ struct efm32_adc_irqdata *irqdata = data;
+ u32 iflag = efm32_adc_read32(irqdata->ddata, ADC_IF);
+
+ if (iflag & ADC_IF_SINGLE) {
+ efm32_adc_write32(irqdata->ddata, ADC_IF_SINGLE, ADC_IFC);
+ complete(&irqdata->done);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int efm32_adc_read_single(struct device *dev,
+ struct device_attribute *devattr, unsigned int *val)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct efm32_adc_ddata *ddata = platform_get_drvdata(pdev);
+ int ret;
+ struct efm32_adc_irqdata irqdata = { .ddata = ddata, };
+ u32 status;
+ unsigned long freq;
+
+ ret = clk_prepare(ddata->clk);
+ if (ret < 0) {
+ dev_dbg(ddata->hwmondev, "failed to enable clk (%d)\n", ret);
+ return ret;
+ }
+
+ spin_lock_irq(&ddata->lock);
+ if (ddata->busy) {
+ dev_dbg(ddata->hwmondev, "busy\n");
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ init_completion(&irqdata.done);
+
+ ret = clk_enable(ddata->clk);
+ if (ret < 0) {
+ dev_dbg(ddata->hwmondev, "failed to enable clk (%d)\n", ret);
+ goto out_unlock;
+ }
+ freq = clk_get_rate(ddata->clk);
+
+ efm32_adc_write32(ddata,
+ ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP, ADC_CMD);
+ efm32_adc_write32(ddata, ((freq - 1) / 1000000) << 16 |
+ ((freq / 400000) - 1) << 8, ADC_CTRL);
+ efm32_adc_write32(ddata, 0x800, ADC_SINGLECTRL);
+ efm32_adc_write32(ddata, ADC_IF_SINGLE, ADC_IFC);
+ efm32_adc_write32(ddata, ADC_CMD_SINGLESTART, ADC_CMD);
+
+ ret = request_irq(ddata->irq, efm32_adc_irq, 0, DRIVER_NAME, &irqdata);
+ if (ret) {
+ efm32_adc_write32(ddata, ADC_CMD_SINGLESTOP, ADC_CMD);
+ goto out_clkoff;
+ }
+
+ efm32_adc_write32(ddata, ADC_IF_SINGLE, ADC_IEN);
+
+ ddata->busy = 1;
+
+ spin_unlock_irq(&ddata->lock);
+
+ ret = wait_for_completion_interruptible_timeout(&irqdata.done, 2 * HZ);
+
+ spin_lock_irq(&ddata->lock);
+
+ efm32_adc_write32(ddata, 0, ADC_IEN);
+ free_irq(ddata->irq, &irqdata);
+
+ if (ret < 0)
+ goto done_out_unlock;
+
+ status = efm32_adc_read32(ddata, ADC_STATUS);
+ if (status & ADC_STATUS_SINGLEDV) {
+ *val = efm32_adc_read32(ddata, ADC_SINGLEDATA);
+ ret = 0;
+ } else
+ ret = -ETIMEDOUT;
+
+done_out_unlock:
+ ddata->busy = 0;
+out_clkoff:
+ clk_disable(ddata->clk);
+out_unlock:
+ spin_unlock_irq(&ddata->lock);
+
+ clk_unprepare(ddata->clk);
+
+ return ret;
+}
+
+static ssize_t efm32_adc_read_chan(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ unsigned int val;
+ int ret = efm32_adc_read_single(dev, devattr, &val);
+
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t efm32_adc_read_temp(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ unsigned int val;
+ int ret = efm32_adc_read_single(dev, devattr, &val);
+ /*
+ * XXX: get these via pdata or read them from the device information
+ * registers
+ */
+ unsigned temp0 = 0x19 * 1000;
+ unsigned adc0 = 0x910;
+
+ if (ret)
+ return ret;
+
+ val = temp0 + DIV_ROUND_CLOSEST((adc0 - val) * 10000, 63);
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, efm32_adc_show_name, NULL);
+static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, efm32_adc_read_chan, NULL, 8);
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, efm32_adc_read_temp, NULL, 8);
+
+static struct attribute *efm32_adc_attr[] = {
+ &dev_attr_name.attr,
+ &sensor_dev_attr_in8_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group efm32_adc_group = {
+ .attrs = efm32_adc_attr,
+};
+
+static int efm32_adc_probe(struct platform_device *pdev)
+{
+ struct efm32_adc_ddata *ddata;
+ struct resource *res;
+ int ret;
+
+ ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENXIO;
+ dev_dbg(&pdev->dev, "failed to determine base address\n");
+ goto err_get_base;
+ }
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret <= 0) {
+ ret = -ENXIO;
+ dev_dbg(&pdev->dev, "failed to determine irq\n");
+ goto err_get_irq;
+ }
+ ddata->irq = ret;
+
+ ddata->base = ioremap(res->start, resource_size(res));
+ if (!ddata->base) {
+ ret = -ENOMEM;
+ dev_dbg(&pdev->dev, "failed to remap\n");
+ goto err_ioremap;
+ }
+
+ ddata->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ddata->clk)) {
+ ret = PTR_ERR(ddata->clk);
+ dev_dbg(&pdev->dev, "failed to get clock\n");
+ goto err_clk_get;
+ }
+
+ platform_set_drvdata(pdev, ddata);
+ spin_lock_init(&ddata->lock);
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &efm32_adc_group);
+ if (ret)
+ goto err_create_group;
+
+ ddata->hwmondev = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(ddata->hwmondev)) {
+ ret = PTR_ERR(ddata->hwmondev);
+
+ sysfs_remove_group(&pdev->dev.kobj, &efm32_adc_group);
+err_create_group:
+
+ platform_set_drvdata(pdev, NULL);
+
+ clk_put(ddata->clk);
+err_clk_get:
+
+ iounmap(ddata->base);
+err_ioremap:
+err_get_irq:
+err_get_base:
+ kfree(ddata);
+ }
+
+ return ret;
+}
+
+static int efm32_adc_remove(struct platform_device *pdev)
+{
+ struct efm32_adc_ddata *ddata = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(ddata->hwmondev);
+ sysfs_remove_group(&pdev->dev.kobj, &efm32_adc_group);
+ platform_set_drvdata(pdev, NULL);
+ clk_put(ddata->clk);
+ iounmap(ddata->base);
+ kfree(ddata);
+
+ return 0;
+}
+
+static const struct of_device_id efm32_adc_dt_ids[] = {
+ {
+ .compatible = "energymicro,efm32-adc",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, efm32_adc_dt_ids);
+
+static struct platform_driver efm32_adc_driver = {
+ .probe = efm32_adc_probe,
+ .remove = efm32_adc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = efm32_adc_dt_ids,
+ },
+};
+module_platform_driver(efm32_adc_driver);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
+MODULE_DESCRIPTION("EFM32 ADC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);