391 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (C) 2014-2015 Pengutronix, Markus Pargmann <mpa@pengutronix.de>4 *5 * This is the driver for the imx25 GCQ (Generic Conversion Queue)6 * connected to the imx25 ADC.7 */8 9#include <dt-bindings/iio/adc/fsl-imx25-gcq.h>10#include <linux/clk.h>11#include <linux/iio/iio.h>12#include <linux/interrupt.h>13#include <linux/mfd/imx25-tsadc.h>14#include <linux/module.h>15#include <linux/mod_devicetable.h>16#include <linux/platform_device.h>17#include <linux/property.h>18#include <linux/regmap.h>19#include <linux/regulator/consumer.h>20 21#define MX25_GCQ_TIMEOUT (msecs_to_jiffies(2000))22 23static const char * const driver_name = "mx25-gcq";24 25enum mx25_gcq_cfgs {26 MX25_CFG_XP = 0,27 MX25_CFG_YP,28 MX25_CFG_XN,29 MX25_CFG_YN,30 MX25_CFG_WIPER,31 MX25_CFG_INAUX0,32 MX25_CFG_INAUX1,33 MX25_CFG_INAUX2,34 MX25_NUM_CFGS,35};36 37struct mx25_gcq_priv {38 struct regmap *regs;39 struct completion completed;40 struct clk *clk;41 int irq;42 struct regulator *vref[4];43 u32 channel_vref_mv[MX25_NUM_CFGS];44 /*45 * Lock to protect the device state during a potential concurrent46 * read access from userspace. Reading a raw value requires a sequence47 * of register writes, then a wait for a completion callback,48 * and finally a register read, during which userspace could issue49 * another read request. This lock protects a read access from50 * ocurring before another one has finished.51 */52 struct mutex lock;53};54 55#define MX25_CQG_CHAN(chan, id) {\56 .type = IIO_VOLTAGE,\57 .indexed = 1,\58 .channel = chan,\59 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \60 BIT(IIO_CHAN_INFO_SCALE),\61 .datasheet_name = id,\62}63 64static const struct iio_chan_spec mx25_gcq_channels[MX25_NUM_CFGS] = {65 MX25_CQG_CHAN(MX25_CFG_XP, "xp"),66 MX25_CQG_CHAN(MX25_CFG_YP, "yp"),67 MX25_CQG_CHAN(MX25_CFG_XN, "xn"),68 MX25_CQG_CHAN(MX25_CFG_YN, "yn"),69 MX25_CQG_CHAN(MX25_CFG_WIPER, "wiper"),70 MX25_CQG_CHAN(MX25_CFG_INAUX0, "inaux0"),71 MX25_CQG_CHAN(MX25_CFG_INAUX1, "inaux1"),72 MX25_CQG_CHAN(MX25_CFG_INAUX2, "inaux2"),73};74 75static const char * const mx25_gcq_refp_names[] = {76 [MX25_ADC_REFP_YP] = "yp",77 [MX25_ADC_REFP_XP] = "xp",78 [MX25_ADC_REFP_INT] = "int",79 [MX25_ADC_REFP_EXT] = "ext",80};81 82static irqreturn_t mx25_gcq_irq(int irq, void *data)83{84 struct mx25_gcq_priv *priv = data;85 u32 stats;86 87 regmap_read(priv->regs, MX25_ADCQ_SR, &stats);88 89 if (stats & MX25_ADCQ_SR_EOQ) {90 regmap_set_bits(priv->regs, MX25_ADCQ_MR,91 MX25_ADCQ_MR_EOQ_IRQ);92 complete(&priv->completed);93 }94 95 /* Disable conversion queue run */96 regmap_clear_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS);97 98 /* Acknowledge all possible irqs */99 regmap_write(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR |100 MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR |101 MX25_ADCQ_SR_EOQ | MX25_ADCQ_SR_PD);102 103 return IRQ_HANDLED;104}105 106static int mx25_gcq_get_raw_value(struct device *dev,107 struct iio_chan_spec const *chan,108 struct mx25_gcq_priv *priv,109 int *val)110{111 long time_left;112 u32 data;113 114 /* Setup the configuration we want to use */115 regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0,116 MX25_ADCQ_ITEM(0, chan->channel));117 118 regmap_clear_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ);119 120 /* Trigger queue for one run */121 regmap_set_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS);122 123 time_left = wait_for_completion_interruptible_timeout(124 &priv->completed, MX25_GCQ_TIMEOUT);125 if (time_left < 0) {126 dev_err(dev, "ADC wait for measurement failed\n");127 return time_left;128 } else if (time_left == 0) {129 dev_err(dev, "ADC timed out\n");130 return -ETIMEDOUT;131 }132 133 regmap_read(priv->regs, MX25_ADCQ_FIFO, &data);134 135 *val = MX25_ADCQ_FIFO_DATA(data);136 137 return IIO_VAL_INT;138}139 140static int mx25_gcq_read_raw(struct iio_dev *indio_dev,141 struct iio_chan_spec const *chan, int *val,142 int *val2, long mask)143{144 struct mx25_gcq_priv *priv = iio_priv(indio_dev);145 int ret;146 147 switch (mask) {148 case IIO_CHAN_INFO_RAW:149 mutex_lock(&priv->lock);150 ret = mx25_gcq_get_raw_value(&indio_dev->dev, chan, priv, val);151 mutex_unlock(&priv->lock);152 return ret;153 154 case IIO_CHAN_INFO_SCALE:155 *val = priv->channel_vref_mv[chan->channel];156 *val2 = 12;157 return IIO_VAL_FRACTIONAL_LOG2;158 159 default:160 return -EINVAL;161 }162}163 164static const struct iio_info mx25_gcq_iio_info = {165 .read_raw = mx25_gcq_read_raw,166};167 168static const struct regmap_config mx25_gcq_regconfig = {169 .max_register = 0x5c,170 .reg_bits = 32,171 .val_bits = 32,172 .reg_stride = 4,173};174 175static int mx25_gcq_ext_regulator_setup(struct device *dev,176 struct mx25_gcq_priv *priv, u32 refp)177{178 char reg_name[12];179 int ret;180 181 if (priv->vref[refp])182 return 0;183 184 ret = snprintf(reg_name, sizeof(reg_name), "vref-%s",185 mx25_gcq_refp_names[refp]);186 if (ret < 0)187 return ret;188 189 priv->vref[refp] = devm_regulator_get_optional(dev, reg_name);190 if (IS_ERR(priv->vref[refp]))191 return dev_err_probe(dev, PTR_ERR(priv->vref[refp]),192 "Error, trying to use external voltage reference without a %s regulator.",193 reg_name);194 195 return 0;196}197 198static int mx25_gcq_setup_cfgs(struct platform_device *pdev,199 struct mx25_gcq_priv *priv)200{201 struct device *dev = &pdev->dev;202 int ret, i;203 204 /*205 * Setup all configurations registers with a default conversion206 * configuration for each input207 */208 for (i = 0; i < MX25_NUM_CFGS; ++i)209 regmap_write(priv->regs, MX25_ADCQ_CFG(i),210 MX25_ADCQ_CFG_YPLL_OFF |211 MX25_ADCQ_CFG_XNUR_OFF |212 MX25_ADCQ_CFG_XPUL_OFF |213 MX25_ADCQ_CFG_REFP_INT |214 MX25_ADCQ_CFG_IN(i) |215 MX25_ADCQ_CFG_REFN_NGND2);216 217 device_for_each_child_node_scoped(dev, child) {218 u32 reg;219 u32 refp = MX25_ADCQ_CFG_REFP_INT;220 u32 refn = MX25_ADCQ_CFG_REFN_NGND2;221 222 ret = fwnode_property_read_u32(child, "reg", ®);223 if (ret)224 return dev_err_probe(dev, ret,225 "Failed to get reg property\n");226 227 if (reg >= MX25_NUM_CFGS)228 return dev_err_probe(dev, -EINVAL,229 "reg value is greater than the number of available configuration registers\n");230 231 fwnode_property_read_u32(child, "fsl,adc-refp", &refp);232 fwnode_property_read_u32(child, "fsl,adc-refn", &refn);233 234 switch (refp) {235 case MX25_ADC_REFP_EXT:236 case MX25_ADC_REFP_XP:237 case MX25_ADC_REFP_YP:238 ret = mx25_gcq_ext_regulator_setup(&pdev->dev, priv, refp);239 if (ret)240 return ret;241 priv->channel_vref_mv[reg] =242 regulator_get_voltage(priv->vref[refp]);243 /* Conversion from uV to mV */244 priv->channel_vref_mv[reg] /= 1000;245 break;246 case MX25_ADC_REFP_INT:247 priv->channel_vref_mv[reg] = 2500;248 break;249 default:250 return dev_err_probe(dev, -EINVAL,251 "Invalid positive reference %d\n", refp);252 }253 254 /*255 * Shift the read values to the correct positions within the256 * register.257 */258 refp = MX25_ADCQ_CFG_REFP(refp);259 refn = MX25_ADCQ_CFG_REFN(refn);260 261 if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp)262 return dev_err_probe(dev, -EINVAL,263 "Invalid fsl,adc-refp property value\n");264 265 if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn)266 return dev_err_probe(dev, -EINVAL,267 "Invalid fsl,adc-refn property value\n");268 269 regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg),270 MX25_ADCQ_CFG_REFP_MASK |271 MX25_ADCQ_CFG_REFN_MASK,272 refp | refn);273 }274 regmap_set_bits(priv->regs, MX25_ADCQ_CR,275 MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST);276 277 regmap_write(priv->regs, MX25_ADCQ_CR,278 MX25_ADCQ_CR_PDMSK | MX25_ADCQ_CR_QSM_FQS);279 280 return 0;281}282 283static void mx25_gcq_reg_disable(void *reg)284{285 regulator_disable(reg);286}287 288/* Custom handling needed as this driver doesn't own the clock */289static void mx25_gcq_clk_disable(void *clk)290{291 clk_disable_unprepare(clk);292}293 294static int mx25_gcq_probe(struct platform_device *pdev)295{296 struct iio_dev *indio_dev;297 struct mx25_gcq_priv *priv;298 struct mx25_tsadc *tsadc = dev_get_drvdata(pdev->dev.parent);299 struct device *dev = &pdev->dev;300 void __iomem *mem;301 int ret;302 int i;303 304 indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));305 if (!indio_dev)306 return -ENOMEM;307 308 priv = iio_priv(indio_dev);309 310 mem = devm_platform_ioremap_resource(pdev, 0);311 if (IS_ERR(mem))312 return PTR_ERR(mem);313 314 priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig);315 if (IS_ERR(priv->regs))316 return dev_err_probe(dev, PTR_ERR(priv->regs),317 "Failed to initialize regmap\n");318 319 mutex_init(&priv->lock);320 321 init_completion(&priv->completed);322 323 ret = mx25_gcq_setup_cfgs(pdev, priv);324 if (ret)325 return ret;326 327 for (i = 0; i != 4; ++i) {328 if (!priv->vref[i])329 continue;330 331 ret = regulator_enable(priv->vref[i]);332 if (ret)333 return ret;334 335 ret = devm_add_action_or_reset(dev, mx25_gcq_reg_disable,336 priv->vref[i]);337 if (ret)338 return ret;339 }340 341 priv->clk = tsadc->clk;342 ret = clk_prepare_enable(priv->clk);343 if (ret)344 return dev_err_probe(dev, ret, "Failed to enable clock\n");345 346 ret = devm_add_action_or_reset(dev, mx25_gcq_clk_disable,347 priv->clk);348 if (ret)349 return ret;350 351 ret = platform_get_irq(pdev, 0);352 if (ret < 0)353 return ret;354 355 priv->irq = ret;356 ret = devm_request_irq(dev, priv->irq, mx25_gcq_irq, 0, pdev->name,357 priv);358 if (ret)359 return dev_err_probe(dev, ret, "Failed requesting IRQ\n");360 361 indio_dev->channels = mx25_gcq_channels;362 indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels);363 indio_dev->info = &mx25_gcq_iio_info;364 indio_dev->name = driver_name;365 366 ret = devm_iio_device_register(dev, indio_dev);367 if (ret)368 return dev_err_probe(dev, ret, "Failed to register iio device\n");369 370 return 0;371}372 373static const struct of_device_id mx25_gcq_ids[] = {374 { .compatible = "fsl,imx25-gcq", },375 { /* Sentinel */ }376};377MODULE_DEVICE_TABLE(of, mx25_gcq_ids);378 379static struct platform_driver mx25_gcq_driver = {380 .driver = {381 .name = "mx25-gcq",382 .of_match_table = mx25_gcq_ids,383 },384 .probe = mx25_gcq_probe,385};386module_platform_driver(mx25_gcq_driver);387 388MODULE_DESCRIPTION("ADC driver for Freescale mx25");389MODULE_AUTHOR("Markus Pargmann <mpa@pengutronix.de>");390MODULE_LICENSE("GPL v2");391