[RFC PATCH v1] EP93XX: Add ADC support

Christian Gagneraud cgagneraud at techworks.ie
Tue Oct 13 09:17:01 EDT 2009


Ryan Mallon wrote:
> 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;
> 	...
> };

OK.

> 
>> +};
>> +
>> +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
>  above the first functions in this file.

Bit of nitpick, but I will move the first functions _below_ the 
structure definitions and variable declarations. :)

> 
>> +
>> +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?

Actually I need to use clock_get_rate I think. But yes, why not 
putting the clock configuration in the platform data in the meantime.

Any particular comment about using mdelay within 
local_irq_save/restore context (used to protect the list), and within 
an interrupt handler (needed to fire the next operation).

> 
>> +	}
>> +
>> +	/* 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?

I think they are this function is called only by ep93xx_adc_start() 
and the interrupt handler, both use local_irq_save/restore.

> 
>> +		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__);

Will do.

> 
>> +		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?

Yes, it was a typo. I removed as well the 
EXPORT_SYMBOL_GPL(ep93xx_adc_start).

> 
>> +
>> +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?

Yes, it was a quick debug thingy that I forgot to remove.

> 
>> +
>> +	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.

OK, no problem.

> 
>> +	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?

I will clarify the comment, actually I meant:
If all samples are done for this client (channel), disable IRQ, and 
kick start the next client (channel) if any.

> 
>> +	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.

OK, will do.

> 
>> +
>> +	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.

Sure!

> 
>> +	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.

OK, I will remove that.

> 
>> +
>> +	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.

I think I still need to find the best way to cope with that, 0xFFFF is 
because we're manipulating 16bits data and 25000 is the value for 
Vref/2 (from datasheet).

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

Why? isn't clk_put() the reverse of clk_get()?

> 
>> +
>> +      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?

As I said in a previous email, there's ADC stuff and there's 
touchscreen stuff, both use the same ressource, so I chossed a 
"neutral" name for these shared ressource (it's not ADC, it's not TS, 
it's "analog").

I'm really thinking about to make everything ADC centric.


> 
>> +		   .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.

OK.

> 
>> +
>> +	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 <<

Yeah, I have to fix all these space issue, and I have to fix my emacs 
settings.

> 
>> +
>> +/* 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__?

Yes, I saw that as well, but as I said in another email, I first tried 
not to do cosmetic changes, so i leaved this guy as it was.

> 
>> +
>> +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.

Sure.

> 
>> +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)?

I didn't want to put any magic numbers here, so it means 3.3V 
multiplied by 150K resistor in serie with 10K resistor, divided by a 
10K resistor multiplied by the full scale value.

> 
>> +	},
>> +	/* 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");

Thanks for the comments,
Chris

>>
>>
>> _______________________________________________
>> linux-arm-kernel mailing list
>> linux-arm-kernel at lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
> 
> 




More information about the linux-arm-kernel mailing list