Hello All, On 22 January 2013 15:14, Lars-Peter Clausen <lars@xxxxxxxxxx> wrote: > > 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@xxxxxxxxxxx> > > + * > > + * 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. Will correct that. > > > + > > +#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. I've taken the example of "drivers/iio/adc/lp8788_adc.c" I think this one is a tightly coupled driver. Kindly, suggest me a better example. > > > + > > +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? Took from the reference > > > + > > + 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 ... Sure > > > + > > + 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. Right, sorry > > > + } 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"), The next version has 10 channels and future version are gonna support more that that. May be my earlier doubt will fix this as well. > > +}; > > + > > +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... This is a call back. So.. > > > + > > +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. Sure. > > > + 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. Sure > > > + 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. I've tested with ntc thermistors (hwmon device) on a chromebook. > > > + 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 Right, sure > > > + 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) Sorry, missed to remove the 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@xxxxxxxxxxx>"); > > +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver"); > > +MODULE_LICENSE("GPL"); > Lars, Thanks for your comments. Will submit a V2 soon.. -- Shine bright, (: Nav :) -- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html