All of lore.kernel.org
 help / color / mirror / Atom feed
From: Lars-Peter Clausen <lars@metafoo.de>
To: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Cc: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-samsung-soc@vger.kernel.org, dianders@chromium.org,
	gregkh@linuxfoundation.org, naveenkrishna.ch@gmail.com
Subject: Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
Date: Tue, 22 Jan 2013 10:44:22 +0100	[thread overview]
Message-ID: <50FE5F76.7090803@metafoo.de> (raw)
In-Reply-To: <1358775470-21278-1-git-send-email-ch.naveen@samsung.com>

Hi,

On 01/21/2013 02:37 PM, Naveen Krishna Chatradhi wrote:
> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
> Also adds the Documentation for device tree bindings.
>[...]
> diff --git a/drivers/iio/adc/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
> new file mode 100644
> index 0000000..cd33ea2
> --- /dev/null
> +++ b/drivers/iio/adc/exynos5_adc.c
> @@ -0,0 +1,412 @@
> +/*
> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
> + *
> + *  8-channel, 10/12-bit ADC
> + *
> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> + *
> + *  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.
> + *
> + *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +/* Samsung ADC registers definitions */
> +#define EXYNOS_ADC_CON(x)	((x) + 0x00)
> +#define EXYNOS_ADC_DLY(x)	((x) + 0x08)
> +#define EXYNOS_ADC_DATX(x)	((x) + 0x0C)
> +#define EXYNOS_ADC_INTCLR(x)	((x) + 0x18)
> +#define EXYNOS_ADC_MUX(x)	((x) + 0x1c)
> +
> +/* Bit definitions for EXYNOS_ADC_MUX: */
> +#define ADC_RES		(1u << 16)
> +#define ADC_ECFLG	(1u << 15)
> +#define ADC_PRSCEN	(1u << 14)
> +#define ADC_PRSCLV(x)	(((x) & 0xFF) << 6)
> +#define ADC_PRSCVLMASK	(0xFF << 6)
> +#define ADC_STANDBY	(1u << 2)
> +#define ADC_READ_START	(1u << 1)
> +#define ADC_EN_START	(1u << 0)

You are a bit inconsistent with your prefixes, sometimes you use EXYNOS_ADC,
sometimes just ADC, it would be better to use the same prefix all the time.

> +
> +#define ADC_DATX_MASK	0xFFF
> +
> +struct exynos5_adc {
> +	void __iomem		*regs;
> +	struct clk		*clk;
> +	unsigned int		irq;
> +	struct regulator	*vdd;
> +
> +	struct completion	completion;
> +
> +	struct iio_map		*map;
> +	u32			value;
> +	u32			prescale;
> +};
> +
> +static const struct of_device_id exynos5_adc_match[] = {
> +	{ .compatible = "samsung,exynos5250-adc" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
> +
> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
> +static struct iio_map exynos5_adc_iio_maps[] = {
> +	{
> +		.consumer_dev_name = "0.ncp15wb473",
> +		.consumer_channel = "adc_user3",
> +		.adc_channel_label = "adc3",
> +	},
> +	{},
> +};

Hm... this should not be in the driver itself.

> +
> +static int exynos5_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 con;
> +
> +	if (mask == IIO_CHAN_INFO_RAW) {
> +		mutex_lock(&indio_dev->mlock);
> +
> +		/* Select the channel to be used */
> +		writel(chan->address, EXYNOS_ADC_MUX(info->regs));
> +		/* Trigger conversion */
> +		con = readl(EXYNOS_ADC_CON(info->regs));
> +		writel(con | ADC_EN_START, EXYNOS_ADC_CON(info->regs));
> +
> +		wait_for_completion(&info->completion);
> +		*val = info->value;
> +
> +		mutex_unlock(&indio_dev->mlock);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
> +{
> +	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
> +
> +	/* Read value and clear irq */
> +	info->value = readl(EXYNOS_ADC_DATX(info->regs)) &
> +				ADC_DATX_MASK;
> +	writel(0, EXYNOS_ADC_INTCLR(info->regs));
> +
> +	complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 ret;
> +
> +	mutex_lock(&indio_dev->mlock);

Do we really need to take the lock here?

> +
> +	if (readval != NULL) {
> +		if (reg == 0x08)
> +			ret = readl(EXYNOS_ADC_DLY(info->regs));
> +		else if (reg == 0x0C)
> +			ret = readl(EXYNOS_ADC_DATX(info->regs));
> +		else if (reg == 0x1C)
> +			ret = readl(EXYNOS_ADC_MUX(info->regs));
> +		else
> +			ret = readl(EXYNOS_ADC_CON(info->regs));

How about
		ret = readl(info->regs + reg)

instead of the whole if ... else if ... else if ...

> +
> +		readval = &ret;

Uhm, I'm pretty sure that the line above does not work. At least it won't do
what you want it to do.

> +	} else
> +		ret = -EINVAL;
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info exynos5_adc_iio_info = {
> +	.read_raw = &exynos5_read_raw,
> +	.debugfs_reg_access = &exynos5_adc_reg_access,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define EXYNOS_ADC_CHANNEL(_index, _id) {		\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.channel = _index,				\
> +	.address = _index,				\
> +	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	.datasheet_name = _id,				\
> +}
> +
> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
> +	EXYNOS_ADC_CHANNEL(0, "adc0"),
> +	EXYNOS_ADC_CHANNEL(1, "adc1"),
> +	EXYNOS_ADC_CHANNEL(2, "adc2"),
> +	EXYNOS_ADC_CHANNEL(3, "adc3"),
> +	EXYNOS_ADC_CHANNEL(4, "adc4"),
> +	EXYNOS_ADC_CHANNEL(5, "adc5"),
> +	EXYNOS_ADC_CHANNEL(6, "adc6"),
> +	EXYNOS_ADC_CHANNEL(7, "adc7"),
> +};
> +
> +static int exynos5_adc_iio_map_register(struct iio_dev *indio_dev,
> +				struct exynos5_adc *adc)
> +{
> +	int ret;
> +
> +	adc->map = exynos5_adc_iio_maps;
> +
> +	ret = iio_map_array_register(indio_dev, adc->map);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "iio map err: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}

Just use iio_map_array_register instead of exynos5_adc_iio_map_register

> +
> +static inline void exynos5_adc_iio_map_unregister(struct iio_dev *indio_dev,
> +				struct exynos5_adc *adc)
> +{
> +	iio_map_array_unregister(indio_dev, adc->map);
> +}

Just use iio_map_array_unregister directly instead of
exynos5_adc_iio_map_unregister

> +
> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	platform_device_unregister(pdev);
> +
> +	return 0;
> +}

Again, a single line wrapper function...

> +
> +static int exynos5_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos5_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *iodev = NULL;

this is usually called indio_dev, would be good to be consistent across dirvers.

> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +	u32 con;
> +
> +	iodev = iio_device_alloc(sizeof(struct exynos5_adc));
> +	if (!iodev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(iodev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!mem) {

devm_request_and_ioremap will check if mem is NULL, so you don't need to do
this manually.

> +		dev_err(&pdev->dev, "no mem resource?\n");
> +		goto err_iio;
> +	}
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> +	if (!info->regs) {
> +		dev_err(&pdev->dev, "cannot map EXYNOS5 ADC registers\n");

devm_request_and_ioremap already prints an error message.

> +		ret = -ENOMEM;
> +		goto err_iio;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	ret = devm_request_irq(&pdev->dev, info->irq, exynos5_adc_isr,
> +					0, dev_name(&pdev->dev), info);

Using devm_request_irq is racy in this case, since the IRQ will be freed
after the device has been freed, while it needs to be freed before the devie
has been freed.

> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "cannot request Samsung ADC IRQ %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock\n");

I think it is a good idea to include the error code in the error message.

> +		ret = PTR_ERR(info->clk);
> +		goto err_iio;
> +	}
> +
> +	clk_enable(info->clk);
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "operating without regulator \"vdd\" .\n");

Same here.

> +		ret = PTR_ERR(info->vdd);
> +		goto err_iio;
> +	}
> +
> +	platform_set_drvdata(pdev, iodev);
> +
> +	init_completion(&info->completion);
> +
> +	iodev->name = dev_name(&pdev->dev);
> +	iodev->dev.parent = &pdev->dev;
> +	iodev->dev.of_node = pdev->dev.of_node;
> +	iodev->info = &exynos5_adc_iio_info;
> +	iodev->modes = INDIO_DIRECT_MODE;
> +	iodev->channels = exynos5_adc_iio_channels;
> +	iodev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
> +
> +	/* Enable prescaler and set platform default */
> +	info->prescale = ADC_PRSCLV(49);
> +	con = info->prescale | ADC_PRSCEN;
> +
> +	/* Enable 12-bit ADC resolution */
> +	con |= ADC_RES;
> +
> +	writel(con, EXYNOS_ADC_CON(info->regs));
> +
> +	ret = exynos5_adc_iio_map_register(iodev, info);
> +	if (ret)
> +		goto err_iio;
> +
> +	ret = iio_device_register(iodev);
> +	if (ret)
> +		goto err_map;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;
> +
> +	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);

Which kind of child nodes to you expect it to have? There is nothing about
this in your dt bindings documentation.

> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to add child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	dev_info(&pdev->dev, "EXYNOS5 ADC driver loaded.\n");

That's just noise, please remove it.

> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
> +err_iio_dev:
> +	iio_device_unregister(iodev);
> +err_map:
> +	exynos5_adc_iio_map_unregister(iodev, info);
> +err_iio:
> +	iio_device_free(iodev);
> +	return ret;
> +}
> +
> +static int exynos5_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *iodev = platform_get_drvdata(pdev);
> +	struct exynos5_adc *info = iio_priv(iodev);
> +
> +	platform_set_drvdata(pdev, NULL);

The line above should not be needed

> +	iio_device_unregister(iodev);
> +	exynos5_adc_iio_map_unregister(iodev, info);
> +	iio_device_free(iodev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM

CONFIG_PM_SLEEP

> +static int exynos5_adc_suspend(struct device *dev)
> +{
> +	struct platform_device *pdev = container_of(dev,
> +			struct platform_device, dev);

to_platform_device

> +	struct exynos5_adc *info = platform_get_drvdata(pdev);

Although using dev_get_data(dev) here would also be ok I guess.

> +	u32 con;
> +
> +	con = readl(EXYNOS_ADC_CON(info->regs));
> +	con |= ADC_STANDBY;
> +	writel(con, EXYNOS_ADC_CON(info->regs));
> +
> +	clk_disable(info->clk);

clk_unprepare_disable

> +	regulator_disable(info->vdd);
> +
> +	return 0;
> +}
> +
> +static int exynos5_adc_resume(struct device *dev)
> +{
> +	struct platform_device *pdev = container_of(dev,
> +			struct platform_device, dev);

Same as above.

> +	struct exynos5_adc *info = platform_get_drvdata(pdev);
> +	unsigned int con;
> +	int ret;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		return ret;
> +
> +	clk_enable(info->clk);

clk_prepare_enable

> +
> +	/* TODO: Enable prescalar */
> +	con = info->prescale | ADC_PRSCEN;

I guess you need to enable the prescaler here ;) (or removed the TODO comment)

> +	writel(con | ADC_RES, EXYNOS_ADC_CON(info->regs));
> +
> +	return 0;
> +}
> +
> +#else
> +#define s3c_adc_suspend NULL
> +#define s3c_adc_resume NULL
> +#endif
> +
> +static const struct dev_pm_ops adc_pm_ops = {
> +	.suspend	= exynos5_adc_suspend,
> +	.resume		= exynos5_adc_resume,
> +};

static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops, exynos5_adc_suspend,
	exynos5_adc_resume);

> +
> +static struct platform_driver exynos5_adc_driver = {
> +	.probe		= exynos5_adc_probe,
> +	.remove		= exynos5_adc_remove,
> +	.driver		= {
> +		.name	= "exynos5-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = of_match_ptr(exynos5_adc_match),
> +		.pm	= &adc_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(exynos5_adc_driver);
> +
> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> +MODULE_LICENSE("GPL");


WARNING: multiple messages have this Message-ID (diff)
From: Lars-Peter Clausen <lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org>
To: Naveen Krishna Chatradhi
	<ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Cc: linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-samsung-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	dianders-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org,
	gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r@public.gmane.org,
	naveenkrishna.ch-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
Subject: Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
Date: Tue, 22 Jan 2013 10:44:22 +0100	[thread overview]
Message-ID: <50FE5F76.7090803@metafoo.de> (raw)
In-Reply-To: <1358775470-21278-1-git-send-email-ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>

Hi,

On 01/21/2013 02:37 PM, Naveen Krishna Chatradhi wrote:
> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
> Also adds the Documentation for device tree bindings.
>[...]
> diff --git a/drivers/iio/adc/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
> new file mode 100644
> index 0000000..cd33ea2
> --- /dev/null
> +++ b/drivers/iio/adc/exynos5_adc.c
> @@ -0,0 +1,412 @@
> +/*
> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
> + *
> + *  8-channel, 10/12-bit ADC
> + *
> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> + *
> + *  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.
> + *
> + *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +/* Samsung ADC registers definitions */
> +#define EXYNOS_ADC_CON(x)	((x) + 0x00)
> +#define EXYNOS_ADC_DLY(x)	((x) + 0x08)
> +#define EXYNOS_ADC_DATX(x)	((x) + 0x0C)
> +#define EXYNOS_ADC_INTCLR(x)	((x) + 0x18)
> +#define EXYNOS_ADC_MUX(x)	((x) + 0x1c)
> +
> +/* Bit definitions for EXYNOS_ADC_MUX: */
> +#define ADC_RES		(1u << 16)
> +#define ADC_ECFLG	(1u << 15)
> +#define ADC_PRSCEN	(1u << 14)
> +#define ADC_PRSCLV(x)	(((x) & 0xFF) << 6)
> +#define ADC_PRSCVLMASK	(0xFF << 6)
> +#define ADC_STANDBY	(1u << 2)
> +#define ADC_READ_START	(1u << 1)
> +#define ADC_EN_START	(1u << 0)

You are a bit inconsistent with your prefixes, sometimes you use EXYNOS_ADC,
sometimes just ADC, it would be better to use the same prefix all the time.

> +
> +#define ADC_DATX_MASK	0xFFF
> +
> +struct exynos5_adc {
> +	void __iomem		*regs;
> +	struct clk		*clk;
> +	unsigned int		irq;
> +	struct regulator	*vdd;
> +
> +	struct completion	completion;
> +
> +	struct iio_map		*map;
> +	u32			value;
> +	u32			prescale;
> +};
> +
> +static const struct of_device_id exynos5_adc_match[] = {
> +	{ .compatible = "samsung,exynos5250-adc" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
> +
> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
> +static struct iio_map exynos5_adc_iio_maps[] = {
> +	{
> +		.consumer_dev_name = "0.ncp15wb473",
> +		.consumer_channel = "adc_user3",
> +		.adc_channel_label = "adc3",
> +	},
> +	{},
> +};

Hm... this should not be in the driver itself.

> +
> +static int exynos5_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 con;
> +
> +	if (mask == IIO_CHAN_INFO_RAW) {
> +		mutex_lock(&indio_dev->mlock);
> +
> +		/* Select the channel to be used */
> +		writel(chan->address, EXYNOS_ADC_MUX(info->regs));
> +		/* Trigger conversion */
> +		con = readl(EXYNOS_ADC_CON(info->regs));
> +		writel(con | ADC_EN_START, EXYNOS_ADC_CON(info->regs));
> +
> +		wait_for_completion(&info->completion);
> +		*val = info->value;
> +
> +		mutex_unlock(&indio_dev->mlock);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
> +{
> +	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
> +
> +	/* Read value and clear irq */
> +	info->value = readl(EXYNOS_ADC_DATX(info->regs)) &
> +				ADC_DATX_MASK;
> +	writel(0, EXYNOS_ADC_INTCLR(info->regs));
> +
> +	complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 ret;
> +
> +	mutex_lock(&indio_dev->mlock);

Do we really need to take the lock here?

> +
> +	if (readval != NULL) {
> +		if (reg == 0x08)
> +			ret = readl(EXYNOS_ADC_DLY(info->regs));
> +		else if (reg == 0x0C)
> +			ret = readl(EXYNOS_ADC_DATX(info->regs));
> +		else if (reg == 0x1C)
> +			ret = readl(EXYNOS_ADC_MUX(info->regs));
> +		else
> +			ret = readl(EXYNOS_ADC_CON(info->regs));

How about
		ret = readl(info->regs + reg)

instead of the whole if ... else if ... else if ...

> +
> +		readval = &ret;

Uhm, I'm pretty sure that the line above does not work. At least it won't do
what you want it to do.

> +	} else
> +		ret = -EINVAL;
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info exynos5_adc_iio_info = {
> +	.read_raw = &exynos5_read_raw,
> +	.debugfs_reg_access = &exynos5_adc_reg_access,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define EXYNOS_ADC_CHANNEL(_index, _id) {		\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.channel = _index,				\
> +	.address = _index,				\
> +	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	.datasheet_name = _id,				\
> +}
> +
> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
> +	EXYNOS_ADC_CHANNEL(0, "adc0"),
> +	EXYNOS_ADC_CHANNEL(1, "adc1"),
> +	EXYNOS_ADC_CHANNEL(2, "adc2"),
> +	EXYNOS_ADC_CHANNEL(3, "adc3"),
> +	EXYNOS_ADC_CHANNEL(4, "adc4"),
> +	EXYNOS_ADC_CHANNEL(5, "adc5"),
> +	EXYNOS_ADC_CHANNEL(6, "adc6"),
> +	EXYNOS_ADC_CHANNEL(7, "adc7"),
> +};
> +
> +static int exynos5_adc_iio_map_register(struct iio_dev *indio_dev,
> +				struct exynos5_adc *adc)
> +{
> +	int ret;
> +
> +	adc->map = exynos5_adc_iio_maps;
> +
> +	ret = iio_map_array_register(indio_dev, adc->map);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "iio map err: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}

Just use iio_map_array_register instead of exynos5_adc_iio_map_register

> +
> +static inline void exynos5_adc_iio_map_unregister(struct iio_dev *indio_dev,
> +				struct exynos5_adc *adc)
> +{
> +	iio_map_array_unregister(indio_dev, adc->map);
> +}

Just use iio_map_array_unregister directly instead of
exynos5_adc_iio_map_unregister

> +
> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	platform_device_unregister(pdev);
> +
> +	return 0;
> +}

Again, a single line wrapper function...

> +
> +static int exynos5_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos5_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *iodev = NULL;

this is usually called indio_dev, would be good to be consistent across dirvers.

> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +	u32 con;
> +
> +	iodev = iio_device_alloc(sizeof(struct exynos5_adc));
> +	if (!iodev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(iodev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!mem) {

devm_request_and_ioremap will check if mem is NULL, so you don't need to do
this manually.

> +		dev_err(&pdev->dev, "no mem resource?\n");
> +		goto err_iio;
> +	}
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> +	if (!info->regs) {
> +		dev_err(&pdev->dev, "cannot map EXYNOS5 ADC registers\n");

devm_request_and_ioremap already prints an error message.

> +		ret = -ENOMEM;
> +		goto err_iio;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	ret = devm_request_irq(&pdev->dev, info->irq, exynos5_adc_isr,
> +					0, dev_name(&pdev->dev), info);

Using devm_request_irq is racy in this case, since the IRQ will be freed
after the device has been freed, while it needs to be freed before the devie
has been freed.

> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "cannot request Samsung ADC IRQ %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock\n");

I think it is a good idea to include the error code in the error message.

> +		ret = PTR_ERR(info->clk);
> +		goto err_iio;
> +	}
> +
> +	clk_enable(info->clk);
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "operating without regulator \"vdd\" .\n");

Same here.

> +		ret = PTR_ERR(info->vdd);
> +		goto err_iio;
> +	}
> +
> +	platform_set_drvdata(pdev, iodev);
> +
> +	init_completion(&info->completion);
> +
> +	iodev->name = dev_name(&pdev->dev);
> +	iodev->dev.parent = &pdev->dev;
> +	iodev->dev.of_node = pdev->dev.of_node;
> +	iodev->info = &exynos5_adc_iio_info;
> +	iodev->modes = INDIO_DIRECT_MODE;
> +	iodev->channels = exynos5_adc_iio_channels;
> +	iodev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
> +
> +	/* Enable prescaler and set platform default */
> +	info->prescale = ADC_PRSCLV(49);
> +	con = info->prescale | ADC_PRSCEN;
> +
> +	/* Enable 12-bit ADC resolution */
> +	con |= ADC_RES;
> +
> +	writel(con, EXYNOS_ADC_CON(info->regs));
> +
> +	ret = exynos5_adc_iio_map_register(iodev, info);
> +	if (ret)
> +		goto err_iio;
> +
> +	ret = iio_device_register(iodev);
> +	if (ret)
> +		goto err_map;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;
> +
> +	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);

Which kind of child nodes to you expect it to have? There is nothing about
this in your dt bindings documentation.

> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to add child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	dev_info(&pdev->dev, "EXYNOS5 ADC driver loaded.\n");

That's just noise, please remove it.

> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
> +err_iio_dev:
> +	iio_device_unregister(iodev);
> +err_map:
> +	exynos5_adc_iio_map_unregister(iodev, info);
> +err_iio:
> +	iio_device_free(iodev);
> +	return ret;
> +}
> +
> +static int exynos5_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *iodev = platform_get_drvdata(pdev);
> +	struct exynos5_adc *info = iio_priv(iodev);
> +
> +	platform_set_drvdata(pdev, NULL);

The line above should not be needed

> +	iio_device_unregister(iodev);
> +	exynos5_adc_iio_map_unregister(iodev, info);
> +	iio_device_free(iodev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM

CONFIG_PM_SLEEP

> +static int exynos5_adc_suspend(struct device *dev)
> +{
> +	struct platform_device *pdev = container_of(dev,
> +			struct platform_device, dev);

to_platform_device

> +	struct exynos5_adc *info = platform_get_drvdata(pdev);

Although using dev_get_data(dev) here would also be ok I guess.

> +	u32 con;
> +
> +	con = readl(EXYNOS_ADC_CON(info->regs));
> +	con |= ADC_STANDBY;
> +	writel(con, EXYNOS_ADC_CON(info->regs));
> +
> +	clk_disable(info->clk);

clk_unprepare_disable

> +	regulator_disable(info->vdd);
> +
> +	return 0;
> +}
> +
> +static int exynos5_adc_resume(struct device *dev)
> +{
> +	struct platform_device *pdev = container_of(dev,
> +			struct platform_device, dev);

Same as above.

> +	struct exynos5_adc *info = platform_get_drvdata(pdev);
> +	unsigned int con;
> +	int ret;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		return ret;
> +
> +	clk_enable(info->clk);

clk_prepare_enable

> +
> +	/* TODO: Enable prescalar */
> +	con = info->prescale | ADC_PRSCEN;

I guess you need to enable the prescaler here ;) (or removed the TODO comment)

> +	writel(con | ADC_RES, EXYNOS_ADC_CON(info->regs));
> +
> +	return 0;
> +}
> +
> +#else
> +#define s3c_adc_suspend NULL
> +#define s3c_adc_resume NULL
> +#endif
> +
> +static const struct dev_pm_ops adc_pm_ops = {
> +	.suspend	= exynos5_adc_suspend,
> +	.resume		= exynos5_adc_resume,
> +};

static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops, exynos5_adc_suspend,
	exynos5_adc_resume);

> +
> +static struct platform_driver exynos5_adc_driver = {
> +	.probe		= exynos5_adc_probe,
> +	.remove		= exynos5_adc_remove,
> +	.driver		= {
> +		.name	= "exynos5-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = of_match_ptr(exynos5_adc_match),
> +		.pm	= &adc_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(exynos5_adc_driver);
> +
> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> +MODULE_LICENSE("GPL");

  reply	other threads:[~2013-01-22  9:43 UTC|newest]

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
2013-01-21 13:37 ` Naveen Krishna Chatradhi
2013-01-22  9:44 ` Lars-Peter Clausen [this message]
2013-01-22  9:44   ` Lars-Peter Clausen
2013-01-22 14:03   ` Naveen Krishna Ch
2013-01-22 14:03     ` Naveen Krishna Ch
2013-01-22 14:27 ` Naveen Krishna Chatradhi
2013-01-23  4:58 ` Naveen Krishna Chatradhi
2013-01-23 12:52   ` Lars-Peter Clausen
2013-01-24  0:42     ` Doug Anderson
2013-01-24  0:42       ` Doug Anderson
2013-01-24  9:54       ` Lars-Peter Clausen
2013-01-24  9:54         ` Lars-Peter Clausen
2013-01-24 14:20         ` Naveen Krishna Ch
2013-01-24 14:20           ` Naveen Krishna Ch
2013-01-24 18:11           ` Lars-Peter Clausen
2013-01-24 16:12         ` Doug Anderson
2013-01-24 18:19           ` Lars-Peter Clausen
2013-01-24 18:19             ` Lars-Peter Clausen
2013-01-24 19:15             ` Tomasz Figa
2013-01-24 19:15               ` Tomasz Figa
2013-01-24 19:30               ` Lars-Peter Clausen
2013-02-12 21:07   ` Guenter Roeck
2013-02-13  2:48     ` Naveen Krishna Ch
2013-02-13  2:48       ` Naveen Krishna Ch
2013-02-13 11:05       ` Naveen Krishna Ch
2013-02-13 11:05         ` Naveen Krishna Ch
2013-02-13 13:16       ` Naveen Krishna Ch
2013-02-13 13:30         ` Lars-Peter Clausen
2013-02-13 13:53           ` Naveen Krishna Ch
2013-02-13 13:53             ` Naveen Krishna Ch
2013-02-13 14:05             ` Lars-Peter Clausen
2013-02-13 15:51         ` Guenter Roeck
2013-02-13 15:51           ` Guenter Roeck
2013-01-24  4:58 ` [PATCH] " Naveen Krishna Chatradhi
2013-01-24  4:58   ` Naveen Krishna Chatradhi
2013-01-26 10:57   ` Jonathan Cameron
2013-01-26 10:57     ` Jonathan Cameron
2013-01-30  6:02     ` Naveen Krishna Ch
2013-01-24  5:05 ` Naveen Krishna Chatradhi
2013-02-12  1:22   ` Olof Johansson
2013-02-14 12:11 ` [PATCH v6] iio: adc: add exynos " Naveen Krishna Chatradhi
2013-02-14 12:11   ` Naveen Krishna Chatradhi
2013-02-14 20:55   ` Lars-Peter Clausen
2013-02-14 20:55     ` Lars-Peter Clausen
2013-02-15  6:56   ` [PATCH v7] " Naveen Krishna Chatradhi
2013-02-15 13:13     ` Lars-Peter Clausen
2013-02-15 13:17       ` Naveen Krishna Ch
2013-02-15 13:17         ` Naveen Krishna Ch
2013-02-15 13:26         ` Lars-Peter Clausen
2013-02-15 13:35           ` Naveen Krishna Ch
2013-03-03 12:16             ` Jonathan Cameron
2013-03-03 12:16               ` Jonathan Cameron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=50FE5F76.7090803@metafoo.de \
    --to=lars@metafoo.de \
    --cc=ch.naveen@samsung.com \
    --cc=dianders@chromium.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-samsung-soc@vger.kernel.org \
    --cc=naveenkrishna.ch@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.