cregit-Linux how code gets into the kernel

Release 4.11 drivers/iio/adc/rcar-gyroadc.c

Directory: drivers/iio/adc
/*
 * Renesas R-Car GyroADC driver
 *
 * Copyright 2016 Marek Vasut <marek.vasut@gmail.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.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/regulator/consumer.h>
#include <linux/of_platform.h>
#include <linux/err.h>
#include <linux/pm_runtime.h>

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger.h>


#define DRIVER_NAME				"rcar-gyroadc"

/* GyroADC registers. */

#define RCAR_GYROADC_MODE_SELECT		0x00

#define RCAR_GYROADC_MODE_SELECT_1_MB88101A	0x0

#define RCAR_GYROADC_MODE_SELECT_2_ADCS7476	0x1

#define RCAR_GYROADC_MODE_SELECT_3_MAX1162	0x3


#define RCAR_GYROADC_START_STOP			0x04

#define RCAR_GYROADC_START_STOP_START		BIT(0)


#define RCAR_GYROADC_CLOCK_LENGTH		0x08

#define RCAR_GYROADC_1_25MS_LENGTH		0x0c


#define RCAR_GYROADC_REALTIME_DATA(ch)		(0x10 + ((ch) * 4))

#define RCAR_GYROADC_100MS_ADDED_DATA(ch)	(0x30 + ((ch) * 4))

#define RCAR_GYROADC_10MS_AVG_DATA(ch)		(0x50 + ((ch) * 4))


#define RCAR_GYROADC_FIFO_STATUS		0x70

#define RCAR_GYROADC_FIFO_STATUS_EMPTY(ch)	BIT(0 + (4 * (ch)))

#define RCAR_GYROADC_FIFO_STATUS_FULL(ch)	BIT(1 + (4 * (ch)))

#define RCAR_GYROADC_FIFO_STATUS_ERROR(ch)	BIT(2 + (4 * (ch)))


#define RCAR_GYROADC_INTR			0x74

#define RCAR_GYROADC_INTR_INT			BIT(0)


#define RCAR_GYROADC_INTENR			0x78

#define RCAR_GYROADC_INTENR_INTEN		BIT(0)


#define RCAR_GYROADC_SAMPLE_RATE		800	
/* Hz */


#define RCAR_GYROADC_RUNTIME_PM_DELAY_MS	2000


enum rcar_gyroadc_model {
	
RCAR_GYROADC_MODEL_DEFAULT,
	
RCAR_GYROADC_MODEL_R8A7792,
};


struct rcar_gyroadc {
	
struct device			*dev;
	
void __iomem			*regs;
	
struct clk			*iclk;
	
struct regulator		*vref[8];
	
unsigned int			num_channels;
	
enum rcar_gyroadc_model		model;
	
unsigned int			mode;
	
unsigned int			sample_width;
};


static void rcar_gyroadc_hw_init(struct rcar_gyroadc *priv) { const unsigned long clk_mhz = clk_get_rate(priv->iclk) / 1000000; const unsigned long clk_mul = (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) ? 10 : 5; unsigned long clk_len = clk_mhz * clk_mul; /* * According to the R-Car Gen2 datasheet Rev. 1.01, Sept 08 2014, * page 77-7, clock length must be even number. If it's odd number, * add one. */ if (clk_len & 1) clk_len++; /* Stop the GyroADC. */ writel(0, priv->regs + RCAR_GYROADC_START_STOP); /* Disable IRQ on V2H. */ if (priv->model == RCAR_GYROADC_MODEL_R8A7792) writel(0, priv->regs + RCAR_GYROADC_INTENR); /* Set mode and timing. */ writel(priv->mode, priv->regs + RCAR_GYROADC_MODE_SELECT); writel(clk_len, priv->regs + RCAR_GYROADC_CLOCK_LENGTH); writel(clk_mhz * 1250, priv->regs + RCAR_GYROADC_1_25MS_LENGTH); }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut130100.00%1100.00%
Total130100.00%1100.00%


static void rcar_gyroadc_hw_start(struct rcar_gyroadc *priv) { /* Start sampling. */ writel(RCAR_GYROADC_START_STOP_START, priv->regs + RCAR_GYROADC_START_STOP); /* * Wait for the first conversion to complete. This is longer than * the 1.25 mS in the datasheet because 1.25 mS is not enough for * the hardware to deliver the first sample and the hardware does * then return zeroes instead of valid data. */ mdelay(3); }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut29100.00%1100.00%
Total29100.00%1100.00%


static void rcar_gyroadc_hw_stop(struct rcar_gyroadc *priv) { /* Stop the GyroADC. */ writel(0, priv->regs + RCAR_GYROADC_START_STOP); }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut23100.00%1100.00%
Total23100.00%1100.00%

#define RCAR_GYROADC_CHAN(_idx) { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .channel = (_idx), \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ BIT(IIO_CHAN_INFO_SCALE), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ } static const struct iio_chan_spec rcar_gyroadc_iio_channels_1[] = { RCAR_GYROADC_CHAN(0), RCAR_GYROADC_CHAN(1), RCAR_GYROADC_CHAN(2), RCAR_GYROADC_CHAN(3), }; static const struct iio_chan_spec rcar_gyroadc_iio_channels_2[] = { RCAR_GYROADC_CHAN(0), RCAR_GYROADC_CHAN(1), RCAR_GYROADC_CHAN(2), RCAR_GYROADC_CHAN(3), RCAR_GYROADC_CHAN(4), RCAR_GYROADC_CHAN(5), RCAR_GYROADC_CHAN(6), RCAR_GYROADC_CHAN(7), }; static const struct iio_chan_spec rcar_gyroadc_iio_channels_3[] = { RCAR_GYROADC_CHAN(0), RCAR_GYROADC_CHAN(1), RCAR_GYROADC_CHAN(2), RCAR_GYROADC_CHAN(3), RCAR_GYROADC_CHAN(4), RCAR_GYROADC_CHAN(5), RCAR_GYROADC_CHAN(6), RCAR_GYROADC_CHAN(7), };
static int rcar_gyroadc_set_power(struct rcar_gyroadc *priv, bool on) { struct device *dev = priv->dev; int ret; if (on) { ret = pm_runtime_get_sync(dev); if (ret < 0) pm_runtime_put_noidle(dev); } else { pm_runtime_mark_last_busy(dev); ret = pm_runtime_put_autosuspend(dev); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut68100.00%1100.00%
Total68100.00%1100.00%


static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { struct rcar_gyroadc *priv = iio_priv(indio_dev); struct regulator *consumer; unsigned int datareg = RCAR_GYROADC_REALTIME_DATA(chan->channel); unsigned int vref; int ret; /* * MB88101 is special in that it has only single regulator for * all four channels. */ if (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) consumer = priv->vref[0]; else consumer = priv->vref[chan->channel]; switch (mask) { case IIO_CHAN_INFO_RAW: if (chan->type != IIO_VOLTAGE) return -EINVAL; /* Channel not connected. */ if (!consumer) return -EINVAL; ret = iio_device_claim_direct_mode(indio_dev); if (ret) return ret; ret = rcar_gyroadc_set_power(priv, true); if (ret < 0) { iio_device_release_direct_mode(indio_dev); return ret; } *val = readl(priv->regs + datareg); *val &= BIT(priv->sample_width) - 1; ret = rcar_gyroadc_set_power(priv, false); iio_device_release_direct_mode(indio_dev); if (ret < 0) return ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: /* Channel not connected. */ if (!consumer) return -EINVAL; vref = regulator_get_voltage(consumer); *val = vref / 1000; *val2 = 1 << priv->sample_width; return IIO_VAL_FRACTIONAL; case IIO_CHAN_INFO_SAMP_FREQ: *val = RCAR_GYROADC_SAMPLE_RATE; return IIO_VAL_INT; default: return -EINVAL; } }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut266100.00%1100.00%
Total266100.00%1100.00%


static int rcar_gyroadc_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { struct rcar_gyroadc *priv = iio_priv(indio_dev); unsigned int maxreg = RCAR_GYROADC_FIFO_STATUS; if (readval == NULL) return -EINVAL; if (reg % 4) return -EINVAL; /* Handle the V2H case with extra interrupt block. */ if (priv->model == RCAR_GYROADC_MODEL_R8A7792) maxreg = RCAR_GYROADC_INTENR; if (reg > maxreg) return -EINVAL; *readval = readl(priv->regs + reg); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut98100.00%1100.00%
Total98100.00%1100.00%

static const struct iio_info rcar_gyroadc_iio_info = { .driver_module = THIS_MODULE, .read_raw = rcar_gyroadc_read_raw, .debugfs_reg_access = rcar_gyroadc_reg_access, }; static const struct of_device_id rcar_gyroadc_match[] = { { /* R-Car compatible GyroADC */ .compatible = "renesas,rcar-gyroadc", .data = (void *)RCAR_GYROADC_MODEL_DEFAULT, }, { /* R-Car V2H specialty with interrupt registers. */ .compatible = "renesas,r8a7792-gyroadc", .data = (void *)RCAR_GYROADC_MODEL_R8A7792, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rcar_gyroadc_match); static const struct of_device_id rcar_gyroadc_child_match[] = { /* Mode 1 ADCs */ { .compatible = "fujitsu,mb88101a", .data = (void *)RCAR_GYROADC_MODE_SELECT_1_MB88101A, }, /* Mode 2 ADCs */ { .compatible = "ti,adcs7476", .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, }, { .compatible = "ti,adc121", .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, }, { .compatible = "adi,ad7476", .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, }, /* Mode 3 ADCs */ { .compatible = "maxim,max1162", .data = (void *)RCAR_GYROADC_MODE_SELECT_3_MAX1162, }, { .compatible = "maxim,max11100", .data = (void *)RCAR_GYROADC_MODE_SELECT_3_MAX1162, }, { /* sentinel */ } };
static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) { const struct of_device_id *of_id; const struct iio_chan_spec *channels; struct rcar_gyroadc *priv = iio_priv(indio_dev); struct device *dev = priv->dev; struct device_node *np = dev->of_node; struct device_node *child; struct regulator *vref; unsigned int reg; unsigned int adcmode = -1, childmode; unsigned int sample_width; unsigned int num_channels; int ret, first = 1; for_each_child_of_node(np, child) { of_id = of_match_node(rcar_gyroadc_child_match, child); if (!of_id) { dev_err(dev, "Ignoring unsupported ADC \"%s\".", child->name); continue; } childmode = (unsigned int)of_id->data; switch (childmode) { case RCAR_GYROADC_MODE_SELECT_1_MB88101A: sample_width = 12; channels = rcar_gyroadc_iio_channels_1; num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_1); break; case RCAR_GYROADC_MODE_SELECT_2_ADCS7476: sample_width = 15; channels = rcar_gyroadc_iio_channels_2; num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_2); break; case RCAR_GYROADC_MODE_SELECT_3_MAX1162: sample_width = 16; channels = rcar_gyroadc_iio_channels_3; num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_3); break; default: return -EINVAL; } /* * MB88101 is special in that it's only a single chip taking * up all the CHS lines. Thus, the DT binding is also special * and has no reg property. If we run into such ADC, handle * it here. */ if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) { reg = 0; } else { ret = of_property_read_u32(child, "reg", &reg); if (ret) { dev_err(dev, "Failed to get child reg property of ADC \"%s\".\n", child->name); return ret; } /* Channel number is too high. */ if (reg >= num_channels) { dev_err(dev, "Only %i channels supported with %s, but reg = <%i>.\n", num_channels, child->name, reg); return ret; } } /* Child node selected different mode than the rest. */ if (!first && (adcmode != childmode)) { dev_err(dev, "Channel %i uses different ADC mode than the rest.\n", reg); return ret; } /* Channel is valid, grab the regulator. */ dev->of_node = child; vref = devm_regulator_get(dev, "vref"); dev->of_node = np; if (IS_ERR(vref)) { dev_dbg(dev, "Channel %i 'vref' supply not connected.\n", reg); return PTR_ERR(vref); } priv->vref[reg] = vref; if (!first) continue; /* First child node which passed sanity tests. */ adcmode = childmode; first = 0; priv->num_channels = num_channels; priv->mode = childmode; priv->sample_width = sample_width; indio_dev->channels = channels; indio_dev->num_channels = num_channels; /* * MB88101 is special and we only have one such device * attached to the GyroADC at a time, so if we found it, * we can stop parsing here. */ if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) break; } if (first) { dev_err(dev, "No valid ADC channels found, aborting.\n"); return -EINVAL; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut42498.15%150.00%
Arnd Bergmann81.85%150.00%
Total432100.00%2100.00%


static void rcar_gyroadc_deinit_supplies(struct iio_dev *indio_dev) { struct rcar_gyroadc *priv = iio_priv(indio_dev); unsigned int i; for (i = 0; i < priv->num_channels; i++) { if (!priv->vref[i]) continue; regulator_disable(priv->vref[i]); } }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut63100.00%1100.00%
Total63100.00%1100.00%


static int rcar_gyroadc_init_supplies(struct iio_dev *indio_dev) { struct rcar_gyroadc *priv = iio_priv(indio_dev); struct device *dev = priv->dev; unsigned int i; int ret; for (i = 0; i < priv->num_channels; i++) { if (!priv->vref[i]) continue; ret = regulator_enable(priv->vref[i]); if (ret) { dev_err(dev, "Failed to enable regulator %i (ret=%i)\n", i, ret); goto err; } } return 0; err: rcar_gyroadc_deinit_supplies(indio_dev); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut110100.00%1100.00%
Total110100.00%1100.00%


static int rcar_gyroadc_probe(struct platform_device *pdev) { const struct of_device_id *of_id = of_match_device(rcar_gyroadc_match, &pdev->dev); struct device *dev = &pdev->dev; struct rcar_gyroadc *priv; struct iio_dev *indio_dev; struct resource *mem; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); if (!indio_dev) { dev_err(dev, "Failed to allocate IIO device.\n"); return -ENOMEM; } priv = iio_priv(indio_dev); priv->dev = dev; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->regs = devm_ioremap_resource(dev, mem); if (IS_ERR(priv->regs)) return PTR_ERR(priv->regs); priv->iclk = devm_clk_get(dev, "if"); if (IS_ERR(priv->iclk)) { ret = PTR_ERR(priv->iclk); if (ret != -EPROBE_DEFER) dev_err(dev, "Failed to get IF clock (ret=%i)\n", ret); return ret; } ret = rcar_gyroadc_parse_subdevs(indio_dev); if (ret) return ret; ret = rcar_gyroadc_init_supplies(indio_dev); if (ret) return ret; priv->model = (enum rcar_gyroadc_model)of_id->data; platform_set_drvdata(pdev, indio_dev); indio_dev->name = DRIVER_NAME; indio_dev->dev.parent = dev; indio_dev->dev.of_node = pdev->dev.of_node; indio_dev->info = &rcar_gyroadc_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; ret = clk_prepare_enable(priv->iclk); if (ret) { dev_err(dev, "Could not prepare or enable the IF clock.\n"); goto err_clk_if_enable; } pm_runtime_set_autosuspend_delay(dev, RCAR_GYROADC_RUNTIME_PM_DELAY_MS); pm_runtime_use_autosuspend(dev); pm_runtime_enable(dev); pm_runtime_get_sync(dev); rcar_gyroadc_hw_init(priv); rcar_gyroadc_hw_start(priv); ret = iio_device_register(indio_dev); if (ret) { dev_err(dev, "Couldn't register IIO device.\n"); goto err_iio_device_register; } pm_runtime_put_sync(dev); return 0; err_iio_device_register: rcar_gyroadc_hw_stop(priv); pm_runtime_put_sync(dev); pm_runtime_disable(dev); pm_runtime_set_suspended(dev); clk_disable_unprepare(priv->iclk); err_clk_if_enable: rcar_gyroadc_deinit_supplies(indio_dev); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut401100.00%1100.00%
Total401100.00%1100.00%


static int rcar_gyroadc_remove(struct platform_device *pdev) { struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct rcar_gyroadc *priv = iio_priv(indio_dev); struct device *dev = priv->dev; iio_device_unregister(indio_dev); pm_runtime_get_sync(dev); rcar_gyroadc_hw_stop(priv); pm_runtime_put_sync(dev); pm_runtime_disable(dev); pm_runtime_set_suspended(dev); clk_disable_unprepare(priv->iclk); rcar_gyroadc_deinit_supplies(indio_dev); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut85100.00%1100.00%
Total85100.00%1100.00%

#if defined(CONFIG_PM)
static int rcar_gyroadc_suspend(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct rcar_gyroadc *priv = iio_priv(indio_dev); rcar_gyroadc_hw_stop(priv); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut39100.00%1100.00%
Total39100.00%1100.00%


static int rcar_gyroadc_resume(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct rcar_gyroadc *priv = iio_priv(indio_dev); rcar_gyroadc_hw_start(priv); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut39100.00%1100.00%
Total39100.00%1100.00%

#endif static const struct dev_pm_ops rcar_gyroadc_pm_ops = { SET_RUNTIME_PM_OPS(rcar_gyroadc_suspend, rcar_gyroadc_resume, NULL) }; static struct platform_driver rcar_gyroadc_driver = { .probe = rcar_gyroadc_probe, .remove = rcar_gyroadc_remove, .driver = { .name = DRIVER_NAME, .of_match_table = rcar_gyroadc_match, .pm = &rcar_gyroadc_pm_ops, }, }; module_platform_driver(rcar_gyroadc_driver); MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); MODULE_DESCRIPTION("Renesas R-Car GyroADC driver"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Marek Vašut239099.67%150.00%
Arnd Bergmann80.33%150.00%
Total2398100.00%2100.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.