[RFC PATCH v1] EP93XX: Add ADC support

Ryan Mallon ryan at bluewatersys.com
Mon Oct 12 16:33:51 EDT 2009


Christian Gagneraud wrote:
> This patch add support for the ADC found in the EP93XX.
> 
> This work is based on S3C platform ADC, apart from hardware related
> stuff, the following modifications have been done:
> - Remove touchscreen support:
>   On S3C the TS is a "normal" ADC client, but it has priority over all
>   other clients. On EP93XX the TS controller is built-in and offer
>   advanced features.
> - Remove convert and select callbacks:
>   This was done for the shake of simplicity, it can be added easily.
> - Channel definition:
>   On S3c, channel is just an index (unsigned char). On EP93xx channel
>   is the analog switch configuration (unsigned long), it gives the
>   client full freedom on how to make the analog conversion (including
>   routing VRef+ and VRef-, activationg PU/PD resistors, connecting
>   pins to VDD/GND, ...)
> 
> 
> This is a first draft. Comments and criticism welcome.

Hi Christian, some comments below.

> 
> Signed-off-by: Christian Gagneraud <cgagneraud at techworks.ie>
> ---
> 
>  arch/arm/mach-ep93xx/Makefile                   |    2 
>  arch/arm/mach-ep93xx/adc.c                      |  415 +++++++++++++++++++++++
>  arch/arm/mach-ep93xx/clock.c                    |    7 
>  arch/arm/mach-ep93xx/core.c                     |   31 ++
>  arch/arm/mach-ep93xx/include/mach/adc.h         |   54 +++
>  arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h |   21 +
>  arch/arm/mach-ep93xx/include/mach/hwmon.h       |   43 ++
>  arch/arm/mach-ep93xx/ts72xx.c                   |   78 ++++
>  drivers/hwmon/Kconfig                           |   17 +
>  drivers/hwmon/Makefile                          |    1 
>  drivers/hwmon/ep93xx-hwmon.c                    |  416 +++++++++++++++++++++++
>  11 files changed, 1083 insertions(+), 2 deletions(-)
>  create mode 100644 arch/arm/mach-ep93xx/adc.c
>  create mode 100644 arch/arm/mach-ep93xx/include/mach/adc.h
>  create mode 100644 arch/arm/mach-ep93xx/include/mach/hwmon.h
>  create mode 100644 drivers/hwmon/ep93xx-hwmon.c
> 
> diff --git a/arch/arm/mach-ep93xx/Makefile b/arch/arm/mach-ep93xx/Makefile
> index eae6199..e3c62fe 100644
> --- a/arch/arm/mach-ep93xx/Makefile
> +++ b/arch/arm/mach-ep93xx/Makefile
> @@ -6,6 +6,8 @@ obj-m			:=
>  obj-n			:=
>  obj-			:=
>  
> +obj-$(CONFIG_EP93XX_ADC)	+= adc.o
> +
>  obj-$(CONFIG_MACH_ADSSPHERE)	+= adssphere.o
>  obj-$(CONFIG_MACH_EDB93XX)	+= edb93xx.o
>  obj-$(CONFIG_MACH_GESBC9312)	+= gesbc9312.o
> diff --git a/arch/arm/mach-ep93xx/adc.c b/arch/arm/mach-ep93xx/adc.c
> new file mode 100644
> index 0000000..78dc074
> --- /dev/null
> +++ b/arch/arm/mach-ep93xx/adc.c
> @@ -0,0 +1,415 @@
> +/*
> + * arch/arm/mach-ep93xx/adc.c
> + *
> + * ADC support for Cirrus EP93xx chips.
> + *
> + * Copyright (C) 2009 Christian Gagneraud <cgagneraud at techworks.ie>
> + *
> + * Based on arch/arm/plat-s3c24xx/adc.c by Simtec Electronics
> + *
> + * 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.
> + */
> +        
> +/*
> + * Touchscreen and Direct ADC:
> + *
> + * +------------------------+-----+-----+-----+-----+ 
> + * |ADC/TS driver in use    | n/n | y/n | n/y | y/y | 
> + * +------------------------+-----+-----+-----+-----+
> + * |SYSCON_DEVCFG_ADCPD     |  1  |  0  |  0  |  0  |
> + * |SYSCON_DEVCFG_TIN       |  X  |  1  |  0  | 1/0 |
> + * |SYSCON_KEYTCHCLKDIV_TSEN|  0  |  1  |  1  |  1  |
> + * |TS_SETUP_ENABLE (*)     |  0  |  0  |  1  | 0/1 |
> + * +------------------------+-----+-----+-----+-----+
> + * (*) 
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/platform_device.h>
> +#include <linux/list.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/delay.h>
> +#include <linux/sched.h>
> +
> +#include <mach/hardware.h>
> +
> +static inline void ep93xx_adc_set_reg(void __iomem *reg,
> +                                      unsigned long val)
> +{	
> +	__raw_writel(EP93XX_TS_SWLOCK_UNLOCK, EP93XX_TS_SWLOCK);
> +	__raw_writel(val, reg);
> +}
> +
> +static inline void ep93xx_adc_clr_reg_bits(void __iomem *reg,
> +                                           unsigned long bits)
> +{
> +	unsigned long val = __raw_readl(reg);
> +	val &= ~bits;
> +	ep93xx_adc_set_reg(reg,val);
> +}
> +
> +static inline void ep93xx_adc_set_reg_bits(void __iomem *reg,
> +                                           unsigned long bits)
> +{
> +	unsigned long val = __raw_readl(reg);
> +	val |= bits;
> +	ep93xx_adc_set_reg(reg,val);
> +}
> +
> +struct ep93xx_adc_client {
> +	struct platform_device *pdev;
> +	struct list_head pend;
> +	wait_queue_head_t *wait;
> +	unsigned int nr_samples;
> +	int result;
> +	unsigned int channel;

Tab delimiting can make these easier to read, ie:

struct ep93xx_adc_client {
	struct platform_device	*pdev;
	struct list_head	pend;
	wait_queue_head_t	*wait;
	...
};

> +};
> +
> +struct adc_device {
> +	struct platform_device *pdev;
> +	struct platform_device *owner;
> +	struct clk *clk;
> +	struct ep93xx_adc_client *cur;
> +	void __iomem *regs;
> +	int irq;
> +	unsigned int refm;              /* Reading when Vin = Vref- */
> +	unsigned int refp;              /* Reading when Vin = Vref+ */
> +};
> +
> +static struct adc_device *adc_dev;
> +
> +static LIST_HEAD(adc_pending);
> +
> +#define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg)

Bit of a nitpick, but can you move the structure definitions and
variable declarations above the first functions in this file.

> +
> +static inline void ep93xx_adc_convert(struct adc_device *adc)
> +{
> +	unsigned long prev_switch;
> +	unsigned long next_switch;
> +
> +	/* Configure the switches */
> +	prev_switch = __raw_readl(EP93XX_TS_DIRECT);
> +	next_switch = adc->cur->channel;
> +	if (next_switch != prev_switch) {
> +		ep93xx_adc_set_reg(EP93XX_TS_DIRECT, next_switch);
> +		/* Channel switch settling time */
> +		/* TODO: depends on clock speed (500us or 2ms) */
> +		/* FIXME: the caller has disabled interrupts and the
> +		 * caller can even be the irq handler. Should we
> +		 * better fire a timer? */
> +		mdelay(2);

Perhaps make the mdelay value a platform data in the meantime then?

> +	}
> +
> +	/* Fire ADC engine with Sync Data Ready IRQ enabled */
> +	ep93xx_adc_set_reg_bits(EP93XX_TS_SETUP2, EP93XX_TS_SETUP2_RINTEN);
> +	__raw_readl(EP93XX_TS_XYRESULT);
> +}
> +
> +static void ep93xx_adc_try(struct adc_device *adc)
> +{
> +	struct ep93xx_adc_client *next;
> +
> +	if (!list_empty(&adc_pending)) {
> +		next = list_first_entry(&adc_pending,
> +					struct ep93xx_adc_client, pend);
> +		list_del(&next->pend);

Are all of the callers of ep93xx_adc_try safely locked?

> +		adc_dbg(adc, "new client is %p\n", next);
> +		adc->cur = next;
> +		ep93xx_adc_convert(adc);
> +	}
> +}
> +
> +int ep93xx_adc_start(struct ep93xx_adc_client *client,
> +		     unsigned long 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__);

Change to:
	pr_error("ep93xx_adc: %s: Failed to find adc\n", __func__);

> +		return -EINVAL;
> +	}
> +
> +	local_irq_save(flags);
> +
> +	client->channel = channel;
> +	client->nr_samples = nr_samples;
> +
> +	list_add_tail(&client->pend, &adc_pending);
> +
> +	if (!adc->cur)
> +		ep93xx_adc_try(adc);
> +	local_irq_restore(flags);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(ep93xx_adc_start);
> +
> +static void ep93xx_convert_done(struct ep93xx_adc_client *client,
> +				unsigned val, unsigned *left)
> +{
> +	client->result = val;
> +	wake_up(client->wait);
> +}
> +
> +int ep93xx_adc_read(struct ep93xx_adc_client *client, unsigned int ch)
> +{
> +	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
> +	int ret;
> +
> +	client->wait = &wake;
> +	client->result = -1;
> +
> +	ret = ep93xx_adc_start(client, ch, 1);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = wait_event_timeout(wake, client->result >= 0, HZ / 2);
> +	if (client->result < 0) {
> +		ret = -ETIMEDOUT;
> +		goto err;
> +	}
> +
> +	return client->result;
> +      err:
> +	return ret;
> +}
> +
> +EXPORT_SYMBOL_GPL(ep93xx_adc_convert);

The function ep93xx_adc_convert is static inline, so this doesn't make
sense (unless I'm missing something). The s3c code also looks broken is
this regard (s3c_adc_convert isn't used anywhere but the driver). I
think this should be exporting ep93xx_adc_read?

> +
> +struct ep93xx_adc_client *ep93xx_adc_register(struct platform_device *pdev)
> +{
> +	struct ep93xx_adc_client *client;
> +
> +	printk("adc_register: %s\n", pdev->name);

Do we need to print this? Maybe use pr_debug or dev_dbg?

> +
> +	WARN_ON(!pdev);

You dereference pdev above this line so you will have already bugged :-).

> +
> +	if (!pdev)
> +		return ERR_PTR(-EINVAL);
> +
> +	client = kzalloc(sizeof(struct ep93xx_adc_client), GFP_KERNEL);
> +	if (!client) {
> +		dev_err(&pdev->dev, "no memory for adc client\n");
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	client->pdev = pdev;
> +
> +	return client;
> +}
> +
> +EXPORT_SYMBOL_GPL(ep93xx_adc_register);
> +
> +void ep93xx_adc_release(struct ep93xx_adc_client *client)
> +{
> +	struct list_head *p, *n;
> +	struct ep93xx_adc_client *tmp;
> +
> +	/* We should really check that nothing is in progress. */
> +	if (adc_dev->cur == client)
> +		adc_dev->cur = NULL;
> +
> +	list_for_each_safe(p, n, &adc_pending) {
> +		tmp = list_entry(p, struct ep93xx_adc_client, pend);
> +		if (tmp == client)
> +			list_del(&tmp->pend);
> +	}
> +
> +	if (adc_dev->cur == NULL)
> +		ep93xx_adc_try(adc_dev);
> +	kfree(client);
> +}
> +
> +EXPORT_SYMBOL_GPL(ep93xx_adc_release);
> +
> +static irqreturn_t ep93xx_adc_irq(int irq, void *pw)
> +{
> +	struct adc_device *adc = pw;
> +	struct ep93xx_adc_client *client = adc->cur;
> +	unsigned long flags;
> +	unsigned long data;
> +
> +	if (!client) {
> +		dev_warn(&adc->pdev->dev, "%s: no adc pending\n",
> +			 __func__);
> +		return IRQ_HANDLED;
> +	}
> +
> +	/* Read and convert data */
> +	data = __raw_readl(EP93XX_TS_XYRESULT);
> +	data &= 0x0000FFFF;

data &= 0xffff; is fine.

> +	if (data < 0x7000)
> +		data += 0x10000;

Nitpick: data |= 0x10000 is a bit more clear.

> +	data -= adc->refm;
> +
> +	client->nr_samples--;
> +
> +	ep93xx_convert_done(client, data, &client->nr_samples);
> +
> +	/* If all samples are done, disable IRQ, and kick start the next
> +	 * one if any. */

This comment doesn't make sense. If all samples are done, kick start the
next one? Am I missing something?

> +	if (client->nr_samples == 0) {
> +		local_irq_save(flags);
> +		adc->cur = NULL;
> +		ep93xx_adc_clr_reg_bits(EP93XX_TS_SETUP2,
> +                                        EP93XX_TS_SETUP2_RINTEN);
> +		ep93xx_adc_try(adc);
> +		local_irq_restore(flags);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ep93xx_adc_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct adc_device *adc;
> +	struct resource *regs;
> +	int ret;
> +
> +	printk("adc_probe: %s\n", pdev->name);

pr_debug.

> +
> +	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;
> +
> +	adc->irq = platform_get_irq(pdev, 0);
> +	if (adc->irq <= 0) {
> +		dev_err(dev, "failed to get adc irq\n");
> +		ret = -ENOENT;
> +		goto err_alloc;
> +	}
> +
> +	ret = request_irq(adc->irq, ep93xx_adc_irq, IRQF_DISABLED,
> +			  dev_name(dev), adc);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to attach adc irq\n");
> +		goto err_alloc;
> +	}
> +
> +	adc->clk = clk_get(dev, "ep93xx-analog");
> +	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;
> +	}
> +
> +	clk_enable(adc->clk);
> +
> +	// Enable ADC, inactivate TS controller

/* */ comments please.

> +	ep93xx_devcfg_set_clear(EP93XX_SYSCON_DEVCFG_TIN,
> +				EP93XX_SYSCON_DEVCFG_ADCPD);
> +	// Disable TS engine
> +	ep93xx_adc_clr_reg_bits(EP93XX_TS_SETUP, EP93XX_TS_SETUP_ENABLE);
> +	// Set ADC to signed mode
> +	ep93xx_adc_clr_reg_bits(EP93XX_TS_SETUP2, EP93XX_TS_SETUP2_NSIGND);
> +
> +	dev_info(dev, "attached adc driver\n");

Don't really need to pollute the dmesg output.

> +
> +	platform_set_drvdata(pdev, adc);
> +
> +	/* Defaults from datasheet (approximated values) */
> +	adc->refm = 0xFFFF - 25000;

Erk, why two different bases? Can we just #define these values somewhere.

> +	adc->refp = 25000;
> +	adc_dev = adc;
> +
> +	return 0;
> +
> +      err_clk:
> +	clk_put(adc->clk);

clk_disable?

> +
> +      err_irq:
> +	free_irq(adc->irq, adc);
> +
> +      err_alloc:
> +	kfree(adc);
> +	return ret;
> +
> +}
> +
> +static int ep93xx_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;
> +}
> +
> +#ifdef CONFIG_PM
> +static int ep93xx_adc_suspend(struct platform_device *pdev,
> +			      pm_message_t state)
> +{
> +	struct adc_device *adc = platform_get_drvdata(pdev);
> +	ep93xx_devcfg_clear(EP93XX_SYSCON_DEVCFG_ADCPD);
> +	clk_disable(adc->clk);
> +	return 0;
> +}
> +
> +static int ep93xx_adc_resume(struct platform_device *pdev)
> +{
> +	struct adc_device *adc = platform_get_drvdata(pdev);
> +	clk_enable(adc->clk);
> +	ep93xx_devcfg_set(EP93XX_SYSCON_DEVCFG_ADCPD);
> +	return 0;
> +}
> +
> +#else
> +#define ep93xx_adc_suspend NULL
> +#define ep93xx_adc_resume NULL
> +#endif
> +
> +static struct platform_driver ep93xx_adc_driver = {
> +	.driver = {
> +		   .name = "ep93xx-analog",

Why not ep93xx-adc?

> +		   .owner = THIS_MODULE,
> +		   },
> +	.probe = ep93xx_adc_probe,
> +	.remove = __devexit_p(ep93xx_adc_remove),
> +	.suspend = ep93xx_adc_suspend,
> +	.resume = ep93xx_adc_resume,
> +};
> +
> +static int __init adc_init(void)
> +{
> +	int ret;
> +
> +	ret = platform_driver_register(&ep93xx_adc_driver);
> +	if (ret)
> +		printk(KERN_ERR "%s: failed to add adc driver\n",
> +		       __func__);

pr_error.

> +
> +	return ret;
> +}
> +
> +arch_initcall(adc_init);
> diff --git a/arch/arm/mach-ep93xx/clock.c b/arch/arm/mach-ep93xx/clock.c
> index dda19cd..d604e37 100644
> --- a/arch/arm/mach-ep93xx/clock.c
> +++ b/arch/arm/mach-ep93xx/clock.c
> @@ -72,6 +72,12 @@ static struct clk clk_keypad = {
>  	.enable_mask	= EP93XX_SYSCON_KEYTCHCLKDIV_KEN,
>  	.set_rate	= set_keytchclk_rate,
>  };
> +static struct clk clk_analog = {
> +	.sw_locked	= 1,
> +	.enable_reg	= EP93XX_SYSCON_KEYTCHCLKDIV,
> +	.enable_mask	= EP93XX_SYSCON_KEYTCHCLKDIV_TSEN,
> +	.set_rate	= set_keytchclk_rate,
> +};
>  static struct clk clk_pwm = {
>  	.rate		= EP93XX_EXT_CLK_RATE,
>  };
> @@ -147,6 +153,7 @@ static struct clk_lookup clocks[] = {
>  	INIT_CK(NULL,			"pll2",		&clk_pll2),
>  	INIT_CK("ep93xx-ohci",		NULL,		&clk_usb_host),
>  	INIT_CK("ep93xx-keypad",	NULL,		&clk_keypad),
> +	INIT_CK("ep93xx-analog",	NULL,		&clk_analog),
>  	INIT_CK("ep93xx-fb",		NULL,		&clk_video),
>  	INIT_CK(NULL,			"pwm_clk",	&clk_pwm),
>  	INIT_CK(NULL,			"m2p0",		&clk_m2p0),
> diff --git a/arch/arm/mach-ep93xx/core.c b/arch/arm/mach-ep93xx/core.c
> index f7ebed9..17d3c11 100644
> --- a/arch/arm/mach-ep93xx/core.c
> +++ b/arch/arm/mach-ep93xx/core.c
> @@ -684,6 +684,37 @@ EXPORT_SYMBOL(ep93xx_pwm_release_gpio);
>  
>  
>  /*************************************************************************
> + * EP93xx Analog Touch Screen Interface peripheral handling.
> + * Note: TS and ADC share the same resource
> + *************************************************************************/
> +static struct resource ep93xx_analog_resource[] = {
> +	[0] = {
> +		.start	= EP93XX_TS_PHYS_BASE,
> +		.end	= EP93XX_TS_PHYS_BASE + 0x24 - 1,
> +		.flags	= IORESOURCE_MEM,
> +	},
> +	[1] = {
> +		.start	= IRQ_EP93XX_TOUCH,
> +		.end	= IRQ_EP93XX_TOUCH,
> +		.flags	= IORESOURCE_IRQ,
> +	},
> +};
> +
> +struct platform_device ep93xx_analog_device = {
> +	.name		= "ep93xx-analog",
> +	.id		= -1,
> +	.num_resources	= ARRAY_SIZE(ep93xx_analog_resource),
> +	.resource	= ep93xx_analog_resource,
> +};
> +
> +/* HWMON */
> +struct platform_device ep93xx_hwmon_device = {
> +	.name		= "ep93xx-hwmon",
> +	.id		= -1,
> +	.dev.parent	= &ep93xx_analog_device.dev,
> +};
> +
> +/*************************************************************************
>   * EP93xx video peripheral handling
>   *************************************************************************/
>  static struct ep93xxfb_mach_info ep93xxfb_data;
> diff --git a/arch/arm/mach-ep93xx/include/mach/adc.h b/arch/arm/mach-ep93xx/include/mach/adc.h
> new file mode 100644
> index 0000000..c4c6a5a
> --- /dev/null
> +++ b/arch/arm/mach-ep93xx/include/mach/adc.h
> @@ -0,0 +1,54 @@
> +/* arch/arm/mach-ep93xx/include/arch/adc.h
> + *
> + * Copyright 2009, Christian Gagneraud <cgagneraud at techworks.ie>
> + *
> + * Based on arch/arm/plat-s3c/include/plat/adc.h by Simtec Electronics
> + *
> + * EP93XX ADC driver information
> + *
> + * 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.
> +*/
> +
> +#ifndef __ASM_ARCH_ADC_H
> +#define __ASM_ARCH_ADC_H __FILE__
> +
> +/* ADC Channels definition (switches) */
> +#define ADC_SW_XP_IN		(1<<0)
> +#define ADC_SW_XM_IN		(1<<1)
> +#define ADC_SW_YP_IN		(1<<2)
> +#define ADC_SW_YM_IN		(1<<3)
> +#define ADC_SW_SXP_IN           (1<<4)
> +#define ADC_SW_SXM_IN           (1<<5)
> +#define ADC_SW_SYP_IN           (1<<6)
> +#define ADC_SW_SYM_IN           (1<<7)
> +#define ADC_SW_VBAT_IN          (1<<8)
> +#define ADC_SW_AVDD_REFP        (1<<9)
> +#define ADC_SW_AGND_REFM        (1<<10)
> +#define ADC_SW_SXP_REFP         (1<<24)
> +#define ADC_SW_SXM_REFM         (1<<25)
> +#define ADC_SW_SYP_REFP         (1<<26)
> +#define ADC_SW_SYM_REFM         (1<<27)
> +#define ADC_SW_DAC_IN		(1<<29)
> +#define ADC_SW_IN_LOAD		(1<<30)

Nitpick, spaces around <<

> +
> +/* AGND as VREF-, AVDD as VREF+ */
> +#define ADC_SW_APWR_REF         (ADC_SW_AVDD_REFP|ADC_SW_AGND_REFM)

Spaces around |

> +
> +struct ep93xx_adc_client;
> +
> +extern struct platform_device ep93xx_analog_device;
> +
> +extern int ep93xx_adc_start(struct ep93xx_adc_client *client,
> +			    unsigned int channel, unsigned int nr_samples);
> +
> +extern int ep93xx_adc_read(struct ep93xx_adc_client *client,
> +			   unsigned int ch);
> +
> +extern struct ep93xx_adc_client *ep93xx_adc_register(struct platform_device
> +						     *pdev);
> +
> +extern void ep93xx_adc_release(struct ep93xx_adc_client *client);
> +
> +#endif				/* __ASM_ARCH_ADC_H */
> diff --git a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h
> index 0fbf87b..7453c08 100644
> --- a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h
> +++ b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h
> @@ -145,8 +145,25 @@
>  
>  #define EP93XX_KEY_MATRIX_BASE		EP93XX_APB_IOMEM(0x000f0000)
>  
> -#define EP93XX_ADC_BASE			EP93XX_APB_IOMEM(0x00100000)
> -#define EP93XX_TOUCHSCREEN_BASE		EP93XX_APB_IOMEM(0x00100000)
> +/* Note:
> + * The EP9301 and EP9302 processors support a general 12-bit ADC, but
> + * no touchscreen, the EP9307, EP9312 and EP9315 processors each
> + * support up to an 8-wire touch screen or a general 12-bit ADC
> + */
> +#define EP93XX_TS_PHYS_BASE		(EP93XX_APB_PHYS_BASE + 0x00100000)
> +#define EP93XX_TS_BASE			EP93XX_APB_IOMEM(0x00100000)
> +#define EP93XX_TS_REG(x)		(EP93XX_TS_BASE + (x))
> +#define EP93XX_TS_SETUP			EP93XX_TS_REG(0x00)
> +#define EP93XX_TS_SETUP_ENABLE		(1<<15)
> +#define EP93XX_TS_XYRESULT              EP93XX_TS_REG(0x08)
> +#define EP93XX_TS_XYRESULT_SDR          (1<<31)
> +#define EP93XX_TS_DIRECT		EP93XX_TS_REG(0x18)
> +#define EP93XX_TS_SWLOCK		EP93XX_TS_REG(0x20)
> +#define EP93XX_TS_SWLOCK_UNLOCK		0x000000AA
> +#define EP93XX_TS_SETUP2		EP93XX_TS_REG(0x24)
> +#define EP93XX_TS_SETUP2_RINTEN		(1<<11)
> +#define EP93XX_TS_SETUP2_NSIGND		(1<<9)
> +
>  
>  #define EP93XX_PWM_PHYS_BASE		(EP93XX_APB_PHYS_BASE + 0x00110000)
>  #define EP93XX_PWM_BASE			EP93XX_APB_IOMEM(0x00110000)
> diff --git a/arch/arm/mach-ep93xx/include/mach/hwmon.h b/arch/arm/mach-ep93xx/include/mach/hwmon.h
> new file mode 100644
> index 0000000..fec3db9
> --- /dev/null
> +++ b/arch/arm/mach-ep93xx/include/mach/hwmon.h
> @@ -0,0 +1,43 @@
> +/* linux/arch/arm/mach-ep93xx/include/arch/hwmon.h
> + *
> + * Copyright (C) 2009 Christian Gagneraud <cgagneraud at techworks.ie>
> + *
> + * Based on linux/arch/arm/plat-s3c/include/plat/hwmon.h by Simtec Electronics
> + *
> + * EP93XX - HWMon interface for ADC
> + *
> + * 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.
> +*/
> +
> +#ifndef __ASM_ARCH_HWMON_H
> +#define __ASM_ARCH_HWMON_H __FILE__

Stray __FILE__?

> +
> +extern struct platform_device ep93xx_hwmon_device;
> +
> +/**
> + * ep93xx_hwmon_chcfg - channel configuration
> + * @name: The name to give this channel.
> + * @mult: Multiply the ADC value read by this.
> + * @div: Divide the value from the ADC by this.
> + *
> + * The value read from the ADC is converted to a value that
> + * hwmon expects (mV) by result = (value_read * @mult) / @div.
> + */
> +struct ep93xx_hwmon_chcfg {
> +	const char *name;
> +	unsigned long channel;
> +	unsigned long mult;
> +	unsigned long div;
> +};
> +
> +/**
> + * ep93xx_hwmon_pdata - HWMON platform data
> + * @in: One configuration for each possible channel used.
> + */
> +struct ep93xx_hwmon_data {
> +	struct ep93xx_hwmon_chcfg *in[7];
> +};
> +
> +#endif				/* __ASM_ARCH_HWMON_H */
> diff --git a/arch/arm/mach-ep93xx/ts72xx.c b/arch/arm/mach-ep93xx/ts72xx.c
> index 259f782..2bbf05b 100644
> --- a/arch/arm/mach-ep93xx/ts72xx.c
> +++ b/arch/arm/mach-ep93xx/ts72xx.c
> @@ -18,6 +18,8 @@
>  #include <linux/mtd/physmap.h>
>  
>  #include <mach/hardware.h>
> +#include <mach/adc.h>
> +#include <mach/hwmon.h>
>  #include <mach/ts72xx.h>
>  
>  #include <asm/mach-types.h>
> @@ -92,6 +94,8 @@ static struct map_desc ts72xx_alternate_nand_io_desc[] __initdata = {
>  	}
>  };
>  
> +static struct ep93xx_hwmon_data ts72xx_hwmon_info;
> +
>  static void __init ts72xx_map_io(void)
>  {
>  	ep93xx_map_io();
> @@ -109,6 +113,8 @@ static void __init ts72xx_map_io(void)
>  				ARRAY_SIZE(ts72xx_nand_io_desc));
>  		}
>  	}
> +        
> +	ep93xx_hwmon_device.dev.platform_data = &ts72xx_hwmon_info;
>  }
>  
>  /*************************************************************************
> @@ -170,6 +176,74 @@ static struct ep93xx_eth_data ts72xx_eth_data = {
>  	.phy_id		= 1,
>  };
>  
> +
> +#define TS72XX_ADC0 (ADC_SW_YM_IN  | ADC_SW_APWR_REF)
> +#define TS72XX_ADC1 (ADC_SW_SXP_IN | ADC_SW_APWR_REF)
> +#define TS72XX_ADC2 (ADC_SW_SXM_IN | ADC_SW_APWR_REF)
> +#define TS72XX_ADC3 (ADC_SW_SYP_IN | ADC_SW_APWR_REF)
> +#define TS72XX_ADC4 (ADC_SW_SYM_IN | ADC_SW_APWR_REF)
> +
> +// TODO: board dependant
> +// TS-7200: ADC0, ADC4
> +// TS-7250: ADC0 to ADC4
> +// TS-7260: Vin, 5V_104, ADC2, Vcore, ADC4
> +// TS-7300: 5V, 1.2V, ADC2, 1.8V, ADC4
> +
> +// 0V gives 0, 3.3V gives c. 50000
> +// TODO: self calibrate by using VBAT and Load resistor switches

/* */ comments please.

> +static struct ep93xx_hwmon_data ts72xx_hwmon_info = {
> +	/* POWER_IN*10K/150K (4.5-20V) */
> +	.in[0] = &(struct ep93xx_hwmon_chcfg) {

That cast looks really ugly, is there a better way?

> +		.name		= "ts72xx-vin",
> +                .channel        = TS72XX_ADC0,
> +		.mult		= 33*(15+1),
> +		.div		= 500*1,

Spaces around operators please. Also why the explict multiplies
(especially by 1)?

> +	},
> +	/* PC104_5V*10K/54.9K (5V) */
> +	.in[1] = &(struct ep93xx_hwmon_chcfg) {
> +		.name		= "ts72xx-v5",
> +                .channel        = TS72XX_ADC4,
> +		.mult		= 33*(549+100),
> +		.div		= 500*100,
> +	},
> +	/* Vcore (1.8V)  */
> +	.in[2] = &(struct ep93xx_hwmon_chcfg) {
> +		.name		= "ts72xx-vcore",
> +                .channel        = TS72XX_ADC2,
> +		.mult		= 33*1,
> +		.div		= 500*1,
> +	},
> +	/* User ADC on DIO2.8  (0-3.3V) */
> +	.in[3] = &(struct ep93xx_hwmon_chcfg) {
> +		.name		= "ts72xx-dio2.8",
> +                .channel        = TS72XX_ADC3,
> +		.mult		= 33*1,
> +		.div		= 500*1,
> +	},
> +	/* User ADC on DIO2.10 (0-3.3V) */
> +	.in[4] = &(struct ep93xx_hwmon_chcfg) {
> +		.name		= "ts72xx-dio2.10",
> +                .channel        = TS72XX_ADC1,
> +		.mult		= 33*1,
> +		.div		= 500*1,
> +	},
> +	/* EP93XX.VBAT (1.8V)  */
> +	.in[5] = &(struct ep93xx_hwmon_chcfg) {
> +		.name		= "ep93xx-vbat",
> +                .channel        = ADC_SW_VBAT_IN|ADC_SW_APWR_REF,
> +		.mult		= 33*1,
> +		.div		= 500*1,
> +	},
> +	/* EP93XX.DAC (?)  */
> +	.in[6] = &(struct ep93xx_hwmon_chcfg) {
> +		.name		= "ep93xx-vdac",
> +                .channel        = ADC_SW_DAC_IN|ADC_SW_APWR_REF,
> +		.mult		= 33*1,
> +		.div		= 500*1,
> +	},
> +};
> +
> +
>  static void __init ts72xx_init_machine(void)
>  {
>  	ep93xx_init_devices();
> @@ -177,6 +251,10 @@ static void __init ts72xx_init_machine(void)
>  	platform_device_register(&ts72xx_rtc_device);
>  
>  	ep93xx_register_eth(&ts72xx_eth_data, 1);
> +        
> +	platform_device_register(&ep93xx_analog_device);
> +        
> +	platform_device_register(&ep93xx_hwmon_device);
>  }
>  
>  MACHINE_START(TS72XX, "Technologic Systems TS-72xx SBC")
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 700e93a..af6f08a 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -690,6 +690,23 @@ config SENSORS_S3C_RAW
>  	  Say Y here if you want to include raw copies of all the ADC
>  	  channels in sysfs.
>  
> +config SENSORS_EP93XX
> +	tristate "EP93XX on-chip ADC"
> +	depends on ARCH_EP93XX
> +	help
> +	  If you say yes here you get support for the on-board ADCs of
> +	  the Cirrus Logic EP93XX series of SoC
> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called ep93xx-hwmo.
> +
> +config SENSORS_EP93XX_RAW
> +	bool "Include raw channel attributes in sysfs"
> +	depends on SENSORS_EP93XX
> +	help
> +	  Say Y here if you want to include raw copies of all the ADC
> +	  channels in sysfs.
> +
>  config SENSORS_SIS5595
>  	tristate "Silicon Integrated Systems Corp. SiS5595"
>  	depends on PCI
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 9f46cb0..eea13fc 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -77,6 +77,7 @@ obj-$(CONFIG_SENSORS_PC87360)	+= pc87360.o
>  obj-$(CONFIG_SENSORS_PC87427)	+= pc87427.o
>  obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
>  obj-$(CONFIG_SENSORS_S3C)	+= s3c-hwmon.o
> +obj-$(CONFIG_SENSORS_EP93XX)	+= ep93xx-hwmon.o
>  obj-$(CONFIG_SENSORS_SHT15)	+= sht15.o
>  obj-$(CONFIG_SENSORS_SIS5595)	+= sis5595.o
>  obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
> diff --git a/drivers/hwmon/ep93xx-hwmon.c b/drivers/hwmon/ep93xx-hwmon.c
> new file mode 100644
> index 0000000..2e4a9f7
> --- /dev/null
> +++ b/drivers/hwmon/ep93xx-hwmon.c
> @@ -0,0 +1,416 @@
> +/* linux/drivers/hwmon/ep93xx-hwmon.c
> + *
> + * Copyright 2009, Christian Gagneraud <cgagneraud at techworks.ie>
> + *
> + * Based on linux/drivers/hwmon/s3c-hwmon.c by Simtec Electronics
> + *
> + * EP93XX ADC hwmon support
> + *
> + * 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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> +*/
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/init.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +
> +#include <mach/adc.h>
> +#include <mach/hwmon.h>
> +
> +struct ep93xx_hwmon_attr {
> +	struct sensor_device_attribute in;
> +	struct sensor_device_attribute label;
> +	char in_name[12];
> +	char label_name[12];
> +};
> +
> +/**
> + * struct ep93xx_hwmon - ADC hwmon client information
> + * @lock: Access lock to serialise the conversions.
> + * @client: The client we registered with the EP93XX ADC core.
> + * @hwmon_dev: The hwmon device we created.
> + * @attr: The holders for the channel attributes.
> +*/
> +struct ep93xx_hwmon {
> +	struct semaphore lock;
> +	struct ep93xx_adc_client *client;
> +	struct device *hwmon_dev;
> +	struct ep93xx_hwmon_attr attrs[7];
> +};
> +
> +/**
> + * ep93xx_hwmon_read_ch - read a value from a given adc channel.
> + * @dev: The device.
> + * @hwmon: Our state.
> + * @channel: The channel we're reading from.
> + *
> + * Read a value from the @channel with the proper locking and sleep until
> + * either the read completes or we timeout awaiting the ADC core to get
> + * back to us.
> + */
> +static int ep93xx_hwmon_read_ch(struct device *dev,
> +				struct ep93xx_hwmon *hwmon, int channel)
> +{
> +	int ret;
> +
> +	ret = down_interruptible(&hwmon->lock);
> +	if (ret < 0)
> +		return ret;
> +
> +	dev_dbg(dev, "reading channel %d\n", channel);
> +
> +	ret = ep93xx_adc_read(hwmon->client, channel);
> +	up(&hwmon->lock);
> +
> +	return ret;
> +}
> +
> +#ifdef CONFIG_SENSORS_EP93XX_RAW
> +/**
> + * ep93xx_hwmon_show_raw - show a conversion from the raw channel number.
> + * @dev: The device that the attribute belongs to.
> + * @attr: The attribute being read.
> + * @buf: The result buffer.
> + *
> + * This show deals with the raw attribute, registered for each possible
> + * ADC channel. This does a conversion and returns the raw (un-scaled)
> + * value returned from the hardware.
> + */
> +static ssize_t ep93xx_hwmon_show_raw(struct device *dev,
> +				     struct device_attribute *attr,
> +				     char *buf)
> +{
> +	struct ep93xx_hwmon *adc =
> +	    platform_get_drvdata(to_platform_device(dev));
> +	struct sensor_device_attribute *sa = to_sensor_dev_attr(attr);
> +	struct ep93xx_hwmon_data *pdata = dev->platform_data;
> +	struct ep93xx_hwmon_chcfg *cfg;
> +	int ret;
> +
> +	cfg = pdata->in[sa->index];
> +
> +	ret = ep93xx_hwmon_read_ch(dev, adc, cfg->channel);
> +
> +	return (ret < 0) ? ret : snprintf(buf, PAGE_SIZE, "%d\n", ret);
> +}
> +
> +#define DEF_ADC_ATTR(x)	\
> +	static SENSOR_DEVICE_ATTR(adc##x##_raw, S_IRUGO, ep93xx_hwmon_show_raw, NULL, x)
> +
> +DEF_ADC_ATTR(0);
> +DEF_ADC_ATTR(1);
> +DEF_ADC_ATTR(2);
> +DEF_ADC_ATTR(3);
> +DEF_ADC_ATTR(4);
> +DEF_ADC_ATTR(5);
> +DEF_ADC_ATTR(6);
> +
> +static struct attribute *ep93xx_hwmon_attrs[8] = {
> +	&sensor_dev_attr_adc0_raw.dev_attr.attr,
> +	&sensor_dev_attr_adc1_raw.dev_attr.attr,
> +	&sensor_dev_attr_adc2_raw.dev_attr.attr,
> +	&sensor_dev_attr_adc3_raw.dev_attr.attr,
> +	&sensor_dev_attr_adc4_raw.dev_attr.attr,
> +	&sensor_dev_attr_adc5_raw.dev_attr.attr,
> +	&sensor_dev_attr_adc6_raw.dev_attr.attr,
> +	NULL,
> +};
> +
> +static struct attribute_group ep93xx_hwmon_attrgroup = {
> +	.attrs = ep93xx_hwmon_attrs,
> +};
> +
> +static inline int ep93xx_hwmon_add_raw(struct device *dev)
> +{
> +	return sysfs_create_group(&dev->kobj, &ep93xx_hwmon_attrgroup);
> +}
> +
> +static inline void ep93xx_hwmon_remove_raw(struct device *dev)
> +{
> +	sysfs_remove_group(&dev->kobj, &ep93xx_hwmon_attrgroup);
> +}
> +
> +#else
> +
> +static inline void ep93xx_hwmon_add_raw(struct device *dev)
> +{
> +}
> +
> +static inline void ep93xx_hwmon_remove_raw(struct device *dev)
> +{
> +}
> +
> +#endif				/* CONFIG_SENSORS_EP93XX_RAW */
> +
> +/**
> + * ep93xx_hwmon_ch_show - show value of a given channel
> + * @dev: The device that the attribute belongs to.
> + * @attr: The attribute being read.
> + * @buf: The result buffer.
> + *
> + * Read a value from the ADC and scale it before returning it to the
> + * caller. The scale factor is gained from the channel configuration
> + * passed via the platform data when the device was registered.
> + */
> +static ssize_t ep93xx_hwmon_ch_show(struct device *dev,
> +				    struct device_attribute *attr,
> +				    char *buf)
> +{
> +	struct sensor_device_attribute *sen_attr =
> +	    to_sensor_dev_attr(attr);
> +	struct ep93xx_hwmon *hwmon =
> +	    platform_get_drvdata(to_platform_device(dev));
> +	struct ep93xx_hwmon_data *pdata = dev->platform_data;
> +	struct ep93xx_hwmon_chcfg *cfg;
> +	int ret;
> +
> +	cfg = pdata->in[sen_attr->index];
> +
> +	ret = ep93xx_hwmon_read_ch(dev, hwmon, cfg->channel);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret *= cfg->mult;
> +	ret = DIV_ROUND_CLOSEST(ret, cfg->div);
> +
> +	return snprintf(buf, PAGE_SIZE, "%d\n", ret);
> +}
> +
> +/**
> + * ep93xx_hwmon_label_show - show label name of the given channel.
> + * @dev: The device that the attribute belongs to.
> + * @attr: The attribute being read.
> + * @buf: The result buffer.
> + *
> + * Return the label name of a given channel
> + */
> +static ssize_t ep93xx_hwmon_label_show(struct device *dev,
> +				       struct device_attribute *attr,
> +				       char *buf)
> +{
> +	struct sensor_device_attribute *sen_attr =
> +	    to_sensor_dev_attr(attr);
> +	struct ep93xx_hwmon_data *pdata = dev->platform_data;
> +	struct ep93xx_hwmon_chcfg *cfg;
> +
> +	cfg = pdata->in[sen_attr->index];
> +
> +	return snprintf(buf, PAGE_SIZE, "%s\n", cfg->name);
> +}
> +
> +/**
> + * ep93xx_hwmon_create_attr - create hwmon attribute for given channel.
> + * @dev: The device to create the attribute on.
> + * @cfg: The channel configuration passed from the platform data.
> + * @channel: The ADC channel number to process.
> + *
> + * Create the scaled attribute for use with hwmon from the specified
> + * platform data in @pdata. The sysfs entry is handled by the routine
> + * ep93xx_hwmon_ch_show().
> + *
> + * The attribute name is taken from the configuration data if present
> + * otherwise the name is taken by concatenating in_ with the channel
> + * number.
> + */
> +static int ep93xx_hwmon_create_attr(struct device *dev,
> +				    struct ep93xx_hwmon_chcfg *cfg,
> +				    struct ep93xx_hwmon_attr *attrs,
> +				    int channel)
> +{
> +	struct sensor_device_attribute *attr;
> +	int ret;
> +
> +	snprintf(attrs->in_name, sizeof(attrs->in_name), "in%d_input",
> +		 channel);
> +
> +	attr = &attrs->in;
> +	attr->index = channel;
> +	attr->dev_attr.attr.name = attrs->in_name;
> +	attr->dev_attr.attr.mode = S_IRUGO;
> +	attr->dev_attr.attr.owner = THIS_MODULE;
> +	attr->dev_attr.show = ep93xx_hwmon_ch_show;
> +
> +	ret = device_create_file(dev, &attr->dev_attr);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to create input attribute\n");
> +		return ret;
> +	}
> +
> +	/* if this has a name, add a label */
> +	if (cfg->name) {
> +		snprintf(attrs->label_name, sizeof(attrs->label_name),
> +			 "in%d_label", channel);
> +
> +		attr = &attrs->label;
> +		attr->index = channel;
> +		attr->dev_attr.attr.name = attrs->label_name;
> +		attr->dev_attr.attr.mode = S_IRUGO;
> +		attr->dev_attr.attr.owner = THIS_MODULE;
> +		attr->dev_attr.show = ep93xx_hwmon_label_show;
> +
> +		ret = device_create_file(dev, &attr->dev_attr);
> +		if (ret < 0) {
> +			device_remove_file(dev, &attrs->in.dev_attr);
> +			dev_err(dev, "failed to create label attribute\n");
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static void ep93xx_hwmon_remove_attr(struct device *dev,
> +				     struct ep93xx_hwmon_attr *attrs)
> +{
> +	device_remove_file(dev, &attrs->in.dev_attr);
> +	device_remove_file(dev, &attrs->label.dev_attr);
> +}
> +
> +/**
> + * ep93xx_hwmon_probe - device probe entry.
> + * @dev: The device being probed.
> +*/
> +static int __devinit ep93xx_hwmon_probe(struct platform_device *dev)
> +{
> +	struct ep93xx_hwmon_data *pdata = dev->dev.platform_data;
> +	struct ep93xx_hwmon *hwmon;
> +	int ret = 0;
> +	int i;
> +
> +	if (!pdata) {
> +		dev_err(&dev->dev, "no platform data supplied\n");
> +		return -EINVAL;
> +	}
> +
> +	hwmon = kzalloc(sizeof(struct ep93xx_hwmon), GFP_KERNEL);
> +	if (hwmon == NULL) {
> +		dev_err(&dev->dev, "no memory\n");
> +		return -ENOMEM;
> +	}
> +
> +	platform_set_drvdata(dev, hwmon);
> +
> +	init_MUTEX(&hwmon->lock);
> +
> +	/* Register with the core ADC driver. */
> +
> +	hwmon->client = ep93xx_adc_register(dev);
> +	if (IS_ERR(hwmon->client)) {
> +		dev_err(&dev->dev, "cannot register adc\n");
> +		ret = PTR_ERR(hwmon->client);
> +		goto err_mem;
> +	}
> +
> +	/* add attributes for our adc devices. */
> +
> +	ret = ep93xx_hwmon_add_raw(&dev->dev);
> +	if (ret)
> +		goto err_registered;
> +
> +	/* register with the hwmon core */
> +
> +	hwmon->hwmon_dev = hwmon_device_register(&dev->dev);
> +	if (IS_ERR(hwmon->hwmon_dev)) {
> +		dev_err(&dev->dev, "error registering with hwmon\n");
> +		ret = PTR_ERR(hwmon->hwmon_dev);
> +		goto err_raw_attribute;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(pdata->in); i++) {
> +		if (!pdata->in[i])
> +			continue;
> +
> +		if (pdata->in[i]->mult >= 0x10000)
> +			dev_warn(&dev->dev,
> +				 "channel %d multiplier too large\n", i);
> +
> +		ret = ep93xx_hwmon_create_attr(&dev->dev, pdata->in[i],
> +					       &hwmon->attrs[i], i);
> +		if (ret) {
> +			dev_err(&dev->dev,
> +				"error creating channel %d\n", i);
> +
> +			for (i--; i >= 0; i--)
> +				ep93xx_hwmon_remove_attr(&dev->dev,
> +							 &hwmon->attrs[i]);
> +
> +			goto err_hwmon_register;
> +		}
> +	}
> +
> +	return 0;
> +
> +      err_hwmon_register:
> +	hwmon_device_unregister(hwmon->hwmon_dev);
> +
> +      err_raw_attribute:
> +	ep93xx_hwmon_remove_raw(&dev->dev);
> +
> +      err_registered:
> +	ep93xx_adc_release(hwmon->client);
> +
> +      err_mem:
> +	kfree(hwmon);
> +	return ret;
> +}
> +
> +static int __devexit ep93xx_hwmon_remove(struct platform_device *dev)
> +{
> +	struct ep93xx_hwmon *hwmon = platform_get_drvdata(dev);
> +	int i;
> +
> +	ep93xx_hwmon_remove_raw(&dev->dev);
> +
> +	for (i = 0; i < ARRAY_SIZE(hwmon->attrs); i++)
> +		ep93xx_hwmon_remove_attr(&dev->dev, &hwmon->attrs[i]);
> +
> +	hwmon_device_unregister(hwmon->hwmon_dev);
> +	ep93xx_adc_release(hwmon->client);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver ep93xx_hwmon_driver = {
> +	.driver = {
> +		   .name = "ep93xx-hwmon",
> +		   .owner = THIS_MODULE,
> +		   },
> +	.probe = ep93xx_hwmon_probe,
> +	.remove = __devexit_p(ep93xx_hwmon_remove),
> +};
> +
> +static int __init ep93xx_hwmon_init(void)
> +{
> +	return platform_driver_register(&ep93xx_hwmon_driver);
> +}
> +
> +static void __exit ep93xx_hwmon_exit(void)
> +{
> +	platform_driver_unregister(&ep93xx_hwmon_driver);
> +}
> +
> +module_init(ep93xx_hwmon_init);
> +module_exit(ep93xx_hwmon_exit);
> +
> +MODULE_AUTHOR("Christian Gagneraud <cgagneraud at techworks.ie>");
> +MODULE_DESCRIPTION("EP93XX ADC HWMon driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ep93xx-hwmon");
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel


-- 
Bluewater Systems Ltd - ARM Technology Solution Centre

Ryan Mallon         		5 Amuri Park, 404 Barbadoes St
ryan at bluewatersys.com         	PO Box 13 889, Christchurch 8013
http://www.bluewatersys.com	New Zealand
Phone: +64 3 3779127		Freecall: Australia 1800 148 751
Fax:   +64 3 3779135			  USA 1800 261 2934



More information about the linux-arm-kernel mailing list