From patchwork Fri Jan 29 09:40:55 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Pietrek, Markus" X-Patchwork-Id: 75694 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.3/8.14.3) with ESMTP id o0T9fD5I024027 for ; Fri, 29 Jan 2010 09:41:13 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756106Ab0A2JlB (ORCPT ); Fri, 29 Jan 2010 04:41:01 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1756793Ab0A2JlB (ORCPT ); Fri, 29 Jan 2010 04:41:01 -0500 Received: from mail3.emtrion.de ([80.150.99.69]:10816 "EHLO mail3.emtrion.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756106Ab0A2Jk7 convert rfc822-to-8bit (ORCPT ); Fri, 29 Jan 2010 04:40:59 -0500 Received: from BMK019S01.emtrion.local ([fe80::4a1:cedc:cab6:e9ce]) by BMK019S01.emtrion.local ([fe80::4a1:cedc:cab6:e9ce%10]) with mapi; Fri, 29 Jan 2010 10:40:56 +0100 From: "Pietrek, Markus" To: "linux-sh@vger.kernel.org" Date: Fri, 29 Jan 2010 10:40:55 +0100 Subject: [PATCH] sh: sh7723: Add A/D converter driver support Thread-Topic: [PATCH] sh: sh7723: Add A/D converter driver support Thread-Index: AcqgxydrFdrrwBSwScSxUW1KbinYKg== Message-ID: <95F51F4B902CAC40AF459205F6322F0171E8D49856@BMK019S01.emtrion.local> Accept-Language: de-DE Content-Language: de-DE X-MS-Has-Attach: X-MS-TNEF-Correlator: acceptlanguage: de-DE x-tm-as-product-ver: SMEX-8.0.0.1307-6.000.1038-17160.005 x-tm-as-result: No--42.793100-8.000000-31 x-tm-as-user-approved-sender: No x-tm-as-user-blocked-sender: No MIME-Version: 1.0 Sender: linux-sh-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-sh@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Fri, 29 Jan 2010 09:41:13 +0000 (UTC) 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */