From 77fc5151f9c0e6068f1567b73d33e75a0c35333d Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 17 Feb 2024 16:42:35 +0000 Subject: [PATCH 001/108] device property: Move fwnode_handle_put() into property.h By having this function as static inline in the header, the compiler is able to see if can optmize the call out if (IS_ERR_OR_NULL(fwnode)) This will allow a simpler DEFINE_FREE() call in the following patch. Suggested-by: Sakari Ailus Reviewed-by: Sakari Ailus Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20240217164249.921878-2-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/base/property.c | 14 -------------- include/linux/property.h | 14 +++++++++++++- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/drivers/base/property.c b/drivers/base/property.c index 7324a704a9a1..6a3a434d0d6f 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -868,20 +868,6 @@ struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *fwnode) } EXPORT_SYMBOL_GPL(fwnode_handle_get); -/** - * fwnode_handle_put - Drop reference to a device node - * @fwnode: Pointer to the device node to drop the reference to. - * - * This has to be used when terminating device_for_each_child_node() iteration - * with break or return to prevent stale device node references from being left - * behind. - */ -void fwnode_handle_put(struct fwnode_handle *fwnode) -{ - fwnode_call_void_op(fwnode, put); -} -EXPORT_SYMBOL_GPL(fwnode_handle_put); - /** * fwnode_device_is_available - check if a device is available for use * @fwnode: Pointer to the fwnode of the device. diff --git a/include/linux/property.h b/include/linux/property.h index 3a1045eb786c..93d992a92f59 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -180,7 +180,19 @@ struct fwnode_handle *device_get_named_child_node(const struct device *dev, const char *childname); struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *fwnode); -void fwnode_handle_put(struct fwnode_handle *fwnode); + +/** + * fwnode_handle_put - Drop reference to a device node + * @fwnode: Pointer to the device node to drop the reference to. + * + * This has to be used when terminating device_for_each_child_node() iteration + * with break or return to prevent stale device node references from being left + * behind. + */ +static inline void fwnode_handle_put(struct fwnode_handle *fwnode) +{ + fwnode_call_void_op(fwnode, put); +} int fwnode_irq_get(const struct fwnode_handle *fwnode, unsigned int index); int fwnode_irq_get_byname(const struct fwnode_handle *fwnode, const char *name); From 59ed5e2d505bf5f9b4af64d0021cd0c96aec1f7c Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 17 Feb 2024 16:42:36 +0000 Subject: [PATCH 002/108] device property: Add cleanup.h based fwnode_handle_put() scope based cleanup. Useful where the fwnode_handle was obtained from a call such as fwnode_find_reference() as it will safely do nothing if IS_ERR() is true and will automatically release the reference on the variable leaving scope. Reviewed-by: Andy Shevchenko Acked-by: Greg Kroah-Hartman Reviewed-by: Sakari Ailus Link: https://lore.kernel.org/r/20240217164249.921878-3-jic23@kernel.org Signed-off-by: Jonathan Cameron --- include/linux/property.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/property.h b/include/linux/property.h index 93d992a92f59..1322f0cce77a 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -194,6 +195,8 @@ static inline void fwnode_handle_put(struct fwnode_handle *fwnode) fwnode_call_void_op(fwnode, put); } +DEFINE_FREE(fwnode_handle, struct fwnode_handle *, fwnode_handle_put(_T)) + int fwnode_irq_get(const struct fwnode_handle *fwnode, unsigned int index); int fwnode_irq_get_byname(const struct fwnode_handle *fwnode, const char *name); From 365130fd47af6d4317aa16a407874b699ab8d8cb Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 17 Feb 2024 16:42:38 +0000 Subject: [PATCH 003/108] device property: Introduce device_for_each_child_node_scoped() Similar to recently propose for_each_child_of_node_scoped() this new version of the loop macro instantiates a new local struct fwnode_handle * that uses the __free(fwnode_handle) auto cleanup handling so that if a reference to a node is held on early exit from the loop the reference will be released. If the loop runs to completion, the child pointer will be NULL and no action will be taken. The reason this is useful is that it removes the need for fwnode_handle_put() on early loop exits. If there is a need to retain the reference, then return_ptr(child) or no_free_ptr(child) may be used to safely disable the auto cleanup. Acked-by: Greg Kroah-Hartman Reviewed-by: Sakari Ailus Link: https://lore.kernel.org/r/20240217164249.921878-5-jic23@kernel.org Signed-off-by: Jonathan Cameron --- include/linux/property.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/property.h b/include/linux/property.h index 1322f0cce77a..61fc20e5f81f 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -175,6 +175,11 @@ struct fwnode_handle *device_get_next_child_node(const struct device *dev, for (child = device_get_next_child_node(dev, NULL); child; \ child = device_get_next_child_node(dev, child)) +#define device_for_each_child_node_scoped(dev, child) \ + for (struct fwnode_handle *child __free(fwnode_handle) = \ + device_get_next_child_node(dev, NULL); \ + child; child = device_get_next_child_node(dev, child)) + struct fwnode_handle *fwnode_get_named_child_node(const struct fwnode_handle *fwnode, const char *childname); struct fwnode_handle *device_get_named_child_node(const struct device *dev, From 1693d2a7459183dcc8e7e89d849076f082f3d4c9 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 17 Feb 2024 16:42:39 +0000 Subject: [PATCH 004/108] iio: adc: max11410: Use device_for_each_child_node_scoped() Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240217164249.921878-6-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/max11410.c | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/drivers/iio/adc/max11410.c b/drivers/iio/adc/max11410.c index 6af829349b4e..45368850b220 100644 --- a/drivers/iio/adc/max11410.c +++ b/drivers/iio/adc/max11410.c @@ -696,7 +696,6 @@ static int max11410_parse_channels(struct max11410_state *st, struct device *dev = &st->spi_dev->dev; struct max11410_channel_config *cfg; struct iio_chan_spec *channels; - struct fwnode_handle *child; u32 reference, sig_path; const char *node_name; u32 inputs[2], scale; @@ -720,7 +719,7 @@ static int max11410_parse_channels(struct max11410_state *st, if (!st->channels) return -ENOMEM; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { node_name = fwnode_get_name(child); if (fwnode_property_present(child, "diff-channels")) { ret = fwnode_property_read_u32_array(child, @@ -735,47 +734,37 @@ static int max11410_parse_channels(struct max11410_state *st, inputs[1] = 0; chanspec.differential = 0; } - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } if (inputs[0] > MAX11410_CHANNEL_INDEX_MAX || - inputs[1] > MAX11410_CHANNEL_INDEX_MAX) { - fwnode_handle_put(child); + inputs[1] > MAX11410_CHANNEL_INDEX_MAX) return dev_err_probe(&indio_dev->dev, -EINVAL, "Invalid channel index for %s, should be less than %d\n", node_name, MAX11410_CHANNEL_INDEX_MAX + 1); - } cfg = &st->channels[chan_idx]; reference = MAX11410_REFSEL_AVDD_AGND; fwnode_property_read_u32(child, "adi,reference", &reference); - if (reference > MAX11410_REFSEL_MAX) { - fwnode_handle_put(child); + if (reference > MAX11410_REFSEL_MAX) return dev_err_probe(&indio_dev->dev, -EINVAL, "Invalid adi,reference value for %s, should be less than %d.\n", node_name, MAX11410_REFSEL_MAX + 1); - } if (!max11410_get_vrefp(st, reference) || - (!max11410_get_vrefn(st, reference) && reference <= 2)) { - fwnode_handle_put(child); + (!max11410_get_vrefn(st, reference) && reference <= 2)) return dev_err_probe(&indio_dev->dev, -EINVAL, "Invalid VREF configuration for %s, either specify corresponding VREF regulators or change adi,reference property.\n", node_name); - } sig_path = MAX11410_PGA_SIG_PATH_BUFFERED; fwnode_property_read_u32(child, "adi,input-mode", &sig_path); - if (sig_path > MAX11410_SIG_PATH_MAX) { - fwnode_handle_put(child); + if (sig_path > MAX11410_SIG_PATH_MAX) return dev_err_probe(&indio_dev->dev, -EINVAL, "Invalid adi,input-mode value for %s, should be less than %d.\n", node_name, MAX11410_SIG_PATH_MAX + 1); - } fwnode_property_read_u32(child, "settling-time-us", &cfg->settling_time_us); @@ -793,10 +782,8 @@ static int max11410_parse_channels(struct max11410_state *st, cfg->scale_avail = devm_kcalloc(dev, MAX11410_SCALE_AVAIL_SIZE * 2, sizeof(*cfg->scale_avail), GFP_KERNEL); - if (!cfg->scale_avail) { - fwnode_handle_put(child); + if (!cfg->scale_avail) return -ENOMEM; - } scale = max11410_get_scale(st, *cfg); for (i = 0; i < MAX11410_SCALE_AVAIL_SIZE; i++) { From 2fe97fccbd3f1da3f64818c123264db7cd2a257b Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 17 Feb 2024 16:42:46 +0000 Subject: [PATCH 005/108] iio: addac: ad74413r: Use device_for_each_child_node_scoped() Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. The use of fwnode_for_each_available_child_node() here is assumed to have been down to a false assumption that device_for_each_child_node() doesn't check avaialble - so this transition to the scoped device_for_each_child_node_scoped() is equivalent. Cc: Rasmus Villemoes Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240217164249.921878-13-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/addac/ad74413r.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/iio/addac/ad74413r.c b/drivers/iio/addac/ad74413r.c index 7af3e4b8fe3b..cd26a16dc0ff 100644 --- a/drivers/iio/addac/ad74413r.c +++ b/drivers/iio/addac/ad74413r.c @@ -1255,21 +1255,15 @@ static int ad74413r_parse_channel_config(struct iio_dev *indio_dev, static int ad74413r_parse_channel_configs(struct iio_dev *indio_dev) { struct ad74413r_state *st = iio_priv(indio_dev); - struct fwnode_handle *channel_node = NULL; int ret; - fwnode_for_each_available_child_node(dev_fwnode(st->dev), channel_node) { + device_for_each_child_node_scoped(st->dev, channel_node) { ret = ad74413r_parse_channel_config(indio_dev, channel_node); if (ret) - goto put_channel_node; + return ret; } return 0; - -put_channel_node: - fwnode_handle_put(channel_node); - - return ret; } static int ad74413r_setup_channels(struct iio_dev *indio_dev) From 8a85e72eaedc26acb5de768dc1bf53c7b4e3ada5 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 17 Feb 2024 16:42:49 +0000 Subject: [PATCH 006/108] iio: dac: ltc2688: Use device_for_each_child_node_scoped() Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. Reviewed-by: Andy Shevchenko Tested-by: Nuno Sa Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240217164249.921878-16-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ltc2688.c | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/drivers/iio/dac/ltc2688.c b/drivers/iio/dac/ltc2688.c index fc8eb53c65be..c4b1ba30f935 100644 --- a/drivers/iio/dac/ltc2688.c +++ b/drivers/iio/dac/ltc2688.c @@ -746,26 +746,21 @@ static int ltc2688_span_lookup(const struct ltc2688_state *st, int min, int max) static int ltc2688_channel_config(struct ltc2688_state *st) { struct device *dev = &st->spi->dev; - struct fwnode_handle *child; u32 reg, clk_input, val, tmp[2]; int ret, span; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct ltc2688_chan *chan; ret = fwnode_property_read_u32(child, "reg", ®); - if (ret) { - fwnode_handle_put(child); + if (ret) return dev_err_probe(dev, ret, "Failed to get reg property\n"); - } - if (reg >= LTC2688_DAC_CHANNELS) { - fwnode_handle_put(child); + if (reg >= LTC2688_DAC_CHANNELS) return dev_err_probe(dev, -EINVAL, "reg bigger than: %d\n", LTC2688_DAC_CHANNELS); - } val = 0; chan = &st->channels[reg]; @@ -786,12 +781,10 @@ static int ltc2688_channel_config(struct ltc2688_state *st) if (!ret) { span = ltc2688_span_lookup(st, (int)tmp[0] / 1000, tmp[1] / 1000); - if (span < 0) { - fwnode_handle_put(child); - return dev_err_probe(dev, -EINVAL, + if (span < 0) + return dev_err_probe(dev, span, "output range not valid:[%d %d]\n", tmp[0], tmp[1]); - } val |= FIELD_PREP(LTC2688_CH_SPAN_MSK, span); } @@ -800,17 +793,14 @@ static int ltc2688_channel_config(struct ltc2688_state *st) &clk_input); if (!ret) { if (clk_input >= LTC2688_CH_TGP_MAX) { - fwnode_handle_put(child); return dev_err_probe(dev, -EINVAL, "toggle-dither-input inv value(%d)\n", clk_input); } ret = ltc2688_tgp_clk_setup(st, chan, child, clk_input); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } /* * 0 means software toggle which is the default mode. @@ -844,11 +834,9 @@ static int ltc2688_channel_config(struct ltc2688_state *st) ret = regmap_write(st->regmap, LTC2688_CMD_CH_SETTING(reg), val); - if (ret) { - fwnode_handle_put(child); - return dev_err_probe(dev, -EINVAL, + if (ret) + return dev_err_probe(dev, ret, "failed to set chan settings\n"); - } } return 0; From 39d5790d0be1e5e82ded824f6b531bdcf5316390 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:24 +0000 Subject: [PATCH 007/108] iio: adc: fsl-imx25-gcq: Switch from of specific handing to fwnode based. Using the generic firmware data access functions from property.h provides a number of advantages: 1) Works with different firmware types. 2) Doesn't provide a 'bad' example for new IIO drivers. 3) Lets us use the new _scoped() loops with automatic reference count cleanup for fwnode_handle Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-2-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/fsl-imx25-gcq.c | 54 +++++++++++++-------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c index 68c813de0605..534e73a24eb4 100644 --- a/drivers/iio/adc/fsl-imx25-gcq.c +++ b/drivers/iio/adc/fsl-imx25-gcq.c @@ -12,8 +12,9 @@ #include #include #include -#include +#include #include +#include #include #include @@ -198,8 +199,6 @@ static int mx25_gcq_ext_regulator_setup(struct device *dev, static int mx25_gcq_setup_cfgs(struct platform_device *pdev, struct mx25_gcq_priv *priv) { - struct device_node *np = pdev->dev.of_node; - struct device_node *child; struct device *dev = &pdev->dev; int ret, i; @@ -216,37 +215,30 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev, MX25_ADCQ_CFG_IN(i) | MX25_ADCQ_CFG_REFN_NGND2); - for_each_child_of_node(np, child) { + device_for_each_child_node_scoped(dev, child) { u32 reg; u32 refp = MX25_ADCQ_CFG_REFP_INT; u32 refn = MX25_ADCQ_CFG_REFN_NGND2; - ret = of_property_read_u32(child, "reg", ®); - if (ret) { - dev_err(dev, "Failed to get reg property\n"); - of_node_put(child); - return ret; - } + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get reg property\n"); - if (reg >= MX25_NUM_CFGS) { - dev_err(dev, + if (reg >= MX25_NUM_CFGS) + return dev_err_probe(dev, -EINVAL, "reg value is greater than the number of available configuration registers\n"); - of_node_put(child); - return -EINVAL; - } - of_property_read_u32(child, "fsl,adc-refp", &refp); - of_property_read_u32(child, "fsl,adc-refn", &refn); + fwnode_property_read_u32(child, "fsl,adc-refp", &refp); + fwnode_property_read_u32(child, "fsl,adc-refn", &refn); switch (refp) { case MX25_ADC_REFP_EXT: case MX25_ADC_REFP_XP: case MX25_ADC_REFP_YP: ret = mx25_gcq_ext_regulator_setup(&pdev->dev, priv, refp); - if (ret) { - of_node_put(child); + if (ret) return ret; - } priv->channel_vref_mv[reg] = regulator_get_voltage(priv->vref[refp]); /* Conversion from uV to mV */ @@ -256,9 +248,8 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev, priv->channel_vref_mv[reg] = 2500; break; default: - dev_err(dev, "Invalid positive reference %d\n", refp); - of_node_put(child); - return -EINVAL; + return dev_err_probe(dev, -EINVAL, + "Invalid positive reference %d\n", refp); } /* @@ -268,16 +259,13 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev, refp = MX25_ADCQ_CFG_REFP(refp); refn = MX25_ADCQ_CFG_REFN(refn); - if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp) { - dev_err(dev, "Invalid fsl,adc-refp property value\n"); - of_node_put(child); - return -EINVAL; - } - if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn) { - dev_err(dev, "Invalid fsl,adc-refn property value\n"); - of_node_put(child); - return -EINVAL; - } + if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp) + return dev_err_probe(dev, -EINVAL, + "Invalid fsl,adc-refp property value\n"); + + if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn) + return dev_err_probe(dev, -EINVAL, + "Invalid fsl,adc-refn property value\n"); regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg), MX25_ADCQ_CFG_REFP_MASK | From f84aec5a6bb509b38c855ef9b40c7c458b87b4b0 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:25 +0000 Subject: [PATCH 008/108] iio: adc: fsl-imx25-gcq: Use devm_* and dev_err_probe() to simplify probe Custom callbacks are need for regulators (so there is a handle to read the voltage from) and the clk because it is retrieved from the parent rather than directly from firmware description. Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-3-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/fsl-imx25-gcq.c | 86 ++++++++++++++------------------- 1 file changed, 35 insertions(+), 51 deletions(-) diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c index 534e73a24eb4..394396b91630 100644 --- a/drivers/iio/adc/fsl-imx25-gcq.c +++ b/drivers/iio/adc/fsl-imx25-gcq.c @@ -282,6 +282,17 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev, return 0; } +static void mx25_gcq_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +/* Custom handling needed as this driver doesn't own the clock */ +static void mx25_gcq_clk_disable(void *clk) +{ + clk_disable_unprepare(clk); +} + static int mx25_gcq_probe(struct platform_device *pdev) { struct iio_dev *indio_dev; @@ -303,10 +314,9 @@ static int mx25_gcq_probe(struct platform_device *pdev) return PTR_ERR(mem); priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig); - if (IS_ERR(priv->regs)) { - dev_err(dev, "Failed to initialize regmap\n"); - return PTR_ERR(priv->regs); - } + if (IS_ERR(priv->regs)) + return dev_err_probe(dev, PTR_ERR(priv->regs), + "Failed to initialize regmap\n"); mutex_init(&priv->lock); @@ -322,69 +332,44 @@ static int mx25_gcq_probe(struct platform_device *pdev) ret = regulator_enable(priv->vref[i]); if (ret) - goto err_regulator_disable; + return ret; + + ret = devm_add_action_or_reset(dev, mx25_gcq_reg_disable, + priv->vref[i]); + if (ret) + return ret; } priv->clk = tsadc->clk; ret = clk_prepare_enable(priv->clk); - if (ret) { - dev_err(dev, "Failed to enable clock\n"); - goto err_vref_disable; - } + if (ret) + return dev_err_probe(dev, ret, "Failed to enable clock\n"); + + ret = devm_add_action_or_reset(dev, mx25_gcq_clk_disable, + priv->clk); + if (ret) + return ret; ret = platform_get_irq(pdev, 0); if (ret < 0) - goto err_clk_unprepare; + return ret; priv->irq = ret; - ret = request_irq(priv->irq, mx25_gcq_irq, 0, pdev->name, priv); - if (ret) { - dev_err(dev, "Failed requesting IRQ\n"); - goto err_clk_unprepare; - } + ret = devm_request_irq(dev, priv->irq, mx25_gcq_irq, 0, pdev->name, + priv); + if (ret) + return dev_err_probe(dev, ret, "Failed requesting IRQ\n"); indio_dev->channels = mx25_gcq_channels; indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels); indio_dev->info = &mx25_gcq_iio_info; indio_dev->name = driver_name; - ret = iio_device_register(indio_dev); - if (ret) { - dev_err(dev, "Failed to register iio device\n"); - goto err_irq_free; - } - - platform_set_drvdata(pdev, indio_dev); + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device\n"); return 0; - -err_irq_free: - free_irq(priv->irq, priv); -err_clk_unprepare: - clk_disable_unprepare(priv->clk); -err_vref_disable: - i = 4; -err_regulator_disable: - for (; i-- > 0;) { - if (priv->vref[i]) - regulator_disable(priv->vref[i]); - } - return ret; -} - -static void mx25_gcq_remove(struct platform_device *pdev) -{ - struct iio_dev *indio_dev = platform_get_drvdata(pdev); - struct mx25_gcq_priv *priv = iio_priv(indio_dev); - int i; - - iio_device_unregister(indio_dev); - free_irq(priv->irq, priv); - clk_disable_unprepare(priv->clk); - for (i = 4; i-- > 0;) { - if (priv->vref[i]) - regulator_disable(priv->vref[i]); - } } static const struct of_device_id mx25_gcq_ids[] = { @@ -399,7 +384,6 @@ static struct platform_driver mx25_gcq_driver = { .of_match_table = mx25_gcq_ids, }, .probe = mx25_gcq_probe, - .remove_new = mx25_gcq_remove, }; module_platform_driver(mx25_gcq_driver); From a6eaf02b82744b424b9b2c74847282deb2c6f77b Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:26 +0000 Subject: [PATCH 009/108] iio: adc: ad7124: Switch from of specific to fwnode based property handling Using the generic firmware data access functions from property.h provides a number of advantages: 1) Works with different firmware types. 2) Doesn't provide a 'bad' example for new IIO drivers. 3) Lets us use the new _scoped() loops with automatic reference count cleanup for fwnode_handle Cc: Lars-Peter Clausen Cc: Michael Hennerich Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-4-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 55 +++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index b9b206fcd748..e7b1d517d3de 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -14,7 +14,8 @@ #include #include #include -#include +#include +#include #include #include @@ -807,22 +808,19 @@ static int ad7124_check_chip_id(struct ad7124_state *st) return 0; } -static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev, - struct device_node *np) +static int ad7124_parse_channel_config(struct iio_dev *indio_dev, + struct device *dev) { struct ad7124_state *st = iio_priv(indio_dev); struct ad7124_channel_config *cfg; struct ad7124_channel *channels; - struct device_node *child; struct iio_chan_spec *chan; unsigned int ain[2], channel = 0, tmp; int ret; - st->num_channels = of_get_available_child_count(np); - if (!st->num_channels) { - dev_err(indio_dev->dev.parent, "no channel children\n"); - return -ENODEV; - } + st->num_channels = device_get_child_node_count(dev); + if (!st->num_channels) + return dev_err_probe(dev, -ENODEV, "no channel children\n"); chan = devm_kcalloc(indio_dev->dev.parent, st->num_channels, sizeof(*chan), GFP_KERNEL); @@ -838,39 +836,38 @@ static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev, indio_dev->num_channels = st->num_channels; st->channels = channels; - for_each_available_child_of_node(np, child) { + device_for_each_child_node_scoped(dev, child) { cfg = &st->channels[channel].cfg; - ret = of_property_read_u32(child, "reg", &channel); + ret = fwnode_property_read_u32(child, "reg", &channel); if (ret) - goto err; + return ret; - if (channel >= indio_dev->num_channels) { - dev_err(indio_dev->dev.parent, + if (channel >= indio_dev->num_channels) + return dev_err_probe(dev, -EINVAL, "Channel index >= number of channels\n"); - ret = -EINVAL; - goto err; - } - ret = of_property_read_u32_array(child, "diff-channels", - ain, 2); + ret = fwnode_property_read_u32_array(child, "diff-channels", + ain, 2); if (ret) - goto err; + return ret; st->channels[channel].nr = channel; st->channels[channel].ain = AD7124_CHANNEL_AINP(ain[0]) | AD7124_CHANNEL_AINM(ain[1]); - cfg->bipolar = of_property_read_bool(child, "bipolar"); + cfg->bipolar = fwnode_property_read_bool(child, "bipolar"); - ret = of_property_read_u32(child, "adi,reference-select", &tmp); + ret = fwnode_property_read_u32(child, "adi,reference-select", &tmp); if (ret) cfg->refsel = AD7124_INT_REF; else cfg->refsel = tmp; - cfg->buf_positive = of_property_read_bool(child, "adi,buffered-positive"); - cfg->buf_negative = of_property_read_bool(child, "adi,buffered-negative"); + cfg->buf_positive = + fwnode_property_read_bool(child, "adi,buffered-positive"); + cfg->buf_negative = + fwnode_property_read_bool(child, "adi,buffered-negative"); chan[channel] = ad7124_channel_template; chan[channel].address = channel; @@ -880,10 +877,6 @@ static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev, } return 0; -err: - of_node_put(child); - - return ret; } static int ad7124_setup(struct ad7124_state *st) @@ -943,9 +936,7 @@ static int ad7124_probe(struct spi_device *spi) struct iio_dev *indio_dev; int i, ret; - info = of_device_get_match_data(&spi->dev); - if (!info) - info = (void *)spi_get_device_id(spi)->driver_data; + info = spi_get_device_match_data(spi); if (!info) return -ENODEV; @@ -965,7 +956,7 @@ static int ad7124_probe(struct spi_device *spi) if (ret < 0) return ret; - ret = ad7124_of_parse_channel_config(indio_dev, spi->dev.of_node); + ret = ad7124_parse_channel_config(indio_dev, &spi->dev); if (ret < 0) return ret; From bb134d2fbc86a729af21096734b85f717c690fcd Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:27 +0000 Subject: [PATCH 010/108] iio: adc: ad7292: Switch from of specific to fwnode property handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reduces the wrong of device tree only IIO drivers that might be copied by converting over this simple case. Makes use of the new _scoped() handling to automatically release the fwnode_handle on early exit from the loop. Cc: Nuno Sá Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-5-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7292.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/iio/adc/ad7292.c b/drivers/iio/adc/ad7292.c index cccacec5db6d..6aadd14f459d 100644 --- a/drivers/iio/adc/ad7292.c +++ b/drivers/iio/adc/ad7292.c @@ -8,7 +8,8 @@ #include #include #include -#include +#include +#include #include #include @@ -260,7 +261,6 @@ static int ad7292_probe(struct spi_device *spi) { struct ad7292_state *st; struct iio_dev *indio_dev; - struct device_node *child; bool diff_channels = false; int ret; @@ -305,12 +305,11 @@ static int ad7292_probe(struct spi_device *spi) indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &ad7292_info; - for_each_available_child_of_node(spi->dev.of_node, child) { - diff_channels = of_property_read_bool(child, "diff-channels"); - if (diff_channels) { - of_node_put(child); + device_for_each_child_node_scoped(&spi->dev, child) { + diff_channels = fwnode_property_read_bool(child, + "diff-channels"); + if (diff_channels) break; - } } if (diff_channels) { From c3708c829a0662af429897a90aed46b70f14a50b Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:28 +0000 Subject: [PATCH 011/108] iio: adc: ad7192: Convert from of specific to fwnode property handling Enables use of with other firmwware types. Removes a case of device tree specific handlers that might get copied into new drivers. Cc: Alisa-Dariana Roman Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-6-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7192.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c index adc3cbe92d6e..7bcc7e2aa2a2 100644 --- a/drivers/iio/adc/ad7192.c +++ b/drivers/iio/adc/ad7192.c @@ -17,7 +17,9 @@ #include #include #include -#include +#include +#include +#include #include #include @@ -364,19 +366,19 @@ static inline bool ad7192_valid_external_frequency(u32 freq) freq <= AD7192_EXT_FREQ_MHZ_MAX); } -static int ad7192_of_clock_select(struct ad7192_state *st) +static int ad7192_clock_select(struct ad7192_state *st) { - struct device_node *np = st->sd.spi->dev.of_node; + struct device *dev = &st->sd.spi->dev; unsigned int clock_sel; clock_sel = AD7192_CLK_INT; /* use internal clock */ if (!st->mclk) { - if (of_property_read_bool(np, "adi,int-clock-output-enable")) + if (device_property_read_bool(dev, "adi,int-clock-output-enable")) clock_sel = AD7192_CLK_INT_CO; } else { - if (of_property_read_bool(np, "adi,clock-xtal")) + if (device_property_read_bool(dev, "adi,clock-xtal")) clock_sel = AD7192_CLK_EXT_MCLK1_2; else clock_sel = AD7192_CLK_EXT_MCLK2; @@ -385,7 +387,7 @@ static int ad7192_of_clock_select(struct ad7192_state *st) return clock_sel; } -static int ad7192_setup(struct iio_dev *indio_dev, struct device_node *np) +static int ad7192_setup(struct iio_dev *indio_dev, struct device *dev) { struct ad7192_state *st = iio_priv(indio_dev); bool rej60_en, refin2_en; @@ -407,7 +409,7 @@ static int ad7192_setup(struct iio_dev *indio_dev, struct device_node *np) id = FIELD_GET(AD7192_ID_MASK, id); if (id != st->chip_info->chip_id) - dev_warn(&st->sd.spi->dev, "device ID query failed (0x%X != 0x%X)\n", + dev_warn(dev, "device ID query failed (0x%X != 0x%X)\n", id, st->chip_info->chip_id); st->mode = FIELD_PREP(AD7192_MODE_SEL_MASK, AD7192_MODE_IDLE) | @@ -416,30 +418,30 @@ static int ad7192_setup(struct iio_dev *indio_dev, struct device_node *np) st->conf = FIELD_PREP(AD7192_CONF_GAIN_MASK, 0); - rej60_en = of_property_read_bool(np, "adi,rejection-60-Hz-enable"); + rej60_en = device_property_read_bool(dev, "adi,rejection-60-Hz-enable"); if (rej60_en) st->mode |= AD7192_MODE_REJ60; - refin2_en = of_property_read_bool(np, "adi,refin2-pins-enable"); + refin2_en = device_property_read_bool(dev, "adi,refin2-pins-enable"); if (refin2_en && st->chip_info->chip_id != CHIPID_AD7195) st->conf |= AD7192_CONF_REFSEL; st->conf &= ~AD7192_CONF_CHOP; - buf_en = of_property_read_bool(np, "adi,buffer-enable"); + buf_en = device_property_read_bool(dev, "adi,buffer-enable"); if (buf_en) st->conf |= AD7192_CONF_BUF; - bipolar = of_property_read_bool(np, "bipolar"); + bipolar = device_property_read_bool(dev, "bipolar"); if (!bipolar) st->conf |= AD7192_CONF_UNIPOLAR; - burnout_curr_en = of_property_read_bool(np, - "adi,burnout-currents-enable"); + burnout_curr_en = device_property_read_bool(dev, + "adi,burnout-currents-enable"); if (burnout_curr_en && buf_en) { st->conf |= AD7192_CONF_BURN; } else if (burnout_curr_en) { - dev_warn(&st->sd.spi->dev, + dev_warn(dev, "Can't enable burnout currents: see CHOP or buffer\n"); } @@ -1117,9 +1119,7 @@ static int ad7192_probe(struct spi_device *spi) } st->int_vref_mv = ret / 1000; - st->chip_info = of_device_get_match_data(&spi->dev); - if (!st->chip_info) - st->chip_info = (void *)spi_get_device_id(spi)->driver_data; + st->chip_info = spi_get_device_match_data(spi); indio_dev->name = st->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = st->chip_info->channels; @@ -1140,7 +1140,7 @@ static int ad7192_probe(struct spi_device *spi) if (IS_ERR(st->mclk)) return PTR_ERR(st->mclk); - st->clock_sel = ad7192_of_clock_select(st); + st->clock_sel = ad7192_clock_select(st); if (st->clock_sel == AD7192_CLK_EXT_MCLK1_2 || st->clock_sel == AD7192_CLK_EXT_MCLK2) { @@ -1152,7 +1152,7 @@ static int ad7192_probe(struct spi_device *spi) } } - ret = ad7192_setup(indio_dev, spi->dev.of_node); + ret = ad7192_setup(indio_dev, &spi->dev); if (ret) return ret; From 13c524162b3aa98ed1de7e528a1831dd4646451b Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:29 +0000 Subject: [PATCH 012/108] iio: accel: mma8452: Switch from of specific to fwnode property handling. In this case only use was to get an irq so easily converted. Also include linux/mod_devicetable.h for struct of_device_id definition. Using the generic firmware handling, this driver may be used with other firmware types. This also removes an example that might get copied into other drivers leaving them unable to be used with alternative firmware types. Cc: Haibo Chen Reviewed-and-tested-by: Haibo Chen Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-7-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mma8452.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index d3fd0318e47b..62e6369e2269 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -19,6 +19,8 @@ */ #include +#include +#include #include #include #include @@ -28,8 +30,6 @@ #include #include #include -#include -#include #include #include @@ -1642,7 +1642,7 @@ static int mma8452_probe(struct i2c_client *client) if (client->irq) { int irq2; - irq2 = of_irq_get_byname(client->dev.of_node, "INT2"); + irq2 = fwnode_irq_get_byname(dev_fwnode(&client->dev), "INT2"); if (irq2 == client->irq) { dev_dbg(&client->dev, "using interrupt line INT2\n"); From 2936e7d8c1177a4c77d6b88c96039f7370806638 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:30 +0000 Subject: [PATCH 013/108] iio: accel: fxls8962af: Switch from of specific to fwnode based properties. Only the irq was retrieved using an of specific accessor. Switch to the fwnode equivalent and adjust headers. Also include missing mod_devicetable.h and irq.h. Cc: Sean Nyekjaer Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-8-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/accel/fxls8962af-core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c index be8a15cb945f..4fbc01bda62e 100644 --- a/drivers/iio/accel/fxls8962af-core.c +++ b/drivers/iio/accel/fxls8962af-core.c @@ -15,9 +15,11 @@ #include #include #include +#include #include -#include +#include #include +#include #include #include @@ -1062,12 +1064,12 @@ static void fxls8962af_pm_disable(void *dev_ptr) fxls8962af_standby(iio_priv(indio_dev)); } -static void fxls8962af_get_irq(struct device_node *of_node, +static void fxls8962af_get_irq(struct device *dev, enum fxls8962af_int_pin *pin) { int irq; - irq = of_irq_get_byname(of_node, "INT2"); + irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2"); if (irq > 0) { *pin = FXLS8962AF_PIN_INT2; return; @@ -1086,7 +1088,7 @@ static int fxls8962af_irq_setup(struct iio_dev *indio_dev, int irq) u8 int_pin_sel; int ret; - fxls8962af_get_irq(dev->of_node, &int_pin); + fxls8962af_get_irq(dev, &int_pin); switch (int_pin) { case FXLS8962AF_PIN_INT1: int_pin_sel = FXLS8962AF_INT_PIN_SEL_INT1; From e1186ee3f48eb29c296f30232ab0c0997c0c6a06 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 18 Feb 2024 17:27:31 +0000 Subject: [PATCH 014/108] iio: adc: hx711: Switch from of specific to fwnode property handling. Allows driver to be used with other firmware types and removes an example that might be copied into new IIO drivers. Cc: Andreas Klinger Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240218172731.1023367-9-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/hx711.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c index c80c55fb8c6c..fef97c1d226a 100644 --- a/drivers/iio/adc/hx711.c +++ b/drivers/iio/adc/hx711.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -459,7 +459,6 @@ static const struct iio_chan_spec hx711_chan_spec[] = { static int hx711_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct hx711_data *hx711_data; struct iio_dev *indio_dev; int ret; @@ -533,7 +532,7 @@ static int hx711_probe(struct platform_device *pdev) hx711_data->gain_chan_a = 128; hx711_data->clock_frequency = 400000; - ret = of_property_read_u32(np, "clock-frequency", + ret = device_property_read_u32(&pdev->dev, "clock-frequency", &hx711_data->clock_frequency); /* From 8ca555bd059a4e645b5cf0ec09c3e002a3d347af Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 24 Feb 2024 12:32:07 +0000 Subject: [PATCH 015/108] iio: temp: ltc2983: Use __free(fwnode_handle) and device_for_each_node_scoped() This use of the new cleanup.h scope based freeing infrastructure allows us to exit directly from error conditions and in the good path with the reference obtained from fwnode_find_reference() (which may be an error pointer) automatically released. Similarly the _scoped() version of device_for_each_child_node() removes the need for the manual calling of fwnode_handl_put() in paths where the code exits the loop early. Tidy up some unusual indentation in a dev_dbg() whilst here. Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240224123215.161469-2-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/ltc2983.c | 137 +++++++++++------------------- 1 file changed, 50 insertions(+), 87 deletions(-) diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c index 39447c786af3..3c4524d57b8e 100644 --- a/drivers/iio/temperature/ltc2983.c +++ b/drivers/iio/temperature/ltc2983.c @@ -657,7 +657,6 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data const struct ltc2983_sensor *sensor) { struct ltc2983_thermocouple *thermo; - struct fwnode_handle *ref; u32 oc_current; int ret; @@ -704,7 +703,8 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data return ERR_PTR(-EINVAL); } - ref = fwnode_find_reference(child, "adi,cold-junction-handle", 0); + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,cold-junction-handle", 0); if (IS_ERR(ref)) { ref = NULL; } else { @@ -715,7 +715,7 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data * the error right away. */ dev_err(&st->spi->dev, "Property reg must be given\n"); - goto fail; + return ERR_PTR(ret); } } @@ -726,22 +726,15 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data thermo->custom = __ltc2983_custom_sensor_new(st, child, propname, false, 16384, true); - if (IS_ERR(thermo->custom)) { - ret = PTR_ERR(thermo->custom); - goto fail; - } + if (IS_ERR(thermo->custom)) + return ERR_CAST(thermo->custom); } /* set common parameters */ thermo->sensor.fault_handler = ltc2983_thermocouple_fault_handler; thermo->sensor.assign_chan = ltc2983_thermocouple_assign_chan; - fwnode_handle_put(ref); return &thermo->sensor; - -fail: - fwnode_handle_put(ref); - return ERR_PTR(ret); } static struct ltc2983_sensor * @@ -751,14 +744,14 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, struct ltc2983_rtd *rtd; int ret = 0; struct device *dev = &st->spi->dev; - struct fwnode_handle *ref; u32 excitation_current = 0, n_wires = 0; rtd = devm_kzalloc(dev, sizeof(*rtd), GFP_KERNEL); if (!rtd) return ERR_PTR(-ENOMEM); - ref = fwnode_find_reference(child, "adi,rsense-handle", 0); + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,rsense-handle", 0); if (IS_ERR(ref)) { dev_err(dev, "Property adi,rsense-handle missing or invalid"); return ERR_CAST(ref); @@ -767,7 +760,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, ret = fwnode_property_read_u32(ref, "reg", &rtd->r_sense_chan); if (ret) { dev_err(dev, "Property reg must be given\n"); - goto fail; + return ERR_PTR(ret); } ret = fwnode_property_read_u32(child, "adi,number-of-wires", &n_wires); @@ -788,8 +781,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, break; default: dev_err(dev, "Invalid number of wires:%u\n", n_wires); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } } @@ -799,8 +791,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, if (n_wires == 2 || n_wires == 3) { dev_err(dev, "Rotation not allowed for 2/3 Wire RTDs"); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } rtd->sensor_config |= LTC2983_RTD_C_ROTATE(1); } else { @@ -830,16 +821,14 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, "Invalid rsense chann:%d to use in kelvin rsense", rtd->r_sense_chan); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } if (sensor->chan < min || sensor->chan > max) { dev_err(dev, "Invalid chann:%d for the rtd config", sensor->chan); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } } else { /* same as differential case */ @@ -847,8 +836,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, dev_err(&st->spi->dev, "Invalid chann:%d for RTD", sensor->chan); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } } @@ -857,10 +845,8 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, rtd->custom = __ltc2983_custom_sensor_new(st, child, "adi,custom-rtd", false, 2048, false); - if (IS_ERR(rtd->custom)) { - ret = PTR_ERR(rtd->custom); - goto fail; - } + if (IS_ERR(rtd->custom)) + return ERR_CAST(rtd->custom); } /* set common parameters */ @@ -902,18 +888,13 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, dev_err(&st->spi->dev, "Invalid value for excitation current(%u)", excitation_current); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } } fwnode_property_read_u32(child, "adi,rtd-curve", &rtd->rtd_curve); - fwnode_handle_put(ref); return &rtd->sensor; -fail: - fwnode_handle_put(ref); - return ERR_PTR(ret); } static struct ltc2983_sensor * @@ -922,7 +903,6 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s { struct ltc2983_thermistor *thermistor; struct device *dev = &st->spi->dev; - struct fwnode_handle *ref; u32 excitation_current = 0; int ret = 0; @@ -930,7 +910,8 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s if (!thermistor) return ERR_PTR(-ENOMEM); - ref = fwnode_find_reference(child, "adi,rsense-handle", 0); + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,rsense-handle", 0); if (IS_ERR(ref)) { dev_err(dev, "Property adi,rsense-handle missing or invalid"); return ERR_CAST(ref); @@ -939,7 +920,7 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s ret = fwnode_property_read_u32(ref, "reg", &thermistor->r_sense_chan); if (ret) { dev_err(dev, "rsense channel must be configured...\n"); - goto fail; + return ERR_PTR(ret); } if (fwnode_property_read_bool(child, "adi,single-ended")) { @@ -959,8 +940,7 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s dev_err(&st->spi->dev, "Invalid chann:%d for differential thermistor", sensor->chan); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } /* check custom sensor */ @@ -979,10 +959,8 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s propname, steinhart, 64, false); - if (IS_ERR(thermistor->custom)) { - ret = PTR_ERR(thermistor->custom); - goto fail; - } + if (IS_ERR(thermistor->custom)) + return ERR_CAST(thermistor->custom); } /* set common parameters */ thermistor->sensor.fault_handler = ltc2983_common_fault_handler; @@ -1006,8 +984,7 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s LTC2983_SENSOR_THERMISTOR_STEINHART) { dev_err(&st->spi->dev, "Auto Range not allowed for custom sensors\n"); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } thermistor->excitation_current = 0x0c; break; @@ -1048,16 +1025,11 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s dev_err(&st->spi->dev, "Invalid value for excitation current(%u)", excitation_current); - ret = -EINVAL; - goto fail; + return ERR_PTR(-EINVAL); } } - fwnode_handle_put(ref); return &thermistor->sensor; -fail: - fwnode_handle_put(ref); - return ERR_PTR(ret); } static struct ltc2983_sensor * @@ -1350,8 +1322,7 @@ static irqreturn_t ltc2983_irq_handler(int irq, void *data) static int ltc2983_parse_fw(struct ltc2983_data *st) { struct device *dev = &st->spi->dev; - struct fwnode_handle *child; - int ret = 0, chan = 0, channel_avail_mask = 0; + int ret, chan = 0, channel_avail_mask = 0; device_property_read_u32(dev, "adi,mux-delay-config-us", &st->mux_delay_config); @@ -1369,38 +1340,35 @@ static int ltc2983_parse_fw(struct ltc2983_data *st) return -ENOMEM; st->iio_channels = st->num_channels; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct ltc2983_sensor sensor; ret = fwnode_property_read_u32(child, "reg", &sensor.chan); - if (ret) { - dev_err(dev, "reg property must given for child nodes\n"); - goto put_child; - } + if (ret) + return dev_err_probe(dev, ret, + "reg property must given for child nodes\n"); /* check if we have a valid channel */ if (sensor.chan < LTC2983_MIN_CHANNELS_NR || - sensor.chan > st->info->max_channels_nr) { - ret = -EINVAL; - dev_err(dev, "chan:%d must be from %u to %u\n", sensor.chan, - LTC2983_MIN_CHANNELS_NR, st->info->max_channels_nr); - goto put_child; - } else if (channel_avail_mask & BIT(sensor.chan)) { - ret = -EINVAL; - dev_err(dev, "chan:%d already in use\n", sensor.chan); - goto put_child; - } + sensor.chan > st->info->max_channels_nr) + return dev_err_probe(dev, -EINVAL, + "chan:%d must be from %u to %u\n", + sensor.chan, + LTC2983_MIN_CHANNELS_NR, + st->info->max_channels_nr); + + if (channel_avail_mask & BIT(sensor.chan)) + return dev_err_probe(dev, -EINVAL, + "chan:%d already in use\n", + sensor.chan); ret = fwnode_property_read_u32(child, "adi,sensor-type", &sensor.type); - if (ret) { - dev_err(dev, + if (ret) + return dev_err_probe(dev, ret, "adi,sensor-type property must given for child nodes\n"); - goto put_child; - } dev_dbg(dev, "Create new sensor, type %u, chann %u", - sensor.type, - sensor.chan); + sensor.type, sensor.chan); if (sensor.type >= LTC2983_SENSOR_THERMOCOUPLE && sensor.type <= LTC2983_SENSOR_THERMOCOUPLE_CUSTOM) { @@ -1427,17 +1395,15 @@ static int ltc2983_parse_fw(struct ltc2983_data *st) sensor.type == LTC2983_SENSOR_ACTIVE_TEMP) { st->sensors[chan] = ltc2983_temp_new(child, st, &sensor); } else { - dev_err(dev, "Unknown sensor type %d\n", sensor.type); - ret = -EINVAL; - goto put_child; + return dev_err_probe(dev, -EINVAL, + "Unknown sensor type %d\n", + sensor.type); } - if (IS_ERR(st->sensors[chan])) { - dev_err(dev, "Failed to create sensor %ld", - PTR_ERR(st->sensors[chan])); - ret = PTR_ERR(st->sensors[chan]); - goto put_child; - } + if (IS_ERR(st->sensors[chan])) + return dev_err_probe(dev, PTR_ERR(st->sensors[chan]), + "Failed to create sensor\n"); + /* set generic sensor parameters */ st->sensors[chan]->chan = sensor.chan; st->sensors[chan]->type = sensor.type; @@ -1447,9 +1413,6 @@ static int ltc2983_parse_fw(struct ltc2983_data *st) } return 0; -put_child: - fwnode_handle_put(child); - return ret; } static int ltc2983_eeprom_cmd(struct ltc2983_data *st, unsigned int cmd, From 5cfb5587f9145ea4e6de4e5c87f9bc60b6587e3b Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 24 Feb 2024 12:32:10 +0000 Subject: [PATCH 016/108] iio: adc: rzg2l_adc: Use device_for_each_child_node_scoped() Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. Cc: Lad Prabhakar Reviewed-by: Lad Prabhakar Link: https://lore.kernel.org/r/20240224123215.161469-5-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rzg2l_adc.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c index 0921ff2d9b3a..cd3a7e46ea53 100644 --- a/drivers/iio/adc/rzg2l_adc.c +++ b/drivers/iio/adc/rzg2l_adc.c @@ -302,7 +302,6 @@ static irqreturn_t rzg2l_adc_isr(int irq, void *dev_id) static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l_adc *adc) { struct iio_chan_spec *chan_array; - struct fwnode_handle *fwnode; struct rzg2l_adc_data *data; unsigned int channel; int num_channels; @@ -330,17 +329,13 @@ static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l return -ENOMEM; i = 0; - device_for_each_child_node(&pdev->dev, fwnode) { + device_for_each_child_node_scoped(&pdev->dev, fwnode) { ret = fwnode_property_read_u32(fwnode, "reg", &channel); - if (ret) { - fwnode_handle_put(fwnode); + if (ret) return ret; - } - if (channel >= RZG2L_ADC_MAX_CHANNELS) { - fwnode_handle_put(fwnode); + if (channel >= RZG2L_ADC_MAX_CHANNELS) return -EINVAL; - } chan_array[i].type = IIO_VOLTAGE; chan_array[i].indexed = 1; From 582021f4e9f8003146aeec2125fafeadfe1139f8 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 25 Feb 2024 14:27:14 +0000 Subject: [PATCH 017/108] iio: adc: rcar-gyroadc: use for_each_available_child_node_scoped() Using automated cleanup to replace of_node_put() handling allows for a simplfied flow by enabling direct returns on errors. Non available child nodes should never have been considered; that is ones where status != okay and was defined. Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20240225142714.286440-5-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rcar-gyroadc.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c index d524f2e8e927..15a21d2860e7 100644 --- a/drivers/iio/adc/rcar-gyroadc.c +++ b/drivers/iio/adc/rcar-gyroadc.c @@ -318,7 +318,6 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) 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; @@ -326,7 +325,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) unsigned int num_channels; int ret, first = 1; - for_each_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { of_id = of_match_node(rcar_gyroadc_child_match, child); if (!of_id) { dev_err(dev, "Ignoring unsupported ADC \"%pOFn\".", @@ -352,7 +351,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_3); break; default: - goto err_e_inval; + return -EINVAL; } /* @@ -369,7 +368,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) dev_err(dev, "Failed to get child reg property of ADC \"%pOFn\".\n", child); - goto err_of_node_put; + return ret; } /* Channel number is too high. */ @@ -377,7 +376,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) dev_err(dev, "Only %i channels supported with %pOFn, but reg = <%i>.\n", num_channels, child, reg); - goto err_e_inval; + return -EINVAL; } } @@ -386,7 +385,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) dev_err(dev, "Channel %i uses different ADC mode than the rest.\n", reg); - goto err_e_inval; + return -EINVAL; } /* Channel is valid, grab the regulator. */ @@ -396,8 +395,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) if (IS_ERR(vref)) { dev_dbg(dev, "Channel %i 'vref' supply not connected.\n", reg); - ret = PTR_ERR(vref); - goto err_of_node_put; + return PTR_ERR(vref); } priv->vref[reg] = vref; @@ -422,7 +420,6 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) * we can stop parsing here. */ if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) { - of_node_put(child); break; } } @@ -433,12 +430,6 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) } return 0; - -err_e_inval: - ret = -EINVAL; -err_of_node_put: - of_node_put(child); - return ret; } static void rcar_gyroadc_deinit_supplies(struct iio_dev *indio_dev) From 9266dba06876dd2d7e2f1709a19a8b1b3eb6caef Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Feb 2024 22:30:23 +0200 Subject: [PATCH 018/108] iio: adc: spear_adc: Make use of device properties Convert the module to be property provider agnostic and allow it to be used on non-OF platforms. Include mod_devicetable.h explicitly to replace the dropped of.h which included mod_devicetable.h indirectly. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240228203023.3609181-1-andriy.shevchenko@linux.intel.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/spear_adc.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/drivers/iio/adc/spear_adc.c b/drivers/iio/adc/spear_adc.c index 71362c2ddf89..b6dd096391c1 100644 --- a/drivers/iio/adc/spear_adc.c +++ b/drivers/iio/adc/spear_adc.c @@ -5,8 +5,10 @@ * Copyright 2012 Stefan Roese */ +#include #include #include +#include #include #include #include @@ -15,8 +17,6 @@ #include #include #include -#include -#include #include #include @@ -70,7 +70,7 @@ struct adc_regs_spear6xx { }; struct spear_adc_state { - struct device_node *np; + struct device *dev; struct adc_regs_spear3xx __iomem *adc_base_spear3xx; struct adc_regs_spear6xx __iomem *adc_base_spear6xx; struct clk *clk; @@ -123,7 +123,7 @@ static void spear_adc_set_ctrl(struct spear_adc_state *st, int n, static u32 spear_adc_get_average(struct spear_adc_state *st) { - if (of_device_is_compatible(st->np, "st,spear600-adc")) { + if (device_is_compatible(st->dev, "st,spear600-adc")) { return __raw_readl(&st->adc_base_spear6xx->average.msb) & SPEAR_ADC_DATA_MASK; } else { @@ -134,7 +134,7 @@ static u32 spear_adc_get_average(struct spear_adc_state *st) static void spear_adc_set_scanrate(struct spear_adc_state *st, u32 rate) { - if (of_device_is_compatible(st->np, "st,spear600-adc")) { + if (device_is_compatible(st->dev, "st,spear600-adc")) { __raw_writel(SPEAR600_ADC_SCAN_RATE_LO(rate), &st->adc_base_spear6xx->scan_rate_lo); __raw_writel(SPEAR600_ADC_SCAN_RATE_HI(rate), @@ -266,7 +266,6 @@ static const struct iio_info spear_adc_info = { static int spear_adc_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct spear_adc_state *st; struct iio_dev *indio_dev = NULL; @@ -279,11 +278,10 @@ static int spear_adc_probe(struct platform_device *pdev) "failed allocating iio device\n"); st = iio_priv(indio_dev); + st->dev = dev; mutex_init(&st->lock); - st->np = np; - /* * SPEAr600 has a different register layout than other SPEAr SoC's * (e.g. SPEAr3xx). Let's provide two register base addresses @@ -310,8 +308,7 @@ static int spear_adc_probe(struct platform_device *pdev) if (ret < 0) return dev_err_probe(dev, ret, "failed requesting interrupt\n"); - if (of_property_read_u32(np, "sampling-frequency", - &st->sampling_freq)) + if (device_property_read_u32(dev, "sampling-frequency", &st->sampling_freq)) return dev_err_probe(dev, -EINVAL, "sampling-frequency missing in DT\n"); @@ -319,13 +316,13 @@ static int spear_adc_probe(struct platform_device *pdev) * Optional avg_samples defaults to 0, resulting in single data * conversion */ - of_property_read_u32(np, "average-samples", &st->avg_samples); + device_property_read_u32(dev, "average-samples", &st->avg_samples); /* * Optional vref_external defaults to 0, resulting in internal vref * selection */ - of_property_read_u32(np, "vref-external", &st->vref_external); + device_property_read_u32(dev, "vref-external", &st->vref_external); spear_adc_configure(st); @@ -346,19 +343,17 @@ static int spear_adc_probe(struct platform_device *pdev) return 0; } -#ifdef CONFIG_OF static const struct of_device_id spear_adc_dt_ids[] = { { .compatible = "st,spear600-adc", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, spear_adc_dt_ids); -#endif static struct platform_driver spear_adc_driver = { .probe = spear_adc_probe, .driver = { .name = SPEAR_ADC_MOD_NAME, - .of_match_table = of_match_ptr(spear_adc_dt_ids), + .of_match_table = spear_adc_dt_ids, }, }; From 3d50d03f21949625534c81b744b8e42765623e9e Mon Sep 17 00:00:00 2001 From: Dumitru Ceclan Date: Wed, 28 Feb 2024 13:06:18 +0200 Subject: [PATCH 019/108] dt-bindings: adc: add AD7173 The AD7173 family offer a complete integrated Sigma-Delta ADC solution which can be used in high precision, low noise single channel applications or higher speed multiplexed applications. The Sigma-Delta ADC is intended primarily for measurement of signals close to DC but also delivers outstanding performance with input bandwidths out to ~10kHz. Reviewed-by: Conor Dooley Signed-off-by: Dumitru Ceclan Link: https://lore.kernel.org/r/20240228110622.25114-1-mitrutzceclan@gmail.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad7173.yaml | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml new file mode 100644 index 000000000000..36f16a325bc5 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml @@ -0,0 +1,246 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2023 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad7173.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD7173 ADC + +maintainers: + - Ceclan Dumitru + +description: | + Analog Devices AD717x ADC's: + The AD717x family offer a complete integrated Sigma-Delta ADC solution which + can be used in high precision, low noise single channel applications + (Life Science measurements) or higher speed multiplexed applications + (Factory Automation PLC Input modules). The Sigma-Delta ADC is intended + primarily for measurement of signals close to DC but also delivers + outstanding performance with input bandwidths out to ~10kHz. + + Datasheets for supported chips: + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7173-8.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7175-2.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7176-2.pdf + +properties: + compatible: + enum: + - adi,ad7172-2 + - adi,ad7173-8 + - adi,ad7175-2 + - adi,ad7176-2 + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + items: + - description: | + Ready: multiplexed with SPI data out. While SPI CS is low, + can be used to indicate the completion of a conversion. + + - description: | + Error: The three error bits in the status register (ADC_ERROR, CRC_ERROR, + and REG_ERROR) are OR'ed, inverted, and mapped to the ERROR pin. + Therefore, the ERROR pin indicates that an error has occurred. + + interrupt-names: + minItems: 1 + items: + - const: rdy + - const: err + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + spi-max-frequency: + maximum: 20000000 + + gpio-controller: + description: Marks the device node as a GPIO controller. + + '#gpio-cells': + const: 2 + description: + The first cell is the GPIO number and the second cell specifies + GPIO flags, as defined in . + + vref-supply: + description: | + Differential external reference supply used for conversion. The reference + voltage (Vref) specified here must be the voltage difference between the + REF+ and REF- pins: Vref = (REF+) - (REF-). + + vref2-supply: + description: | + Differential external reference supply used for conversion. The reference + voltage (Vref2) specified here must be the voltage difference between the + REF2+ and REF2- pins: Vref2 = (REF2+) - (REF2-). + + avdd-supply: + description: Avdd supply, can be used as reference for conversion. + This supply is referenced to AVSS, voltage specified here + represents (AVDD1 - AVSS). + + avdd2-supply: + description: Avdd2 supply, used as the input to the internal voltage regulator. + This supply is referenced to AVSS, voltage specified here + represents (AVDD2 - AVSS). + + iovdd-supply: + description: iovdd supply, used for the chip digital interface. + + clocks: + maxItems: 1 + description: | + Optional external clock source. Can include one clock source: external + clock or external crystal. + + clock-names: + enum: + - ext-clk + - xtal + + '#clock-cells': + const: 0 + +patternProperties: + "^channel@[0-9a-f]$": + type: object + $ref: adc.yaml + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 15 + + diff-channels: + items: + minimum: 0 + maximum: 31 + + adi,reference-select: + description: | + Select the reference source to use when converting on + the specific channel. Valid values are: + vref : REF+ /REF− + vref2 : REF2+ /REF2− + refout-avss: REFOUT/AVSS (Internal reference) + avdd : AVDD /AVSS + + External reference ref2 only available on ad7173-8. + If not specified, internal reference used. + $ref: /schemas/types.yaml#/definitions/string + enum: + - vref + - vref2 + - refout-avss + - avdd + default: refout-avss + + required: + - reg + - diff-channels + +required: + - compatible + - reg + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + properties: + compatible: + not: + contains: + const: adi,ad7173-8 + then: + properties: + vref2-supply: false + patternProperties: + "^channel@[0-9a-f]$": + properties: + adi,reference-select: + enum: + - vref + - refout-avss + - avdd + reg: + maximum: 3 + + - if: + anyOf: + - required: [clock-names] + - required: [clocks] + then: + properties: + '#clock-cells': false + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad7173-8"; + reg = <0>; + + #address-cells = <1>; + #size-cells = <0>; + + interrupts = <25 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "rdy"; + interrupt-parent = <&gpio>; + spi-max-frequency = <5000000>; + gpio-controller; + #gpio-cells = <2>; + #clock-cells = <0>; + + vref-supply = <&dummy_regulator>; + + channel@0 { + reg = <0>; + bipolar; + diff-channels = <0 1>; + adi,reference-select = "vref"; + }; + + channel@1 { + reg = <1>; + diff-channels = <2 3>; + }; + + channel@2 { + reg = <2>; + bipolar; + diff-channels = <4 5>; + }; + + channel@3 { + reg = <3>; + bipolar; + diff-channels = <6 7>; + }; + + channel@4 { + reg = <4>; + diff-channels = <8 9>; + adi,reference-select = "avdd"; + }; + }; + }; From 7b0c9f8fa3d25622c21eeaa2c095c8a36b8b08b7 Mon Sep 17 00:00:00 2001 From: Dumitru Ceclan Date: Wed, 28 Feb 2024 13:06:19 +0200 Subject: [PATCH 020/108] iio: adc: ad_sigma_delta: Add optional irq selection Add optional irq_num attribute to ad_sigma_delta_info structure for selecting the used interrupt line for ADC's conversion completion. Signed-off-by: Dumitru Ceclan Reviewed-by: Nuno Sa Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240228110622.25114-2-mitrutzceclan@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad_sigma_delta.c | 23 ++++++++++++++--------- include/linux/iio/adc/ad_sigma_delta.h | 3 +++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c index a602429cdde4..97a05f325df7 100644 --- a/drivers/iio/adc/ad_sigma_delta.c +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -222,11 +222,11 @@ int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta, goto out; sigma_delta->irq_dis = false; - enable_irq(sigma_delta->spi->irq); + enable_irq(sigma_delta->irq_line); timeout = wait_for_completion_timeout(&sigma_delta->completion, 2 * HZ); if (timeout == 0) { sigma_delta->irq_dis = true; - disable_irq_nosync(sigma_delta->spi->irq); + disable_irq_nosync(sigma_delta->irq_line); ret = -EIO; } else { ret = 0; @@ -295,7 +295,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev, ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_SINGLE); sigma_delta->irq_dis = false; - enable_irq(sigma_delta->spi->irq); + enable_irq(sigma_delta->irq_line); ret = wait_for_completion_interruptible_timeout( &sigma_delta->completion, HZ); @@ -315,7 +315,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev, out: if (!sigma_delta->irq_dis) { - disable_irq_nosync(sigma_delta->spi->irq); + disable_irq_nosync(sigma_delta->irq_line); sigma_delta->irq_dis = true; } @@ -396,7 +396,7 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) goto err_unlock; sigma_delta->irq_dis = false; - enable_irq(sigma_delta->spi->irq); + enable_irq(sigma_delta->irq_line); return 0; @@ -414,7 +414,7 @@ static int ad_sd_buffer_postdisable(struct iio_dev *indio_dev) wait_for_completion_timeout(&sigma_delta->completion, HZ); if (!sigma_delta->irq_dis) { - disable_irq_nosync(sigma_delta->spi->irq); + disable_irq_nosync(sigma_delta->irq_line); sigma_delta->irq_dis = true; } @@ -516,7 +516,7 @@ static irqreturn_t ad_sd_trigger_handler(int irq, void *p) irq_handled: iio_trigger_notify_done(indio_dev->trig); sigma_delta->irq_dis = false; - enable_irq(sigma_delta->spi->irq); + enable_irq(sigma_delta->irq_line); return IRQ_HANDLED; } @@ -587,13 +587,13 @@ static int devm_ad_sd_probe_trigger(struct device *dev, struct iio_dev *indio_de sigma_delta->irq_dis = true; /* the IRQ core clears IRQ_DISABLE_UNLAZY flag when freeing an IRQ */ - irq_set_status_flags(sigma_delta->spi->irq, IRQ_DISABLE_UNLAZY); + irq_set_status_flags(sigma_delta->irq_line, IRQ_DISABLE_UNLAZY); /* Allow overwriting the flags from firmware */ if (!irq_flags) irq_flags = sigma_delta->info->irq_flags; - ret = devm_request_irq(dev, sigma_delta->spi->irq, + ret = devm_request_irq(dev, sigma_delta->irq_line, ad_sd_data_rdy_trig_poll, irq_flags | IRQF_NO_AUTOEN, indio_dev->name, @@ -673,6 +673,11 @@ int ad_sd_init(struct ad_sigma_delta *sigma_delta, struct iio_dev *indio_dev, } } + if (info->irq_line) + sigma_delta->irq_line = info->irq_line; + else + sigma_delta->irq_line = spi->irq; + iio_device_set_drvdata(indio_dev, sigma_delta); return 0; diff --git a/include/linux/iio/adc/ad_sigma_delta.h b/include/linux/iio/adc/ad_sigma_delta.h index 719cf9cc6e1a..383614ebd760 100644 --- a/include/linux/iio/adc/ad_sigma_delta.h +++ b/include/linux/iio/adc/ad_sigma_delta.h @@ -48,6 +48,7 @@ struct iio_dev; * be used. * @irq_flags: flags for the interrupt used by the triggered buffer * @num_slots: Number of sequencer slots + * @irq_line: IRQ for reading conversions. If 0, spi->irq will be used */ struct ad_sigma_delta_info { int (*set_channel)(struct ad_sigma_delta *, unsigned int channel); @@ -62,6 +63,7 @@ struct ad_sigma_delta_info { unsigned int data_reg; unsigned long irq_flags; unsigned int num_slots; + int irq_line; }; /** @@ -89,6 +91,7 @@ struct ad_sigma_delta { unsigned int active_slots; unsigned int current_slot; unsigned int num_slots; + int irq_line; bool status_appended; /* map slots to channels in order to know what to expect from devices */ unsigned int *slots; From 76a1e6a4280211b1ceffec4f79f96e492c661229 Mon Sep 17 00:00:00 2001 From: Dumitru Ceclan Date: Wed, 28 Feb 2024 13:06:20 +0200 Subject: [PATCH 021/108] iio: adc: ad7173: add AD7173 driver The AD7173 family offer a complete integrated Sigma-Delta ADC solution which can be used in high precision, low noise single channel applications or higher speed multiplexed applications. The Sigma-Delta ADC is intended primarily for measurement of signals close to DC but also delivers outstanding performance with input bandwidths out to ~10kHz. Reviewed-by: Andy Shevchenko Reviewed-by: Michael Walle # for gpio-regmap Signed-off-by: Dumitru Ceclan Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240228110622.25114-3-mitrutzceclan@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 17 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad7173.c | 1116 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1134 insertions(+) create mode 100644 drivers/iio/adc/ad7173.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 0d9282fa67f5..91286ab83bd0 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -70,6 +70,23 @@ config AD7124 To compile this driver as a module, choose M here: the module will be called ad7124. +config AD7173 + tristate "Analog Devices AD7173 driver" + depends on SPI_MASTER + select AD_SIGMA_DELTA + select GPIO_REGMAP if GPIOLIB + select REGMAP_SPI if GPIOLIB + help + Say yes here to build support for Analog Devices AD7173 and similar ADC + Currently supported models: + - AD7172-2 + - AD7173-8 + - AD7175-2 + - AD7176-2 + + To compile this driver as a module, choose M here: the module will be + called ad7173. + config AD7192 tristate "Analog Devices AD7190 AD7192 AD7193 AD7195 ADC driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index b3c434722364..717e964afed9 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_AD7091R) += ad7091r-base.o obj-$(CONFIG_AD7091R5) += ad7091r5.o obj-$(CONFIG_AD7091R8) += ad7091r8.o obj-$(CONFIG_AD7124) += ad7124.o +obj-$(CONFIG_AD7173) += ad7173.o obj-$(CONFIG_AD7192) += ad7192.o obj-$(CONFIG_AD7266) += ad7266.o obj-$(CONFIG_AD7280) += ad7280a.o diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c new file mode 100644 index 000000000000..b42fbe28a325 --- /dev/null +++ b/drivers/iio/adc/ad7173.c @@ -0,0 +1,1116 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD7172-2/AD7173-8/AD7175-2/AD7176-2 SPI ADC driver + * Copyright (C) 2015, 2024 Analog Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define AD7173_REG_COMMS 0x00 +#define AD7173_REG_ADC_MODE 0x01 +#define AD7173_REG_INTERFACE_MODE 0x02 +#define AD7173_REG_CRC 0x03 +#define AD7173_REG_DATA 0x04 +#define AD7173_REG_GPIO 0x06 +#define AD7173_REG_ID 0x07 +#define AD7173_REG_CH(x) (0x10 + (x)) +#define AD7173_REG_SETUP(x) (0x20 + (x)) +#define AD7173_REG_FILTER(x) (0x28 + (x)) +#define AD7173_REG_OFFSET(x) (0x30 + (x)) +#define AD7173_REG_GAIN(x) (0x38 + (x)) + +#define AD7173_RESET_LENGTH BITS_TO_BYTES(64) + +#define AD7173_CH_ENABLE BIT(15) +#define AD7173_CH_SETUP_SEL_MASK GENMASK(14, 12) +#define AD7173_CH_SETUP_AINPOS_MASK GENMASK(9, 5) +#define AD7173_CH_SETUP_AINNEG_MASK GENMASK(4, 0) + +#define AD7173_CH_ADDRESS(pos, neg) \ + (FIELD_PREP(AD7173_CH_SETUP_AINPOS_MASK, pos) | \ + FIELD_PREP(AD7173_CH_SETUP_AINNEG_MASK, neg)) +#define AD7173_AIN_TEMP_POS 17 +#define AD7173_AIN_TEMP_NEG 18 + +#define AD7172_ID 0x00d0 +#define AD7173_ID 0x30d0 +#define AD7175_ID 0x0cd0 +#define AD7176_ID 0x0c90 +#define AD7173_ID_MASK GENMASK(15, 4) + +#define AD7173_ADC_MODE_REF_EN BIT(15) +#define AD7173_ADC_MODE_SING_CYC BIT(13) +#define AD7173_ADC_MODE_MODE_MASK GENMASK(6, 4) +#define AD7173_ADC_MODE_CLOCKSEL_MASK GENMASK(3, 2) +#define AD7173_ADC_MODE_CLOCKSEL_INT 0x0 +#define AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT 0x1 +#define AD7173_ADC_MODE_CLOCKSEL_EXT 0x2 +#define AD7173_ADC_MODE_CLOCKSEL_XTAL 0x3 + +#define AD7173_GPIO_PDSW BIT(14) +#define AD7173_GPIO_OP_EN2_3 BIT(13) +#define AD7173_GPIO_MUX_IO BIT(12) +#define AD7173_GPIO_SYNC_EN BIT(11) +#define AD7173_GPIO_ERR_EN BIT(10) +#define AD7173_GPIO_ERR_DAT BIT(9) +#define AD7173_GPIO_GP_DATA3 BIT(7) +#define AD7173_GPIO_GP_DATA2 BIT(6) +#define AD7173_GPIO_IP_EN1 BIT(5) +#define AD7173_GPIO_IP_EN0 BIT(4) +#define AD7173_GPIO_OP_EN1 BIT(3) +#define AD7173_GPIO_OP_EN0 BIT(2) +#define AD7173_GPIO_GP_DATA1 BIT(1) +#define AD7173_GPIO_GP_DATA0 BIT(0) + +#define AD7173_GPO12_DATA(x) BIT((x) + 0) +#define AD7173_GPO23_DATA(x) BIT((x) + 4) +#define AD7173_GPO_DATA(x) ((x) < 2 ? AD7173_GPO12_DATA(x) : AD7173_GPO23_DATA(x)) + +#define AD7173_INTERFACE_DATA_STAT BIT(6) +#define AD7173_INTERFACE_DATA_STAT_EN(x) \ + FIELD_PREP(AD7173_INTERFACE_DATA_STAT, x) + +#define AD7173_SETUP_BIPOLAR BIT(12) +#define AD7173_SETUP_AREF_BUF_MASK GENMASK(11, 10) +#define AD7173_SETUP_AIN_BUF_MASK GENMASK(9, 8) + +#define AD7173_SETUP_REF_SEL_MASK GENMASK(5, 4) +#define AD7173_SETUP_REF_SEL_AVDD1_AVSS 0x3 +#define AD7173_SETUP_REF_SEL_INT_REF 0x2 +#define AD7173_SETUP_REF_SEL_EXT_REF2 0x1 +#define AD7173_SETUP_REF_SEL_EXT_REF 0x0 +#define AD7173_VOLTAGE_INT_REF_uV 2500000 +#define AD7173_TEMP_SENSIIVITY_uV_per_C 477 + +#define AD7173_FILTER_ODR0_MASK GENMASK(5, 0) +#define AD7173_MAX_CONFIGS 8 + +enum ad7173_ids { + ID_AD7172_2, + ID_AD7173_8, + ID_AD7175_2, + ID_AD7176_2, +}; + +struct ad7173_device_info { + const unsigned int *sinc5_data_rates; + unsigned int num_sinc5_data_rates; + unsigned int num_channels; + unsigned int num_configs; + unsigned int num_inputs; + unsigned int clock; + unsigned int id; + char *name; + bool has_temp; + u8 num_gpios; +}; + +struct ad7173_channel_config { + u8 cfg_slot; + bool live; + + /* Following fields are used to compare equality. */ + struct_group(config_props, + bool bipolar; + bool input_buf; + u8 odr; + u8 ref_sel; + ); +}; + +struct ad7173_channel { + unsigned int chan_reg; + unsigned int ain; + struct ad7173_channel_config cfg; +}; + +struct ad7173_state { + struct ad_sigma_delta sd; + const struct ad7173_device_info *info; + struct ad7173_channel *channels; + struct regulator_bulk_data regulators[3]; + unsigned int adc_mode; + unsigned int interface_mode; + unsigned int num_channels; + struct ida cfg_slots_status; + unsigned long long config_usage_counter; + unsigned long long *config_cnts; + struct clk *ext_clk; + struct clk_hw int_clk_hw; +#if IS_ENABLED(CONFIG_GPIOLIB) + struct regmap *reg_gpiocon_regmap; + struct gpio_regmap *gpio_regmap; +#endif +}; + +static const unsigned int ad7173_sinc5_data_rates[] = { + 6211000, 6211000, 6211000, 6211000, 6211000, 6211000, 5181000, 4444000, /* 0-7 */ + 3115000, 2597000, 1007000, 503800, 381000, 200300, 100500, 59520, /* 8-15 */ + 49680, 20010, 16333, 10000, 5000, 2500, 1250, /* 16-22 */ +}; + +static const unsigned int ad7175_sinc5_data_rates[] = { + 50000000, 41667000, 31250000, 27778000, /* 0-3 */ + 20833000, 17857000, 12500000, 10000000, /* 4-7 */ + 5000000, 2500000, 1000000, 500000, /* 8-11 */ + 397500, 200000, 100000, 59920, /* 12-15 */ + 49960, 20000, 16666, 10000, /* 16-19 */ + 5000, /* 20 */ +}; + +static const struct ad7173_device_info ad7173_device_info[] = { + [ID_AD7172_2] = { + .name = "ad7172-2", + .id = AD7172_ID, + .num_inputs = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), + }, + [ID_AD7173_8] = { + .name = "ad7173-8", + .id = AD7173_ID, + .num_inputs = 17, + .num_channels = 16, + .num_configs = 8, + .num_gpios = 4, + .has_temp = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), + }, + [ID_AD7175_2] = { + .name = "ad7175-2", + .id = AD7175_ID, + .num_inputs = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), + }, + [ID_AD7176_2] = { + .name = "ad7176-2", + .id = AD7176_ID, + .num_inputs = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = false, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), + }, +}; + +static const char *const ad7173_ref_sel_str[] = { + [AD7173_SETUP_REF_SEL_EXT_REF] = "vref", + [AD7173_SETUP_REF_SEL_EXT_REF2] = "vref2", + [AD7173_SETUP_REF_SEL_INT_REF] = "refout-avss", + [AD7173_SETUP_REF_SEL_AVDD1_AVSS] = "avdd", +}; + +static const char *const ad7173_clk_sel[] = { + "ext-clk", "xtal" +}; + +#if IS_ENABLED(CONFIG_GPIOLIB) + +static const struct regmap_range ad7173_range_gpio[] = { + regmap_reg_range(AD7173_REG_GPIO, AD7173_REG_GPIO), +}; + +static const struct regmap_access_table ad7173_access_table = { + .yes_ranges = ad7173_range_gpio, + .n_yes_ranges = ARRAY_SIZE(ad7173_range_gpio), +}; + +static const struct regmap_config ad7173_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .rd_table = &ad7173_access_table, + .wr_table = &ad7173_access_table, + .read_flag_mask = BIT(6), +}; + +static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base, + unsigned int offset, unsigned int *reg, + unsigned int *mask) +{ + *mask = AD7173_GPO_DATA(offset); + *reg = base; + return 0; +} + +static void ad7173_gpio_disable(void *data) +{ + struct ad7173_state *st = data; + unsigned int mask; + + mask = AD7173_GPIO_OP_EN0 | AD7173_GPIO_OP_EN1 | AD7173_GPIO_OP_EN2_3; + regmap_update_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, mask, ~mask); +} + +static int ad7173_gpio_init(struct ad7173_state *st) +{ + struct gpio_regmap_config gpio_regmap = {}; + struct device *dev = &st->sd.spi->dev; + unsigned int mask; + int ret; + + st->reg_gpiocon_regmap = devm_regmap_init_spi(st->sd.spi, &ad7173_regmap_config); + ret = PTR_ERR_OR_ZERO(st->reg_gpiocon_regmap); + if (ret) + return dev_err_probe(dev, ret, "Unable to init regmap\n"); + + mask = AD7173_GPIO_OP_EN0 | AD7173_GPIO_OP_EN1 | AD7173_GPIO_OP_EN2_3; + regmap_update_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, mask, mask); + + ret = devm_add_action_or_reset(dev, ad7173_gpio_disable, st); + if (ret) + return ret; + + gpio_regmap.parent = dev; + gpio_regmap.regmap = st->reg_gpiocon_regmap; + gpio_regmap.ngpio = st->info->num_gpios; + gpio_regmap.reg_set_base = AD7173_REG_GPIO; + gpio_regmap.reg_mask_xlate = ad7173_mask_xlate; + + st->gpio_regmap = devm_gpio_regmap_register(dev, &gpio_regmap); + ret = PTR_ERR_OR_ZERO(st->gpio_regmap); + if (ret) + return dev_err_probe(dev, ret, "Unable to init gpio-regmap\n"); + + return 0; +} +#else +static int ad7173_gpio_init(struct ad7173_state *st) +{ + return 0; +} +#endif /* CONFIG_GPIOLIB */ + +static struct ad7173_state *ad_sigma_delta_to_ad7173(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7173_state, sd); +} + +static struct ad7173_state *clk_hw_to_ad7173(struct clk_hw *hw) +{ + return container_of(hw, struct ad7173_state, int_clk_hw); +} + +static void ad7173_ida_destroy(void *data) +{ + struct ad7173_state *st = data; + + ida_destroy(&st->cfg_slots_status); +} + +static void ad7173_reset_usage_cnts(struct ad7173_state *st) +{ + memset64(st->config_cnts, 0, st->info->num_configs); + st->config_usage_counter = 0; +} + +static struct ad7173_channel_config * +ad7173_find_live_config(struct ad7173_state *st, struct ad7173_channel_config *cfg) +{ + struct ad7173_channel_config *cfg_aux; + ptrdiff_t cmp_size; + int i; + + cmp_size = sizeof_field(struct ad7173_channel_config, config_props); + for (i = 0; i < st->num_channels; i++) { + cfg_aux = &st->channels[i].cfg; + + if (cfg_aux->live && + !memcmp(&cfg->config_props, &cfg_aux->config_props, cmp_size)) + return cfg_aux; + } + return NULL; +} + +/* Could be replaced with a generic LRU implementation */ +static int ad7173_free_config_slot_lru(struct ad7173_state *st) +{ + int i, lru_position = 0; + + for (i = 1; i < st->info->num_configs; i++) + if (st->config_cnts[i] < st->config_cnts[lru_position]) + lru_position = i; + + for (i = 0; i < st->num_channels; i++) + if (st->channels[i].cfg.cfg_slot == lru_position) + st->channels[i].cfg.live = false; + + ida_free(&st->cfg_slots_status, lru_position); + return ida_alloc(&st->cfg_slots_status, GFP_KERNEL); +} + +/* Could be replaced with a generic LRU implementation */ +static int ad7173_load_config(struct ad7173_state *st, + struct ad7173_channel_config *cfg) +{ + unsigned int config; + int free_cfg_slot, ret; + + free_cfg_slot = ida_alloc_range(&st->cfg_slots_status, 0, + st->info->num_configs - 1, GFP_KERNEL); + if (free_cfg_slot < 0) + free_cfg_slot = ad7173_free_config_slot_lru(st); + + cfg->cfg_slot = free_cfg_slot; + config = FIELD_PREP(AD7173_SETUP_REF_SEL_MASK, cfg->ref_sel); + + if (cfg->bipolar) + config |= AD7173_SETUP_BIPOLAR; + + if (cfg->input_buf) + config |= AD7173_SETUP_AIN_BUF_MASK; + + ret = ad_sd_write_reg(&st->sd, AD7173_REG_SETUP(free_cfg_slot), 2, config); + if (ret) + return ret; + + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, + AD7173_FILTER_ODR0_MASK & cfg->odr); +} + +static int ad7173_config_channel(struct ad7173_state *st, int addr) +{ + struct ad7173_channel_config *cfg = &st->channels[addr].cfg; + struct ad7173_channel_config *live_cfg; + int ret; + + if (!cfg->live) { + live_cfg = ad7173_find_live_config(st, cfg); + if (live_cfg) { + cfg->cfg_slot = live_cfg->cfg_slot; + } else { + ret = ad7173_load_config(st, cfg); + if (ret) + return ret; + cfg->live = true; + } + } + + if (st->config_usage_counter == U64_MAX) + ad7173_reset_usage_cnts(st); + + st->config_usage_counter++; + st->config_cnts[cfg->cfg_slot] = st->config_usage_counter; + + return 0; +} + +static int ad7173_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + unsigned int val; + int ret; + + ret = ad7173_config_channel(st, channel); + if (ret) + return ret; + + val = AD7173_CH_ENABLE | + FIELD_PREP(AD7173_CH_SETUP_SEL_MASK, st->channels[channel].cfg.cfg_slot) | + st->channels[channel].ain; + + return ad_sd_write_reg(&st->sd, AD7173_REG_CH(channel), 2, val); +} + +static int ad7173_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + + st->adc_mode &= ~AD7173_ADC_MODE_MODE_MASK; + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_MODE_MASK, mode); + + return ad_sd_write_reg(&st->sd, AD7173_REG_ADC_MODE, 2, st->adc_mode); +} + +static int ad7173_append_status(struct ad_sigma_delta *sd, bool append) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + unsigned int interface_mode = st->interface_mode; + int ret; + + interface_mode |= AD7173_INTERFACE_DATA_STAT_EN(append); + ret = ad_sd_write_reg(&st->sd, AD7173_REG_INTERFACE_MODE, 2, interface_mode); + if (ret) + return ret; + + st->interface_mode = interface_mode; + + return 0; +} + +static int ad7173_disable_all(struct ad_sigma_delta *sd) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + int ret; + int i; + + for (i = 0; i < st->num_channels; i++) { + ret = ad_sd_write_reg(sd, AD7173_REG_CH(i), 2, 0); + if (ret < 0) + return ret; + } + + return 0; +} + +static struct ad_sigma_delta_info ad7173_sigma_delta_info = { + .set_channel = ad7173_set_channel, + .append_status = ad7173_append_status, + .disable_all = ad7173_disable_all, + .set_mode = ad7173_set_mode, + .has_registers = true, + .addr_shift = 0, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .data_reg = AD7173_REG_DATA, +}; + +static int ad7173_setup(struct iio_dev *indio_dev) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = &st->sd.spi->dev; + u8 buf[AD7173_RESET_LENGTH]; + unsigned int id; + int ret; + + /* reset the serial interface */ + memset(buf, 0xff, AD7173_RESET_LENGTH); + ret = spi_write_then_read(st->sd.spi, buf, sizeof(buf), NULL, 0); + if (ret < 0) + return ret; + + /* datasheet recommends a delay of at least 500us after reset */ + fsleep(500); + + ret = ad_sd_read_reg(&st->sd, AD7173_REG_ID, 2, &id); + if (ret) + return ret; + + id &= AD7173_ID_MASK; + if (id != st->info->id) + dev_warn(dev, "Unexpected device id: 0x%04X, expected: 0x%04X\n", + id, st->info->id); + + st->adc_mode |= AD7173_ADC_MODE_SING_CYC; + st->interface_mode = 0x0; + + st->config_usage_counter = 0; + st->config_cnts = devm_kcalloc(dev, st->info->num_configs, + sizeof(*st->config_cnts), GFP_KERNEL); + if (!st->config_cnts) + return -ENOMEM; + + /* All channels are enabled by default after a reset */ + return ad7173_disable_all(&st->sd); +} + +static unsigned int ad7173_get_ref_voltage_milli(struct ad7173_state *st, + u8 reference_select) +{ + int vref; + + switch (reference_select) { + case AD7173_SETUP_REF_SEL_EXT_REF: + vref = regulator_get_voltage(st->regulators[0].consumer); + break; + + case AD7173_SETUP_REF_SEL_EXT_REF2: + vref = regulator_get_voltage(st->regulators[1].consumer); + break; + + case AD7173_SETUP_REF_SEL_INT_REF: + vref = AD7173_VOLTAGE_INT_REF_uV; + break; + + case AD7173_SETUP_REF_SEL_AVDD1_AVSS: + vref = regulator_get_voltage(st->regulators[2].consumer); + break; + + default: + return -EINVAL; + } + + if (vref < 0) + return vref; + + return vref / (MICRO / MILLI); +} + +static int ad7173_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *ch = &st->channels[chan->address]; + unsigned int reg; + u64 temp; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = ad_sigma_delta_single_conversion(indio_dev, chan, val); + if (ret < 0) + return ret; + + /* disable channel after single conversion */ + ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(chan->address), 2, 0); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + temp = AD7173_VOLTAGE_INT_REF_uV * MILLI; + temp /= AD7173_TEMP_SENSIIVITY_uV_per_C; + *val = temp; + *val2 = chan->scan_type.realbits; + } else { + *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel); + *val2 = chan->scan_type.realbits - !!(ch->cfg.bipolar); + } + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + /* 0 Kelvin -> raw sample */ + temp = -ABSOLUTE_ZERO_MILLICELSIUS; + temp *= AD7173_TEMP_SENSIIVITY_uV_per_C; + temp <<= chan->scan_type.realbits; + temp = DIV_U64_ROUND_CLOSEST(temp, + AD7173_VOLTAGE_INT_REF_uV * + MILLI); + *val = -temp; + } else { + *val = -BIT(chan->scan_type.realbits - 1); + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + reg = st->channels[chan->address].cfg.odr; + + *val = st->info->sinc5_data_rates[reg] / MILLI; + *val2 = (st->info->sinc5_data_rates[reg] % MILLI) * (MICRO / MILLI); + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int ad7173_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel_config *cfg; + unsigned int freq, i, reg; + int ret; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + freq = val * MILLI + val2 / MILLI; + for (i = 0; i < st->info->num_sinc5_data_rates - 1; i++) + if (freq >= st->info->sinc5_data_rates[i]) + break; + + cfg = &st->channels[chan->address].cfg; + cfg->odr = i; + + if (!cfg->live) + break; + + ret = ad_sd_read_reg(&st->sd, AD7173_REG_FILTER(cfg->cfg_slot), 2, ®); + if (ret) + break; + reg &= ~AD7173_FILTER_ODR0_MASK; + reg |= FIELD_PREP(AD7173_FILTER_ODR0_MASK, i); + ret = ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(cfg->cfg_slot), 2, reg); + break; + + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int ad7173_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7173_state *st = iio_priv(indio_dev); + int i, ret; + + for (i = 0; i < indio_dev->num_channels; i++) { + if (test_bit(i, scan_mask)) + ret = ad7173_set_channel(&st->sd, i); + else + ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(i), 2, 0); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ad7173_debug_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad7173_state *st = iio_priv(indio_dev); + u8 reg_size; + + if (reg == AD7173_REG_COMMS) + reg_size = 1; + else if (reg == AD7173_REG_CRC || reg == AD7173_REG_DATA || + reg >= AD7173_REG_OFFSET(0)) + reg_size = 3; + else + reg_size = 2; + + if (readval) + return ad_sd_read_reg(&st->sd, reg, reg_size, readval); + + return ad_sd_write_reg(&st->sd, reg, reg_size, writeval); +} + +static const struct iio_info ad7173_info = { + .read_raw = &ad7173_read_raw, + .write_raw = &ad7173_write_raw, + .debugfs_reg_access = &ad7173_debug_reg_access, + .validate_trigger = ad_sd_validate_trigger, + .update_scan_mode = ad7173_update_scan_mode, +}; + +static const struct iio_chan_spec ad7173_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, +}; + +static const struct iio_chan_spec ad7173_temp_iio_channel_template = { + .type = IIO_TEMP, + .indexed = 1, + .channel = AD7173_AIN_TEMP_POS, + .channel2 = AD7173_AIN_TEMP_NEG, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, +}; + +static void ad7173_disable_regulators(void *data) +{ + struct ad7173_state *st = data; + + regulator_bulk_disable(ARRAY_SIZE(st->regulators), st->regulators); +} + +static void ad7173_clk_disable_unprepare(void *clk) +{ + clk_disable_unprepare(clk); +} + +static unsigned long ad7173_sel_clk(struct ad7173_state *st, + unsigned int clk_sel) +{ + int ret; + + st->adc_mode &= !AD7173_ADC_MODE_CLOCKSEL_MASK; + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, clk_sel); + ret = ad_sd_write_reg(&st->sd, AD7173_REG_ADC_MODE, 0x2, st->adc_mode); + + return ret; +} + +static unsigned long ad7173_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + + return st->info->clock / HZ_PER_KHZ; +} + +static int ad7173_clk_output_is_enabled(struct clk_hw *hw) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + u32 clk_sel; + + clk_sel = FIELD_GET(AD7173_ADC_MODE_CLOCKSEL_MASK, st->adc_mode); + return clk_sel == AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT; +} + +static int ad7173_clk_output_prepare(struct clk_hw *hw) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + + return ad7173_sel_clk(st, AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT); +} + +static void ad7173_clk_output_unprepare(struct clk_hw *hw) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + + ad7173_sel_clk(st, AD7173_ADC_MODE_CLOCKSEL_INT); +} + +static const struct clk_ops ad7173_int_clk_ops = { + .recalc_rate = ad7173_clk_recalc_rate, + .is_enabled = ad7173_clk_output_is_enabled, + .prepare = ad7173_clk_output_prepare, + .unprepare = ad7173_clk_output_unprepare, +}; + +static int ad7173_register_clk_provider(struct iio_dev *indio_dev) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct clk_init_data init = {}; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + init.name = fwnode_get_name(fwnode); + init.ops = &ad7173_int_clk_ops; + + st->int_clk_hw.init = &init; + ret = devm_clk_hw_register(dev, &st->int_clk_hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &st->int_clk_hw); +} + +static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) +{ + struct ad7173_channel *chans_st_arr, *chan_st_priv; + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct iio_chan_spec *chan_arr, *chan; + unsigned int ain[2], chan_index = 0; + struct fwnode_handle *child; + int ref_sel, ret; + + chan_arr = devm_kcalloc(dev, sizeof(*indio_dev->channels), + st->num_channels, GFP_KERNEL); + if (!chan_arr) + return -ENOMEM; + + chans_st_arr = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels), + GFP_KERNEL); + if (!chans_st_arr) + return -ENOMEM; + + indio_dev->channels = chan_arr; + st->channels = chans_st_arr; + + if (st->info->has_temp) { + chan_arr[chan_index] = ad7173_temp_iio_channel_template; + chan_st_priv = &chans_st_arr[chan_index]; + chan_st_priv->ain = + AD7173_CH_ADDRESS(chan_arr[chan_index].channel, + chan_arr[chan_index].channel2); + chan_st_priv->cfg.bipolar = false; + chan_st_priv->cfg.input_buf = true; + chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF; + st->adc_mode |= AD7173_ADC_MODE_REF_EN; + + chan_index++; + } + + device_for_each_child_node(dev, child) { + chan = &chan_arr[chan_index]; + chan_st_priv = &chans_st_arr[chan_index]; + ret = fwnode_property_read_u32_array(child, "diff-channels", + ain, ARRAY_SIZE(ain)); + if (ret) { + fwnode_handle_put(child); + return ret; + } + + if (ain[0] >= st->info->num_inputs || + ain[1] >= st->info->num_inputs) { + fwnode_handle_put(child); + return dev_err_probe(dev, -EINVAL, + "Input pin number out of range for pair (%d %d).\n", + ain[0], ain[1]); + } + + ret = fwnode_property_match_property_string(child, + "adi,reference-select", + ad7173_ref_sel_str, + ARRAY_SIZE(ad7173_ref_sel_str)); + if (ret < 0) + ref_sel = AD7173_SETUP_REF_SEL_INT_REF; + else + ref_sel = ret; + + if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && + st->info->id != AD7173_ID) { + fwnode_handle_put(child); + return dev_err_probe(dev, -EINVAL, + "External reference 2 is only available on ad7173-8\n"); + } + + ret = ad7173_get_ref_voltage_milli(st, ref_sel); + if (ret < 0) { + fwnode_handle_put(child); + return dev_err_probe(dev, ret, + "Cannot use reference %u\n", ref_sel); + } + if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF) + st->adc_mode |= AD7173_ADC_MODE_REF_EN; + chan_st_priv->cfg.ref_sel = ref_sel; + + *chan = ad7173_channel_template; + chan->address = chan_index; + chan->scan_index = chan_index; + chan->channel = ain[0]; + chan->channel2 = ain[1]; + chan->differential = true; + + chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]); + chan_st_priv->chan_reg = chan_index; + chan_st_priv->cfg.input_buf = true; + chan_st_priv->cfg.odr = 0; + + chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar"); + if (chan_st_priv->cfg.bipolar) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET); + + chan_index++; + } + return 0; +} + +static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + unsigned int num_channels; + int ret; + + st->regulators[0].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_EXT_REF]; + st->regulators[1].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_EXT_REF2]; + st->regulators[2].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_AVDD1_AVSS]; + + /* + * If a regulator is not available, it will be set to a dummy regulator. + * Each channel reference is checked with regulator_get_voltage() before + * setting attributes so if any channel uses a dummy supply the driver + * probe will fail. + */ + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st->regulators), + st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(st->regulators), st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + ret = devm_add_action_or_reset(dev, ad7173_disable_regulators, st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add regulators disable action\n"); + + ret = device_property_match_property_string(dev, "clock-names", + ad7173_clk_sel, + ARRAY_SIZE(ad7173_clk_sel)); + if (ret < 0) { + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, + AD7173_ADC_MODE_CLOCKSEL_INT); + ad7173_register_clk_provider(indio_dev); + } else { + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, + AD7173_ADC_MODE_CLOCKSEL_EXT + ret); + st->ext_clk = devm_clk_get(dev, ad7173_clk_sel[ret]); + if (IS_ERR(st->ext_clk)) + return dev_err_probe(dev, PTR_ERR(st->ext_clk), + "Failed to get external clock\n"); + + ret = clk_prepare_enable(st->ext_clk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable external clock\n"); + + ret = devm_add_action_or_reset(dev, ad7173_clk_disable_unprepare, + st->ext_clk); + if (ret) + return ret; + } + + ret = fwnode_irq_get_byname(dev_fwnode(dev), "rdy"); + if (ret < 0) + return dev_err_probe(dev, ret, "Interrupt 'rdy' is required\n"); + + ad7173_sigma_delta_info.irq_line = ret; + + num_channels = device_get_child_node_count(dev); + + if (st->info->has_temp) + num_channels++; + + if (num_channels == 0) + return dev_err_probe(dev, -ENODATA, "No channels specified\n"); + indio_dev->num_channels = num_channels; + st->num_channels = num_channels; + + return ad7173_fw_parse_channel_config(indio_dev); +} + +static int ad7173_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7173_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + ida_init(&st->cfg_slots_status); + ret = devm_add_action_or_reset(dev, ad7173_ida_destroy, st); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7173_info; + + spi->mode = SPI_MODE_3; + spi_setup(spi); + + ad7173_sigma_delta_info.num_slots = st->info->num_configs; + ret = ad_sd_init(&st->sd, indio_dev, spi, &ad7173_sigma_delta_info); + if (ret) + return ret; + + ret = ad7173_fw_parse_device_config(indio_dev); + if (ret) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + ret = ad7173_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_GPIOLIB)) + return ad7173_gpio_init(st); + + return 0; +} + +static const struct of_device_id ad7173_of_match[] = { + { .compatible = "adi,ad7172-2", + .data = &ad7173_device_info[ID_AD7172_2]}, + { .compatible = "adi,ad7173-8", + .data = &ad7173_device_info[ID_AD7173_8]}, + { .compatible = "adi,ad7175-2", + .data = &ad7173_device_info[ID_AD7175_2]}, + { .compatible = "adi,ad7176-2", + .data = &ad7173_device_info[ID_AD7176_2]}, + { } +}; +MODULE_DEVICE_TABLE(of, ad7173_of_match); + +static const struct spi_device_id ad7173_id_table[] = { + { "ad7172-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_2]}, + { "ad7173-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7173_8]}, + { "ad7175-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7175_2]}, + { "ad7176-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7176_2]}, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7173_id_table); + +static struct spi_driver ad7173_driver = { + .driver = { + .name = "ad7173", + .of_match_table = ad7173_of_match, + }, + .probe = ad7173_probe, + .id_table = ad7173_id_table, +}; +module_spi_driver(ad7173_driver); + +MODULE_IMPORT_NS(IIO_AD_SIGMA_DELTA); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_AUTHOR("Dumitru Ceclan "); +MODULE_DESCRIPTION("Analog Devices AD7172/AD7173/AD7175/AD7176 ADC driver"); +MODULE_LICENSE("GPL"); From ba7352d019e7814fec475f429207a70b54e66e11 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 3 Mar 2024 23:34:40 +0100 Subject: [PATCH 022/108] io: light: st_uvis25: drop casting to void in dev_set_drvdata The C standard specifies that there is no need to cast from a pointer to void [1]. Therefore, it can be safely dropped. [1] C Standard Committee: https://c0x.shape-of-code.com/6.3.2.3.html Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240303-void_in_dev_set_drvdata-v1-2-ae39027d740b@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/light/st_uvis25_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c index 50f95c5d2060..d4e17079b2f4 100644 --- a/drivers/iio/light/st_uvis25_core.c +++ b/drivers/iio/light/st_uvis25_core.c @@ -291,7 +291,7 @@ int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap) if (!iio_dev) return -ENOMEM; - dev_set_drvdata(dev, (void *)iio_dev); + dev_set_drvdata(dev, iio_dev); hw = iio_priv(iio_dev); hw->irq = irq; From 8c5b0ea6179d40dac239f772da706c2b779d29aa Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 3 Mar 2024 23:34:41 +0100 Subject: [PATCH 023/108] iio: humidity: hts211: drop casting to void in dev_set_drvdata The C standard specifies that there is no need to cast from a pointer to void [1]. Therefore, it can be safely dropped. [1] C Standard Committee: https://c0x.shape-of-code.com/6.3.2.3.html Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240303-void_in_dev_set_drvdata-v1-3-ae39027d740b@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/hts221_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c index 2a413da87b76..87627d116eff 100644 --- a/drivers/iio/humidity/hts221_core.c +++ b/drivers/iio/humidity/hts221_core.c @@ -573,7 +573,7 @@ int hts221_probe(struct device *dev, int irq, const char *name, if (!iio_dev) return -ENOMEM; - dev_set_drvdata(dev, (void *)iio_dev); + dev_set_drvdata(dev, iio_dev); hw = iio_priv(iio_dev); hw->name = name; From cb98410db73f2584c8d8437472a7d4727f7c53fc Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 3 Mar 2024 23:34:42 +0100 Subject: [PATCH 024/108] iio: imu: st_lsm6dsx: drop casting to void in dev_set_drvdata The C standard specifies that there is no need to cast from a pointer to void [1]. Therefore, it can be safely dropped. [1] C Standard Committee: https://c0x.shape-of-code.com/6.3.2.3.html Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240303-void_in_dev_set_drvdata-v1-4-ae39027d740b@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 0716986f9812..937ff9c5a74c 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -2726,7 +2726,7 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, if (!hw) return -ENOMEM; - dev_set_drvdata(dev, (void *)hw); + dev_set_drvdata(dev, hw); mutex_init(&hw->fifo_lock); mutex_init(&hw->conf_lock); From f0245ab389330cbc1d187e358a5b890d9f5383db Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 4 Mar 2024 16:04:32 +0200 Subject: [PATCH 025/108] iio: core: Leave private pointer NULL when no private data supplied In iio_device_alloc() when size of the private data is 0, the private pointer is calculated to point behind the valid data. Leave it NULL when no private data supplied. Fixes: 6d4ebd565d15 ("iio: core: wrap IIO device into an iio_dev_opaque object") Signed-off-by: Andy Shevchenko Reviewed-by: David Lechner Link: https://lore.kernel.org/r/20240304140650.977784-2-andriy.shevchenko@linux.intel.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 4302093b92c7..8684ba246969 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1654,8 +1654,10 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) return NULL; indio_dev = &iio_dev_opaque->indio_dev; - indio_dev->priv = (char *)iio_dev_opaque + - ALIGN(sizeof(struct iio_dev_opaque), IIO_DMA_MINALIGN); + + if (sizeof_priv) + indio_dev->priv = (char *)iio_dev_opaque + + ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN); indio_dev->dev.parent = parent; indio_dev->dev.type = &iio_device_type; From 5c4e411566dfd76202c1af0d4ca04438e544ca28 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 4 Mar 2024 16:04:33 +0200 Subject: [PATCH 026/108] iio: core: Calculate alloc_size only once in iio_device_alloc() No need to rewrite the value, instead use 'else' branch. This will also help further refactoring the code later on. Signed-off-by: Andy Shevchenko Reviewed-by: David Lechner Link: https://lore.kernel.org/r/20240304140650.977784-3-andriy.shevchenko@linux.intel.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 8684ba246969..c7ad88932015 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1643,11 +1643,10 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) struct iio_dev *indio_dev; size_t alloc_size; - alloc_size = sizeof(struct iio_dev_opaque); - if (sizeof_priv) { - alloc_size = ALIGN(alloc_size, IIO_DMA_MINALIGN); - alloc_size += sizeof_priv; - } + if (sizeof_priv) + alloc_size = ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN) + sizeof_priv; + else + alloc_size = sizeof(*iio_dev_opaque); iio_dev_opaque = kzalloc(alloc_size, GFP_KERNEL); if (!iio_dev_opaque) From 9278524c48658a0ddccf69a0c185129951d15853 Mon Sep 17 00:00:00 2001 From: Subhajit Ghosh Date: Sat, 9 Mar 2024 21:20:27 +1030 Subject: [PATCH 027/108] dt-bindings: iio: light: Merge APDS9300 and APDS9960 schemas Merge very similar schemas for APDS9300 and APDS9960. Suggested-by: Krzysztof Kozlowski Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/all/4e785d2e-d310-4592-a75a-13549938dcef@linaro.org/ Signed-off-by: Subhajit Ghosh Link: https://lore.kernel.org/r/20240309105031.10313-2-subhajit.ghosh@tweaklogic.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/light/avago,apds9300.yaml | 11 +++-- .../bindings/iio/light/avago,apds9960.yaml | 44 ------------------- 2 files changed, 7 insertions(+), 48 deletions(-) delete mode 100644 Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml diff --git a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml index 206af44f2c43..c610780346e8 100644 --- a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml +++ b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml @@ -4,17 +4,20 @@ $id: http://devicetree.org/schemas/iio/light/avago,apds9300.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Avago APDS9300 ambient light sensor +title: Avago Gesture/RGB/ALS/Proximity sensors maintainers: - - Jonathan Cameron + - Subhajit Ghosh description: | - Datasheet at https://www.avagotech.com/docs/AV02-1077EN + Datasheet: https://www.avagotech.com/docs/AV02-1077EN + Datasheet: https://www.avagotech.com/docs/AV02-4191EN properties: compatible: - const: avago,apds9300 + enum: + - avago,apds9300 + - avago,apds9960 reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml b/Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml deleted file mode 100644 index f06e0fda5629..000000000000 --- a/Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/iio/light/avago,apds9960.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Avago APDS9960 gesture/RGB/ALS/proximity sensor - -maintainers: - - Matt Ranostay - -description: | - Datasheet at https://www.avagotech.com/docs/AV02-4191EN - -properties: - compatible: - const: avago,apds9960 - - reg: - maxItems: 1 - - interrupts: - maxItems: 1 - -additionalProperties: false - -required: - - compatible - - reg - -examples: - - | - i2c { - #address-cells = <1>; - #size-cells = <0>; - - light-sensor@39 { - compatible = "avago,apds9960"; - reg = <0x39>; - interrupt-parent = <&gpio1>; - interrupts = <16 1>; - }; - }; -... From 59ee18822a24ebbf23327895436b705e39c857fd Mon Sep 17 00:00:00 2001 From: Subhajit Ghosh Date: Sat, 9 Mar 2024 21:20:28 +1030 Subject: [PATCH 028/108] dt-bindings: iio: light: adps9300: Add missing vdd-supply All devices covered by the binding have a vdd supply. Acked-by: Conor Dooley Signed-off-by: Subhajit Ghosh Link: https://lore.kernel.org/r/20240309105031.10313-3-subhajit.ghosh@tweaklogic.com Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/light/avago,apds9300.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml index c610780346e8..a328c8a1daef 100644 --- a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml +++ b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml @@ -25,6 +25,8 @@ properties: interrupts: maxItems: 1 + vdd-supply: true + additionalProperties: false required: @@ -42,6 +44,7 @@ examples: reg = <0x39>; interrupt-parent = <&gpio2>; interrupts = <29 8>; + vdd-supply = <®ulator_3v3>; }; }; ... From 2f8608f71bfecd9aaeb571ea688e66e4ba225d12 Mon Sep 17 00:00:00 2001 From: Subhajit Ghosh Date: Sat, 9 Mar 2024 21:20:29 +1030 Subject: [PATCH 029/108] dt-bindings: iio: light: adps9300: Update interrupt definitions Include irq.h and irq level macro in the example for readability Acked-by: Conor Dooley Signed-off-by: Subhajit Ghosh Link: https://lore.kernel.org/r/20240309105031.10313-4-subhajit.ghosh@tweaklogic.com Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/light/avago,apds9300.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml index a328c8a1daef..e07a074f6acf 100644 --- a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml +++ b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml @@ -35,6 +35,8 @@ required: examples: - | + #include + i2c { #address-cells = <1>; #size-cells = <0>; @@ -43,7 +45,7 @@ examples: compatible = "avago,apds9300"; reg = <0x39>; interrupt-parent = <&gpio2>; - interrupts = <29 8>; + interrupts = <29 IRQ_TYPE_LEVEL_LOW>; vdd-supply = <®ulator_3v3>; }; }; From f31f1f27b0e37badd68b68aa8b426108520cc814 Mon Sep 17 00:00:00 2001 From: Subhajit Ghosh Date: Sat, 9 Mar 2024 21:20:30 +1030 Subject: [PATCH 030/108] dt-bindings: iio: light: Avago APDS9306 Extend avago,apds9300.yaml schema file to support apds9306 device. Acked-by: Conor Dooley Signed-off-by: Subhajit Ghosh Link: https://lore.kernel.org/r/20240309105031.10313-5-subhajit.ghosh@tweaklogic.com Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml index e07a074f6acf..b750096530bc 100644 --- a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml +++ b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml @@ -12,11 +12,13 @@ maintainers: description: | Datasheet: https://www.avagotech.com/docs/AV02-1077EN Datasheet: https://www.avagotech.com/docs/AV02-4191EN + Datasheet: https://www.avagotech.com/docs/AV02-4755EN properties: compatible: enum: - avago,apds9300 + - avago,apds9306 - avago,apds9960 reg: From 620d1e6c7a3fcc54c641f091e07b9040f13def19 Mon Sep 17 00:00:00 2001 From: Subhajit Ghosh Date: Sat, 9 Mar 2024 21:20:31 +1030 Subject: [PATCH 031/108] iio: light: Add support for APDS9306 Light Sensor Driver support for Avago (Broadcom) APDS9306 Ambient Light Sensor. It has two channels - ALS and CLEAR. The ALS (Ambient Light Sensor) channel approximates the response of the human-eye providing direct read out where the output count is proportional to ambient light levels. It is internally temperature compensated and rejects 50Hz and 60Hz flicker caused by artificial light sources. Hardware interrupt configuration is optional. It is a low power device with 20 bit resolution and has configurable adaptive interrupt mode and interrupt persistence mode. The device also features inbuilt hardware gain, multiple integration time selection options and sampling frequency selection options. This driver also uses the IIO GTS (Gain Time Scale) Helpers Namespace for Scales, Gains and Integration time implementation. Signed-off-by: Subhajit Ghosh Link: https://lore.kernel.org/r/20240309105031.10313-6-subhajit.ghosh@tweaklogic.com Signed-off-by: Jonathan Cameron --- drivers/iio/light/Kconfig | 12 + drivers/iio/light/Makefile | 1 + drivers/iio/light/apds9306.c | 1355 ++++++++++++++++++++++++++++++++++ 3 files changed, 1368 insertions(+) create mode 100644 drivers/iio/light/apds9306.c diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index fd5a9879a582..9a587d403118 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -73,6 +73,18 @@ config APDS9300 To compile this driver as a module, choose M here: the module will be called apds9300. +config APDS9306 + tristate "Avago APDS9306 Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + select IIO_GTS_HELPER + help + If you say Y or M here, you get support for Avago APDS9306 + Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + apds9306. + config APDS9960 tristate "Avago APDS9960 gesture/RGB/ALS/proximity sensor" select REGMAP_I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 2e5fdb33e0e9..a30f906e91ba 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_ADUX1020) += adux1020.o obj-$(CONFIG_AL3010) += al3010.o obj-$(CONFIG_AL3320A) += al3320a.o obj-$(CONFIG_APDS9300) += apds9300.o +obj-$(CONFIG_APDS9306) += apds9306.o obj-$(CONFIG_APDS9960) += apds9960.o obj-$(CONFIG_AS73211) += as73211.o obj-$(CONFIG_BH1750) += bh1750.o diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c new file mode 100644 index 000000000000..4d8490602cd7 --- /dev/null +++ b/drivers/iio/light/apds9306.c @@ -0,0 +1,1355 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * APDS-9306/APDS-9306-065 Ambient Light Sensor + * I2C Address: 0x52 + * Datasheet: https://docs.broadcom.com/doc/AV02-4755EN + * + * Copyright (C) 2024 Subhajit Ghosh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define APDS9306_MAIN_CTRL_REG 0x00 +#define APDS9306_ALS_MEAS_RATE_REG 0x04 +#define APDS9306_ALS_GAIN_REG 0x05 +#define APDS9306_PART_ID_REG 0x06 +#define APDS9306_MAIN_STATUS_REG 0x07 +#define APDS9306_CLEAR_DATA_0_REG 0x0A +#define APDS9306_CLEAR_DATA_1_REG 0x0B +#define APDS9306_CLEAR_DATA_2_REG 0x0C +#define APDS9306_ALS_DATA_0_REG 0x0D +#define APDS9306_ALS_DATA_1_REG 0x0E +#define APDS9306_ALS_DATA_2_REG 0x0F +#define APDS9306_INT_CFG_REG 0x19 +#define APDS9306_INT_PERSISTENCE_REG 0x1A +#define APDS9306_ALS_THRES_UP_0_REG 0x21 +#define APDS9306_ALS_THRES_UP_1_REG 0x22 +#define APDS9306_ALS_THRES_UP_2_REG 0x23 +#define APDS9306_ALS_THRES_LOW_0_REG 0x24 +#define APDS9306_ALS_THRES_LOW_1_REG 0x25 +#define APDS9306_ALS_THRES_LOW_2_REG 0x26 +#define APDS9306_ALS_THRES_VAR_REG 0x27 + +#define APDS9306_ALS_INT_STAT_MASK BIT(4) +#define APDS9306_ALS_DATA_STAT_MASK BIT(3) + +#define APDS9306_ALS_THRES_VAL_MAX (BIT(20) - 1) +#define APDS9306_ALS_THRES_VAR_VAL_MAX (BIT(3) - 1) +#define APDS9306_ALS_PERSIST_VAL_MAX (BIT(4) - 1) +#define APDS9306_ALS_READ_DATA_DELAY_US (20 * USEC_PER_MSEC) +#define APDS9306_NUM_REPEAT_RATES 7 +#define APDS9306_INT_SRC_CLEAR 0 +#define APDS9306_INT_SRC_ALS 1 +#define APDS9306_SAMP_FREQ_10HZ 0 + +/** + * struct part_id_gts_multiplier - Part no. and corresponding gts multiplier + * + * GTS (Gain Time Scale) are helper functions for Light sensors which along + * with hardware gains also has gains associated with Integration times. + * + * There are two variants of the device with slightly different characteristics, + * they have same ADC count for different Lux levels as mentioned in the + * datasheet. This multiplier array is used to store the derived Lux per count + * value for the two variants to be used by the GTS helper functions. + * + * @part_id: Part ID of the device + * @max_scale_int: Multiplier for iio_init_iio_gts() + * @max_scale_nano: Multiplier for iio_init_iio_gts() + */ +struct part_id_gts_multiplier { + int part_id; + int max_scale_int; + int max_scale_nano; +}; + +/* + * As per the datasheet, at HW Gain = 3x, Integration time 100mS (32x), + * typical 2000 ADC counts are observed for 49.8 uW per sq cm (340.134 lux) + * for apds9306 and 43 uW per sq cm (293.69 lux) for apds9306-065. + * Assuming lux per count is linear across all integration time ranges. + * + * Lux = (raw + offset) * scale; offset can be any value by userspace. + * HG = Hardware Gain; ITG = Gain by changing integration time. + * Scale table by IIO GTS Helpers = (1 / HG) * (1 / ITG) * Multiplier. + * + * The Lux values provided in the datasheet are at ITG=32x and HG=3x, + * at typical 2000 count for both variants of the device. + * + * Lux per ADC count at 3x and 32x for apds9306 = 340.134 / 2000 + * Lux per ADC count at 3x and 32x for apds9306-065 = 293.69 / 2000 + * + * The Multiplier for the scale table provided to userspace: + * IIO GTS scale Multiplier for apds9306 = (340.134 / 2000) * 32 * 3 = 16.326432 + * and for apds9306-065 = (293.69 / 2000) * 32 * 3 = 14.09712 + */ +static const struct part_id_gts_multiplier apds9306_gts_mul[] = { + { + .part_id = 0xB1, + .max_scale_int = 16, + .max_scale_nano = 3264320, + }, { + .part_id = 0xB3, + .max_scale_int = 14, + .max_scale_nano = 9712000, + }, +}; + +static const int apds9306_repeat_rate_freq[APDS9306_NUM_REPEAT_RATES][2] = { + { 40, 0 }, + { 20, 0 }, + { 10, 0 }, + { 5, 0 }, + { 2, 0 }, + { 1, 0 }, + { 0, 500000 }, +}; + +static const int apds9306_repeat_rate_period[APDS9306_NUM_REPEAT_RATES] = { + 25000, 50000, 100000, 200000, 500000, 1000000, 2000000, +}; + +/** + * struct apds9306_regfields - apds9306 regmap fields definitions + * + * @sw_reset: Software reset regfield + * @en: Enable regfield + * @intg_time: Resolution regfield + * @repeat_rate: Measurement Rate regfield + * @gain: Hardware gain regfield + * @int_src: Interrupt channel regfield + * @int_thresh_var_en: Interrupt variance threshold regfield + * @int_en: Interrupt enable regfield + * @int_persist_val: Interrupt persistence regfield + * @int_thresh_var_val: Interrupt threshold variance value regfield + */ +struct apds9306_regfields { + struct regmap_field *sw_reset; + struct regmap_field *en; + struct regmap_field *intg_time; + struct regmap_field *repeat_rate; + struct regmap_field *gain; + struct regmap_field *int_src; + struct regmap_field *int_thresh_var_en; + struct regmap_field *int_en; + struct regmap_field *int_persist_val; + struct regmap_field *int_thresh_var_val; +}; + +/** + * struct apds9306_data - apds9306 private data and registers definitions + * + * @dev: Pointer to the device structure + * @gts: IIO Gain Time Scale structure + * @mutex: Lock for protecting adc reads, device settings changes where + * some calculations are required before or after setting or + * getting the raw settings values from regmap writes or reads + * respectively. + * @regmap: Regmap structure pointer + * @rf: Regmap register fields structure + * @nlux_per_count: Nano lux per ADC count for a particular model + * @read_data_available: Flag set by IRQ handler for ADC data available + */ +struct apds9306_data { + struct device *dev; + struct iio_gts gts; + + struct mutex mutex; + + struct regmap *regmap; + struct apds9306_regfields rf; + + int nlux_per_count; + int read_data_available; +}; + +/* + * Available scales with gain 1x - 18x, timings 3.125, 25, 50, 100, 200, 400 ms + * Time impacts to gain: 1x, 8x, 16x, 32x, 64x, 128x + */ +#define APDS9306_GSEL_1X 0x00 +#define APDS9306_GSEL_3X 0x01 +#define APDS9306_GSEL_6X 0x02 +#define APDS9306_GSEL_9X 0x03 +#define APDS9306_GSEL_18X 0x04 + +static const struct iio_gain_sel_pair apds9306_gains[] = { + GAIN_SCALE_GAIN(1, APDS9306_GSEL_1X), + GAIN_SCALE_GAIN(3, APDS9306_GSEL_3X), + GAIN_SCALE_GAIN(6, APDS9306_GSEL_6X), + GAIN_SCALE_GAIN(9, APDS9306_GSEL_9X), + GAIN_SCALE_GAIN(18, APDS9306_GSEL_18X), +}; + +#define APDS9306_MEAS_MODE_400MS 0x00 +#define APDS9306_MEAS_MODE_200MS 0x01 +#define APDS9306_MEAS_MODE_100MS 0x02 +#define APDS9306_MEAS_MODE_50MS 0x03 +#define APDS9306_MEAS_MODE_25MS 0x04 +#define APDS9306_MEAS_MODE_3125US 0x05 + +static const struct iio_itime_sel_mul apds9306_itimes[] = { + GAIN_SCALE_ITIME_US(400000, APDS9306_MEAS_MODE_400MS, BIT(7)), + GAIN_SCALE_ITIME_US(200000, APDS9306_MEAS_MODE_200MS, BIT(6)), + GAIN_SCALE_ITIME_US(100000, APDS9306_MEAS_MODE_100MS, BIT(5)), + GAIN_SCALE_ITIME_US(50000, APDS9306_MEAS_MODE_50MS, BIT(4)), + GAIN_SCALE_ITIME_US(25000, APDS9306_MEAS_MODE_25MS, BIT(3)), + GAIN_SCALE_ITIME_US(3125, APDS9306_MEAS_MODE_3125US, BIT(0)), +}; + +static const struct iio_event_spec apds9306_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH_ADAPTIVE, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec apds9306_channels_with_events[] = { + { + .type = IIO_LIGHT, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .event_spec = apds9306_event_spec, + .num_event_specs = ARRAY_SIZE(apds9306_event_spec), + }, { + .type = IIO_INTENSITY, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = 1, + .event_spec = apds9306_event_spec, + .num_event_specs = ARRAY_SIZE(apds9306_event_spec), + }, +}; + +static const struct iio_chan_spec apds9306_channels_without_events[] = { + { + .type = IIO_LIGHT, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_INTENSITY, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = 1, + .channel2 = IIO_MOD_LIGHT_CLEAR, + }, +}; + +/* INT_PERSISTENCE available */ +static IIO_CONST_ATTR(thresh_either_period_available, "[0 1 15]"); + +/* ALS_THRESH_VAR available */ +static IIO_CONST_ATTR(thresh_adaptive_either_values_available, "[0 1 7]"); + +static struct attribute *apds9306_event_attributes[] = { + &iio_const_attr_thresh_either_period_available.dev_attr.attr, + &iio_const_attr_thresh_adaptive_either_values_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group apds9306_event_attr_group = { + .attrs = apds9306_event_attributes, +}; + +static const struct regmap_range apds9306_readable_ranges[] = { + regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_THRES_VAR_REG) +}; + +static const struct regmap_range apds9306_writable_ranges[] = { + regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_GAIN_REG), + regmap_reg_range(APDS9306_INT_CFG_REG, APDS9306_ALS_THRES_VAR_REG) +}; + +static const struct regmap_range apds9306_volatile_ranges[] = { + regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG), + regmap_reg_range(APDS9306_CLEAR_DATA_0_REG, APDS9306_ALS_DATA_2_REG) +}; + +static const struct regmap_range apds9306_precious_ranges[] = { + regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG) +}; + +static const struct regmap_access_table apds9306_readable_table = { + .yes_ranges = apds9306_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_readable_ranges) +}; + +static const struct regmap_access_table apds9306_writable_table = { + .yes_ranges = apds9306_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_writable_ranges) +}; + +static const struct regmap_access_table apds9306_volatile_table = { + .yes_ranges = apds9306_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_volatile_ranges) +}; + +static const struct regmap_access_table apds9306_precious_table = { + .yes_ranges = apds9306_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_precious_ranges) +}; + +static const struct regmap_config apds9306_regmap = { + .name = "apds9306_regmap", + .reg_bits = 8, + .val_bits = 8, + .rd_table = &apds9306_readable_table, + .wr_table = &apds9306_writable_table, + .volatile_table = &apds9306_volatile_table, + .precious_table = &apds9306_precious_table, + .max_register = APDS9306_ALS_THRES_VAR_REG, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct reg_field apds9306_rf_sw_reset = + REG_FIELD(APDS9306_MAIN_CTRL_REG, 4, 4); + +static const struct reg_field apds9306_rf_en = + REG_FIELD(APDS9306_MAIN_CTRL_REG, 1, 1); + +static const struct reg_field apds9306_rf_intg_time = + REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 4, 6); + +static const struct reg_field apds9306_rf_repeat_rate = + REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 0, 2); + +static const struct reg_field apds9306_rf_gain = + REG_FIELD(APDS9306_ALS_GAIN_REG, 0, 2); + +static const struct reg_field apds9306_rf_int_src = + REG_FIELD(APDS9306_INT_CFG_REG, 4, 5); + +static const struct reg_field apds9306_rf_int_thresh_var_en = + REG_FIELD(APDS9306_INT_CFG_REG, 3, 3); + +static const struct reg_field apds9306_rf_int_en = + REG_FIELD(APDS9306_INT_CFG_REG, 2, 2); + +static const struct reg_field apds9306_rf_int_persist_val = + REG_FIELD(APDS9306_INT_PERSISTENCE_REG, 4, 7); + +static const struct reg_field apds9306_rf_int_thresh_var_val = + REG_FIELD(APDS9306_ALS_THRES_VAR_REG, 0, 2); + +static int apds9306_regfield_init(struct apds9306_data *data) +{ + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + struct regmap_field *tmp; + struct apds9306_regfields *rf = &data->rf; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_sw_reset); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->sw_reset = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_intg_time); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->intg_time = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_repeat_rate); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->repeat_rate = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_gain); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->gain = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_src); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_src = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_thresh_var_en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_persist_val); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_persist_val = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_val); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_thresh_var_val = tmp; + + return 0; +} + +static int apds9306_power_state(struct apds9306_data *data, bool state) +{ + struct apds9306_regfields *rf = &data->rf; + int ret; + + /* Reset not included as it causes ugly I2C bus error */ + if (state) { + ret = regmap_field_write(rf->en, 1); + if (ret) + return ret; + /* 5ms wake up time */ + fsleep(5000); + return 0; + } + + return regmap_field_write(rf->en, 0); +} + +static int apds9306_read_data(struct apds9306_data *data, int *val, int reg) +{ + struct device *dev = data->dev; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct apds9306_regfields *rf = &data->rf; + u64 ev_code; + int ret, delay, intg_time, intg_time_idx, repeat_rate_idx, int_src; + int status = 0; + u8 buff[3]; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret) + return ret; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + intg_time = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg_time < 0) + return intg_time; + + /* Whichever is greater - integration time period or sampling period. */ + delay = max(intg_time, apds9306_repeat_rate_period[repeat_rate_idx]); + + /* + * Clear stale data flag that might have been set by the interrupt + * handler if it got data available flag set in the status reg. + */ + data->read_data_available = 0; + + /* + * If this function runs parallel with the interrupt handler, either + * this reads and clears the status registers or the interrupt handler + * does. The interrupt handler sets a flag for read data available + * in our private structure which we read here. + */ + ret = regmap_read_poll_timeout(data->regmap, APDS9306_MAIN_STATUS_REG, + status, data->read_data_available || + (status & (APDS9306_ALS_DATA_STAT_MASK | + APDS9306_ALS_INT_STAT_MASK)), + APDS9306_ALS_READ_DATA_DELAY_US, delay * 2); + if (ret) + return ret; + + /* If we reach here before the interrupt handler we push an event */ + if ((status & APDS9306_ALS_INT_STAT_MASK)) { + if (int_src == APDS9306_INT_SRC_ALS) + ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + else + ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + } + + ret = regmap_bulk_read(data->regmap, reg, buff, sizeof(buff)); + if (ret) { + dev_err_ratelimited(dev, "read data failed\n"); + return ret; + } + + *val = get_unaligned_le24(&buff); + + pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return 0; +} + +static int apds9306_intg_time_get(struct apds9306_data *data, int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int ret, intg_time_idx; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (ret < 0) + return ret; + + *val2 = ret; + + return 0; +} + +static int apds9306_intg_time_set(struct apds9306_data *data, int val2) +{ + struct device *dev = data->dev; + struct apds9306_regfields *rf = &data->rf; + int ret, intg_old, gain_old, gain_new, gain_new_closest, intg_time_idx; + int gain_idx; + bool ok; + + if (!iio_gts_valid_time(&data->gts, val2)) { + dev_err_ratelimited(dev, "Unsupported integration time %u\n", val2); + return -EINVAL; + } + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->gain, &gain_idx); + if (ret) + return ret; + + intg_old = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (ret < 0) + return ret; + + if (intg_old == val2) + return 0; + + gain_old = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (gain_old < 0) + return gain_old; + + iio_gts_find_new_gain_by_old_gain_time(&data->gts, gain_old, intg_old, + val2, &gain_new); + + if (gain_new < 0) { + dev_err_ratelimited(dev, "Unsupported gain with time\n"); + return gain_new; + } + + gain_new_closest = iio_find_closest_gain_low(&data->gts, gain_new, &ok); + if (gain_new_closest < 0) { + gain_new_closest = iio_gts_get_min_gain(&data->gts); + if (gain_new_closest < 0) + return gain_new_closest; + } + if (!ok) + dev_dbg(dev, "Unable to find optimum gain, setting minimum"); + + ret = iio_gts_find_sel_by_int_time(&data->gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(rf->intg_time, ret); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, gain_new_closest); + if (ret < 0) + return ret; + + return regmap_field_write(rf->gain, ret); +} + +static int apds9306_sampling_freq_get(struct apds9306_data *data, int *val, + int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int ret, repeat_rate_idx; + + ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx); + if (ret) + return ret; + + if (repeat_rate_idx > ARRAY_SIZE(apds9306_repeat_rate_freq)) + return -EINVAL; + + *val = apds9306_repeat_rate_freq[repeat_rate_idx][0]; + *val2 = apds9306_repeat_rate_freq[repeat_rate_idx][1]; + + return 0; +} + +static int apds9306_sampling_freq_set(struct apds9306_data *data, int val, + int val2) +{ + struct apds9306_regfields *rf = &data->rf; + int i; + + for (i = 0; i < ARRAY_SIZE(apds9306_repeat_rate_freq); i++) { + if (apds9306_repeat_rate_freq[i][0] == val && + apds9306_repeat_rate_freq[i][1] == val2) + return regmap_field_write(rf->repeat_rate, i); + } + + return -EINVAL; +} + +static int apds9306_scale_get(struct apds9306_data *data, int *val, int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int gain, intg, ret, intg_time_idx, gain_idx; + + ret = regmap_field_read(rf->gain, &gain_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (gain < 0) + return gain; + + intg = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg < 0) + return intg; + + return iio_gts_get_scale(&data->gts, gain, intg, val, val2); +} + +static int apds9306_scale_set(struct apds9306_data *data, int val, int val2) +{ + struct apds9306_regfields *rf = &data->rf; + int i, ret, time_sel, gain_sel, intg_time_idx; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + intg_time_idx, val, val2, &gain_sel); + if (ret) { + for (i = 0; i < data->gts.num_itime; i++) { + time_sel = data->gts.itime_table[i].sel; + + if (time_sel == intg_time_idx) + continue; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + time_sel, val, val2, &gain_sel); + if (!ret) + break; + } + if (ret) + return -EINVAL; + + ret = regmap_field_write(rf->intg_time, time_sel); + if (ret) + return ret; + } + + return regmap_field_write(rf->gain, gain_sel); +} + +static int apds9306_event_period_get(struct apds9306_data *data, int *val) +{ + struct apds9306_regfields *rf = &data->rf; + int period, ret; + + ret = regmap_field_read(rf->int_persist_val, &period); + if (ret) + return ret; + + if (!in_range(period, 0, APDS9306_ALS_PERSIST_VAL_MAX)) + return -EINVAL; + + *val = period; + + return ret; +} + +static int apds9306_event_period_set(struct apds9306_data *data, int val) +{ + struct apds9306_regfields *rf = &data->rf; + + if (!in_range(val, 0, APDS9306_ALS_PERSIST_VAL_MAX)) + return -EINVAL; + + return regmap_field_write(rf->int_persist_val, val); +} + +static int apds9306_event_thresh_get(struct apds9306_data *data, int dir, + int *val) +{ + int var, ret; + u8 buff[3]; + + if (dir == IIO_EV_DIR_RISING) + var = APDS9306_ALS_THRES_UP_0_REG; + else if (dir == IIO_EV_DIR_FALLING) + var = APDS9306_ALS_THRES_LOW_0_REG; + else + return -EINVAL; + + ret = regmap_bulk_read(data->regmap, var, buff, sizeof(buff)); + if (ret) + return ret; + + *val = get_unaligned_le24(&buff); + + return 0; +} + +static int apds9306_event_thresh_set(struct apds9306_data *data, int dir, + int val) +{ + int var; + u8 buff[3]; + + if (dir == IIO_EV_DIR_RISING) + var = APDS9306_ALS_THRES_UP_0_REG; + else if (dir == IIO_EV_DIR_FALLING) + var = APDS9306_ALS_THRES_LOW_0_REG; + else + return -EINVAL; + + if (!in_range(val, 0, APDS9306_ALS_THRES_VAL_MAX)) + return -EINVAL; + + put_unaligned_le24(val, buff); + + return regmap_bulk_write(data->regmap, var, buff, sizeof(buff)); +} + +static int apds9306_event_thresh_adaptive_get(struct apds9306_data *data, int *val) +{ + struct apds9306_regfields *rf = &data->rf; + int thr_adpt, ret; + + ret = regmap_field_read(rf->int_thresh_var_val, &thr_adpt); + if (ret) + return ret; + + if (!in_range(thr_adpt, 0, APDS9306_ALS_THRES_VAR_VAL_MAX)) + return -EINVAL; + + *val = thr_adpt; + + return ret; +} + +static int apds9306_event_thresh_adaptive_set(struct apds9306_data *data, int val) +{ + struct apds9306_regfields *rf = &data->rf; + + if (!in_range(val, 0, APDS9306_ALS_THRES_VAR_VAL_MAX)) + return -EINVAL; + + return regmap_field_write(rf->int_thresh_var_val, val); +} + +static int apds9306_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + int ret, reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->channel2 == IIO_MOD_LIGHT_CLEAR) + reg = APDS9306_CLEAR_DATA_0_REG; + else + reg = APDS9306_ALS_DATA_0_REG; + /* + * Changing device parameters during adc operation, resets + * the ADC which has to avoided. + */ + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = apds9306_read_data(data, val, reg); + iio_device_release_direct_mode(indio_dev); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + ret = apds9306_intg_time_get(data, val2); + if (ret) + return ret; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = apds9306_sampling_freq_get(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + ret = apds9306_scale_get(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +}; + +static int apds9306_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(apds9306_repeat_rate_freq) * 2; + *vals = (const int *)apds9306_repeat_rate_freq; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int apds9306_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int apds9306_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val) + return -EINVAL; + return apds9306_intg_time_set(data, val2); + case IIO_CHAN_INFO_SCALE: + return apds9306_scale_set(data, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return apds9306_sampling_freq_set(data, val, val2); + default: + return -EINVAL; + } +} + +static irqreturn_t apds9306_irq_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + u64 ev_code; + int ret, status, int_src; + + /* + * The interrupt line is released and the interrupt flag is + * cleared as a result of reading the status register. All the + * status flags are cleared as a result of this read. + */ + ret = regmap_read(data->regmap, APDS9306_MAIN_STATUS_REG, &status); + if (ret < 0) { + dev_err_ratelimited(data->dev, "status reg read failed\n"); + return IRQ_HANDLED; + } + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + if ((status & APDS9306_ALS_INT_STAT_MASK)) { + if (int_src == APDS9306_INT_SRC_ALS) + ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + else + ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + } + + /* + * If a one-shot read through sysfs is underway at the same time + * as this interrupt handler is executing and a read data available + * flag was set, this flag is set to inform read_poll_timeout() + * to exit. + */ + if ((status & APDS9306_ALS_DATA_STAT_MASK)) + data->read_data_available = 1; + + return IRQ_HANDLED; +} + +static int apds9306_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct apds9306_data *data = iio_priv(indio_dev); + int ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + ret = apds9306_event_period_get(data, val); + else + ret = apds9306_event_thresh_get(data, dir, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + ret = apds9306_event_thresh_adaptive_get(data, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int apds9306_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + return apds9306_event_period_set(data, val); + + return apds9306_event_thresh_set(data, dir, val); + case IIO_EV_TYPE_THRESH_ADAPTIVE: + return apds9306_event_thresh_adaptive_set(data, val); + default: + return -EINVAL; + } +} + +static int apds9306_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + int int_en, int_src, ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: { + guard(mutex)(&data->mutex); + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + ret = regmap_field_read(rf->int_en, &int_en); + if (ret) + return ret; + + if (chan->type == IIO_LIGHT) + return int_en && (int_src == APDS9306_INT_SRC_ALS); + + if (chan->type == IIO_INTENSITY) + return int_en && (int_src == APDS9306_INT_SRC_CLEAR); + + return -EINVAL; + } + case IIO_EV_TYPE_THRESH_ADAPTIVE: + ret = regmap_field_read(rf->int_thresh_var_en, &int_en); + if (ret) + return ret; + + return int_en; + default: + return -EINVAL; + } +} + +static int apds9306_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + int ret, val; + + state = !!state; + + switch (type) { + case IIO_EV_TYPE_THRESH: { + guard(mutex)(&data->mutex); + + /* + * If interrupt is enabled, the channel is set before enabling + * the interrupt. In case of disable, no need to switch + * channels. In case of different channel is selected while + * interrupt in on, just change the channel. + */ + if (state) { + if (chan->type == IIO_LIGHT) + val = 1; + else if (chan->type == IIO_INTENSITY) + val = 0; + else + return -EINVAL; + + ret = regmap_field_write(rf->int_src, val); + if (ret) + return ret; + } + + ret = regmap_field_read(rf->int_en, &val); + if (ret) + return ret; + + if (val == state) + return 0; + + ret = regmap_field_write(rf->int_en, state); + if (ret) + return ret; + + if (state) + return pm_runtime_resume_and_get(data->dev); + + pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return 0; + } + case IIO_EV_TYPE_THRESH_ADAPTIVE: + return regmap_field_write(rf->int_thresh_var_en, state); + default: + return -EINVAL; + } +} + +static const struct iio_info apds9306_info_no_events = { + .read_avail = apds9306_read_avail, + .read_raw = apds9306_read_raw, + .write_raw = apds9306_write_raw, + .write_raw_get_fmt = apds9306_write_raw_get_fmt, +}; + +static const struct iio_info apds9306_info = { + .read_avail = apds9306_read_avail, + .read_raw = apds9306_read_raw, + .write_raw = apds9306_write_raw, + .write_raw_get_fmt = apds9306_write_raw_get_fmt, + .read_event_value = apds9306_read_event, + .write_event_value = apds9306_write_event, + .read_event_config = apds9306_read_event_config, + .write_event_config = apds9306_write_event_config, + .event_attrs = &apds9306_event_attr_group, +}; + +static int apds9306_init_iio_gts(struct apds9306_data *data) +{ + int i, ret, part_id; + + ret = regmap_read(data->regmap, APDS9306_PART_ID_REG, &part_id); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(apds9306_gts_mul); i++) + if (part_id == apds9306_gts_mul[i].part_id) + break; + + if (i == ARRAY_SIZE(apds9306_gts_mul)) + return -ENOENT; + + return devm_iio_init_iio_gts(data->dev, + apds9306_gts_mul[i].max_scale_int, + apds9306_gts_mul[i].max_scale_nano, + apds9306_gains, ARRAY_SIZE(apds9306_gains), + apds9306_itimes, ARRAY_SIZE(apds9306_itimes), + &data->gts); +} + +static void apds9306_powerdown(void *ptr) +{ + struct apds9306_data *data = (struct apds9306_data *)ptr; + struct apds9306_regfields *rf = &data->rf; + int ret; + + ret = regmap_field_write(rf->int_thresh_var_en, 0); + if (ret) + return; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return; + + apds9306_power_state(data, false); +} + +static int apds9306_device_init(struct apds9306_data *data) +{ + struct apds9306_regfields *rf = &data->rf; + int ret; + + ret = apds9306_init_iio_gts(data); + if (ret) + return ret; + + ret = regmap_field_write(rf->intg_time, APDS9306_MEAS_MODE_100MS); + if (ret) + return ret; + + ret = regmap_field_write(rf->repeat_rate, APDS9306_SAMP_FREQ_10HZ); + if (ret) + return ret; + + ret = regmap_field_write(rf->gain, APDS9306_GSEL_3X); + if (ret) + return ret; + + ret = regmap_field_write(rf->int_src, APDS9306_INT_SRC_ALS); + if (ret) + return ret; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return ret; + + return regmap_field_write(rf->int_thresh_var_en, 0); +} + +static int apds9306_pm_init(struct apds9306_data *data) +{ + struct device *dev = data->dev; + int ret; + + ret = apds9306_power_state(data, true); + if (ret) + return ret; + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 5000); + pm_runtime_use_autosuspend(dev); + pm_runtime_get(dev); + + return 0; +} + +static int apds9306_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct apds9306_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + mutex_init(&data->mutex); + + data->regmap = devm_regmap_init_i2c(client, &apds9306_regmap); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + data->dev = dev; + i2c_set_clientdata(client, indio_dev); + + ret = apds9306_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "regfield initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + indio_dev->name = "apds9306"; + indio_dev->modes = INDIO_DIRECT_MODE; + if (client->irq) { + indio_dev->info = &apds9306_info; + indio_dev->channels = apds9306_channels_with_events; + indio_dev->num_channels = ARRAY_SIZE(apds9306_channels_with_events); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + apds9306_irq_handler, IRQF_ONESHOT, + "apds9306_event", indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "failed to assign interrupt.\n"); + } else { + indio_dev->info = &apds9306_info_no_events; + indio_dev->channels = apds9306_channels_without_events; + indio_dev->num_channels = + ARRAY_SIZE(apds9306_channels_without_events); + } + + ret = apds9306_pm_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed pm init\n"); + + ret = apds9306_device_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init device\n"); + + ret = devm_add_action_or_reset(dev, apds9306_powerdown, data); + if (ret) + return dev_err_probe(dev, ret, "failed to add action or reset\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed iio device registration\n"); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int apds9306_runtime_suspend(struct device *dev) +{ + struct apds9306_data *data = iio_priv(dev_get_drvdata(dev)); + + return apds9306_power_state(data, false); +} + +static int apds9306_runtime_resume(struct device *dev) +{ + struct apds9306_data *data = iio_priv(dev_get_drvdata(dev)); + + return apds9306_power_state(data, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(apds9306_pm_ops, + apds9306_runtime_suspend, + apds9306_runtime_resume, + NULL); + +static const struct of_device_id apds9306_of_match[] = { + { .compatible = "avago,apds9306" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9306_of_match); + +static struct i2c_driver apds9306_driver = { + .driver = { + .name = "apds9306", + .pm = pm_ptr(&apds9306_pm_ops), + .of_match_table = apds9306_of_match, + }, + .probe = apds9306_probe, +}; +module_i2c_driver(apds9306_driver); + +MODULE_AUTHOR("Subhajit Ghosh "); +MODULE_DESCRIPTION("APDS9306 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_GTS_HELPER); From 88a1ffc690676b16f64ae8254253489b907c68e7 Mon Sep 17 00:00:00 2001 From: Dumitru Ceclan Date: Wed, 6 Mar 2024 13:09:54 +0200 Subject: [PATCH 032/108] dt-bindings: adc: ad7173: add support for additional models Add support for: AD7172-2, AD7175-8, AD7177-2. AD7172-4 does not feature an internal reference, check for external reference presence. Signed-off-by: Dumitru Ceclan Reviewed-by: Conor Dooley Link: https://lore.kernel.org/r/20240306110956.13167-2-mitrutzceclan@gmail.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad7173.yaml | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml index 36f16a325bc5..ea6cfcd0aff4 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml @@ -21,17 +21,23 @@ description: | Datasheets for supported chips: https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-4.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD7173-8.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD7175-2.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7175-8.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD7176-2.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD7177-2.pdf properties: compatible: enum: - adi,ad7172-2 + - adi,ad7172-4 - adi,ad7173-8 - adi,ad7175-2 + - adi,ad7175-8 - adi,ad7176-2 + - adi,ad7177-2 reg: maxItems: 1 @@ -136,8 +142,10 @@ patternProperties: refout-avss: REFOUT/AVSS (Internal reference) avdd : AVDD /AVSS - External reference ref2 only available on ad7173-8. - If not specified, internal reference used. + External reference ref2 only available on ad7173-8 and ad7172-4. + Internal reference refout-avss not available on ad7172-4. + + If not specified, internal reference used (if available). $ref: /schemas/types.yaml#/definitions/string enum: - vref @@ -157,12 +165,17 @@ required: allOf: - $ref: /schemas/spi/spi-peripheral-props.yaml# + # Only ad7172-4, ad7173-8 and ad7175-8 support vref2 + # Other models have [0-3] channel registers - if: properties: compatible: not: contains: - const: adi,ad7173-8 + enum: + - adi,ad7172-4 + - adi,ad7173-8 + - adi,ad7175-8 then: properties: vref2-supply: false @@ -177,6 +190,26 @@ allOf: reg: maximum: 3 + # Model ad7172-4 does not support internal reference + - if: + properties: + compatible: + contains: + const: adi,ad7172-4 + then: + patternProperties: + "^channel@[0-9a-f]$": + properties: + reg: + maximum: 7 + adi,reference-select: + enum: + - vref + - vref2 + - avdd + required: + - adi,reference-select + - if: anyOf: - required: [clock-names] From 393310526b4aaeb291d68c5b34f78f1bddae487a Mon Sep 17 00:00:00 2001 From: Dumitru Ceclan Date: Wed, 6 Mar 2024 13:09:55 +0200 Subject: [PATCH 033/108] iio: adc: ad7173: improve chip id's defines Rename to AD7172_2_ID to avoid confusion with _4 model. Reorder id's by reg value. Signed-off-by: Dumitru Ceclan Link: https://lore.kernel.org/r/20240306110956.13167-3-mitrutzceclan@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7173.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index b42fbe28a325..59a55d2944a7 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -61,10 +61,10 @@ #define AD7173_AIN_TEMP_POS 17 #define AD7173_AIN_TEMP_NEG 18 -#define AD7172_ID 0x00d0 -#define AD7173_ID 0x30d0 +#define AD7172_2_ID 0x00d0 #define AD7175_ID 0x0cd0 #define AD7176_ID 0x0c90 +#define AD7173_ID 0x30d0 #define AD7173_ID_MASK GENMASK(15, 4) #define AD7173_ADC_MODE_REF_EN BIT(15) @@ -190,7 +190,7 @@ static const unsigned int ad7175_sinc5_data_rates[] = { static const struct ad7173_device_info ad7173_device_info[] = { [ID_AD7172_2] = { .name = "ad7172-2", - .id = AD7172_ID, + .id = AD7172_2_ID, .num_inputs = 5, .num_channels = 4, .num_configs = 4, From 37ae8381ccda3c79d100677d29b233e207700b2b Mon Sep 17 00:00:00 2001 From: Dumitru Ceclan Date: Wed, 6 Mar 2024 13:09:56 +0200 Subject: [PATCH 034/108] iio: adc: ad7173: add support for additional models Add support for Analog Devices AD7172-2, AD7175-8, AD7177-2. Signed-off-by: Dumitru Ceclan Link: https://lore.kernel.org/r/20240306110956.13167-4-mitrutzceclan@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7173.c | 86 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index 59a55d2944a7..4ff6ce46b02c 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -1,6 +1,11 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * AD7172-2/AD7173-8/AD7175-2/AD7176-2 SPI ADC driver + * AD717x family SPI ADC driver + * + * Supported devices: + * AD7172-2/AD7172-4/AD7173-8/AD7175-2 + * AD7175-8/AD7176-2/AD7177-2 + * * Copyright (C) 2015, 2024 Analog Devices, Inc. */ @@ -64,7 +69,11 @@ #define AD7172_2_ID 0x00d0 #define AD7175_ID 0x0cd0 #define AD7176_ID 0x0c90 +#define AD7175_2_ID 0x0cd0 +#define AD7172_4_ID 0x2050 #define AD7173_ID 0x30d0 +#define AD7175_8_ID 0x3cd0 +#define AD7177_ID 0x4fd0 #define AD7173_ID_MASK GENMASK(15, 4) #define AD7173_ADC_MODE_REF_EN BIT(15) @@ -110,20 +119,25 @@ #define AD7173_SETUP_REF_SEL_EXT_REF 0x0 #define AD7173_VOLTAGE_INT_REF_uV 2500000 #define AD7173_TEMP_SENSIIVITY_uV_per_C 477 +#define AD7177_ODR_START_VALUE 0x07 #define AD7173_FILTER_ODR0_MASK GENMASK(5, 0) #define AD7173_MAX_CONFIGS 8 enum ad7173_ids { ID_AD7172_2, + ID_AD7172_4, ID_AD7173_8, ID_AD7175_2, + ID_AD7175_8, ID_AD7176_2, + ID_AD7177_2, }; struct ad7173_device_info { const unsigned int *sinc5_data_rates; unsigned int num_sinc5_data_rates; + unsigned int odr_start_value; unsigned int num_channels; unsigned int num_configs; unsigned int num_inputs; @@ -131,6 +145,8 @@ struct ad7173_device_info { unsigned int id; char *name; bool has_temp; + bool has_int_ref; + bool has_ref2; u8 num_gpios; }; @@ -196,6 +212,19 @@ static const struct ad7173_device_info ad7173_device_info[] = { .num_configs = 4, .num_gpios = 2, .has_temp = true, + .has_int_ref = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), + }, + [ID_AD7172_4] = { + .id = AD7172_4_ID, + .num_inputs = 9, + .num_channels = 8, + .num_configs = 8, + .num_gpios = 4, + .has_temp = false, + .has_ref2 = true, .clock = 2 * HZ_PER_MHZ, .sinc5_data_rates = ad7173_sinc5_data_rates, .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), @@ -208,18 +237,34 @@ static const struct ad7173_device_info ad7173_device_info[] = { .num_configs = 8, .num_gpios = 4, .has_temp = true, + .has_int_ref = true, + .has_ref2 = true, .clock = 2 * HZ_PER_MHZ, .sinc5_data_rates = ad7173_sinc5_data_rates, .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), }, [ID_AD7175_2] = { .name = "ad7175-2", - .id = AD7175_ID, + .id = AD7175_2_ID, .num_inputs = 5, .num_channels = 4, .num_configs = 4, .num_gpios = 2, .has_temp = true, + .has_int_ref = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), + }, + [ID_AD7175_8] = { + .id = AD7175_8_ID, + .num_inputs = 17, + .num_channels = 16, + .num_configs = 8, + .num_gpios = 4, + .has_temp = true, + .has_int_ref = true, + .has_ref2 = true, .clock = 16 * HZ_PER_MHZ, .sinc5_data_rates = ad7175_sinc5_data_rates, .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), @@ -232,10 +277,24 @@ static const struct ad7173_device_info ad7173_device_info[] = { .num_configs = 4, .num_gpios = 2, .has_temp = false, + .has_int_ref = true, .clock = 16 * HZ_PER_MHZ, .sinc5_data_rates = ad7175_sinc5_data_rates, .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), }, + [ID_AD7177_2] = { + .id = AD7177_ID, + .num_inputs = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .has_int_ref = true, + .clock = 16 * HZ_PER_MHZ, + .odr_start_value = AD7177_ODR_START_VALUE, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), + }, }; static const char *const ad7173_ref_sel_str[] = { @@ -656,7 +715,7 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_SAMP_FREQ: freq = val * MILLI + val2 / MILLI; - for (i = 0; i < st->info->num_sinc5_data_rates - 1; i++) + for (i = st->info->odr_start_value; i < st->info->num_sinc5_data_rates - 1; i++) if (freq >= st->info->sinc5_data_rates[i]) break; @@ -908,11 +967,17 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) else ref_sel = ret; - if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && - st->info->id != AD7173_ID) { + if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF && + !st->info->has_int_ref) { fwnode_handle_put(child); return dev_err_probe(dev, -EINVAL, - "External reference 2 is only available on ad7173-8\n"); + "Internal reference is not available on current model.\n"); + } + + if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && !st->info->has_ref2) { + fwnode_handle_put(child); + return dev_err_probe(dev, -EINVAL, + "External reference 2 is not available on current model.\n"); } ret = ad7173_get_ref_voltage_milli(st, ref_sel); @@ -1080,21 +1145,30 @@ static int ad7173_probe(struct spi_device *spi) static const struct of_device_id ad7173_of_match[] = { { .compatible = "adi,ad7172-2", .data = &ad7173_device_info[ID_AD7172_2]}, + { .compatible = "adi,ad7172-4", + .data = &ad7173_device_info[ID_AD7172_4]}, { .compatible = "adi,ad7173-8", .data = &ad7173_device_info[ID_AD7173_8]}, { .compatible = "adi,ad7175-2", .data = &ad7173_device_info[ID_AD7175_2]}, + { .compatible = "adi,ad7175-8", + .data = &ad7173_device_info[ID_AD7175_8]}, { .compatible = "adi,ad7176-2", .data = &ad7173_device_info[ID_AD7176_2]}, + { .compatible = "adi,ad7177-2", + .data = &ad7173_device_info[ID_AD7177_2]}, { } }; MODULE_DEVICE_TABLE(of, ad7173_of_match); static const struct spi_device_id ad7173_id_table[] = { { "ad7172-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_2]}, + { "ad7172-4", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_4]}, { "ad7173-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7173_8]}, { "ad7175-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7175_2]}, + { "ad7175-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7175_8]}, { "ad7176-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7176_2]}, + { "ad7177-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7177_2]}, { } }; MODULE_DEVICE_TABLE(spi, ad7173_id_table); From 1c5aa559a695fcb1a49da4217b8f8f8bfc5b393a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 4 Mar 2024 16:40:06 +0200 Subject: [PATCH 035/108] iio: adc: twl4030-madc: Make use of device properties Convert the module to be property provider agnostic and allow it to be used on non-OF platforms. Include mod_devicetable.h explicitly to replace the dropped of.h which included mod_devicetable.h indirectly. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240304144037.1036390-1-andriy.shevchenko@linux.intel.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/twl4030-madc.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/twl4030-madc.c b/drivers/iio/adc/twl4030-madc.c index 4a247ca25a44..0253064fadec 100644 --- a/drivers/iio/adc/twl4030-madc.c +++ b/drivers/iio/adc/twl4030-madc.c @@ -19,10 +19,12 @@ #include #include #include +#include +#include #include +#include #include #include -#include #include #include #include @@ -30,7 +32,6 @@ #include #include #include -#include #include #include @@ -744,14 +745,14 @@ static int twl4030_madc_set_power(struct twl4030_madc_data *madc, int on) */ static int twl4030_madc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct twl4030_madc_platform_data *pdata = dev_get_platdata(dev); struct twl4030_madc_data *madc; - struct twl4030_madc_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct device_node *np = pdev->dev.of_node; int irq, ret; u8 regval; struct iio_dev *iio_dev = NULL; - if (!pdata && !np) { + if (!pdata && !dev_fwnode(dev)) { dev_err(&pdev->dev, "neither platform data nor Device Tree node available\n"); return -EINVAL; } @@ -779,7 +780,7 @@ static int twl4030_madc_probe(struct platform_device *pdev) if (pdata) madc->use_second_irq = (pdata->irq_line != 1); else - madc->use_second_irq = of_property_read_bool(np, + madc->use_second_irq = device_property_read_bool(dev, "ti,system-uses-second-madc-irq"); madc->imr = madc->use_second_irq ? TWL4030_MADC_IMR2 : @@ -905,20 +906,18 @@ static void twl4030_madc_remove(struct platform_device *pdev) regulator_disable(madc->usb3v1); } -#ifdef CONFIG_OF static const struct of_device_id twl_madc_of_match[] = { { .compatible = "ti,twl4030-madc", }, - { }, + { } }; MODULE_DEVICE_TABLE(of, twl_madc_of_match); -#endif static struct platform_driver twl4030_madc_driver = { .probe = twl4030_madc_probe, .remove_new = twl4030_madc_remove, .driver = { .name = "twl4030_madc", - .of_match_table = of_match_ptr(twl_madc_of_match), + .of_match_table = twl_madc_of_match, }, }; From 59346366e56f8abf72ed2eb081da4abbfd4d5575 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 4 Mar 2024 13:48:46 -0600 Subject: [PATCH 036/108] dt-bindings: iio: adc: add ad7944 ADCs This adds a new binding for the Analog Devices, Inc. AD7944, AD7985, and AD7986 ADCs. Reviewed-by: Rob Herring Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240304-ad7944-mainline-v5-1-f0a38cea8901@baylibre.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad7944.yaml | 213 ++++++++++++++++++ MAINTAINERS | 8 + 2 files changed, 221 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml new file mode 100644 index 000000000000..d17d184842d3 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml @@ -0,0 +1,213 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad7944.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices PulSAR LFCSP Analog to Digital Converters + +maintainers: + - Michael Hennerich + - Nuno Sá + +description: | + A family of pin-compatible single channel differential analog to digital + converters with SPI support in a LFCSP package. + + * https://www.analog.com/en/products/ad7944.html + * https://www.analog.com/en/products/ad7985.html + * https://www.analog.com/en/products/ad7986.html + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ad7944 + - adi,ad7985 + - adi,ad7986 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 111111111 + + spi-cpol: true + spi-cpha: true + + adi,spi-mode: + $ref: /schemas/types.yaml#/definitions/string + enum: [ single, chain ] + description: | + This property indicates the SPI wiring configuration. + + When this property is omitted, it is assumed that the device is using what + the datasheet calls "4-wire mode". This is the conventional SPI mode used + when there are multiple devices on the same bus. In this mode, the CNV + line is used to initiate the conversion and the SDI line is connected to + CS on the SPI controller. + + When this property is present, it indicates that the device is using one + of the following alternative wiring configurations: + + * single: The datasheet calls this "3-wire mode". (NOTE: The datasheet's + definition of 3-wire mode is NOT at all related to the standard + spi-3wire property!) This mode is often used when the ADC is the only + device on the bus. In this mode, SDI is tied to VIO, and the CNV line + can be connected to the CS line of the SPI controller or to a GPIO, in + which case the CS line of the controller is unused. + * chain: The datasheet calls this "chain mode". This mode is used to save + on wiring when multiple ADCs are used. In this mode, the SDI line of + one chip is tied to the SDO of the next chip in the chain and the SDI of + the last chip in the chain is tied to GND. Only the first chip in the + chain is connected to the SPI bus. The CNV line of all chips are tied + together. The CS line of the SPI controller can be used as the CNV line + only if it is active high. + + '#daisy-chained-devices': true + + avdd-supply: + description: A 2.5V supply that powers the analog circuitry. + + dvdd-supply: + description: A 2.5V supply that powers the digital circuitry. + + vio-supply: + description: + A 1.8V to 2.7V supply for the digital inputs and outputs. + + bvdd-supply: + description: + A voltage supply for the buffered power. When using an external reference + without an internal buffer (PDREF high, REFIN low), this should be + connected to the same supply as ref-supply. Otherwise, when using an + internal reference or an external reference with an internal buffer, this + is connected to a 5V supply. + + ref-supply: + description: + Voltage regulator for the external reference voltage (REF). This property + is omitted when using an internal reference. + + refin-supply: + description: + Voltage regulator for the reference buffer input (REFIN). When using an + external buffer with internal reference, this should be connected to a + 1.2V external reference voltage supply. Otherwise, this property is + omitted. + + cnv-gpios: + description: + The Convert Input (CNV). This input has multiple functions. It initiates + the conversions and selects the SPI mode of the device (chain or CS). In + 'single' mode, this property is omitted if the CNV pin is connected to the + CS line of the SPI controller. + maxItems: 1 + + turbo-gpios: + description: + GPIO connected to the TURBO line. If omitted, it is assumed that the TURBO + line is hard-wired and the state is determined by the adi,always-turbo + property. + maxItems: 1 + + adi,always-turbo: + type: boolean + description: + When present, this property indicates that the TURBO line is hard-wired + and the state is always high. If neither this property nor turbo-gpios is + present, the TURBO line is assumed to be hard-wired and the state is + always low. + + interrupts: + description: + The SDO pin can also function as a busy indicator. This node should be + connected to an interrupt that is triggered when the SDO line goes low + while the SDI line is high and the CNV line is low ('single' mode) or the + SDI line is low and the CNV line is high ('multi' mode); or when the SDO + line goes high while the SDI and CNV lines are high (chain mode), + maxItems: 1 + +required: + - compatible + - reg + - avdd-supply + - dvdd-supply + - vio-supply + - bvdd-supply + +allOf: + # ref-supply and refin-supply are mutually exclusive (neither is also valid) + - if: + required: + - ref-supply + then: + properties: + refin-supply: false + - if: + required: + - refin-supply + then: + properties: + ref-supply: false + # in '4-wire' mode, cnv-gpios is required, for other modes it is optional + - if: + not: + required: + - adi,spi-mode + then: + required: + - cnv-gpios + # chain mode has lower SCLK max rate and doesn't work when TURBO is enabled + - if: + required: + - adi,spi-mode + properties: + adi,spi-mode: + const: chain + then: + properties: + spi-max-frequency: + maximum: 90909090 + adi,always-turbo: false + required: + - '#daisy-chained-devices' + else: + properties: + '#daisy-chained-devices': false + # turbo-gpios and adi,always-turbo are mutually exclusive + - if: + required: + - turbo-gpios + then: + properties: + adi,always-turbo: false + - if: + required: + - adi,always-turbo + then: + properties: + turbo-gpios: false + +unevaluatedProperties: false + +examples: + - | + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + adc@0 { + compatible = "adi,ad7944"; + reg = <0>; + spi-cpha; + spi-max-frequency = <111111111>; + avdd-supply = <&supply_2_5V>; + dvdd-supply = <&supply_2_5V>; + vio-supply = <&supply_1_8V>; + bvdd-supply = <&supply_5V>; + cnv-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>; + turbo-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index aa3b947fb080..6c14216ddb75 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -441,6 +441,14 @@ W: http://wiki.analog.com/AD7879 W: https://ez.analog.com/linux-software-drivers F: drivers/input/touchscreen/ad7879.c +AD7944 ADC DRIVER (AD7944/AD7985/AD7986) +M: Michael Hennerich +M: Nuno Sá +R: David Lechner +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml + ADAFRUIT MINI I2C GAMEPAD M: Anshul Dalal L: linux-input@vger.kernel.org From d1efcf8871db514ae349a24790afd1632ba31030 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 4 Mar 2024 13:48:47 -0600 Subject: [PATCH 037/108] iio: adc: ad7944: add driver for AD7944/AD7985/AD7986 This adds a driver for the Analog Devices Inc. AD7944, AD7985, and AD7986 ADCs. These are a family of pin-compatible ADCs that can sample at rates up to 2.5 MSPS. The initial driver adds support for sampling at lower rates using the usual IIO triggered buffer and can handle all 3 possible reference voltage configurations. Signed-off-by: David Lechner Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240304-ad7944-mainline-v5-2-f0a38cea8901@baylibre.com Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad7944.c | 416 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 428 insertions(+) create mode 100644 drivers/iio/adc/ad7944.c diff --git a/MAINTAINERS b/MAINTAINERS index 6c14216ddb75..eefd1a8906bf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -448,6 +448,7 @@ R: David Lechner S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml +F: drivers/iio/adc/ad7944.c ADAFRUIT MINI I2C GAMEPAD M: Anshul Dalal diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 91286ab83bd0..8db68b80b391 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -281,6 +281,16 @@ config AD7923 To compile this driver as a module, choose M here: the module will be called ad7923. +config AD7944 + tristate "Analog Devices AD7944 and similar ADCs driver" + depends on SPI + help + Say yes here to build support for Analog Devices + AD7944, AD7985, AD7986 ADCs. + + To compile this driver as a module, choose M here: the + module will be called ad7944 + config AD7949 tristate "Analog Devices AD7949 and similar ADCs driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 717e964afed9..edb32ce2af02 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_AD7780) += ad7780.o obj-$(CONFIG_AD7791) += ad7791.o obj-$(CONFIG_AD7793) += ad7793.o obj-$(CONFIG_AD7887) += ad7887.o +obj-$(CONFIG_AD7944) += ad7944.o obj-$(CONFIG_AD7949) += ad7949.o obj-$(CONFIG_AD799X) += ad799x.o obj-$(CONFIG_AD9467) += ad9467.o diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c new file mode 100644 index 000000000000..adb007cdd287 --- /dev/null +++ b/drivers/iio/adc/ad7944.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD7944/85/86 PulSAR ADC family driver. + * + * Copyright 2024 Analog Devices, Inc. + * Copyright 2024 BayLibre, SAS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AD7944_INTERNAL_REF_MV 4096 + +struct ad7944_timing_spec { + /* Normal mode max conversion time (t_{CONV}). */ + unsigned int conv_ns; + /* TURBO mode max conversion time (t_{CONV}). */ + unsigned int turbo_conv_ns; +}; + +struct ad7944_adc { + struct spi_device *spi; + /* Chip-specific timing specifications. */ + const struct ad7944_timing_spec *timing_spec; + /* GPIO connected to CNV pin. */ + struct gpio_desc *cnv; + /* Optional GPIO to enable turbo mode. */ + struct gpio_desc *turbo; + /* Indicates TURBO is hard-wired to be always enabled. */ + bool always_turbo; + /* Reference voltage (millivolts). */ + unsigned int ref_mv; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + struct { + union { + u16 u16; + u32 u32; + } raw; + u64 timestamp __aligned(8); + } sample __aligned(IIO_DMA_MINALIGN); +}; + +static const struct ad7944_timing_spec ad7944_timing_spec = { + .conv_ns = 420, + .turbo_conv_ns = 320, +}; + +static const struct ad7944_timing_spec ad7986_timing_spec = { + .conv_ns = 500, + .turbo_conv_ns = 400, +}; + +struct ad7944_chip_info { + const char *name; + const struct ad7944_timing_spec *timing_spec; + const struct iio_chan_spec channels[2]; +}; + +/* + * AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip + * @_name: The name of the chip + * @_ts: The timing specification for the chip + * @_bits: The number of bits in the conversion result + * @_diff: Whether the chip is true differential or not + */ +#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _bits, _diff) \ +static const struct ad7944_chip_info _name##_chip_info = { \ + .name = #_name, \ + .timing_spec = &_ts##_timing_spec, \ + .channels = { \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = _diff, \ + .channel = 0, \ + .channel2 = _diff ? 1 : 0, \ + .scan_index = 0, \ + .scan_type.sign = _diff ? 's' : 'u', \ + .scan_type.realbits = _bits, \ + .scan_type.storagebits = _bits > 16 ? 32 : 16, \ + .scan_type.endianness = IIO_CPU, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + }, \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ + }, \ +} + +/* pseudo-differential with ground sense */ +AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 14, 0); +AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0); +/* fully differential */ +AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); + +/* + * ad7944_4wire_mode_conversion - Perform a 4-wire mode conversion and acquisition + * @adc: The ADC device structure + * @chan: The channel specification + * Return: 0 on success, a negative error code on failure + * + * Upon successful return adc->sample.raw will contain the conversion result. + */ +static int ad7944_4wire_mode_conversion(struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer xfers[] = { + { + /* + * NB: can get better performance from some SPI + * controllers if we use the same bits_per_word + * in every transfer. + */ + .bits_per_word = chan->scan_type.realbits, + /* + * CS has to be high for full conversion time to avoid + * triggering the busy indication. + */ + .cs_off = 1, + .delay = { + .value = t_conv_ns, + .unit = SPI_DELAY_UNIT_NSECS, + }, + + }, + { + .rx_buf = &adc->sample.raw, + .len = BITS_TO_BYTES(chan->scan_type.storagebits), + .bits_per_word = chan->scan_type.realbits, + }, + }; + int ret; + + /* + * In 4-wire mode, the CNV line is held high for the entire conversion + * and acquisition process. + */ + gpiod_set_value_cansleep(adc->cnv, 1); + ret = spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); + gpiod_set_value_cansleep(adc->cnv, 0); + + return ret; +} + +static int ad7944_single_conversion(struct ad7944_adc *adc, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + + ret = ad7944_4wire_mode_conversion(adc, chan); + if (ret) + return ret; + + if (chan->scan_type.storagebits > 16) + *val = adc->sample.raw.u32; + else + *val = adc->sample.raw.u16; + + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; +} + +static int ad7944_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = ad7944_single_conversion(adc, chan, val); + iio_device_release_direct_mode(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = adc->ref_mv; + + if (chan->scan_type.sign == 's') + *val2 = chan->scan_type.realbits - 1; + else + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info ad7944_iio_info = { + .read_raw = &ad7944_read_raw, +}; + +static irqreturn_t ad7944_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7944_adc *adc = iio_priv(indio_dev); + int ret; + + ret = ad7944_4wire_mode_conversion(adc, &indio_dev->channels[0]); + if (ret) + goto out; + + iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, + pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const char * const ad7944_power_supplies[] = { + "avdd", "dvdd", "bvdd", "vio" +}; + +static void ad7944_ref_disable(void *ref) +{ + regulator_disable(ref); +} + +static int ad7944_probe(struct spi_device *spi) +{ + const struct ad7944_chip_info *chip_info; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad7944_adc *adc; + bool have_refin = false; + struct regulator *ref; + int ret; + + /* + * driver currently only supports the conventional "4-wire" mode and + * not other special wiring configurations. + */ + if (device_property_present(dev, "adi,spi-mode")) + return dev_err_probe(dev, -EINVAL, + "adi,spi-mode is not currently supported\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return dev_err_probe(dev, -EINVAL, "no chip info\n"); + + adc->timing_spec = chip_info->timing_spec; + + /* + * Some chips use unusual word sizes, so check now instead of waiting + * for the first xfer. + */ + if (!spi_is_bpw_supported(spi, chip_info->channels[0].scan_type.realbits)) + return dev_err_probe(dev, -EINVAL, + "SPI host does not support %d bits per word\n", + chip_info->channels[0].scan_type.realbits); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad7944_power_supplies), + ad7944_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* + * Sort out what is being used for the reference voltage. Options are: + * - internal reference: neither REF or REFIN is connected + * - internal reference with external buffer: REF not connected, REFIN + * is connected + * - external reference: REF is connected, REFIN is not connected + */ + + ref = devm_regulator_get_optional(dev, "ref"); + if (IS_ERR(ref)) { + if (PTR_ERR(ref) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(ref), + "failed to get REF supply\n"); + + ref = NULL; + } + + ret = devm_regulator_get_enable_optional(dev, "refin"); + if (ret == 0) + have_refin = true; + else if (ret != -ENODEV) + return dev_err_probe(dev, ret, + "failed to get and enable REFIN supply\n"); + + if (have_refin && ref) + return dev_err_probe(dev, -EINVAL, + "cannot have both refin and ref supplies\n"); + + if (ref) { + ret = regulator_enable(ref); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable REF supply\n"); + + ret = devm_add_action_or_reset(dev, ad7944_ref_disable, ref); + if (ret) + return ret; + + ret = regulator_get_voltage(ref); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get REF voltage\n"); + + /* external reference */ + adc->ref_mv = ret / 1000; + } else { + /* internal reference */ + adc->ref_mv = AD7944_INTERNAL_REF_MV; + } + + /* + * CNV gpio is required in 4-wire mode which is the only currently + * supported mode. + */ + adc->cnv = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW); + if (IS_ERR(adc->cnv)) + return dev_err_probe(dev, PTR_ERR(adc->cnv), + "failed to get CNV GPIO\n"); + + adc->turbo = devm_gpiod_get_optional(dev, "turbo", GPIOD_OUT_LOW); + if (IS_ERR(adc->turbo)) + return dev_err_probe(dev, PTR_ERR(adc->turbo), + "failed to get TURBO GPIO\n"); + + adc->always_turbo = device_property_present(dev, "adi,always-turbo"); + + if (adc->turbo && adc->always_turbo) + return dev_err_probe(dev, -EINVAL, + "cannot have both turbo-gpios and adi,always-turbo\n"); + + indio_dev->name = chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7944_iio_info; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = ARRAY_SIZE(chip_info->channels); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad7944_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7944_of_match[] = { + { .compatible = "adi,ad7944", .data = &ad7944_chip_info }, + { .compatible = "adi,ad7985", .data = &ad7985_chip_info }, + { .compatible = "adi,ad7986", .data = &ad7986_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7944_of_match); + +static const struct spi_device_id ad7944_spi_id[] = { + { "ad7944", (kernel_ulong_t)&ad7944_chip_info }, + { "ad7985", (kernel_ulong_t)&ad7985_chip_info }, + { "ad7986", (kernel_ulong_t)&ad7986_chip_info }, + { } + +}; +MODULE_DEVICE_TABLE(spi, ad7944_spi_id); + +static struct spi_driver ad7944_driver = { + .driver = { + .name = "ad7944", + .of_match_table = ad7944_of_match, + }, + .probe = ad7944_probe, + .id_table = ad7944_spi_id, +}; +module_spi_driver(ad7944_driver); + +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("Analog Devices AD7944 PulSAR ADC family driver"); +MODULE_LICENSE("GPL"); From f764c293a1f8a4ee849d377a804e3940a208ad0b Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 3 Mar 2024 22:54:20 +0100 Subject: [PATCH 038/108] iio: humidity: hdc3020: add power management The HDC3020 sensor carries out periodic measurements during normal operation, but as long as the power supply is enabled, it will carry on in low-power modes. In order to avoid that and reduce power consumption, the device can be switched to Trigger-on Demand mode, and if possible, turn off its regulator. According to the datasheet, the maximum "Power Up Ready" is 5 ms. Add resume/suspend pm operations to manage measurement mode and regulator state. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240303-hdc3020-pm-v3-1-48bc02b5241b@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/hdc3020.c | 95 +++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c index 1e5d0d4797b1..7f93024b850c 100644 --- a/drivers/iio/humidity/hdc3020.c +++ b/drivers/iio/humidity/hdc3020.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -68,6 +70,7 @@ struct hdc3020_data { struct i2c_client *client; + struct regulator *vdd_supply; /* * Ensure that the sensor configuration (currently only heater is * supported) will not be changed during the process of reading @@ -551,9 +554,45 @@ static const struct iio_info hdc3020_info = { .write_event_value = hdc3020_write_thresh, }; -static void hdc3020_stop(void *data) +static int hdc3020_power_off(struct hdc3020_data *data) { - hdc3020_exec_cmd((struct hdc3020_data *)data, HDC3020_EXIT_AUTO); + hdc3020_exec_cmd(data, HDC3020_EXIT_AUTO); + + return regulator_disable(data->vdd_supply); +} + +static int hdc3020_power_on(struct hdc3020_data *data) +{ + int ret; + + ret = regulator_enable(data->vdd_supply); + if (ret) + return ret; + + fsleep(5000); + + if (data->client->irq) { + /* + * The alert output is activated by default upon power up, + * hardware reset, and soft reset. Clear the status register. + */ + ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS); + if (ret) { + hdc3020_power_off(data); + return ret; + } + } + + ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0); + if (ret) + hdc3020_power_off(data); + + return ret; +} + +static void hdc3020_exit(void *data) +{ + hdc3020_power_off(data); } static int hdc3020_probe(struct i2c_client *client) @@ -569,6 +608,8 @@ static int hdc3020_probe(struct i2c_client *client) if (!indio_dev) return -ENOMEM; + dev_set_drvdata(&client->dev, indio_dev); + data = iio_priv(indio_dev); data->client = client; mutex_init(&data->lock); @@ -580,6 +621,20 @@ static int hdc3020_probe(struct i2c_client *client) indio_dev->info = &hdc3020_info; indio_dev->channels = hdc3020_channels; indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels); + + data->vdd_supply = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(data->vdd_supply)) + return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply), + "Unable to get VDD regulator\n"); + + ret = hdc3020_power_on(data); + if (ret) + return dev_err_probe(&client->dev, ret, "Power on failed\n"); + + ret = devm_add_action_or_reset(&data->client->dev, hdc3020_exit, data); + if (ret) + return ret; + if (client->irq) { ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, hdc3020_interrupt_handler, @@ -588,25 +643,8 @@ static int hdc3020_probe(struct i2c_client *client) if (ret) return dev_err_probe(&client->dev, ret, "Failed to request IRQ\n"); - - /* - * The alert output is activated by default upon power up, - * hardware reset, and soft reset. Clear the status register. - */ - ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS); - if (ret) - return ret; } - ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0); - if (ret) - return dev_err_probe(&client->dev, ret, - "Unable to set up measurement\n"); - - ret = devm_add_action_or_reset(&data->client->dev, hdc3020_stop, data); - if (ret) - return ret; - ret = devm_iio_device_register(&data->client->dev, indio_dev); if (ret) return dev_err_probe(&client->dev, ret, "Failed to add device"); @@ -614,6 +652,24 @@ static int hdc3020_probe(struct i2c_client *client) return 0; } +static int hdc3020_suspend(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct hdc3020_data *data = iio_priv(iio_dev); + + return hdc3020_power_off(data); +} + +static int hdc3020_resume(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct hdc3020_data *data = iio_priv(iio_dev); + + return hdc3020_power_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(hdc3020_pm_ops, hdc3020_suspend, hdc3020_resume); + static const struct i2c_device_id hdc3020_id[] = { { "hdc3020" }, { "hdc3021" }, @@ -633,6 +689,7 @@ MODULE_DEVICE_TABLE(of, hdc3020_dt_ids); static struct i2c_driver hdc3020_driver = { .driver = { .name = "hdc3020", + .pm = pm_sleep_ptr(&hdc3020_pm_ops), .of_match_table = hdc3020_dt_ids, }, .probe = hdc3020_probe, From 137166ef5fc5a10fbc7bceee20d770e6a930f4cc Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 3 Mar 2024 22:54:21 +0100 Subject: [PATCH 039/108] dt-bindings: iio: humidity: hdc3020: add reset-gpios The HDC3020 provides an active low reset signal that is still not described in the bindings. Add reset-gpios to the bindings and the example. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240303-hdc3020-pm-v3-2-48bc02b5241b@gmail.com Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/humidity/ti,hdc3020.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml b/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml index 8b5dedd1a598..b375d307513f 100644 --- a/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml +++ b/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml @@ -34,6 +34,9 @@ properties: reg: maxItems: 1 + reset-gpios: + maxItems: 1 + required: - compatible - reg @@ -43,6 +46,7 @@ additionalProperties: false examples: - | + #include #include i2c { #address-cells = <1>; @@ -54,5 +58,6 @@ examples: vdd-supply = <&vcc_3v3>; interrupt-parent = <&gpio3>; interrupts = <23 IRQ_TYPE_EDGE_RISING>; + reset-gpios = <&gpio3 27 GPIO_ACTIVE_LOW>; }; }; From e264b086f40a3d74a405227db8b624b4b15906e0 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 3 Mar 2024 22:54:22 +0100 Subject: [PATCH 040/108] iio: humidity: hdc3020: add reset management The HDC3020 provides an active low reset signal that must be handled if connected. Asserting this signal turns the device into Trigger-on Demand measurement mode, reducing its power consumption when no measurements are required like in low-power modes. According to the datasheet, the longest "Reset Ready" is 3 ms, which is only taken into account if the reset signal is defined. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240303-hdc3020-pm-v3-3-48bc02b5241b@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/humidity/hdc3020.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c index 7f93024b850c..cdc4789213ba 100644 --- a/drivers/iio/humidity/hdc3020.c +++ b/drivers/iio/humidity/hdc3020.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,7 @@ struct hdc3020_data { struct i2c_client *client; + struct gpio_desc *reset_gpio; struct regulator *vdd_supply; /* * Ensure that the sensor configuration (currently only heater is @@ -558,6 +560,9 @@ static int hdc3020_power_off(struct hdc3020_data *data) { hdc3020_exec_cmd(data, HDC3020_EXIT_AUTO); + if (data->reset_gpio) + gpiod_set_value_cansleep(data->reset_gpio, 1); + return regulator_disable(data->vdd_supply); } @@ -571,6 +576,11 @@ static int hdc3020_power_on(struct hdc3020_data *data) fsleep(5000); + if (data->reset_gpio) { + gpiod_set_value_cansleep(data->reset_gpio, 0); + fsleep(3000); + } + if (data->client->irq) { /* * The alert output is activated by default upon power up, @@ -627,6 +637,12 @@ static int hdc3020_probe(struct i2c_client *client) return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply), "Unable to get VDD regulator\n"); + data->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(data->reset_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio), + "Cannot get reset GPIO\n"); + ret = hdc3020_power_on(data); if (ret) return dev_err_probe(&client->dev, ret, "Power on failed\n"); From 0b70c0844955a7d0fd24048e53fdd62e94f4e0e9 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 11 Mar 2024 16:05:54 +0000 Subject: [PATCH 041/108] iio: imu: inv_mpu6050: add WoM (Wake-on-Motion) sensor WoM is a threshold test on accel value comparing actual sample with previous one. It maps best to roc rising event. Add support of a new WOM sensor and functions for handling the associated roc_rising event. The event value is in SI units. Ensure WOM is stopped and restarted at suspend-resume, handle usage with buffer data ready interrupt, and handle change in sampling rate impacting already set roc value. Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240311160557.437337-2-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 282 +++++++++++++++++- drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 20 +- drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 6 +- drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c | 14 +- 4 files changed, 309 insertions(+), 13 deletions(-) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 0e94e5335e93..46607d404e59 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -332,7 +334,7 @@ static int inv_mpu6050_clock_switch(struct inv_mpu6050_state *st, int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, unsigned int mask) { - unsigned int sleep; + unsigned int sleep, val; u8 pwr_mgmt2, user_ctrl; int ret; @@ -345,6 +347,14 @@ int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, mask &= ~INV_MPU6050_SENSOR_TEMP; if (mask & INV_MPU6050_SENSOR_MAGN && en == st->chip_config.magn_en) mask &= ~INV_MPU6050_SENSOR_MAGN; + if (mask & INV_MPU6050_SENSOR_WOM && en == st->chip_config.wom_en) + mask &= ~INV_MPU6050_SENSOR_WOM; + + /* force accel on if WoM is on and not going off */ + if (!en && (mask & INV_MPU6050_SENSOR_ACCL) && st->chip_config.wom_en && + !(mask & INV_MPU6050_SENSOR_WOM)) + mask &= ~INV_MPU6050_SENSOR_ACCL; + if (mask == 0) return 0; @@ -439,6 +449,16 @@ int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, } } + /* enable/disable accel intelligence control */ + if (mask & INV_MPU6050_SENSOR_WOM) { + val = en ? INV_MPU6500_BIT_ACCEL_INTEL_EN | + INV_MPU6500_BIT_ACCEL_INTEL_MODE : 0; + ret = regmap_write(st->map, INV_MPU6500_REG_ACCEL_INTEL_CTRL, val); + if (ret) + return ret; + st->chip_config.wom_en = en; + } + return 0; } @@ -893,6 +913,239 @@ static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, return result; } +static u64 inv_mpu6050_convert_wom_to_roc(unsigned int threshold, unsigned int freq_div) +{ + /* 4mg per LSB converted in m/s² in micro (1000000) */ + const unsigned int convert = 4U * 9807U; + u64 value; + + value = threshold * convert; + + /* compute the differential by multiplying by the frequency */ + return div_u64(value * INV_MPU6050_INTERNAL_FREQ_HZ, freq_div); +} + +static unsigned int inv_mpu6050_convert_roc_to_wom(u64 roc, unsigned int freq_div) +{ + /* 4mg per LSB converted in m/s² in micro (1000000) */ + const unsigned int convert = 4U * 9807U; + u64 value; + + /* return 0 only if roc is 0 */ + if (roc == 0) + return 0; + + value = div_u64(roc * freq_div, convert * INV_MPU6050_INTERNAL_FREQ_HZ); + + /* limit value to 8 bits and prevent 0 */ + return min(255, max(1, value)); +} + +static int inv_mpu6050_set_wom_int(struct inv_mpu6050_state *st, bool on) +{ + unsigned int reg_val, val; + + switch (st->chip_type) { + case INV_MPU6050: + case INV_MPU6500: + case INV_MPU6515: + case INV_MPU6880: + case INV_MPU6000: + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + reg_val = INV_MPU6500_BIT_WOM_INT_EN; + break; + default: + reg_val = INV_ICM20608_BIT_WOM_INT_EN; + break; + } + + val = on ? reg_val : 0; + + return regmap_update_bits(st->map, st->reg->int_enable, reg_val, val); +} + +static int inv_mpu6050_set_wom_threshold(struct inv_mpu6050_state *st, u64 value, + unsigned int freq_div) +{ + unsigned int threshold; + int result; + + /* convert roc to wom threshold and convert back to handle clipping */ + threshold = inv_mpu6050_convert_roc_to_wom(value, freq_div); + value = inv_mpu6050_convert_wom_to_roc(threshold, freq_div); + + dev_dbg(regmap_get_device(st->map), "wom_threshold: 0x%x\n", threshold); + + switch (st->chip_type) { + case INV_ICM20609: + case INV_ICM20689: + case INV_ICM20600: + case INV_ICM20602: + case INV_ICM20690: + st->data[0] = threshold; + st->data[1] = threshold; + st->data[2] = threshold; + result = regmap_bulk_write(st->map, INV_ICM20609_REG_ACCEL_WOM_X_THR, + st->data, 3); + break; + default: + result = regmap_write(st->map, INV_MPU6500_REG_WOM_THRESHOLD, threshold); + break; + } + if (result) + return result; + + st->chip_config.roc_threshold = value; + + return 0; +} + +static int inv_mpu6050_enable_wom(struct inv_mpu6050_state *st, bool en) +{ + struct device *pdev = regmap_get_device(st->map); + unsigned int mask; + int result; + + if (en) { + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + + mask = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_WOM; + result = inv_mpu6050_switch_engine(st, true, mask); + if (result) + goto error_suspend; + + result = inv_mpu6050_set_wom_int(st, true); + if (result) + goto error_suspend; + } else { + result = inv_mpu6050_set_wom_int(st, false); + if (result) + dev_err(pdev, "error %d disabling WoM interrupt bit", result); + + /* disable only WoM and let accel be disabled by autosuspend */ + result = inv_mpu6050_switch_engine(st, false, INV_MPU6050_SENSOR_WOM); + if (result) { + dev_err(pdev, "error %d disabling WoM force off", result); + /* force WoM off */ + st->chip_config.wom_en = false; + } + + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + } + + return result; + +error_suspend: + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + return result; +} + +static int inv_mpu6050_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + /* support only WoM (accel roc rising) event */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING) + return -EINVAL; + + guard(mutex)(&st->lock); + + return st->chip_config.wom_en ? 1 : 0; +} + +static int inv_mpu6050_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int enable; + + /* support only WoM (accel roc rising) event */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING) + return -EINVAL; + + enable = !!state; + + guard(mutex)(&st->lock); + + if (st->chip_config.wom_en == enable) + return 0; + + return inv_mpu6050_enable_wom(st, enable); +} + +static int inv_mpu6050_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + u32 rem; + + /* support only WoM (accel roc rising) event value */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING || info != IIO_EV_INFO_VALUE) + return -EINVAL; + + guard(mutex)(&st->lock); + + /* return value in micro */ + *val = div_u64_rem(st->chip_config.roc_threshold, 1000000U, &rem); + *val2 = rem; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_mpu6050_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + u64 value; + int result; + + /* support only WoM (accel roc rising) event value */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING || info != IIO_EV_INFO_VALUE) + return -EINVAL; + + if (val < 0 || val2 < 0) + return -EINVAL; + + guard(mutex)(&st->lock); + + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + + value = (u64)val * 1000000ULL + (u64)val2; + result = inv_mpu6050_set_wom_threshold(st, value, INV_MPU6050_FREQ_DIVIDER(st)); + + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + + return result; +} + /* * inv_mpu6050_set_lpf() - set low pass filer based on fifo rate. * @@ -989,6 +1242,12 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, if (result) goto fifo_rate_fail_power_off; + /* update wom threshold since roc is dependent on sampling frequency */ + result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold, + INV_MPU6050_FREQ_DIVIDER(st)); + if (result) + goto fifo_rate_fail_power_off; + pm_runtime_mark_last_busy(pdev); fifo_rate_fail_power_off: pm_runtime_put_autosuspend(pdev); @@ -1326,6 +1585,10 @@ static const struct iio_info mpu_info = { .write_raw = &inv_mpu6050_write_raw, .write_raw_get_fmt = &inv_write_raw_get_fmt, .attrs = &inv_attribute_group, + .read_event_config = inv_mpu6050_read_event_config, + .write_event_config = inv_mpu6050_write_event_config, + .read_event_value = inv_mpu6050_read_event_value, + .write_event_value = inv_mpu6050_write_event_value, .validate_trigger = inv_mpu6050_validate_trigger, .debugfs_reg_access = &inv_mpu6050_reg_access, }; @@ -1706,6 +1969,12 @@ static int inv_mpu_resume(struct device *dev) if (result) goto out_unlock; + if (st->chip_config.wom_en) { + result = inv_mpu6050_set_wom_int(st, true); + if (result) + goto out_unlock; + } + if (iio_buffer_enabled(indio_dev)) result = inv_mpu6050_prepare_fifo(st, true); @@ -1735,6 +2004,12 @@ static int inv_mpu_suspend(struct device *dev) goto out_unlock; } + if (st->chip_config.wom_en) { + result = inv_mpu6050_set_wom_int(st, false); + if (result) + goto out_unlock; + } + if (st->chip_config.accl_en) st->suspended_sensors |= INV_MPU6050_SENSOR_ACCL; if (st->chip_config.gyro_en) @@ -1743,6 +2018,8 @@ static int inv_mpu_suspend(struct device *dev) st->suspended_sensors |= INV_MPU6050_SENSOR_TEMP; if (st->chip_config.magn_en) st->suspended_sensors |= INV_MPU6050_SENSOR_MAGN; + if (st->chip_config.wom_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_WOM; result = inv_mpu6050_switch_engine(st, false, st->suspended_sensors); if (result) goto out_unlock; @@ -1767,7 +2044,8 @@ static int inv_mpu_runtime_suspend(struct device *dev) mutex_lock(&st->lock); sensors = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO | - INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN; + INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN | + INV_MPU6050_SENSOR_WOM; ret = inv_mpu6050_switch_engine(st, false, sensors); if (ret) goto out_unlock; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h index 5950e2419ebb..58bb5242ae0a 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -88,11 +88,12 @@ enum inv_devices { INV_NUM_PARTS }; -/* chip sensors mask: accelerometer, gyroscope, temperature, magnetometer */ +/* chip sensors mask: accelerometer, gyroscope, temperature, magnetometer, WoM */ #define INV_MPU6050_SENSOR_ACCL BIT(0) #define INV_MPU6050_SENSOR_GYRO BIT(1) #define INV_MPU6050_SENSOR_TEMP BIT(2) #define INV_MPU6050_SENSOR_MAGN BIT(3) +#define INV_MPU6050_SENSOR_WOM BIT(4) /** * struct inv_mpu6050_chip_config - Cached chip configuration data. @@ -104,11 +105,13 @@ enum inv_devices { * @gyro_en: gyro engine enabled * @temp_en: temperature sensor enabled * @magn_en: magn engine (i2c master) enabled + * @wom_en: Wake-on-Motion enabled * @accl_fifo_enable: enable accel data output * @gyro_fifo_enable: enable gyro data output * @temp_fifo_enable: enable temp data output * @magn_fifo_enable: enable magn data output * @divider: chip sample rate divider (sample rate divider - 1) + * @roc_threshold: save ROC threshold (WoM) set value */ struct inv_mpu6050_chip_config { unsigned int clk:3; @@ -119,12 +122,14 @@ struct inv_mpu6050_chip_config { unsigned int gyro_en:1; unsigned int temp_en:1; unsigned int magn_en:1; + unsigned int wom_en:1; unsigned int accl_fifo_enable:1; unsigned int gyro_fifo_enable:1; unsigned int temp_fifo_enable:1; unsigned int magn_fifo_enable:1; u8 divider; u8 user_ctrl; + u64 roc_threshold; }; /* @@ -256,12 +261,16 @@ struct inv_mpu6050_state { #define INV_MPU6050_REG_INT_ENABLE 0x38 #define INV_MPU6050_BIT_DATA_RDY_EN 0x01 #define INV_MPU6050_BIT_DMP_INT_EN 0x02 +#define INV_MPU6500_BIT_WOM_INT_EN BIT(6) +#define INV_ICM20608_BIT_WOM_INT_EN GENMASK(7, 5) #define INV_MPU6050_REG_RAW_ACCEL 0x3B #define INV_MPU6050_REG_TEMPERATURE 0x41 #define INV_MPU6050_REG_RAW_GYRO 0x43 #define INV_MPU6050_REG_INT_STATUS 0x3A +#define INV_MPU6500_BIT_WOM_INT BIT(6) +#define INV_ICM20608_BIT_WOM_INT GENMASK(7, 5) #define INV_MPU6050_BIT_FIFO_OVERFLOW_INT 0x10 #define INV_MPU6050_BIT_RAW_DATA_RDY_INT 0x01 @@ -301,6 +310,11 @@ struct inv_mpu6050_state { #define INV_MPU6050_BIT_PWR_ACCL_STBY 0x38 #define INV_MPU6050_BIT_PWR_GYRO_STBY 0x07 +/* ICM20609 registers */ +#define INV_ICM20609_REG_ACCEL_WOM_X_THR 0x20 +#define INV_ICM20609_REG_ACCEL_WOM_Y_THR 0x21 +#define INV_ICM20609_REG_ACCEL_WOM_Z_THR 0x22 + /* ICM20602 register */ #define INV_ICM20602_REG_I2C_IF 0x70 #define INV_ICM20602_BIT_I2C_IF_DIS 0x40 @@ -320,6 +334,10 @@ struct inv_mpu6050_state { /* mpu6500 registers */ #define INV_MPU6500_REG_ACCEL_CONFIG_2 0x1D #define INV_ICM20689_BITS_FIFO_SIZE_MAX 0xC0 +#define INV_MPU6500_REG_WOM_THRESHOLD 0x1F +#define INV_MPU6500_REG_ACCEL_INTEL_CTRL 0x69 +#define INV_MPU6500_BIT_ACCEL_INTEL_EN BIT(7) +#define INV_MPU6500_BIT_ACCEL_INTEL_MODE BIT(6) #define INV_MPU6500_REG_ACCEL_OFFSET 0x77 /* delay time in milliseconds */ diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c index d4f9b5d8d28d..f1620d5f4423 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -33,10 +33,8 @@ static int inv_reset_fifo(struct iio_dev *indio_dev) reset_fifo_fail: dev_err(regmap_get_device(st->map), "reset fifo failed %d\n", result); - result = regmap_write(st->map, st->reg->int_enable, - INV_MPU6050_BIT_DATA_RDY_EN); - - return result; + return regmap_update_bits(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN, INV_MPU6050_BIT_DATA_RDY_EN); } /* diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c index e6e6e94452a3..30bcba334239 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -135,11 +135,13 @@ int inv_mpu6050_prepare_fifo(struct inv_mpu6050_state *st, bool enable) ret = regmap_write(st->map, st->reg->user_ctrl, d); if (ret) return ret; - /* enable interrupt */ - ret = regmap_write(st->map, st->reg->int_enable, - INV_MPU6050_BIT_DATA_RDY_EN); + /* enable data interrupt */ + ret = regmap_update_bits(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN, INV_MPU6050_BIT_DATA_RDY_EN); } else { - ret = regmap_write(st->map, st->reg->int_enable, 0); + /* disable data interrupt */ + ret = regmap_update_bits(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN, 0); if (ret) return ret; ret = regmap_write(st->map, st->reg->fifo_en, 0); @@ -172,9 +174,9 @@ static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) return result; /* * In case autosuspend didn't trigger, turn off first not - * required sensors. + * required sensors excepted WoM */ - result = inv_mpu6050_switch_engine(st, false, ~scan); + result = inv_mpu6050_switch_engine(st, false, ~scan & ~INV_MPU6050_SENSOR_WOM); if (result) goto error_power_off; result = inv_mpu6050_switch_engine(st, true, scan); From d0e79bebce8b11f100a9c9b43852c2ed98dcac51 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 11 Mar 2024 16:05:55 +0000 Subject: [PATCH 042/108] iio: imu: inv_mpu6050: add WoM event as accel event Add WoM (roc rising) event as accel x_or_y_or_z event for all chips >= MPU-6500. This requires to create new MPU-6500 channels as default and MPU-6050 channels for older chips. Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240311160557.437337-3-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 67 +++++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 46607d404e59..eb341b87c8f3 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -1348,6 +1348,15 @@ static const struct iio_chan_spec_ext_info inv_ext_info[] = { { } }; +static const struct iio_event_spec inv_wom_events[] = { + { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + #define INV_MPU6050_CHAN(_type, _channel2, _index) \ { \ .type = _type, \ @@ -1383,7 +1392,17 @@ static const struct iio_chan_spec_ext_info inv_ext_info[] = { }, \ } -static const struct iio_chan_spec inv_mpu_channels[] = { +#define INV_MPU6050_EVENT_CHAN(_type, _channel2, _events, _events_nb) \ +{ \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .event_spec = _events, \ + .num_event_specs = _events_nb, \ + .scan_index = -1, \ +} + +static const struct iio_chan_spec inv_mpu6050_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), @@ -1397,6 +1416,23 @@ static const struct iio_chan_spec inv_mpu_channels[] = { INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), }; +static const struct iio_chan_spec inv_mpu6500_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), + + INV_MPU6050_EVENT_CHAN(IIO_ACCEL, IIO_MOD_X_OR_Y_OR_Z, + inv_wom_events, ARRAY_SIZE(inv_wom_events)), +}; + #define INV_MPU6050_SCAN_MASK_3AXIS_ACCEL \ (BIT(INV_MPU6050_SCAN_ACCL_X) \ | BIT(INV_MPU6050_SCAN_ACCL_Y) \ @@ -1876,6 +1912,12 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, return result; switch (chip_type) { + case INV_MPU6000: + case INV_MPU6050: + indio_dev->channels = inv_mpu6050_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6050_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; case INV_MPU9150: indio_dev->channels = inv_mpu9150_channels; indio_dev->num_channels = ARRAY_SIZE(inv_mpu9150_channels); @@ -1889,13 +1931,13 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, break; case INV_ICM20600: case INV_ICM20602: - indio_dev->channels = inv_mpu_channels; - indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + indio_dev->channels = inv_mpu6500_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels); indio_dev->available_scan_masks = inv_icm20602_scan_masks; break; default: - indio_dev->channels = inv_mpu_channels; - indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + indio_dev->channels = inv_mpu6500_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels); indio_dev->available_scan_masks = inv_mpu_scan_masks; break; } @@ -1904,9 +1946,18 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, * auxiliary device in use. Otherwise Going back to 6-axis only. */ if (st->magn_disabled) { - indio_dev->channels = inv_mpu_channels; - indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); - indio_dev->available_scan_masks = inv_mpu_scan_masks; + switch (chip_type) { + case INV_MPU9150: + indio_dev->channels = inv_mpu6050_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6050_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; + default: + indio_dev->channels = inv_mpu6500_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; + } } indio_dev->info = &mpu_info; From 5537f653d9be09a591063395c0d9ae97c6a4b23f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 11 Mar 2024 16:05:56 +0000 Subject: [PATCH 043/108] iio: imu: inv_mpu6050: add new interrupt handler for WoM events Add new interrupt handler for generating WoM event from int status register bits. Launch from interrupt the trigger poll function for data buffer. Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240311160557.437337-4-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 2 + drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 11 --- drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c | 69 +++++++++++++++++-- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h index 58bb5242ae0a..f48a8f642c3e 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -185,6 +185,7 @@ struct inv_mpu6050_hw { * @magn_orient: magnetometer sensor chip orientation if available. * @suspended_sensors: sensors mask of sensors turned off for suspend * @data: read buffer used for bulk reads. + * @it_timestamp: interrupt timestamp. */ struct inv_mpu6050_state { struct mutex lock; @@ -210,6 +211,7 @@ struct inv_mpu6050_state { unsigned int suspended_sensors; bool level_shifter; u8 *data; + s64 it_timestamp; }; /*register and associated bit definition*/ diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c index f1620d5f4423..86465226f7e1 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -51,21 +51,10 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) u32 fifo_period; s64 timestamp; u8 data[INV_MPU6050_OUTPUT_DATA_SIZE]; - int int_status; size_t i, nb; mutex_lock(&st->lock); - /* ack interrupt and check status */ - result = regmap_read(st->map, st->reg->int_status, &int_status); - if (result) { - dev_err(regmap_get_device(st->map), - "failed to ack interrupt\n"); - goto flush_fifo; - } - if (!(int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT)) - goto end_session; - if (!(st->chip_config.accl_fifo_enable | st->chip_config.gyro_fifo_enable | st->chip_config.magn_fifo_enable)) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c index 30bcba334239..1b603567ccc8 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -6,6 +6,7 @@ #include #include +#include #include "inv_mpu_iio.h" @@ -228,6 +229,65 @@ static const struct iio_trigger_ops inv_mpu_trigger_ops = { .set_trigger_state = &inv_mpu_data_rdy_trigger_set_state, }; +static irqreturn_t inv_mpu6050_interrupt_timestamp(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->it_timestamp = iio_get_time_ns(indio_dev); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t inv_mpu6050_interrupt_handle(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + unsigned int int_status, wom_bits; + u64 ev_code; + int result; + + switch (st->chip_type) { + case INV_MPU6050: + case INV_MPU6500: + case INV_MPU6515: + case INV_MPU6880: + case INV_MPU6000: + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + wom_bits = INV_MPU6500_BIT_WOM_INT; + break; + default: + wom_bits = INV_ICM20608_BIT_WOM_INT; + break; + } + + scoped_guard(mutex, &st->lock) { + /* ack interrupt and check status */ + result = regmap_read(st->map, st->reg->int_status, &int_status); + if (result) { + dev_err(regmap_get_device(st->map), "failed to ack interrupt\n"); + return IRQ_HANDLED; + } + + /* handle WoM event */ + if (st->chip_config.wom_en && (int_status & wom_bits)) { + ev_code = IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING); + iio_push_event(indio_dev, ev_code, st->it_timestamp); + } + } + + /* handle raw data interrupt */ + if (int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT) { + indio_dev->pollfunc->timestamp = st->it_timestamp; + iio_trigger_poll_nested(st->trig); + } + + return IRQ_HANDLED; +} + int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type) { int ret; @@ -240,11 +300,10 @@ int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type) if (!st->trig) return -ENOMEM; - ret = devm_request_irq(&indio_dev->dev, st->irq, - &iio_trigger_generic_data_rdy_poll, - irq_type, - "inv_mpu", - st->trig); + ret = devm_request_threaded_irq(&indio_dev->dev, st->irq, + &inv_mpu6050_interrupt_timestamp, + &inv_mpu6050_interrupt_handle, + irq_type, "inv_mpu", indio_dev); if (ret) return ret; From 305914d018077a125b0541b196ed080804f72106 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 11 Mar 2024 16:05:57 +0000 Subject: [PATCH 044/108] iio: imu: inv_mpu6050: add WoM suspend wakeup with low-power mode Add wakeup from suspend for WoM when enabled and put accel in low-power mode when suspended. Requires rewriting pwr_mgmt_1 register handling and factorize out accel LPF settings. Use a low-power rate similar to the chip sampling rate but always lower for a best match of the sampling rate while saving power and adjust threshold to follow the required roc value. Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240311160557.437337-5-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 203 +++++++++++++++------ drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 14 ++ 2 files changed, 166 insertions(+), 51 deletions(-) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index eb341b87c8f3..14d95f34e981 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -289,7 +289,7 @@ static const struct inv_mpu6050_hw hw_info[] = { }; static int inv_mpu6050_pwr_mgmt_1_write(struct inv_mpu6050_state *st, bool sleep, - int clock, int temp_dis) + bool cycle, int clock, int temp_dis) { u8 val; @@ -303,6 +303,8 @@ static int inv_mpu6050_pwr_mgmt_1_write(struct inv_mpu6050_state *st, bool sleep val |= INV_MPU6050_BIT_TEMP_DIS; if (sleep) val |= INV_MPU6050_BIT_SLEEP; + if (cycle) + val |= INV_MPU6050_BIT_CYCLE; dev_dbg(regmap_get_device(st->map), "pwr_mgmt_1: 0x%x\n", val); return regmap_write(st->map, st->reg->pwr_mgmt_1, val); @@ -318,7 +320,7 @@ static int inv_mpu6050_clock_switch(struct inv_mpu6050_state *st, case INV_MPU6000: case INV_MPU9150: /* old chips: switch clock manually */ - ret = inv_mpu6050_pwr_mgmt_1_write(st, false, clock, -1); + ret = inv_mpu6050_pwr_mgmt_1_write(st, false, false, clock, -1); if (ret) return ret; st->chip_config.clk = clock; @@ -360,7 +362,7 @@ int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, /* turn on/off temperature sensor */ if (mask & INV_MPU6050_SENSOR_TEMP) { - ret = inv_mpu6050_pwr_mgmt_1_write(st, false, -1, !en); + ret = inv_mpu6050_pwr_mgmt_1_write(st, false, false, -1, !en); if (ret) return ret; st->chip_config.temp_en = en; @@ -467,7 +469,7 @@ static int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, { int result; - result = inv_mpu6050_pwr_mgmt_1_write(st, !power_on, -1, -1); + result = inv_mpu6050_pwr_mgmt_1_write(st, !power_on, false, -1, -1); if (result) return result; @@ -497,22 +499,9 @@ static int inv_mpu6050_set_gyro_fsr(struct inv_mpu6050_state *st, return regmap_write(st->map, st->reg->gyro_config, data); } -/* - * inv_mpu6050_set_lpf_regs() - set low pass filter registers, chip dependent - * - * MPU60xx/MPU9150 use only 1 register for accelerometer + gyroscope - * MPU6500 and above have a dedicated register for accelerometer - */ -static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st, - enum inv_mpu6050_filter_e val) +static int inv_mpu6050_set_accel_lpf_regs(struct inv_mpu6050_state *st, + enum inv_mpu6050_filter_e val) { - int result; - - result = regmap_write(st->map, st->reg->lpf, val); - if (result) - return result; - - /* set accel lpf */ switch (st->chip_type) { case INV_MPU6050: case INV_MPU6000: @@ -531,6 +520,25 @@ static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st, return regmap_write(st->map, st->reg->accel_lpf, val); } +/* + * inv_mpu6050_set_lpf_regs() - set low pass filter registers, chip dependent + * + * MPU60xx/MPU9150 use only 1 register for accelerometer + gyroscope + * MPU6500 and above have a dedicated register for accelerometer + */ +static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st, + enum inv_mpu6050_filter_e val) +{ + int result; + + result = regmap_write(st->map, st->reg->lpf, val); + if (result) + return result; + + /* set accel lpf */ + return inv_mpu6050_set_accel_lpf_regs(st, val); +} + /* * inv_mpu6050_init_config() - Initialize hardware, disable FIFO. * @@ -1002,6 +1010,84 @@ static int inv_mpu6050_set_wom_threshold(struct inv_mpu6050_state *st, u64 value return 0; } +static int inv_mpu6050_set_lp_odr(struct inv_mpu6050_state *st, unsigned int freq_div, + unsigned int *lp_div) +{ + static const unsigned int freq_dividers[] = {2, 4, 8, 16, 32, 64, 128, 256}; + static const unsigned int reg_values[] = { + INV_MPU6050_LPOSC_500HZ, INV_MPU6050_LPOSC_250HZ, + INV_MPU6050_LPOSC_125HZ, INV_MPU6050_LPOSC_62HZ, + INV_MPU6050_LPOSC_31HZ, INV_MPU6050_LPOSC_16HZ, + INV_MPU6050_LPOSC_8HZ, INV_MPU6050_LPOSC_4HZ, + }; + unsigned int val, i; + + switch (st->chip_type) { + case INV_ICM20609: + case INV_ICM20689: + case INV_ICM20600: + case INV_ICM20602: + case INV_ICM20690: + /* nothing to do */ + *lp_div = INV_MPU6050_FREQ_DIVIDER(st); + return 0; + default: + break; + } + + /* found the nearest superior frequency divider */ + i = ARRAY_SIZE(reg_values) - 1; + val = reg_values[i]; + *lp_div = freq_dividers[i]; + for (i = 0; i < ARRAY_SIZE(freq_dividers); ++i) { + if (freq_div <= freq_dividers[i]) { + val = reg_values[i]; + *lp_div = freq_dividers[i]; + break; + } + } + + dev_dbg(regmap_get_device(st->map), "lp_odr: 0x%x\n", val); + return regmap_write(st->map, INV_MPU6500_REG_LP_ODR, val); +} + +static int inv_mpu6050_set_wom_lp(struct inv_mpu6050_state *st, bool on) +{ + unsigned int lp_div; + int result; + + if (on) { + /* set low power ODR */ + result = inv_mpu6050_set_lp_odr(st, INV_MPU6050_FREQ_DIVIDER(st), &lp_div); + if (result) + return result; + /* disable accel low pass filter */ + result = inv_mpu6050_set_accel_lpf_regs(st, INV_MPU6050_FILTER_NOLPF); + if (result) + return result; + /* update wom threshold with new low-power frequency divider */ + result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold, lp_div); + if (result) + return result; + /* set cycle mode */ + result = inv_mpu6050_pwr_mgmt_1_write(st, false, true, -1, -1); + } else { + /* disable cycle mode */ + result = inv_mpu6050_pwr_mgmt_1_write(st, false, false, -1, -1); + if (result) + return result; + /* restore wom threshold */ + result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold, + INV_MPU6050_FREQ_DIVIDER(st)); + if (result) + return result; + /* restore accel low pass filter */ + result = inv_mpu6050_set_accel_lpf_regs(st, st->chip_config.lpf); + } + + return result; +} + static int inv_mpu6050_enable_wom(struct inv_mpu6050_state *st, bool en) { struct device *pdev = regmap_get_device(st->map); @@ -1836,6 +1922,7 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, irq_type); return -EINVAL; } + device_set_wakeup_capable(dev, true); st->vdd_supply = devm_regulator_get(dev, "vdd"); if (IS_ERR(st->vdd_supply)) @@ -2001,16 +2088,27 @@ static int inv_mpu_resume(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct inv_mpu6050_state *st = iio_priv(indio_dev); + bool wakeup; int result; - mutex_lock(&st->lock); - result = inv_mpu_core_enable_regulator_vddio(st); - if (result) - goto out_unlock; + guard(mutex)(&st->lock); - result = inv_mpu6050_set_power_itg(st, true); - if (result) - goto out_unlock; + wakeup = device_may_wakeup(dev) && st->chip_config.wom_en; + + if (wakeup) { + enable_irq(st->irq); + disable_irq_wake(st->irq); + result = inv_mpu6050_set_wom_lp(st, false); + if (result) + return result; + } else { + result = inv_mpu_core_enable_regulator_vddio(st); + if (result) + return result; + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + } pm_runtime_disable(dev); pm_runtime_set_active(dev); @@ -2018,20 +2116,17 @@ static int inv_mpu_resume(struct device *dev) result = inv_mpu6050_switch_engine(st, true, st->suspended_sensors); if (result) - goto out_unlock; + return result; - if (st->chip_config.wom_en) { + if (st->chip_config.wom_en && !wakeup) { result = inv_mpu6050_set_wom_int(st, true); if (result) - goto out_unlock; + return result; } if (iio_buffer_enabled(indio_dev)) result = inv_mpu6050_prepare_fifo(st, true); -out_unlock: - mutex_unlock(&st->lock); - return result; } @@ -2039,29 +2134,30 @@ static int inv_mpu_suspend(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct inv_mpu6050_state *st = iio_priv(indio_dev); + bool wakeup; int result; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->suspended_sensors = 0; - if (pm_runtime_suspended(dev)) { - result = 0; - goto out_unlock; - } + if (pm_runtime_suspended(dev)) + return 0; if (iio_buffer_enabled(indio_dev)) { result = inv_mpu6050_prepare_fifo(st, false); if (result) - goto out_unlock; + return result; } - if (st->chip_config.wom_en) { + wakeup = device_may_wakeup(dev) && st->chip_config.wom_en; + + if (st->chip_config.wom_en && !wakeup) { result = inv_mpu6050_set_wom_int(st, false); if (result) - goto out_unlock; + return result; } - if (st->chip_config.accl_en) + if (st->chip_config.accl_en && !wakeup) st->suspended_sensors |= INV_MPU6050_SENSOR_ACCL; if (st->chip_config.gyro_en) st->suspended_sensors |= INV_MPU6050_SENSOR_GYRO; @@ -2069,21 +2165,26 @@ static int inv_mpu_suspend(struct device *dev) st->suspended_sensors |= INV_MPU6050_SENSOR_TEMP; if (st->chip_config.magn_en) st->suspended_sensors |= INV_MPU6050_SENSOR_MAGN; - if (st->chip_config.wom_en) + if (st->chip_config.wom_en && !wakeup) st->suspended_sensors |= INV_MPU6050_SENSOR_WOM; result = inv_mpu6050_switch_engine(st, false, st->suspended_sensors); if (result) - goto out_unlock; + return result; - result = inv_mpu6050_set_power_itg(st, false); - if (result) - goto out_unlock; + if (wakeup) { + result = inv_mpu6050_set_wom_lp(st, true); + if (result) + return result; + enable_irq_wake(st->irq); + disable_irq(st->irq); + } else { + result = inv_mpu6050_set_power_itg(st, false); + if (result) + return result; + inv_mpu_core_disable_regulator_vddio(st); + } - inv_mpu_core_disable_regulator_vddio(st); -out_unlock: - mutex_unlock(&st->lock); - - return result; + return 0; } static int inv_mpu_runtime_suspend(struct device *dev) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h index f48a8f642c3e..e1c0c5146876 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -305,6 +305,7 @@ struct inv_mpu6050_state { #define INV_MPU6050_REG_PWR_MGMT_1 0x6B #define INV_MPU6050_BIT_H_RESET 0x80 #define INV_MPU6050_BIT_SLEEP 0x40 +#define INV_MPU6050_BIT_CYCLE 0x20 #define INV_MPU6050_BIT_TEMP_DIS 0x08 #define INV_MPU6050_BIT_CLK_MASK 0x7 @@ -336,6 +337,7 @@ struct inv_mpu6050_state { /* mpu6500 registers */ #define INV_MPU6500_REG_ACCEL_CONFIG_2 0x1D #define INV_ICM20689_BITS_FIFO_SIZE_MAX 0xC0 +#define INV_MPU6500_REG_LP_ODR 0x1E #define INV_MPU6500_REG_WOM_THRESHOLD 0x1F #define INV_MPU6500_REG_ACCEL_INTEL_CTRL 0x69 #define INV_MPU6500_BIT_ACCEL_INTEL_EN BIT(7) @@ -452,6 +454,18 @@ enum inv_mpu6050_filter_e { NUM_MPU6050_FILTER }; +enum inv_mpu6050_lposc_e { + INV_MPU6050_LPOSC_4HZ = 4, + INV_MPU6050_LPOSC_8HZ, + INV_MPU6050_LPOSC_16HZ, + INV_MPU6050_LPOSC_31HZ, + INV_MPU6050_LPOSC_62HZ, + INV_MPU6050_LPOSC_125HZ, + INV_MPU6050_LPOSC_250HZ, + INV_MPU6050_LPOSC_500HZ, + NUM_MPU6050_LPOSC, +}; + /* IIO attribute address */ enum INV_MPU6050_IIO_ATTR_ADDR { ATTR_GYRO_MATRIX, From 42ea59925387e8468f490155ffdc382aba2539ae Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Thu, 29 Feb 2024 16:10:25 +0100 Subject: [PATCH 045/108] iio: core: move to cleanup.h magic Use the new cleanup magic for handling mutexes in IIO. This allows us to greatly simplify some code paths. Note that we keep the plain mutex calls in the iio_device_release|acquire() APIs since in there the macros would likely not help much (as we want to keep the lock acquired when he leave the APIs). Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240229-iio-use-cleanup-magic-v3-1-c3d34889ae3c@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 34 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index c7ad88932015..fa7cc051b4c4 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -1810,31 +1811,24 @@ static long iio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) struct iio_dev *indio_dev = ib->indio_dev; struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); struct iio_ioctl_handler *h; - int ret = -ENODEV; - - mutex_lock(&iio_dev_opaque->info_exist_lock); + int ret; + guard(mutex)(&iio_dev_opaque->info_exist_lock); /* * The NULL check here is required to prevent crashing when a device * is being removed while userspace would still have open file handles * to try to access this device. */ if (!indio_dev->info) - goto out_unlock; + return -ENODEV; list_for_each_entry(h, &iio_dev_opaque->ioctl_handlers, entry) { ret = h->ioctl(indio_dev, filp, cmd, arg); if (ret != IIO_IOCTL_UNHANDLED) - break; + return ret; } - if (ret == IIO_IOCTL_UNHANDLED) - ret = -ENODEV; - -out_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return -ENODEV; } static const struct file_operations iio_buffer_fileops = { @@ -2062,18 +2056,16 @@ void iio_device_unregister(struct iio_dev *indio_dev) cdev_device_del(&iio_dev_opaque->chrdev, &indio_dev->dev); - mutex_lock(&iio_dev_opaque->info_exist_lock); + scoped_guard(mutex, &iio_dev_opaque->info_exist_lock) { + iio_device_unregister_debugfs(indio_dev); - iio_device_unregister_debugfs(indio_dev); + iio_disable_all_buffers(indio_dev); - iio_disable_all_buffers(indio_dev); + indio_dev->info = NULL; - indio_dev->info = NULL; - - iio_device_wakeup_eventset(indio_dev); - iio_buffer_wakeup_poll(indio_dev); - - mutex_unlock(&iio_dev_opaque->info_exist_lock); + iio_device_wakeup_eventset(indio_dev); + iio_buffer_wakeup_poll(indio_dev); + } iio_buffers_free_sysfs_and_mask(indio_dev); } From 095be2d5305580122810d5dcad14d61a4d10c023 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Thu, 29 Feb 2024 16:10:26 +0100 Subject: [PATCH 046/108] iio: trigger: move to the cleanup.h magic Use the new cleanup magic for handling mutexes in IIO. This allows us to greatly simplify some code paths. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240229-iio-use-cleanup-magic-v3-2-c3d34889ae3c@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-trigger.c | 71 +++++++++++++----------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c index 18f83158f637..16de57846bd9 100644 --- a/drivers/iio/industrialio-trigger.c +++ b/drivers/iio/industrialio-trigger.c @@ -4,6 +4,7 @@ * Copyright (c) 2008 Jonathan Cameron */ +#include #include #include #include @@ -80,19 +81,18 @@ int iio_trigger_register(struct iio_trigger *trig_info) goto error_unregister_id; /* Add to list of available triggers held by the IIO core */ - mutex_lock(&iio_trigger_list_lock); - if (__iio_trigger_find_by_name(trig_info->name)) { - pr_err("Duplicate trigger name '%s'\n", trig_info->name); - ret = -EEXIST; - goto error_device_del; + scoped_guard(mutex, &iio_trigger_list_lock) { + if (__iio_trigger_find_by_name(trig_info->name)) { + pr_err("Duplicate trigger name '%s'\n", trig_info->name); + ret = -EEXIST; + goto error_device_del; + } + list_add_tail(&trig_info->list, &iio_trigger_list); } - list_add_tail(&trig_info->list, &iio_trigger_list); - mutex_unlock(&iio_trigger_list_lock); return 0; error_device_del: - mutex_unlock(&iio_trigger_list_lock); device_del(&trig_info->dev); error_unregister_id: ida_free(&iio_trigger_ida, trig_info->id); @@ -102,9 +102,8 @@ EXPORT_SYMBOL(iio_trigger_register); void iio_trigger_unregister(struct iio_trigger *trig_info) { - mutex_lock(&iio_trigger_list_lock); - list_del(&trig_info->list); - mutex_unlock(&iio_trigger_list_lock); + scoped_guard(mutex, &iio_trigger_list_lock) + list_del(&trig_info->list); ida_free(&iio_trigger_ida, trig_info->id); /* Possible issue in here */ @@ -120,12 +119,11 @@ int iio_trigger_set_immutable(struct iio_dev *indio_dev, struct iio_trigger *tri return -EINVAL; iio_dev_opaque = to_iio_dev_opaque(indio_dev); - mutex_lock(&iio_dev_opaque->mlock); + guard(mutex)(&iio_dev_opaque->mlock); WARN_ON(iio_dev_opaque->trig_readonly); indio_dev->trig = iio_trigger_get(trig); iio_dev_opaque->trig_readonly = true; - mutex_unlock(&iio_dev_opaque->mlock); return 0; } @@ -145,18 +143,14 @@ static struct iio_trigger *__iio_trigger_find_by_name(const char *name) static struct iio_trigger *iio_trigger_acquire_by_name(const char *name) { - struct iio_trigger *trig = NULL, *iter; + struct iio_trigger *iter; - mutex_lock(&iio_trigger_list_lock); + guard(mutex)(&iio_trigger_list_lock); list_for_each_entry(iter, &iio_trigger_list, list) - if (sysfs_streq(iter->name, name)) { - trig = iter; - iio_trigger_get(trig); - break; - } - mutex_unlock(&iio_trigger_list_lock); + if (sysfs_streq(iter->name, name)) + return iio_trigger_get(iter); - return trig; + return NULL; } static void iio_reenable_work_fn(struct work_struct *work) @@ -259,22 +253,21 @@ static int iio_trigger_get_irq(struct iio_trigger *trig) { int ret; - mutex_lock(&trig->pool_lock); - ret = bitmap_find_free_region(trig->pool, - CONFIG_IIO_CONSUMERS_PER_TRIGGER, - ilog2(1)); - mutex_unlock(&trig->pool_lock); - if (ret >= 0) - ret += trig->subirq_base; + scoped_guard(mutex, &trig->pool_lock) { + ret = bitmap_find_free_region(trig->pool, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + ilog2(1)); + if (ret < 0) + return ret; + } - return ret; + return ret + trig->subirq_base; } static void iio_trigger_put_irq(struct iio_trigger *trig, int irq) { - mutex_lock(&trig->pool_lock); + guard(mutex)(&trig->pool_lock); clear_bit(irq - trig->subirq_base, trig->pool); - mutex_unlock(&trig->pool_lock); } /* Complexity in here. With certain triggers (datardy) an acknowledgement @@ -451,16 +444,12 @@ static ssize_t current_trigger_store(struct device *dev, struct iio_trigger *trig; int ret; - mutex_lock(&iio_dev_opaque->mlock); - if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED) { - mutex_unlock(&iio_dev_opaque->mlock); - return -EBUSY; + scoped_guard(mutex, &iio_dev_opaque->mlock) { + if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED) + return -EBUSY; + if (iio_dev_opaque->trig_readonly) + return -EPERM; } - if (iio_dev_opaque->trig_readonly) { - mutex_unlock(&iio_dev_opaque->mlock); - return -EPERM; - } - mutex_unlock(&iio_dev_opaque->mlock); trig = iio_trigger_acquire_by_name(buf); if (oldtrig == trig) { From 714b5b4c2c2431d830f0d5e9e85282beef40c747 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Thu, 29 Feb 2024 16:10:27 +0100 Subject: [PATCH 047/108] iio: buffer: iio: core: move to the cleanup.h magic Use the new cleanup magic for handling mutexes in IIO. This allows us to greatly simplify some code paths. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240229-iio-use-cleanup-magic-v3-3-c3d34889ae3c@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 122 ++++++++++++------------------ 1 file changed, 48 insertions(+), 74 deletions(-) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index b581a7e80566..1d950a3e153b 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -10,6 +10,7 @@ * - Alternative access techniques? */ #include +#include #include #include #include @@ -533,28 +534,26 @@ static ssize_t iio_scan_el_store(struct device *dev, ret = kstrtobool(buf, &state); if (ret < 0) return ret; - mutex_lock(&iio_dev_opaque->mlock); - if (iio_buffer_is_active(buffer)) { - ret = -EBUSY; - goto error_ret; - } + + guard(mutex)(&iio_dev_opaque->mlock); + if (iio_buffer_is_active(buffer)) + return -EBUSY; + ret = iio_scan_mask_query(indio_dev, buffer, this_attr->address); if (ret < 0) - goto error_ret; - if (!state && ret) { - ret = iio_scan_mask_clear(buffer, this_attr->address); - if (ret) - goto error_ret; - } else if (state && !ret) { + return ret; + + if (state && ret) + return len; + + if (state) ret = iio_scan_mask_set(indio_dev, buffer, this_attr->address); - if (ret) - goto error_ret; - } + else + ret = iio_scan_mask_clear(buffer, this_attr->address); + if (ret) + return ret; -error_ret: - mutex_unlock(&iio_dev_opaque->mlock); - - return ret < 0 ? ret : len; + return len; } static ssize_t iio_scan_el_ts_show(struct device *dev, @@ -581,16 +580,13 @@ static ssize_t iio_scan_el_ts_store(struct device *dev, if (ret < 0) return ret; - mutex_lock(&iio_dev_opaque->mlock); - if (iio_buffer_is_active(buffer)) { - ret = -EBUSY; - goto error_ret; - } - buffer->scan_timestamp = state; -error_ret: - mutex_unlock(&iio_dev_opaque->mlock); + guard(mutex)(&iio_dev_opaque->mlock); + if (iio_buffer_is_active(buffer)) + return -EBUSY; - return ret ? ret : len; + buffer->scan_timestamp = state; + + return len; } static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev, @@ -674,21 +670,16 @@ static ssize_t length_store(struct device *dev, struct device_attribute *attr, if (val == buffer->length) return len; - mutex_lock(&iio_dev_opaque->mlock); - if (iio_buffer_is_active(buffer)) { - ret = -EBUSY; - } else { - buffer->access->set_length(buffer, val); - ret = 0; - } - if (ret) - goto out; + guard(mutex)(&iio_dev_opaque->mlock); + if (iio_buffer_is_active(buffer)) + return -EBUSY; + + buffer->access->set_length(buffer, val); + if (buffer->length && buffer->length < buffer->watermark) buffer->watermark = buffer->length; -out: - mutex_unlock(&iio_dev_opaque->mlock); - return ret ? ret : len; + return len; } static ssize_t enable_show(struct device *dev, struct device_attribute *attr, @@ -1268,7 +1259,6 @@ int iio_update_buffers(struct iio_dev *indio_dev, struct iio_buffer *remove_buffer) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); - int ret; if (insert_buffer == remove_buffer) return 0; @@ -1277,8 +1267,8 @@ int iio_update_buffers(struct iio_dev *indio_dev, insert_buffer->direction == IIO_BUFFER_DIRECTION_OUT) return -EINVAL; - mutex_lock(&iio_dev_opaque->info_exist_lock); - mutex_lock(&iio_dev_opaque->mlock); + guard(mutex)(&iio_dev_opaque->info_exist_lock); + guard(mutex)(&iio_dev_opaque->mlock); if (insert_buffer && iio_buffer_is_active(insert_buffer)) insert_buffer = NULL; @@ -1286,23 +1276,13 @@ int iio_update_buffers(struct iio_dev *indio_dev, if (remove_buffer && !iio_buffer_is_active(remove_buffer)) remove_buffer = NULL; - if (!insert_buffer && !remove_buffer) { - ret = 0; - goto out_unlock; - } + if (!insert_buffer && !remove_buffer) + return 0; - if (!indio_dev->info) { - ret = -ENODEV; - goto out_unlock; - } + if (!indio_dev->info) + return -ENODEV; - ret = __iio_update_buffers(indio_dev, insert_buffer, remove_buffer); - -out_unlock: - mutex_unlock(&iio_dev_opaque->mlock); - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return __iio_update_buffers(indio_dev, insert_buffer, remove_buffer); } EXPORT_SYMBOL_GPL(iio_update_buffers); @@ -1326,22 +1306,22 @@ static ssize_t enable_store(struct device *dev, struct device_attribute *attr, if (ret < 0) return ret; - mutex_lock(&iio_dev_opaque->mlock); + guard(mutex)(&iio_dev_opaque->mlock); /* Find out if it is in the list */ inlist = iio_buffer_is_active(buffer); /* Already in desired state */ if (inlist == requested_state) - goto done; + return len; if (requested_state) ret = __iio_update_buffers(indio_dev, buffer, NULL); else ret = __iio_update_buffers(indio_dev, NULL, buffer); + if (ret) + return ret; -done: - mutex_unlock(&iio_dev_opaque->mlock); - return (ret < 0) ? ret : len; + return len; } static ssize_t watermark_show(struct device *dev, struct device_attribute *attr, @@ -1368,23 +1348,17 @@ static ssize_t watermark_store(struct device *dev, if (!val) return -EINVAL; - mutex_lock(&iio_dev_opaque->mlock); + guard(mutex)(&iio_dev_opaque->mlock); - if (val > buffer->length) { - ret = -EINVAL; - goto out; - } + if (val > buffer->length) + return -EINVAL; - if (iio_buffer_is_active(buffer)) { - ret = -EBUSY; - goto out; - } + if (iio_buffer_is_active(buffer)) + return -EBUSY; buffer->watermark = val; -out: - mutex_unlock(&iio_dev_opaque->mlock); - return ret ? ret : len; + return len; } static ssize_t data_available_show(struct device *dev, From 3092bde731ca8c499fa8a4a9e987904abfbf55bf Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Thu, 29 Feb 2024 16:10:28 +0100 Subject: [PATCH 048/108] iio: inkern: move to the cleanup.h magic Use the new cleanup magic for handling mutexes in IIO. This allows us to greatly simplify some code paths. While at it, also use __free(kfree) where allocations are done and drop obvious comment in iio_channel_read_min(). Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240229-iio-use-cleanup-magic-v3-4-c3d34889ae3c@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/inkern.c | 263 +++++++++++++++---------------------------- 1 file changed, 89 insertions(+), 174 deletions(-) diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index 7a1f6713318a..52d773261828 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -3,6 +3,7 @@ * * Copyright (c) 2011 Jonathan Cameron */ +#include #include #include #include @@ -43,13 +44,14 @@ static int iio_map_array_unregister_locked(struct iio_dev *indio_dev) int iio_map_array_register(struct iio_dev *indio_dev, struct iio_map *maps) { - int i = 0, ret = 0; struct iio_map_internal *mapi; + int i = 0; + int ret; if (!maps) return 0; - mutex_lock(&iio_map_list_lock); + guard(mutex)(&iio_map_list_lock); while (maps[i].consumer_dev_name) { mapi = kzalloc(sizeof(*mapi), GFP_KERNEL); if (!mapi) { @@ -61,11 +63,10 @@ int iio_map_array_register(struct iio_dev *indio_dev, struct iio_map *maps) list_add_tail(&mapi->l, &iio_map_list); i++; } -error_ret: - if (ret) - iio_map_array_unregister_locked(indio_dev); - mutex_unlock(&iio_map_list_lock); + return 0; +error_ret: + iio_map_array_unregister_locked(indio_dev); return ret; } EXPORT_SYMBOL_GPL(iio_map_array_register); @@ -75,13 +76,8 @@ EXPORT_SYMBOL_GPL(iio_map_array_register); */ int iio_map_array_unregister(struct iio_dev *indio_dev) { - int ret; - - mutex_lock(&iio_map_list_lock); - ret = iio_map_array_unregister_locked(indio_dev); - mutex_unlock(&iio_map_list_lock); - - return ret; + guard(mutex)(&iio_map_list_lock); + return iio_map_array_unregister_locked(indio_dev); } EXPORT_SYMBOL_GPL(iio_map_array_unregister); @@ -183,25 +179,21 @@ static int __fwnode_iio_channel_get(struct iio_channel *channel, static struct iio_channel *fwnode_iio_channel_get(struct fwnode_handle *fwnode, int index) { - struct iio_channel *channel; int err; if (index < 0) return ERR_PTR(-EINVAL); - channel = kzalloc(sizeof(*channel), GFP_KERNEL); + struct iio_channel *channel __free(kfree) = + kzalloc(sizeof(*channel), GFP_KERNEL); if (!channel) return ERR_PTR(-ENOMEM); err = __fwnode_iio_channel_get(channel, fwnode, index); if (err) - goto err_free_channel; + return ERR_PTR(err); - return channel; - -err_free_channel: - kfree(channel); - return ERR_PTR(err); + return_ptr(channel); } static struct iio_channel * @@ -291,7 +283,6 @@ EXPORT_SYMBOL_GPL(fwnode_iio_channel_get_by_name); static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev) { struct fwnode_handle *fwnode = dev_fwnode(dev); - struct iio_channel *chans; int i, mapind, nummaps = 0; int ret; @@ -307,7 +298,8 @@ static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev) return ERR_PTR(-ENODEV); /* NULL terminated array to save passing size */ - chans = kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL); + struct iio_channel *chans __free(kfree) = + kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL); if (!chans) return ERR_PTR(-ENOMEM); @@ -317,12 +309,11 @@ static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev) if (ret) goto error_free_chans; } - return chans; + return_ptr(chans); error_free_chans: for (i = 0; i < mapind; i++) iio_device_put(chans[i].indio_dev); - kfree(chans); return ERR_PTR(ret); } @@ -330,28 +321,28 @@ static struct iio_channel *iio_channel_get_sys(const char *name, const char *channel_name) { struct iio_map_internal *c_i = NULL, *c = NULL; - struct iio_channel *channel; int err; if (!(name || channel_name)) return ERR_PTR(-ENODEV); /* first find matching entry the channel map */ - mutex_lock(&iio_map_list_lock); - list_for_each_entry(c_i, &iio_map_list, l) { - if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) || - (channel_name && - strcmp(channel_name, c_i->map->consumer_channel) != 0)) - continue; - c = c_i; - iio_device_get(c->indio_dev); - break; + scoped_guard(mutex, &iio_map_list_lock) { + list_for_each_entry(c_i, &iio_map_list, l) { + if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) || + (channel_name && + strcmp(channel_name, c_i->map->consumer_channel) != 0)) + continue; + c = c_i; + iio_device_get(c->indio_dev); + break; + } } - mutex_unlock(&iio_map_list_lock); if (!c) return ERR_PTR(-ENODEV); - channel = kzalloc(sizeof(*channel), GFP_KERNEL); + struct iio_channel *channel __free(kfree) = + kzalloc(sizeof(*channel), GFP_KERNEL); if (!channel) { err = -ENOMEM; goto error_no_mem; @@ -366,14 +357,12 @@ static struct iio_channel *iio_channel_get_sys(const char *name, if (!channel->channel) { err = -EINVAL; - goto error_no_chan; + goto error_no_mem; } } - return channel; + return_ptr(channel); -error_no_chan: - kfree(channel); error_no_mem: iio_device_put(c->indio_dev); return ERR_PTR(err); @@ -450,8 +439,8 @@ EXPORT_SYMBOL_GPL(devm_fwnode_iio_channel_get_by_name); struct iio_channel *iio_channel_get_all(struct device *dev) { const char *name; - struct iio_channel *chans; struct iio_map_internal *c = NULL; + struct iio_channel *fw_chans; int nummaps = 0; int mapind = 0; int i, ret; @@ -459,17 +448,17 @@ struct iio_channel *iio_channel_get_all(struct device *dev) if (!dev) return ERR_PTR(-EINVAL); - chans = fwnode_iio_channel_get_all(dev); + fw_chans = fwnode_iio_channel_get_all(dev); /* * We only want to carry on if the error is -ENODEV. Anything else * should be reported up the stack. */ - if (!IS_ERR(chans) || PTR_ERR(chans) != -ENODEV) - return chans; + if (!IS_ERR(fw_chans) || PTR_ERR(fw_chans) != -ENODEV) + return fw_chans; name = dev_name(dev); - mutex_lock(&iio_map_list_lock); + guard(mutex)(&iio_map_list_lock); /* first count the matching maps */ list_for_each_entry(c, &iio_map_list, l) if (name && strcmp(name, c->map->consumer_dev_name) != 0) @@ -477,17 +466,14 @@ struct iio_channel *iio_channel_get_all(struct device *dev) else nummaps++; - if (nummaps == 0) { - ret = -ENODEV; - goto error_ret; - } + if (nummaps == 0) + return ERR_PTR(-ENODEV); /* NULL terminated array to save passing size */ - chans = kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL); - if (!chans) { - ret = -ENOMEM; - goto error_ret; - } + struct iio_channel *chans __free(kfree) = + kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL); + if (!chans) + return ERR_PTR(-ENOMEM); /* for each map fill in the chans element */ list_for_each_entry(c, &iio_map_list, l) { @@ -509,17 +495,12 @@ struct iio_channel *iio_channel_get_all(struct device *dev) ret = -ENODEV; goto error_free_chans; } - mutex_unlock(&iio_map_list_lock); - return chans; + return_ptr(chans); error_free_chans: for (i = 0; i < nummaps; i++) iio_device_put(chans[i].indio_dev); - kfree(chans); -error_ret: - mutex_unlock(&iio_map_list_lock); - return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(iio_channel_get_all); @@ -590,38 +571,24 @@ static int iio_channel_read(struct iio_channel *chan, int *val, int *val2, int iio_read_channel_raw(struct iio_channel *chan, int *val) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); } EXPORT_SYMBOL_GPL(iio_read_channel_raw); int iio_read_channel_average_raw(struct iio_channel *chan, int *val) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW); } EXPORT_SYMBOL_GPL(iio_read_channel_average_raw); @@ -708,20 +675,13 @@ int iio_convert_raw_to_processed(struct iio_channel *chan, int raw, int *processed, unsigned int scale) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_convert_raw_to_processed_unlocked(chan, raw, processed, - scale); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_convert_raw_to_processed_unlocked(chan, raw, processed, + scale); } EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed); @@ -729,19 +689,12 @@ int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2, enum iio_chan_info_enum attribute) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_channel_read(chan, val, val2, attribute); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_channel_read(chan, val, val2, attribute); } EXPORT_SYMBOL_GPL(iio_read_channel_attribute); @@ -757,30 +710,26 @@ int iio_read_channel_processed_scale(struct iio_channel *chan, int *val, struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); int ret; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) { ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_PROCESSED); if (ret < 0) - goto err_unlock; + return ret; *val *= scale; + + return 0; } else { ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); if (ret < 0) - goto err_unlock; - ret = iio_convert_raw_to_processed_unlocked(chan, *val, val, - scale); + return ret; + + return iio_convert_raw_to_processed_unlocked(chan, *val, val, + scale); } - -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; } EXPORT_SYMBOL_GPL(iio_read_channel_processed_scale); @@ -813,19 +762,12 @@ int iio_read_avail_channel_attribute(struct iio_channel *chan, enum iio_chan_info_enum attribute) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_channel_read_avail(chan, vals, type, length, attribute); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_channel_read_avail(chan, vals, type, length, attribute); } EXPORT_SYMBOL_GPL(iio_read_avail_channel_attribute); @@ -892,20 +834,13 @@ static int iio_channel_read_max(struct iio_channel *chan, int iio_read_max_channel_raw(struct iio_channel *chan, int *val) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; int type; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW); } EXPORT_SYMBOL_GPL(iio_read_max_channel_raw); @@ -955,40 +890,27 @@ static int iio_channel_read_min(struct iio_channel *chan, int iio_read_min_channel_raw(struct iio_channel *chan, int *val) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; int type; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW); } EXPORT_SYMBOL_GPL(iio_read_min_channel_raw); int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret = 0; - /* Need to verify underlying driver has not gone away */ - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; *type = chan->channel->type; -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - return ret; + return 0; } EXPORT_SYMBOL_GPL(iio_get_channel_type); @@ -1003,19 +925,12 @@ int iio_write_channel_attribute(struct iio_channel *chan, int val, int val2, enum iio_chan_info_enum attribute) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; - mutex_lock(&iio_dev_opaque->info_exist_lock); - if (!chan->indio_dev->info) { - ret = -ENODEV; - goto err_unlock; - } + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; - ret = iio_channel_write(chan, val, val2, attribute); -err_unlock: - mutex_unlock(&iio_dev_opaque->info_exist_lock); - - return ret; + return iio_channel_write(chan, val, val2, attribute); } EXPORT_SYMBOL_GPL(iio_write_channel_attribute); From fcdb03e4310695013f6a6c5a29485b02b13e2662 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 15 Mar 2024 09:14:36 +0000 Subject: [PATCH 049/108] iio: accel: adxl367: Remove second semicolon There is a statement with two semicolons. Remove the second one, it is redundant. Signed-off-by: Colin Ian King Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240315091436.2430227-1-colin.i.king@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl367.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c index 210228affb80..5cf4828a5eb5 100644 --- a/drivers/iio/accel/adxl367.c +++ b/drivers/iio/accel/adxl367.c @@ -621,7 +621,7 @@ static int _adxl367_set_odr(struct adxl367_state *st, enum adxl367_odr odr) static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr) { iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { - struct adxl367_state *st = iio_priv(indio_dev);; + struct adxl367_state *st = iio_priv(indio_dev); int ret; guard(mutex)(&st->lock); From 346ae0e8276fe6fc5f0b7496e53343e1739ff5b0 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 14 Mar 2024 12:43:38 -0500 Subject: [PATCH 050/108] iio: adc: ad7944: Add support for "3-wire mode" This adds support for AD7944 ADCs wired in "3-wire mode". (NOTE: 3-wire is the datasheet name for this wiring configuration and has nothing to do with SPI_3WIRE.) In the 3-wire mode, the SPI controller CS line can be wired to the CNV line on the ADC and used to trigger conversions rather that using a separate GPIO line. The turbo/chain mode compatibility check at the end of the probe function is technically can't be triggered right now but adding it now anyway so that we don't forget to add it later when support for daisy-chaining is added. Reviewed-by: Nuno Sa Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240314-mainline-ad7944-3-wire-mode-v2-1-d469da0705d2@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7944.c | 157 ++++++++++++++++++++++++++++++++++----- 1 file changed, 139 insertions(+), 18 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index adb007cdd287..d5ec6b5a41c7 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -32,8 +32,25 @@ struct ad7944_timing_spec { unsigned int turbo_conv_ns; }; +enum ad7944_spi_mode { + /* datasheet calls this "4-wire mode" */ + AD7944_SPI_MODE_DEFAULT, + /* datasheet calls this "3-wire mode" (not related to SPI_3WIRE!) */ + AD7944_SPI_MODE_SINGLE, + /* datasheet calls this "chain mode" */ + AD7944_SPI_MODE_CHAIN, +}; + +/* maps adi,spi-mode property value to enum */ +static const char * const ad7944_spi_modes[] = { + [AD7944_SPI_MODE_DEFAULT] = "", + [AD7944_SPI_MODE_SINGLE] = "single", + [AD7944_SPI_MODE_CHAIN] = "chain", +}; + struct ad7944_adc { struct spi_device *spi; + enum ad7944_spi_mode spi_mode; /* Chip-specific timing specifications. */ const struct ad7944_timing_spec *timing_spec; /* GPIO connected to CNV pin. */ @@ -58,6 +75,9 @@ struct ad7944_adc { } sample __aligned(IIO_DMA_MINALIGN); }; +/* quite time before CNV rising edge */ +#define T_QUIET_NS 20 + static const struct ad7944_timing_spec ad7944_timing_spec = { .conv_ns = 420, .turbo_conv_ns = 320, @@ -110,6 +130,65 @@ AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0); /* fully differential */ AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); +/* + * ad7944_3wire_cs_mode_conversion - Perform a 3-wire CS mode conversion and + * acquisition + * @adc: The ADC device structure + * @chan: The channel specification + * Return: 0 on success, a negative error code on failure + * + * This performs a conversion and reads data when the chip is wired in 3-wire + * mode with the CNV line on the ADC tied to the CS line on the SPI controller. + * + * Upon successful return adc->sample.raw will contain the conversion result. + */ +static int ad7944_3wire_cs_mode_conversion(struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer xfers[] = { + { + /* + * NB: can get better performance from some SPI + * controllers if we use the same bits_per_word + * in every transfer. + */ + .bits_per_word = chan->scan_type.realbits, + /* + * CS is tied to CNV and we need a low to high + * transition to start the conversion, so place CNV + * low for t_QUIET to prepare for this. + */ + .delay = { + .value = T_QUIET_NS, + .unit = SPI_DELAY_UNIT_NSECS, + }, + + }, + { + .bits_per_word = chan->scan_type.realbits, + /* + * CS has to be high for full conversion time to avoid + * triggering the busy indication. + */ + .cs_off = 1, + .delay = { + .value = t_conv_ns, + .unit = SPI_DELAY_UNIT_NSECS, + }, + }, + { + /* Then we can read the data during the acquisition phase */ + .rx_buf = &adc->sample.raw, + .len = BITS_TO_BYTES(chan->scan_type.storagebits), + .bits_per_word = chan->scan_type.realbits, + }, + }; + + return spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); +} + /* * ad7944_4wire_mode_conversion - Perform a 4-wire mode conversion and acquisition * @adc: The ADC device structure @@ -167,9 +246,22 @@ static int ad7944_single_conversion(struct ad7944_adc *adc, { int ret; - ret = ad7944_4wire_mode_conversion(adc, chan); - if (ret) - return ret; + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + ret = ad7944_4wire_mode_conversion(adc, chan); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_SINGLE: + ret = ad7944_3wire_cs_mode_conversion(adc, chan); + if (ret) + return ret; + + break; + default: + return -EOPNOTSUPP; + } if (chan->scan_type.storagebits > 16) *val = adc->sample.raw.u32; @@ -230,9 +322,23 @@ static irqreturn_t ad7944_trigger_handler(int irq, void *p) struct ad7944_adc *adc = iio_priv(indio_dev); int ret; - ret = ad7944_4wire_mode_conversion(adc, &indio_dev->channels[0]); - if (ret) + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + ret = ad7944_4wire_mode_conversion(adc, &indio_dev->channels[0]); + if (ret) + goto out; + + break; + case AD7944_SPI_MODE_SINGLE: + ret = ad7944_3wire_cs_mode_conversion(adc, &indio_dev->channels[0]); + if (ret) + goto out; + + break; + default: + /* not supported */ goto out; + } iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, pf->timestamp); @@ -260,16 +366,9 @@ static int ad7944_probe(struct spi_device *spi) struct ad7944_adc *adc; bool have_refin = false; struct regulator *ref; + const char *str_val; int ret; - /* - * driver currently only supports the conventional "4-wire" mode and - * not other special wiring configurations. - */ - if (device_property_present(dev, "adi,spi-mode")) - return dev_err_probe(dev, -EINVAL, - "adi,spi-mode is not currently supported\n"); - indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); if (!indio_dev) return -ENOMEM; @@ -283,6 +382,22 @@ static int ad7944_probe(struct spi_device *spi) adc->timing_spec = chip_info->timing_spec; + if (device_property_read_string(dev, "adi,spi-mode", &str_val) == 0) { + ret = sysfs_match_string(ad7944_spi_modes, str_val); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, + "unsupported adi,spi-mode\n"); + + adc->spi_mode = ret; + } else { + /* absence of adi,spi-mode property means default mode */ + adc->spi_mode = AD7944_SPI_MODE_DEFAULT; + } + + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) + return dev_err_probe(dev, -EINVAL, + "chain mode is not implemented\n"); + /* * Some chips use unusual word sizes, so check now instead of waiting * for the first xfer. @@ -349,15 +464,17 @@ static int ad7944_probe(struct spi_device *spi) adc->ref_mv = AD7944_INTERNAL_REF_MV; } - /* - * CNV gpio is required in 4-wire mode which is the only currently - * supported mode. - */ - adc->cnv = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW); + adc->cnv = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW); if (IS_ERR(adc->cnv)) return dev_err_probe(dev, PTR_ERR(adc->cnv), "failed to get CNV GPIO\n"); + if (!adc->cnv && adc->spi_mode == AD7944_SPI_MODE_DEFAULT) + return dev_err_probe(&spi->dev, -EINVAL, "CNV GPIO is required\n"); + if (adc->cnv && adc->spi_mode != AD7944_SPI_MODE_DEFAULT) + return dev_err_probe(&spi->dev, -EINVAL, + "CNV GPIO in single and chain mode is not currently supported\n"); + adc->turbo = devm_gpiod_get_optional(dev, "turbo", GPIOD_OUT_LOW); if (IS_ERR(adc->turbo)) return dev_err_probe(dev, PTR_ERR(adc->turbo), @@ -369,6 +486,10 @@ static int ad7944_probe(struct spi_device *spi) return dev_err_probe(dev, -EINVAL, "cannot have both turbo-gpios and adi,always-turbo\n"); + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN && adc->always_turbo) + return dev_err_probe(dev, -EINVAL, + "cannot have both chain mode and always turbo\n"); + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &ad7944_iio_info; From 74f7ffd684334dfae365d708633e9e73256bdd11 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 22 Mar 2024 16:52:13 -0500 Subject: [PATCH 051/108] MAINTAINERS: add Documentation/iio/ to IIO subsystem Patches touching the IIO subsystem documentation should also be sent to the IIO mailing list. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240322-mainline-ad7944-doc-v2-1-0923d35d5596@baylibre.com Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index eefd1a8906bf..c3ddf2754780 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10554,6 +10554,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git F: Documentation/ABI/testing/configfs-iio* F: Documentation/ABI/testing/sysfs-bus-iio* F: Documentation/devicetree/bindings/iio/ +F: Documentation/iio/ F: drivers/iio/ F: drivers/staging/iio/ F: include/dt-bindings/iio/ From 18b51455e618d0de57975ab47cef85666fb92636 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 22 Mar 2024 16:52:14 -0500 Subject: [PATCH 052/108] docs: iio: new docs for ad7944 driver This adds a new page to document how to use the ad7944 ADC driver. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240322-mainline-ad7944-doc-v2-2-0923d35d5596@baylibre.com Signed-off-by: Jonathan Cameron --- Documentation/iio/ad7944.rst | 130 +++++++++++++++++++++++++++++++++++ Documentation/iio/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 132 insertions(+) create mode 100644 Documentation/iio/ad7944.rst diff --git a/Documentation/iio/ad7944.rst b/Documentation/iio/ad7944.rst new file mode 100644 index 000000000000..f418ab1288ae --- /dev/null +++ b/Documentation/iio/ad7944.rst @@ -0,0 +1,130 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +AD7944 driver +============= + +ADC driver for Analog Devices Inc. AD7944 and similar devices. The module name +is ``ad7944``. + + +Supported devices +================= + +The following chips are supported by this driver: + +* `AD7944 `_ +* `AD7985 `_ +* `AD7986 `_ + + +Supported features +================== + +SPI wiring modes +---------------- + +The driver currently supports two of the many possible SPI wiring configurations. + +CS mode, 3-wire, without busy indicator +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: + + +-------------+ + +--------------------| CS | + v | | + VIO +--------------------+ | HOST | + | | CNV | | | + +--->| SDI AD7944 SDO |-------->| SDI | + | SCK | | | + +--------------------+ | | + ^ | | + +--------------------| SCLK | + +-------------+ + +To select this mode in the device tree, set the ``adi,spi-mode`` property to +``"single"`` and omit the ``cnv-gpios`` property. + +CS mode, 4-wire, without busy indicator +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: + + +-------------+ + +-----------------------------------| CS | + | | | + | +--------------------| GPIO | + | v | | + | +--------------------+ | HOST | + | | CNV | | | + +--->| SDI AD7944 SDO |-------->| SDI | + | SCK | | | + +--------------------+ | | + ^ | | + +--------------------| SCLK | + +-------------+ + +To select this mode in the device tree, omit the ``adi,spi-mode`` property and +provide the ``cnv-gpios`` property. + +Reference voltage +----------------- + +All 3 possible reference voltage sources are supported: + +- Internal reference +- External 1.2V reference and internal buffer +- External reference + +The source is determined by the device tree. If ``ref-supply`` is present, then +the external reference is used. If ``refin-supply`` is present, then the internal +buffer is used. If neither is present, then the internal reference is used. + +Unimplemented features +---------------------- + +- ``BUSY`` indication +- ``TURBO`` mode +- Daisy chain mode + + +Device attributes +================= + +There are two types of ADCs in this family, pseudo-differential and fully +differential. The channel name is different depending on the type of ADC. + +Pseudo-differential ADCs +------------------------ + +AD7944 and AD7985 are pseudo-differential ADCs and have the following attributes: + ++---------------------------------------+--------------------------------------------------------------+ +| Attribute | Description | ++=======================================+==============================================================+ +| ``in_voltage0_raw`` | Raw ADC voltage value (*IN+* referenced to ground sense). | ++---------------------------------------+--------------------------------------------------------------+ +| ``in_voltage0_scale`` | Scale factor to convert raw value to mV. | ++---------------------------------------+--------------------------------------------------------------+ + +Fully-differential ADCs +----------------------- + +AD7986 is a fully-differential ADC and has the following attributes: + ++---------------------------------------+--------------------------------------------------------------+ +| Attribute | Description | ++=======================================+==============================================================+ +| ``in_voltage0-voltage1_raw`` | Raw ADC voltage value (*IN+* - *IN-*). | ++---------------------------------------+--------------------------------------------------------------+ +| ``in_voltage0-voltage1_scale`` | Scale factor to convert raw value to mV. | ++---------------------------------------+--------------------------------------------------------------+ + + +Device buffers +============== + +This driver supports IIO triggered buffers. + +See :doc:`iio_devbuf` for more information. diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index 30b09eefe75e..fb6f9d743211 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -16,6 +16,7 @@ Industrial I/O Kernel Drivers .. toctree:: :maxdepth: 1 + ad7944 adis16475 bno055 ep93xx_adc diff --git a/MAINTAINERS b/MAINTAINERS index c3ddf2754780..a7287cf44869 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -448,6 +448,7 @@ R: David Lechner S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml +F: Documentation/iio/ad7944.rst F: drivers/iio/adc/ad7944.c ADAFRUIT MINI I2C GAMEPAD From 905908546cb883079971b7e150537d260df64f00 Mon Sep 17 00:00:00 2001 From: Vasileios Amoiridis Date: Tue, 19 Mar 2024 01:29:20 +0100 Subject: [PATCH 053/108] iio: pressure: BMP280 core driver headers sorting Sort headers in alphabetical order. Signed-off-by: Vasileios Amoiridis Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240319002925.2121016-2-vassilisamir@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/bmp280-core.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c index fe8734468ed3..871b2214121b 100644 --- a/drivers/iio/pressure/bmp280-core.c +++ b/drivers/iio/pressure/bmp280-core.c @@ -27,20 +27,20 @@ #include #include -#include -#include -#include -#include +#include #include -#include -#include +#include #include -#include #include #include /* For irq_get_irq_data() */ -#include +#include +#include #include #include +#include +#include + +#include #include From 86156cadbeff81c194e18e78c23585a8aa673abc Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 24 Mar 2024 13:36:16 +0100 Subject: [PATCH 054/108] iio: pressure: hsc030pa: Use spi_read() Use spi_read() instead of hand-writing it. It is less verbose. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/8327ac591d244ac85aa83c01e559076159c7ba12.1711283728.git.christophe.jaillet@wanadoo.fr Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/hsc030pa_spi.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/iio/pressure/hsc030pa_spi.c b/drivers/iio/pressure/hsc030pa_spi.c index 818fa6303454..337eecc577d2 100644 --- a/drivers/iio/pressure/hsc030pa_spi.c +++ b/drivers/iio/pressure/hsc030pa_spi.c @@ -23,14 +23,9 @@ static int hsc_spi_recv(struct hsc_data *data) { struct spi_device *spi = to_spi_device(data->dev); - struct spi_transfer xfer = { - .tx_buf = NULL, - .rx_buf = data->buffer, - .len = HSC_REG_MEASUREMENT_RD_SIZE, - }; msleep_interruptible(HSC_RESP_TIME_MS); - return spi_sync_transfer(spi, &xfer, 1); + return spi_read(spi, data->buffer, HSC_REG_MEASUREMENT_RD_SIZE); } static int hsc_spi_probe(struct spi_device *spi) From 0a2c44324f3be607345834234259c0a1eaf65c1e Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 24 Mar 2024 20:20:28 +0100 Subject: [PATCH 055/108] dt-bindings: iio: health: maxim,max30102: add max30101 The Maxim max30101 is the replacement for the max30105, which is no longer recommended for future designs. The max30101 does not require new properties, and it can be described with the existing ones for the max30105, which will be used as a fallback compatible. Signed-off-by: Javier Carrasco Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240324-max30101-v2-1-611deb510c97@gmail.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/health/maxim,max30102.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml index c13c10c8d65d..a0fe0a818d86 100644 --- a/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml +++ b/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml @@ -4,16 +4,20 @@ $id: http://devicetree.org/schemas/iio/health/maxim,max30102.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Maxim MAX30102 heart rate and pulse oximeter and MAX30105 particle-sensor +title: Maxim MAX30101/2 heart rate and pulse oximeter and MAX30105 particle-sensor maintainers: - Matt Ranostay properties: compatible: - enum: - - maxim,max30102 - - maxim,max30105 + oneOf: + - enum: + - maxim,max30102 + - maxim,max30105 + - items: + - const: maxim,max30101 + - const: maxim,max30105 reg: maxItems: 1 From c71af78d9bf2edd9777113e7a9cbacc518318976 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 24 Mar 2024 20:20:29 +0100 Subject: [PATCH 056/108] iio: health: max30102: add support for max30101 The Maxim max30101 is the replacement for the max30105, which is no longer recommended for future designs. Their internal structure is identical, as well as the register map, configuration options and sensitivity, which allows for code recycling. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240324-max30101-v2-2-611deb510c97@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/health/max30102.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c index 37e619827e8a..6616729af5b7 100644 --- a/drivers/iio/health/max30102.c +++ b/drivers/iio/health/max30102.c @@ -613,6 +613,7 @@ static void max30102_remove(struct i2c_client *client) } static const struct i2c_device_id max30102_id[] = { + { "max30101", max30105 }, { "max30102", max30102 }, { "max30105", max30105 }, {} @@ -620,6 +621,7 @@ static const struct i2c_device_id max30102_id[] = { MODULE_DEVICE_TABLE(i2c, max30102_id); static const struct of_device_id max30102_dt_ids[] = { + { .compatible = "maxim,max30101" }, { .compatible = "maxim,max30102" }, { .compatible = "maxim,max30105" }, { } From cba15a6237652c07ed01aaa73623deb68473a940 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 25 Mar 2024 22:32:41 +0200 Subject: [PATCH 057/108] dt-bindings: iio: dac: ti,dac5571: Add DAC081C081 support The DAC081C081 is a TI DAC whose software interface is compatible with the DAC5571. It is the 8-bit version of the DAC121C081, already supported by the DAC5571 bindings. Extends the bindings to support this chip. Signed-off-by: Laurent Pinchart Reviewed-by: Sean Nyekjaer Link: https://lore.kernel.org/r/20240325203245.31660-2-laurent.pinchart@ideasonboard.com Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml b/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml index 79da0323c327..e59db861e2eb 100644 --- a/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml +++ b/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml @@ -21,6 +21,7 @@ properties: - ti,dac5573 - ti,dac6573 - ti,dac7573 + - ti,dac081c081 - ti,dac121c081 reg: From 3d797af1d69a23571ec89dc4d95eb0e0e971d3d8 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 25 Mar 2024 22:32:42 +0200 Subject: [PATCH 058/108] iio: dac: ti-dac5571: Add DAC081C081 support The DAC081C081 is a TI DAC whose software interface is compatible with the DAC5571. It is the 8-bit version of the DAC121C081, already supported by the DAC5571 driver. Extends the driver to support this chip. Signed-off-by: Laurent Pinchart Reviewed-by: Sean Nyekjaer > Link: https://lore.kernel.org/r/20240325203245.31660-3-laurent.pinchart@ideasonboard.com Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ti-dac5571.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/dac/ti-dac5571.c b/drivers/iio/dac/ti-dac5571.c index efb1269a77c1..c5162b72951a 100644 --- a/drivers/iio/dac/ti-dac5571.c +++ b/drivers/iio/dac/ti-dac5571.c @@ -13,6 +13,7 @@ * https://www.ti.com/lit/ds/symlink/dac5573.pdf * https://www.ti.com/lit/ds/symlink/dac6573.pdf * https://www.ti.com/lit/ds/symlink/dac7573.pdf + * https://www.ti.com/lit/ds/symlink/dac081c081.pdf * https://www.ti.com/lit/ds/symlink/dac121c081.pdf */ @@ -386,6 +387,7 @@ static void dac5571_remove(struct i2c_client *i2c) } static const struct of_device_id dac5571_of_id[] = { + {.compatible = "ti,dac081c081", .data = &dac5571_spec[single_8bit] }, {.compatible = "ti,dac121c081", .data = &dac5571_spec[single_12bit] }, {.compatible = "ti,dac5571", .data = &dac5571_spec[single_8bit] }, {.compatible = "ti,dac6571", .data = &dac5571_spec[single_10bit] }, @@ -401,6 +403,7 @@ static const struct of_device_id dac5571_of_id[] = { MODULE_DEVICE_TABLE(of, dac5571_of_id); static const struct i2c_device_id dac5571_id[] = { + {"dac081c081", (kernel_ulong_t)&dac5571_spec[single_8bit] }, {"dac121c081", (kernel_ulong_t)&dac5571_spec[single_12bit] }, {"dac5571", (kernel_ulong_t)&dac5571_spec[single_8bit] }, {"dac6571", (kernel_ulong_t)&dac5571_spec[single_10bit] }, From 27eea4778db8268cd6dc80a5b853c599bd3099f1 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 25 Mar 2024 14:40:36 -0500 Subject: [PATCH 059/108] iio: adc: ad7944: simplify adi,spi-mode property parsing This simplifies the adi,spi-mode property parsing by using device_property_match_property_string() instead of two separate functions. Also, the error return value is now more informative in cases where there was a problem parsing the property. Suggested-by: Andy Shevchenko Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240325-ad7944-cleanups-v3-1-3a19120cdd06@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7944.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index d5ec6b5a41c7..9aa3e98cd75c 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -366,7 +366,6 @@ static int ad7944_probe(struct spi_device *spi) struct ad7944_adc *adc; bool have_refin = false; struct regulator *ref; - const char *str_val; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); @@ -382,17 +381,17 @@ static int ad7944_probe(struct spi_device *spi) adc->timing_spec = chip_info->timing_spec; - if (device_property_read_string(dev, "adi,spi-mode", &str_val) == 0) { - ret = sysfs_match_string(ad7944_spi_modes, str_val); - if (ret < 0) - return dev_err_probe(dev, -EINVAL, - "unsupported adi,spi-mode\n"); - - adc->spi_mode = ret; - } else { - /* absence of adi,spi-mode property means default mode */ + ret = device_property_match_property_string(dev, "adi,spi-mode", + ad7944_spi_modes, + ARRAY_SIZE(ad7944_spi_modes)); + /* absence of adi,spi-mode property means default mode */ + if (ret == -EINVAL) adc->spi_mode = AD7944_SPI_MODE_DEFAULT; - } + else if (ret < 0) + return dev_err_probe(dev, ret, + "getting adi,spi-mode property failed\n"); + else + adc->spi_mode = ret; if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) return dev_err_probe(dev, -EINVAL, From 6020ca4de8e5404b20f15a6d9873cd6eb5f6d8d6 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 28 Mar 2024 16:16:59 -0500 Subject: [PATCH 060/108] iio: adc: ad7944: use spi_optimize_message() This modifies the ad7944 driver to use spi_optimize_message() to reduce CPU usage and increase the max sample rate by avoiding repeating validation of the spi message on each transfer. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240328-ad7944-spi-optimize-message-v2-1-a142b2576379@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7944.c | 177 +++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index 9aa3e98cd75c..9dc86ec23c36 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -51,6 +51,8 @@ static const char * const ad7944_spi_modes[] = { struct ad7944_adc { struct spi_device *spi; enum ad7944_spi_mode spi_mode; + struct spi_transfer xfers[3]; + struct spi_message msg; /* Chip-specific timing specifications. */ const struct ad7944_timing_spec *timing_spec; /* GPIO connected to CNV pin. */ @@ -130,6 +132,88 @@ AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0); /* fully differential */ AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); +static void ad7944_unoptimize_msg(void *msg) +{ + spi_unoptimize_message(msg); +} + +static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer *xfers = adc->xfers; + int ret; + + /* + * NB: can get better performance from some SPI controllers if we use + * the same bits_per_word in every transfer. + */ + xfers[0].bits_per_word = chan->scan_type.realbits; + /* + * CS is tied to CNV and we need a low to high transition to start the + * conversion, so place CNV low for t_QUIET to prepare for this. + */ + xfers[0].delay.value = T_QUIET_NS; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + /* + * CS has to be high for full conversion time to avoid triggering the + * busy indication. + */ + xfers[1].cs_off = 1; + xfers[1].delay.value = t_conv_ns; + xfers[1].delay.unit = SPI_DELAY_UNIT_NSECS; + xfers[1].bits_per_word = chan->scan_type.realbits; + + /* Then we can read the data during the acquisition phase */ + xfers[2].rx_buf = &adc->sample.raw; + xfers[2].len = BITS_TO_BYTES(chan->scan_type.storagebits); + xfers[2].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->msg, xfers, 3); + + ret = spi_optimize_message(adc->spi, &adc->msg); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg); +} + +static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer *xfers = adc->xfers; + int ret; + + /* + * NB: can get better performance from some SPI controllers if we use + * the same bits_per_word in every transfer. + */ + xfers[0].bits_per_word = chan->scan_type.realbits; + /* + * CS has to be high for full conversion time to avoid triggering the + * busy indication. + */ + xfers[0].cs_off = 1; + xfers[0].delay.value = t_conv_ns; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = &adc->sample.raw; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); + xfers[1].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->msg, xfers, 2); + + ret = spi_optimize_message(adc->spi, &adc->msg); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg); +} + /* * ad7944_3wire_cs_mode_conversion - Perform a 3-wire CS mode conversion and * acquisition @@ -145,48 +229,7 @@ AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1); static int ad7944_3wire_cs_mode_conversion(struct ad7944_adc *adc, const struct iio_chan_spec *chan) { - unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns - : adc->timing_spec->conv_ns; - struct spi_transfer xfers[] = { - { - /* - * NB: can get better performance from some SPI - * controllers if we use the same bits_per_word - * in every transfer. - */ - .bits_per_word = chan->scan_type.realbits, - /* - * CS is tied to CNV and we need a low to high - * transition to start the conversion, so place CNV - * low for t_QUIET to prepare for this. - */ - .delay = { - .value = T_QUIET_NS, - .unit = SPI_DELAY_UNIT_NSECS, - }, - - }, - { - .bits_per_word = chan->scan_type.realbits, - /* - * CS has to be high for full conversion time to avoid - * triggering the busy indication. - */ - .cs_off = 1, - .delay = { - .value = t_conv_ns, - .unit = SPI_DELAY_UNIT_NSECS, - }, - }, - { - /* Then we can read the data during the acquisition phase */ - .rx_buf = &adc->sample.raw, - .len = BITS_TO_BYTES(chan->scan_type.storagebits), - .bits_per_word = chan->scan_type.realbits, - }, - }; - - return spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); + return spi_sync(adc->spi, &adc->msg); } /* @@ -200,33 +243,6 @@ static int ad7944_3wire_cs_mode_conversion(struct ad7944_adc *adc, static int ad7944_4wire_mode_conversion(struct ad7944_adc *adc, const struct iio_chan_spec *chan) { - unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns - : adc->timing_spec->conv_ns; - struct spi_transfer xfers[] = { - { - /* - * NB: can get better performance from some SPI - * controllers if we use the same bits_per_word - * in every transfer. - */ - .bits_per_word = chan->scan_type.realbits, - /* - * CS has to be high for full conversion time to avoid - * triggering the busy indication. - */ - .cs_off = 1, - .delay = { - .value = t_conv_ns, - .unit = SPI_DELAY_UNIT_NSECS, - }, - - }, - { - .rx_buf = &adc->sample.raw, - .len = BITS_TO_BYTES(chan->scan_type.storagebits), - .bits_per_word = chan->scan_type.realbits, - }, - }; int ret; /* @@ -234,7 +250,7 @@ static int ad7944_4wire_mode_conversion(struct ad7944_adc *adc, * and acquisition process. */ gpiod_set_value_cansleep(adc->cnv, 1); - ret = spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); + ret = spi_sync(adc->spi, &adc->msg); gpiod_set_value_cansleep(adc->cnv, 0); return ret; @@ -393,10 +409,6 @@ static int ad7944_probe(struct spi_device *spi) else adc->spi_mode = ret; - if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) - return dev_err_probe(dev, -EINVAL, - "chain mode is not implemented\n"); - /* * Some chips use unusual word sizes, so check now instead of waiting * for the first xfer. @@ -489,6 +501,23 @@ static int ad7944_probe(struct spi_device *spi) return dev_err_probe(dev, -EINVAL, "cannot have both chain mode and always turbo\n"); + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + ret = ad7944_4wire_mode_init_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_SINGLE: + ret = ad7944_3wire_cs_mode_init_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_CHAIN: + return dev_err_probe(dev, -EINVAL, "chain mode is not implemented\n"); + } + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &ad7944_iio_info; From 2d766e79ba2bd15e59ceda4b0596e369c9564948 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 18:53:04 +0000 Subject: [PATCH 061/108] iio: dac: ad3552r: Use device_for_each_child_node_scoped() Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. Removing the goto err; statements also allows more extensive use of dev_err_probe() further simplifying the code. Cc: Mihail Chindris Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330185305.1319844-8-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad3552r.c | 51 +++++++++++++++------------------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/drivers/iio/dac/ad3552r.c b/drivers/iio/dac/ad3552r.c index a492e8f2fc0f..e14a065b29ca 100644 --- a/drivers/iio/dac/ad3552r.c +++ b/drivers/iio/dac/ad3552r.c @@ -880,7 +880,6 @@ static void ad3552r_reg_disable(void *reg) static int ad3552r_configure_device(struct ad3552r_desc *dac) { struct device *dev = &dac->spi->dev; - struct fwnode_handle *child; struct regulator *vref; int err, cnt = 0, voltage, delta = 100000; u32 vals[2], val, ch; @@ -949,53 +948,45 @@ static int ad3552r_configure_device(struct ad3552r_desc *dac) return -ENODEV; } - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { err = fwnode_property_read_u32(child, "reg", &ch); - if (err) { - dev_err(dev, "mandatory reg property missing\n"); - goto put_child; - } - if (ch >= AD3552R_NUM_CH) { - dev_err(dev, "reg must be less than %d\n", - AD3552R_NUM_CH); - err = -EINVAL; - goto put_child; - } + if (err) + return dev_err_probe(dev, err, + "mandatory reg property missing\n"); + if (ch >= AD3552R_NUM_CH) + return dev_err_probe(dev, -EINVAL, + "reg must be less than %d\n", + AD3552R_NUM_CH); if (fwnode_property_present(child, "adi,output-range-microvolt")) { err = fwnode_property_read_u32_array(child, "adi,output-range-microvolt", vals, 2); - if (err) { - dev_err(dev, + if (err) + return dev_err_probe(dev, err, "adi,output-range-microvolt property could not be parsed\n"); - goto put_child; - } err = ad3552r_find_range(dac->chip_id, vals); - if (err < 0) { - dev_err(dev, - "Invalid adi,output-range-microvolt value\n"); - goto put_child; - } + if (err < 0) + return dev_err_probe(dev, err, + "Invalid adi,output-range-microvolt value\n"); + val = err; err = ad3552r_set_ch_value(dac, AD3552R_CH_OUTPUT_RANGE_SEL, ch, val); if (err) - goto put_child; + return err; dac->ch_data[ch].range = val; } else if (dac->chip_id == AD3542R_ID) { - dev_err(dev, - "adi,output-range-microvolt is required for ad3542r\n"); - err = -EINVAL; - goto put_child; + return dev_err_probe(dev, -EINVAL, + "adi,output-range-microvolt is required for ad3542r\n"); } else { err = ad3552r_configure_custom_gain(dac, child, ch); if (err) - goto put_child; + return err; } ad3552r_calc_gain_and_offset(dac, ch); @@ -1003,7 +994,7 @@ static int ad3552r_configure_device(struct ad3552r_desc *dac) err = ad3552r_set_ch_value(dac, AD3552R_CH_SELECT, ch, 1); if (err < 0) - goto put_child; + return err; dac->channels[cnt] = AD3552R_CH_DAC(ch); ++cnt; @@ -1021,10 +1012,6 @@ static int ad3552r_configure_device(struct ad3552r_desc *dac) dac->num_ch = cnt; return 0; -put_child: - fwnode_handle_put(child); - - return err; } static int ad3552r_init(struct ad3552r_desc *dac) From eff19f6b9e00f70cd20a3c62779b68b6e820500c Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 18:53:05 +0000 Subject: [PATCH 062/108] iio: dac: ad5770r: Use device_for_each_child_node_scoped() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. Cc: Nuno Sá Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330185305.1319844-9-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5770r.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/drivers/iio/dac/ad5770r.c b/drivers/iio/dac/ad5770r.c index f66d67402e43..c360ebf5297a 100644 --- a/drivers/iio/dac/ad5770r.c +++ b/drivers/iio/dac/ad5770r.c @@ -515,39 +515,32 @@ static int ad5770r_channel_config(struct ad5770r_state *st) { int ret, tmp[2], min, max; unsigned int num; - struct fwnode_handle *child; num = device_get_child_node_count(&st->spi->dev); if (num != AD5770R_MAX_CHANNELS) return -EINVAL; - device_for_each_child_node(&st->spi->dev, child) { + device_for_each_child_node_scoped(&st->spi->dev, child) { ret = fwnode_property_read_u32(child, "reg", &num); if (ret) - goto err_child_out; - if (num >= AD5770R_MAX_CHANNELS) { - ret = -EINVAL; - goto err_child_out; - } + return ret; + if (num >= AD5770R_MAX_CHANNELS) + return -EINVAL; ret = fwnode_property_read_u32_array(child, "adi,range-microamp", tmp, 2); if (ret) - goto err_child_out; + return ret; min = tmp[0] / 1000; max = tmp[1] / 1000; ret = ad5770r_store_output_range(st, min, max, num); if (ret) - goto err_child_out; + return ret; } return 0; - -err_child_out: - fwnode_handle_put(child); - return ret; } static int ad5770r_init(struct ad5770r_state *st) From 18abc64bfe0478716adcf0fd6b95ea4dbfe9f9e7 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 19:08:43 +0000 Subject: [PATCH 063/108] iio: adc: ab8500-gpadc: Fix kernel-doc parameter names. Seems these got renamed at somepoint but the documentation wasn't updated to match. Cc: Linus Walleij Reviewed-by: Linus Walleij Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330190849.1321065-3-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ab8500-gpadc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ab8500-gpadc.c b/drivers/iio/adc/ab8500-gpadc.c index 80645fee79a4..0bc1550c7f11 100644 --- a/drivers/iio/adc/ab8500-gpadc.c +++ b/drivers/iio/adc/ab8500-gpadc.c @@ -1021,8 +1021,8 @@ static int ab8500_gpadc_parse_channel(struct device *dev, /** * ab8500_gpadc_parse_channels() - Parse the GPADC channels from DT * @gpadc: the GPADC to configure the channels for - * @chans: the IIO channels we parsed - * @nchans: the number of IIO channels we parsed + * @chans_parsed: the IIO channels we parsed + * @nchans_parsed: the number of IIO channels we parsed */ static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc, struct iio_chan_spec **chans_parsed, From f875790e6ae8b53e99530e6c7f7d9394c1f6b9ee Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 19:08:44 +0000 Subject: [PATCH 064/108] iio: adc: ab8500-gpadc: Use device_for_each_child_node_scoped() to simplify erorr paths. This new loop definition automatically releases the handle on early exit reducing the chance of bugs that cause resource leaks. Cc: Linus Walleij Reviewed-by: Linus Walleij Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330190849.1321065-4-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ab8500-gpadc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/iio/adc/ab8500-gpadc.c b/drivers/iio/adc/ab8500-gpadc.c index 0bc1550c7f11..59f66e9cb0e8 100644 --- a/drivers/iio/adc/ab8500-gpadc.c +++ b/drivers/iio/adc/ab8500-gpadc.c @@ -1028,7 +1028,6 @@ static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc, struct iio_chan_spec **chans_parsed, unsigned int *nchans_parsed) { - struct fwnode_handle *child; struct ab8500_gpadc_chan_info *ch; struct iio_chan_spec *iio_chans; unsigned int nchans; @@ -1052,7 +1051,7 @@ static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc, return -ENOMEM; i = 0; - device_for_each_child_node(gpadc->dev, child) { + device_for_each_child_node_scoped(gpadc->dev, child) { struct iio_chan_spec *iio_chan; int ret; @@ -1062,7 +1061,6 @@ static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc, ret = ab8500_gpadc_parse_channel(gpadc->dev, child, ch, iio_chan); if (ret) { - fwnode_handle_put(child); return ret; } i++; From 8c40277ec673635a24fccf016510583065d30069 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 19:08:45 +0000 Subject: [PATCH 065/108] iio: adc: ad4130: Use device_for_each_child_node_scoped() to simplify error paths. This loop definition removes the need for manual releasing of the fwnode_handle in early exit paths (here an error path) allow simplfication of the code and reducing the chance of future modificiations not releasing fwnode_handle correctly. Cc: Cosmin Tanislav Cc: Nuno Sa Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330190849.1321065-5-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4130.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/ad4130.c b/drivers/iio/adc/ad4130.c index febb64e67955..aaf1fb0ac447 100644 --- a/drivers/iio/adc/ad4130.c +++ b/drivers/iio/adc/ad4130.c @@ -1600,17 +1600,14 @@ static int ad4130_parse_fw_children(struct iio_dev *indio_dev) { struct ad4130_state *st = iio_priv(indio_dev); struct device *dev = &st->spi->dev; - struct fwnode_handle *child; int ret; indio_dev->channels = st->chans; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { ret = ad4130_parse_fw_channel(indio_dev, child); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } } return 0; From cf84f1e0a8fd236a965c4b79a1de1509e05e0343 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 19:08:46 +0000 Subject: [PATCH 066/108] iio: adc: ad7173: Use device_for_each_child_node_scoped() to simplify error paths. This loop definition automatically releases the fwnode_handle on early exit such as the error cases seen here. This reducing boilerplate and the chance of a resource leak being introduced in future. Cc: Dumitru Ceclan Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330190849.1321065-6-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7173.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index 4ff6ce46b02c..f6d29abe1d04 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -910,7 +910,6 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) struct device *dev = indio_dev->dev.parent; struct iio_chan_spec *chan_arr, *chan; unsigned int ain[2], chan_index = 0; - struct fwnode_handle *child; int ref_sel, ret; chan_arr = devm_kcalloc(dev, sizeof(*indio_dev->channels), @@ -940,23 +939,19 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) chan_index++; } - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { chan = &chan_arr[chan_index]; chan_st_priv = &chans_st_arr[chan_index]; ret = fwnode_property_read_u32_array(child, "diff-channels", ain, ARRAY_SIZE(ain)); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } if (ain[0] >= st->info->num_inputs || - ain[1] >= st->info->num_inputs) { - fwnode_handle_put(child); + ain[1] >= st->info->num_inputs) return dev_err_probe(dev, -EINVAL, "Input pin number out of range for pair (%d %d).\n", ain[0], ain[1]); - } ret = fwnode_property_match_property_string(child, "adi,reference-select", @@ -968,24 +963,19 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) ref_sel = ret; if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF && - !st->info->has_int_ref) { - fwnode_handle_put(child); + !st->info->has_int_ref) return dev_err_probe(dev, -EINVAL, "Internal reference is not available on current model.\n"); - } - if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && !st->info->has_ref2) { - fwnode_handle_put(child); + if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && !st->info->has_ref2) return dev_err_probe(dev, -EINVAL, "External reference 2 is not available on current model.\n"); - } ret = ad7173_get_ref_voltage_milli(st, ref_sel); - if (ret < 0) { - fwnode_handle_put(child); + if (ret < 0) return dev_err_probe(dev, ret, "Cannot use reference %u\n", ref_sel); - } + if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF) st->adc_mode |= AD7173_ADC_MODE_REF_EN; chan_st_priv->cfg.ref_sel = ref_sel; From de4eefe8f16fb2854e8375978c0c517dcddc4d71 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 19:08:47 +0000 Subject: [PATCH 067/108] iio: frequency: admfm2000: Use device_for_each_child_node_scoped() to simplify error paths. This loop definition automatically release the fwnode_handle on early exit simplifying error handling paths. Cc: Kim Seer Paller Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330190849.1321065-7-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/admfm2000.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/drivers/iio/frequency/admfm2000.c b/drivers/iio/frequency/admfm2000.c index c34d79e55a7c..b2263b9afeda 100644 --- a/drivers/iio/frequency/admfm2000.c +++ b/drivers/iio/frequency/admfm2000.c @@ -160,26 +160,21 @@ static int admfm2000_channel_config(struct admfm2000_state *st, { struct platform_device *pdev = to_platform_device(indio_dev->dev.parent); struct device *dev = &pdev->dev; - struct fwnode_handle *child; struct gpio_desc **dsa; struct gpio_desc **sw; int ret, i; bool mode; u32 reg; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { ret = fwnode_property_read_u32(child, "reg", ®); - if (ret) { - fwnode_handle_put(child); + if (ret) return dev_err_probe(dev, ret, "Failed to get reg property\n"); - } - if (reg >= indio_dev->num_channels) { - fwnode_handle_put(child); + if (reg >= indio_dev->num_channels) return dev_err_probe(dev, -EINVAL, "reg bigger than: %d\n", indio_dev->num_channels); - } if (fwnode_property_present(child, "adi,mixer-mode")) mode = ADMFM2000_MIXER_MODE; @@ -196,36 +191,29 @@ static int admfm2000_channel_config(struct admfm2000_state *st, dsa = st->dsa2_gpios; break; default: - fwnode_handle_put(child); return -EINVAL; } for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) { sw[i] = devm_fwnode_gpiod_get_index(dev, child, "switch", i, GPIOD_OUT_LOW, NULL); - if (IS_ERR(sw[i])) { - fwnode_handle_put(child); + if (IS_ERR(sw[i])) return dev_err_probe(dev, PTR_ERR(sw[i]), "Failed to get gpios\n"); - } } for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) { dsa[i] = devm_fwnode_gpiod_get_index(dev, child, "attenuation", i, GPIOD_OUT_LOW, NULL); - if (IS_ERR(dsa[i])) { - fwnode_handle_put(child); + if (IS_ERR(dsa[i])) return dev_err_probe(dev, PTR_ERR(dsa[i]), "Failed to get gpios\n"); - } } ret = admfm2000_mode(indio_dev, reg, mode); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } } return 0; From bcf571a854d4a91617d688015277525edfbc30fb Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 19:08:48 +0000 Subject: [PATCH 068/108] iio: dac: ad3552: Use __free(fwnode_handle) to simplify error handling. By using this scoped based cleanup direct returns on errors are enabled simplifying the code. Cc: Mihail Chindris Cc: Marcelo Schmitt Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330190849.1321065-8-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad3552r.c | 59 +++++++++++++++------------------------ 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/drivers/iio/dac/ad3552r.c b/drivers/iio/dac/ad3552r.c index e14a065b29ca..8aa942896b5b 100644 --- a/drivers/iio/dac/ad3552r.c +++ b/drivers/iio/dac/ad3552r.c @@ -801,51 +801,45 @@ static int ad3552r_configure_custom_gain(struct ad3552r_desc *dac, u32 ch) { struct device *dev = &dac->spi->dev; - struct fwnode_handle *gain_child; u32 val; int err; u8 addr; u16 reg = 0, offset; - gain_child = fwnode_get_named_child_node(child, - "custom-output-range-config"); - if (!gain_child) { - dev_err(dev, - "mandatory custom-output-range-config property missing\n"); - return -EINVAL; - } + struct fwnode_handle *gain_child __free(fwnode_handle) + = fwnode_get_named_child_node(child, + "custom-output-range-config"); + if (!gain_child) + return dev_err_probe(dev, -EINVAL, + "mandatory custom-output-range-config property missing\n"); dac->ch_data[ch].range_override = 1; reg |= ad3552r_field_prep(1, AD3552R_MASK_CH_RANGE_OVERRIDE); err = fwnode_property_read_u32(gain_child, "adi,gain-scaling-p", &val); - if (err) { - dev_err(dev, "mandatory adi,gain-scaling-p property missing\n"); - goto put_child; - } + if (err) + return dev_err_probe(dev, err, + "mandatory adi,gain-scaling-p property missing\n"); reg |= ad3552r_field_prep(val, AD3552R_MASK_CH_GAIN_SCALING_P); dac->ch_data[ch].p = val; err = fwnode_property_read_u32(gain_child, "adi,gain-scaling-n", &val); - if (err) { - dev_err(dev, "mandatory adi,gain-scaling-n property missing\n"); - goto put_child; - } + if (err) + return dev_err_probe(dev, err, + "mandatory adi,gain-scaling-n property missing\n"); reg |= ad3552r_field_prep(val, AD3552R_MASK_CH_GAIN_SCALING_N); dac->ch_data[ch].n = val; err = fwnode_property_read_u32(gain_child, "adi,rfb-ohms", &val); - if (err) { - dev_err(dev, "mandatory adi,rfb-ohms property missing\n"); - goto put_child; - } + if (err) + return dev_err_probe(dev, err, + "mandatory adi,rfb-ohms property missing\n"); dac->ch_data[ch].rfb = val; err = fwnode_property_read_u32(gain_child, "adi,gain-offset", &val); - if (err) { - dev_err(dev, "mandatory adi,gain-offset property missing\n"); - goto put_child; - } + if (err) + return dev_err_probe(dev, err, + "mandatory adi,gain-offset property missing\n"); dac->ch_data[ch].gain_offset = val; offset = abs((s32)val); @@ -855,21 +849,14 @@ static int ad3552r_configure_custom_gain(struct ad3552r_desc *dac, addr = AD3552R_REG_ADDR_CH_GAIN(ch); err = ad3552r_write_reg(dac, addr, offset & AD3552R_MASK_CH_OFFSET_BITS_0_7); - if (err) { - dev_err(dev, "Error writing register\n"); - goto put_child; - } + if (err) + return dev_err_probe(dev, err, "Error writing register\n"); err = ad3552r_write_reg(dac, addr, reg); - if (err) { - dev_err(dev, "Error writing register\n"); - goto put_child; - } + if (err) + return dev_err_probe(dev, err, "Error writing register\n"); -put_child: - fwnode_handle_put(gain_child); - - return err; + return 0; } static void ad3552r_reg_disable(void *reg) From 2eef045c5231658b3d7514c6640951e0de3e9d05 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 19:08:49 +0000 Subject: [PATCH 069/108] iio: adc: pac1934: Use device_for_each_available_child_node_scoped() to simplify error handling. Also change both firmware parsing functions to return meaningful errors. Whilst for the ACPI case this isn't that useful, for the generic fwnode case we can return the errors coming from the property parsing instead of just whether we succeeded or not. Cc: Marius Cristea Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240330190849.1321065-9-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/pac1934.c | 77 ++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index e0c2742da523..f751260605e4 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -1079,8 +1079,8 @@ static int pac1934_chip_identify(struct pac1934_chip_info *info) * documentation related to the ACPI device definition * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC1934-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf */ -static bool pac1934_acpi_parse_channel_config(struct i2c_client *client, - struct pac1934_chip_info *info) +static int pac1934_acpi_parse_channel_config(struct i2c_client *client, + struct pac1934_chip_info *info) { acpi_handle handle; union acpi_object *rez; @@ -1095,7 +1095,7 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client, rez = acpi_evaluate_dsm(handle, &guid, 0, PAC1934_ACPI_GET_NAMES_AND_MOHMS_VALS, NULL); if (!rez) - return false; + return -EINVAL; for (i = 0; i < rez->package.count; i += 2) { idx = i / 2; @@ -1118,7 +1118,7 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client, * and assign the default sampling rate */ info->sample_rate_value = PAC1934_DEFAULT_CHIP_SAMP_SPEED_HZ; - return true; + return 0; } for (i = 0; i < rez->package.count; i++) { @@ -1131,7 +1131,7 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client, rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_BIPOLAR_SETTINGS, NULL); if (!rez) - return false; + return -EINVAL; bi_dir_mask = rez->package.elements[0].integer.value; info->bi_dir[0] = ((bi_dir_mask & (1 << 3)) | (bi_dir_mask & (1 << 7))) != 0; @@ -1143,19 +1143,18 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client, rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_SAMP, NULL); if (!rez) - return false; + return -EINVAL; info->sample_rate_value = rez->package.elements[0].integer.value; ACPI_FREE(rez); - return true; + return 0; } -static bool pac1934_of_parse_channel_config(struct i2c_client *client, - struct pac1934_chip_info *info) +static int pac1934_fw_parse_channel_config(struct i2c_client *client, + struct pac1934_chip_info *info) { - struct fwnode_handle *node, *fwnode; struct device *dev = &client->dev; unsigned int current_channel; int idx, ret; @@ -1163,46 +1162,38 @@ static bool pac1934_of_parse_channel_config(struct i2c_client *client, info->sample_rate_value = 1024; current_channel = 1; - fwnode = dev_fwnode(dev); - fwnode_for_each_available_child_node(fwnode, node) { + device_for_each_child_node_scoped(dev, node) { ret = fwnode_property_read_u32(node, "reg", &idx); - if (ret) { - dev_err_probe(dev, ret, - "reading invalid channel index\n"); - goto err_fwnode; - } + if (ret) + return dev_err_probe(dev, ret, + "reading invalid channel index\n"); + /* adjust idx to match channel index (1 to 4) from the datasheet */ idx--; if (current_channel >= (info->phys_channels + 1) || - idx >= info->phys_channels || idx < 0) { - dev_err_probe(dev, -EINVAL, - "%s: invalid channel_index %d value\n", - fwnode_get_name(node), idx); - goto err_fwnode; - } + idx >= info->phys_channels || idx < 0) + return dev_err_probe(dev, -EINVAL, + "%s: invalid channel_index %d value\n", + fwnode_get_name(node), idx); /* enable channel */ info->active_channels[idx] = true; ret = fwnode_property_read_u32(node, "shunt-resistor-micro-ohms", &info->shunts[idx]); - if (ret) { - dev_err_probe(dev, ret, - "%s: invalid shunt-resistor value: %d\n", - fwnode_get_name(node), info->shunts[idx]); - goto err_fwnode; - } + if (ret) + return dev_err_probe(dev, ret, + "%s: invalid shunt-resistor value: %d\n", + fwnode_get_name(node), info->shunts[idx]); if (fwnode_property_present(node, "label")) { ret = fwnode_property_read_string(node, "label", (const char **)&info->labels[idx]); - if (ret) { - dev_err_probe(dev, ret, - "%s: invalid rail-name value\n", - fwnode_get_name(node)); - goto err_fwnode; - } + if (ret) + return dev_err_probe(dev, ret, + "%s: invalid rail-name value\n", + fwnode_get_name(node)); } info->bi_dir[idx] = fwnode_property_read_bool(node, "bipolar"); @@ -1210,12 +1201,7 @@ static bool pac1934_of_parse_channel_config(struct i2c_client *client, current_channel++; } - return true; - -err_fwnode: - fwnode_handle_put(node); - - return false; + return 0; } static void pac1934_cancel_delayed_work(void *dwork) @@ -1485,7 +1471,6 @@ static int pac1934_probe(struct i2c_client *client) const struct pac1934_features *chip; struct iio_dev *indio_dev; int cnt, ret; - bool match = false; struct device *dev = &client->dev; indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); @@ -1519,16 +1504,16 @@ static int pac1934_probe(struct i2c_client *client) } if (acpi_match_device(dev->driver->acpi_match_table, dev)) - match = pac1934_acpi_parse_channel_config(client, info); + ret = pac1934_acpi_parse_channel_config(client, info); else /* * This makes it possible to use also ACPI PRP0001 for * registering the device using device tree properties. */ - match = pac1934_of_parse_channel_config(client, info); + ret = pac1934_fw_parse_channel_config(client, info); - if (!match) - return dev_err_probe(dev, -EINVAL, + if (ret) + return dev_err_probe(dev, ret, "parameter parsing returned an error\n"); mutex_init(&info->lock); From f68ebfe1501bf1110eebf5e968c4d9186cba8706 Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:48:59 +0000 Subject: [PATCH 070/108] iio: accel: adxl345: Make data_range obsolete Replace write() data_format by regmap_update_bits() to keep bus specific pre-configuration which might have happened before on the same register. The bus specific bits in data_format register then need to be masked out, Remove the data_range field from the struct adxl345_data, because it is not used anymore. Signed-off-by: Lothar Rubusch Link: https://lore.kernel.org/r/20240401194906.56810-2-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 8bd30a23ed3b..ff89215e90fd 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -37,7 +37,11 @@ #define ADXL345_POWER_CTL_MEASURE BIT(3) #define ADXL345_POWER_CTL_STANDBY 0x00 -#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */ +#define ADXL345_DATA_FORMAT_RANGE GENMASK(1, 0) /* Set the g range */ +#define ADXL345_DATA_FORMAT_JUSTIFY BIT(2) /* Left-justified (MSB) mode */ +#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */ +#define ADXL345_DATA_FORMAT_SELF_TEST BIT(7) /* Enable a self test */ + #define ADXL345_DATA_FORMAT_2G 0 #define ADXL345_DATA_FORMAT_4G 1 #define ADXL345_DATA_FORMAT_8G 2 @@ -48,7 +52,6 @@ struct adxl345_data { const struct adxl345_chip_info *info; struct regmap *regmap; - u8 data_range; }; #define ADXL345_CHANNEL(index, axis) { \ @@ -202,6 +205,10 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap) struct adxl345_data *data; struct iio_dev *indio_dev; u32 regval; + unsigned int data_format_mask = (ADXL345_DATA_FORMAT_RANGE | + ADXL345_DATA_FORMAT_JUSTIFY | + ADXL345_DATA_FORMAT_FULL_RES | + ADXL345_DATA_FORMAT_SELF_TEST); int ret; ret = regmap_read(regmap, ADXL345_REG_DEVID, ®val); @@ -218,15 +225,14 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap) data = iio_priv(indio_dev); data->regmap = regmap; - /* Enable full-resolution mode */ - data->data_range = ADXL345_DATA_FORMAT_FULL_RES; data->info = device_get_match_data(dev); if (!data->info) return -ENODEV; - ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT, - data->data_range); - if (ret < 0) + /* Enable full-resolution mode */ + ret = regmap_update_bits(regmap, ADXL345_REG_DATA_FORMAT, + data_format_mask, ADXL345_DATA_FORMAT_FULL_RES); + if (ret) return dev_err_probe(dev, ret, "Failed to set data range\n"); indio_dev->name = data->info->name; From ab158628d46c0cf15f77bc839e96d800dbdcbf8a Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:49:00 +0000 Subject: [PATCH 071/108] iio: accel: adxl345: Group bus configuration Group the indio_dev initialization and bus configuration for improved readability. Signed-off-by: Lothar Rubusch Link: https://lore.kernel.org/r/20240401194906.56810-3-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index ff89215e90fd..e4afc6d2a681 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -229,18 +229,18 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap) if (!data->info) return -ENODEV; - /* Enable full-resolution mode */ - ret = regmap_update_bits(regmap, ADXL345_REG_DATA_FORMAT, - data_format_mask, ADXL345_DATA_FORMAT_FULL_RES); - if (ret) - return dev_err_probe(dev, ret, "Failed to set data range\n"); - indio_dev->name = data->info->name; indio_dev->info = &adxl345_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = adxl345_channels; indio_dev->num_channels = ARRAY_SIZE(adxl345_channels); + /* Enable full-resolution mode */ + ret = regmap_update_bits(regmap, ADXL345_REG_DATA_FORMAT, + data_format_mask, ADXL345_DATA_FORMAT_FULL_RES); + if (ret) + return dev_err_probe(dev, ret, "Failed to set data range\n"); + /* Enable measurement mode */ ret = adxl345_powerup(data->regmap); if (ret < 0) From 25b83220873a19cab7e424da656da0106573431c Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:49:01 +0000 Subject: [PATCH 072/108] iio: accel: adxl345: Move defines to header Move defines from core to the header file. Keep the defines block together in one location. Signed-off-by: Lothar Rubusch Link: https://lore.kernel.org/r/20240401194906.56810-4-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345.h | 32 ++++++++++++++++++++++++++++++++ drivers/iio/accel/adxl345_core.c | 32 -------------------------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h index 284bd387ce69..732820d34db3 100644 --- a/drivers/iio/accel/adxl345.h +++ b/drivers/iio/accel/adxl345.h @@ -8,6 +8,38 @@ #ifndef _ADXL345_H_ #define _ADXL345_H_ +#define ADXL345_REG_DEVID 0x00 +#define ADXL345_REG_OFSX 0x1E +#define ADXL345_REG_OFSY 0x1F +#define ADXL345_REG_OFSZ 0x20 +#define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index)) +#define ADXL345_REG_BW_RATE 0x2C +#define ADXL345_REG_POWER_CTL 0x2D +#define ADXL345_REG_DATA_FORMAT 0x31 +#define ADXL345_REG_DATAX0 0x32 +#define ADXL345_REG_DATAY0 0x34 +#define ADXL345_REG_DATAZ0 0x36 +#define ADXL345_REG_DATA_AXIS(index) \ + (ADXL345_REG_DATAX0 + (index) * sizeof(__le16)) + +#define ADXL345_BW_RATE GENMASK(3, 0) +#define ADXL345_BASE_RATE_NANO_HZ 97656250LL + +#define ADXL345_POWER_CTL_MEASURE BIT(3) +#define ADXL345_POWER_CTL_STANDBY 0x00 + +#define ADXL345_DATA_FORMAT_RANGE GENMASK(1, 0) /* Set the g range */ +#define ADXL345_DATA_FORMAT_JUSTIFY BIT(2) /* Left-justified (MSB) mode */ +#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */ +#define ADXL345_DATA_FORMAT_SELF_TEST BIT(7) /* Enable a self test */ + +#define ADXL345_DATA_FORMAT_2G 0 +#define ADXL345_DATA_FORMAT_4G 1 +#define ADXL345_DATA_FORMAT_8G 2 +#define ADXL345_DATA_FORMAT_16G 3 + +#define ADXL345_DEVID 0xE5 + /* * In full-resolution mode, scale factor is maintained at ~4 mg/LSB * in all g ranges. diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index e4afc6d2a681..f875a6275fb8 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -17,38 +17,6 @@ #include "adxl345.h" -#define ADXL345_REG_DEVID 0x00 -#define ADXL345_REG_OFSX 0x1e -#define ADXL345_REG_OFSY 0x1f -#define ADXL345_REG_OFSZ 0x20 -#define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index)) -#define ADXL345_REG_BW_RATE 0x2C -#define ADXL345_REG_POWER_CTL 0x2D -#define ADXL345_REG_DATA_FORMAT 0x31 -#define ADXL345_REG_DATAX0 0x32 -#define ADXL345_REG_DATAY0 0x34 -#define ADXL345_REG_DATAZ0 0x36 -#define ADXL345_REG_DATA_AXIS(index) \ - (ADXL345_REG_DATAX0 + (index) * sizeof(__le16)) - -#define ADXL345_BW_RATE GENMASK(3, 0) -#define ADXL345_BASE_RATE_NANO_HZ 97656250LL - -#define ADXL345_POWER_CTL_MEASURE BIT(3) -#define ADXL345_POWER_CTL_STANDBY 0x00 - -#define ADXL345_DATA_FORMAT_RANGE GENMASK(1, 0) /* Set the g range */ -#define ADXL345_DATA_FORMAT_JUSTIFY BIT(2) /* Left-justified (MSB) mode */ -#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */ -#define ADXL345_DATA_FORMAT_SELF_TEST BIT(7) /* Enable a self test */ - -#define ADXL345_DATA_FORMAT_2G 0 -#define ADXL345_DATA_FORMAT_4G 1 -#define ADXL345_DATA_FORMAT_8G 2 -#define ADXL345_DATA_FORMAT_16G 3 - -#define ADXL345_DEVID 0xE5 - struct adxl345_data { const struct adxl345_chip_info *info; struct regmap *regmap; From 59307e8f74fb339f5c665fc521466bbf18752bce Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:49:02 +0000 Subject: [PATCH 073/108] dt-bindings: iio: accel: adxl345: Add spi-3wire Add spi-3wire because the device allows to be configured for spi 3-wire communication. Signed-off-by: Lothar Rubusch Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240401194906.56810-5-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml index 07cacc3f6a97..280ed479ef5a 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml @@ -32,6 +32,8 @@ properties: spi-cpol: true + spi-3wire: true + interrupts: maxItems: 1 From 41561abc417eb92b38631cb05427fecbfeec19cb Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:49:03 +0000 Subject: [PATCH 074/108] iio: accel: adxl345: Pass function pointer to core Provide a way for bus specific pre-configuration by adding a function pointer argument to the driver core's probe() function, and keep the driver core implementation bus independent. In case NULL was passed, a regmap_write() shall initialize all bits of the data_format register, else regmap_update() is used. In this way spi and i2c are covered. Signed-off-by: Lothar Rubusch Link: https://lore.kernel.org/r/20240401194906.56810-6-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345.h | 3 ++- drivers/iio/accel/adxl345_core.c | 32 +++++++++++++++++++++++++------- drivers/iio/accel/adxl345_i2c.c | 2 +- drivers/iio/accel/adxl345_spi.c | 2 +- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h index 732820d34db3..e859c01d4bff 100644 --- a/drivers/iio/accel/adxl345.h +++ b/drivers/iio/accel/adxl345.h @@ -60,6 +60,7 @@ struct adxl345_chip_info { int uscale; }; -int adxl345_core_probe(struct device *dev, struct regmap *regmap); +int adxl345_core_probe(struct device *dev, struct regmap *regmap, + int (*setup)(struct device*, struct regmap*)); #endif /* _ADXL345_H_ */ diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index f875a6275fb8..8d4a66d8c528 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -168,7 +168,8 @@ static void adxl345_powerdown(void *regmap) regmap_write(regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_STANDBY); } -int adxl345_core_probe(struct device *dev, struct regmap *regmap) +int adxl345_core_probe(struct device *dev, struct regmap *regmap, + int (*setup)(struct device*, struct regmap*)) { struct adxl345_data *data; struct iio_dev *indio_dev; @@ -179,6 +180,29 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap) ADXL345_DATA_FORMAT_SELF_TEST); int ret; + if (setup) { + /* Perform optional initial bus specific configuration */ + ret = setup(dev, regmap); + if (ret) + return ret; + + /* Enable full-resolution mode */ + ret = regmap_update_bits(regmap, ADXL345_REG_DATA_FORMAT, + data_format_mask, + ADXL345_DATA_FORMAT_FULL_RES); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set data range\n"); + + } else { + /* Enable full-resolution mode (init all data_format bits) */ + ret = regmap_write(regmap, ADXL345_REG_DATA_FORMAT, + ADXL345_DATA_FORMAT_FULL_RES); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set data range\n"); + } + ret = regmap_read(regmap, ADXL345_REG_DEVID, ®val); if (ret < 0) return dev_err_probe(dev, ret, "Error reading device ID\n"); @@ -203,12 +227,6 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap) indio_dev->channels = adxl345_channels; indio_dev->num_channels = ARRAY_SIZE(adxl345_channels); - /* Enable full-resolution mode */ - ret = regmap_update_bits(regmap, ADXL345_REG_DATA_FORMAT, - data_format_mask, ADXL345_DATA_FORMAT_FULL_RES); - if (ret) - return dev_err_probe(dev, ret, "Failed to set data range\n"); - /* Enable measurement mode */ ret = adxl345_powerup(data->regmap); if (ret < 0) diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c index a3084b0a8f78..4065b8f7c8a8 100644 --- a/drivers/iio/accel/adxl345_i2c.c +++ b/drivers/iio/accel/adxl345_i2c.c @@ -27,7 +27,7 @@ static int adxl345_i2c_probe(struct i2c_client *client) if (IS_ERR(regmap)) return dev_err_probe(&client->dev, PTR_ERR(regmap), "Error initializing regmap\n"); - return adxl345_core_probe(&client->dev, regmap); + return adxl345_core_probe(&client->dev, regmap, NULL); } static const struct adxl345_chip_info adxl345_i2c_info = { diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c index 93ca349f1780..1c0513bd3b9b 100644 --- a/drivers/iio/accel/adxl345_spi.c +++ b/drivers/iio/accel/adxl345_spi.c @@ -33,7 +33,7 @@ static int adxl345_spi_probe(struct spi_device *spi) if (IS_ERR(regmap)) return dev_err_probe(&spi->dev, PTR_ERR(regmap), "Error initializing regmap\n"); - return adxl345_core_probe(&spi->dev, regmap); + return adxl345_core_probe(&spi->dev, regmap, NULL); } static const struct adxl345_chip_info adxl345_spi_info = { From 196fb733e5e6c4f05e208ad31c4155cd525750e9 Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:49:04 +0000 Subject: [PATCH 075/108] iio: accel: adxl345: Reorder probe initialization Bring indio_dev, setup() and data initialization to begin of the probe() function to increase readability. Access members through data pointer to assure implicitely the driver's data instance is correctly initialized and functional. Signed-off-by: Lothar Rubusch Link: https://lore.kernel.org/r/20240401194906.56810-7-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 62 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 8d4a66d8c528..5d0f3243ec6c 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -180,37 +180,6 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap, ADXL345_DATA_FORMAT_SELF_TEST); int ret; - if (setup) { - /* Perform optional initial bus specific configuration */ - ret = setup(dev, regmap); - if (ret) - return ret; - - /* Enable full-resolution mode */ - ret = regmap_update_bits(regmap, ADXL345_REG_DATA_FORMAT, - data_format_mask, - ADXL345_DATA_FORMAT_FULL_RES); - if (ret) - return dev_err_probe(dev, ret, - "Failed to set data range\n"); - - } else { - /* Enable full-resolution mode (init all data_format bits) */ - ret = regmap_write(regmap, ADXL345_REG_DATA_FORMAT, - ADXL345_DATA_FORMAT_FULL_RES); - if (ret) - return dev_err_probe(dev, ret, - "Failed to set data range\n"); - } - - ret = regmap_read(regmap, ADXL345_REG_DEVID, ®val); - if (ret < 0) - return dev_err_probe(dev, ret, "Error reading device ID\n"); - - if (regval != ADXL345_DEVID) - return dev_err_probe(dev, -ENODEV, "Invalid device ID: %x, expected %x\n", - regval, ADXL345_DEVID); - indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -227,6 +196,37 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap, indio_dev->channels = adxl345_channels; indio_dev->num_channels = ARRAY_SIZE(adxl345_channels); + if (setup) { + /* Perform optional initial bus specific configuration */ + ret = setup(dev, data->regmap); + if (ret) + return ret; + + /* Enable full-resolution mode */ + ret = regmap_update_bits(data->regmap, ADXL345_REG_DATA_FORMAT, + data_format_mask, + ADXL345_DATA_FORMAT_FULL_RES); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set data range\n"); + + } else { + /* Enable full-resolution mode (init all data_format bits) */ + ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT, + ADXL345_DATA_FORMAT_FULL_RES); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set data range\n"); + } + + ret = regmap_read(data->regmap, ADXL345_REG_DEVID, ®val); + if (ret < 0) + return dev_err_probe(dev, ret, "Error reading device ID\n"); + + if (regval != ADXL345_DEVID) + return dev_err_probe(dev, -ENODEV, "Invalid device ID: %x, expected %x\n", + regval, ADXL345_DEVID); + /* Enable measurement mode */ ret = adxl345_powerup(data->regmap); if (ret < 0) From 34ca62caa0dd7c62b57e5b48a69af677c7a63824 Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:49:05 +0000 Subject: [PATCH 076/108] iio: accel: adxl345: Add comment to probe Add a comment to the probe() function to describe the passed function pointer argument in particular. Signed-off-by: Lothar Rubusch Link: https://lore.kernel.org/r/20240401194906.56810-8-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345_core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index 5d0f3243ec6c..006ce66c0aa3 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -168,6 +168,16 @@ static void adxl345_powerdown(void *regmap) regmap_write(regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_STANDBY); } +/** + * adxl345_core_probe() - probe and setup for the adxl345 accelerometer, + * also covers the adlx375 accelerometer + * @dev: Driver model representation of the device + * @regmap: Regmap instance for the device + * @setup: Setup routine to be executed right before the standard device + * setup + * + * Return: 0 on success, negative errno on error + */ int adxl345_core_probe(struct device *dev, struct regmap *regmap, int (*setup)(struct device*, struct regmap*)) { From 2f896dd97cedbf6a8a7a14f4c6090d2b088cb694 Mon Sep 17 00:00:00 2001 From: Lothar Rubusch Date: Mon, 1 Apr 2024 19:49:06 +0000 Subject: [PATCH 077/108] iio: accel: adxl345: Add spi-3wire option Add a setup function implementation to the spi module to enable spi-3wire when specified in the device-tree. If spi-3wire is not specified in the device-tree, NULL is returned as bus pre-initialization. This behavior is identical to the i2c initialization, hence the default initialization. Signed-off-by: Lothar Rubusch Link: https://lore.kernel.org/r/20240401194906.56810-9-l.rubusch@gmail.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl345.h | 1 + drivers/iio/accel/adxl345_spi.c | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h index e859c01d4bff..3d5c8719db3d 100644 --- a/drivers/iio/accel/adxl345.h +++ b/drivers/iio/accel/adxl345.h @@ -31,6 +31,7 @@ #define ADXL345_DATA_FORMAT_RANGE GENMASK(1, 0) /* Set the g range */ #define ADXL345_DATA_FORMAT_JUSTIFY BIT(2) /* Left-justified (MSB) mode */ #define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */ +#define ADXL345_DATA_FORMAT_SPI_3WIRE BIT(6) /* 3-wire SPI mode */ #define ADXL345_DATA_FORMAT_SELF_TEST BIT(7) /* Enable a self test */ #define ADXL345_DATA_FORMAT_2G 0 diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c index 1c0513bd3b9b..57e16b441702 100644 --- a/drivers/iio/accel/adxl345_spi.c +++ b/drivers/iio/accel/adxl345_spi.c @@ -20,6 +20,11 @@ static const struct regmap_config adxl345_spi_regmap_config = { .read_flag_mask = BIT(7) | BIT(6), }; +static int adxl345_spi_setup(struct device *dev, struct regmap *regmap) +{ + return regmap_write(regmap, ADXL345_REG_DATA_FORMAT, ADXL345_DATA_FORMAT_SPI_3WIRE); +} + static int adxl345_spi_probe(struct spi_device *spi) { struct regmap *regmap; @@ -33,7 +38,10 @@ static int adxl345_spi_probe(struct spi_device *spi) if (IS_ERR(regmap)) return dev_err_probe(&spi->dev, PTR_ERR(regmap), "Error initializing regmap\n"); - return adxl345_core_probe(&spi->dev, regmap, NULL); + if (spi->mode & SPI_3WIRE) + return adxl345_core_probe(&spi->dev, regmap, adxl345_spi_setup); + else + return adxl345_core_probe(&spi->dev, regmap, NULL); } static const struct adxl345_chip_info adxl345_spi_info = { From c8e2d4873902c58ee14d3eb5138f516ba963e7d9 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 4 Apr 2024 10:31:25 +0300 Subject: [PATCH 078/108] iio: light: apds9306: Fix off by one in apds9306_sampling_freq_get() The > comparison needs to be >= to prevent an out of bounds access. Fixes: 620d1e6c7a3f ("iio: light: Add support for APDS9306 Light Sensor") Signed-off-by: Dan Carpenter Reviewed-by: Subhajit Ghosh Link: https://lore.kernel.org/r/69c5cb83-0209-40ff-a276-a0ae5e81c528@moroto.mountain Signed-off-by: Jonathan Cameron --- drivers/iio/light/apds9306.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index 4d8490602cd7..49fa6b7d5170 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -635,7 +635,7 @@ static int apds9306_sampling_freq_get(struct apds9306_data *data, int *val, if (ret) return ret; - if (repeat_rate_idx > ARRAY_SIZE(apds9306_repeat_rate_freq)) + if (repeat_rate_idx >= ARRAY_SIZE(apds9306_repeat_rate_freq)) return -EINVAL; *val = apds9306_repeat_rate_freq[repeat_rate_idx][0]; From 107585b06926aac6b36e466d4c6f05c520bb0f41 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 4 Apr 2024 10:31:32 +0300 Subject: [PATCH 079/108] iio: adc: ad7173: Fix ! vs ~ typo in ad7173_sel_clk() This was obviously supposed to be a bitwise negate instead of logical. Fixes: 76a1e6a42802 ("iio: adc: ad7173: add AD7173 driver") Signed-off-by: Dan Carpenter Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/5401c681-c4aa-4fab-8c8c-8f0a379e2687@moroto.mountain Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7173.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index f6d29abe1d04..a7826bba0852 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -835,7 +835,7 @@ static unsigned long ad7173_sel_clk(struct ad7173_state *st, { int ret; - st->adc_mode &= !AD7173_ADC_MODE_CLOCKSEL_MASK; + st->adc_mode &= ~AD7173_ADC_MODE_CLOCKSEL_MASK; st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, clk_sel); ret = ad_sd_write_reg(&st->sd, AD7173_REG_ADC_MODE, 0x2, st->adc_mode); From f979e52d3bc136b4ef1351479464c54a7c81c934 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Thu, 4 Apr 2024 10:58:17 +0200 Subject: [PATCH 080/108] dt-bindings: iio: temperature: ltc2983: document power supply Add a property for the VDD power supply regulator. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240404-ltc2983-misc-improv-v5-1-c1f58057fcea@analog.com Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/temperature/adi,ltc2983.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml b/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml index dbb85135fd66..312febeeb3bb 100644 --- a/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml +++ b/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml @@ -57,6 +57,8 @@ properties: interrupts: maxItems: 1 + vdd-supply: true + adi,mux-delay-config-us: description: | Extra delay prior to each conversion, in addition to the internal 1ms @@ -460,6 +462,7 @@ required: - compatible - reg - interrupts + - vdd-supply additionalProperties: false @@ -489,6 +492,7 @@ examples: #address-cells = <1>; #size-cells = <0>; + vdd-supply = <&supply>; interrupts = <20 IRQ_TYPE_EDGE_RISING>; interrupt-parent = <&gpio>; From 47ef0501a9cc25c88dcac2b57ed3bab4da53f3d5 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Thu, 4 Apr 2024 10:58:18 +0200 Subject: [PATCH 081/108] iio: temperature: ltc2983: support vdd regulator Add support for the power supply regulator. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240404-ltc2983-misc-improv-v5-2-c1f58057fcea@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/ltc2983.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c index 3c4524d57b8e..24d19f3c7292 100644 --- a/drivers/iio/temperature/ltc2983.c +++ b/drivers/iio/temperature/ltc2983.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -1597,6 +1598,10 @@ static int ltc2983_probe(struct spi_device *spi) if (ret) return ret; + ret = devm_regulator_get_enable(&spi->dev, "vdd"); + if (ret) + return ret; + gpio = devm_gpiod_get_optional(&st->spi->dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(gpio)) return PTR_ERR(gpio); From 85795d3eddeb5f0e3e11caad24cc78f6c43d7fd1 Mon Sep 17 00:00:00 2001 From: Subhajit Ghosh Date: Fri, 5 Apr 2024 21:16:41 +1030 Subject: [PATCH 082/108] iio: light: apds9306: Improve apds9306_write_event_config() Simplify event configuration flow. Suggested-by: Jonathan Cameron Link: https://lore.kernel.org/all/20240310124237.52fa8a56@jic23-huawei/ Signed-off-by: Subhajit Ghosh Link: https://lore.kernel.org/r/20240405104641.29478-1-subhajit.ghosh@tweaklogic.com Signed-off-by: Jonathan Cameron --- drivers/iio/light/apds9306.c | 48 ++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index 49fa6b7d5170..46c647ccd44c 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -1075,14 +1075,16 @@ static int apds9306_write_event_config(struct iio_dev *indio_dev, { struct apds9306_data *data = iio_priv(indio_dev); struct apds9306_regfields *rf = &data->rf; - int ret, val; - - state = !!state; + int ret, enabled; switch (type) { case IIO_EV_TYPE_THRESH: { guard(mutex)(&data->mutex); + ret = regmap_field_read(rf->int_en, &enabled); + if (ret) + return ret; + /* * If interrupt is enabled, the channel is set before enabling * the interrupt. In case of disable, no need to switch @@ -1091,38 +1093,42 @@ static int apds9306_write_event_config(struct iio_dev *indio_dev, */ if (state) { if (chan->type == IIO_LIGHT) - val = 1; + ret = regmap_field_write(rf->int_src, 1); else if (chan->type == IIO_INTENSITY) - val = 0; + ret = regmap_field_write(rf->int_src, 0); else return -EINVAL; - ret = regmap_field_write(rf->int_src, val); if (ret) return ret; - } - ret = regmap_field_read(rf->int_en, &val); - if (ret) - return ret; + if (enabled) + return 0; - if (val == state) - return 0; + ret = regmap_field_write(rf->int_en, 1); + if (ret) + return ret; - ret = regmap_field_write(rf->int_en, state); - if (ret) - return ret; - - if (state) return pm_runtime_resume_and_get(data->dev); + } else { + if (!enabled) + return 0; - pm_runtime_mark_last_busy(data->dev); - pm_runtime_put_autosuspend(data->dev); + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return ret; - return 0; + pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return 0; + } } case IIO_EV_TYPE_THRESH_ADAPTIVE: - return regmap_field_write(rf->int_thresh_var_en, state); + if (state) + return regmap_field_write(rf->int_thresh_var_en, 1); + else + return regmap_field_write(rf->int_thresh_var_en, 0); default: return -EINVAL; } From aabc0aa90c927a03d509d0b592720d9897894ce4 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Sat, 6 Apr 2024 17:31:04 +0200 Subject: [PATCH 083/108] Documentation: ABI: document in_temp_input file For example the BMP280 barometric pressure sensor on Qualcomm MSM8974-based Nexus 5 smartphone exposes such file in sysfs. Document it. Whilst here, fix up incorrect repeated use of X as a wildcard for in_tempY_input. Signed-off-by: Luca Weiss Link: https://lore.kernel.org/r/20240406-in_temp_input-v1-1-a1744a4a49e3@z3ntu.xyz Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 2e6d5ebfd3c7..7cee78ad4108 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -243,7 +243,8 @@ Description: less measurements. Units after application of scale and offset are milli degrees Celsius. -What: /sys/bus/iio/devices/iio:deviceX/in_tempX_input +What: /sys/bus/iio/devices/iio:deviceX/in_tempY_input +What: /sys/bus/iio/devices/iio:deviceX/in_temp_input KernelVersion: 2.6.38 Contact: linux-iio@vger.kernel.org Description: From 3735ca0b072656c3aa2cedc617a5e639b583a472 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 18:53:00 +0000 Subject: [PATCH 084/108] iio: adc: stm32: Fixing err code to not indicate success This path would result in returning 0 / success on an error path. Cc: Olivier Moysan Fixes: 95bc818404b2 ("iio: adc: stm32-adc: add support of generic channels binding") Reviewed-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240330185305.1319844-4-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/stm32-adc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index b5d3c9cea5c4..283c20757106 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -2234,6 +2234,7 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev, if (vin[0] != val || vin[1] >= adc_info->max_channels) { dev_err(&indio_dev->dev, "Invalid channel in%d-in%d\n", vin[0], vin[1]); + ret = -EINVAL; goto err; } } else if (ret != -EINVAL) { From 24622259e3a8f7df1972aafd801b888443e44363 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 18:53:01 +0000 Subject: [PATCH 085/108] iio: adc: stm32: Use device_for_each_child_node_scoped() Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. Note this would have made the bug fixed in the previous path much less likely as it allows for direct returns. Took advantage of dev_err_probe() to futher simplify things given no longer a need for the goto err. Cc: Olivier Moysan Tested-by: Fabrice Gasnier Acked-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240330185305.1319844-5-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/stm32-adc.c | 62 ++++++++++++++----------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 283c20757106..36add95212c3 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -2187,59 +2187,52 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev, struct iio_chan_spec *channels) { const struct stm32_adc_info *adc_info = adc->cfg->adc_info; - struct fwnode_handle *child; + struct device *dev = &indio_dev->dev; const char *name; int val, scan_index = 0, ret; bool differential; u32 vin[2]; - device_for_each_child_node(&indio_dev->dev, child) { + device_for_each_child_node_scoped(dev, child) { ret = fwnode_property_read_u32(child, "reg", &val); - if (ret) { - dev_err(&indio_dev->dev, "Missing channel index %d\n", ret); - goto err; - } + if (ret) + return dev_err_probe(dev, ret, + "Missing channel index\n"); ret = fwnode_property_read_string(child, "label", &name); /* label is optional */ if (!ret) { - if (strlen(name) >= STM32_ADC_CH_SZ) { - dev_err(&indio_dev->dev, "Label %s exceeds %d characters\n", - name, STM32_ADC_CH_SZ); - ret = -EINVAL; - goto err; - } + if (strlen(name) >= STM32_ADC_CH_SZ) + return dev_err_probe(dev, -EINVAL, + "Label %s exceeds %d characters\n", + name, STM32_ADC_CH_SZ); + strscpy(adc->chan_name[val], name, STM32_ADC_CH_SZ); ret = stm32_adc_populate_int_ch(indio_dev, name, val); if (ret == -ENOENT) continue; else if (ret) - goto err; + return ret; } else if (ret != -EINVAL) { - dev_err(&indio_dev->dev, "Invalid label %d\n", ret); - goto err; + return dev_err_probe(dev, ret, "Invalid label\n"); } - if (val >= adc_info->max_channels) { - dev_err(&indio_dev->dev, "Invalid channel %d\n", val); - ret = -EINVAL; - goto err; - } + if (val >= adc_info->max_channels) + return dev_err_probe(dev, -EINVAL, + "Invalid channel %d\n", val); differential = false; ret = fwnode_property_read_u32_array(child, "diff-channels", vin, 2); /* diff-channels is optional */ if (!ret) { differential = true; - if (vin[0] != val || vin[1] >= adc_info->max_channels) { - dev_err(&indio_dev->dev, "Invalid channel in%d-in%d\n", - vin[0], vin[1]); - ret = -EINVAL; - goto err; - } + if (vin[0] != val || vin[1] >= adc_info->max_channels) + return dev_err_probe(dev, -EINVAL, + "Invalid channel in%d-in%d\n", + vin[0], vin[1]); } else if (ret != -EINVAL) { - dev_err(&indio_dev->dev, "Invalid diff-channels property %d\n", ret); - goto err; + return dev_err_probe(dev, ret, + "Invalid diff-channels property\n"); } stm32_adc_chan_init_one(indio_dev, &channels[scan_index], val, @@ -2248,11 +2241,9 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev, val = 0; ret = fwnode_property_read_u32(child, "st,min-sample-time-ns", &val); /* st,min-sample-time-ns is optional */ - if (ret && ret != -EINVAL) { - dev_err(&indio_dev->dev, "Invalid st,min-sample-time-ns property %d\n", - ret); - goto err; - } + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, + "Invalid st,min-sample-time-ns property\n"); stm32_adc_smpr_init(adc, channels[scan_index].channel, val); if (differential) @@ -2262,11 +2253,6 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev, } return scan_index; - -err: - fwnode_handle_put(child); - - return ret; } static int stm32_adc_chan_fw_init(struct iio_dev *indio_dev, bool timestamping) From 77dc3b173d7258329e22ad8d8407bd5f8fe97fd1 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 18:52:59 +0000 Subject: [PATCH 086/108] iio: adc: qcom-spmi-adc5: Use device_for_each_child_node_scoped() Switching to the _scoped() version removes the need for manual calling of fwnode_handle_put() in the paths where the code exits the loop early. In this case that's all in error paths. A slightly less convincing usecase than many as all the failure paths are wrapped up in a call to a per fwnode_handle utility function. The complexity in that function is sufficient that it makes sense to factor it out even if it this new auto cleanup would enable simpler returns if the code was inline at the call site. Hence I've left it alone. Cc: Marijn Suijten Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20240330185305.1319844-3-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/qcom-spmi-adc5.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c index b6b612d733ff..9b69f40beed8 100644 --- a/drivers/iio/adc/qcom-spmi-adc5.c +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -825,7 +825,6 @@ static int adc5_get_fw_data(struct adc5_chip *adc) const struct adc5_channels *adc_chan; struct iio_chan_spec *iio_chan; struct adc5_channel_prop prop, *chan_props; - struct fwnode_handle *child; unsigned int index = 0; int ret; @@ -849,12 +848,10 @@ static int adc5_get_fw_data(struct adc5_chip *adc) if (!adc->data) adc->data = &adc5_data_pmic; - device_for_each_child_node(adc->dev, child) { + device_for_each_child_node_scoped(adc->dev, child) { ret = adc5_get_fw_channel_data(adc, &prop, child, adc->data); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } prop.scale_fn_type = adc->data->adc_chans[prop.channel].scale_fn_type; From d9dd38cb59fb828a428084128569e684dd51cbe9 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Mon, 8 Apr 2024 18:34:33 +0200 Subject: [PATCH 087/108] dt-bindings: iio: imu: mpu6050: Improve i2c-gate disallow list Before all supported sensors except for MPU{9150,9250,9255} were not allowed to use i2c-gate in the bindings which excluded quite a few supported sensors where this functionality is supported. Switch the list of sensors to ones where the Linux driver explicitly disallows support for the auxiliary bus ("inv_mpu_i2c_aux_bus"). Since the driver is also based on "default: return true" this should scale better into the future. Signed-off-by: Luca Weiss Acked-by: Jean-Baptiste Maneyrol Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240408-mpu6050-i2c-gate-v1-1-621f051ce7de@z3ntu.xyz Signed-off-by: Jonathan Cameron --- .../bindings/iio/imu/invensense,mpu6050.yaml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml index 297b8a1a7ffb..587ff2bced2d 100644 --- a/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml +++ b/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml @@ -62,14 +62,15 @@ properties: allOf: - $ref: /schemas/spi/spi-peripheral-props.yaml# - if: - not: - properties: - compatible: - contains: - enum: - - invensense,mpu9150 - - invensense,mpu9250 - - invensense,mpu9255 + properties: + compatible: + contains: + enum: + - invensense,iam20680 + - invensense,icm20602 + - invensense,icm20608 + - invensense,icm20609 + - invensense,icm20689 then: properties: i2c-gate: false From 61c8031af674bb80a7e346368ea0a41632e1f5fc Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 12 Apr 2024 18:25:02 -0500 Subject: [PATCH 088/108] iio: adc: ad7944: Consolidate spi_sync() wrapper Since commit 6020ca4de8e5 ("iio: adc: ad7944: use spi_optimize_message()"), The helper functions wrapping spi_sync() for 3-wire and 4-wire modes are virtually identical. Since gpiod_set_value_cansleep() does a NULL check internally, we can consolidate the two functions into one and avoid switch statements at the call sites. The default cases of the removed switch statement were just to make the compiler happy and are not reachable since the mode is validated in the probe function. So removing those should be safe. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240412-ad7944-consolidate-msg-v1-1-7fdeff89172f@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7944.c | 67 ++++++++-------------------------------- 1 file changed, 13 insertions(+), 54 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index 9dc86ec23c36..4af574ffa864 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -214,40 +214,26 @@ static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg); } -/* - * ad7944_3wire_cs_mode_conversion - Perform a 3-wire CS mode conversion and - * acquisition +/** + * ad7944_convert_and_acquire - Perform a single conversion and acquisition * @adc: The ADC device structure * @chan: The channel specification * Return: 0 on success, a negative error code on failure * - * This performs a conversion and reads data when the chip is wired in 3-wire - * mode with the CNV line on the ADC tied to the CS line on the SPI controller. + * Perform a conversion and acquisition of a single sample using the + * pre-optimized adc->msg. * * Upon successful return adc->sample.raw will contain the conversion result. */ -static int ad7944_3wire_cs_mode_conversion(struct ad7944_adc *adc, - const struct iio_chan_spec *chan) -{ - return spi_sync(adc->spi, &adc->msg); -} - -/* - * ad7944_4wire_mode_conversion - Perform a 4-wire mode conversion and acquisition - * @adc: The ADC device structure - * @chan: The channel specification - * Return: 0 on success, a negative error code on failure - * - * Upon successful return adc->sample.raw will contain the conversion result. - */ -static int ad7944_4wire_mode_conversion(struct ad7944_adc *adc, - const struct iio_chan_spec *chan) +static int ad7944_convert_and_acquire(struct ad7944_adc *adc, + const struct iio_chan_spec *chan) { int ret; /* * In 4-wire mode, the CNV line is held high for the entire conversion - * and acquisition process. + * and acquisition process. In other modes adc->cnv is NULL and is + * ignored (CS is wired to CNV in those cases). */ gpiod_set_value_cansleep(adc->cnv, 1); ret = spi_sync(adc->spi, &adc->msg); @@ -262,22 +248,9 @@ static int ad7944_single_conversion(struct ad7944_adc *adc, { int ret; - switch (adc->spi_mode) { - case AD7944_SPI_MODE_DEFAULT: - ret = ad7944_4wire_mode_conversion(adc, chan); - if (ret) - return ret; - - break; - case AD7944_SPI_MODE_SINGLE: - ret = ad7944_3wire_cs_mode_conversion(adc, chan); - if (ret) - return ret; - - break; - default: - return -EOPNOTSUPP; - } + ret = ad7944_convert_and_acquire(adc, chan); + if (ret) + return ret; if (chan->scan_type.storagebits > 16) *val = adc->sample.raw.u32; @@ -338,23 +311,9 @@ static irqreturn_t ad7944_trigger_handler(int irq, void *p) struct ad7944_adc *adc = iio_priv(indio_dev); int ret; - switch (adc->spi_mode) { - case AD7944_SPI_MODE_DEFAULT: - ret = ad7944_4wire_mode_conversion(adc, &indio_dev->channels[0]); - if (ret) - goto out; - - break; - case AD7944_SPI_MODE_SINGLE: - ret = ad7944_3wire_cs_mode_conversion(adc, &indio_dev->channels[0]); - if (ret) - goto out; - - break; - default: - /* not supported */ + ret = ad7944_convert_and_acquire(adc, &indio_dev->channels[0]); + if (ret) goto out; - } iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, pf->timestamp); From 6a9e5518287ba3807968ba2213b5636e572567ad Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 8 Apr 2024 09:07:19 +0000 Subject: [PATCH 089/108] dt-bindings: iio: imu: add icm42688 inside inv_icm42600 Add bindings for ICM-42688-P chip. Signed-off-by: Jean-Baptiste Maneyrol Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240408090720.847107-2-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/imu/invensense,icm42600.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml index 7cd05bcbee31..5e0bed2c45de 100644 --- a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml @@ -32,6 +32,7 @@ properties: - invensense,icm42605 - invensense,icm42622 - invensense,icm42631 + - invensense,icm42688 reg: maxItems: 1 From 88b49449f25ddab6d10e4ba4fad225cb81d0a6d8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 8 Apr 2024 09:07:20 +0000 Subject: [PATCH 090/108] iio: imu: inv_icm42600: add support of ICM-42688-P Add ICM-42688-P support inside driver. Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240408090720.847107-3-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42600/inv_icm42600.h | 2 ++ drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 5 +++++ drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c | 3 +++ drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c | 3 +++ 4 files changed, 13 insertions(+) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h index 0e290c807b0f..0566340b2660 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -22,6 +22,7 @@ enum inv_icm42600_chip { INV_CHIP_ICM42602, INV_CHIP_ICM42605, INV_CHIP_ICM42622, + INV_CHIP_ICM42688, INV_CHIP_ICM42631, INV_CHIP_NB, }; @@ -304,6 +305,7 @@ struct inv_icm42600_state { #define INV_ICM42600_WHOAMI_ICM42602 0x41 #define INV_ICM42600_WHOAMI_ICM42605 0x42 #define INV_ICM42600_WHOAMI_ICM42622 0x46 +#define INV_ICM42600_WHOAMI_ICM42688 0x47 #define INV_ICM42600_WHOAMI_ICM42631 0x5C /* User bank 1 (MSB 0x10) */ diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c index a5e81906e37e..82e0a2e2ad70 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -87,6 +87,11 @@ static const struct inv_icm42600_hw inv_icm42600_hw[INV_CHIP_NB] = { .name = "icm42622", .conf = &inv_icm42600_default_conf, }, + [INV_CHIP_ICM42688] = { + .whoami = INV_ICM42600_WHOAMI_ICM42688, + .name = "icm42688", + .conf = &inv_icm42600_default_conf, + }, [INV_CHIP_ICM42631] = { .whoami = INV_ICM42600_WHOAMI_ICM42631, .name = "icm42631", diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c index 1af559403ba6..ebb28f84ba98 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c @@ -84,6 +84,9 @@ static const struct of_device_id inv_icm42600_of_matches[] = { }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, }, { .compatible = "invensense,icm42631", .data = (void *)INV_CHIP_ICM42631, diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c index 6be4ac794937..50217a10e0bb 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c @@ -80,6 +80,9 @@ static const struct of_device_id inv_icm42600_of_matches[] = { }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, }, { .compatible = "invensense,icm42631", .data = (void *)INV_CHIP_ICM42631, From 62d3fb9dcc091ccdf25eb3b716e90e07e3ed861f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 13 Apr 2024 17:45:11 +0200 Subject: [PATCH 091/108] iio: dac: ad5755: make use of of_device_id table Store pointers to chip info (struct ad5755_chip_info) in driver match data, instead of enum, so every value will be != 0, populate the of_device_id table and use it in driver. Even though it is one change, it gives multiple benefits: 1. Allows to use spi_get_device_match_data() dropping local 'type' variable. 2. Makes both ID tables usable, so kernel can match via any of these methods. 3. Code is more obvious as both tables are properly filled. 4. Fixes W=1 warning: ad5755.c:866:34: error: unused variable 'ad5755_of_match' [-Werror,-Wunused-const-variable] Cc: Arnd Bergmann Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240413154511.52576-1-krzysztof.kozlowski@linaro.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5755.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c index 404865e35460..0b24cb19ac9d 100644 --- a/drivers/iio/dac/ad5755.c +++ b/drivers/iio/dac/ad5755.c @@ -809,7 +809,6 @@ static struct ad5755_platform_data *ad5755_parse_fw(struct device *dev) static int ad5755_probe(struct spi_device *spi) { - enum ad5755_type type = spi_get_device_id(spi)->driver_data; const struct ad5755_platform_data *pdata; struct iio_dev *indio_dev; struct ad5755_state *st; @@ -824,7 +823,7 @@ static int ad5755_probe(struct spi_device *spi) st = iio_priv(indio_dev); spi_set_drvdata(spi, indio_dev); - st->chip_info = &ad5755_chip_info_tbl[type]; + st->chip_info = spi_get_device_match_data(spi); st->spi = spi; st->pwr_down = 0xf; @@ -854,21 +853,21 @@ static int ad5755_probe(struct spi_device *spi) } static const struct spi_device_id ad5755_id[] = { - { "ad5755", ID_AD5755 }, - { "ad5755-1", ID_AD5755 }, - { "ad5757", ID_AD5757 }, - { "ad5735", ID_AD5735 }, - { "ad5737", ID_AD5737 }, + { "ad5755", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5755] }, + { "ad5755-1", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5755] }, + { "ad5757", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5757] }, + { "ad5735", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5735] }, + { "ad5737", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5737] }, {} }; MODULE_DEVICE_TABLE(spi, ad5755_id); static const struct of_device_id ad5755_of_match[] = { - { .compatible = "adi,ad5755" }, - { .compatible = "adi,ad5755-1" }, - { .compatible = "adi,ad5757" }, - { .compatible = "adi,ad5735" }, - { .compatible = "adi,ad5737" }, + { .compatible = "adi,ad5755", &ad5755_chip_info_tbl[ID_AD5755] }, + { .compatible = "adi,ad5755-1", &ad5755_chip_info_tbl[ID_AD5755] }, + { .compatible = "adi,ad5757", &ad5755_chip_info_tbl[ID_AD5757] }, + { .compatible = "adi,ad5735", &ad5755_chip_info_tbl[ID_AD5735] }, + { .compatible = "adi,ad5737", &ad5755_chip_info_tbl[ID_AD5737] }, { } }; MODULE_DEVICE_TABLE(of, ad5755_of_match); @@ -876,6 +875,7 @@ MODULE_DEVICE_TABLE(of, ad5755_of_match); static struct spi_driver ad5755_driver = { .driver = { .name = "ad5755", + .of_match_table = ad5755_of_match, }, .probe = ad5755_probe, .id_table = ad5755_id, From 499eb311513fa30e136d01abfb316c482379afa1 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 17 Apr 2024 12:04:22 -0500 Subject: [PATCH 092/108] dt-bindings: iio: adc: Add GPADC for Allwinner H616 Add support for the GPADC for the Allwinner H616. It is identical to the existing ADC for the D1/T113s/R329/T507 SoCs. Signed-off-by: Chris Morgan Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240417170423.20640-3-macroalpha82@gmail.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml b/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml index 7ef46c90ebc8..da605a051b94 100644 --- a/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml @@ -11,8 +11,13 @@ maintainers: properties: compatible: - enum: - - allwinner,sun20i-d1-gpadc + oneOf: + - enum: + - allwinner,sun20i-d1-gpadc + - items: + - enum: + - allwinner,sun50i-h616-gpadc + - const: allwinner,sun20i-d1-gpadc "#io-channel-cells": const: 1 From 9dd6b32e76ff714308964cd9ec91466a343dcb8b Mon Sep 17 00:00:00 2001 From: Thomas Haemmerle Date: Mon, 15 Apr 2024 12:50:27 +0200 Subject: [PATCH 093/108] iio: pressure: dps310: support negative temperature values The current implementation interprets negative values returned from `dps310_calculate_temp` as error codes. This has a side effect that when negative temperature values are calculated, they are interpreted as error. Fix this by using the return value only for error handling and passing a pointer for the value. Fixes: ba6ec48e76bc ("iio: Add driver for Infineon DPS310") Signed-off-by: Thomas Haemmerle Link: https://lore.kernel.org/r/20240415105030.1161770-2-thomas.haemmerle@leica-geosystems.com Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index 1ff091b2f764..d0a516d56da4 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -730,7 +730,7 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, } } -static int dps310_calculate_temp(struct dps310_data *data) +static int dps310_calculate_temp(struct dps310_data *data, int *val) { s64 c0; s64 t; @@ -746,7 +746,9 @@ static int dps310_calculate_temp(struct dps310_data *data) t = c0 + ((s64)data->temp_raw * (s64)data->c1); /* Convert to milliCelsius and scale the temperature */ - return (int)div_s64(t * 1000LL, kt); + *val = (int)div_s64(t * 1000LL, kt); + + return 0; } static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, @@ -768,11 +770,10 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, if (rc) return rc; - rc = dps310_calculate_temp(data); - if (rc < 0) + rc = dps310_calculate_temp(data, val); + if (rc) return rc; - *val = rc; return IIO_VAL_INT; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: From b8189beb2c87cb7aae114734baf19d2bd33342ce Mon Sep 17 00:00:00 2001 From: Thomas Haemmerle Date: Mon, 15 Apr 2024 12:50:28 +0200 Subject: [PATCH 094/108] iio: pressure: dps310: introduce consistent error handling Align error handling with `dps310_calculate_temp`, where it's not possible to differentiate between errors and valid calculations by checking if the returned value is negative. Signed-off-by: Thomas Haemmerle Link: https://lore.kernel.org/r/20240415105030.1161770-3-thomas.haemmerle@leica-geosystems.com Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 129 +++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 57 deletions(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index d0a516d56da4..3fc436f36fa7 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -256,24 +256,24 @@ static int dps310_startup(struct dps310_data *data) return dps310_temp_workaround(data); } -static int dps310_get_pres_precision(struct dps310_data *data) +static int dps310_get_pres_precision(struct dps310_data *data, int *val) { - int rc; - int val; + int reg_val, rc; - rc = regmap_read(data->regmap, DPS310_PRS_CFG, &val); + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); if (rc < 0) return rc; - return BIT(val & GENMASK(2, 0)); + *val = BIT(reg_val & GENMASK(2, 0)); + + return 0; } -static int dps310_get_temp_precision(struct dps310_data *data) +static int dps310_get_temp_precision(struct dps310_data *data, int *val) { - int rc; - int val; + int reg_val, rc; - rc = regmap_read(data->regmap, DPS310_TMP_CFG, &val); + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); if (rc < 0) return rc; @@ -281,7 +281,9 @@ static int dps310_get_temp_precision(struct dps310_data *data) * Scale factor is bottom 4 bits of the register, but 1111 is * reserved so just grab bottom three */ - return BIT(val & GENMASK(2, 0)); + *val = BIT(reg_val & GENMASK(2, 0)); + + return 0; } /* Called with lock held */ @@ -350,48 +352,56 @@ static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq) DPS310_TMP_RATE_BITS, val); } -static int dps310_get_pres_samp_freq(struct dps310_data *data) +static int dps310_get_pres_samp_freq(struct dps310_data *data, int *val) { - int rc; - int val; + int reg_val, rc; - rc = regmap_read(data->regmap, DPS310_PRS_CFG, &val); + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); if (rc < 0) return rc; - return BIT((val & DPS310_PRS_RATE_BITS) >> 4); + *val = BIT((reg_val & DPS310_PRS_RATE_BITS) >> 4); + + return 0; } -static int dps310_get_temp_samp_freq(struct dps310_data *data) +static int dps310_get_temp_samp_freq(struct dps310_data *data, int *val) { - int rc; - int val; + int reg_val, rc; - rc = regmap_read(data->regmap, DPS310_TMP_CFG, &val); + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); if (rc < 0) return rc; - return BIT((val & DPS310_TMP_RATE_BITS) >> 4); + *val = BIT((reg_val & DPS310_TMP_RATE_BITS) >> 4); + + return 0; } -static int dps310_get_pres_k(struct dps310_data *data) +static int dps310_get_pres_k(struct dps310_data *data, int *val) { - int rc = dps310_get_pres_precision(data); + int reg_val, rc; - if (rc < 0) + rc = dps310_get_pres_precision(data, ®_val); + if (rc) return rc; - return scale_factors[ilog2(rc)]; + *val = scale_factors[ilog2(reg_val)]; + + return 0; } -static int dps310_get_temp_k(struct dps310_data *data) +static int dps310_get_temp_k(struct dps310_data *data, int *val) { - int rc = dps310_get_temp_precision(data); + int reg_val, rc; - if (rc < 0) + rc = dps310_get_temp_precision(data, ®_val); + if (rc) return rc; - return scale_factors[ilog2(rc)]; + *val = scale_factors[ilog2(reg_val)]; + + return 0; } static int dps310_reset_wait(struct dps310_data *data) @@ -464,7 +474,10 @@ static int dps310_read_pres_raw(struct dps310_data *data) if (mutex_lock_interruptible(&data->lock)) return -EINTR; - rate = dps310_get_pres_samp_freq(data); + rc = dps310_get_pres_samp_freq(data, &rate); + if (rc) + goto done; + timeout = DPS310_POLL_TIMEOUT_US(rate); /* Poll for sensor readiness; base the timeout upon the sample rate. */ @@ -510,7 +523,10 @@ static int dps310_read_temp_raw(struct dps310_data *data) if (mutex_lock_interruptible(&data->lock)) return -EINTR; - rate = dps310_get_temp_samp_freq(data); + rc = dps310_get_temp_samp_freq(data, &rate); + if (rc) + goto done; + timeout = DPS310_POLL_TIMEOUT_US(rate); /* Poll for sensor readiness; base the timeout upon the sample rate. */ @@ -612,13 +628,13 @@ static int dps310_write_raw(struct iio_dev *iio, return rc; } -static int dps310_calculate_pressure(struct dps310_data *data) +static int dps310_calculate_pressure(struct dps310_data *data, int *val) { int i; int rc; int t_ready; - int kpi = dps310_get_pres_k(data); - int kti = dps310_get_temp_k(data); + int kpi; + int kti; s64 rem = 0ULL; s64 pressure = 0ULL; s64 p; @@ -629,11 +645,13 @@ static int dps310_calculate_pressure(struct dps310_data *data) s64 kp; s64 kt; - if (kpi < 0) - return kpi; + rc = dps310_get_pres_k(data, &kpi); + if (rc) + return rc; - if (kti < 0) - return kti; + rc = dps310_get_temp_k(data, &kti); + if (rc) + return rc; kp = (s64)kpi; kt = (s64)kti; @@ -687,7 +705,9 @@ static int dps310_calculate_pressure(struct dps310_data *data) if (pressure < 0LL) return -ERANGE; - return (int)min_t(s64, pressure, INT_MAX); + *val = (int)min_t(s64, pressure, INT_MAX); + + return 0; } static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, @@ -697,11 +717,10 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: - rc = dps310_get_pres_samp_freq(data); - if (rc < 0) + rc = dps310_get_pres_samp_freq(data, val); + if (rc) return rc; - *val = rc; return IIO_VAL_INT; case IIO_CHAN_INFO_PROCESSED: @@ -709,20 +728,17 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, if (rc) return rc; - rc = dps310_calculate_pressure(data); - if (rc < 0) + rc = dps310_calculate_pressure(data, val); + if (rc) return rc; - *val = rc; *val2 = 1000; /* Convert Pa to KPa per IIO ABI */ return IIO_VAL_FRACTIONAL; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: - rc = dps310_get_pres_precision(data); - if (rc < 0) + rc = dps310_get_pres_precision(data, val); + if (rc) return rc; - - *val = rc; return IIO_VAL_INT; default: @@ -734,10 +750,11 @@ static int dps310_calculate_temp(struct dps310_data *data, int *val) { s64 c0; s64 t; - int kt = dps310_get_temp_k(data); + int kt, rc; - if (kt < 0) - return kt; + rc = dps310_get_temp_k(data, &kt); + if (rc) + return rc; /* Obtain inverse-scaled offset */ c0 = div_s64((s64)kt * (s64)data->c0, 2); @@ -758,11 +775,10 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: - rc = dps310_get_temp_samp_freq(data); - if (rc < 0) + rc = dps310_get_temp_samp_freq(data, val); + if (rc) return rc; - *val = rc; return IIO_VAL_INT; case IIO_CHAN_INFO_PROCESSED: @@ -777,11 +793,10 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, return IIO_VAL_INT; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: - rc = dps310_get_temp_precision(data); - if (rc < 0) + rc = dps310_get_temp_precision(data, val); + if (rc) return rc; - *val = rc; return IIO_VAL_INT; default: From c046bb5d9512e3c00b0e17c581f50bbb6c641d58 Mon Sep 17 00:00:00 2001 From: Thomas Haemmerle Date: Mon, 15 Apr 2024 12:50:29 +0200 Subject: [PATCH 095/108] iio: pressure: dps310: consistently check return value of `regmap_read` Align the check of return values `regmap_read` so that it's consistent across this driver code. Signed-off-by: Thomas Haemmerle Link: https://lore.kernel.org/r/20240415105030.1161770-4-thomas.haemmerle@leica-geosystems.com Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index 3fc436f36fa7..c30623d96f0e 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -171,7 +171,7 @@ static int dps310_temp_workaround(struct dps310_data *data) int reg; rc = regmap_read(data->regmap, 0x32, ®); - if (rc) + if (rc < 0) return rc; /* From 5826711e841450755cae32bacc59f6465075db94 Mon Sep 17 00:00:00 2001 From: Thomas Haemmerle Date: Mon, 15 Apr 2024 12:50:30 +0200 Subject: [PATCH 096/108] iio: pressure: dps310: simplify scale factor reading Both functions `dps310_get_pres_precision` and `dps310_get_temp_precision` provide the oversampling rate by calling the `BIT()` macro. However, to look up the corresponding scale factor, we need the register value itself. Currently, this is achieved by undoing the calculation of the oversampling rate with `ilog2()`. Simplify the two functions for getting the scale factor and directly use the register content for the lookup. Signed-off-by: Thomas Haemmerle Link: https://lore.kernel.org/r/20240415105030.1161770-5-thomas.haemmerle@leica-geosystems.com Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/dps310.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index c30623d96f0e..7d882e15e556 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -382,11 +382,11 @@ static int dps310_get_pres_k(struct dps310_data *data, int *val) { int reg_val, rc; - rc = dps310_get_pres_precision(data, ®_val); - if (rc) + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); + if (rc < 0) return rc; - *val = scale_factors[ilog2(reg_val)]; + *val = scale_factors[reg_val & GENMASK(2, 0)]; return 0; } @@ -395,11 +395,11 @@ static int dps310_get_temp_k(struct dps310_data *data, int *val) { int reg_val, rc; - rc = dps310_get_temp_precision(data, ®_val); - if (rc) + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); + if (rc < 0) return rc; - *val = scale_factors[ilog2(reg_val)]; + *val = scale_factors[reg_val & GENMASK(2, 0)]; return 0; } From a094de22e2efc2ec7f540d10d1edb7038f863925 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 19 Apr 2024 10:25:34 +0200 Subject: [PATCH 097/108] iio: buffer-dma: add iio_dmaengine_buffer_setup() This brings the DMA buffer API more in line with what we have in the triggered buffer. There's no need of having both devm_iio_dmaengine_buffer_setup() and devm_iio_dmaengine_buffer_alloc(). Hence we introduce the new iio_dmaengine_buffer_setup() that together with devm_iio_dmaengine_buffer_setup() should be all we need. Note that as part of this change iio_dmaengine_buffer_alloc() is again static and the axi-adc was updated accordingly. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-1-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 16 +------ .../buffer/industrialio-buffer-dmaengine.c | 48 ++++++++----------- include/linux/iio/buffer-dmaengine.h | 5 +- 3 files changed, 24 insertions(+), 45 deletions(-) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 4156639b3c8b..184b36dca6d0 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -124,26 +124,12 @@ static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back, struct iio_dev *indio_dev) { struct adi_axi_adc_state *st = iio_backend_get_priv(back); - struct iio_buffer *buffer; const char *dma_name; - int ret; if (device_property_read_string(st->dev, "dma-names", &dma_name)) dma_name = "rx"; - buffer = iio_dmaengine_buffer_alloc(st->dev, dma_name); - if (IS_ERR(buffer)) { - dev_err(st->dev, "Could not get DMA buffer, %ld\n", - PTR_ERR(buffer)); - return ERR_CAST(buffer); - } - - indio_dev->modes |= INDIO_BUFFER_HARDWARE; - ret = iio_device_attach_buffer(indio_dev, buffer); - if (ret) - return ERR_PTR(ret); - - return buffer; + return iio_dmaengine_buffer_setup(st->dev, indio_dev, dma_name); } static void axi_adc_free_buffer(struct iio_backend *back, diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index a18c1da292af..97f3116566f5 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -159,7 +159,7 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = { * Once done using the buffer iio_dmaengine_buffer_free() should be used to * release it. */ -struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev, +static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev, const char *channel) { struct dmaengine_buffer *dmaengine_buffer; @@ -210,7 +210,6 @@ struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev, kfree(dmaengine_buffer); return ERR_PTR(ret); } -EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_alloc, IIO_DMAENGINE_BUFFER); /** * iio_dmaengine_buffer_free() - Free dmaengine buffer @@ -230,39 +229,33 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer) } EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, IIO_DMAENGINE_BUFFER); -static void __devm_iio_dmaengine_buffer_free(void *buffer) -{ - iio_dmaengine_buffer_free(buffer); -} - -/** - * devm_iio_dmaengine_buffer_alloc() - Resource-managed iio_dmaengine_buffer_alloc() - * @dev: Parent device for the buffer - * @channel: DMA channel name, typically "rx". - * - * This allocates a new IIO buffer which internally uses the DMAengine framework - * to perform its transfers. The parent device will be used to request the DMA - * channel. - * - * The buffer will be automatically de-allocated once the device gets destroyed. - */ -static struct iio_buffer *devm_iio_dmaengine_buffer_alloc(struct device *dev, - const char *channel) +struct iio_buffer *iio_dmaengine_buffer_setup(struct device *dev, + struct iio_dev *indio_dev, + const char *channel) { struct iio_buffer *buffer; int ret; buffer = iio_dmaengine_buffer_alloc(dev, channel); if (IS_ERR(buffer)) - return buffer; + return ERR_CAST(buffer); - ret = devm_add_action_or_reset(dev, __devm_iio_dmaengine_buffer_free, - buffer); - if (ret) + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + ret = iio_device_attach_buffer(indio_dev, buffer); + if (ret) { + iio_dmaengine_buffer_free(buffer); return ERR_PTR(ret); + } return buffer; } +EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup, IIO_DMAENGINE_BUFFER); + +static void __devm_iio_dmaengine_buffer_free(void *buffer) +{ + iio_dmaengine_buffer_free(buffer); +} /** * devm_iio_dmaengine_buffer_setup() - Setup a DMA buffer for an IIO device @@ -281,13 +274,12 @@ int devm_iio_dmaengine_buffer_setup(struct device *dev, { struct iio_buffer *buffer; - buffer = devm_iio_dmaengine_buffer_alloc(dev, channel); + buffer = iio_dmaengine_buffer_setup(dev, indio_dev, channel); if (IS_ERR(buffer)) return PTR_ERR(buffer); - indio_dev->modes |= INDIO_BUFFER_HARDWARE; - - return iio_device_attach_buffer(indio_dev, buffer); + return devm_add_action_or_reset(dev, __devm_iio_dmaengine_buffer_free, + buffer); } EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup, IIO_DMAENGINE_BUFFER); diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-dmaengine.h index cbb8ba957fad..acb60f9a3fff 100644 --- a/include/linux/iio/buffer-dmaengine.h +++ b/include/linux/iio/buffer-dmaengine.h @@ -10,9 +10,10 @@ struct iio_dev; struct device; -struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev, - const char *channel); void iio_dmaengine_buffer_free(struct iio_buffer *buffer); +struct iio_buffer *iio_dmaengine_buffer_setup(struct device *dev, + struct iio_dev *indio_dev, + const char *channel); int devm_iio_dmaengine_buffer_setup(struct device *dev, struct iio_dev *indio_dev, const char *channel); From 04ae3b1a76b77f98a4a0c8ed2c544007334fc680 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Fri, 19 Apr 2024 10:25:35 +0200 Subject: [PATCH 098/108] iio: buffer-dma: Rename iio_dma_buffer_data_available() Change its name to iio_dma_buffer_usage(), as this function can be used both for the .data_available and the .space_available callbacks. Signed-off-by: Paul Cercueil Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-2-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dma.c | 11 ++++++----- drivers/iio/buffer/industrialio-buffer-dmaengine.c | 2 +- include/linux/iio/buffer-dma.h | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c index 5610ba67925e..404f9867bdc5 100644 --- a/drivers/iio/buffer/industrialio-buffer-dma.c +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -547,13 +547,14 @@ int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, EXPORT_SYMBOL_GPL(iio_dma_buffer_read); /** - * iio_dma_buffer_data_available() - DMA buffer data_available callback + * iio_dma_buffer_usage() - DMA buffer data_available and + * space_available callback * @buf: Buffer to check for data availability * - * Should be used as the data_available callback for iio_buffer_access_ops - * struct for DMA buffers. + * Should be used as the data_available and space_available callbacks for + * iio_buffer_access_ops struct for DMA buffers. */ -size_t iio_dma_buffer_data_available(struct iio_buffer *buf) +size_t iio_dma_buffer_usage(struct iio_buffer *buf) { struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf); struct iio_dma_buffer_block *block; @@ -586,7 +587,7 @@ size_t iio_dma_buffer_data_available(struct iio_buffer *buf) return data_available; } -EXPORT_SYMBOL_GPL(iio_dma_buffer_data_available); +EXPORT_SYMBOL_GPL(iio_dma_buffer_usage); /** * iio_dma_buffer_set_bytes_per_datum() - DMA buffer set_bytes_per_datum callback diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 97f3116566f5..df05d66afff9 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -117,7 +117,7 @@ static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { .request_update = iio_dma_buffer_request_update, .enable = iio_dma_buffer_enable, .disable = iio_dma_buffer_disable, - .data_available = iio_dma_buffer_data_available, + .data_available = iio_dma_buffer_usage, .release = iio_dmaengine_buffer_release, .modes = INDIO_BUFFER_HARDWARE, diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h index 18d3702fa95d..52a838ec0e57 100644 --- a/include/linux/iio/buffer-dma.h +++ b/include/linux/iio/buffer-dma.h @@ -132,7 +132,7 @@ int iio_dma_buffer_disable(struct iio_buffer *buffer, struct iio_dev *indio_dev); int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, char __user *user_buffer); -size_t iio_dma_buffer_data_available(struct iio_buffer *buffer); +size_t iio_dma_buffer_usage(struct iio_buffer *buffer); int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd); int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length); int iio_dma_buffer_request_update(struct iio_buffer *buffer); From fb09febafd160b7aefd9e61f710a0c50f0472403 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Fri, 19 Apr 2024 10:25:36 +0200 Subject: [PATCH 099/108] iio: buffer-dma: Enable buffer write support Adding write support to the buffer-dma code is easy - the write() function basically needs to do the exact same thing as the read() function: dequeue a block, read or write the data, enqueue the block when entirely processed. Therefore, the iio_buffer_dma_read() and the new iio_buffer_dma_write() now both call a function iio_buffer_dma_io(), which will perform this task. Note that we preemptively reset block->bytes_used to the buffer's size in iio_dma_buffer_request_update(), as in the future the iio_dma_buffer_enqueue() function won't reset it. Signed-off-by: Paul Cercueil Reviewed-by: Alexandru Ardelean Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-3-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dma.c | 89 ++++++++++++++++---- include/linux/iio/buffer-dma.h | 2 + 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c index 404f9867bdc5..13b1a858969e 100644 --- a/drivers/iio/buffer/industrialio-buffer-dma.c +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -195,6 +195,18 @@ static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) block->state = IIO_BLOCK_STATE_DONE; } +static void iio_dma_buffer_queue_wake(struct iio_dma_buffer_queue *queue) +{ + __poll_t flags; + + if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) + flags = EPOLLIN | EPOLLRDNORM; + else + flags = EPOLLOUT | EPOLLWRNORM; + + wake_up_interruptible_poll(&queue->buffer.pollq, flags); +} + /** * iio_dma_buffer_block_done() - Indicate that a block has been completed * @block: The completed block @@ -212,7 +224,7 @@ void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) spin_unlock_irqrestore(&queue->list_lock, flags); iio_buffer_block_put_atomic(block); - wake_up_interruptible_poll(&queue->buffer.pollq, EPOLLIN | EPOLLRDNORM); + iio_dma_buffer_queue_wake(queue); } EXPORT_SYMBOL_GPL(iio_dma_buffer_block_done); @@ -241,7 +253,7 @@ void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, } spin_unlock_irqrestore(&queue->list_lock, flags); - wake_up_interruptible_poll(&queue->buffer.pollq, EPOLLIN | EPOLLRDNORM); + iio_dma_buffer_queue_wake(queue); } EXPORT_SYMBOL_GPL(iio_dma_buffer_block_list_abort); @@ -335,8 +347,24 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) queue->fileio.blocks[i] = block; } - block->state = IIO_BLOCK_STATE_QUEUED; - list_add_tail(&block->head, &queue->incoming); + /* + * block->bytes_used may have been modified previously, e.g. by + * iio_dma_buffer_block_list_abort(). Reset it here to the + * block's so that iio_dma_buffer_io() will work. + */ + block->bytes_used = block->size; + + /* + * If it's an input buffer, mark the block as queued, and + * iio_dma_buffer_enable() will submit it. Otherwise mark it as + * done, which means it's ready to be dequeued. + */ + if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) { + block->state = IIO_BLOCK_STATE_QUEUED; + list_add_tail(&block->head, &queue->incoming); + } else { + block->state = IIO_BLOCK_STATE_DONE; + } } out_unlock: @@ -488,20 +516,12 @@ static struct iio_dma_buffer_block *iio_dma_buffer_dequeue( return block; } -/** - * iio_dma_buffer_read() - DMA buffer read callback - * @buffer: Buffer to read form - * @n: Number of bytes to read - * @user_buffer: Userspace buffer to copy the data to - * - * Should be used as the read callback for iio_buffer_access_ops - * struct for DMA buffers. - */ -int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, - char __user *user_buffer) +static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, + char __user *user_buffer, bool is_from_user) { struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); struct iio_dma_buffer_block *block; + void *addr; int ret; if (n < buffer->bytes_per_datum) @@ -524,8 +544,13 @@ int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, n = rounddown(n, buffer->bytes_per_datum); if (n > block->bytes_used - queue->fileio.pos) n = block->bytes_used - queue->fileio.pos; + addr = block->vaddr + queue->fileio.pos; - if (copy_to_user(user_buffer, block->vaddr + queue->fileio.pos, n)) { + if (is_from_user) + ret = copy_from_user(addr, user_buffer, n); + else + ret = copy_to_user(user_buffer, addr, n); + if (ret) { ret = -EFAULT; goto out_unlock; } @@ -544,8 +569,40 @@ int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, return ret; } + +/** + * iio_dma_buffer_read() - DMA buffer read callback + * @buffer: Buffer to read form + * @n: Number of bytes to read + * @user_buffer: Userspace buffer to copy the data to + * + * Should be used as the read callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, + char __user *user_buffer) +{ + return iio_dma_buffer_io(buffer, n, user_buffer, false); +} EXPORT_SYMBOL_GPL(iio_dma_buffer_read); +/** + * iio_dma_buffer_write() - DMA buffer write callback + * @buffer: Buffer to read form + * @n: Number of bytes to read + * @user_buffer: Userspace buffer to copy the data from + * + * Should be used as the write callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n, + const char __user *user_buffer) +{ + return iio_dma_buffer_io(buffer, n, + (__force __user char *)user_buffer, true); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_write); + /** * iio_dma_buffer_usage() - DMA buffer data_available and * space_available callback diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h index 52a838ec0e57..6e27e47077d5 100644 --- a/include/linux/iio/buffer-dma.h +++ b/include/linux/iio/buffer-dma.h @@ -132,6 +132,8 @@ int iio_dma_buffer_disable(struct iio_buffer *buffer, struct iio_dev *indio_dev); int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, char __user *user_buffer); +int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n, + const char __user *user_buffer); size_t iio_dma_buffer_usage(struct iio_buffer *buffer); int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd); int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length); From c1b91566580c245cf1147745d174be5e059ace6b Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Fri, 19 Apr 2024 10:25:37 +0200 Subject: [PATCH 100/108] iio: buffer-dmaengine: Support specifying buffer direction Update the devm_iio_dmaengine_buffer_setup() function to support specifying the buffer direction. Update the iio_dmaengine_buffer_submit() function to handle input buffers as well as output buffers. Signed-off-by: Paul Cercueil Reviewed-by: Alexandru Ardelean Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-4-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- .../buffer/industrialio-buffer-dmaengine.c | 44 +++++++++++++------ include/linux/iio/buffer-dmaengine.h | 25 ++++++++--- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index df05d66afff9..951012651018 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -64,14 +64,25 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, struct dmaengine_buffer *dmaengine_buffer = iio_buffer_to_dmaengine_buffer(&queue->buffer); struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction dma_dir; + size_t max_size; dma_cookie_t cookie; - block->bytes_used = min(block->size, dmaengine_buffer->max_size); - block->bytes_used = round_down(block->bytes_used, - dmaengine_buffer->align); + max_size = min(block->size, dmaengine_buffer->max_size); + max_size = round_down(max_size, dmaengine_buffer->align); + + if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) { + block->bytes_used = max_size; + dma_dir = DMA_DEV_TO_MEM; + } else { + dma_dir = DMA_MEM_TO_DEV; + } + + if (!block->bytes_used || block->bytes_used > max_size) + return -EINVAL; desc = dmaengine_prep_slave_single(dmaengine_buffer->chan, - block->phys_addr, block->bytes_used, DMA_DEV_TO_MEM, + block->phys_addr, block->bytes_used, dma_dir, DMA_PREP_INTERRUPT); if (!desc) return -ENOMEM; @@ -229,9 +240,10 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer) } EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, IIO_DMAENGINE_BUFFER); -struct iio_buffer *iio_dmaengine_buffer_setup(struct device *dev, - struct iio_dev *indio_dev, - const char *channel) +struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + const char *channel, + enum iio_buffer_direction dir) { struct iio_buffer *buffer; int ret; @@ -242,6 +254,8 @@ struct iio_buffer *iio_dmaengine_buffer_setup(struct device *dev, indio_dev->modes |= INDIO_BUFFER_HARDWARE; + buffer->direction = dir; + ret = iio_device_attach_buffer(indio_dev, buffer); if (ret) { iio_dmaengine_buffer_free(buffer); @@ -250,7 +264,7 @@ struct iio_buffer *iio_dmaengine_buffer_setup(struct device *dev, return buffer; } -EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup, IIO_DMAENGINE_BUFFER); +EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup_ext, IIO_DMAENGINE_BUFFER); static void __devm_iio_dmaengine_buffer_free(void *buffer) { @@ -258,30 +272,32 @@ static void __devm_iio_dmaengine_buffer_free(void *buffer) } /** - * devm_iio_dmaengine_buffer_setup() - Setup a DMA buffer for an IIO device + * devm_iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device * @dev: Parent device for the buffer * @indio_dev: IIO device to which to attach this buffer. * @channel: DMA channel name, typically "rx". + * @dir: Direction of buffer (in or out) * * This allocates a new IIO buffer with devm_iio_dmaengine_buffer_alloc() * and attaches it to an IIO device with iio_device_attach_buffer(). * It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of the * IIO device. */ -int devm_iio_dmaengine_buffer_setup(struct device *dev, - struct iio_dev *indio_dev, - const char *channel) +int devm_iio_dmaengine_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + const char *channel, + enum iio_buffer_direction dir) { struct iio_buffer *buffer; - buffer = iio_dmaengine_buffer_setup(dev, indio_dev, channel); + buffer = iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, dir); if (IS_ERR(buffer)) return PTR_ERR(buffer); return devm_add_action_or_reset(dev, __devm_iio_dmaengine_buffer_free, buffer); } -EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup, IIO_DMAENGINE_BUFFER); +EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext, IIO_DMAENGINE_BUFFER); MODULE_AUTHOR("Lars-Peter Clausen "); MODULE_DESCRIPTION("DMA buffer for the IIO framework"); diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-dmaengine.h index acb60f9a3fff..81d9a19aeb91 100644 --- a/include/linux/iio/buffer-dmaengine.h +++ b/include/linux/iio/buffer-dmaengine.h @@ -7,15 +7,28 @@ #ifndef __IIO_DMAENGINE_H__ #define __IIO_DMAENGINE_H__ +#include + struct iio_dev; struct device; void iio_dmaengine_buffer_free(struct iio_buffer *buffer); -struct iio_buffer *iio_dmaengine_buffer_setup(struct device *dev, - struct iio_dev *indio_dev, - const char *channel); -int devm_iio_dmaengine_buffer_setup(struct device *dev, - struct iio_dev *indio_dev, - const char *channel); +struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + const char *channel, + enum iio_buffer_direction dir); + +#define iio_dmaengine_buffer_setup(dev, indio_dev, channel) \ + iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \ + IIO_BUFFER_DIRECTION_IN) + +int devm_iio_dmaengine_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + const char *channel, + enum iio_buffer_direction dir); + +#define devm_iio_dmaengine_buffer_setup(dev, indio_dev, channel) \ + devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \ + IIO_BUFFER_DIRECTION_IN) #endif From 3afb27d15f8ddaa5ce5781d18bb249c683d29260 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Fri, 19 Apr 2024 10:25:38 +0200 Subject: [PATCH 101/108] iio: buffer-dmaengine: Enable write support Use the iio_dma_buffer_write() and iio_dma_buffer_space_available() functions provided by the buffer-dma core, to enable write support in the buffer-dmaengine code. Signed-off-by: Paul Cercueil Reviewed-by: Alexandru Ardelean Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-5-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dmaengine.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 951012651018..918f6f8d65b6 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -123,12 +123,14 @@ static void iio_dmaengine_buffer_release(struct iio_buffer *buf) static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { .read = iio_dma_buffer_read, + .write = iio_dma_buffer_write, .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, .set_length = iio_dma_buffer_set_length, .request_update = iio_dma_buffer_request_update, .enable = iio_dma_buffer_enable, .disable = iio_dma_buffer_disable, .data_available = iio_dma_buffer_usage, + .space_available = iio_dma_buffer_usage, .release = iio_dmaengine_buffer_release, .modes = INDIO_BUFFER_HARDWARE, From 2d1af46cfe2efff7dbfab5041399641e19c30f81 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 19 Apr 2024 10:25:39 +0200 Subject: [PATCH 102/108] dt-bindings: iio: dac: add docs for AXI DAC IP This adds the bindings documentation for the Analog Devices AXI DAC IP core. Reviewed-by: Rob Herring Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-6-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/dac/adi,axi-dac.yaml | 62 +++++++++++++++++++ MAINTAINERS | 7 +++ 2 files changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml b/Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml new file mode 100644 index 000000000000..a55e9bfc66d7 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,axi-dac.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AXI DAC IP core + +maintainers: + - Nuno Sa + +description: | + Analog Devices Generic AXI DAC IP core for interfacing a DAC device + with a high speed serial (JESD204B/C) or source synchronous parallel + interface (LVDS/CMOS). + Usually, some other interface type (i.e SPI) is used as a control + interface for the actual DAC, while this IP core will interface + to the data-lines of the DAC and handle the streaming of data from + memory via DMA into the DAC. + + https://wiki.analog.com/resources/fpga/docs/axi_dac_ip + +properties: + compatible: + enum: + - adi,axi-dac-9.1.b + + reg: + maxItems: 1 + + dmas: + maxItems: 1 + + dma-names: + items: + - const: tx + + clocks: + maxItems: 1 + + '#io-backend-cells': + const: 0 + +required: + - compatible + - dmas + - reg + - clocks + +additionalProperties: false + +examples: + - | + dac@44a00000 { + compatible = "adi,axi-dac-9.1.b"; + reg = <0x44a00000 0x10000>; + dmas = <&tx_dma 0>; + dma-names = "tx"; + #io-backend-cells = <0>; + clocks = <&axi_clk>; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index a7287cf44869..2137eb452376 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1399,6 +1399,13 @@ F: sound/soc/codecs/adav* F: sound/soc/codecs/sigmadsp.* F: sound/soc/codecs/ssm* +ANALOG DEVICES INC AXI DAC DRIVER +M: Nuno Sa +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml + ANALOG DEVICES INC DMA DRIVERS M: Lars-Peter Clausen S: Supported From a33486d38ea8f2b1446c4dbda8639eb19af6018b Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 19 Apr 2024 10:25:40 +0200 Subject: [PATCH 103/108] dt-bindings: iio: dac: add docs for AD9739A This adds the bindings documentation for the 14 bit RF Digital-to-Analog converter. Reviewed-by: Rob Herring Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-7-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- .../bindings/iio/dac/adi,ad9739a.yaml | 95 +++++++++++++++++++ MAINTAINERS | 8 ++ 2 files changed, 103 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml new file mode 100644 index 000000000000..c0b36476113a --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,ad9739a.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD9739A RF DAC + +maintainers: + - Dragos Bogdan + - Nuno Sa + +description: | + The AD9739A is a 14-bit, 2.5 GSPS high performance RF DACs that are capable + of synthesizing wideband signals from dc up to 3 GHz. + + https://www.analog.com/media/en/technical-documentation/data-sheets/ad9737a_9739a.pdf + +properties: + compatible: + enum: + - adi,ad9739a + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + interrupts: + maxItems: 1 + + vdd-3p3-supply: + description: 3.3V Digital input supply. + + vdd-supply: + description: 1.8V Digital input supply. + + vdda-supply: + description: 3.3V Analog input supply. + + vddc-supply: + description: 1.8V Clock input supply. + + vref-supply: + description: Input/Output reference supply. + + io-backends: + maxItems: 1 + + adi,full-scale-microamp: + description: This property represents the DAC full scale current. + minimum: 8580 + maximum: 31700 + default: 20000 + +required: + - compatible + - reg + - clocks + - io-backends + - vdd-3p3-supply + - vdd-supply + - vdda-supply + - vddc-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,ad9739a"; + reg = <0>; + + clocks = <&dac_clk>; + + io-backends = <&iio_backend>; + + vdd-3p3-supply = <&vdd_3_3>; + vdd-supply = <&vdd>; + vdda-supply = <&vdd_3_3>; + vddc-supply = <&vdd>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 2137eb452376..76e872e320d7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1234,6 +1234,14 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad7780.yaml F: drivers/iio/adc/ad7780.c +ANALOG DEVICES INC AD9739a DRIVER +M: Nuno Sa +M: Dragos Bogdan +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml + ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus L: linux-iio@vger.kernel.org From 87800c4342a29d4e1c378ce72d27e3976d094ffa Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 19 Apr 2024 10:25:41 +0200 Subject: [PATCH 104/108] iio: backend: add new functionality This adds the needed backend ops for supporting a backend inerfacing with an high speed dac. The new ops are: * data_source_set(); * set_sampling_freq(); * extend_chan_spec(); * ext_info_set(); * ext_info_get(). Also to note the new helpers that are meant to be used by the backends when extending an IIO channel (adding extended info): * iio_backend_ext_info_set(); * iio_backend_ext_info_get(). Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-8-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-backend.c | 179 +++++++++++++++++++++++++++++ include/linux/iio/backend.h | 49 ++++++++ 2 files changed, 228 insertions(+) diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c index 2fea2bbbe47f..f08ed6d70ae5 100644 --- a/drivers/iio/industrialio-backend.c +++ b/drivers/iio/industrialio-backend.c @@ -43,10 +43,12 @@ #include #include +#include struct iio_backend { struct list_head entry; const struct iio_backend_ops *ops; + struct device *frontend_dev; struct device *dev; struct module *owner; void *priv; @@ -186,6 +188,44 @@ int iio_backend_data_format_set(struct iio_backend *back, unsigned int chan, } EXPORT_SYMBOL_NS_GPL(iio_backend_data_format_set, IIO_BACKEND); +/** + * iio_backend_data_source_set - Select data source + * @back: Backend device + * @chan: Channel number + * @data: Data source + * + * A given backend may have different sources to stream/sync data. This allows + * to choose that source. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_source_set(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source data) +{ + if (data >= IIO_BACKEND_DATA_SOURCE_MAX) + return -EINVAL; + + return iio_backend_op_call(back, data_source_set, chan, data); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_source_set, IIO_BACKEND); + +/** + * iio_backend_set_sampling_freq - Set channel sampling rate + * @back: Backend device + * @chan: Channel number + * @sample_rate_hz: Sample rate + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_set_sampling_freq(struct iio_backend *back, unsigned int chan, + u64 sample_rate_hz) +{ + return iio_backend_op_call(back, set_sample_rate, chan, sample_rate_hz); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_set_sampling_freq, IIO_BACKEND); + static void iio_backend_free_buffer(void *arg) { struct iio_backend_buffer_pair *pair = arg; @@ -231,6 +271,143 @@ int devm_iio_backend_request_buffer(struct device *dev, } EXPORT_SYMBOL_NS_GPL(devm_iio_backend_request_buffer, IIO_BACKEND); +static struct iio_backend *iio_backend_from_indio_dev_parent(const struct device *dev) +{ + struct iio_backend *back = ERR_PTR(-ENODEV), *iter; + + /* + * We deliberately go through all backends even after finding a match. + * The reason is that we want to catch frontend devices which have more + * than one backend in which case returning the first we find is bogus. + * For those cases, frontends need to explicitly define + * get_iio_backend() in struct iio_info. + */ + guard(mutex)(&iio_back_lock); + list_for_each_entry(iter, &iio_back_list, entry) { + if (dev == iter->frontend_dev) { + if (!IS_ERR(back)) { + dev_warn(dev, + "Multiple backends! get_iio_backend() needs to be implemented"); + return ERR_PTR(-ENODEV); + } + + back = iter; + } + } + + return back; +} + +/** + * iio_backend_ext_info_get - IIO ext_info read callback + * @indio_dev: IIO device + * @private: Data private to the driver + * @chan: IIO channel + * @buf: Buffer where to place the attribute data + * + * This helper is intended to be used by backends that extend an IIO channel + * (through iio_backend_extend_chan_spec()) with extended info. In that case, + * backends are not supposed to give their own callbacks (as they would not have + * a way to get the backend from indio_dev). This is the getter. + * + * RETURNS: + * Number of bytes written to buf, negative error number on failure. + */ +ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct iio_backend *back; + + /* + * The below should work for the majority of the cases. It will not work + * when one frontend has multiple backends in which case we'll need a + * new callback in struct iio_info so we can directly request the proper + * backend from the frontend. Anyways, let's only introduce new options + * when really needed... + */ + back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent); + if (IS_ERR(back)) + return PTR_ERR(back); + + return iio_backend_op_call(back, ext_info_get, private, chan, buf); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_get, IIO_BACKEND); + +/** + * iio_backend_ext_info_set - IIO ext_info write callback + * @indio_dev: IIO device + * @private: Data private to the driver + * @chan: IIO channel + * @buf: Buffer holding the sysfs attribute + * @len: Buffer length + * + * This helper is intended to be used by backends that extend an IIO channel + * (trough iio_backend_extend_chan_spec()) with extended info. In that case, + * backends are not supposed to give their own callbacks (as they would not have + * a way to get the backend from indio_dev). This is the setter. + * + * RETURNS: + * Buffer length on success, negative error number on failure. + */ +ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct iio_backend *back; + + back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent); + if (IS_ERR(back)) + return PTR_ERR(back); + + return iio_backend_op_call(back, ext_info_set, private, chan, buf, len); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_set, IIO_BACKEND); + +/** + * iio_backend_extend_chan_spec - Extend an IIO channel + * @indio_dev: IIO device + * @back: Backend device + * @chan: IIO channel + * + * Some backends may have their own functionalities and hence capable of + * extending a frontend's channel. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_extend_chan_spec(struct iio_dev *indio_dev, + struct iio_backend *back, + struct iio_chan_spec *chan) +{ + const struct iio_chan_spec_ext_info *frontend_ext_info = chan->ext_info; + const struct iio_chan_spec_ext_info *back_ext_info; + int ret; + + ret = iio_backend_op_call(back, extend_chan_spec, chan); + if (ret) + return ret; + /* + * Let's keep things simple for now. Don't allow to overwrite the + * frontend's extended info. If ever needed, we can support appending + * it. + */ + if (frontend_ext_info && chan->ext_info != frontend_ext_info) + return -EOPNOTSUPP; + if (!chan->ext_info) + return 0; + + /* Don't allow backends to get creative and force their own handlers */ + for (back_ext_info = chan->ext_info; back_ext_info->name; back_ext_info++) { + if (back_ext_info->read != iio_backend_ext_info_get) + return -EINVAL; + if (back_ext_info->write != iio_backend_ext_info_set) + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_backend_extend_chan_spec, IIO_BACKEND); + static void iio_backend_release(void *arg) { struct iio_backend *back = arg; @@ -263,6 +440,8 @@ static int __devm_iio_backend_get(struct device *dev, struct iio_backend *back) "Could not link to supplier(%s)\n", dev_name(back->dev)); + back->frontend_dev = dev; + dev_dbg(dev, "Found backend(%s) device\n", dev_name(back->dev)); return 0; diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h index a6d79381866e..9d144631134d 100644 --- a/include/linux/iio/backend.h +++ b/include/linux/iio/backend.h @@ -4,6 +4,7 @@ #include +struct iio_chan_spec; struct fwnode_handle; struct iio_backend; struct device; @@ -15,6 +16,26 @@ enum iio_backend_data_type { IIO_BACKEND_DATA_TYPE_MAX }; +enum iio_backend_data_source { + IIO_BACKEND_INTERNAL_CONTINUOS_WAVE, + IIO_BACKEND_EXTERNAL, + IIO_BACKEND_DATA_SOURCE_MAX +}; + +/** + * IIO_BACKEND_EX_INFO - Helper for an IIO extended channel attribute + * @_name: Attribute name + * @_shared: Whether the attribute is shared between all channels + * @_what: Data private to the driver + */ +#define IIO_BACKEND_EX_INFO(_name, _shared, _what) { \ + .name = (_name), \ + .shared = (_shared), \ + .read = iio_backend_ext_info_get, \ + .write = iio_backend_ext_info_set, \ + .private = (_what), \ +} + /** * struct iio_backend_data_fmt - Backend data format * @type: Data type. @@ -35,8 +56,13 @@ struct iio_backend_data_fmt { * @chan_enable: Enable one channel. * @chan_disable: Disable one channel. * @data_format_set: Configure the data format for a specific channel. + * @data_source_set: Configure the data source for a specific channel. + * @set_sample_rate: Configure the sampling rate for a specific channel. * @request_buffer: Request an IIO buffer. * @free_buffer: Free an IIO buffer. + * @extend_chan_spec: Extend an IIO channel. + * @ext_info_set: Extended info setter. + * @ext_info_get: Extended info getter. **/ struct iio_backend_ops { int (*enable)(struct iio_backend *back); @@ -45,10 +71,21 @@ struct iio_backend_ops { int (*chan_disable)(struct iio_backend *back, unsigned int chan); int (*data_format_set)(struct iio_backend *back, unsigned int chan, const struct iio_backend_data_fmt *data); + int (*data_source_set)(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source data); + int (*set_sample_rate)(struct iio_backend *back, unsigned int chan, + u64 sample_rate_hz); struct iio_buffer *(*request_buffer)(struct iio_backend *back, struct iio_dev *indio_dev); void (*free_buffer)(struct iio_backend *back, struct iio_buffer *buffer); + int (*extend_chan_spec)(struct iio_backend *back, + struct iio_chan_spec *chan); + int (*ext_info_set)(struct iio_backend *back, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len); + int (*ext_info_get)(struct iio_backend *back, uintptr_t private, + const struct iio_chan_spec *chan, char *buf); }; int iio_backend_chan_enable(struct iio_backend *back, unsigned int chan); @@ -56,10 +93,22 @@ int iio_backend_chan_disable(struct iio_backend *back, unsigned int chan); int devm_iio_backend_enable(struct device *dev, struct iio_backend *back); int iio_backend_data_format_set(struct iio_backend *back, unsigned int chan, const struct iio_backend_data_fmt *data); +int iio_backend_data_source_set(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source data); +int iio_backend_set_sampling_freq(struct iio_backend *back, unsigned int chan, + u64 sample_rate_hz); int devm_iio_backend_request_buffer(struct device *dev, struct iio_backend *back, struct iio_dev *indio_dev); +ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len); +ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf); +int iio_backend_extend_chan_spec(struct iio_dev *indio_dev, + struct iio_backend *back, + struct iio_chan_spec *chan); void *iio_backend_get_priv(const struct iio_backend *conv); struct iio_backend *devm_iio_backend_get(struct device *dev, const char *name); struct iio_backend * From 4e3949a192e45a8e3543dbdbc853b90c3c25692e Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 19 Apr 2024 10:25:42 +0200 Subject: [PATCH 105/108] iio: dac: add support for AXI DAC IP core Support the Analog Devices Generic AXI DAC IP core. The IP core is used for interfacing with digital-to-analog (DAC) converters that require either a high-speed serial interface (JESD204B/C) or a source synchronous parallel interface (LVDS/CMOS). Typically (for such devices) SPI will be used for configuration only, while this IP core handles the streaming of data into memory via DMA. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-9-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/dac/Kconfig | 21 ++ drivers/iio/dac/Makefile | 1 + drivers/iio/dac/adi-axi-dac.c | 635 ++++++++++++++++++++++++++++++++++ 4 files changed, 658 insertions(+) create mode 100644 drivers/iio/dac/adi-axi-dac.c diff --git a/MAINTAINERS b/MAINTAINERS index 76e872e320d7..505f28dc6da6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1413,6 +1413,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml +F: drivers/iio/dac/adi-axi-dac.c ANALOG DEVICES INC DMA DRIVERS M: Lars-Peter Clausen diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 34eb40bb9529..7c0a8caa9a34 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -131,6 +131,27 @@ config AD5624R_SPI Say yes here to build support for Analog Devices AD5624R, AD5644R and AD5664R converters (DAC). This driver uses the common SPI interface. +config ADI_AXI_DAC + tristate "Analog Devices Generic AXI DAC IP core driver" + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select REGMAP_MMIO + select IIO_BACKEND + help + Say yes here to build support for Analog Devices Generic + AXI DAC IP core. The IP core is used for interfacing with + digital-to-analog (DAC) converters that require either a high-speed + serial interface (JESD204B/C) or a source synchronous parallel + interface (LVDS/CMOS). + Typically (for such devices) SPI will be used for configuration only, + while this IP core handles the streaming of data into memory via DMA. + + Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called adi-axi-dac. + config LTC2688 tristate "Analog Devices LTC2688 DAC spi driver" depends on SPI diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 55bf89739d14..6bcaa65434b2 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_AD5696_I2C) += ad5696-i2c.o obj-$(CONFIG_AD7293) += ad7293.o obj-$(CONFIG_AD7303) += ad7303.o obj-$(CONFIG_AD8801) += ad8801.o +obj-$(CONFIG_ADI_AXI_DAC) += adi-axi-dac.o obj-$(CONFIG_CIO_DAC) += cio-dac.o obj-$(CONFIG_DPOT_DAC) += dpot-dac.o obj-$(CONFIG_DS4424) += ds4424.o diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c new file mode 100644 index 000000000000..9047c5aec0ff --- /dev/null +++ b/drivers/iio/dac/adi-axi-dac.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices Generic AXI DAC IP core + * Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip + * + * Copyright 2016-2024 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * Register definitions: + * https://wiki.analog.com/resources/fpga/docs/axi_dac_ip#register_map + */ + +/* Base controls */ +#define AXI_DAC_REG_CONFIG 0x0c +#define AXI_DDS_DISABLE BIT(6) + + /* DAC controls */ +#define AXI_DAC_REG_RSTN 0x0040 +#define AXI_DAC_RSTN_CE_N BIT(2) +#define AXI_DAC_RSTN_MMCM_RSTN BIT(1) +#define AXI_DAC_RSTN_RSTN BIT(0) +#define AXI_DAC_REG_CNTRL_1 0x0044 +#define AXI_DAC_SYNC BIT(0) +#define AXI_DAC_REG_CNTRL_2 0x0048 +#define ADI_DAC_R1_MODE BIT(4) +#define AXI_DAC_DRP_STATUS 0x0074 +#define AXI_DAC_DRP_LOCKED BIT(17) +/* DAC Channel controls */ +#define AXI_DAC_REG_CHAN_CNTRL_1(c) (0x0400 + (c) * 0x40) +#define AXI_DAC_REG_CHAN_CNTRL_3(c) (0x0408 + (c) * 0x40) +#define AXI_DAC_SCALE_SIGN BIT(15) +#define AXI_DAC_SCALE_INT BIT(14) +#define AXI_DAC_SCALE GENMASK(14, 0) +#define AXI_DAC_REG_CHAN_CNTRL_2(c) (0x0404 + (c) * 0x40) +#define AXI_DAC_REG_CHAN_CNTRL_4(c) (0x040c + (c) * 0x40) +#define AXI_DAC_PHASE GENMASK(31, 16) +#define AXI_DAC_FREQUENCY GENMASK(15, 0) +#define AXI_DAC_REG_CHAN_CNTRL_7(c) (0x0418 + (c) * 0x40) +#define AXI_DAC_DATA_SEL GENMASK(3, 0) + +/* 360 degrees in rad */ +#define AXI_DAC_2_PI_MEGA 6283190 +enum { + AXI_DAC_DATA_INTERNAL_TONE, + AXI_DAC_DATA_DMA = 2, +}; + +struct axi_dac_state { + struct regmap *regmap; + struct device *dev; + /* + * lock to protect multiple accesses to the device registers and global + * data/variables. + */ + struct mutex lock; + u64 dac_clk; + u32 reg_config; + bool int_tone; +}; + +static int axi_dac_enable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + unsigned int __val; + int ret; + + guard(mutex)(&st->lock); + ret = regmap_set_bits(st->regmap, AXI_DAC_REG_RSTN, + AXI_DAC_RSTN_MMCM_RSTN); + if (ret) + return ret; + /* + * Make sure the DRP (Dynamic Reconfiguration Port) is locked. Not all + * designs really use it but if they don't we still get the lock bit + * set. So let's do it all the time so the code is generic. + */ + ret = regmap_read_poll_timeout(st->regmap, AXI_DAC_DRP_STATUS, __val, + __val & AXI_DAC_DRP_LOCKED, 100, 1000); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AXI_DAC_REG_RSTN, + AXI_DAC_RSTN_RSTN | AXI_DAC_RSTN_MMCM_RSTN); +} + +static void axi_dac_disable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + guard(mutex)(&st->lock); + regmap_write(st->regmap, AXI_DAC_REG_RSTN, 0); +} + +static struct iio_buffer *axi_dac_request_buffer(struct iio_backend *back, + struct iio_dev *indio_dev) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + const char *dma_name; + + if (device_property_read_string(st->dev, "dma-names", &dma_name)) + dma_name = "tx"; + + return iio_dmaengine_buffer_setup_ext(st->dev, indio_dev, dma_name, + IIO_BUFFER_DIRECTION_OUT); +} + +static void axi_dac_free_buffer(struct iio_backend *back, + struct iio_buffer *buffer) +{ + iio_dmaengine_buffer_free(buffer); +} + +enum { + AXI_DAC_FREQ_TONE_1, + AXI_DAC_FREQ_TONE_2, + AXI_DAC_SCALE_TONE_1, + AXI_DAC_SCALE_TONE_2, + AXI_DAC_PHASE_TONE_1, + AXI_DAC_PHASE_TONE_2, +}; + +static int __axi_dac_frequency_get(struct axi_dac_state *st, unsigned int chan, + unsigned int tone_2, unsigned int *freq) +{ + u32 reg, raw; + int ret; + + if (!st->dac_clk) { + dev_err(st->dev, "Sampling rate is 0...\n"); + return -EINVAL; + } + + if (tone_2) + reg = AXI_DAC_REG_CHAN_CNTRL_4(chan); + else + reg = AXI_DAC_REG_CHAN_CNTRL_2(chan); + + ret = regmap_read(st->regmap, reg, &raw); + if (ret) + return ret; + + raw = FIELD_GET(AXI_DAC_FREQUENCY, raw); + *freq = DIV_ROUND_CLOSEST_ULL(raw * st->dac_clk, BIT(16)); + + return 0; +} + +static int axi_dac_frequency_get(struct axi_dac_state *st, + const struct iio_chan_spec *chan, char *buf, + unsigned int tone_2) +{ + unsigned int freq; + int ret; + + scoped_guard(mutex, &st->lock) { + ret = __axi_dac_frequency_get(st, chan->channel, tone_2, &freq); + if (ret) + return ret; + } + + return sysfs_emit(buf, "%u\n", freq); +} + +static int axi_dac_scale_get(struct axi_dac_state *st, + const struct iio_chan_spec *chan, char *buf, + unsigned int tone_2) +{ + unsigned int scale, sign; + int ret, vals[2]; + u32 reg, raw; + + if (tone_2) + reg = AXI_DAC_REG_CHAN_CNTRL_3(chan->channel); + else + reg = AXI_DAC_REG_CHAN_CNTRL_1(chan->channel); + + ret = regmap_read(st->regmap, reg, &raw); + if (ret) + return ret; + + sign = FIELD_GET(AXI_DAC_SCALE_SIGN, raw); + raw = FIELD_GET(AXI_DAC_SCALE, raw); + scale = DIV_ROUND_CLOSEST_ULL((u64)raw * MEGA, AXI_DAC_SCALE_INT); + + vals[0] = scale / MEGA; + vals[1] = scale % MEGA; + + if (sign) { + vals[0] *= -1; + if (!vals[0]) + vals[1] *= -1; + } + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), + vals); +} + +static int axi_dac_phase_get(struct axi_dac_state *st, + const struct iio_chan_spec *chan, char *buf, + unsigned int tone_2) +{ + u32 reg, raw, phase; + int ret, vals[2]; + + if (tone_2) + reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel); + else + reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel); + + ret = regmap_read(st->regmap, reg, &raw); + if (ret) + return ret; + + raw = FIELD_GET(AXI_DAC_PHASE, raw); + phase = DIV_ROUND_CLOSEST_ULL((u64)raw * AXI_DAC_2_PI_MEGA, U16_MAX); + + vals[0] = phase / MEGA; + vals[1] = phase % MEGA; + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), + vals); +} + +static int __axi_dac_frequency_set(struct axi_dac_state *st, unsigned int chan, + u64 sample_rate, unsigned int freq, + unsigned int tone_2) +{ + u32 reg; + u16 raw; + int ret; + + if (!sample_rate || freq > sample_rate / 2) { + dev_err(st->dev, "Invalid frequency(%u) dac_clk(%llu)\n", + freq, sample_rate); + return -EINVAL; + } + + if (tone_2) + reg = AXI_DAC_REG_CHAN_CNTRL_4(chan); + else + reg = AXI_DAC_REG_CHAN_CNTRL_2(chan); + + raw = DIV64_U64_ROUND_CLOSEST((u64)freq * BIT(16), sample_rate); + + ret = regmap_update_bits(st->regmap, reg, AXI_DAC_FREQUENCY, raw); + if (ret) + return ret; + + /* synchronize channels */ + return regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC); +} + +static int axi_dac_frequency_set(struct axi_dac_state *st, + const struct iio_chan_spec *chan, + const char *buf, size_t len, unsigned int tone_2) +{ + unsigned int freq; + int ret; + + ret = kstrtou32(buf, 10, &freq); + if (ret) + return ret; + + guard(mutex)(&st->lock); + ret = __axi_dac_frequency_set(st, chan->channel, st->dac_clk, freq, + tone_2); + if (ret) + return ret; + + return len; +} + +static int axi_dac_scale_set(struct axi_dac_state *st, + const struct iio_chan_spec *chan, + const char *buf, size_t len, unsigned int tone_2) +{ + int integer, frac, scale; + u32 raw = 0, reg; + int ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &frac); + if (ret) + return ret; + + scale = integer * MEGA + frac; + if (scale <= -2 * (int)MEGA || scale >= 2 * (int)MEGA) + return -EINVAL; + + /* format is 1.1.14 (sign, integer and fractional bits) */ + if (scale < 0) { + raw = FIELD_PREP(AXI_DAC_SCALE_SIGN, 1); + scale *= -1; + } + + raw |= div_u64((u64)scale * AXI_DAC_SCALE_INT, MEGA); + + if (tone_2) + reg = AXI_DAC_REG_CHAN_CNTRL_3(chan->channel); + else + reg = AXI_DAC_REG_CHAN_CNTRL_1(chan->channel); + + guard(mutex)(&st->lock); + ret = regmap_write(st->regmap, reg, raw); + if (ret) + return ret; + + /* synchronize channels */ + ret = regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC); + if (ret) + return ret; + + return len; +} + +static int axi_dac_phase_set(struct axi_dac_state *st, + const struct iio_chan_spec *chan, + const char *buf, size_t len, unsigned int tone_2) +{ + int integer, frac, phase; + u32 raw, reg; + int ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &frac); + if (ret) + return ret; + + phase = integer * MEGA + frac; + if (phase < 0 || phase > AXI_DAC_2_PI_MEGA) + return -EINVAL; + + raw = DIV_ROUND_CLOSEST_ULL((u64)phase * U16_MAX, AXI_DAC_2_PI_MEGA); + + if (tone_2) + reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel); + else + reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel); + + guard(mutex)(&st->lock); + ret = regmap_update_bits(st->regmap, reg, AXI_DAC_PHASE, + FIELD_PREP(AXI_DAC_PHASE, raw)); + if (ret) + return ret; + + /* synchronize channels */ + ret = regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC); + if (ret) + return ret; + + return len; +} + +static int axi_dac_ext_info_set(struct iio_backend *back, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + switch (private) { + case AXI_DAC_FREQ_TONE_1: + case AXI_DAC_FREQ_TONE_2: + return axi_dac_frequency_set(st, chan, buf, len, + private - AXI_DAC_FREQ_TONE_1); + case AXI_DAC_SCALE_TONE_1: + case AXI_DAC_SCALE_TONE_2: + return axi_dac_scale_set(st, chan, buf, len, + private - AXI_DAC_SCALE_TONE_1); + case AXI_DAC_PHASE_TONE_1: + case AXI_DAC_PHASE_TONE_2: + return axi_dac_phase_set(st, chan, buf, len, + private - AXI_DAC_PHASE_TONE_2); + default: + return -EOPNOTSUPP; + } +} + +static int axi_dac_ext_info_get(struct iio_backend *back, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + switch (private) { + case AXI_DAC_FREQ_TONE_1: + case AXI_DAC_FREQ_TONE_2: + return axi_dac_frequency_get(st, chan, buf, + private - AXI_DAC_FREQ_TONE_1); + case AXI_DAC_SCALE_TONE_1: + case AXI_DAC_SCALE_TONE_2: + return axi_dac_scale_get(st, chan, buf, + private - AXI_DAC_SCALE_TONE_1); + case AXI_DAC_PHASE_TONE_1: + case AXI_DAC_PHASE_TONE_2: + return axi_dac_phase_get(st, chan, buf, + private - AXI_DAC_PHASE_TONE_1); + default: + return -EOPNOTSUPP; + } +} + +static const struct iio_chan_spec_ext_info axi_dac_ext_info[] = { + IIO_BACKEND_EX_INFO("frequency0", IIO_SEPARATE, AXI_DAC_FREQ_TONE_1), + IIO_BACKEND_EX_INFO("frequency1", IIO_SEPARATE, AXI_DAC_FREQ_TONE_2), + IIO_BACKEND_EX_INFO("scale0", IIO_SEPARATE, AXI_DAC_SCALE_TONE_1), + IIO_BACKEND_EX_INFO("scale1", IIO_SEPARATE, AXI_DAC_SCALE_TONE_2), + IIO_BACKEND_EX_INFO("phase0", IIO_SEPARATE, AXI_DAC_PHASE_TONE_1), + IIO_BACKEND_EX_INFO("phase1", IIO_SEPARATE, AXI_DAC_PHASE_TONE_2), + {} +}; + +static int axi_dac_extend_chan(struct iio_backend *back, + struct iio_chan_spec *chan) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + if (chan->type != IIO_ALTVOLTAGE) + return -EINVAL; + if (st->reg_config & AXI_DDS_DISABLE) + /* nothing to extend */ + return 0; + + chan->ext_info = axi_dac_ext_info; + + return 0; +} + +static int axi_dac_data_source_set(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source data) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + switch (data) { + case IIO_BACKEND_INTERNAL_CONTINUOS_WAVE: + return regmap_update_bits(st->regmap, + AXI_DAC_REG_CHAN_CNTRL_7(chan), + AXI_DAC_DATA_SEL, + AXI_DAC_DATA_INTERNAL_TONE); + case IIO_BACKEND_EXTERNAL: + return regmap_update_bits(st->regmap, + AXI_DAC_REG_CHAN_CNTRL_7(chan), + AXI_DAC_DATA_SEL, AXI_DAC_DATA_DMA); + default: + return -EINVAL; + } +} + +static int axi_dac_set_sample_rate(struct iio_backend *back, unsigned int chan, + u64 sample_rate) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + unsigned int freq; + int ret, tone; + + if (!sample_rate) + return -EINVAL; + if (st->reg_config & AXI_DDS_DISABLE) + /* sample_rate has no meaning if DDS is disabled */ + return 0; + + guard(mutex)(&st->lock); + /* + * If dac_clk is 0 then this must be the first time we're being notified + * about the interface sample rate. Hence, just update our internal + * variable and bail... If it's not 0, then we get the current DDS + * frequency (for the old rate) and update the registers for the new + * sample rate. + */ + if (!st->dac_clk) { + st->dac_clk = sample_rate; + return 0; + } + + for (tone = 0; tone <= AXI_DAC_FREQ_TONE_2; tone++) { + ret = __axi_dac_frequency_get(st, chan, tone, &freq); + if (ret) + return ret; + + ret = __axi_dac_frequency_set(st, chan, sample_rate, tone, freq); + if (ret) + return ret; + } + + st->dac_clk = sample_rate; + + return 0; +} + +static const struct iio_backend_ops axi_dac_generic = { + .enable = axi_dac_enable, + .disable = axi_dac_disable, + .request_buffer = axi_dac_request_buffer, + .free_buffer = axi_dac_free_buffer, + .extend_chan_spec = axi_dac_extend_chan, + .ext_info_set = axi_dac_ext_info_set, + .ext_info_get = axi_dac_ext_info_get, + .data_source_set = axi_dac_data_source_set, + .set_sample_rate = axi_dac_set_sample_rate, +}; + +static const struct regmap_config axi_dac_regmap_config = { + .val_bits = 32, + .reg_bits = 32, + .reg_stride = 4, + .max_register = 0x0800, +}; + +static int axi_dac_probe(struct platform_device *pdev) +{ + const unsigned int *expected_ver; + struct axi_dac_state *st; + void __iomem *base; + unsigned int ver; + struct clk *clk; + int ret; + + st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + expected_ver = device_get_match_data(&pdev->dev); + if (!expected_ver) + return -ENODEV; + + clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + st->dev = &pdev->dev; + st->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &axi_dac_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + /* + * Force disable the core. Up to the frontend to enable us. And we can + * still read/write registers... + */ + ret = regmap_write(st->regmap, AXI_DAC_REG_RSTN, 0); + if (ret) + return ret; + + ret = regmap_read(st->regmap, ADI_AXI_REG_VERSION, &ver); + if (ret) + return ret; + + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(*expected_ver)) { + dev_err(&pdev->dev, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(*expected_ver), + ADI_AXI_PCORE_VER_MINOR(*expected_ver), + ADI_AXI_PCORE_VER_PATCH(*expected_ver), + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); + return -ENODEV; + } + + /* Let's get the core read only configuration */ + ret = regmap_read(st->regmap, AXI_DAC_REG_CONFIG, &st->reg_config); + if (ret) + return ret; + + /* + * In some designs, setting the R1_MODE bit to 0 (which is the default + * value) causes all channels of the frontend to be routed to the same + * DMA (so they are sampled together). This is for things like + * Multiple-Input and Multiple-Output (MIMO). As most of the times we + * want independent channels let's override the core's default value and + * set the R1_MODE bit. + */ + ret = regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_2, ADI_DAC_R1_MODE); + if (ret) + return ret; + + mutex_init(&st->lock); + ret = devm_iio_backend_register(&pdev->dev, &axi_dac_generic, st); + if (ret) + return ret; + + dev_info(&pdev->dev, "AXI DAC IP core (%d.%.2d.%c) probed\n", + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); + + return 0; +} + +static unsigned int axi_dac_9_1_b_info = ADI_AXI_PCORE_VER(9, 1, 'b'); + +static const struct of_device_id axi_dac_of_match[] = { + { .compatible = "adi,axi-dac-9.1.b", .data = &axi_dac_9_1_b_info }, + {} +}; +MODULE_DEVICE_TABLE(of, axi_dac_of_match); + +static struct platform_driver axi_dac_driver = { + .driver = { + .name = "adi-axi-dac", + .of_match_table = axi_dac_of_match, + }, + .probe = axi_dac_probe, +}; +module_platform_driver(axi_dac_driver); + +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("Analog Devices Generic AXI DAC IP core driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_DMAENGINE_BUFFER); +MODULE_IMPORT_NS(IIO_BACKEND); From e77603d5468b9093c111a998a86604e21a9e7f48 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 19 Apr 2024 10:25:43 +0200 Subject: [PATCH 106/108] iio: dac: support the ad9739a RF DAC The AD9739A is a 14-bit, 2.5 GSPS high performance RF DACs that are capable of synthesizing wideband signals from DC up to 3 GHz. A dual-port, source synchronous, LVDS interface simplifies the digital interface with existing FGPA/ASIC technology. On-chip controllers are used to manage external and internal clock domain variations over temperature to ensure reliable data transfer from the host to the DAC core. Co-developed-by: Dragos Bogdan Signed-off-by: Dragos Bogdan Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-iio-backend-axi-dac-v4-10-5ca45b4de294@analog.com Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-ad9739a | 19 + MAINTAINERS | 1 + drivers/iio/dac/Kconfig | 16 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ad9739a.c | 463 ++++++++++++++++++ 5 files changed, 500 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-ad9739a create mode 100644 drivers/iio/dac/ad9739a.c diff --git a/Documentation/ABI/testing/sysfs-bus-iio-ad9739a b/Documentation/ABI/testing/sysfs-bus-iio-ad9739a new file mode 100644 index 000000000000..ed59299e6f8d --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-ad9739a @@ -0,0 +1,19 @@ +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_operating_mode +KernelVersion: 6.9 +Contact: linux-iio@vger.kernel.org +Description: + DAC operating mode. One of the following modes can be selected: + + * normal: This is DAC normal mode. + * mixed-mode: In this mode the output is effectively chopped at + the DAC sample rate. This has the effect of + reducing the power of the fundamental signal while + increasing the power of the images centered around + the DAC sample rate, thus improving the output + power of these images. + +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_operating_mode_available +KernelVersion: 6.9 +Contact: linux-iio@vger.kernel.org +Description: + Available operating modes. diff --git a/MAINTAINERS b/MAINTAINERS index 505f28dc6da6..8ad79cf70552 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1241,6 +1241,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml +F: drivers/iio/dac/ad9739a.c ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 7c0a8caa9a34..3c2bf620f00f 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -131,6 +131,22 @@ config AD5624R_SPI Say yes here to build support for Analog Devices AD5624R, AD5644R and AD5664R converters (DAC). This driver uses the common SPI interface. +config AD9739A + tristate "Analog Devices AD9739A RF DAC spi driver" + depends on SPI || COMPILE_TEST + select REGMAP_SPI + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD9739A Digital-to + Analog Converter. + + The driver requires the assistance of the AXI DAC IP core to operate, + since SPI is used for configuration only, while data has to be + streamed into memory via DMA. + + To compile this driver as a module, choose M here: the module will be + called ad9739a. + config ADI_AXI_DAC tristate "Analog Devices Generic AXI DAC IP core driver" select IIO_BUFFER diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 6bcaa65434b2..8432a81a19dc 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_AD5696_I2C) += ad5696-i2c.o obj-$(CONFIG_AD7293) += ad7293.o obj-$(CONFIG_AD7303) += ad7303.o obj-$(CONFIG_AD8801) += ad8801.o +obj-$(CONFIG_AD9739A) += ad9739a.o obj-$(CONFIG_ADI_AXI_DAC) += adi-axi-dac.o obj-$(CONFIG_CIO_DAC) += cio-dac.o obj-$(CONFIG_DPOT_DAC) += dpot-dac.o diff --git a/drivers/iio/dac/ad9739a.c b/drivers/iio/dac/ad9739a.c new file mode 100644 index 000000000000..ff33120075bf --- /dev/null +++ b/drivers/iio/dac/ad9739a.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD9739a SPI DAC driver + * + * Copyright 2015-2024 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AD9739A_REG_MODE 0 +#define AD9739A_RESET_MASK BIT(5) +#define AD9739A_REG_FSC_1 0x06 +#define AD9739A_REG_FSC_2 0x07 +#define AD9739A_FSC_MSB GENMASK(1, 0) +#define AD9739A_REG_DEC_CNT 0x8 +#define AD9739A_NORMAL_MODE 0 +#define AD9739A_MIXED_MODE 2 +#define AD9739A_DAC_DEC GENMASK(1, 0) +#define AD9739A_REG_LVDS_REC_CNT1 0x10 +#define AD9739A_RCVR_LOOP_EN_MASK GENMASK(1, 0) +#define AD9739A_REG_LVDS_REC_CNT4 0x13 +#define AD9739A_FINE_DEL_SKW_MASK GENMASK(3, 0) +#define AD9739A_REG_LVDS_REC_STAT9 0x21 +#define AD9739A_RCVR_TRACK_AND_LOCK (BIT(3) | BIT(0)) +#define AD9739A_REG_CROSS_CNT1 0x22 +#define AD9739A_REG_CROSS_CNT2 0x23 +#define AD9739A_REG_PHS_DET 0x24 +#define AD9739A_REG_MU_DUTY 0x25 +#define AD9739A_REG_MU_CNT1 0x26 +#define AD9739A_MU_EN_MASK BIT(0) +#define AD9739A_REG_MU_CNT2 0x27 +#define AD9739A_REG_MU_CNT3 0x28 +#define AD9739A_REG_MU_CNT4 0x29 +#define AD9739A_MU_CNT4_DEFAULT 0xcb +#define AD9739A_REG_MU_STAT1 0x2A +#define AD9739A_MU_LOCK_MASK BIT(0) +#define AD9739A_REG_ANA_CNT_1 0x32 +#define AD9739A_REG_ID 0x35 + +#define AD9739A_ID 0x24 +#define AD9739A_REG_IS_RESERVED(reg) \ + ((reg) == 0x5 || (reg) == 0x9 || (reg) == 0x0E || (reg) == 0x0D || \ + (reg) == 0x2B || (reg) == 0x2C || (reg) == 0x34) + +#define AD9739A_FSC_MIN 8580 +#define AD9739A_FSC_MAX 31700 +#define AD9739A_FSC_RANGE (AD9739A_FSC_MAX - AD9739A_FSC_MIN + 1) + +#define AD9739A_MIN_DAC_CLK (1600 * MEGA) +#define AD9739A_MAX_DAC_CLK (2500 * MEGA) +#define AD9739A_DAC_CLK_RANGE (AD9739A_MAX_DAC_CLK - AD9739A_MIN_DAC_CLK + 1) +/* as recommended by the datasheet */ +#define AD9739A_LOCK_N_TRIES 3 + +struct ad9739a_state { + struct iio_backend *back; + struct regmap *regmap; + unsigned long sample_rate; +}; + +static int ad9739a_oper_mode_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + u32 mode; + int ret; + + ret = regmap_read(st->regmap, AD9739A_REG_DEC_CNT, &mode); + if (ret) + return ret; + + mode = FIELD_GET(AD9739A_DAC_DEC, mode); + /* sanity check we get valid values from the HW */ + if (mode != AD9739A_NORMAL_MODE && mode != AD9739A_MIXED_MODE) + return -EIO; + if (!mode) + return AD9739A_NORMAL_MODE; + + /* + * We get 2 from the device but for IIO modes, that means 1. Hence the + * minus 1. + */ + return AD9739A_MIXED_MODE - 1; +} + +static int ad9739a_oper_mode_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, u32 mode) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + /* + * On the IIO interface we have 0 and 1 for mode. But for mixed_mode, we + * need to write 2 in the device. That's what the below check is about. + */ + if (mode == AD9739A_MIXED_MODE - 1) + mode = AD9739A_MIXED_MODE; + + return regmap_update_bits(st->regmap, AD9739A_REG_DEC_CNT, + AD9739A_DAC_DEC, mode); +} + +static int ad9739a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->sample_rate; + *val2 = 0; + return IIO_VAL_INT_64; + default: + return -EINVAL; + } +} + +static int ad9739a_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + return iio_backend_data_source_set(st->back, 0, IIO_BACKEND_EXTERNAL); +} + +static int ad9739a_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + return iio_backend_data_source_set(st->back, 0, + IIO_BACKEND_INTERNAL_CONTINUOS_WAVE); +} + +static bool ad9739a_reg_accessible(struct device *dev, unsigned int reg) +{ + if (AD9739A_REG_IS_RESERVED(reg)) + return false; + if (reg > AD9739A_REG_MU_STAT1 && reg < AD9739A_REG_ANA_CNT_1) + return false; + + return true; +} + +static int ad9739a_reset(struct device *dev, const struct ad9739a_state *st) +{ + struct gpio_desc *gpio; + int ret; + + gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + if (gpio) { + /* minimum pulse width of 40ns */ + ndelay(40); + gpiod_set_value_cansleep(gpio, 0); + return 0; + } + + /* bring all registers to their default state */ + ret = regmap_set_bits(st->regmap, AD9739A_REG_MODE, AD9739A_RESET_MASK); + if (ret) + return ret; + + ndelay(40); + + return regmap_clear_bits(st->regmap, AD9739A_REG_MODE, + AD9739A_RESET_MASK); +} + +/* + * Recommended values (as per datasheet) for the dac clk common mode voltage + * and Mu controller. Look at table 29. + */ +static const struct reg_sequence ad9739a_clk_mu_ctrl[] = { + /* DAC clk common mode voltage */ + { AD9739A_REG_CROSS_CNT1, 0x0f }, + { AD9739A_REG_CROSS_CNT2, 0x0f }, + /* Mu controller configuration */ + { AD9739A_REG_PHS_DET, 0x30 }, + { AD9739A_REG_MU_DUTY, 0x80 }, + { AD9739A_REG_MU_CNT2, 0x44 }, + { AD9739A_REG_MU_CNT3, 0x6c }, +}; + +static int ad9739a_init(struct device *dev, const struct ad9739a_state *st) +{ + unsigned int i = 0, lock, fsc; + u32 fsc_raw; + int ret; + + ret = regmap_multi_reg_write(st->regmap, ad9739a_clk_mu_ctrl, + ARRAY_SIZE(ad9739a_clk_mu_ctrl)); + if (ret) + return ret; + + /* + * Try to get the Mu lock. Repeat the below steps AD9739A_LOCK_N_TRIES + * (as specified by the datasheet) until we get the lock. + */ + do { + ret = regmap_write(st->regmap, AD9739A_REG_MU_CNT4, + AD9739A_MU_CNT4_DEFAULT); + if (ret) + return ret; + + /* Enable the Mu controller search and track mode. */ + ret = regmap_set_bits(st->regmap, AD9739A_REG_MU_CNT1, + AD9739A_MU_EN_MASK); + if (ret) + return ret; + + /* Ensure the DLL loop is locked */ + ret = regmap_read_poll_timeout(st->regmap, AD9739A_REG_MU_STAT1, + lock, lock & AD9739A_MU_LOCK_MASK, + 0, 1000); + if (ret && ret != -ETIMEDOUT) + return ret; + } while (ret && ++i < AD9739A_LOCK_N_TRIES); + + if (i == AD9739A_LOCK_N_TRIES) + return dev_err_probe(dev, ret, "Mu lock timeout\n"); + + /* Receiver tracking and lock. Same deal as the Mu controller */ + i = 0; + do { + ret = regmap_update_bits(st->regmap, AD9739A_REG_LVDS_REC_CNT4, + AD9739A_FINE_DEL_SKW_MASK, + FIELD_PREP(AD9739A_FINE_DEL_SKW_MASK, 2)); + if (ret) + return ret; + + /* Disable the receiver and the loop. */ + ret = regmap_write(st->regmap, AD9739A_REG_LVDS_REC_CNT1, 0); + if (ret) + return ret; + + /* + * Re-enable the loop so it falls out of lock and begins the + * search/track routine again. + */ + ret = regmap_set_bits(st->regmap, AD9739A_REG_LVDS_REC_CNT1, + AD9739A_RCVR_LOOP_EN_MASK); + if (ret) + return ret; + + /* Ensure the DLL loop is locked */ + ret = regmap_read_poll_timeout(st->regmap, + AD9739A_REG_LVDS_REC_STAT9, lock, + lock == AD9739A_RCVR_TRACK_AND_LOCK, + 0, 1000); + if (ret && ret != -ETIMEDOUT) + return ret; + } while (ret && ++i < AD9739A_LOCK_N_TRIES); + + if (i == AD9739A_LOCK_N_TRIES) + return dev_err_probe(dev, ret, "Receiver lock timeout\n"); + + ret = device_property_read_u32(dev, "adi,full-scale-microamp", &fsc); + if (ret && ret == -EINVAL) + return 0; + if (ret) + return ret; + if (!in_range(fsc, AD9739A_FSC_MIN, AD9739A_FSC_RANGE)) + return dev_err_probe(dev, -EINVAL, + "Invalid full scale current(%u) [%u %u]\n", + fsc, AD9739A_FSC_MIN, AD9739A_FSC_MAX); + /* + * IOUTFS is given by + * Ioutfs = 0.0226 * FSC + 8.58 + * and is given in mA. Hence we'll have to multiply by 10 * MILLI in + * order to get rid of the fractional. + */ + fsc_raw = DIV_ROUND_CLOSEST(fsc * 10 - 85800, 226); + + ret = regmap_write(st->regmap, AD9739A_REG_FSC_1, fsc_raw & 0xff); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, AD9739A_REG_FSC_2, + AD9739A_FSC_MSB, fsc_raw >> 8); +} + +static const char * const ad9739a_modes_avail[] = { "normal", "mixed-mode" }; + +static const struct iio_enum ad9739a_modes = { + .items = ad9739a_modes_avail, + .num_items = ARRAY_SIZE(ad9739a_modes_avail), + .get = ad9739a_oper_mode_get, + .set = ad9739a_oper_mode_set, +}; + +static const struct iio_chan_spec_ext_info ad9739a_ext_info[] = { + IIO_ENUM_AVAILABLE("operating_mode", IIO_SEPARATE, &ad9739a_modes), + IIO_ENUM("operating_mode", IIO_SEPARATE, &ad9739a_modes), + { } +}; + +/* + * The reason for having two different channels is because we have, in reality, + * two sources of data: + * ALTVOLTAGE: It's a Continuous Wave that's internally generated by the + * backend device. + * VOLTAGE: It's the typical data we can have in a DAC device and the source + * of it has nothing to do with the backend. The backend will only + * forward it into our data interface to be sent out. + */ +static struct iio_chan_spec ad9739a_channels[] = { + { + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .output = 1, + .scan_index = -1, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .output = 1, + .ext_info = ad9739a_ext_info, + .scan_type = { + .sign = 's', + .storagebits = 16, + .realbits = 16, + }, + } +}; + +static const struct iio_info ad9739a_info = { + .read_raw = ad9739a_read_raw, +}; + +static const struct iio_buffer_setup_ops ad9739a_buffer_setup_ops = { + .preenable = &ad9739a_buffer_preenable, + .postdisable = &ad9739a_buffer_postdisable, +}; + +static const struct regmap_config ad9739a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .readable_reg = ad9739a_reg_accessible, + .writeable_reg = ad9739a_reg_accessible, + .max_register = AD9739A_REG_ID, +}; + +static int ad9739a_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad9739a_state *st; + unsigned int id; + struct clk *clk; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Could not get clkin\n"); + + st->sample_rate = clk_get_rate(clk); + if (!in_range(st->sample_rate, AD9739A_MIN_DAC_CLK, + AD9739A_DAC_CLK_RANGE)) + return dev_err_probe(dev, -EINVAL, + "Invalid dac clk range(%lu) [%lu %lu]\n", + st->sample_rate, AD9739A_MIN_DAC_CLK, + AD9739A_MAX_DAC_CLK); + + st->regmap = devm_regmap_init_spi(spi, &ad9739a_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = regmap_read(st->regmap, AD9739A_REG_ID, &id); + if (ret) + return ret; + + if (id != AD9739A_ID) + dev_warn(dev, "Unrecognized CHIP_ID 0x%X", id); + + ret = ad9739a_reset(dev, st); + if (ret) + return ret; + + ret = ad9739a_init(dev, st); + if (ret) + return ret; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = iio_backend_extend_chan_spec(indio_dev, st->back, + &ad9739a_channels[0]); + if (ret) + return ret; + + ret = iio_backend_set_sampling_freq(st->back, 0, st->sample_rate); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + indio_dev->name = "ad9739a"; + indio_dev->info = &ad9739a_info; + indio_dev->channels = ad9739a_channels; + indio_dev->num_channels = ARRAY_SIZE(ad9739a_channels); + indio_dev->setup_ops = &ad9739a_buffer_setup_ops; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad9739a_of_match[] = { + { .compatible = "adi,ad9739a" }, + {} +}; +MODULE_DEVICE_TABLE(of, ad9739a_of_match); + +static const struct spi_device_id ad9739a_id[] = { + {"ad9739a"}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad9739a_id); + +static struct spi_driver ad9739a_driver = { + .driver = { + .name = "ad9739a", + .of_match_table = ad9739a_of_match, + }, + .probe = ad9739a_probe, + .id_table = ad9739a_id, +}; +module_spi_driver(ad9739a_driver); + +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("Analog Devices AD9739 DAC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_BACKEND); From cf1c833f89e7c8635a28c3db15c68ead150ea712 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 19 Apr 2024 17:36:45 +0200 Subject: [PATCH 107/108] iio: adc: adi-axi-adc: only error out in major version mismatch The IP core only has breaking changes when there major version changes. Hence, only match the major number. This is also in line with the other core ADI has upstream. The current check for erroring out 'expected_version > current_version"' is then wrong as we could just increase the core major with breaking changes and that would go unnoticed. Fixes: ef04070692a2 ("iio: adc: adi-axi-adc: add support for AXI ADC IP core") Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240419-ad9467-new-features-v1-2-3e7628ff6d5e@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 184b36dca6d0..9444b0c5a93c 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -193,9 +193,9 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (ret) return ret; - if (*expected_ver > ver) { + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(*expected_ver)) { dev_err(&pdev->dev, - "IP core version is too old. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", ADI_AXI_PCORE_VER_MAJOR(*expected_ver), ADI_AXI_PCORE_VER_MINOR(*expected_ver), ADI_AXI_PCORE_VER_PATCH(*expected_ver), From b80ad8e3cd2712b78b98804d1f59199680d8ed91 Mon Sep 17 00:00:00 2001 From: Lorenzo Bertin Salvador Date: Sat, 20 Apr 2024 15:27:43 -0300 Subject: [PATCH 108/108] iio: adc: ti-ads131e08: Use device_for_each_child_node_scoped() to simplify error paths. This loop definition automatically releases the handle on early exit reducing the chance of bugs that cause resource leaks. Co-developed-by: Briza Mel Dias de Sousa Signed-off-by: Briza Mel Dias de Sousa Signed-off-by: Lorenzo Bertin Salvador Link: https://lore.kernel.org/r/20240420182744.153184-2-lorenzobs@usp.br Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads131e08.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c index fcfc46254313..cb04a29b3dba 100644 --- a/drivers/iio/adc/ti-ads131e08.c +++ b/drivers/iio/adc/ti-ads131e08.c @@ -694,7 +694,6 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev) struct ads131e08_channel_config *channel_config; struct device *dev = &st->spi->dev; struct iio_chan_spec *channels; - struct fwnode_handle *node; unsigned int channel, tmp; int num_channels, i, ret; @@ -736,10 +735,10 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev) return -ENOMEM; i = 0; - device_for_each_child_node(dev, node) { + device_for_each_child_node_scoped(dev, node) { ret = fwnode_property_read_u32(node, "reg", &channel); if (ret) - goto err_child_out; + return ret; ret = fwnode_property_read_u32(node, "ti,gain", &tmp); if (ret) { @@ -747,7 +746,7 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev) } else { ret = ads131e08_pga_gain_to_field_value(st, tmp); if (ret < 0) - goto err_child_out; + return ret; channel_config[i].pga_gain = tmp; } @@ -758,7 +757,7 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev) } else { ret = ads131e08_validate_channel_mux(st, tmp); if (ret) - goto err_child_out; + return ret; channel_config[i].mux = tmp; } @@ -785,9 +784,6 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev) return 0; -err_child_out: - fwnode_handle_put(node); - return ret; } static void ads131e08_regulator_disable(void *data)