cregit-Linux how code gets into the kernel

Release 4.14 drivers/iio/adc/ep93xx_adc.c

Directory: drivers/iio/adc
/*
 * Driver for ADC module on the Cirrus Logic EP93xx series of SoCs
 *
 * Copyright (C) 2015 Alexander Sverdlin
 *
 * 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.
 *
 * The driver uses polling to get the conversion status. According to EP93xx
 * datasheets, reading ADCResult register starts the conversion, but user is also
 * responsible for ensuring that delay between adjacent conversion triggers is
 * long enough so that maximum allowed conversion rate is not exceeded. This
 * basically renders IRQ mode unusable.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/iio/iio.h>
#include <linux/io.h>
#include <linux/irqflags.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>

/*
 * This code could benefit from real HR Timers, but jiffy granularity would
 * lower ADC conversion rate down to CONFIG_HZ, so we fallback to busy wait
 * in such case.
 *
 * HR Timers-based version loads CPU only up to 10% during back to back ADC
 * conversion, while busy wait-based version consumes whole CPU power.
 */
#ifdef CONFIG_HIGH_RES_TIMERS

#define ep93xx_adc_delay(usmin, usmax) usleep_range(usmin, usmax)
#else

#define ep93xx_adc_delay(usmin, usmax) udelay(usmin)
#endif


#define EP93XX_ADC_RESULT	0x08

#define   EP93XX_ADC_SDR	BIT(31)

#define EP93XX_ADC_SWITCH	0x18

#define EP93XX_ADC_SW_LOCK	0x20


struct ep93xx_adc_priv {
	
struct clk *clk;
	
void __iomem *base;
	
int lastch;
	
struct mutex lock;
};


#define EP93XX_ADC_CH(index, dname, swcfg) {                       \
        .type = IIO_VOLTAGE,                                    \
        .indexed = 1,                                           \
        .channel = index,                                       \
        .address = swcfg,                                       \
        .datasheet_name = dname,                                \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),           \
        .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) |   \
                                   BIT(IIO_CHAN_INFO_OFFSET),   \
}

/*
 * Numbering scheme for channels 0..4 is defined in EP9301 and EP9302 datasheets.
 * EP9307, EP9312 and EP9312 have 3 channels more (total 8), but the numbering is
 * not defined. So the last three are numbered randomly, let's say.
 */

static const struct iio_chan_spec ep93xx_adc_channels[8] = {
	EP93XX_ADC_CH(0, "YM",	0x608),
	EP93XX_ADC_CH(1, "SXP",	0x680),
	EP93XX_ADC_CH(2, "SXM",	0x640),
	EP93XX_ADC_CH(3, "SYP",	0x620),
	EP93XX_ADC_CH(4, "SYM",	0x610),
	EP93XX_ADC_CH(5, "XP",	0x601),
	EP93XX_ADC_CH(6, "XM",	0x602),
	EP93XX_ADC_CH(7, "YP",	0x604),
};


static int ep93xx_read_raw(struct iio_dev *iiodev, struct iio_chan_spec const *channel, int *value, int *shift, long mask) { struct ep93xx_adc_priv *priv = iio_priv(iiodev); unsigned long timeout; int ret; switch (mask) { case IIO_CHAN_INFO_RAW: mutex_lock(&priv->lock); if (priv->lastch != channel->channel) { priv->lastch = channel->channel; /* * Switch register is software-locked, unlocking must be * immediately followed by write */ local_irq_disable(); writel_relaxed(0xAA, priv->base + EP93XX_ADC_SW_LOCK); writel_relaxed(channel->address, priv->base + EP93XX_ADC_SWITCH); local_irq_enable(); /* * Settling delay depends on module clock and could be * 2ms or 500us */ ep93xx_adc_delay(2000, 2000); } /* Start the conversion, eventually discarding old result */ readl_relaxed(priv->base + EP93XX_ADC_RESULT); /* Ensure maximum conversion rate is not exceeded */ ep93xx_adc_delay(DIV_ROUND_UP(1000000, 925), DIV_ROUND_UP(1000000, 925)); /* At this point conversion must be completed, but anyway... */ ret = IIO_VAL_INT; timeout = jiffies + msecs_to_jiffies(1) + 1; while (1) { u32 t; t = readl_relaxed(priv->base + EP93XX_ADC_RESULT); if (t & EP93XX_ADC_SDR) { *value = sign_extend32(t, 15); break; } if (time_after(jiffies, timeout)) { dev_err(&iiodev->dev, "Conversion timeout\n"); ret = -ETIMEDOUT; break; } cpu_relax(); } mutex_unlock(&priv->lock); return ret; case IIO_CHAN_INFO_OFFSET: /* According to datasheet, range is -25000..25000 */ *value = 25000; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: /* Typical supply voltage is 3.3v */ *value = (1ULL << 32) * 3300 / 50000; *shift = 32; return IIO_VAL_FRACTIONAL_LOG2; } return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Sverdlin286100.00%1100.00%
Total286100.00%1100.00%

static const struct iio_info ep93xx_adc_info = { .driver_module = THIS_MODULE, .read_raw = ep93xx_read_raw, };
static int ep93xx_adc_probe(struct platform_device *pdev) { int ret; struct iio_dev *iiodev; struct ep93xx_adc_priv *priv; struct clk *pclk; struct resource *res; iiodev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv)); if (!iiodev) return -ENOMEM; priv = iio_priv(iiodev); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Cannot obtain memory resource\n"); return -ENXIO; } priv->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->base)) { dev_err(&pdev->dev, "Cannot map memory resource\n"); return PTR_ERR(priv->base); } iiodev->dev.parent = &pdev->dev; iiodev->name = dev_name(&pdev->dev); iiodev->modes = INDIO_DIRECT_MODE; iiodev->info = &ep93xx_adc_info; iiodev->num_channels = ARRAY_SIZE(ep93xx_adc_channels); iiodev->channels = ep93xx_adc_channels; priv->lastch = -1; mutex_init(&priv->lock); platform_set_drvdata(pdev, iiodev); priv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, "Cannot obtain clock\n"); return PTR_ERR(priv->clk); } pclk = clk_get_parent(priv->clk); if (!pclk) { dev_warn(&pdev->dev, "Cannot obtain parent clock\n"); } else { /* * This is actually a place for improvement: * EP93xx ADC supports two clock divisors -- 4 and 16, * resulting in conversion rates 3750 and 925 samples per second * with 500us or 2ms settling time respectively. * One might find this interesting enough to be configurable. */ ret = clk_set_rate(priv->clk, clk_get_rate(pclk) / 16); if (ret) dev_warn(&pdev->dev, "Cannot set clock rate\n"); /* * We can tolerate rate setting failure because the module should * work in any case. */ } ret = clk_enable(priv->clk); if (ret) { dev_err(&pdev->dev, "Cannot enable clock\n"); return ret; } ret = iio_device_register(iiodev); if (ret) clk_disable(priv->clk); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Sverdlin367100.00%1100.00%
Total367100.00%1100.00%


static int ep93xx_adc_remove(struct platform_device *pdev) { struct iio_dev *iiodev = platform_get_drvdata(pdev); struct ep93xx_adc_priv *priv = iio_priv(iiodev); iio_device_unregister(iiodev); clk_disable(priv->clk); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Sverdlin46100.00%1100.00%
Total46100.00%1100.00%

static struct platform_driver ep93xx_adc_driver = { .driver = { .name = "ep93xx-adc", }, .probe = ep93xx_adc_probe, .remove = ep93xx_adc_remove, }; module_platform_driver(ep93xx_adc_driver); MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@gmail.com>"); MODULE_DESCRIPTION("Cirrus Logic EP93XX ADC driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:ep93xx-adc");

Overall Contributors

PersonTokensPropCommitsCommitProp
Alexander Sverdlin959100.00%1100.00%
Total959100.00%1100.00%
Directory: drivers/iio/adc
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.