linux-sh.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] sh: sh7723: Add A/D converter driver support
@ 2010-01-29  9:40 Pietrek, Markus
  0 siblings, 0 replies; 2+ messages in thread
From: Pietrek, Markus @ 2010-01-29  9:40 UTC (permalink / raw)
  To: linux-sh

This patch adds support for the SH7723 A/D converter. It can be registered in arch/sh/boards/<xx>/setup.c with
static struct sh_adc_platform_data hico7723_adc_platform_data = {
        .voltage_reference = 3300,
        .ports = SH_ADC_PORT_A | SH_ADC_PORT_B | SH_ADC_PORT_C | SH_ADC_PORT_D,
};


diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 46c3c56..5d35645 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1066,6 +1066,15 @@ config SENSORS_MC13783_ADC
         help
           Support for the A/D converter on MC13783 PMIC.

+config SENSORS_SH_ADC
+        tristate "SH ADC"
+        depends on CPU_SUBTYPE_SH7723
+        help
+          Support for the A/D converter on the SH7723. Only single mode is supported, sampling only
+         one channel at a time. The voltage reference can be set in
+          /sys/devices/platform/sh_adc.0/vref as millivolt. Sampling can be done by reading from the
+          channels an0, an1, an2 or an3 (values are in millivolt).
+
 if ACPI

 comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 450c8e8..9b352b3 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)       += w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)        += w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)   += wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)   += wm8350-hwmon.o
+obj-$(CONFIG_SENSORS_SH_ADC)   += sh_adc.o

 ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/hwmon/sh_adc.c b/drivers/hwmon/sh_adc.c
new file mode 100644
index 0000000..7cf01d6
--- /dev/null
+++ b/drivers/hwmon/sh_adc.c
@@ -0,0 +1,366 @@
+/*
+ * sh_adc.c
+ *
+ * Copyright (c) 2010 by emtrion GmbH
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 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.
+ *
+ * Author:      Markus Pietrek
+ * Description: Driver for the ADC of the SH (SH7723). Only single
+ *              mode is supported
+ *
+ **/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/wait.h>
+#include <asm/io.h>
+
+#include "sh_adc.h"
+
+/* A/D Converter registers */
+#define ADDR(ch)       (0x00+((ch)&0x7)*2)
+#define ADCSR          0x08
+
+#define ADCSR_ADF      (1 << 15)
+#define ADCSR_ADIE     (1 << 14)
+#define ADCSR_ADST     (1 << 13)
+#define ADCSR_CKS_545  (2 <<  6)
+#define ADCSR_MULTI_SINGLE (0 << 4)
+#define ADCSR_CH(ch)    ((ch)&0x7)
+
+#define MAX_ANALOG_CHANNELS    4
+
+struct sh_adc_priv {
+       struct device          *hwmon_dev;
+       struct platform_device *pdev;
+       wait_queue_head_t       waitq;
+        void __iomem  *base;
+        struct clk    *clk;
+        struct mutex  mutex;
+       spinlock_t    lock;
+       int           irq;
+       int           voltage_reference;
+
+       struct {
+               struct attribute      *attrs[MAX_ANALOG_CHANNELS+1+1]; /* analog channels+vref+NULL */
+               struct attribute_group group;
+       } sysfs;
+
+       volatile char sample_available;
+};
+
+/********** low-level I/O **********/
+
+static inline void sh_adc_write(const struct sh_adc_priv *priv, u16 val, int reg)
+{
+        iowrite16(val, priv->base + reg);
+}
+
+static inline u16 sh_adc_read(const struct sh_adc_priv *priv, int reg)
+{
+        return ioread16(priv->base + reg);
+}
+
+static irqreturn_t sh_adc_isr(int irq, void *dev_id)
+{
+        struct sh_adc_priv *priv = dev_id;
+       u16 status;
+
+       spin_lock(&priv->lock);
+       status = sh_adc_read(priv, ADCSR);
+
+       if (status & ADCSR_ADF) {
+               /* clear end-of-conversion flag */
+               sh_adc_write(priv, status & ~ADCSR_ADF, ADCSR);
+
+               priv->sample_available = 1;
+               wake_up_interruptible(&priv->waitq);
+       }
+       spin_unlock(&priv->lock);
+
+       return IRQ_HANDLED;
+}
+
+static int sh_adc_get_sample(struct sh_adc_priv *priv, int ch, u16 *sample)
+{
+       int res;
+       unsigned long flags;
+
+       enable_irq(priv->irq);
+       clk_enable(priv->clk);
+
+       spin_lock_irqsave(&priv->lock, flags);
+       priv->sample_available = 0;
+       /* set to maximum conversion time */
+       sh_adc_write(priv,
+                    ADCSR_ADIE    |
+                    ADCSR_ADST    |
+                    ADCSR_CKS_545 |
+                    ADCSR_MULTI_SINGLE |
+                    ADCSR_CH(ch),
+                    ADCSR);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       res = wait_event_interruptible(priv->waitq, (priv->sample_available = 1));
+
+       spin_lock_irqsave(&priv->lock, flags);
+       *sample = sh_adc_read(priv, ADDR(ch));
+       sh_adc_write(priv, 0, ADCSR);
+       spin_unlock_irqrestore(&priv->lock, flags);
+
+       clk_disable(priv->clk);
+       disable_irq(priv->irq);
+
+       return 0;
+}
+
+/*********** platform sysfs **********/
+
+static ssize_t sh_adc_sysfs_show_vref(struct device *dev, struct device_attribute *da,
+                                     char *buf)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct sh_adc_priv *priv     = platform_get_drvdata(pdev);
+       int vref;
+
+       mutex_lock(&priv->mutex);
+       vref = priv->voltage_reference;
+       mutex_unlock(&priv->mutex);
+
+       return sprintf(buf, "%i mV\n", vref);
+}
+
+static ssize_t sh_adc_sysfs_set_vref(struct device *dev, struct device_attribute *devattr,
+                                    const char *buf, size_t count)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct sh_adc_priv *priv     = platform_get_drvdata(pdev);
+       int vref = simple_strtol(buf, NULL, 10);
+
+       mutex_lock(&priv->mutex);
+       priv->voltage_reference = vref;
+       mutex_unlock(&priv->mutex);
+
+       return count;
+}
+
+static ssize_t sh_adc_sysfs_show_anx(struct device *dev, struct device_attribute *da,
+                                    char *buf)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct sh_adc_priv *priv     = platform_get_drvdata(pdev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+       int res;
+       u16 sample;
+       u16 vin;
+
+       /* we support only single mode. No other channel can be read at the same
+        * time */
+       mutex_lock(&priv->mutex);
+       res = sh_adc_get_sample(priv, attr->index, &sample);
+       /* convert the 10bit digital value to mV */
+       vin = (sample*priv->voltage_reference)/65536;
+       mutex_unlock(&priv->mutex);
+
+       return res ? res : sprintf(buf, "%u mV\n", vin);
+}
+
+#define MK(index) SENSOR_ATTR(an##index, S_IRUGO, sh_adc_sysfs_show_anx, NULL, index)
+static struct sensor_device_attribute sh_adc_dev_attr[MAX_ANALOG_CHANNELS] = {
+       MK(0),
+       MK(1),
+       MK(2),
+       MK(3),
+};
+#undef MK
+SENSOR_DEVICE_ATTR(vref, S_IRUGO | S_IWUSR, sh_adc_sysfs_show_vref, sh_adc_sysfs_set_vref, 0);
+
+/*********** platform probe **********/
+
+static int __devinit sh_adc_probe(struct platform_device *pdev)
+{
+        struct sh_adc_priv *priv;
+        struct sh_adc_platform_data *pdata = dev_get_platdata(&pdev->dev);
+       struct resource *res;
+       struct attribute **attr;
+        int error = 0;
+       int i;
+       unsigned long flags;
+
+        if (!pdata) {
+                dev_err(&pdev->dev, "Need platform data\n");
+                error = -EINVAL;
+                goto error;
+        }
+
+        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+        if (!priv) {
+                dev_err(&pdev->dev, "cannot get memory\n");
+                error = -ENOMEM;
+                goto error;
+        }
+        priv->pdev = pdev;
+        platform_set_drvdata(pdev, priv);
+
+        mutex_init(&priv->mutex);
+       spin_lock_init(&priv->lock);
+       init_waitqueue_head(&priv->waitq);
+
+       priv->voltage_reference = pdata->voltage_reference;
+
+       /* register resources */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+        if (!res) {
+                dev_err(&pdev->dev, "cannot get platform mem resources\n");
+                error = -EINVAL;
+                goto error_mem;
+        }
+
+        priv->base = ioremap(res->start, (res->end-res->start)+1);
+        if (!priv->base) {
+                dev_err(&pdev->dev, "cannot get io memory\n");
+                error = -ENOMEM;
+                goto error_mem;
+        }
+
+       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+        if (!res) {
+                dev_err(&pdev->dev, "cannot get platform irq resources\n");
+                error = -EINVAL;
+                goto error_irq;
+        }
+       priv->irq = res->start;
+        error = request_irq(priv->irq, sh_adc_isr, IRQF_DISABLED, dev_name(&priv->pdev->dev), priv);
+        if (error) {
+                dev_err(&pdev->dev, "cannot get irq\n");
+                goto error_irq;
+        }
+
+        priv->clk = clk_get(&pdev->dev, "adc0");
+        if (IS_ERR(priv->clk)) {
+                dev_err(&pdev->dev, "cannot get clock");
+                goto error_clock;
+        }
+
+       /* register sysfs hooks */
+       attr = &priv->sysfs.attrs[0];
+       *attr = &sensor_dev_attr_vref.dev_attr.attr;
+       attr++;
+       /* add SH_ADC_PORT_A/B/C/D when present on board */
+       for (i=0; i < ARRAY_SIZE(sh_adc_dev_attr); i++) {
+               if (pdata->ports & (1 << i)) {
+                       *attr = &sh_adc_dev_attr[i].dev_attr.attr;
+                       attr++;
+               }
+       }
+       *attr = NULL;
+       priv->sysfs.group.attrs = priv->sysfs.attrs;
+
+       error = sysfs_create_group(&pdev->dev.kobj, &priv->sysfs.group);
+       if (error) {
+                dev_err(&pdev->dev, "sysfs failed");
+               goto error_sysfs;
+       }
+
+       /* register hwmon hooks */
+       priv->hwmon_dev = hwmon_device_register(&pdev->dev);
+       if (IS_ERR(priv->hwmon_dev)) {
+                dev_err(&pdev->dev, "hwmon_device_register failed\n");
+               error = PTR_ERR(priv->hwmon_dev);
+               goto error_hwmon;
+       }
+
+       /* reset A/D */
+       clk_enable(priv->clk);
+       disable_irq(priv->irq);
+       spin_lock_irqsave(&priv->lock, flags);
+       sh_adc_write(priv, 0, ADCSR);
+       if (sh_adc_read(priv, ADCSR))
+               /* clear end of A/D conversion */
+               sh_adc_write(priv, ADCSR_ADF, ADCSR);
+       spin_unlock_irqrestore(&priv->lock, flags);
+       clk_disable(priv->clk);
+
+        dev_info(&pdev->dev, "initialized\n");
+        return 0;
+
+error_hwmon:
+       sysfs_remove_group(&pdev->dev.kobj, &priv->sysfs.group);
+
+error_sysfs:
+        clk_put(priv->clk);
+
+error_clock:
+        free_irq(priv->irq, priv);
+
+error_irq:
+        iounmap(priv->base);
+
+error_mem:
+        kfree(priv);
+
+error:
+        return error;
+}
+
+static int __devexit sh_adc_remove(struct platform_device *pdev)
+{
+       struct sh_adc_priv *priv = platform_get_drvdata(pdev);
+
+       hwmon_device_unregister(priv->hwmon_dev);
+       sysfs_remove_group(&pdev->dev.kobj, &priv->sysfs.group);
+        clk_put(priv->clk);
+        free_irq(priv->irq, priv);
+        iounmap(priv->base);
+        kfree(priv);
+
+        platform_set_drvdata(pdev, NULL);
+
+        return 0;
+}
+
+
+static struct platform_driver sh_adc_driver = {
+       .probe          = sh_adc_probe,
+       .remove         = __devexit_p(sh_adc_remove),
+       .driver         = {
+               .name   = "sh_adc",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init sh_adc_init(void)
+{
+       return platform_driver_register(&sh_adc_driver);
+}
+
+static void __exit sh_adc_exit(void)
+{
+       platform_driver_unregister(&sh_adc_driver);
+}
+
+module_init(sh_adc_init);
+module_exit(sh_adc_exit);
+
+MODULE_AUTHOR("Markus Pietrek");
+MODULE_DESCRIPTION("SH7723 A/D Converter driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/hwmon/sh_adc.h b/drivers/hwmon/sh_adc.h
new file mode 100644
index 0000000..964a3a2
--- /dev/null
+++ b/drivers/hwmon/sh_adc.h
@@ -0,0 +1,33 @@
+/*
+ * sh_adc.h
+ *
+ * Copyright (c) 2010 by emtrion GmbH
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * 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.
+ *
+ **/
+
+#ifndef __DRIVERS_HWMON_SH_ADC_H
+#define __DRIVERS_HWMON_SH_ADC_H
+
+struct sh_adc_platform_data {
+       int     voltage_reference; /* in mV */
+       u8      ports;
+#define SH_ADC_PORT_A  (1<<0)
+#define SH_ADC_PORT_B  (1<<1)
+#define SH_ADC_PORT_C  (1<<2)
+#define SH_ADC_PORT_D  (1<<3)
+};
+
+#endif /* __DRIVERS_HWMON_SH_ADC_H */

_____________________________________

Amtsgericht Mannheim
HRB 110 300
Gesch?ftsf?hrer: Dieter Baur, Ramona Maurer
_____________________________________

Important Note:
- This e-mail may contain trade secrets or privileged, undisclosed or otherwise confidential information.
- If you have received this e-mail in error, you are hereby notified that any review, copying or distribution of it is strictly prohibited.
- Please inform us immediately and destroy the original transmittal.

Thank you for your cooperation.

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] sh: sh7723: Add A/D converter driver support
@ 2010-02-02  4:19 Paul Mundt
  0 siblings, 0 replies; 2+ messages in thread
From: Paul Mundt @ 2010-02-02  4:19 UTC (permalink / raw)
  To: linux-sh

On Fri, Jan 29, 2010 at 10:40:55AM +0100, Pietrek, Markus wrote:
> This patch adds support for the SH7723 A/D converter. It can be
> registered in arch/sh/boards/<xx>/setup.c with

This looks like a good start, but it will need some extending for the
existing in-tree ADC users (hp6xx, via the SH-3 ADC), which will require
an in-kernel API. (You can grep for adc_single to find the existing
in-tree users).

Having said that, what you have now shouldn't take much to get tidied up
and made more generic, so if you don't get around to it now then I'll
probably just take care of it prior to the next merge window. It's in
patchwork now at least, so it won't get lost.

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2010-02-02  4:19 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-02-02  4:19 [PATCH] sh: sh7723: Add A/D converter driver support Paul Mundt
  -- strict thread matches above, loose matches on Subject: below --
2010-01-29  9:40 Pietrek, Markus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).