From 8b39a723ef1fa3737e11832ca11183bbaeda2498 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 7 Mar 2024 16:35:47 +0200 Subject: [PATCH 001/319] w1: gpio: Make use of device properties Convert the module to be property provider agnostic and allow it to be used on non-OF platforms. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240307143644.3787260-2-andriy.shevchenko@linux.intel.com Signed-off-by: Krzysztof Kozlowski --- drivers/w1/masters/w1-gpio.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index 34128e6bbbfa..8ea53c757c99 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -6,13 +6,13 @@ */ #include +#include #include #include +#include #include #include -#include #include -#include #include #include @@ -63,20 +63,11 @@ static u8 w1_gpio_read_bit(void *data) return gpiod_get_value(ddata->gpiod) ? 1 : 0; } -#if defined(CONFIG_OF) -static const struct of_device_id w1_gpio_dt_ids[] = { - { .compatible = "w1-gpio" }, - {} -}; -MODULE_DEVICE_TABLE(of, w1_gpio_dt_ids); -#endif - static int w1_gpio_probe(struct platform_device *pdev) { struct w1_bus_master *master; struct w1_gpio_ddata *ddata; struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; /* Enforce open drain mode by default */ enum gpiod_flags gflags = GPIOD_OUT_LOW_OPEN_DRAIN; int err; @@ -91,7 +82,7 @@ static int w1_gpio_probe(struct platform_device *pdev) * driver it high/low like we are in full control of the line and * open drain will happen transparently. */ - if (of_property_present(np, "linux,open-drain")) + if (device_property_present(dev, "linux,open-drain")) gflags = GPIOD_OUT_LOW; master = devm_kzalloc(dev, sizeof(struct w1_bus_master), @@ -152,10 +143,16 @@ static void w1_gpio_remove(struct platform_device *pdev) w1_remove_master_device(master); } +static const struct of_device_id w1_gpio_dt_ids[] = { + { .compatible = "w1-gpio" }, + {} +}; +MODULE_DEVICE_TABLE(of, w1_gpio_dt_ids); + static struct platform_driver w1_gpio_driver = { .driver = { .name = "w1-gpio", - .of_match_table = of_match_ptr(w1_gpio_dt_ids), + .of_match_table = w1_gpio_dt_ids, }, .probe = w1_gpio_probe, .remove_new = w1_gpio_remove, From 9e085c045868a6a727b3bd0fc7840ccc9e04d3a3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 7 Mar 2024 16:35:48 +0200 Subject: [PATCH 002/319] w1: gpio: Switch to use dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch to use dev_err_probe() to simplify the error path and unify a message template. Signed-off-by: Andy Shevchenko Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240307143644.3787260-3-andriy.shevchenko@linux.intel.com Signed-off-by: Krzysztof Kozlowski --- drivers/w1/masters/w1-gpio.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index 8ea53c757c99..a5ac34b32f54 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -91,18 +91,14 @@ static int w1_gpio_probe(struct platform_device *pdev) return -ENOMEM; ddata->gpiod = devm_gpiod_get_index(dev, NULL, 0, gflags); - if (IS_ERR(ddata->gpiod)) { - dev_err(dev, "gpio_request (pin) failed\n"); - return PTR_ERR(ddata->gpiod); - } + if (IS_ERR(ddata->gpiod)) + return dev_err_probe(dev, PTR_ERR(ddata->gpiod), "gpio_request (pin) failed\n"); ddata->pullup_gpiod = devm_gpiod_get_index_optional(dev, NULL, 1, GPIOD_OUT_LOW); - if (IS_ERR(ddata->pullup_gpiod)) { - dev_err(dev, "gpio_request_one " - "(ext_pullup_enable_pin) failed\n"); - return PTR_ERR(ddata->pullup_gpiod); - } + if (IS_ERR(ddata->pullup_gpiod)) + return dev_err_probe(dev, PTR_ERR(ddata->pullup_gpiod), + "gpio_request (ext_pullup_enable_pin) failed\n"); master->data = ddata; master->read_bit = w1_gpio_read_bit; @@ -119,10 +115,8 @@ static int w1_gpio_probe(struct platform_device *pdev) master->set_pullup = w1_gpio_set_pullup; err = w1_add_master_device(master); - if (err) { - dev_err(dev, "w1_add_master device failed\n"); - return err; - } + if (err) + return dev_err_probe(dev, err, "w1_add_master device failed\n"); if (ddata->pullup_gpiod) gpiod_set_value(ddata->pullup_gpiod, 1); From ef2b810e1152d77686032e7dc064ff89b4350b00 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 7 Mar 2024 16:35:49 +0200 Subject: [PATCH 003/319] w1: gpio: Use sizeof(*pointer) instead of sizeof(type) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is preferred to use sizeof(*pointer) instead of sizeof(type). The type of the variable can change and one needs not change the former (unlike the latter). No functional change intended. Signed-off-by: Andy Shevchenko Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240307143644.3787260-4-andriy.shevchenko@linux.intel.com Signed-off-by: Krzysztof Kozlowski --- drivers/w1/masters/w1-gpio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index a5ac34b32f54..3881f2eaed2f 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -85,8 +85,7 @@ static int w1_gpio_probe(struct platform_device *pdev) if (device_property_present(dev, "linux,open-drain")) gflags = GPIOD_OUT_LOW; - master = devm_kzalloc(dev, sizeof(struct w1_bus_master), - GFP_KERNEL); + master = devm_kzalloc(dev, sizeof(*master), GFP_KERNEL); if (!master) return -ENOMEM; From 540d3f15c0aa2baf7e9b48a4e516391c179daab2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 7 Mar 2024 16:35:50 +0200 Subject: [PATCH 004/319] w1: gpio: Remove duplicate NULL checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gpiod_set_value() is NULL-aware, no need to check that in the caller. Signed-off-by: Andy Shevchenko Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240307143644.3787260-5-andriy.shevchenko@linux.intel.com Signed-off-by: Krzysztof Kozlowski --- drivers/w1/masters/w1-gpio.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index 3881f2eaed2f..8fd9fedd8c56 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -117,8 +117,7 @@ static int w1_gpio_probe(struct platform_device *pdev) if (err) return dev_err_probe(dev, err, "w1_add_master device failed\n"); - if (ddata->pullup_gpiod) - gpiod_set_value(ddata->pullup_gpiod, 1); + gpiod_set_value(ddata->pullup_gpiod, 1); platform_set_drvdata(pdev, master); @@ -130,8 +129,7 @@ static void w1_gpio_remove(struct platform_device *pdev) struct w1_bus_master *master = platform_get_drvdata(pdev); struct w1_gpio_ddata *ddata = master->data; - if (ddata->pullup_gpiod) - gpiod_set_value(ddata->pullup_gpiod, 0); + gpiod_set_value(ddata->pullup_gpiod, 0); w1_remove_master_device(master); } From cde37a5bdb0ed2c4c7b86ef688e5fdb697525a57 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 7 Mar 2024 16:35:51 +0200 Subject: [PATCH 005/319] w1: gpio: Don't use "proxy" headers Update header inclusions to follow IWYU (Include What You Use) principle. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240307143644.3787260-6-andriy.shevchenko@linux.intel.com Signed-off-by: Krzysztof Kozlowski --- drivers/w1/masters/w1-gpio.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index 8fd9fedd8c56..a39fa8bf866a 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -5,15 +5,15 @@ * Copyright (C) 2007 Ville Syrjala */ -#include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include -#include +#include #include From 77fc5151f9c0e6068f1567b73d33e75a0c35333d Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 17 Feb 2024 16:42:35 +0000 Subject: [PATCH 006/319] 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 007/319] 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 008/319] 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 009/319] 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 010/319] 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 011/319] 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 012/319] 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 013/319] 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 014/319] 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 015/319] 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 016/319] 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 017/319] 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 018/319] 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 019/319] 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 020/319] 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 021/319] 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 022/319] 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 023/319] 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 024/319] 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 025/319] 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 026/319] 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 027/319] 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 028/319] 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 029/319] 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 030/319] 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 031/319] 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 032/319] 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 033/319] 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 034/319] 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 035/319] 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 036/319] 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 037/319] 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 038/319] 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 039/319] 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 040/319] 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 041/319] 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 042/319] 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 043/319] 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 044/319] 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 045/319] 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 046/319] 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 @@ error_write_raw_unlock: 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 047/319] 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 048/319] 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 049/319] 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 050/319] 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 051/319] 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 052/319] 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 053/319] 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 @@ err_put: 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 054/319] 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 055/319] 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 056/319] 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 057/319] 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 058/319] 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 059/319] 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 060/319] 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 061/319] 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 062/319] 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 063/319] 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 064/319] 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 065/319] 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 4d4d2d4346857bf778fafaa97d6f76bb1663e3c9 Mon Sep 17 00:00:00 2001 From: Marco Pagani Date: Tue, 5 Mar 2024 20:29:26 +0100 Subject: [PATCH 066/319] fpga: manager: add owner module and take its refcount The current implementation of the fpga manager assumes that the low-level module registers a driver for the parent device and uses its owner pointer to take the module's refcount. This approach is problematic since it can lead to a null pointer dereference while attempting to get the manager if the parent device does not have a driver. To address this problem, add a module owner pointer to the fpga_manager struct and use it to take the module's refcount. Modify the functions for registering the manager to take an additional owner module parameter and rename them to avoid conflicts. Use the old function names for helper macros that automatically set the module that registers the manager as the owner. This ensures compatibility with existing low-level control modules and reduces the chances of registering a manager without setting the owner. Also, update the documentation to keep it consistent with the new interface for registering an fpga manager. Other changes: opportunistically move put_device() from __fpga_mgr_get() to fpga_mgr_get() and of_fpga_mgr_get() to improve code clarity since the manager device is taken in these functions. Fixes: 654ba4cc0f3e ("fpga manager: ensure lifetime with of_fpga_mgr_get") Suggested-by: Greg Kroah-Hartman Suggested-by: Xu Yilun Signed-off-by: Marco Pagani Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240305192926.84886-1-marpagan@redhat.com Signed-off-by: Xu Yilun --- Documentation/driver-api/fpga/fpga-mgr.rst | 34 +++++---- drivers/fpga/fpga-mgr.c | 82 +++++++++++++--------- include/linux/fpga/fpga-mgr.h | 26 +++++-- 3 files changed, 89 insertions(+), 53 deletions(-) diff --git a/Documentation/driver-api/fpga/fpga-mgr.rst b/Documentation/driver-api/fpga/fpga-mgr.rst index 49c0a9512653..8d2b79f696c1 100644 --- a/Documentation/driver-api/fpga/fpga-mgr.rst +++ b/Documentation/driver-api/fpga/fpga-mgr.rst @@ -24,7 +24,8 @@ How to support a new FPGA device -------------------------------- To add another FPGA manager, write a driver that implements a set of ops. The -probe function calls fpga_mgr_register() or fpga_mgr_register_full(), such as:: +probe function calls ``fpga_mgr_register()`` or ``fpga_mgr_register_full()``, +such as:: static const struct fpga_manager_ops socfpga_fpga_ops = { .write_init = socfpga_fpga_ops_configure_init, @@ -69,10 +70,11 @@ probe function calls fpga_mgr_register() or fpga_mgr_register_full(), such as:: } Alternatively, the probe function could call one of the resource managed -register functions, devm_fpga_mgr_register() or devm_fpga_mgr_register_full(). -When these functions are used, the parameter syntax is the same, but the call -to fpga_mgr_unregister() should be removed. In the above example, the -socfpga_fpga_remove() function would not be required. +register functions, ``devm_fpga_mgr_register()`` or +``devm_fpga_mgr_register_full()``. When these functions are used, the +parameter syntax is the same, but the call to ``fpga_mgr_unregister()`` should be +removed. In the above example, the ``socfpga_fpga_remove()`` function would not be +required. The ops will implement whatever device specific register writes are needed to do the programming sequence for this particular FPGA. These ops return 0 for @@ -125,15 +127,19 @@ API for implementing a new FPGA Manager driver * struct fpga_manager - the FPGA manager struct * struct fpga_manager_ops - Low level FPGA manager driver ops * struct fpga_manager_info - Parameter structure for fpga_mgr_register_full() -* fpga_mgr_register_full() - Create and register an FPGA manager using the +* __fpga_mgr_register_full() - Create and register an FPGA manager using the fpga_mgr_info structure to provide the full flexibility of options -* fpga_mgr_register() - Create and register an FPGA manager using standard +* __fpga_mgr_register() - Create and register an FPGA manager using standard arguments -* devm_fpga_mgr_register_full() - Resource managed version of - fpga_mgr_register_full() -* devm_fpga_mgr_register() - Resource managed version of fpga_mgr_register() +* __devm_fpga_mgr_register_full() - Resource managed version of + __fpga_mgr_register_full() +* __devm_fpga_mgr_register() - Resource managed version of __fpga_mgr_register() * fpga_mgr_unregister() - Unregister an FPGA manager +Helper macros ``fpga_mgr_register_full()``, ``fpga_mgr_register()``, +``devm_fpga_mgr_register_full()``, and ``devm_fpga_mgr_register()`` are available +to ease the registration. + .. kernel-doc:: include/linux/fpga/fpga-mgr.h :functions: fpga_mgr_states @@ -147,16 +153,16 @@ API for implementing a new FPGA Manager driver :functions: fpga_manager_info .. kernel-doc:: drivers/fpga/fpga-mgr.c - :functions: fpga_mgr_register_full + :functions: __fpga_mgr_register_full .. kernel-doc:: drivers/fpga/fpga-mgr.c - :functions: fpga_mgr_register + :functions: __fpga_mgr_register .. kernel-doc:: drivers/fpga/fpga-mgr.c - :functions: devm_fpga_mgr_register_full + :functions: __devm_fpga_mgr_register_full .. kernel-doc:: drivers/fpga/fpga-mgr.c - :functions: devm_fpga_mgr_register + :functions: __devm_fpga_mgr_register .. kernel-doc:: drivers/fpga/fpga-mgr.c :functions: fpga_mgr_unregister diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 06651389c592..0f4035b089a2 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -664,20 +664,16 @@ static struct attribute *fpga_mgr_attrs[] = { }; ATTRIBUTE_GROUPS(fpga_mgr); -static struct fpga_manager *__fpga_mgr_get(struct device *dev) +static struct fpga_manager *__fpga_mgr_get(struct device *mgr_dev) { struct fpga_manager *mgr; - mgr = to_fpga_manager(dev); + mgr = to_fpga_manager(mgr_dev); - if (!try_module_get(dev->parent->driver->owner)) - goto err_dev; + if (!try_module_get(mgr->mops_owner)) + mgr = ERR_PTR(-ENODEV); return mgr; - -err_dev: - put_device(dev); - return ERR_PTR(-ENODEV); } static int fpga_mgr_dev_match(struct device *dev, const void *data) @@ -693,12 +689,18 @@ static int fpga_mgr_dev_match(struct device *dev, const void *data) */ struct fpga_manager *fpga_mgr_get(struct device *dev) { - struct device *mgr_dev = class_find_device(&fpga_mgr_class, NULL, dev, - fpga_mgr_dev_match); + struct fpga_manager *mgr; + struct device *mgr_dev; + + mgr_dev = class_find_device(&fpga_mgr_class, NULL, dev, fpga_mgr_dev_match); if (!mgr_dev) return ERR_PTR(-ENODEV); - return __fpga_mgr_get(mgr_dev); + mgr = __fpga_mgr_get(mgr_dev); + if (IS_ERR(mgr)) + put_device(mgr_dev); + + return mgr; } EXPORT_SYMBOL_GPL(fpga_mgr_get); @@ -711,13 +713,18 @@ EXPORT_SYMBOL_GPL(fpga_mgr_get); */ struct fpga_manager *of_fpga_mgr_get(struct device_node *node) { - struct device *dev; + struct fpga_manager *mgr; + struct device *mgr_dev; - dev = class_find_device_by_of_node(&fpga_mgr_class, node); - if (!dev) + mgr_dev = class_find_device_by_of_node(&fpga_mgr_class, node); + if (!mgr_dev) return ERR_PTR(-ENODEV); - return __fpga_mgr_get(dev); + mgr = __fpga_mgr_get(mgr_dev); + if (IS_ERR(mgr)) + put_device(mgr_dev); + + return mgr; } EXPORT_SYMBOL_GPL(of_fpga_mgr_get); @@ -727,7 +734,7 @@ EXPORT_SYMBOL_GPL(of_fpga_mgr_get); */ void fpga_mgr_put(struct fpga_manager *mgr) { - module_put(mgr->dev.parent->driver->owner); + module_put(mgr->mops_owner); put_device(&mgr->dev); } EXPORT_SYMBOL_GPL(fpga_mgr_put); @@ -766,9 +773,10 @@ void fpga_mgr_unlock(struct fpga_manager *mgr) EXPORT_SYMBOL_GPL(fpga_mgr_unlock); /** - * fpga_mgr_register_full - create and register an FPGA Manager device + * __fpga_mgr_register_full - create and register an FPGA Manager device * @parent: fpga manager device from pdev * @info: parameters for fpga manager + * @owner: owner module containing the ops * * The caller of this function is responsible for calling fpga_mgr_unregister(). * Using devm_fpga_mgr_register_full() instead is recommended. @@ -776,7 +784,8 @@ EXPORT_SYMBOL_GPL(fpga_mgr_unlock); * Return: pointer to struct fpga_manager pointer or ERR_PTR() */ struct fpga_manager * -fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info) +__fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info, + struct module *owner) { const struct fpga_manager_ops *mops = info->mops; struct fpga_manager *mgr; @@ -804,6 +813,8 @@ fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *in mutex_init(&mgr->ref_mutex); + mgr->mops_owner = owner; + mgr->name = info->name; mgr->mops = info->mops; mgr->priv = info->priv; @@ -841,14 +852,15 @@ error_kfree: return ERR_PTR(ret); } -EXPORT_SYMBOL_GPL(fpga_mgr_register_full); +EXPORT_SYMBOL_GPL(__fpga_mgr_register_full); /** - * fpga_mgr_register - create and register an FPGA Manager device + * __fpga_mgr_register - create and register an FPGA Manager device * @parent: fpga manager device from pdev * @name: fpga manager name * @mops: pointer to structure of fpga manager ops * @priv: fpga manager private data + * @owner: owner module containing the ops * * The caller of this function is responsible for calling fpga_mgr_unregister(). * Using devm_fpga_mgr_register() instead is recommended. This simple @@ -859,8 +871,8 @@ EXPORT_SYMBOL_GPL(fpga_mgr_register_full); * Return: pointer to struct fpga_manager pointer or ERR_PTR() */ struct fpga_manager * -fpga_mgr_register(struct device *parent, const char *name, - const struct fpga_manager_ops *mops, void *priv) +__fpga_mgr_register(struct device *parent, const char *name, + const struct fpga_manager_ops *mops, void *priv, struct module *owner) { struct fpga_manager_info info = { 0 }; @@ -868,9 +880,9 @@ fpga_mgr_register(struct device *parent, const char *name, info.mops = mops; info.priv = priv; - return fpga_mgr_register_full(parent, &info); + return __fpga_mgr_register_full(parent, &info, owner); } -EXPORT_SYMBOL_GPL(fpga_mgr_register); +EXPORT_SYMBOL_GPL(__fpga_mgr_register); /** * fpga_mgr_unregister - unregister an FPGA manager @@ -900,9 +912,10 @@ static void devm_fpga_mgr_unregister(struct device *dev, void *res) } /** - * devm_fpga_mgr_register_full - resource managed variant of fpga_mgr_register() + * __devm_fpga_mgr_register_full - resource managed variant of fpga_mgr_register() * @parent: fpga manager device from pdev * @info: parameters for fpga manager + * @owner: owner module containing the ops * * Return: fpga manager pointer on success, negative error code otherwise. * @@ -910,7 +923,8 @@ static void devm_fpga_mgr_unregister(struct device *dev, void *res) * function will be called automatically when the managing device is detached. */ struct fpga_manager * -devm_fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info) +__devm_fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info, + struct module *owner) { struct fpga_mgr_devres *dr; struct fpga_manager *mgr; @@ -919,7 +933,7 @@ devm_fpga_mgr_register_full(struct device *parent, const struct fpga_manager_inf if (!dr) return ERR_PTR(-ENOMEM); - mgr = fpga_mgr_register_full(parent, info); + mgr = __fpga_mgr_register_full(parent, info, owner); if (IS_ERR(mgr)) { devres_free(dr); return mgr; @@ -930,14 +944,15 @@ devm_fpga_mgr_register_full(struct device *parent, const struct fpga_manager_inf return mgr; } -EXPORT_SYMBOL_GPL(devm_fpga_mgr_register_full); +EXPORT_SYMBOL_GPL(__devm_fpga_mgr_register_full); /** - * devm_fpga_mgr_register - resource managed variant of fpga_mgr_register() + * __devm_fpga_mgr_register - resource managed variant of fpga_mgr_register() * @parent: fpga manager device from pdev * @name: fpga manager name * @mops: pointer to structure of fpga manager ops * @priv: fpga manager private data + * @owner: owner module containing the ops * * Return: fpga manager pointer on success, negative error code otherwise. * @@ -946,8 +961,9 @@ EXPORT_SYMBOL_GPL(devm_fpga_mgr_register_full); * device is detached. */ struct fpga_manager * -devm_fpga_mgr_register(struct device *parent, const char *name, - const struct fpga_manager_ops *mops, void *priv) +__devm_fpga_mgr_register(struct device *parent, const char *name, + const struct fpga_manager_ops *mops, void *priv, + struct module *owner) { struct fpga_manager_info info = { 0 }; @@ -955,9 +971,9 @@ devm_fpga_mgr_register(struct device *parent, const char *name, info.mops = mops; info.priv = priv; - return devm_fpga_mgr_register_full(parent, &info); + return __devm_fpga_mgr_register_full(parent, &info, owner); } -EXPORT_SYMBOL_GPL(devm_fpga_mgr_register); +EXPORT_SYMBOL_GPL(__devm_fpga_mgr_register); static void fpga_mgr_dev_release(struct device *dev) { diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 54f63459efd6..0d4fe068f3d8 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -201,6 +201,7 @@ struct fpga_manager_ops { * @state: state of fpga manager * @compat_id: FPGA manager id for compatibility check. * @mops: pointer to struct of fpga manager ops + * @mops_owner: module containing the mops * @priv: low level driver private date */ struct fpga_manager { @@ -210,6 +211,7 @@ struct fpga_manager { enum fpga_mgr_states state; struct fpga_compat_id *compat_id; const struct fpga_manager_ops *mops; + struct module *mops_owner; void *priv; }; @@ -230,18 +232,30 @@ struct fpga_manager *fpga_mgr_get(struct device *dev); void fpga_mgr_put(struct fpga_manager *mgr); +#define fpga_mgr_register_full(parent, info) \ + __fpga_mgr_register_full(parent, info, THIS_MODULE) struct fpga_manager * -fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info); +__fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info, + struct module *owner); +#define fpga_mgr_register(parent, name, mops, priv) \ + __fpga_mgr_register(parent, name, mops, priv, THIS_MODULE) struct fpga_manager * -fpga_mgr_register(struct device *parent, const char *name, - const struct fpga_manager_ops *mops, void *priv); +__fpga_mgr_register(struct device *parent, const char *name, + const struct fpga_manager_ops *mops, void *priv, struct module *owner); + void fpga_mgr_unregister(struct fpga_manager *mgr); +#define devm_fpga_mgr_register_full(parent, info) \ + __devm_fpga_mgr_register_full(parent, info, THIS_MODULE) struct fpga_manager * -devm_fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info); +__devm_fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *info, + struct module *owner); +#define devm_fpga_mgr_register(parent, name, mops, priv) \ + __devm_fpga_mgr_register(parent, name, mops, priv, THIS_MODULE) struct fpga_manager * -devm_fpga_mgr_register(struct device *parent, const char *name, - const struct fpga_manager_ops *mops, void *priv); +__devm_fpga_mgr_register(struct device *parent, const char *name, + const struct fpga_manager_ops *mops, void *priv, + struct module *owner); #endif /*_LINUX_FPGA_MGR_H */ From 1da11f822042eb6ef4b6064dc048f157a7852529 Mon Sep 17 00:00:00 2001 From: Marco Pagani Date: Fri, 22 Mar 2024 18:18:37 +0100 Subject: [PATCH 067/319] fpga: bridge: add owner module and take its refcount The current implementation of the fpga bridge assumes that the low-level module registers a driver for the parent device and uses its owner pointer to take the module's refcount. This approach is problematic since it can lead to a null pointer dereference while attempting to get the bridge if the parent device does not have a driver. To address this problem, add a module owner pointer to the fpga_bridge struct and use it to take the module's refcount. Modify the function for registering a bridge to take an additional owner module parameter and rename it to avoid conflicts. Use the old function name for a helper macro that automatically sets the module that registers the bridge as the owner. This ensures compatibility with existing low-level control modules and reduces the chances of registering a bridge without setting the owner. Also, update the documentation to keep it consistent with the new interface for registering an fpga bridge. Other changes: opportunistically move put_device() from __fpga_bridge_get() to fpga_bridge_get() and of_fpga_bridge_get() to improve code clarity since the bridge device is taken in these functions. Fixes: 21aeda950c5f ("fpga: add fpga bridge framework") Suggested-by: Greg Kroah-Hartman Suggested-by: Xu Yilun Reviewed-by: Russ Weight Signed-off-by: Marco Pagani Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240322171839.233864-1-marpagan@redhat.com Signed-off-by: Xu Yilun --- Documentation/driver-api/fpga/fpga-bridge.rst | 7 ++- drivers/fpga/fpga-bridge.c | 57 ++++++++++--------- include/linux/fpga/fpga-bridge.h | 10 +++- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/Documentation/driver-api/fpga/fpga-bridge.rst b/Documentation/driver-api/fpga/fpga-bridge.rst index 604208534095..833f68fb0700 100644 --- a/Documentation/driver-api/fpga/fpga-bridge.rst +++ b/Documentation/driver-api/fpga/fpga-bridge.rst @@ -6,9 +6,12 @@ API to implement a new FPGA bridge * struct fpga_bridge - The FPGA Bridge structure * struct fpga_bridge_ops - Low level Bridge driver ops -* fpga_bridge_register() - Create and register a bridge +* __fpga_bridge_register() - Create and register a bridge * fpga_bridge_unregister() - Unregister a bridge +The helper macro ``fpga_bridge_register()`` automatically sets +the module that registers the FPGA bridge as the owner. + .. kernel-doc:: include/linux/fpga/fpga-bridge.h :functions: fpga_bridge @@ -16,7 +19,7 @@ API to implement a new FPGA bridge :functions: fpga_bridge_ops .. kernel-doc:: drivers/fpga/fpga-bridge.c - :functions: fpga_bridge_register + :functions: __fpga_bridge_register .. kernel-doc:: drivers/fpga/fpga-bridge.c :functions: fpga_bridge_unregister diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c index 79c473b3c7c3..8ef395b49bf8 100644 --- a/drivers/fpga/fpga-bridge.c +++ b/drivers/fpga/fpga-bridge.c @@ -55,33 +55,26 @@ int fpga_bridge_disable(struct fpga_bridge *bridge) } EXPORT_SYMBOL_GPL(fpga_bridge_disable); -static struct fpga_bridge *__fpga_bridge_get(struct device *dev, +static struct fpga_bridge *__fpga_bridge_get(struct device *bridge_dev, struct fpga_image_info *info) { struct fpga_bridge *bridge; - int ret = -ENODEV; - bridge = to_fpga_bridge(dev); + bridge = to_fpga_bridge(bridge_dev); bridge->info = info; - if (!mutex_trylock(&bridge->mutex)) { - ret = -EBUSY; - goto err_dev; - } + if (!mutex_trylock(&bridge->mutex)) + return ERR_PTR(-EBUSY); - if (!try_module_get(dev->parent->driver->owner)) - goto err_ll_mod; + if (!try_module_get(bridge->br_ops_owner)) { + mutex_unlock(&bridge->mutex); + return ERR_PTR(-ENODEV); + } dev_dbg(&bridge->dev, "get\n"); return bridge; - -err_ll_mod: - mutex_unlock(&bridge->mutex); -err_dev: - put_device(dev); - return ERR_PTR(ret); } /** @@ -98,13 +91,18 @@ err_dev: struct fpga_bridge *of_fpga_bridge_get(struct device_node *np, struct fpga_image_info *info) { - struct device *dev; + struct fpga_bridge *bridge; + struct device *bridge_dev; - dev = class_find_device_by_of_node(&fpga_bridge_class, np); - if (!dev) + bridge_dev = class_find_device_by_of_node(&fpga_bridge_class, np); + if (!bridge_dev) return ERR_PTR(-ENODEV); - return __fpga_bridge_get(dev, info); + bridge = __fpga_bridge_get(bridge_dev, info); + if (IS_ERR(bridge)) + put_device(bridge_dev); + + return bridge; } EXPORT_SYMBOL_GPL(of_fpga_bridge_get); @@ -125,6 +123,7 @@ static int fpga_bridge_dev_match(struct device *dev, const void *data) struct fpga_bridge *fpga_bridge_get(struct device *dev, struct fpga_image_info *info) { + struct fpga_bridge *bridge; struct device *bridge_dev; bridge_dev = class_find_device(&fpga_bridge_class, NULL, dev, @@ -132,7 +131,11 @@ struct fpga_bridge *fpga_bridge_get(struct device *dev, if (!bridge_dev) return ERR_PTR(-ENODEV); - return __fpga_bridge_get(bridge_dev, info); + bridge = __fpga_bridge_get(bridge_dev, info); + if (IS_ERR(bridge)) + put_device(bridge_dev); + + return bridge; } EXPORT_SYMBOL_GPL(fpga_bridge_get); @@ -146,7 +149,7 @@ void fpga_bridge_put(struct fpga_bridge *bridge) dev_dbg(&bridge->dev, "put\n"); bridge->info = NULL; - module_put(bridge->dev.parent->driver->owner); + module_put(bridge->br_ops_owner); mutex_unlock(&bridge->mutex); put_device(&bridge->dev); } @@ -316,18 +319,19 @@ static struct attribute *fpga_bridge_attrs[] = { ATTRIBUTE_GROUPS(fpga_bridge); /** - * fpga_bridge_register - create and register an FPGA Bridge device + * __fpga_bridge_register - create and register an FPGA Bridge device * @parent: FPGA bridge device from pdev * @name: FPGA bridge name * @br_ops: pointer to structure of fpga bridge ops * @priv: FPGA bridge private data + * @owner: owner module containing the br_ops * * Return: struct fpga_bridge pointer or ERR_PTR() */ struct fpga_bridge * -fpga_bridge_register(struct device *parent, const char *name, - const struct fpga_bridge_ops *br_ops, - void *priv) +__fpga_bridge_register(struct device *parent, const char *name, + const struct fpga_bridge_ops *br_ops, + void *priv, struct module *owner) { struct fpga_bridge *bridge; int id, ret; @@ -357,6 +361,7 @@ fpga_bridge_register(struct device *parent, const char *name, bridge->name = name; bridge->br_ops = br_ops; + bridge->br_ops_owner = owner; bridge->priv = priv; bridge->dev.groups = br_ops->groups; @@ -386,7 +391,7 @@ error_kfree: return ERR_PTR(ret); } -EXPORT_SYMBOL_GPL(fpga_bridge_register); +EXPORT_SYMBOL_GPL(__fpga_bridge_register); /** * fpga_bridge_unregister - unregister an FPGA bridge diff --git a/include/linux/fpga/fpga-bridge.h b/include/linux/fpga/fpga-bridge.h index 223da48a6d18..94c4edd047e5 100644 --- a/include/linux/fpga/fpga-bridge.h +++ b/include/linux/fpga/fpga-bridge.h @@ -45,6 +45,7 @@ struct fpga_bridge_info { * @dev: FPGA bridge device * @mutex: enforces exclusive reference to bridge * @br_ops: pointer to struct of FPGA bridge ops + * @br_ops_owner: module containing the br_ops * @info: fpga image specific information * @node: FPGA bridge list node * @priv: low level driver private date @@ -54,6 +55,7 @@ struct fpga_bridge { struct device dev; struct mutex mutex; /* for exclusive reference to bridge */ const struct fpga_bridge_ops *br_ops; + struct module *br_ops_owner; struct fpga_image_info *info; struct list_head node; void *priv; @@ -79,10 +81,12 @@ int of_fpga_bridge_get_to_list(struct device_node *np, struct fpga_image_info *info, struct list_head *bridge_list); +#define fpga_bridge_register(parent, name, br_ops, priv) \ + __fpga_bridge_register(parent, name, br_ops, priv, THIS_MODULE) struct fpga_bridge * -fpga_bridge_register(struct device *parent, const char *name, - const struct fpga_bridge_ops *br_ops, - void *priv); +__fpga_bridge_register(struct device *parent, const char *name, + const struct fpga_bridge_ops *br_ops, void *priv, + struct module *owner); void fpga_bridge_unregister(struct fpga_bridge *br); #endif /* _LINUX_FPGA_BRIDGE_H */ From a52e3a9dba347134ee53ebfe68b7b22548a387b0 Mon Sep 17 00:00:00 2001 From: Charles Perry Date: Thu, 21 Mar 2024 18:04:33 -0400 Subject: [PATCH 068/319] fpga: xilinx-spi: extract a common driver core Factor out the gpio handshaking (using PROGRAM_B, INIT_B and DONE) protocol in xilinx-core so that it can be reused for another driver. This commit does not change anything functionally to xilinx-spi. xilinx-core expects drivers to provide a write(const char* buf, size_t count) function that performs the actual write to the device, as well as a struct device* for resource management. Signed-off-by: Charles Perry Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240321220447.3260065-2-charles.perry@savoirfairelinux.com Signed-off-by: Xu Yilun --- drivers/fpga/Kconfig | 4 + drivers/fpga/Makefile | 1 + drivers/fpga/xilinx-core.c | 213 +++++++++++++++++++++++++++++++++++ drivers/fpga/xilinx-core.h | 27 +++++ drivers/fpga/xilinx-spi.c | 224 +++---------------------------------- 5 files changed, 260 insertions(+), 209 deletions(-) create mode 100644 drivers/fpga/xilinx-core.c create mode 100644 drivers/fpga/xilinx-core.h diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 2f689ac4ba3a..d27a1ebf4083 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -64,9 +64,13 @@ config FPGA_MGR_STRATIX10_SOC help FPGA manager driver support for the Intel Stratix10 SoC. +config FPGA_MGR_XILINX_CORE + tristate + config FPGA_MGR_XILINX_SPI tristate "Xilinx Configuration over Slave Serial (SPI)" depends on SPI + select FPGA_MGR_XILINX_CORE help FPGA manager driver support for Xilinx FPGA configuration over slave serial interface. diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 352a2612623e..7ec795b6a5a7 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o obj-$(CONFIG_FPGA_MGR_STRATIX10_SOC) += stratix10-soc.o obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o +obj-$(CONFIG_FPGA_MGR_XILINX_CORE) += xilinx-core.o obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA) += zynqmp-fpga.o diff --git a/drivers/fpga/xilinx-core.c b/drivers/fpga/xilinx-core.c new file mode 100644 index 000000000000..a35c43382dd5 --- /dev/null +++ b/drivers/fpga/xilinx-core.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Common parts of the Xilinx Spartan6 and 7 Series FPGA manager drivers. + * + * Copyright (C) 2017 DENX Software Engineering + * + * Anatolij Gustschin + */ + +#include "xilinx-core.h" + +#include +#include +#include +#include + +static int get_done_gpio(struct fpga_manager *mgr) +{ + struct xilinx_fpga_core *core = mgr->priv; + int ret; + + ret = gpiod_get_value(core->done); + if (ret < 0) + dev_err(&mgr->dev, "Error reading DONE (%d)\n", ret); + + return ret; +} + +static enum fpga_mgr_states xilinx_core_state(struct fpga_manager *mgr) +{ + if (!get_done_gpio(mgr)) + return FPGA_MGR_STATE_RESET; + + return FPGA_MGR_STATE_UNKNOWN; +} + +/** + * wait_for_init_b - wait for the INIT_B pin to have a given state, or wait + * a given delay if the pin is unavailable + * + * @mgr: The FPGA manager object + * @value: Value INIT_B to wait for (1 = asserted = low) + * @alt_udelay: Delay to wait if the INIT_B GPIO is not available + * + * Returns 0 when the INIT_B GPIO reached the given state or -ETIMEDOUT if + * too much time passed waiting for that. If no INIT_B GPIO is available + * then always return 0. + */ +static int wait_for_init_b(struct fpga_manager *mgr, int value, + unsigned long alt_udelay) +{ + struct xilinx_fpga_core *core = mgr->priv; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + if (core->init_b) { + while (time_before(jiffies, timeout)) { + int ret = gpiod_get_value(core->init_b); + + if (ret == value) + return 0; + + if (ret < 0) { + dev_err(&mgr->dev, + "Error reading INIT_B (%d)\n", ret); + return ret; + } + + usleep_range(100, 400); + } + + dev_err(&mgr->dev, "Timeout waiting for INIT_B to %s\n", + value ? "assert" : "deassert"); + return -ETIMEDOUT; + } + + udelay(alt_udelay); + + return 0; +} + +static int xilinx_core_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, + size_t count) +{ + struct xilinx_fpga_core *core = mgr->priv; + int err; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported\n"); + return -EINVAL; + } + + gpiod_set_value(core->prog_b, 1); + + err = wait_for_init_b(mgr, 1, 1); /* min is 500 ns */ + if (err) { + gpiod_set_value(core->prog_b, 0); + return err; + } + + gpiod_set_value(core->prog_b, 0); + + err = wait_for_init_b(mgr, 0, 0); + if (err) + return err; + + if (get_done_gpio(mgr)) { + dev_err(&mgr->dev, "Unexpected DONE pin state...\n"); + return -EIO; + } + + /* program latency */ + usleep_range(7500, 7600); + return 0; +} + +static int xilinx_core_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct xilinx_fpga_core *core = mgr->priv; + + return core->write(core, buf, count); +} + +static int xilinx_core_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct xilinx_fpga_core *core = mgr->priv; + unsigned long timeout = + jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + bool expired = false; + int done; + int ret; + const char padding[1] = { 0xff }; + + /* + * This loop is carefully written such that if the driver is + * scheduled out for more than 'timeout', we still check for DONE + * before giving up and we apply 8 extra CCLK cycles in all cases. + */ + while (!expired) { + expired = time_after(jiffies, timeout); + + done = get_done_gpio(mgr); + if (done < 0) + return done; + + ret = core->write(core, padding, sizeof(padding)); + if (ret) + return ret; + + if (done) + return 0; + } + + if (core->init_b) { + ret = gpiod_get_value(core->init_b); + + if (ret < 0) { + dev_err(&mgr->dev, "Error reading INIT_B (%d)\n", ret); + return ret; + } + + dev_err(&mgr->dev, + ret ? "CRC error or invalid device\n" : + "Missing sync word or incomplete bitstream\n"); + } else { + dev_err(&mgr->dev, "Timeout after config data transfer\n"); + } + + return -ETIMEDOUT; +} + +static const struct fpga_manager_ops xilinx_core_ops = { + .state = xilinx_core_state, + .write_init = xilinx_core_write_init, + .write = xilinx_core_write, + .write_complete = xilinx_core_write_complete, +}; + +int xilinx_core_probe(struct xilinx_fpga_core *core) +{ + struct fpga_manager *mgr; + + if (!core || !core->dev || !core->write) + return -EINVAL; + + /* PROGRAM_B is active low */ + core->prog_b = devm_gpiod_get(core->dev, "prog_b", GPIOD_OUT_LOW); + if (IS_ERR(core->prog_b)) + return dev_err_probe(core->dev, PTR_ERR(core->prog_b), + "Failed to get PROGRAM_B gpio\n"); + + core->init_b = devm_gpiod_get_optional(core->dev, "init-b", GPIOD_IN); + if (IS_ERR(core->init_b)) + return dev_err_probe(core->dev, PTR_ERR(core->init_b), + "Failed to get INIT_B gpio\n"); + + core->done = devm_gpiod_get(core->dev, "done", GPIOD_IN); + if (IS_ERR(core->done)) + return dev_err_probe(core->dev, PTR_ERR(core->done), + "Failed to get DONE gpio\n"); + + mgr = devm_fpga_mgr_register(core->dev, + "Xilinx Slave Serial FPGA Manager", + &xilinx_core_ops, core); + return PTR_ERR_OR_ZERO(mgr); +} +EXPORT_SYMBOL_GPL(xilinx_core_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anatolij Gustschin "); +MODULE_DESCRIPTION("Xilinx 7 Series FPGA manager core"); diff --git a/drivers/fpga/xilinx-core.h b/drivers/fpga/xilinx-core.h new file mode 100644 index 000000000000..f02ac67fce7b --- /dev/null +++ b/drivers/fpga/xilinx-core.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __XILINX_CORE_H +#define __XILINX_CORE_H + +#include + +/** + * struct xilinx_fpga_core - interface between the driver and the core manager + * of Xilinx 7 Series FPGA manager + * @dev: device node + * @write: write callback of the driver + */ +struct xilinx_fpga_core { +/* public: */ + struct device *dev; + int (*write)(struct xilinx_fpga_core *core, const char *buf, + size_t count); +/* private: handled by xilinx-core */ + struct gpio_desc *prog_b; + struct gpio_desc *init_b; + struct gpio_desc *done; +}; + +int xilinx_core_probe(struct xilinx_fpga_core *core); + +#endif /* __XILINX_CORE_H */ diff --git a/drivers/fpga/xilinx-spi.c b/drivers/fpga/xilinx-spi.c index e1a227e7ff2a..8756504340de 100644 --- a/drivers/fpga/xilinx-spi.c +++ b/drivers/fpga/xilinx-spi.c @@ -10,127 +10,17 @@ * the slave serial configuration interface. */ -#include -#include -#include -#include +#include "xilinx-core.h" + #include #include #include #include -#include -struct xilinx_spi_conf { - struct spi_device *spi; - struct gpio_desc *prog_b; - struct gpio_desc *init_b; - struct gpio_desc *done; -}; - -static int get_done_gpio(struct fpga_manager *mgr) -{ - struct xilinx_spi_conf *conf = mgr->priv; - int ret; - - ret = gpiod_get_value(conf->done); - - if (ret < 0) - dev_err(&mgr->dev, "Error reading DONE (%d)\n", ret); - - return ret; -} - -static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr) -{ - if (!get_done_gpio(mgr)) - return FPGA_MGR_STATE_RESET; - - return FPGA_MGR_STATE_UNKNOWN; -} - -/** - * wait_for_init_b - wait for the INIT_B pin to have a given state, or wait - * a given delay if the pin is unavailable - * - * @mgr: The FPGA manager object - * @value: Value INIT_B to wait for (1 = asserted = low) - * @alt_udelay: Delay to wait if the INIT_B GPIO is not available - * - * Returns 0 when the INIT_B GPIO reached the given state or -ETIMEDOUT if - * too much time passed waiting for that. If no INIT_B GPIO is available - * then always return 0. - */ -static int wait_for_init_b(struct fpga_manager *mgr, int value, - unsigned long alt_udelay) -{ - struct xilinx_spi_conf *conf = mgr->priv; - unsigned long timeout = jiffies + msecs_to_jiffies(1000); - - if (conf->init_b) { - while (time_before(jiffies, timeout)) { - int ret = gpiod_get_value(conf->init_b); - - if (ret == value) - return 0; - - if (ret < 0) { - dev_err(&mgr->dev, "Error reading INIT_B (%d)\n", ret); - return ret; - } - - usleep_range(100, 400); - } - - dev_err(&mgr->dev, "Timeout waiting for INIT_B to %s\n", - value ? "assert" : "deassert"); - return -ETIMEDOUT; - } - - udelay(alt_udelay); - - return 0; -} - -static int xilinx_spi_write_init(struct fpga_manager *mgr, - struct fpga_image_info *info, - const char *buf, size_t count) -{ - struct xilinx_spi_conf *conf = mgr->priv; - int err; - - if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { - dev_err(&mgr->dev, "Partial reconfiguration not supported\n"); - return -EINVAL; - } - - gpiod_set_value(conf->prog_b, 1); - - err = wait_for_init_b(mgr, 1, 1); /* min is 500 ns */ - if (err) { - gpiod_set_value(conf->prog_b, 0); - return err; - } - - gpiod_set_value(conf->prog_b, 0); - - err = wait_for_init_b(mgr, 0, 0); - if (err) - return err; - - if (get_done_gpio(mgr)) { - dev_err(&mgr->dev, "Unexpected DONE pin state...\n"); - return -EIO; - } - - /* program latency */ - usleep_range(7500, 7600); - return 0; -} - -static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf, +static int xilinx_spi_write(struct xilinx_fpga_core *core, const char *buf, size_t count) { - struct xilinx_spi_conf *conf = mgr->priv; + struct spi_device *spi = to_spi_device(core->dev); const char *fw_data = buf; const char *fw_data_end = fw_data + count; @@ -141,9 +31,9 @@ static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf, remaining = fw_data_end - fw_data; stride = min_t(size_t, remaining, SZ_4K); - ret = spi_write(conf->spi, fw_data, stride); + ret = spi_write(spi, fw_data, stride); if (ret) { - dev_err(&mgr->dev, "SPI error in firmware write: %d\n", + dev_err(core->dev, "SPI error in firmware write: %d\n", ret); return ret; } @@ -153,109 +43,25 @@ static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf, return 0; } -static int xilinx_spi_apply_cclk_cycles(struct xilinx_spi_conf *conf) -{ - struct spi_device *spi = conf->spi; - const u8 din_data[1] = { 0xff }; - int ret; - - ret = spi_write(conf->spi, din_data, sizeof(din_data)); - if (ret) - dev_err(&spi->dev, "applying CCLK cycles failed: %d\n", ret); - - return ret; -} - -static int xilinx_spi_write_complete(struct fpga_manager *mgr, - struct fpga_image_info *info) -{ - struct xilinx_spi_conf *conf = mgr->priv; - unsigned long timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us); - bool expired = false; - int done; - int ret; - - /* - * This loop is carefully written such that if the driver is - * scheduled out for more than 'timeout', we still check for DONE - * before giving up and we apply 8 extra CCLK cycles in all cases. - */ - while (!expired) { - expired = time_after(jiffies, timeout); - - done = get_done_gpio(mgr); - if (done < 0) - return done; - - ret = xilinx_spi_apply_cclk_cycles(conf); - if (ret) - return ret; - - if (done) - return 0; - } - - if (conf->init_b) { - ret = gpiod_get_value(conf->init_b); - - if (ret < 0) { - dev_err(&mgr->dev, "Error reading INIT_B (%d)\n", ret); - return ret; - } - - dev_err(&mgr->dev, - ret ? "CRC error or invalid device\n" - : "Missing sync word or incomplete bitstream\n"); - } else { - dev_err(&mgr->dev, "Timeout after config data transfer\n"); - } - - return -ETIMEDOUT; -} - -static const struct fpga_manager_ops xilinx_spi_ops = { - .state = xilinx_spi_state, - .write_init = xilinx_spi_write_init, - .write = xilinx_spi_write, - .write_complete = xilinx_spi_write_complete, -}; - static int xilinx_spi_probe(struct spi_device *spi) { - struct xilinx_spi_conf *conf; - struct fpga_manager *mgr; + struct xilinx_fpga_core *core; - conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); - if (!conf) + core = devm_kzalloc(&spi->dev, sizeof(*core), GFP_KERNEL); + if (!core) return -ENOMEM; - conf->spi = spi; + core->dev = &spi->dev; + core->write = xilinx_spi_write; - /* PROGRAM_B is active low */ - conf->prog_b = devm_gpiod_get(&spi->dev, "prog_b", GPIOD_OUT_LOW); - if (IS_ERR(conf->prog_b)) - return dev_err_probe(&spi->dev, PTR_ERR(conf->prog_b), - "Failed to get PROGRAM_B gpio\n"); - - conf->init_b = devm_gpiod_get_optional(&spi->dev, "init-b", GPIOD_IN); - if (IS_ERR(conf->init_b)) - return dev_err_probe(&spi->dev, PTR_ERR(conf->init_b), - "Failed to get INIT_B gpio\n"); - - conf->done = devm_gpiod_get(&spi->dev, "done", GPIOD_IN); - if (IS_ERR(conf->done)) - return dev_err_probe(&spi->dev, PTR_ERR(conf->done), - "Failed to get DONE gpio\n"); - - mgr = devm_fpga_mgr_register(&spi->dev, - "Xilinx Slave Serial FPGA Manager", - &xilinx_spi_ops, conf); - return PTR_ERR_OR_ZERO(mgr); + return xilinx_core_probe(core); } #ifdef CONFIG_OF static const struct of_device_id xlnx_spi_of_match[] = { - { .compatible = "xlnx,fpga-slave-serial", }, + { + .compatible = "xlnx,fpga-slave-serial", + }, {} }; MODULE_DEVICE_TABLE(of, xlnx_spi_of_match); From 8afcb190f3aac8d9ae4ca4f84a67bb281a4d9a3f Mon Sep 17 00:00:00 2001 From: Charles Perry Date: Thu, 21 Mar 2024 18:04:34 -0400 Subject: [PATCH 069/319] dt-bindings: fpga: xlnx,fpga-selectmap: add DT schema Document the SelectMAP interface of Xilinx 7 series FPGA. Signed-off-by: Charles Perry Reviewed-by: Krzysztof Kozlowski Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240321220447.3260065-3-charles.perry@savoirfairelinux.com Signed-off-by: Xu Yilun --- .../bindings/fpga/xlnx,fpga-selectmap.yaml | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Documentation/devicetree/bindings/fpga/xlnx,fpga-selectmap.yaml diff --git a/Documentation/devicetree/bindings/fpga/xlnx,fpga-selectmap.yaml b/Documentation/devicetree/bindings/fpga/xlnx,fpga-selectmap.yaml new file mode 100644 index 000000000000..05775746fd70 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/xlnx,fpga-selectmap.yaml @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/fpga/xlnx,fpga-selectmap.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Xilinx SelectMAP FPGA interface + +maintainers: + - Charles Perry + +description: | + Xilinx 7 Series FPGAs support a method of loading the bitstream over a + parallel port named the SelectMAP interface in the documentation. Only + the x8 mode is supported where data is loaded at one byte per rising edge of + the clock, with the MSB of each byte presented to the D0 pin. + + Datasheets: + https://www.xilinx.com/support/documentation/user_guides/ug470_7Series_Config.pdf + +allOf: + - $ref: /schemas/memory-controllers/mc-peripheral-props.yaml# + +properties: + compatible: + enum: + - xlnx,fpga-xc7s-selectmap + - xlnx,fpga-xc7a-selectmap + - xlnx,fpga-xc7k-selectmap + - xlnx,fpga-xc7v-selectmap + + reg: + description: + At least 1 byte of memory mapped IO + maxItems: 1 + + prog-gpios: + description: + config pin (referred to as PROGRAM_B in the manual) + maxItems: 1 + + done-gpios: + description: + config status pin (referred to as DONE in the manual) + maxItems: 1 + + init-gpios: + description: + initialization status and configuration error pin + (referred to as INIT_B in the manual) + maxItems: 1 + + csi-gpios: + description: + chip select pin (referred to as CSI_B in the manual) + Optional gpio for if the bus controller does not provide a chip select. + maxItems: 1 + + rdwr-gpios: + description: + read/write select pin (referred to as RDWR_B in the manual) + Optional gpio for if the bus controller does not provide this pin. + maxItems: 1 + +required: + - compatible + - reg + - prog-gpios + - done-gpios + - init-gpios + +unevaluatedProperties: false + +examples: + - | + #include + fpga-mgr@8000000 { + compatible = "xlnx,fpga-xc7s-selectmap"; + reg = <0x8000000 0x4>; + prog-gpios = <&gpio5 5 GPIO_ACTIVE_LOW>; + init-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; + done-gpios = <&gpio2 30 GPIO_ACTIVE_HIGH>; + csi-gpios = <&gpio3 19 GPIO_ACTIVE_LOW>; + rdwr-gpios = <&gpio3 10 GPIO_ACTIVE_LOW>; + }; +... From 104712a0866f7e3eb050271fc104c543aac642e8 Mon Sep 17 00:00:00 2001 From: Charles Perry Date: Thu, 21 Mar 2024 18:04:35 -0400 Subject: [PATCH 070/319] fpga: xilinx-selectmap: add new driver Xilinx 7 series FPGA can be programmed using a parallel port named the SelectMAP interface in the datasheet. This interface is compatible with the i.MX6 EIM bus controller but other types of external memory mapped parallel bus might work. xilinx-selectmap currently only supports the x8 mode where data is loaded at one byte per rising edge of the clock, with the MSb of each byte presented to the D0 pin. Signed-off-by: Charles Perry [yilun.xu@linux.intel.com: replace data type of i from u32 to size_t] Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240321220447.3260065-4-charles.perry@savoirfairelinux.com Signed-off-by: Xu Yilun --- drivers/fpga/Kconfig | 8 +++ drivers/fpga/Makefile | 1 + drivers/fpga/xilinx-selectmap.c | 95 +++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 drivers/fpga/xilinx-selectmap.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index d27a1ebf4083..37b35f58f0df 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -67,6 +67,14 @@ config FPGA_MGR_STRATIX10_SOC config FPGA_MGR_XILINX_CORE tristate +config FPGA_MGR_XILINX_SELECTMAP + tristate "Xilinx Configuration over SelectMAP" + depends on HAS_IOMEM + select FPGA_MGR_XILINX_CORE + help + FPGA manager driver support for Xilinx FPGA configuration + over SelectMAP interface. + config FPGA_MGR_XILINX_SPI tristate "Xilinx Configuration over Slave Serial (SPI)" depends on SPI diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 7ec795b6a5a7..aeb89bb13517 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o obj-$(CONFIG_FPGA_MGR_STRATIX10_SOC) += stratix10-soc.o obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o obj-$(CONFIG_FPGA_MGR_XILINX_CORE) += xilinx-core.o +obj-$(CONFIG_FPGA_MGR_XILINX_SELECTMAP) += xilinx-selectmap.o obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA) += zynqmp-fpga.o diff --git a/drivers/fpga/xilinx-selectmap.c b/drivers/fpga/xilinx-selectmap.c new file mode 100644 index 000000000000..2cd87e7e913f --- /dev/null +++ b/drivers/fpga/xilinx-selectmap.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Xilinx Spartan6 and 7 Series SelectMAP interface driver + * + * (C) 2024 Charles Perry + * + * Manage Xilinx FPGA firmware loaded over the SelectMAP configuration + * interface. + */ + +#include "xilinx-core.h" + +#include +#include +#include +#include +#include +#include + +struct xilinx_selectmap_conf { + struct xilinx_fpga_core core; + void __iomem *base; +}; + +#define to_xilinx_selectmap_conf(obj) \ + container_of(obj, struct xilinx_selectmap_conf, core) + +static int xilinx_selectmap_write(struct xilinx_fpga_core *core, + const char *buf, size_t count) +{ + struct xilinx_selectmap_conf *conf = to_xilinx_selectmap_conf(core); + size_t i; + + for (i = 0; i < count; ++i) + writeb(buf[i], conf->base); + + return 0; +} + +static int xilinx_selectmap_probe(struct platform_device *pdev) +{ + struct xilinx_selectmap_conf *conf; + struct gpio_desc *gpio; + void __iomem *base; + + conf = devm_kzalloc(&pdev->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + conf->core.dev = &pdev->dev; + conf->core.write = xilinx_selectmap_write; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); + if (IS_ERR(base)) + return dev_err_probe(&pdev->dev, PTR_ERR(base), + "ioremap error\n"); + conf->base = base; + + /* CSI_B is active low */ + gpio = devm_gpiod_get_optional(&pdev->dev, "csi", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(gpio), + "Failed to get CSI_B gpio\n"); + + /* RDWR_B is active low */ + gpio = devm_gpiod_get_optional(&pdev->dev, "rdwr", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return dev_err_probe(&pdev->dev, PTR_ERR(gpio), + "Failed to get RDWR_B gpio\n"); + + return xilinx_core_probe(&conf->core); +} + +static const struct of_device_id xlnx_selectmap_of_match[] = { + { .compatible = "xlnx,fpga-xc7s-selectmap", }, // Spartan-7 + { .compatible = "xlnx,fpga-xc7a-selectmap", }, // Artix-7 + { .compatible = "xlnx,fpga-xc7k-selectmap", }, // Kintex-7 + { .compatible = "xlnx,fpga-xc7v-selectmap", }, // Virtex-7 + {}, +}; +MODULE_DEVICE_TABLE(of, xlnx_selectmap_of_match); + +static struct platform_driver xilinx_selectmap_driver = { + .driver = { + .name = "xilinx-selectmap", + .of_match_table = xlnx_selectmap_of_match, + }, + .probe = xilinx_selectmap_probe, +}; + +module_platform_driver(xilinx_selectmap_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Charles Perry "); +MODULE_DESCRIPTION("Load Xilinx FPGA firmware over SelectMap"); From 4a1f12b5b50dd7d8ad681be701b2b00521c9d201 Mon Sep 17 00:00:00 2001 From: Charles Perry Date: Thu, 21 Mar 2024 18:04:36 -0400 Subject: [PATCH 071/319] fpga: xilinx-core: add new gpio names for prog and init Old names (prog_b and init-b) are used as a fallback for hardware compatible with the "xlnx,fpga-slave-serial" string. Signed-off-by: Charles Perry Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240321220447.3260065-5-charles.perry@savoirfairelinux.com Signed-off-by: Xu Yilun --- drivers/fpga/xilinx-core.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/fpga/xilinx-core.c b/drivers/fpga/xilinx-core.c index a35c43382dd5..39aeacf2e4f1 100644 --- a/drivers/fpga/xilinx-core.c +++ b/drivers/fpga/xilinx-core.c @@ -171,6 +171,20 @@ static int xilinx_core_write_complete(struct fpga_manager *mgr, return -ETIMEDOUT; } +static inline struct gpio_desc * +xilinx_core_devm_gpiod_get(struct device *dev, const char *con_id, + const char *legacy_con_id, enum gpiod_flags flags) +{ + struct gpio_desc *desc; + + desc = devm_gpiod_get(dev, con_id, flags); + if (IS_ERR(desc) && PTR_ERR(desc) == -ENOENT && + of_device_is_compatible(dev->of_node, "xlnx,fpga-slave-serial")) + desc = devm_gpiod_get(dev, legacy_con_id, flags); + + return desc; +} + static const struct fpga_manager_ops xilinx_core_ops = { .state = xilinx_core_state, .write_init = xilinx_core_write_init, @@ -186,12 +200,14 @@ int xilinx_core_probe(struct xilinx_fpga_core *core) return -EINVAL; /* PROGRAM_B is active low */ - core->prog_b = devm_gpiod_get(core->dev, "prog_b", GPIOD_OUT_LOW); + core->prog_b = xilinx_core_devm_gpiod_get(core->dev, "prog", "prog_b", + GPIOD_OUT_LOW); if (IS_ERR(core->prog_b)) return dev_err_probe(core->dev, PTR_ERR(core->prog_b), "Failed to get PROGRAM_B gpio\n"); - core->init_b = devm_gpiod_get_optional(core->dev, "init-b", GPIOD_IN); + core->init_b = xilinx_core_devm_gpiod_get(core->dev, "init", "init-b", + GPIOD_IN); if (IS_ERR(core->init_b)) return dev_err_probe(core->dev, PTR_ERR(core->init_b), "Failed to get INIT_B gpio\n"); From a97fc99a02c767f5970b75ce40fedd2a0843f323 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 27 Mar 2024 18:49:09 +0100 Subject: [PATCH 072/319] fpga: altera: drop driver owner assignment Core in spi_register_driver() already sets the .owner, so driver does not need to. Signed-off-by: Krzysztof Kozlowski Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240327174909.519796-1-krzysztof.kozlowski@linaro.org Signed-off-by: Xu Yilun --- drivers/fpga/altera-ps-spi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/fpga/altera-ps-spi.c b/drivers/fpga/altera-ps-spi.c index 740980e7cef8..d0ec3539b31f 100644 --- a/drivers/fpga/altera-ps-spi.c +++ b/drivers/fpga/altera-ps-spi.c @@ -284,7 +284,6 @@ MODULE_DEVICE_TABLE(spi, altera_ps_spi_ids); static struct spi_driver altera_ps_driver = { .driver = { .name = "altera-ps-spi", - .owner = THIS_MODULE, .of_match_table = of_ef_match, }, .id_table = altera_ps_spi_ids, From f6c86fdf3716c9257612f8f001c6e95db84fb844 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Mon, 1 Apr 2024 15:08:21 +0200 Subject: [PATCH 073/319] fpga: altera-cvp: Remove an unused field in struct altera_cvp_conf In "struct altera_cvp_conf", the 'mgr' field is unused. Remove it. Found with cppcheck, unusedStructMember. Signed-off-by: Christophe JAILLET Acked-by: Xu Yilun Link: https://lore.kernel.org/r/7986690e79fa6f7880bc1db783cb0e46a1c2723e.1711976883.git.christophe.jaillet@wanadoo.fr Signed-off-by: Xu Yilun --- drivers/fpga/altera-cvp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/fpga/altera-cvp.c b/drivers/fpga/altera-cvp.c index 4ffb9da537d8..6b0914432445 100644 --- a/drivers/fpga/altera-cvp.c +++ b/drivers/fpga/altera-cvp.c @@ -72,7 +72,6 @@ static bool altera_cvp_chkcfg; struct cvp_priv; struct altera_cvp_conf { - struct fpga_manager *mgr; struct pci_dev *pci_dev; void __iomem *map; void (*write_data)(struct altera_cvp_conf *conf, From 416bdb89605d960405178b9bf04df512d1ace1a3 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 22 Dec 2023 21:05:11 -0800 Subject: [PATCH 074/319] counter: linux/counter.h: fix Excess kernel-doc description warning Remove the @priv: line to prevent the kernel-doc warning: include/linux/counter.h:400: warning: Excess struct member 'priv' description in 'counter_device' Signed-off-by: Randy Dunlap Fixes: f2ee4759fb70 ("counter: remove old and now unused registration API") Link: https://lore.kernel.org/r/20231223050511.13849-1-rdunlap@infradead.org Signed-off-by: William Breathitt Gray --- include/linux/counter.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/counter.h b/include/linux/counter.h index 702e9108bbb4..b767b5c821f5 100644 --- a/include/linux/counter.h +++ b/include/linux/counter.h @@ -359,7 +359,6 @@ struct counter_ops { * @num_counts: number of Counts specified in @counts * @ext: optional array of Counter device extensions * @num_ext: number of Counter device extensions specified in @ext - * @priv: optional private data supplied by driver * @dev: internal device structure * @chrdev: internal character device structure * @events_list: list of current watching Counter events From 6b0828ca8bd18315647511dfe731101b6a59de2b Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Sun, 4 Feb 2024 13:02:30 -0300 Subject: [PATCH 075/319] counter: make counter_bus_type const Now that the driver core can properly handle constant struct bus_type, move the counter_bus_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Suggested-by: Greg Kroah-Hartman Signed-off-by: "Ricardo B. Marliere" Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20240204-bus_cleanup-counter-v1-1-cef9dd719bdc@marliere.net Signed-off-by: William Breathitt Gray --- drivers/counter/counter-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/counter/counter-core.c b/drivers/counter/counter-core.c index 3f24481fc04a..29df0985f2ba 100644 --- a/drivers/counter/counter-core.c +++ b/drivers/counter/counter-core.c @@ -54,7 +54,7 @@ static struct device_type counter_device_type = { .release = counter_device_release, }; -static struct bus_type counter_bus_type = { +static const struct bus_type counter_bus_type = { .name = "counter", .dev_name = "counter", }; From e0363c0706a187b037c06e1f30b2ac0fb44c31c3 Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Mon, 19 Feb 2024 16:52:52 -0300 Subject: [PATCH 076/319] counter: constify the struct device_type usage Since commit aed65af1cc2f ("drivers: make device_type const"), the driver core can properly handle constant struct device_type. Move the counter_device_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Cc: Greg Kroah-Hartman Signed-off-by: "Ricardo B. Marliere" Link: https://lore.kernel.org/r/20240219-device_cleanup-counter-v1-1-24d0316ae815@marliere.net Signed-off-by: William Breathitt Gray --- drivers/counter/counter-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/counter/counter-core.c b/drivers/counter/counter-core.c index 29df0985f2ba..893b4f0726d2 100644 --- a/drivers/counter/counter-core.c +++ b/drivers/counter/counter-core.c @@ -49,7 +49,7 @@ static void counter_device_release(struct device *dev) kfree(container_of(counter, struct counter_device_allochelper, counter)); } -static struct device_type counter_device_type = { +static const struct device_type counter_device_type = { .name = "counter_device", .release = counter_device_release, }; From 2f48aba356a004d854bc6d77fbc032b2fc666911 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Wed, 6 Mar 2024 16:36:31 +0100 Subject: [PATCH 077/319] counter: Introduce the COUNTER_COMP_FREQUENCY() macro Now that there are two users for the "frequency" extension, introduce a new COUNTER_COMP_FREQUENCY() macro. This extension is intended to be a read-only signal attribute. Suggested-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240306153631.4051115-1-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- include/linux/counter.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/counter.h b/include/linux/counter.h index b767b5c821f5..cd35d8574ee2 100644 --- a/include/linux/counter.h +++ b/include/linux/counter.h @@ -601,6 +601,9 @@ struct counter_array { #define COUNTER_COMP_FLOOR(_read, _write) \ COUNTER_COMP_COUNT_U64("floor", _read, _write) +#define COUNTER_COMP_FREQUENCY(_read) \ + COUNTER_COMP_SIGNAL_U64("frequency", _read, NULL) + #define COUNTER_COMP_POLARITY(_read, _write, _available) \ { \ .type = COUNTER_COMP_SIGNAL_POLARITY, \ From f7131297d638ffc9a63005464f7108e0dfdda8b6 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:32:57 +0100 Subject: [PATCH 078/319] counter: stm32-timer-cnt: rename quadrature signal Drop the Quadrature convention in the signal name. On stm32-timer: - Quadrature A signal corresponds to timer input ch1, hence "Channel 1" - Quadrature B signal corresponds to timer input ch2, hence "Channel 2". So name these signals after their channel. I suspect it referred to the (unique) quadrature counter support earlier, but the physical input really is CH1/CH2. This will be easier to support other counter modes. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-2-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index 6206d2dc3d47..36d812ddf162 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -279,11 +279,11 @@ static const struct counter_ops stm32_timer_cnt_ops = { static struct counter_signal stm32_signals[] = { { .id = 0, - .name = "Channel 1 Quadrature A" + .name = "Channel 1" }, { .id = 1, - .name = "Channel 1 Quadrature B" + .name = "Channel 2" } }; From 752923ccfd988401c254c3d63632e87b33835da5 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:32:58 +0100 Subject: [PATCH 079/319] counter: stm32-timer-cnt: rename counter The STM32 timer may count on various sources or channels. The counter isn't specifically counting on channe1 1. So rename it to avoid a confusion. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-3-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index 36d812ddf162..668e9d1061d3 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -302,7 +302,7 @@ static struct counter_synapse stm32_count_synapses[] = { static struct counter_count stm32_counts = { .id = 0, - .name = "Channel 1 Count", + .name = "STM32 Timer Counter", .functions_list = stm32_count_functions, .num_functions = ARRAY_SIZE(stm32_count_functions), .synapses = stm32_count_synapses, From 5679d5f76ca781c46212db3e274e610aeea62af9 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:32:59 +0100 Subject: [PATCH 080/319] counter: stm32-timer-cnt: adopt signal definitions Adopt signals definitions to ease later signals additions. There are no intended functional changes here. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-4-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index 668e9d1061d3..c34747d7857e 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -21,6 +21,9 @@ #define TIM_CCER_MASK (TIM_CCER_CC1P | TIM_CCER_CC1NP | \ TIM_CCER_CC2P | TIM_CCER_CC2NP) +#define STM32_CH1_SIG 0 +#define STM32_CH2_SIG 1 + struct stm32_timer_regs { u32 cr1; u32 cnt; @@ -247,14 +250,14 @@ static int stm32_action_read(struct counter_device *counter, return 0; case COUNTER_FUNCTION_QUADRATURE_X2_A: /* counts up/down on TI1FP1 edge depending on TI2FP2 level */ - if (synapse->signal->id == count->synapses[0].signal->id) + if (synapse->signal->id == STM32_CH1_SIG) *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; else *action = COUNTER_SYNAPSE_ACTION_NONE; return 0; case COUNTER_FUNCTION_QUADRATURE_X2_B: /* counts up/down on TI2FP2 edge depending on TI1FP1 level */ - if (synapse->signal->id == count->synapses[1].signal->id) + if (synapse->signal->id == STM32_CH2_SIG) *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; else *action = COUNTER_SYNAPSE_ACTION_NONE; @@ -278,11 +281,11 @@ static const struct counter_ops stm32_timer_cnt_ops = { static struct counter_signal stm32_signals[] = { { - .id = 0, + .id = STM32_CH1_SIG, .name = "Channel 1" }, { - .id = 1, + .id = STM32_CH2_SIG, .name = "Channel 2" } }; @@ -291,12 +294,12 @@ static struct counter_synapse stm32_count_synapses[] = { { .actions_list = stm32_synapse_actions, .num_actions = ARRAY_SIZE(stm32_synapse_actions), - .signal = &stm32_signals[0] + .signal = &stm32_signals[STM32_CH1_SIG] }, { .actions_list = stm32_synapse_actions, .num_actions = ARRAY_SIZE(stm32_synapse_actions), - .signal = &stm32_signals[1] + .signal = &stm32_signals[STM32_CH2_SIG] } }; From 7a6c69f2be82e60a57d75ef9ad29fac0aa3d619e Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:33:00 +0100 Subject: [PATCH 081/319] counter: stm32-timer-cnt: introduce clock signal Introduce the internal clock signal, used to count when in simple rising function. Also add the "frequency" extension to the clock signal. With this patch, signal action reports a consistent state when "increase" function is used, and the counting frequency: $ echo increase > function $ grep -H "" signal*_action signal0_action:none signal1_action:none signal2_action:rising edge $ echo 1 > enable $ cat count 25425 $ cat count 44439 $ cat ../signal2/frequency 208877930 Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-5-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 53 ++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index c34747d7857e..65b447b42e75 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -23,6 +23,7 @@ #define STM32_CH1_SIG 0 #define STM32_CH2_SIG 1 +#define STM32_CLOCK_SIG 2 struct stm32_timer_regs { u32 cr1; @@ -226,6 +227,10 @@ static struct counter_comp stm32_count_ext[] = { stm32_count_ceiling_write), }; +static const enum counter_synapse_action stm32_clock_synapse_actions[] = { + COUNTER_SYNAPSE_ACTION_RISING_EDGE, +}; + static const enum counter_synapse_action stm32_synapse_actions[] = { COUNTER_SYNAPSE_ACTION_NONE, COUNTER_SYNAPSE_ACTION_BOTH_EDGES @@ -246,7 +251,10 @@ static int stm32_action_read(struct counter_device *counter, switch (function) { case COUNTER_FUNCTION_INCREASE: /* counts on internal clock when CEN=1 */ - *action = COUNTER_SYNAPSE_ACTION_NONE; + if (synapse->signal->id == STM32_CLOCK_SIG) + *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE; + else + *action = COUNTER_SYNAPSE_ACTION_NONE; return 0; case COUNTER_FUNCTION_QUADRATURE_X2_A: /* counts up/down on TI1FP1 edge depending on TI2FP2 level */ @@ -264,7 +272,10 @@ static int stm32_action_read(struct counter_device *counter, return 0; case COUNTER_FUNCTION_QUADRATURE_X4: /* counts up/down on both TI1FP1 and TI2FP2 edges */ - *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + if (synapse->signal->id == STM32_CH1_SIG || synapse->signal->id == STM32_CH2_SIG) + *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + else + *action = COUNTER_SYNAPSE_ACTION_NONE; return 0; default: return -EINVAL; @@ -279,7 +290,30 @@ static const struct counter_ops stm32_timer_cnt_ops = { .action_read = stm32_action_read, }; +static int stm32_count_clk_get_freq(struct counter_device *counter, + struct counter_signal *signal, u64 *freq) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + + *freq = clk_get_rate(priv->clk); + + return 0; +} + +static struct counter_comp stm32_count_clock_ext[] = { + COUNTER_COMP_FREQUENCY(stm32_count_clk_get_freq), +}; + static struct counter_signal stm32_signals[] = { + /* + * Need to declare all the signals as a static array, and keep the signals order here, + * even if they're unused or unexisting on some timer instances. It's an abstraction, + * e.g. high level view of the counter features. + * + * Userspace programs may rely on signal0 to be "Channel 1", signal1 to be "Channel 2", + * and so on. When a signal is unexisting, the COUNTER_SYNAPSE_ACTION_NONE can be used, + * to indicate that a signal doesn't affect the counter. + */ { .id = STM32_CH1_SIG, .name = "Channel 1" @@ -287,7 +321,13 @@ static struct counter_signal stm32_signals[] = { { .id = STM32_CH2_SIG, .name = "Channel 2" - } + }, + { + .id = STM32_CLOCK_SIG, + .name = "Clock", + .ext = stm32_count_clock_ext, + .num_ext = ARRAY_SIZE(stm32_count_clock_ext), + }, }; static struct counter_synapse stm32_count_synapses[] = { @@ -300,7 +340,12 @@ static struct counter_synapse stm32_count_synapses[] = { .actions_list = stm32_synapse_actions, .num_actions = ARRAY_SIZE(stm32_synapse_actions), .signal = &stm32_signals[STM32_CH2_SIG] - } + }, + { + .actions_list = stm32_clock_synapse_actions, + .num_actions = ARRAY_SIZE(stm32_clock_synapse_actions), + .signal = &stm32_signals[STM32_CLOCK_SIG] + }, }; static struct counter_count stm32_counts = { From b73d03b3474212028f5b41675b0f30273c7ffa58 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:33:01 +0100 Subject: [PATCH 082/319] counter: stm32-timer-cnt: add counter prescaler extension There's a prescaler in between the selected input signal used for counting (CK_PSC), and the counter input (CK_CNT). So add the "prescaler" extension to the counter. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-6-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index 65b447b42e75..b969d550e90a 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -220,11 +220,40 @@ static int stm32_count_enable_write(struct counter_device *counter, return 0; } +static int stm32_count_prescaler_read(struct counter_device *counter, + struct counter_count *count, u64 *prescaler) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + u32 psc; + + regmap_read(priv->regmap, TIM_PSC, &psc); + + *prescaler = psc + 1; + + return 0; +} + +static int stm32_count_prescaler_write(struct counter_device *counter, + struct counter_count *count, u64 prescaler) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + u32 psc; + + if (!prescaler || prescaler > MAX_TIM_PSC + 1) + return -ERANGE; + + psc = prescaler - 1; + + return regmap_write(priv->regmap, TIM_PSC, psc); +} + static struct counter_comp stm32_count_ext[] = { COUNTER_COMP_DIRECTION(stm32_count_direction_read), COUNTER_COMP_ENABLE(stm32_count_enable_read, stm32_count_enable_write), COUNTER_COMP_CEILING(stm32_count_ceiling_read, stm32_count_ceiling_write), + COUNTER_COMP_COUNT_U64("prescaler", stm32_count_prescaler_read, + stm32_count_prescaler_write), }; static const enum counter_synapse_action stm32_clock_synapse_actions[] = { From 29646ee33cc34b322578e923a121a3ba5eedc22b Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:33:02 +0100 Subject: [PATCH 083/319] counter: stm32-timer-cnt: add checks on quadrature encoder capability This is a precursor patch to support capture channels on all possible channels and stm32 timer types. Original driver was intended to be used only as quadrature encoder and simple counter on internal clock. So, add a check on encoder capability, so the driver may be probed for timer instances without encoder feature. This way, all timers may be used as simple counter on internal clock, starting from here. Encoder capability is retrieved by using the timer index (originally in stm32-timer-trigger driver and dt-bindings). The need to keep backward compatibility with existing device tree lead to parse aside trigger node. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-7-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 55 +++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index b969d550e90a..17f87ace450d 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ struct stm32_timer_cnt { u32 max_arr; bool enabled; struct stm32_timer_regs bak; + bool has_encoder; }; static const enum counter_function stm32_count_functions[] = { @@ -111,12 +113,18 @@ static int stm32_count_function_write(struct counter_device *counter, sms = TIM_SMCR_SMS_SLAVE_MODE_DISABLED; break; case COUNTER_FUNCTION_QUADRATURE_X2_A: + if (!priv->has_encoder) + return -EOPNOTSUPP; sms = TIM_SMCR_SMS_ENCODER_MODE_1; break; case COUNTER_FUNCTION_QUADRATURE_X2_B: + if (!priv->has_encoder) + return -EOPNOTSUPP; sms = TIM_SMCR_SMS_ENCODER_MODE_2; break; case COUNTER_FUNCTION_QUADRATURE_X4: + if (!priv->has_encoder) + return -EOPNOTSUPP; sms = TIM_SMCR_SMS_ENCODER_MODE_3; break; default: @@ -388,6 +396,49 @@ static struct counter_count stm32_counts = { .num_ext = ARRAY_SIZE(stm32_count_ext) }; +/* encoder supported on TIM1 TIM2 TIM3 TIM4 TIM5 TIM8 */ +#define STM32_TIM_ENCODER_SUPPORTED (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(7)) + +static const char * const stm32_timer_trigger_compat[] = { + "st,stm32-timer-trigger", + "st,stm32h7-timer-trigger", +}; + +static int stm32_timer_cnt_probe_encoder(struct device *dev, + struct stm32_timer_cnt *priv) +{ + struct device *parent = dev->parent; + struct device_node *tnode = NULL, *pnode = parent->of_node; + int i, ret; + u32 idx; + + /* + * Need to retrieve the trigger node index from DT, to be able + * to determine if the counter supports encoder mode. It also + * enforce backward compatibility, and allow to support other + * counter modes in this driver (when the timer doesn't support + * encoder). + */ + for (i = 0; i < ARRAY_SIZE(stm32_timer_trigger_compat) && !tnode; i++) + tnode = of_get_compatible_child(pnode, stm32_timer_trigger_compat[i]); + if (!tnode) { + dev_err(dev, "Can't find trigger node\n"); + return -ENODATA; + } + + ret = of_property_read_u32(tnode, "reg", &idx); + if (ret) { + dev_err(dev, "Can't get index (%d)\n", ret); + return ret; + } + + priv->has_encoder = !!(STM32_TIM_ENCODER_SUPPORTED & BIT(idx)); + + dev_dbg(dev, "encoder support: %s\n", priv->has_encoder ? "yes" : "no"); + + return 0; +} + static int stm32_timer_cnt_probe(struct platform_device *pdev) { struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent); @@ -409,6 +460,10 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev) priv->clk = ddata->clk; priv->max_arr = ddata->max_arr; + ret = stm32_timer_cnt_probe_encoder(dev, priv); + if (ret) + return ret; + counter->name = dev_name(dev); counter->parent = dev; counter->ops = &stm32_timer_cnt_ops; From efec660d78f06cd1101b3300ebfef4918cc0f63f Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:33:03 +0100 Subject: [PATCH 084/319] counter: stm32-timer-cnt: introduce channels Simply add channels 3 and 4 that can be used for capture. Statically add them, despite some timers doesn't have them. Rather rely on stm32_action_read that will report "none" action for these currently. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-8-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index 17f87ace450d..f63d0c3e3f22 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -25,6 +25,8 @@ #define STM32_CH1_SIG 0 #define STM32_CH2_SIG 1 #define STM32_CLOCK_SIG 2 +#define STM32_CH3_SIG 3 +#define STM32_CH4_SIG 4 struct stm32_timer_regs { u32 cr1; @@ -365,6 +367,14 @@ static struct counter_signal stm32_signals[] = { .ext = stm32_count_clock_ext, .num_ext = ARRAY_SIZE(stm32_count_clock_ext), }, + { + .id = STM32_CH3_SIG, + .name = "Channel 3" + }, + { + .id = STM32_CH4_SIG, + .name = "Channel 4" + }, }; static struct counter_synapse stm32_count_synapses[] = { @@ -383,6 +393,16 @@ static struct counter_synapse stm32_count_synapses[] = { .num_actions = ARRAY_SIZE(stm32_clock_synapse_actions), .signal = &stm32_signals[STM32_CLOCK_SIG] }, + { + .actions_list = stm32_synapse_actions, + .num_actions = ARRAY_SIZE(stm32_synapse_actions), + .signal = &stm32_signals[STM32_CH3_SIG] + }, + { + .actions_list = stm32_synapse_actions, + .num_actions = ARRAY_SIZE(stm32_synapse_actions), + .signal = &stm32_signals[STM32_CH4_SIG] + }, }; static struct counter_count stm32_counts = { From f7630270b67836bedc518e96fb4c64b11fe3fb5e Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:33:04 +0100 Subject: [PATCH 085/319] counter: stm32-timer-cnt: probe number of channels from registers Probe the number of capture compare channels, by writing CCER register bits and read them back. Take care to restore the register original value. This is a precursor patch to support capture channels. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-9-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index f63d0c3e3f22..e1c0a502b74c 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -42,6 +42,7 @@ struct stm32_timer_cnt { bool enabled; struct stm32_timer_regs bak; bool has_encoder; + unsigned int nchannels; }; static const enum counter_function stm32_count_functions[] = { @@ -416,6 +417,20 @@ static struct counter_count stm32_counts = { .num_ext = ARRAY_SIZE(stm32_count_ext) }; +static void stm32_timer_cnt_detect_channels(struct device *dev, + struct stm32_timer_cnt *priv) +{ + u32 ccer, ccer_backup; + + regmap_read(priv->regmap, TIM_CCER, &ccer_backup); + regmap_set_bits(priv->regmap, TIM_CCER, TIM_CCER_CCXE); + regmap_read(priv->regmap, TIM_CCER, &ccer); + regmap_write(priv->regmap, TIM_CCER, ccer_backup); + priv->nchannels = hweight32(ccer & TIM_CCER_CCXE); + + dev_dbg(dev, "has %d cc channels\n", priv->nchannels); +} + /* encoder supported on TIM1 TIM2 TIM3 TIM4 TIM5 TIM8 */ #define STM32_TIM_ENCODER_SUPPORTED (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(7)) @@ -484,6 +499,8 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev) if (ret) return ret; + stm32_timer_cnt_detect_channels(dev, priv); + counter->name = dev_name(dev); counter->parent = dev; counter->ops = &stm32_timer_cnt_ops; From 2c70ccd45985f1e458f9785fc8cf9b8c48e3807f Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:33:05 +0100 Subject: [PATCH 086/319] counter: stm32-timer-cnt: add support for overflow events Add support overflow events. Also add the related validation and configuration routine. Register and enable interrupts to push events. STM32 Timers can have either 1 global interrupt, or 4 dedicated interrupt lines. Request only the necessary interrupt, e.g. either global interrupt that can report all event types, or update interrupt only for overflow event. Reviewed-by: William Breathitt Gray Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-10-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 138 +++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index e1c0a502b74c..9fcafec682b7 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -8,6 +8,7 @@ * */ #include +#include #include #include #include @@ -43,6 +44,9 @@ struct stm32_timer_cnt { struct stm32_timer_regs bak; bool has_encoder; unsigned int nchannels; + unsigned int nr_irqs; + spinlock_t lock; /* protects nb_ovf */ + u64 nb_ovf; }; static const enum counter_function stm32_count_functions[] = { @@ -258,6 +262,32 @@ static int stm32_count_prescaler_write(struct counter_device *counter, return regmap_write(priv->regmap, TIM_PSC, psc); } +static int stm32_count_nb_ovf_read(struct counter_device *counter, + struct counter_count *count, u64 *val) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + unsigned long irqflags; + + spin_lock_irqsave(&priv->lock, irqflags); + *val = priv->nb_ovf; + spin_unlock_irqrestore(&priv->lock, irqflags); + + return 0; +} + +static int stm32_count_nb_ovf_write(struct counter_device *counter, + struct counter_count *count, u64 val) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + unsigned long irqflags; + + spin_lock_irqsave(&priv->lock, irqflags); + priv->nb_ovf = val; + spin_unlock_irqrestore(&priv->lock, irqflags); + + return 0; +} + static struct counter_comp stm32_count_ext[] = { COUNTER_COMP_DIRECTION(stm32_count_direction_read), COUNTER_COMP_ENABLE(stm32_count_enable_read, stm32_count_enable_write), @@ -265,6 +295,7 @@ static struct counter_comp stm32_count_ext[] = { stm32_count_ceiling_write), COUNTER_COMP_COUNT_U64("prescaler", stm32_count_prescaler_read, stm32_count_prescaler_write), + COUNTER_COMP_COUNT_U64("num_overflows", stm32_count_nb_ovf_read, stm32_count_nb_ovf_write), }; static const enum counter_synapse_action stm32_clock_synapse_actions[] = { @@ -322,12 +353,57 @@ static int stm32_action_read(struct counter_device *counter, } } +static int stm32_count_events_configure(struct counter_device *counter) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + struct counter_event_node *event_node; + u32 dier = 0; + + list_for_each_entry(event_node, &counter->events_list, l) { + switch (event_node->event) { + case COUNTER_EVENT_OVERFLOW_UNDERFLOW: + /* first clear possibly latched UIF before enabling */ + if (!regmap_test_bits(priv->regmap, TIM_DIER, TIM_DIER_UIE)) + regmap_write(priv->regmap, TIM_SR, (u32)~TIM_SR_UIF); + dier |= TIM_DIER_UIE; + break; + default: + /* should never reach this path */ + return -EINVAL; + } + } + + /* Enable / disable all events at once, from events_list, so write all DIER bits */ + regmap_write(priv->regmap, TIM_DIER, dier); + + return 0; +} + +static int stm32_count_watch_validate(struct counter_device *counter, + const struct counter_watch *watch) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + + /* Interrupts are optional */ + if (!priv->nr_irqs) + return -EOPNOTSUPP; + + switch (watch->event) { + case COUNTER_EVENT_OVERFLOW_UNDERFLOW: + return 0; + default: + return -EINVAL; + } +} + static const struct counter_ops stm32_timer_cnt_ops = { .count_read = stm32_count_read, .count_write = stm32_count_write, .function_read = stm32_count_function_read, .function_write = stm32_count_function_write, .action_read = stm32_action_read, + .events_configure = stm32_count_events_configure, + .watch_validate = stm32_count_watch_validate, }; static int stm32_count_clk_get_freq(struct counter_device *counter, @@ -417,6 +493,37 @@ static struct counter_count stm32_counts = { .num_ext = ARRAY_SIZE(stm32_count_ext) }; +static irqreturn_t stm32_timer_cnt_isr(int irq, void *ptr) +{ + struct counter_device *counter = ptr; + struct stm32_timer_cnt *const priv = counter_priv(counter); + u32 clr = GENMASK(31, 0); /* SR flags can be cleared by writing 0 (wr 1 has no effect) */ + u32 sr, dier; + + regmap_read(priv->regmap, TIM_SR, &sr); + regmap_read(priv->regmap, TIM_DIER, &dier); + /* + * Some status bits in SR don't match with the enable bits in DIER. Only take care of + * the possibly enabled bits in DIER (that matches in between SR and DIER). + */ + dier &= TIM_DIER_UIE; + sr &= dier; + + if (sr & TIM_SR_UIF) { + spin_lock(&priv->lock); + priv->nb_ovf++; + spin_unlock(&priv->lock); + counter_push_event(counter, COUNTER_EVENT_OVERFLOW_UNDERFLOW, 0); + dev_dbg(counter->parent, "COUNTER_EVENT_OVERFLOW_UNDERFLOW\n"); + /* SR flags can be cleared by writing 0, only clear relevant flag */ + clr &= ~TIM_SR_UIF; + } + + regmap_write(priv->regmap, TIM_SR, clr); + + return IRQ_HANDLED; +}; + static void stm32_timer_cnt_detect_channels(struct device *dev, struct stm32_timer_cnt *priv) { @@ -480,7 +587,7 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct stm32_timer_cnt *priv; struct counter_device *counter; - int ret; + int i, ret; if (IS_ERR_OR_NULL(ddata)) return -EINVAL; @@ -494,6 +601,7 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev) priv->regmap = ddata->regmap; priv->clk = ddata->clk; priv->max_arr = ddata->max_arr; + priv->nr_irqs = ddata->nr_irqs; ret = stm32_timer_cnt_probe_encoder(dev, priv); if (ret) @@ -509,8 +617,36 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev) counter->signals = stm32_signals; counter->num_signals = ARRAY_SIZE(stm32_signals); + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + /* STM32 Timers can have either 1 global, or 4 dedicated interrupts (optional) */ + if (priv->nr_irqs == 1) { + /* All events reported through the global interrupt */ + ret = devm_request_irq(&pdev->dev, ddata->irq[0], stm32_timer_cnt_isr, + 0, dev_name(dev), counter); + if (ret) { + dev_err(dev, "Failed to request irq %d (err %d)\n", + ddata->irq[0], ret); + return ret; + } + } else { + for (i = 0; i < priv->nr_irqs; i++) { + /* Only take care of update IRQ for overflow events */ + if (i != STM32_TIMERS_IRQ_UP) + continue; + + ret = devm_request_irq(&pdev->dev, ddata->irq[i], stm32_timer_cnt_isr, + 0, dev_name(dev), counter); + if (ret) { + dev_err(dev, "Failed to request irq %d (err %d)\n", + ddata->irq[i], ret); + return ret; + } + } + } + /* Reset input selector to its default input */ regmap_write(priv->regmap, TIM_TISEL, 0x0); From 1aed15275b7ce17b5ebdfc112a76e0d7165ed46b Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Thu, 7 Mar 2024 14:33:06 +0100 Subject: [PATCH 087/319] counter: stm32-timer-cnt: add support for capture events Add support for capture events. Captured counter value for each channel can be retrieved through CCRx register. STM32 timers can have up to 4 capture channels (on input channel 1 to channel 4), hence need to check the number of channels before reading the capture data. The capture configuration is hard-coded to capture signals on both edges (non-inverted). Interrupts are used to report events independently for each channel. Reviewed-by: William Breathitt Gray Acked-by: Lee Jones Signed-off-by: Fabrice Gasnier Link: https://lore.kernel.org/r/20240307133306.383045-11-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray --- drivers/counter/stm32-timer-cnt.c | 134 +++++++++++++++++++++++++++++- include/linux/mfd/stm32-timers.h | 13 +++ 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c index 9fcafec682b7..0664ef969f79 100644 --- a/drivers/counter/stm32-timer-cnt.c +++ b/drivers/counter/stm32-timer-cnt.c @@ -262,6 +262,40 @@ static int stm32_count_prescaler_write(struct counter_device *counter, return regmap_write(priv->regmap, TIM_PSC, psc); } +static int stm32_count_cap_read(struct counter_device *counter, + struct counter_count *count, + size_t ch, u64 *cap) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + u32 ccrx; + + if (ch >= priv->nchannels) + return -EOPNOTSUPP; + + switch (ch) { + case 0: + regmap_read(priv->regmap, TIM_CCR1, &ccrx); + break; + case 1: + regmap_read(priv->regmap, TIM_CCR2, &ccrx); + break; + case 2: + regmap_read(priv->regmap, TIM_CCR3, &ccrx); + break; + case 3: + regmap_read(priv->regmap, TIM_CCR4, &ccrx); + break; + default: + return -EINVAL; + } + + dev_dbg(counter->parent, "CCR%zu: 0x%08x\n", ch + 1, ccrx); + + *cap = ccrx; + + return 0; +} + static int stm32_count_nb_ovf_read(struct counter_device *counter, struct counter_count *count, u64 *val) { @@ -288,6 +322,8 @@ static int stm32_count_nb_ovf_write(struct counter_device *counter, return 0; } +static DEFINE_COUNTER_ARRAY_CAPTURE(stm32_count_cap_array, 4); + static struct counter_comp stm32_count_ext[] = { COUNTER_COMP_DIRECTION(stm32_count_direction_read), COUNTER_COMP_ENABLE(stm32_count_enable_read, stm32_count_enable_write), @@ -295,6 +331,7 @@ static struct counter_comp stm32_count_ext[] = { stm32_count_ceiling_write), COUNTER_COMP_COUNT_U64("prescaler", stm32_count_prescaler_read, stm32_count_prescaler_write), + COUNTER_COMP_ARRAY_CAPTURE(stm32_count_cap_read, NULL, stm32_count_cap_array), COUNTER_COMP_COUNT_U64("num_overflows", stm32_count_nb_ovf_read, stm32_count_nb_ovf_write), }; @@ -353,11 +390,68 @@ static int stm32_action_read(struct counter_device *counter, } } +struct stm32_count_cc_regs { + u32 ccmr_reg; + u32 ccmr_mask; + u32 ccmr_bits; + u32 ccer_bits; +}; + +static const struct stm32_count_cc_regs stm32_cc[] = { + { TIM_CCMR1, TIM_CCMR_CC1S, TIM_CCMR_CC1S_TI1, + TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC1NP }, + { TIM_CCMR1, TIM_CCMR_CC2S, TIM_CCMR_CC2S_TI2, + TIM_CCER_CC2E | TIM_CCER_CC2P | TIM_CCER_CC2NP }, + { TIM_CCMR2, TIM_CCMR_CC3S, TIM_CCMR_CC3S_TI3, + TIM_CCER_CC3E | TIM_CCER_CC3P | TIM_CCER_CC3NP }, + { TIM_CCMR2, TIM_CCMR_CC4S, TIM_CCMR_CC4S_TI4, + TIM_CCER_CC4E | TIM_CCER_CC4P | TIM_CCER_CC4NP }, +}; + +static int stm32_count_capture_configure(struct counter_device *counter, unsigned int ch, + bool enable) +{ + struct stm32_timer_cnt *const priv = counter_priv(counter); + const struct stm32_count_cc_regs *cc; + u32 ccmr, ccer; + + if (ch >= ARRAY_SIZE(stm32_cc) || ch >= priv->nchannels) { + dev_err(counter->parent, "invalid ch: %d\n", ch); + return -EINVAL; + } + + cc = &stm32_cc[ch]; + + /* + * configure channel in input capture mode, map channel 1 on TI1, channel2 on TI2... + * Select both edges / non-inverted to trigger a capture. + */ + if (enable) { + /* first clear possibly latched capture flag upon enabling */ + if (!regmap_test_bits(priv->regmap, TIM_CCER, cc->ccer_bits)) + regmap_write(priv->regmap, TIM_SR, ~TIM_SR_CC_IF(ch)); + regmap_update_bits(priv->regmap, cc->ccmr_reg, cc->ccmr_mask, + cc->ccmr_bits); + regmap_set_bits(priv->regmap, TIM_CCER, cc->ccer_bits); + } else { + regmap_clear_bits(priv->regmap, TIM_CCER, cc->ccer_bits); + regmap_clear_bits(priv->regmap, cc->ccmr_reg, cc->ccmr_mask); + } + + regmap_read(priv->regmap, cc->ccmr_reg, &ccmr); + regmap_read(priv->regmap, TIM_CCER, &ccer); + dev_dbg(counter->parent, "%s(%s) ch%d 0x%08x 0x%08x\n", __func__, enable ? "ena" : "dis", + ch, ccmr, ccer); + + return 0; +} + static int stm32_count_events_configure(struct counter_device *counter) { struct stm32_timer_cnt *const priv = counter_priv(counter); struct counter_event_node *event_node; u32 dier = 0; + int i, ret; list_for_each_entry(event_node, &counter->events_list, l) { switch (event_node->event) { @@ -367,6 +461,12 @@ static int stm32_count_events_configure(struct counter_device *counter) regmap_write(priv->regmap, TIM_SR, (u32)~TIM_SR_UIF); dier |= TIM_DIER_UIE; break; + case COUNTER_EVENT_CAPTURE: + ret = stm32_count_capture_configure(counter, event_node->channel, true); + if (ret) + return ret; + dier |= TIM_DIER_CC_IE(event_node->channel); + break; default: /* should never reach this path */ return -EINVAL; @@ -376,6 +476,15 @@ static int stm32_count_events_configure(struct counter_device *counter) /* Enable / disable all events at once, from events_list, so write all DIER bits */ regmap_write(priv->regmap, TIM_DIER, dier); + /* check for disabled capture events */ + for (i = 0 ; i < priv->nchannels; i++) { + if (!(dier & TIM_DIER_CC_IE(i))) { + ret = stm32_count_capture_configure(counter, i, false); + if (ret) + return ret; + } + } + return 0; } @@ -389,6 +498,12 @@ static int stm32_count_watch_validate(struct counter_device *counter, return -EOPNOTSUPP; switch (watch->event) { + case COUNTER_EVENT_CAPTURE: + if (watch->channel >= priv->nchannels) { + dev_err(counter->parent, "Invalid channel %d\n", watch->channel); + return -EINVAL; + } + return 0; case COUNTER_EVENT_OVERFLOW_UNDERFLOW: return 0; default: @@ -499,6 +614,7 @@ static irqreturn_t stm32_timer_cnt_isr(int irq, void *ptr) struct stm32_timer_cnt *const priv = counter_priv(counter); u32 clr = GENMASK(31, 0); /* SR flags can be cleared by writing 0 (wr 1 has no effect) */ u32 sr, dier; + int i; regmap_read(priv->regmap, TIM_SR, &sr); regmap_read(priv->regmap, TIM_DIER, &dier); @@ -506,7 +622,7 @@ static irqreturn_t stm32_timer_cnt_isr(int irq, void *ptr) * Some status bits in SR don't match with the enable bits in DIER. Only take care of * the possibly enabled bits in DIER (that matches in between SR and DIER). */ - dier &= TIM_DIER_UIE; + dier &= (TIM_DIER_UIE | TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE); sr &= dier; if (sr & TIM_SR_UIF) { @@ -519,6 +635,15 @@ static irqreturn_t stm32_timer_cnt_isr(int irq, void *ptr) clr &= ~TIM_SR_UIF; } + /* Check capture events */ + for (i = 0 ; i < priv->nchannels; i++) { + if (sr & TIM_SR_CC_IF(i)) { + counter_push_event(counter, COUNTER_EVENT_CAPTURE, i); + clr &= ~TIM_SR_CC_IF(i); + dev_dbg(counter->parent, "COUNTER_EVENT_CAPTURE, %d\n", i); + } + } + regmap_write(priv->regmap, TIM_SR, clr); return IRQ_HANDLED; @@ -633,8 +758,11 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev) } } else { for (i = 0; i < priv->nr_irqs; i++) { - /* Only take care of update IRQ for overflow events */ - if (i != STM32_TIMERS_IRQ_UP) + /* + * Only take care of update IRQ for overflow events, and cc for + * capture events. + */ + if (i != STM32_TIMERS_IRQ_UP && i != STM32_TIMERS_IRQ_CC) continue; ret = devm_request_irq(&pdev->dev, ddata->irq[i], stm32_timer_cnt_isr, diff --git a/include/linux/mfd/stm32-timers.h b/include/linux/mfd/stm32-timers.h index ca35af30745f..9eb17481b07f 100644 --- a/include/linux/mfd/stm32-timers.h +++ b/include/linux/mfd/stm32-timers.h @@ -41,6 +41,11 @@ #define TIM_SMCR_SMS (BIT(0) | BIT(1) | BIT(2)) /* Slave mode selection */ #define TIM_SMCR_TS (BIT(4) | BIT(5) | BIT(6)) /* Trigger selection */ #define TIM_DIER_UIE BIT(0) /* Update interrupt */ +#define TIM_DIER_CC1IE BIT(1) /* CC1 Interrupt Enable */ +#define TIM_DIER_CC2IE BIT(2) /* CC2 Interrupt Enable */ +#define TIM_DIER_CC3IE BIT(3) /* CC3 Interrupt Enable */ +#define TIM_DIER_CC4IE BIT(4) /* CC4 Interrupt Enable */ +#define TIM_DIER_CC_IE(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt enable */ #define TIM_DIER_UDE BIT(8) /* Update DMA request Enable */ #define TIM_DIER_CC1DE BIT(9) /* CC1 DMA request Enable */ #define TIM_DIER_CC2DE BIT(10) /* CC2 DMA request Enable */ @@ -49,6 +54,7 @@ #define TIM_DIER_COMDE BIT(13) /* COM DMA request Enable */ #define TIM_DIER_TDE BIT(14) /* Trigger DMA request Enable */ #define TIM_SR_UIF BIT(0) /* Update interrupt flag */ +#define TIM_SR_CC_IF(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt flag */ #define TIM_EGR_UG BIT(0) /* Update Generation */ #define TIM_CCMR_PE BIT(3) /* Channel Preload Enable */ #define TIM_CCMR_M1 (BIT(6) | BIT(5)) /* Channel PWM Mode 1 */ @@ -60,16 +66,23 @@ #define TIM_CCMR_CC1S_TI2 BIT(1) /* IC1/IC3 selects TI2/TI4 */ #define TIM_CCMR_CC2S_TI2 BIT(8) /* IC2/IC4 selects TI2/TI4 */ #define TIM_CCMR_CC2S_TI1 BIT(9) /* IC2/IC4 selects TI1/TI3 */ +#define TIM_CCMR_CC3S (BIT(0) | BIT(1)) /* Capture/compare 3 sel */ +#define TIM_CCMR_CC4S (BIT(8) | BIT(9)) /* Capture/compare 4 sel */ +#define TIM_CCMR_CC3S_TI3 BIT(0) /* IC3 selects TI3 */ +#define TIM_CCMR_CC4S_TI4 BIT(8) /* IC4 selects TI4 */ #define TIM_CCER_CC1E BIT(0) /* Capt/Comp 1 out Ena */ #define TIM_CCER_CC1P BIT(1) /* Capt/Comp 1 Polarity */ #define TIM_CCER_CC1NE BIT(2) /* Capt/Comp 1N out Ena */ #define TIM_CCER_CC1NP BIT(3) /* Capt/Comp 1N Polarity */ #define TIM_CCER_CC2E BIT(4) /* Capt/Comp 2 out Ena */ #define TIM_CCER_CC2P BIT(5) /* Capt/Comp 2 Polarity */ +#define TIM_CCER_CC2NP BIT(7) /* Capt/Comp 2N Polarity */ #define TIM_CCER_CC3E BIT(8) /* Capt/Comp 3 out Ena */ #define TIM_CCER_CC3P BIT(9) /* Capt/Comp 3 Polarity */ +#define TIM_CCER_CC3NP BIT(11) /* Capt/Comp 3N Polarity */ #define TIM_CCER_CC4E BIT(12) /* Capt/Comp 4 out Ena */ #define TIM_CCER_CC4P BIT(13) /* Capt/Comp 4 Polarity */ +#define TIM_CCER_CC4NP BIT(15) /* Capt/Comp 4N Polarity */ #define TIM_CCER_CCXE (BIT(0) | BIT(4) | BIT(8) | BIT(12)) #define TIM_BDTR_BKE(x) BIT(12 + (x) * 12) /* Break input enable */ #define TIM_BDTR_BKP(x) BIT(13 + (x) * 12) /* Break input polarity */ From c90663596e7c97363d8855c635f39500ed2f0030 Mon Sep 17 00:00:00 2001 From: William Breathitt Gray Date: Sat, 9 Mar 2024 10:47:56 -0500 Subject: [PATCH 088/319] MAINTAINERS: Update email addresses for William Breathitt Gray Using a kernel.org address is more consistent for kernel work and should prevent the need to update again if the underlying email address changes. Signed-off-by: William Breathitt Gray --- MAINTAINERS | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 7c121493f43d..bb0e4c6091dc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -210,44 +210,44 @@ S: Maintained F: drivers/hwmon/abituguru3.c ACCES 104-DIO-48E GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-104-dio-48e.c ACCES 104-IDI-48 GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-104-idi-48.c ACCES 104-IDIO-16 GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-104-idio-16.c ACCES 104-QUAD-8 DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-iio@vger.kernel.org S: Maintained F: drivers/counter/104-quad-8.c ACCES IDIO-16 GPIO LIBRARY -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-idio-16.c F: drivers/gpio/gpio-idio-16.h ACCES PCI-IDIO-16 GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-pci-idio-16.c ACCES PCIe-IDIO-24 GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-pcie-idio-24.c @@ -1453,7 +1453,7 @@ S: Maintained F: sound/aoa/ APEX EMBEDDED SYSTEMS STX104 IIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-iio@vger.kernel.org S: Maintained F: drivers/iio/addac/stx104.c @@ -5459,7 +5459,7 @@ F: Documentation/hwmon/corsair-psu.rst F: drivers/hwmon/corsair-psu.c COUNTER SUBSYSTEM -M: William Breathitt Gray +M: William Breathitt Gray L: linux-iio@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/wbg/counter.git @@ -6245,7 +6245,7 @@ F: include/sound/da[79]*.h F: sound/soc/codecs/da[79]*.[ch] DIAMOND SYSTEMS GPIO-MM GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-gpio-mm.c @@ -10756,14 +10756,14 @@ S: Maintained F: drivers/video/fbdev/i810/ INTEL 8254 COUNTER DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-iio@vger.kernel.org S: Maintained F: drivers/counter/i8254.c F: include/linux/i8254.h INTEL 8255 GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-i8255.c @@ -11460,7 +11460,7 @@ F: Documentation/devicetree/bindings/interrupt-controller/ F: drivers/irqchip/ ISA -M: William Breathitt Gray +M: William Breathitt Gray S: Maintained F: Documentation/driver-api/isa.rst F: drivers/base/isa.c @@ -13472,7 +13472,7 @@ F: drivers/net/mdio/mdio-regmap.c F: include/linux/mdio/mdio-regmap.h MEASUREMENT COMPUTING CIO-DAC IIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-iio@vger.kernel.org S: Maintained F: drivers/iio/dac/cio-dac.c @@ -23888,7 +23888,7 @@ S: Orphan F: drivers/watchdog/ebc-c384_wdt.c WINSYSTEMS WS16C48 GPIO DRIVER -M: William Breathitt Gray +M: William Breathitt Gray L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-ws16c48.c From e9b4895fd171dfa961c570d31ef61f443b210ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 10 Mar 2024 09:06:06 +0100 Subject: [PATCH 089/319] counter: ti-ecap-capture: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/f70902b2aabecaa9295c28629cd7a8a0e6eb06d0.1710057753.git.u.kleine-koenig@pengutronix.de Signed-off-by: William Breathitt Gray --- drivers/counter/ti-ecap-capture.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/counter/ti-ecap-capture.c b/drivers/counter/ti-ecap-capture.c index fb1cb1774674..d33d35055b91 100644 --- a/drivers/counter/ti-ecap-capture.c +++ b/drivers/counter/ti-ecap-capture.c @@ -537,15 +537,13 @@ static int ecap_cnt_probe(struct platform_device *pdev) return 0; } -static int ecap_cnt_remove(struct platform_device *pdev) +static void ecap_cnt_remove(struct platform_device *pdev) { struct counter_device *counter_dev = platform_get_drvdata(pdev); struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev); if (ecap_dev->enabled) ecap_cnt_capture_disable(counter_dev); - - return 0; } static int ecap_cnt_suspend(struct device *dev) @@ -600,7 +598,7 @@ MODULE_DEVICE_TABLE(of, ecap_cnt_of_match); static struct platform_driver ecap_cnt_driver = { .probe = ecap_cnt_probe, - .remove = ecap_cnt_remove, + .remove_new = ecap_cnt_remove, .driver = { .name = "ecap-capture", .of_match_table = ecap_cnt_of_match, From 4b986b68e6993c512106548ed712aebe40d3605e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 10 Mar 2024 09:06:07 +0100 Subject: [PATCH 090/319] counter: ti-eqep: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Acked-by: David Lechner Link: https://lore.kernel.org/r/bf78595f6a49be0b6bb403b466c13177d72c02b7.1710057753.git.u.kleine-koenig@pengutronix.de Signed-off-by: William Breathitt Gray --- drivers/counter/ti-eqep.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/counter/ti-eqep.c b/drivers/counter/ti-eqep.c index b0f24cf3e891..072b11fd6b32 100644 --- a/drivers/counter/ti-eqep.c +++ b/drivers/counter/ti-eqep.c @@ -425,7 +425,7 @@ static int ti_eqep_probe(struct platform_device *pdev) return 0; } -static int ti_eqep_remove(struct platform_device *pdev) +static void ti_eqep_remove(struct platform_device *pdev) { struct counter_device *counter = platform_get_drvdata(pdev); struct device *dev = &pdev->dev; @@ -433,8 +433,6 @@ static int ti_eqep_remove(struct platform_device *pdev) counter_unregister(counter); pm_runtime_put_sync(dev); pm_runtime_disable(dev); - - return 0; } static const struct of_device_id ti_eqep_of_match[] = { @@ -445,7 +443,7 @@ MODULE_DEVICE_TABLE(of, ti_eqep_of_match); static struct platform_driver ti_eqep_driver = { .probe = ti_eqep_probe, - .remove = ti_eqep_remove, + .remove_new = ti_eqep_remove, .driver = { .name = "ti-eqep-cnt", .of_match_table = ti_eqep_of_match, From 916baadd293a4d11e08a7ca1e2968314451ade6c Mon Sep 17 00:00:00 2001 From: William Breathitt Gray Date: Thu, 21 Mar 2024 12:32:16 -0400 Subject: [PATCH 091/319] counter: ti-ecap-capture: Utilize COUNTER_COMP_FREQUENCY macro Reduce boilerplate by leveraging the COUNTER_COMP_FREQUENCY() macro to define the "frequency" extension. Link: https://lore.kernel.org/r/ZfxhEKdSi1amfcJC@ishi Signed-off-by: William Breathitt Gray --- drivers/counter/ti-ecap-capture.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/counter/ti-ecap-capture.c b/drivers/counter/ti-ecap-capture.c index d33d35055b91..675447315caf 100644 --- a/drivers/counter/ti-ecap-capture.c +++ b/drivers/counter/ti-ecap-capture.c @@ -369,7 +369,7 @@ static const enum counter_synapse_action ecap_cnt_input_actions[] = { }; static struct counter_comp ecap_cnt_clock_ext[] = { - COUNTER_COMP_SIGNAL_U64("frequency", ecap_cnt_clk_get_freq, NULL), + COUNTER_COMP_FREQUENCY(ecap_cnt_clk_get_freq), }; static const enum counter_signal_polarity ecap_cnt_pol_avail[] = { From 2d766e79ba2bd15e59ceda4b0596e369c9564948 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 18:53:04 +0000 Subject: [PATCH 092/319] 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 093/319] 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 094/319] 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 095/319] 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 096/319] 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 097/319] 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 098/319] 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 099/319] 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 100/319] 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 101/319] 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 102/319] 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 103/319] 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 104/319] 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 105/319] 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 106/319] 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 107/319] 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 108/319] 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 109/319] 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 110/319] 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 111/319] 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 112/319] 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 113/319] 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 114/319] 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 4d2bc3f7dea4d17243924c6758e0447cc1aa0eea Mon Sep 17 00:00:00 2001 From: Marco Pagani Date: Fri, 29 Mar 2024 18:48:47 +0100 Subject: [PATCH 115/319] fpga: tests: use KUnit devices instead of platform devices KUnit now provides helper functions to create fake devices, so use them instead of relying on platform devices. Other changes: remove an unnecessary white space in the fpga region suite. Reviewed-by: Russ Weight Signed-off-by: Marco Pagani Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240329174849.248243-1-marpagan@redhat.com Signed-off-by: Xu Yilun --- drivers/fpga/tests/fpga-bridge-test.c | 33 ++++++++++----------- drivers/fpga/tests/fpga-mgr-test.c | 16 +++++------ drivers/fpga/tests/fpga-region-test.c | 41 +++++++++++++-------------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/drivers/fpga/tests/fpga-bridge-test.c b/drivers/fpga/tests/fpga-bridge-test.c index 1d258002cdd7..2f7a24f23808 100644 --- a/drivers/fpga/tests/fpga-bridge-test.c +++ b/drivers/fpga/tests/fpga-bridge-test.c @@ -7,8 +7,8 @@ * Author: Marco Pagani */ +#include #include -#include #include #include #include @@ -19,7 +19,7 @@ struct bridge_stats { struct bridge_ctx { struct fpga_bridge *bridge; - struct platform_device *pdev; + struct device *dev; struct bridge_stats stats; }; @@ -43,30 +43,31 @@ static const struct fpga_bridge_ops fake_bridge_ops = { /** * register_test_bridge() - Register a fake FPGA bridge for testing. * @test: KUnit test context object. + * @dev_name: name of the kunit device to be registered * * Return: Context of the newly registered FPGA bridge. */ -static struct bridge_ctx *register_test_bridge(struct kunit *test) +static struct bridge_ctx *register_test_bridge(struct kunit *test, const char *dev_name) { struct bridge_ctx *ctx; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); - ctx->pdev = platform_device_register_simple("bridge_pdev", PLATFORM_DEVID_AUTO, NULL, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->pdev); + ctx->dev = kunit_device_register(test, dev_name); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->dev); - ctx->bridge = fpga_bridge_register(&ctx->pdev->dev, "Fake FPGA bridge", &fake_bridge_ops, + ctx->bridge = fpga_bridge_register(ctx->dev, "Fake FPGA bridge", &fake_bridge_ops, &ctx->stats); KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(ctx->bridge)); return ctx; } -static void unregister_test_bridge(struct bridge_ctx *ctx) +static void unregister_test_bridge(struct kunit *test, struct bridge_ctx *ctx) { fpga_bridge_unregister(ctx->bridge); - platform_device_unregister(ctx->pdev); + kunit_device_unregister(test, ctx->dev); } static void fpga_bridge_test_get(struct kunit *test) @@ -74,10 +75,10 @@ static void fpga_bridge_test_get(struct kunit *test) struct bridge_ctx *ctx = test->priv; struct fpga_bridge *bridge; - bridge = fpga_bridge_get(&ctx->pdev->dev, NULL); + bridge = fpga_bridge_get(ctx->dev, NULL); KUNIT_EXPECT_PTR_EQ(test, bridge, ctx->bridge); - bridge = fpga_bridge_get(&ctx->pdev->dev, NULL); + bridge = fpga_bridge_get(ctx->dev, NULL); KUNIT_EXPECT_EQ(test, PTR_ERR(bridge), -EBUSY); fpga_bridge_put(ctx->bridge); @@ -105,19 +106,19 @@ static void fpga_bridge_test_get_put_list(struct kunit *test) int ret; ctx_0 = test->priv; - ctx_1 = register_test_bridge(test); + ctx_1 = register_test_bridge(test, "fpga-bridge-test-dev-1"); INIT_LIST_HEAD(&bridge_list); /* Get bridge 0 and add it to the list */ - ret = fpga_bridge_get_to_list(&ctx_0->pdev->dev, NULL, &bridge_list); + ret = fpga_bridge_get_to_list(ctx_0->dev, NULL, &bridge_list); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_PTR_EQ(test, ctx_0->bridge, list_first_entry_or_null(&bridge_list, struct fpga_bridge, node)); /* Get bridge 1 and add it to the list */ - ret = fpga_bridge_get_to_list(&ctx_1->pdev->dev, NULL, &bridge_list); + ret = fpga_bridge_get_to_list(ctx_1->dev, NULL, &bridge_list); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_PTR_EQ(test, ctx_1->bridge, @@ -141,19 +142,19 @@ static void fpga_bridge_test_get_put_list(struct kunit *test) KUNIT_EXPECT_TRUE(test, list_empty(&bridge_list)); - unregister_test_bridge(ctx_1); + unregister_test_bridge(test, ctx_1); } static int fpga_bridge_test_init(struct kunit *test) { - test->priv = register_test_bridge(test); + test->priv = register_test_bridge(test, "fpga-bridge-test-dev-0"); return 0; } static void fpga_bridge_test_exit(struct kunit *test) { - unregister_test_bridge(test->priv); + unregister_test_bridge(test, test->priv); } static struct kunit_case fpga_bridge_test_cases[] = { diff --git a/drivers/fpga/tests/fpga-mgr-test.c b/drivers/fpga/tests/fpga-mgr-test.c index 6acec55b60ce..125b3a4d43c6 100644 --- a/drivers/fpga/tests/fpga-mgr-test.c +++ b/drivers/fpga/tests/fpga-mgr-test.c @@ -7,8 +7,8 @@ * Author: Marco Pagani */ +#include #include -#include #include #include #include @@ -40,7 +40,7 @@ struct mgr_stats { struct mgr_ctx { struct fpga_image_info *img_info; struct fpga_manager *mgr; - struct platform_device *pdev; + struct device *dev; struct mgr_stats stats; }; @@ -194,7 +194,7 @@ static void fpga_mgr_test_get(struct kunit *test) struct mgr_ctx *ctx = test->priv; struct fpga_manager *mgr; - mgr = fpga_mgr_get(&ctx->pdev->dev); + mgr = fpga_mgr_get(ctx->dev); KUNIT_EXPECT_PTR_EQ(test, mgr, ctx->mgr); fpga_mgr_put(ctx->mgr); @@ -284,14 +284,14 @@ static int fpga_mgr_test_init(struct kunit *test) ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); - ctx->pdev = platform_device_register_simple("mgr_pdev", PLATFORM_DEVID_AUTO, NULL, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->pdev); + ctx->dev = kunit_device_register(test, "fpga-manager-test-dev"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->dev); - ctx->mgr = devm_fpga_mgr_register(&ctx->pdev->dev, "Fake FPGA Manager", &fake_mgr_ops, + ctx->mgr = devm_fpga_mgr_register(ctx->dev, "Fake FPGA Manager", &fake_mgr_ops, &ctx->stats); KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(ctx->mgr)); - ctx->img_info = fpga_image_info_alloc(&ctx->pdev->dev); + ctx->img_info = fpga_image_info_alloc(ctx->dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->img_info); test->priv = ctx; @@ -304,7 +304,7 @@ static void fpga_mgr_test_exit(struct kunit *test) struct mgr_ctx *ctx = test->priv; fpga_image_info_free(ctx->img_info); - platform_device_unregister(ctx->pdev); + kunit_device_unregister(test, ctx->dev); } static struct kunit_case fpga_mgr_test_cases[] = { diff --git a/drivers/fpga/tests/fpga-region-test.c b/drivers/fpga/tests/fpga-region-test.c index baab07e3fc59..bcf0651df261 100644 --- a/drivers/fpga/tests/fpga-region-test.c +++ b/drivers/fpga/tests/fpga-region-test.c @@ -7,12 +7,12 @@ * Author: Marco Pagani */ +#include #include #include #include #include #include -#include #include struct mgr_stats { @@ -26,11 +26,11 @@ struct bridge_stats { struct test_ctx { struct fpga_manager *mgr; - struct platform_device *mgr_pdev; + struct device *mgr_dev; struct fpga_bridge *bridge; - struct platform_device *bridge_pdev; + struct device *bridge_dev; struct fpga_region *region; - struct platform_device *region_pdev; + struct device *region_dev; struct bridge_stats bridge_stats; struct mgr_stats mgr_stats; }; @@ -91,7 +91,7 @@ static void fpga_region_test_class_find(struct kunit *test) struct test_ctx *ctx = test->priv; struct fpga_region *region; - region = fpga_region_class_find(NULL, &ctx->region_pdev->dev, fake_region_match); + region = fpga_region_class_find(NULL, ctx->region_dev, fake_region_match); KUNIT_EXPECT_PTR_EQ(test, region, ctx->region); put_device(®ion->dev); @@ -108,7 +108,7 @@ static void fpga_region_test_program_fpga(struct kunit *test) char img_buf[4]; int ret; - img_info = fpga_image_info_alloc(&ctx->mgr_pdev->dev); + img_info = fpga_image_info_alloc(ctx->mgr_dev); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, img_info); img_info->buf = img_buf; @@ -148,32 +148,30 @@ static int fpga_region_test_init(struct kunit *test) ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); - ctx->mgr_pdev = platform_device_register_simple("mgr_pdev", PLATFORM_DEVID_AUTO, NULL, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->mgr_pdev); + ctx->mgr_dev = kunit_device_register(test, "fpga-manager-test-dev"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->mgr_dev); - ctx->mgr = devm_fpga_mgr_register(&ctx->mgr_pdev->dev, "Fake FPGA Manager", &fake_mgr_ops, - &ctx->mgr_stats); + ctx->mgr = devm_fpga_mgr_register(ctx->mgr_dev, "Fake FPGA Manager", + &fake_mgr_ops, &ctx->mgr_stats); KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(ctx->mgr)); - ctx->bridge_pdev = platform_device_register_simple("bridge_pdev", PLATFORM_DEVID_AUTO, - NULL, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->bridge_pdev); + ctx->bridge_dev = kunit_device_register(test, "fpga-bridge-test-dev"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->bridge_dev); - ctx->bridge = fpga_bridge_register(&ctx->bridge_pdev->dev, "Fake FPGA Bridge", + ctx->bridge = fpga_bridge_register(ctx->bridge_dev, "Fake FPGA Bridge", &fake_bridge_ops, &ctx->bridge_stats); KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(ctx->bridge)); ctx->bridge_stats.enable = true; - ctx->region_pdev = platform_device_register_simple("region_pdev", PLATFORM_DEVID_AUTO, - NULL, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->region_pdev); + ctx->region_dev = kunit_device_register(test, "fpga-region-test-dev"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->region_dev); region_info.mgr = ctx->mgr; region_info.priv = ctx->bridge; region_info.get_bridges = fake_region_get_bridges; - ctx->region = fpga_region_register_full(&ctx->region_pdev->dev, ®ion_info); + ctx->region = fpga_region_register_full(ctx->region_dev, ®ion_info); KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(ctx->region)); test->priv = ctx; @@ -186,18 +184,17 @@ static void fpga_region_test_exit(struct kunit *test) struct test_ctx *ctx = test->priv; fpga_region_unregister(ctx->region); - platform_device_unregister(ctx->region_pdev); + kunit_device_unregister(test, ctx->region_dev); fpga_bridge_unregister(ctx->bridge); - platform_device_unregister(ctx->bridge_pdev); + kunit_device_unregister(test, ctx->bridge_dev); - platform_device_unregister(ctx->mgr_pdev); + kunit_device_unregister(test, ctx->mgr_dev); } static struct kunit_case fpga_region_test_cases[] = { KUNIT_CASE(fpga_region_test_class_find), KUNIT_CASE(fpga_region_test_program_fpga), - {} }; From 4bc4634eda10b5a9e703d4b52c0941ea7fbf0fa9 Mon Sep 17 00:00:00 2001 From: Samuel Thibault Date: Wed, 27 Mar 2024 12:50:51 +0100 Subject: [PATCH 116/319] speakup: Turn i18n files utf-8 i18n currently assume latin1 encoding, which is not enough for most languages. This separates out the utf-8 processing of /dev/synthu, and uses it for a new synth_writeu, which we make synth_printf now use. This has the effect of making all the i18 messages processed in utf-8. Signed-off-by: Samuel Thibault Link: https://lore.kernel.org/r/20240327115051.ng7xqnhozyii4ik2@begin Signed-off-by: Greg Kroah-Hartman --- drivers/accessibility/speakup/devsynth.c | 57 ++++----------- drivers/accessibility/speakup/speakup.h | 2 + drivers/accessibility/speakup/synth.c | 92 ++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 50 deletions(-) diff --git a/drivers/accessibility/speakup/devsynth.c b/drivers/accessibility/speakup/devsynth.c index cb7e1114e8eb..e3d909bd0480 100644 --- a/drivers/accessibility/speakup/devsynth.c +++ b/drivers/accessibility/speakup/devsynth.c @@ -39,13 +39,13 @@ static ssize_t speakup_file_write(struct file *fp, const char __user *buffer, static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer, size_t nbytes, loff_t *ppos) { - size_t count = nbytes, want; + size_t count = nbytes, consumed, want; const char __user *ptr = buffer; size_t bytes; unsigned long flags; unsigned char buf[256]; u16 ubuf[256]; - size_t in, in2, out; + size_t in, out; if (!synth) return -ENODEV; @@ -58,57 +58,24 @@ static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer, return -EFAULT; /* Convert to u16 */ - for (in = 0, out = 0; in < bytes; in++) { - unsigned char c = buf[in]; - int nbytes = 8 - fls(c ^ 0xff); - u32 value; + for (in = 0, out = 0; in < bytes; in += consumed) { + s32 value; - switch (nbytes) { - case 8: /* 0xff */ - case 7: /* 0xfe */ - case 1: /* 0x80 */ - /* Invalid, drop */ - goto drop; + value = synth_utf8_get(buf + in, bytes - in, &consumed, &want); + if (value == -1) { + /* Invalid or incomplete */ - case 0: - /* ASCII, copy */ - ubuf[out++] = c; - continue; - - default: - /* 2..6-byte UTF-8 */ - - if (bytes - in < nbytes) { + if (want > bytes - in) /* We don't have it all yet, stop here * and wait for the rest */ bytes = in; - want = nbytes; - continue; - } - /* First byte */ - value = c & ((1u << (7 - nbytes)) - 1); - - /* Other bytes */ - for (in2 = 2; in2 <= nbytes; in2++) { - c = buf[in + 1]; - if ((c & 0xc0) != 0x80) { - /* Invalid, drop the head */ - want = 1; - goto drop; - } - value = (value << 6) | (c & 0x3f); - in++; - } - - if (value < 0x10000) - ubuf[out++] = value; - want = 1; - break; + continue; } -drop: - /* empty statement */; + + if (value < 0x10000) + ubuf[out++] = value; } count -= bytes; diff --git a/drivers/accessibility/speakup/speakup.h b/drivers/accessibility/speakup/speakup.h index 364fde99749e..54f1226ea061 100644 --- a/drivers/accessibility/speakup/speakup.h +++ b/drivers/accessibility/speakup/speakup.h @@ -76,7 +76,9 @@ int speakup_paste_selection(struct tty_struct *tty); void speakup_cancel_paste(void); void speakup_register_devsynth(void); void speakup_unregister_devsynth(void); +s32 synth_utf8_get(const char *buf, size_t count, size_t *consumed, size_t *want); void synth_write(const char *buf, size_t count); +void synth_writeu(const char *buf, size_t count); int synth_supports_indexing(void); extern struct vc_data *spk_sel_cons; diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index 45f906103133..85062e605d79 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -217,10 +217,95 @@ void synth_write(const char *_buf, size_t count) synth_start(); } +/* Consume one utf-8 character from buf (that contains up to count bytes), + * returns the unicode codepoint if valid, -1 otherwise. + * In all cases, returns the number of consumed bytes in *consumed, + * and the minimum number of bytes that would be needed for the next character + * in *want. + */ +s32 synth_utf8_get(const char *buf, size_t count, size_t *consumed, size_t *want) +{ + unsigned char c = buf[0]; + int nbytes = 8 - fls(c ^ 0xff); + u32 value; + size_t i; + + switch (nbytes) { + case 8: /* 0xff */ + case 7: /* 0xfe */ + case 1: /* 0x80 */ + /* Invalid, drop */ + *consumed = 1; + *want = 1; + return -1; + + case 0: + /* ASCII, take as such */ + *consumed = 1; + *want = 1; + return c; + + default: + /* 2..6-byte UTF-8 */ + + if (count < nbytes) { + /* We don't have it all */ + *consumed = 0; + *want = nbytes; + return -1; + } + + /* First byte */ + value = c & ((1u << (7 - nbytes)) - 1); + + /* Other bytes */ + for (i = 1; i < nbytes; i++) { + c = buf[i]; + if ((c & 0xc0) != 0x80) { + /* Invalid, drop the head */ + *consumed = i; + *want = 1; + return -1; + } + value = (value << 6) | (c & 0x3f); + } + + *consumed = nbytes; + *want = 1; + return value; + } +} + +void synth_writeu(const char *buf, size_t count) +{ + size_t i, consumed, want; + + /* Convert to u16 */ + for (i = 0; i < count; i++) { + s32 value; + + value = synth_utf8_get(buf + i, count - i, &consumed, &want); + if (value == -1) { + /* Invalid or incomplete */ + + if (want > count - i) + /* We don't have it all, stop */ + count = i; + + continue; + } + + if (value < 0x10000) + synth_buffer_add(value); + } + + synth_start(); +} + void synth_printf(const char *fmt, ...) { va_list args; - unsigned char buf[160], *p; + unsigned char buf[160]; int r; va_start(args, fmt); @@ -229,10 +314,7 @@ void synth_printf(const char *fmt, ...) if (r > sizeof(buf) - 1) r = sizeof(buf) - 1; - p = buf; - while (r--) - synth_buffer_add(*p++); - synth_start(); + synth_writeu(buf, r); } EXPORT_SYMBOL_GPL(synth_printf); From b2c6a73fb4130f3c5e2a8ba4b1c98c5a9db03b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 8 Mar 2024 22:31:01 +0100 Subject: [PATCH 117/319] uio: fsl_elbc_gpcm: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/ca99d4e71054e8452f3cee6c016bf4f89bfc7eaa.1709933231.git.u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_fsl_elbc_gpcm.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/uio/uio_fsl_elbc_gpcm.c b/drivers/uio/uio_fsl_elbc_gpcm.c index 82dda799f327..496caff66e7e 100644 --- a/drivers/uio/uio_fsl_elbc_gpcm.c +++ b/drivers/uio/uio_fsl_elbc_gpcm.c @@ -427,7 +427,7 @@ out_err2: return ret; } -static int uio_fsl_elbc_gpcm_remove(struct platform_device *pdev) +static void uio_fsl_elbc_gpcm_remove(struct platform_device *pdev) { struct uio_info *info = platform_get_drvdata(pdev); struct fsl_elbc_gpcm *priv = info->priv; @@ -438,8 +438,6 @@ static int uio_fsl_elbc_gpcm_remove(struct platform_device *pdev) priv->shutdown(info, false); iounmap(info->mem[0].internal_addr); - return 0; - } static const struct of_device_id uio_fsl_elbc_gpcm_match[] = { @@ -455,7 +453,7 @@ static struct platform_driver uio_fsl_elbc_gpcm_driver = { .dev_groups = uio_fsl_elbc_gpcm_groups, }, .probe = uio_fsl_elbc_gpcm_probe, - .remove = uio_fsl_elbc_gpcm_remove, + .remove_new = uio_fsl_elbc_gpcm_remove, }; module_platform_driver(uio_fsl_elbc_gpcm_driver); From 1019fa4839c97c4efff9c26af4d74a184921d8da Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Wed, 10 Apr 2024 09:48:03 -0500 Subject: [PATCH 118/319] uio: pruss: Remove this driver This UIO driver was used to control the PRU processors found on various TI SoCs. It was created before the Remoteproc framework, but now with that we have a standard way to program and manage the PRU processors. The proper PRU Remoteproc driver should be used instead of this driver. This driver only supported the original class of PRUSS (OMAP-L1xx / AM17xx / AM18xx / TMS320C674x / DA8xx) but when these platforms were switched to use Device Tree the support for DT was not added to this driver and so it is now unused/unusable. Support for these platforms can be added to the proper PRU Remoteproc driver if ever needed. Remove this driver. Signed-off-by: Andrew Davis Link: https://lore.kernel.org/r/20240410144803.126831-1-afd@ti.com Signed-off-by: Greg Kroah-Hartman --- drivers/uio/Kconfig | 18 -- drivers/uio/Makefile | 1 - drivers/uio/uio_pruss.c | 255 ------------------------ include/linux/platform_data/uio_pruss.h | 18 -- 4 files changed, 292 deletions(-) delete mode 100644 drivers/uio/uio_pruss.c delete mode 100644 include/linux/platform_data/uio_pruss.h diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig index 2e16c5338e5b..b060dcd7c635 100644 --- a/drivers/uio/Kconfig +++ b/drivers/uio/Kconfig @@ -125,24 +125,6 @@ config UIO_FSL_ELBC_GPCM_NETX5152 Information about this hardware can be found at: http://www.hilscher.com/netx -config UIO_PRUSS - tristate "Texas Instruments PRUSS driver" - select GENERIC_ALLOCATOR - depends on HAS_IOMEM && HAS_DMA - help - PRUSS driver for OMAPL138/DA850/AM18XX devices - PRUSS driver requires user space components, examples and user space - driver is available from below SVN repo - you may use anonymous login - - https://gforge.ti.com/gf/project/pru_sw/ - - More info on API is available at below wiki - - http://processors.wiki.ti.com/index.php/PRU_Linux_Application_Loader - - To compile this driver as a module, choose M here: the module - will be called uio_pruss. - config UIO_MF624 tristate "Humusoft MF624 DAQ PCI card driver" depends on PCI diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile index f2f416a14228..1c5f3b5a95cf 100644 --- a/drivers/uio/Makefile +++ b/drivers/uio/Makefile @@ -7,7 +7,6 @@ obj-$(CONFIG_UIO_AEC) += uio_aec.o obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o obj-$(CONFIG_UIO_NETX) += uio_netx.o -obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o obj-$(CONFIG_UIO_MF624) += uio_mf624.o obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c deleted file mode 100644 index f67881cba645..000000000000 --- a/drivers/uio/uio_pruss.c +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss) - * - * This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM, - * and DDR RAM to user space for applications interacting with PRUSS firmware - * - * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/ - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRV_NAME "pruss_uio" -#define DRV_VERSION "1.0" - -static int sram_pool_sz = SZ_16K; -module_param(sram_pool_sz, int, 0); -MODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate "); - -static int extram_pool_sz = SZ_256K; -module_param(extram_pool_sz, int, 0); -MODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate"); - -/* - * Host event IRQ numbers from PRUSS - PRUSS can generate up to 8 interrupt - * events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS - * firmware and user space application, async notification from PRU firmware - * to user space application - * 3 PRU_EVTOUT0 - * 4 PRU_EVTOUT1 - * 5 PRU_EVTOUT2 - * 6 PRU_EVTOUT3 - * 7 PRU_EVTOUT4 - * 8 PRU_EVTOUT5 - * 9 PRU_EVTOUT6 - * 10 PRU_EVTOUT7 -*/ -#define MAX_PRUSS_EVT 8 - -#define PINTC_HIDISR 0x0038 -#define PINTC_HIPIR 0x0900 -#define HIPIR_NOPEND 0x80000000 -#define PINTC_HIER 0x1500 - -struct uio_pruss_dev { - struct uio_info *info; - struct clk *pruss_clk; - dma_addr_t sram_paddr; - dma_addr_t ddr_paddr; - void __iomem *prussio_vaddr; - unsigned long sram_vaddr; - void *ddr_vaddr; - unsigned int hostirq_start; - unsigned int pintc_base; - struct gen_pool *sram_pool; -}; - -static irqreturn_t pruss_handler(int irq, struct uio_info *info) -{ - struct uio_pruss_dev *gdev = info->priv; - int intr_bit = (irq - gdev->hostirq_start + 2); - int val, intr_mask = (1 << intr_bit); - void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base; - void __iomem *intren_reg = base + PINTC_HIER; - void __iomem *intrdis_reg = base + PINTC_HIDISR; - void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2); - - val = ioread32(intren_reg); - /* Is interrupt enabled and active ? */ - if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND)) - return IRQ_NONE; - /* Disable interrupt */ - iowrite32(intr_bit, intrdis_reg); - return IRQ_HANDLED; -} - -static void pruss_cleanup(struct device *dev, struct uio_pruss_dev *gdev) -{ - int cnt; - struct uio_info *p = gdev->info; - - for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) { - uio_unregister_device(p); - } - iounmap(gdev->prussio_vaddr); - if (gdev->ddr_vaddr) { - dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr, - gdev->ddr_paddr); - } - if (gdev->sram_vaddr) - gen_pool_free(gdev->sram_pool, - gdev->sram_vaddr, - sram_pool_sz); - clk_disable(gdev->pruss_clk); -} - -static int pruss_probe(struct platform_device *pdev) -{ - struct uio_info *p; - struct uio_pruss_dev *gdev; - struct resource *regs_prussio; - struct device *dev = &pdev->dev; - int ret, cnt, i, len; - struct uio_pruss_pdata *pdata = dev_get_platdata(dev); - - gdev = devm_kzalloc(dev, sizeof(struct uio_pruss_dev), GFP_KERNEL); - if (!gdev) - return -ENOMEM; - - gdev->info = devm_kcalloc(dev, MAX_PRUSS_EVT, sizeof(*p), GFP_KERNEL); - if (!gdev->info) - return -ENOMEM; - - /* Power on PRU in case its not done as part of boot-loader */ - gdev->pruss_clk = devm_clk_get(dev, "pruss"); - if (IS_ERR(gdev->pruss_clk)) { - dev_err(dev, "Failed to get clock\n"); - return PTR_ERR(gdev->pruss_clk); - } - - ret = clk_enable(gdev->pruss_clk); - if (ret) { - dev_err(dev, "Failed to enable clock\n"); - return ret; - } - - regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!regs_prussio) { - dev_err(dev, "No PRUSS I/O resource specified\n"); - ret = -EIO; - goto err_clk_disable; - } - - if (!regs_prussio->start) { - dev_err(dev, "Invalid memory resource\n"); - ret = -EIO; - goto err_clk_disable; - } - - if (pdata->sram_pool) { - gdev->sram_pool = pdata->sram_pool; - gdev->sram_vaddr = - (unsigned long)gen_pool_dma_alloc(gdev->sram_pool, - sram_pool_sz, &gdev->sram_paddr); - if (!gdev->sram_vaddr) { - dev_err(dev, "Could not allocate SRAM pool\n"); - ret = -ENOMEM; - goto err_clk_disable; - } - } - - gdev->ddr_vaddr = dma_alloc_coherent(dev, extram_pool_sz, - &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); - if (!gdev->ddr_vaddr) { - dev_err(dev, "Could not allocate external memory\n"); - ret = -ENOMEM; - goto err_free_sram; - } - - len = resource_size(regs_prussio); - gdev->prussio_vaddr = ioremap(regs_prussio->start, len); - if (!gdev->prussio_vaddr) { - dev_err(dev, "Can't remap PRUSS I/O address range\n"); - ret = -ENOMEM; - goto err_free_ddr_vaddr; - } - - ret = platform_get_irq(pdev, 0); - if (ret < 0) - goto err_unmap; - - gdev->hostirq_start = ret; - gdev->pintc_base = pdata->pintc_base; - - for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) { - p->mem[0].addr = regs_prussio->start; - p->mem[0].size = resource_size(regs_prussio); - p->mem[0].memtype = UIO_MEM_PHYS; - - p->mem[1].addr = gdev->sram_paddr; - p->mem[1].size = sram_pool_sz; - p->mem[1].memtype = UIO_MEM_PHYS; - - p->mem[2].addr = (uintptr_t) gdev->ddr_vaddr; - p->mem[2].dma_addr = gdev->ddr_paddr; - p->mem[2].size = extram_pool_sz; - p->mem[2].memtype = UIO_MEM_DMA_COHERENT; - p->mem[2].dma_device = dev; - - p->name = devm_kasprintf(dev, GFP_KERNEL, "pruss_evt%d", cnt); - p->version = DRV_VERSION; - - /* Register PRUSS IRQ lines */ - p->irq = gdev->hostirq_start + cnt; - p->handler = pruss_handler; - p->priv = gdev; - - ret = uio_register_device(dev, p); - if (ret < 0) - goto err_unloop; - } - - platform_set_drvdata(pdev, gdev); - return 0; - -err_unloop: - for (i = 0, p = gdev->info; i < cnt; i++, p++) { - uio_unregister_device(p); - } -err_unmap: - iounmap(gdev->prussio_vaddr); -err_free_ddr_vaddr: - dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr, - gdev->ddr_paddr); -err_free_sram: - if (pdata->sram_pool) - gen_pool_free(gdev->sram_pool, gdev->sram_vaddr, sram_pool_sz); -err_clk_disable: - clk_disable(gdev->pruss_clk); - - return ret; -} - -static int pruss_remove(struct platform_device *dev) -{ - struct uio_pruss_dev *gdev = platform_get_drvdata(dev); - - pruss_cleanup(&dev->dev, gdev); - return 0; -} - -static struct platform_driver pruss_driver = { - .probe = pruss_probe, - .remove = pruss_remove, - .driver = { - .name = DRV_NAME, - }, -}; - -module_platform_driver(pruss_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_VERSION(DRV_VERSION); -MODULE_AUTHOR("Amit Chatterjee "); -MODULE_AUTHOR("Pratheesh Gangadhar "); diff --git a/include/linux/platform_data/uio_pruss.h b/include/linux/platform_data/uio_pruss.h deleted file mode 100644 index f76fa393b802..000000000000 --- a/include/linux/platform_data/uio_pruss.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * include/linux/platform_data/uio_pruss.h - * - * Platform data for uio_pruss driver - * - * Copyright (C) 2010-11 Texas Instruments Incorporated - https://www.ti.com/ - */ - -#ifndef _UIO_PRUSS_H_ -#define _UIO_PRUSS_H_ - -/* To configure the PRUSS INTC base offset for UIO driver */ -struct uio_pruss_pdata { - u32 pintc_base; - struct gen_pool *sram_pool; -}; -#endif /* _UIO_PRUSS_H_ */ From 90fa0280553a87d0db51dd04f1aea1c2dde53c74 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 9 Apr 2024 11:40:48 +1200 Subject: [PATCH 119/319] uio_pdrv_genirq: convert to use device_property APIs Convert the uio_pdrv_genirq driver to use the device_property_* APIs instead of the of_property_* ones. This allows UIO interrupts to be defined via an ACPI overlay using the Device Tree namespace linkage. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20240408234050.2056374-2-chris.packham@alliedtelesis.co.nz Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_pdrv_genirq.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index 63258b6accc4..796f5be0a086 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -23,8 +23,8 @@ #include #include -#include -#include +#include +#include #define DRIVER_NAME "uio_pdrv_genirq" @@ -110,7 +110,7 @@ static void uio_pdrv_genirq_cleanup(void *data) static int uio_pdrv_genirq_probe(struct platform_device *pdev) { struct uio_info *uioinfo = dev_get_platdata(&pdev->dev); - struct device_node *node = pdev->dev.of_node; + struct fwnode_handle *node = dev_fwnode(&pdev->dev); struct uio_pdrv_genirq_platdata *priv; struct uio_mem *uiomem; int ret = -EINVAL; @@ -127,11 +127,11 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) return -ENOMEM; } - if (!of_property_read_string(node, "linux,uio-name", &name)) + if (!device_property_read_string(&pdev->dev, "linux,uio-name", &name)) uioinfo->name = devm_kstrdup(&pdev->dev, name, GFP_KERNEL); else uioinfo->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, - "%pOFn", node); + "%pfwP", node); uioinfo->version = "devicetree"; /* Multiple IRQs are not supported */ From f8a27dfa4b82d442af1c0645a5acc70cc97c67f6 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 9 Apr 2024 11:40:49 +1200 Subject: [PATCH 120/319] uio: use threaded interrupts Split the existing uio_interrupt into a hardirq handler and a thread function. The hardirq handler deals with the interrupt source in hardware, the thread function notifies userspace that there is an event to be handled. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20240408234050.2056374-3-chris.packham@alliedtelesis.co.nz Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 009158fef2a8..e815856eb46c 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -442,18 +442,27 @@ EXPORT_SYMBOL_GPL(uio_event_notify); * @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer * @dev_id: Pointer to the devices uio_device structure */ -static irqreturn_t uio_interrupt(int irq, void *dev_id) +static irqreturn_t uio_interrupt_handler(int irq, void *dev_id) { struct uio_device *idev = (struct uio_device *)dev_id; irqreturn_t ret; ret = idev->info->handler(irq, idev->info); if (ret == IRQ_HANDLED) - uio_event_notify(idev->info); + ret = IRQ_WAKE_THREAD; return ret; } +static irqreturn_t uio_interrupt_thread(int irq, void *dev_id) +{ + struct uio_device *idev = (struct uio_device *)dev_id; + + uio_event_notify(idev->info); + + return IRQ_HANDLED; +} + struct uio_listener { struct uio_device *dev; s32 event_count; @@ -1024,8 +1033,8 @@ int __uio_register_device(struct module *owner, * FDs at the time of unregister and therefore may not be * freed until they are released. */ - ret = request_irq(info->irq, uio_interrupt, - info->irq_flags, info->name, idev); + ret = request_threaded_irq(info->irq, uio_interrupt_handler, uio_interrupt_thread, + info->irq_flags, info->name, idev); if (ret) { info->uio_dev = NULL; goto err_request_irq; From a36b69775fcaab42262f6dbf288ccb6d470158ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 8 Mar 2024 09:51:22 +0100 Subject: [PATCH 121/319] ndtest: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Reviewed-by: Dave Jiang Reviewed-by: Dan Williams Link: https://lore.kernel.org/r/c04bfc941a9f5d249b049572c1ae122fe551ee5d.1709886922.git.u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- tools/testing/nvdimm/test/ndtest.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/testing/nvdimm/test/ndtest.c b/tools/testing/nvdimm/test/ndtest.c index b8419f460368..2c6285aae852 100644 --- a/tools/testing/nvdimm/test/ndtest.c +++ b/tools/testing/nvdimm/test/ndtest.c @@ -830,12 +830,11 @@ static int ndtest_bus_register(struct ndtest_priv *p) return 0; } -static int ndtest_remove(struct platform_device *pdev) +static void ndtest_remove(struct platform_device *pdev) { struct ndtest_priv *p = to_ndtest_priv(&pdev->dev); nvdimm_bus_unregister(p->bus); - return 0; } static int ndtest_probe(struct platform_device *pdev) @@ -882,7 +881,7 @@ static const struct platform_device_id ndtest_id[] = { static struct platform_driver ndtest_driver = { .probe = ndtest_probe, - .remove = ndtest_remove, + .remove_new = ndtest_remove, .driver = { .name = KBUILD_MODNAME, }, From e8c4bd6c6e6b7e7b416c42806981c2a81370001e Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Sat, 30 Mar 2024 01:51:57 -0700 Subject: [PATCH 122/319] Drivers: hv: vmbus: Add utility function for querying ring size Add a function to query for the preferred ring buffer size of VMBus device. This will allow the drivers (eg. UIO) to allocate the most optimized ring buffer size for devices. Signed-off-by: Saurabh Sengar Reviewed-by: Long Li Link: https://lore.kernel.org/r/1711788723-8593-2-git-send-email-ssengar@linux.microsoft.com Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 15 ++++++++++++--- drivers/hv/hyperv_vmbus.h | 5 +++++ include/linux/hyperv.h | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 2f4d09ce027a..3c6011a48dab 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -120,7 +120,9 @@ const struct vmbus_device vmbus_devs[] = { }, /* File copy */ - { .dev_type = HV_FCOPY, + /* fcopy always uses 16KB ring buffer size and is working well for last many years */ + { .pref_ring_size = 0x4000, + .dev_type = HV_FCOPY, HV_FCOPY_GUID, .perf_device = false, .allowed_in_isolated = false, @@ -140,12 +142,19 @@ const struct vmbus_device vmbus_devs[] = { .allowed_in_isolated = false, }, - /* Unknown GUID */ - { .dev_type = HV_UNKNOWN, + /* + * Unknown GUID + * 64 KB ring buffer + 4 KB header should be sufficient size for any Hyper-V device apart + * from HV_NIC and HV_SCSI. This case avoid the fallback for unknown devices to allocate + * much bigger (2 MB) of ring size. + */ + { .pref_ring_size = 0x11000, + .dev_type = HV_UNKNOWN, .perf_device = false, .allowed_in_isolated = false, }, }; +EXPORT_SYMBOL_GPL(vmbus_devs); static const struct { guid_t guid; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index f6b1e710f805..76ac5185a01a 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -417,6 +417,11 @@ static inline bool hv_is_perf_channel(struct vmbus_channel *channel) return vmbus_devs[channel->device_id].perf_device; } +static inline size_t hv_dev_ring_size(struct vmbus_channel *channel) +{ + return vmbus_devs[channel->device_id].pref_ring_size; +} + static inline bool hv_is_allocated_cpu(unsigned int cpu) { struct vmbus_channel *channel, *sc; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 6ef0557b4bff..7de9f90d3f95 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -820,6 +820,8 @@ struct vmbus_requestor { #define VMBUS_RQST_RESET (U64_MAX - 3) struct vmbus_device { + /* preferred ring buffer size in KB, 0 means no preferred size for this device */ + size_t pref_ring_size; u16 dev_type; guid_t guid; bool perf_device; From e566ed5b64177a0c07b677568f623ed31d23406d Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Sat, 30 Mar 2024 01:51:58 -0700 Subject: [PATCH 123/319] uio_hv_generic: Query the ringbuffer size for device Query the ring buffer size from pre defined table per device and use that value for allocating the ring buffer for that device. Keep the size as current default which is 2 MB if the device doesn't have any preferred ring size. Signed-off-by: Saurabh Sengar Reviewed-by: Long Li Link: https://lore.kernel.org/r/1711788723-8593-3-git-send-email-ssengar@linux.microsoft.com Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_hv_generic.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index 20d9762331bd..4bda6b52e49e 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -238,6 +238,7 @@ hv_uio_probe(struct hv_device *dev, struct hv_uio_private_data *pdata; void *ring_buffer; int ret; + size_t ring_size = hv_dev_ring_size(channel); /* Communicating with host has to be via shared memory not hypercall */ if (!channel->offermsg.monitor_allocated) { @@ -245,12 +246,14 @@ hv_uio_probe(struct hv_device *dev, return -ENOTSUPP; } + if (!ring_size) + ring_size = HV_RING_SIZE * PAGE_SIZE; + pdata = devm_kzalloc(&dev->device, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; - ret = vmbus_alloc_ring(channel, HV_RING_SIZE * PAGE_SIZE, - HV_RING_SIZE * PAGE_SIZE); + ret = vmbus_alloc_ring(channel, ring_size, ring_size); if (ret) return ret; From 547fa4ffd799ad48bf96e60efd24985adeec35de Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Sat, 30 Mar 2024 01:51:59 -0700 Subject: [PATCH 124/319] uio_hv_generic: Enable interrupt for low speed VMBus devices Hyper-V is adding some "specialty" synthetic devices. Instead of writing new kernel-level VMBus drivers for these devices, the devices will be presented to user space via this existing Hyper-V generic UIO driver, so that a user space driver can handle the device. Since these new synthetic devices are low speed devices, they don't support monitor bits and we must use vmbus_setevent() to enable interrupts from the host. Signed-off-by: Saurabh Sengar Reviewed-by: Long Li Link: https://lore.kernel.org/r/1711788723-8593-4-git-send-email-ssengar@linux.microsoft.com Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_hv_generic.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index 4bda6b52e49e..289611c7dfd7 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -84,6 +84,9 @@ hv_uio_irqcontrol(struct uio_info *info, s32 irq_state) dev->channel->inbound.ring_buffer->interrupt_mask = !irq_state; virt_mb(); + if (!dev->channel->offermsg.monitor_allocated && irq_state) + vmbus_setevent(dev->channel); + return 0; } @@ -240,12 +243,6 @@ hv_uio_probe(struct hv_device *dev, int ret; size_t ring_size = hv_dev_ring_size(channel); - /* Communicating with host has to be via shared memory not hypercall */ - if (!channel->offermsg.monitor_allocated) { - dev_err(&dev->device, "vmbus channel requires hypercall\n"); - return -ENOTSUPP; - } - if (!ring_size) ring_size = HV_RING_SIZE * PAGE_SIZE; From 45bab4d746510ac2bdd9102bf152db617ef96a6b Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Sat, 30 Mar 2024 01:52:00 -0700 Subject: [PATCH 125/319] tools: hv: Add vmbus_bufring Common userspace interface for read/write from VMBus ringbuffer. This implementation is open for use by any userspace driver or application seeking direct control over VMBus ring buffers. A significant part of this code is borrowed from DPDK. Link: https://github.com/DPDK/dpdk/ Currently this library is not supported for ARM64. Signed-off-by: Mary Hardy Signed-off-by: Saurabh Sengar Reviewed-by: Long Li Link: https://lore.kernel.org/r/1711788723-8593-5-git-send-email-ssengar@linux.microsoft.com Signed-off-by: Greg Kroah-Hartman --- tools/hv/vmbus_bufring.c | 318 +++++++++++++++++++++++++++++++++++++++ tools/hv/vmbus_bufring.h | 158 +++++++++++++++++++ 2 files changed, 476 insertions(+) create mode 100644 tools/hv/vmbus_bufring.c create mode 100644 tools/hv/vmbus_bufring.h diff --git a/tools/hv/vmbus_bufring.c b/tools/hv/vmbus_bufring.c new file mode 100644 index 000000000000..bac32c1109df --- /dev/null +++ b/tools/hv/vmbus_bufring.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2009-2012,2016,2023 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vmbus_bufring.h" + +/** + * Compiler barrier. + * + * Guarantees that operation reordering does not occur at compile time + * for operations directly before and after the barrier. + */ +#define rte_compiler_barrier() ({ asm volatile ("" : : : "memory"); }) + +#define VMBUS_RQST_ERROR 0xFFFFFFFFFFFFFFFF +#define ALIGN(val, align) ((typeof(val))((val) & (~((typeof(val))((align) - 1))))) + +void *vmbus_uio_map(int *fd, int size) +{ + void *map; + + map = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0); + if (map == MAP_FAILED) + return NULL; + + return map; +} + +/* Increase bufring index by inc with wraparound */ +static inline uint32_t vmbus_br_idxinc(uint32_t idx, uint32_t inc, uint32_t sz) +{ + idx += inc; + if (idx >= sz) + idx -= sz; + + return idx; +} + +void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen) +{ + br->vbr = buf; + br->windex = br->vbr->windex; + br->dsize = blen - sizeof(struct vmbus_bufring); +} + +static inline __always_inline void +rte_smp_mb(void) +{ + asm volatile("lock addl $0, -128(%%rsp); " ::: "memory"); +} + +static inline int +rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src) +{ + uint8_t res; + + asm volatile("lock ; " + "cmpxchgl %[src], %[dst];" + "sete %[res];" + : [res] "=a" (res), /* output */ + [dst] "=m" (*dst) + : [src] "r" (src), /* input */ + "a" (exp), + "m" (*dst) + : "memory"); /* no-clobber list */ + return res; +} + +static inline uint32_t +vmbus_txbr_copyto(const struct vmbus_br *tbr, uint32_t windex, + const void *src0, uint32_t cplen) +{ + uint8_t *br_data = tbr->vbr->data; + uint32_t br_dsize = tbr->dsize; + const uint8_t *src = src0; + + /* XXX use double mapping like Linux kernel? */ + if (cplen > br_dsize - windex) { + uint32_t fraglen = br_dsize - windex; + + /* Wrap-around detected */ + memcpy(br_data + windex, src, fraglen); + memcpy(br_data, src + fraglen, cplen - fraglen); + } else { + memcpy(br_data + windex, src, cplen); + } + + return vmbus_br_idxinc(windex, cplen, br_dsize); +} + +/* + * Write scattered channel packet to TX bufring. + * + * The offset of this channel packet is written as a 64bits value + * immediately after this channel packet. + * + * The write goes through three stages: + * 1. Reserve space in ring buffer for the new data. + * Writer atomically moves priv_write_index. + * 2. Copy the new data into the ring. + * 3. Update the tail of the ring (visible to host) that indicates + * next read location. Writer updates write_index + */ +static int +vmbus_txbr_write(struct vmbus_br *tbr, const struct iovec iov[], int iovlen) +{ + struct vmbus_bufring *vbr = tbr->vbr; + uint32_t ring_size = tbr->dsize; + uint32_t old_windex, next_windex, windex, total; + uint64_t save_windex; + int i; + + total = 0; + for (i = 0; i < iovlen; i++) + total += iov[i].iov_len; + total += sizeof(save_windex); + + /* Reserve space in ring */ + do { + uint32_t avail; + + /* Get current free location */ + old_windex = tbr->windex; + + /* Prevent compiler reordering this with calculation */ + rte_compiler_barrier(); + + avail = vmbus_br_availwrite(tbr, old_windex); + + /* If not enough space in ring, then tell caller. */ + if (avail <= total) + return -EAGAIN; + + next_windex = vmbus_br_idxinc(old_windex, total, ring_size); + + /* Atomic update of next write_index for other threads */ + } while (!rte_atomic32_cmpset(&tbr->windex, old_windex, next_windex)); + + /* Space from old..new is now reserved */ + windex = old_windex; + for (i = 0; i < iovlen; i++) + windex = vmbus_txbr_copyto(tbr, windex, iov[i].iov_base, iov[i].iov_len); + + /* Set the offset of the current channel packet. */ + save_windex = ((uint64_t)old_windex) << 32; + windex = vmbus_txbr_copyto(tbr, windex, &save_windex, + sizeof(save_windex)); + + /* The region reserved should match region used */ + if (windex != next_windex) + return -EINVAL; + + /* Ensure that data is available before updating host index */ + rte_compiler_barrier(); + + /* Checkin for our reservation. wait for our turn to update host */ + while (!rte_atomic32_cmpset(&vbr->windex, old_windex, next_windex)) + _mm_pause(); + + return 0; +} + +int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data, + uint32_t dlen, uint32_t flags) +{ + struct vmbus_chanpkt pkt; + unsigned int pktlen, pad_pktlen; + const uint32_t hlen = sizeof(pkt); + uint64_t pad = 0; + struct iovec iov[3]; + int error; + + pktlen = hlen + dlen; + pad_pktlen = ALIGN(pktlen, sizeof(uint64_t)); + + pkt.hdr.type = type; + pkt.hdr.flags = flags; + pkt.hdr.hlen = hlen >> VMBUS_CHANPKT_SIZE_SHIFT; + pkt.hdr.tlen = pad_pktlen >> VMBUS_CHANPKT_SIZE_SHIFT; + pkt.hdr.xactid = VMBUS_RQST_ERROR; + + iov[0].iov_base = &pkt; + iov[0].iov_len = hlen; + iov[1].iov_base = data; + iov[1].iov_len = dlen; + iov[2].iov_base = &pad; + iov[2].iov_len = pad_pktlen - pktlen; + + error = vmbus_txbr_write(txbr, iov, 3); + + return error; +} + +static inline uint32_t +vmbus_rxbr_copyfrom(const struct vmbus_br *rbr, uint32_t rindex, + void *dst0, size_t cplen) +{ + const uint8_t *br_data = rbr->vbr->data; + uint32_t br_dsize = rbr->dsize; + uint8_t *dst = dst0; + + if (cplen > br_dsize - rindex) { + uint32_t fraglen = br_dsize - rindex; + + /* Wrap-around detected. */ + memcpy(dst, br_data + rindex, fraglen); + memcpy(dst + fraglen, br_data, cplen - fraglen); + } else { + memcpy(dst, br_data + rindex, cplen); + } + + return vmbus_br_idxinc(rindex, cplen, br_dsize); +} + +/* Copy data from receive ring but don't change index */ +static int +vmbus_rxbr_peek(const struct vmbus_br *rbr, void *data, size_t dlen) +{ + uint32_t avail; + + /* + * The requested data and the 64bits channel packet + * offset should be there at least. + */ + avail = vmbus_br_availread(rbr); + if (avail < dlen + sizeof(uint64_t)) + return -EAGAIN; + + vmbus_rxbr_copyfrom(rbr, rbr->vbr->rindex, data, dlen); + return 0; +} + +/* + * Copy data from receive ring and change index + * NOTE: + * We assume (dlen + skip) == sizeof(channel packet). + */ +static int +vmbus_rxbr_read(struct vmbus_br *rbr, void *data, size_t dlen, size_t skip) +{ + struct vmbus_bufring *vbr = rbr->vbr; + uint32_t br_dsize = rbr->dsize; + uint32_t rindex; + + if (vmbus_br_availread(rbr) < dlen + skip + sizeof(uint64_t)) + return -EAGAIN; + + /* Record where host was when we started read (for debug) */ + rbr->windex = rbr->vbr->windex; + + /* + * Copy channel packet from RX bufring. + */ + rindex = vmbus_br_idxinc(rbr->vbr->rindex, skip, br_dsize); + rindex = vmbus_rxbr_copyfrom(rbr, rindex, data, dlen); + + /* + * Discard this channel packet's 64bits offset, which is useless to us. + */ + rindex = vmbus_br_idxinc(rindex, sizeof(uint64_t), br_dsize); + + /* Update the read index _after_ the channel packet is fetched. */ + rte_compiler_barrier(); + + vbr->rindex = rindex; + + return 0; +} + +int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr, + void *data, uint32_t *len) +{ + struct vmbus_chanpkt_hdr pkt; + uint32_t dlen, bufferlen = *len; + int error; + + error = vmbus_rxbr_peek(rxbr, &pkt, sizeof(pkt)); + if (error) + return error; + + if (unlikely(pkt.hlen < VMBUS_CHANPKT_HLEN_MIN)) + /* XXX this channel is dead actually. */ + return -EIO; + + if (unlikely(pkt.hlen > pkt.tlen)) + return -EIO; + + /* Length are in quad words */ + dlen = pkt.tlen << VMBUS_CHANPKT_SIZE_SHIFT; + *len = dlen; + + /* If caller buffer is not large enough */ + if (unlikely(dlen > bufferlen)) + return -ENOBUFS; + + /* Read data and skip packet header */ + error = vmbus_rxbr_read(rxbr, data, dlen, 0); + if (error) + return error; + + /* Return the number of bytes read */ + return dlen + sizeof(uint64_t); +} diff --git a/tools/hv/vmbus_bufring.h b/tools/hv/vmbus_bufring.h new file mode 100644 index 000000000000..6e7caacfff57 --- /dev/null +++ b/tools/hv/vmbus_bufring.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef _VMBUS_BUF_H_ +#define _VMBUS_BUF_H_ + +#include +#include + +#define __packed __attribute__((__packed__)) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#define ICMSGHDRFLAG_TRANSACTION 1 +#define ICMSGHDRFLAG_REQUEST 2 +#define ICMSGHDRFLAG_RESPONSE 4 + +#define IC_VERSION_NEGOTIATION_MAX_VER_COUNT 100 +#define ICMSG_HDR (sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr)) +#define ICMSG_NEGOTIATE_PKT_SIZE(icframe_vercnt, icmsg_vercnt) \ + (ICMSG_HDR + sizeof(struct icmsg_negotiate) + \ + (((icframe_vercnt) + (icmsg_vercnt)) * sizeof(struct ic_version))) + +/* + * Channel packets + */ + +/* Channel packet flags */ +#define VMBUS_CHANPKT_TYPE_INBAND 0x0006 +#define VMBUS_CHANPKT_TYPE_RXBUF 0x0007 +#define VMBUS_CHANPKT_TYPE_GPA 0x0009 +#define VMBUS_CHANPKT_TYPE_COMP 0x000b + +#define VMBUS_CHANPKT_FLAG_NONE 0 +#define VMBUS_CHANPKT_FLAG_RC 0x0001 /* report completion */ + +#define VMBUS_CHANPKT_SIZE_SHIFT 3 +#define VMBUS_CHANPKT_SIZE_ALIGN BIT(VMBUS_CHANPKT_SIZE_SHIFT) +#define VMBUS_CHANPKT_HLEN_MIN \ + (sizeof(struct vmbus_chanpkt_hdr) >> VMBUS_CHANPKT_SIZE_SHIFT) + +/* + * Buffer ring + */ +struct vmbus_bufring { + volatile uint32_t windex; + volatile uint32_t rindex; + + /* + * Interrupt mask {0,1} + * + * For TX bufring, host set this to 1, when it is processing + * the TX bufring, so that we can safely skip the TX event + * notification to host. + * + * For RX bufring, once this is set to 1 by us, host will not + * further dispatch interrupts to us, even if there are data + * pending on the RX bufring. This effectively disables the + * interrupt of the channel to which this RX bufring is attached. + */ + volatile uint32_t imask; + + /* + * Win8 uses some of the reserved bits to implement + * interrupt driven flow management. On the send side + * we can request that the receiver interrupt the sender + * when the ring transitions from being full to being able + * to handle a message of size "pending_send_sz". + * + * Add necessary state for this enhancement. + */ + volatile uint32_t pending_send; + uint32_t reserved1[12]; + + union { + struct { + uint32_t feat_pending_send_sz:1; + }; + uint32_t value; + } feature_bits; + + /* Pad it to rte_mem_page_size() so that data starts on page boundary */ + uint8_t reserved2[4028]; + + /* + * Ring data starts here + RingDataStartOffset + * !!! DO NOT place any fields below this !!! + */ + uint8_t data[]; +} __packed; + +struct vmbus_br { + struct vmbus_bufring *vbr; + uint32_t dsize; + uint32_t windex; /* next available location */ +}; + +struct vmbus_chanpkt_hdr { + uint16_t type; /* VMBUS_CHANPKT_TYPE_ */ + uint16_t hlen; /* header len, in 8 bytes */ + uint16_t tlen; /* total len, in 8 bytes */ + uint16_t flags; /* VMBUS_CHANPKT_FLAG_ */ + uint64_t xactid; +} __packed; + +struct vmbus_chanpkt { + struct vmbus_chanpkt_hdr hdr; +} __packed; + +struct vmbuspipe_hdr { + unsigned int flags; + unsigned int msgsize; +} __packed; + +struct ic_version { + unsigned short major; + unsigned short minor; +} __packed; + +struct icmsg_negotiate { + unsigned short icframe_vercnt; + unsigned short icmsg_vercnt; + unsigned int reserved; + struct ic_version icversion_data[]; /* any size array */ +} __packed; + +struct icmsg_hdr { + struct ic_version icverframe; + unsigned short icmsgtype; + struct ic_version icvermsg; + unsigned short icmsgsize; + unsigned int status; + unsigned char ictransaction_id; + unsigned char icflags; + unsigned char reserved[2]; +} __packed; + +int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr, void *data, uint32_t *len); +int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data, + uint32_t dlen, uint32_t flags); +void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen); +void *vmbus_uio_map(int *fd, int size); + +/* Amount of space available for write */ +static inline uint32_t vmbus_br_availwrite(const struct vmbus_br *br, uint32_t windex) +{ + uint32_t rindex = br->vbr->rindex; + + if (windex >= rindex) + return br->dsize - (windex - rindex); + else + return rindex - windex; +} + +static inline uint32_t vmbus_br_availread(const struct vmbus_br *br) +{ + return br->dsize - vmbus_br_availwrite(br, br->vbr->windex); +} + +#endif /* !_VMBUS_BUF_H_ */ From 82b0945ce2c2d636d5e893ad50210875c929f257 Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Sat, 30 Mar 2024 01:52:01 -0700 Subject: [PATCH 126/319] tools: hv: Add new fcopy application based on uio driver New fcopy application using uio_hv_generic driver. This application copies file from Hyper-V host to guest VM. A big part of this code is copied from tools/hv/hv_fcopy_daemon.c which this new application is replacing. Signed-off-by: Saurabh Sengar Reviewed-by: Long Li Link: https://lore.kernel.org/r/1711788723-8593-6-git-send-email-ssengar@linux.microsoft.com Signed-off-by: Greg Kroah-Hartman --- tools/hv/Build | 3 +- tools/hv/Makefile | 14 +- tools/hv/hv_fcopy_uio_daemon.c | 490 +++++++++++++++++++++++++++++++++ 3 files changed, 501 insertions(+), 6 deletions(-) create mode 100644 tools/hv/hv_fcopy_uio_daemon.c diff --git a/tools/hv/Build b/tools/hv/Build index 6cf51fa4b306..7d1f1698069b 100644 --- a/tools/hv/Build +++ b/tools/hv/Build @@ -1,3 +1,4 @@ hv_kvp_daemon-y += hv_kvp_daemon.o hv_vss_daemon-y += hv_vss_daemon.o -hv_fcopy_daemon-y += hv_fcopy_daemon.o +hv_fcopy_uio_daemon-y += hv_fcopy_uio_daemon.o +hv_fcopy_uio_daemon-y += vmbus_bufring.o diff --git a/tools/hv/Makefile b/tools/hv/Makefile index fe770e679ae8..bb52871da341 100644 --- a/tools/hv/Makefile +++ b/tools/hv/Makefile @@ -2,6 +2,7 @@ # Makefile for Hyper-V tools include ../scripts/Makefile.include +ARCH := $(shell uname -m 2>/dev/null) sbindir ?= /usr/sbin libexecdir ?= /usr/libexec sharedstatedir ?= /var/lib @@ -17,7 +18,10 @@ MAKEFLAGS += -r override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include -ALL_TARGETS := hv_kvp_daemon hv_vss_daemon hv_fcopy_daemon +ALL_TARGETS := hv_kvp_daemon hv_vss_daemon +ifneq ($(ARCH), aarch64) +ALL_TARGETS += hv_fcopy_uio_daemon +endif ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) ALL_SCRIPTS := hv_get_dhcp_info.sh hv_get_dns_info.sh hv_set_ifconfig.sh @@ -39,10 +43,10 @@ $(HV_VSS_DAEMON_IN): FORCE $(OUTPUT)hv_vss_daemon: $(HV_VSS_DAEMON_IN) $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ -HV_FCOPY_DAEMON_IN := $(OUTPUT)hv_fcopy_daemon-in.o -$(HV_FCOPY_DAEMON_IN): FORCE - $(Q)$(MAKE) $(build)=hv_fcopy_daemon -$(OUTPUT)hv_fcopy_daemon: $(HV_FCOPY_DAEMON_IN) +HV_FCOPY_UIO_DAEMON_IN := $(OUTPUT)hv_fcopy_uio_daemon-in.o +$(HV_FCOPY_UIO_DAEMON_IN): FORCE + $(Q)$(MAKE) $(build)=hv_fcopy_uio_daemon +$(OUTPUT)hv_fcopy_uio_daemon: $(HV_FCOPY_UIO_DAEMON_IN) $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ clean: diff --git a/tools/hv/hv_fcopy_uio_daemon.c b/tools/hv/hv_fcopy_uio_daemon.c new file mode 100644 index 000000000000..3ce316cc9f97 --- /dev/null +++ b/tools/hv/hv_fcopy_uio_daemon.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * An implementation of host to guest copy functionality for Linux. + * + * Copyright (C) 2023, Microsoft, Inc. + * + * Author : K. Y. Srinivasan + * Author : Saurabh Sengar + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vmbus_bufring.h" + +#define ICMSGTYPE_NEGOTIATE 0 +#define ICMSGTYPE_FCOPY 7 + +#define WIN8_SRV_MAJOR 1 +#define WIN8_SRV_MINOR 1 +#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR) + +#define MAX_FOLDER_NAME 15 +#define MAX_PATH_LEN 15 +#define FCOPY_UIO "/sys/bus/vmbus/devices/eb765408-105f-49b6-b4aa-c123b64d17d4/uio" + +#define FCOPY_VER_COUNT 1 +static const int fcopy_versions[] = { + WIN8_SRV_VERSION +}; + +#define FW_VER_COUNT 1 +static const int fw_versions[] = { + UTIL_FW_VERSION +}; + +#define HV_RING_SIZE 0x4000 /* 16KB ring buffer size */ + +unsigned char desc[HV_RING_SIZE]; + +static int target_fd; +static char target_fname[PATH_MAX]; +static unsigned long long filesize; + +static int hv_fcopy_create_file(char *file_name, char *path_name, __u32 flags) +{ + int error = HV_E_FAIL; + char *q, *p; + + filesize = 0; + p = path_name; + snprintf(target_fname, sizeof(target_fname), "%s/%s", + path_name, file_name); + + /* + * Check to see if the path is already in place; if not, + * create if required. + */ + while ((q = strchr(p, '/')) != NULL) { + if (q == p) { + p++; + continue; + } + *q = '\0'; + if (access(path_name, F_OK)) { + if (flags & CREATE_PATH) { + if (mkdir(path_name, 0755)) { + syslog(LOG_ERR, "Failed to create %s", + path_name); + goto done; + } + } else { + syslog(LOG_ERR, "Invalid path: %s", path_name); + goto done; + } + } + p = q + 1; + *q = '/'; + } + + if (!access(target_fname, F_OK)) { + syslog(LOG_INFO, "File: %s exists", target_fname); + if (!(flags & OVER_WRITE)) { + error = HV_ERROR_ALREADY_EXISTS; + goto done; + } + } + + target_fd = open(target_fname, + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744); + if (target_fd == -1) { + syslog(LOG_INFO, "Open Failed: %s", strerror(errno)); + goto done; + } + + error = 0; +done: + if (error) + target_fname[0] = '\0'; + return error; +} + +/* copy the data into the file */ +static int hv_copy_data(struct hv_do_fcopy *cpmsg) +{ + ssize_t len; + int ret = 0; + + len = pwrite(target_fd, cpmsg->data, cpmsg->size, cpmsg->offset); + + filesize += cpmsg->size; + if (len != cpmsg->size) { + switch (errno) { + case ENOSPC: + ret = HV_ERROR_DISK_FULL; + break; + default: + ret = HV_E_FAIL; + break; + } + syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)", + filesize, (long)len, strerror(errno)); + } + + return ret; +} + +static int hv_copy_finished(void) +{ + close(target_fd); + target_fname[0] = '\0'; + + return 0; +} + +static void print_usage(char *argv[]) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options are:\n" + " -n, --no-daemon stay in foreground, don't daemonize\n" + " -h, --help print this help\n", argv[0]); +} + +static bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, unsigned char *buf, + unsigned int buflen, const int *fw_version, int fw_vercnt, + const int *srv_version, int srv_vercnt, + int *nego_fw_version, int *nego_srv_version) +{ + int icframe_major, icframe_minor; + int icmsg_major, icmsg_minor; + int fw_major, fw_minor; + int srv_major, srv_minor; + int i, j; + bool found_match = false; + struct icmsg_negotiate *negop; + + /* Check that there's enough space for icframe_vercnt, icmsg_vercnt */ + if (buflen < ICMSG_HDR + offsetof(struct icmsg_negotiate, reserved)) { + syslog(LOG_ERR, "Invalid icmsg negotiate"); + return false; + } + + icmsghdrp->icmsgsize = 0x10; + negop = (struct icmsg_negotiate *)&buf[ICMSG_HDR]; + + icframe_major = negop->icframe_vercnt; + icframe_minor = 0; + + icmsg_major = negop->icmsg_vercnt; + icmsg_minor = 0; + + /* Validate negop packet */ + if (icframe_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || + icmsg_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || + ICMSG_NEGOTIATE_PKT_SIZE(icframe_major, icmsg_major) > buflen) { + syslog(LOG_ERR, "Invalid icmsg negotiate - icframe_major: %u, icmsg_major: %u\n", + icframe_major, icmsg_major); + goto fw_error; + } + + /* + * Select the framework version number we will + * support. + */ + + for (i = 0; i < fw_vercnt; i++) { + fw_major = (fw_version[i] >> 16); + fw_minor = (fw_version[i] & 0xFFFF); + + for (j = 0; j < negop->icframe_vercnt; j++) { + if (negop->icversion_data[j].major == fw_major && + negop->icversion_data[j].minor == fw_minor) { + icframe_major = negop->icversion_data[j].major; + icframe_minor = negop->icversion_data[j].minor; + found_match = true; + break; + } + } + + if (found_match) + break; + } + + if (!found_match) + goto fw_error; + + found_match = false; + + for (i = 0; i < srv_vercnt; i++) { + srv_major = (srv_version[i] >> 16); + srv_minor = (srv_version[i] & 0xFFFF); + + for (j = negop->icframe_vercnt; + (j < negop->icframe_vercnt + negop->icmsg_vercnt); + j++) { + if (negop->icversion_data[j].major == srv_major && + negop->icversion_data[j].minor == srv_minor) { + icmsg_major = negop->icversion_data[j].major; + icmsg_minor = negop->icversion_data[j].minor; + found_match = true; + break; + } + } + + if (found_match) + break; + } + + /* + * Respond with the framework and service + * version numbers we can support. + */ +fw_error: + if (!found_match) { + negop->icframe_vercnt = 0; + negop->icmsg_vercnt = 0; + } else { + negop->icframe_vercnt = 1; + negop->icmsg_vercnt = 1; + } + + if (nego_fw_version) + *nego_fw_version = (icframe_major << 16) | icframe_minor; + + if (nego_srv_version) + *nego_srv_version = (icmsg_major << 16) | icmsg_minor; + + negop->icversion_data[0].major = icframe_major; + negop->icversion_data[0].minor = icframe_minor; + negop->icversion_data[1].major = icmsg_major; + negop->icversion_data[1].minor = icmsg_minor; + + return found_match; +} + +static void wcstoutf8(char *dest, const __u16 *src, size_t dest_size) +{ + size_t len = 0; + + while (len < dest_size) { + if (src[len] < 0x80) + dest[len++] = (char)(*src++); + else + dest[len++] = 'X'; + } + + dest[len] = '\0'; +} + +static int hv_fcopy_start(struct hv_start_fcopy *smsg_in) +{ + setlocale(LC_ALL, "en_US.utf8"); + size_t file_size, path_size; + char *file_name, *path_name; + char *in_file_name = (char *)smsg_in->file_name; + char *in_path_name = (char *)smsg_in->path_name; + + file_size = wcstombs(NULL, (const wchar_t *restrict)in_file_name, 0) + 1; + path_size = wcstombs(NULL, (const wchar_t *restrict)in_path_name, 0) + 1; + + file_name = (char *)malloc(file_size * sizeof(char)); + path_name = (char *)malloc(path_size * sizeof(char)); + + wcstoutf8(file_name, (__u16 *)in_file_name, file_size); + wcstoutf8(path_name, (__u16 *)in_path_name, path_size); + + return hv_fcopy_create_file(file_name, path_name, smsg_in->copy_flags); +} + +static int hv_fcopy_send_data(struct hv_fcopy_hdr *fcopy_msg, int recvlen) +{ + int operation = fcopy_msg->operation; + + /* + * The strings sent from the host are encoded in + * utf16; convert it to utf8 strings. + * The host assures us that the utf16 strings will not exceed + * the max lengths specified. We will however, reserve room + * for the string terminating character - in the utf16s_utf8s() + * function we limit the size of the buffer where the converted + * string is placed to W_MAX_PATH -1 to guarantee + * that the strings can be properly terminated! + */ + + switch (operation) { + case START_FILE_COPY: + return hv_fcopy_start((struct hv_start_fcopy *)fcopy_msg); + case WRITE_TO_FILE: + return hv_copy_data((struct hv_do_fcopy *)fcopy_msg); + case COMPLETE_FCOPY: + return hv_copy_finished(); + } + + return HV_E_FAIL; +} + +/* process the packet recv from host */ +static int fcopy_pkt_process(struct vmbus_br *txbr) +{ + int ret, offset, pktlen; + int fcopy_srv_version; + const struct vmbus_chanpkt_hdr *pkt; + struct hv_fcopy_hdr *fcopy_msg; + struct icmsg_hdr *icmsghdr; + + pkt = (const struct vmbus_chanpkt_hdr *)desc; + offset = pkt->hlen << 3; + pktlen = (pkt->tlen << 3) - offset; + icmsghdr = (struct icmsg_hdr *)&desc[offset + sizeof(struct vmbuspipe_hdr)]; + icmsghdr->status = HV_E_FAIL; + + if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) { + if (vmbus_prep_negotiate_resp(icmsghdr, desc + offset, pktlen, fw_versions, + FW_VER_COUNT, fcopy_versions, FCOPY_VER_COUNT, + NULL, &fcopy_srv_version)) { + syslog(LOG_INFO, "FCopy IC version %d.%d", + fcopy_srv_version >> 16, fcopy_srv_version & 0xFFFF); + icmsghdr->status = 0; + } + } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) { + /* Ensure recvlen is big enough to contain hv_fcopy_hdr */ + if (pktlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) { + syslog(LOG_ERR, "Invalid Fcopy hdr. Packet length too small: %u", + pktlen); + return -ENOBUFS; + } + + fcopy_msg = (struct hv_fcopy_hdr *)&desc[offset + ICMSG_HDR]; + icmsghdr->status = hv_fcopy_send_data(fcopy_msg, pktlen); + } + + icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE; + ret = rte_vmbus_chan_send(txbr, 0x6, desc + offset, pktlen, 0); + if (ret) { + syslog(LOG_ERR, "Write to ringbuffer failed err: %d", ret); + return ret; + } + + return 0; +} + +static void fcopy_get_first_folder(char *path, char *chan_no) +{ + DIR *dir = opendir(path); + struct dirent *entry; + + if (!dir) { + syslog(LOG_ERR, "Failed to open directory (errno=%s).\n", strerror(errno)); + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") != 0 && + strcmp(entry->d_name, "..") != 0) { + strcpy(chan_no, entry->d_name); + break; + } + } + + closedir(dir); +} + +int main(int argc, char *argv[]) +{ + int fcopy_fd = -1, tmp = 1; + int daemonize = 1, long_index = 0, opt, ret = -EINVAL; + struct vmbus_br txbr, rxbr; + void *ring; + uint32_t len = HV_RING_SIZE; + char uio_name[MAX_FOLDER_NAME] = {0}; + char uio_dev_path[MAX_PATH_LEN] = {0}; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h' }, + {"no-daemon", no_argument, 0, 'n' }, + {0, 0, 0, 0 } + }; + + while ((opt = getopt_long(argc, argv, "hn", long_options, + &long_index)) != -1) { + switch (opt) { + case 'n': + daemonize = 0; + break; + case 'h': + default: + print_usage(argv); + goto exit; + } + } + + if (daemonize && daemon(1, 0)) { + syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno)); + goto exit; + } + + openlog("HV_UIO_FCOPY", 0, LOG_USER); + syslog(LOG_INFO, "starting; pid is:%d", getpid()); + + fcopy_get_first_folder(FCOPY_UIO, uio_name); + snprintf(uio_dev_path, sizeof(uio_dev_path), "/dev/%s", uio_name); + fcopy_fd = open(uio_dev_path, O_RDWR); + + if (fcopy_fd < 0) { + syslog(LOG_ERR, "open %s failed; error: %d %s", + uio_dev_path, errno, strerror(errno)); + ret = fcopy_fd; + goto exit; + } + + ring = vmbus_uio_map(&fcopy_fd, HV_RING_SIZE); + if (!ring) { + ret = errno; + syslog(LOG_ERR, "mmap ringbuffer failed; error: %d %s", ret, strerror(ret)); + goto close; + } + vmbus_br_setup(&txbr, ring, HV_RING_SIZE); + vmbus_br_setup(&rxbr, (char *)ring + HV_RING_SIZE, HV_RING_SIZE); + + rxbr.vbr->imask = 0; + + while (1) { + /* + * In this loop we process fcopy messages after the + * handshake is complete. + */ + ret = pread(fcopy_fd, &tmp, sizeof(int), 0); + if (ret < 0) { + syslog(LOG_ERR, "pread failed: %s", strerror(errno)); + continue; + } + + len = HV_RING_SIZE; + ret = rte_vmbus_chan_recv_raw(&rxbr, desc, &len); + if (unlikely(ret <= 0)) { + /* This indicates a failure to communicate (or worse) */ + syslog(LOG_ERR, "VMBus channel recv error: %d", ret); + } else { + ret = fcopy_pkt_process(&txbr); + if (ret < 0) + goto close; + + /* Signal host */ + if ((write(fcopy_fd, &tmp, sizeof(int))) != sizeof(int)) { + ret = errno; + syslog(LOG_ERR, "Signal to host failed: %s\n", strerror(ret)); + goto close; + } + } + } +close: + close(fcopy_fd); +exit: + return ret; +} From ec314f61e4fc2d3dd6ea78aa18a5ac276eb1a8e3 Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Sat, 30 Mar 2024 01:52:02 -0700 Subject: [PATCH 127/319] Drivers: hv: Remove fcopy driver As the new fcopy driver using uio is introduced, remove obsolete driver and application. Signed-off-by: Saurabh Sengar Reviewed-by: Long Li Link: https://lore.kernel.org/r/1711788723-8593-7-git-send-email-ssengar@linux.microsoft.com Signed-off-by: Greg Kroah-Hartman --- drivers/hv/Makefile | 2 +- drivers/hv/hv_fcopy.c | 427 ------------------------------------- drivers/hv/hv_util.c | 12 -- tools/hv/hv_fcopy_daemon.c | 266 ----------------------- 4 files changed, 1 insertion(+), 706 deletions(-) delete mode 100644 drivers/hv/hv_fcopy.c delete mode 100644 tools/hv/hv_fcopy_daemon.c diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile index d76df5c8c2a9..b992c0ed182b 100644 --- a/drivers/hv/Makefile +++ b/drivers/hv/Makefile @@ -10,7 +10,7 @@ hv_vmbus-y := vmbus_drv.o \ hv.o connection.o channel.o \ channel_mgmt.o ring_buffer.o hv_trace.o hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o -hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o hv_utils_transport.o +hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o # Code that must be built-in obj-$(subst m,y,$(CONFIG_HYPERV)) += hv_common.o diff --git a/drivers/hv/hv_fcopy.c b/drivers/hv/hv_fcopy.c deleted file mode 100644 index 922d83eb7ddf..000000000000 --- a/drivers/hv/hv_fcopy.c +++ /dev/null @@ -1,427 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * An implementation of file copy service. - * - * Copyright (C) 2014, Microsoft, Inc. - * - * Author : K. Y. Srinivasan - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include - -#include "hyperv_vmbus.h" -#include "hv_utils_transport.h" - -#define WIN8_SRV_MAJOR 1 -#define WIN8_SRV_MINOR 1 -#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR) - -#define FCOPY_VER_COUNT 1 -static const int fcopy_versions[] = { - WIN8_SRV_VERSION -}; - -#define FW_VER_COUNT 1 -static const int fw_versions[] = { - UTIL_FW_VERSION -}; - -/* - * Global state maintained for transaction that is being processed. - * For a class of integration services, including the "file copy service", - * the specified protocol is a "request/response" protocol which means that - * there can only be single outstanding transaction from the host at any - * given point in time. We use this to simplify memory management in this - * driver - we cache and process only one message at a time. - * - * While the request/response protocol is guaranteed by the host, we further - * ensure this by serializing packet processing in this driver - we do not - * read additional packets from the VMBUs until the current packet is fully - * handled. - */ - -static struct { - int state; /* hvutil_device_state */ - int recv_len; /* number of bytes received. */ - struct hv_fcopy_hdr *fcopy_msg; /* current message */ - struct vmbus_channel *recv_channel; /* chn we got the request */ - u64 recv_req_id; /* request ID. */ -} fcopy_transaction; - -static void fcopy_respond_to_host(int error); -static void fcopy_send_data(struct work_struct *dummy); -static void fcopy_timeout_func(struct work_struct *dummy); -static DECLARE_DELAYED_WORK(fcopy_timeout_work, fcopy_timeout_func); -static DECLARE_WORK(fcopy_send_work, fcopy_send_data); -static const char fcopy_devname[] = "vmbus/hv_fcopy"; -static u8 *recv_buffer; -static struct hvutil_transport *hvt; -/* - * This state maintains the version number registered by the daemon. - */ -static int dm_reg_value; - -static void fcopy_poll_wrapper(void *channel) -{ - /* Transaction is finished, reset the state here to avoid races. */ - fcopy_transaction.state = HVUTIL_READY; - tasklet_schedule(&((struct vmbus_channel *)channel)->callback_event); -} - -static void fcopy_timeout_func(struct work_struct *dummy) -{ - /* - * If the timer fires, the user-mode component has not responded; - * process the pending transaction. - */ - fcopy_respond_to_host(HV_E_FAIL); - hv_poll_channel(fcopy_transaction.recv_channel, fcopy_poll_wrapper); -} - -static void fcopy_register_done(void) -{ - pr_debug("FCP: userspace daemon registered\n"); - hv_poll_channel(fcopy_transaction.recv_channel, fcopy_poll_wrapper); -} - -static int fcopy_handle_handshake(u32 version) -{ - u32 our_ver = FCOPY_CURRENT_VERSION; - - switch (version) { - case FCOPY_VERSION_0: - /* Daemon doesn't expect us to reply */ - dm_reg_value = version; - break; - case FCOPY_VERSION_1: - /* Daemon expects us to reply with our own version */ - if (hvutil_transport_send(hvt, &our_ver, sizeof(our_ver), - fcopy_register_done)) - return -EFAULT; - dm_reg_value = version; - break; - default: - /* - * For now we will fail the registration. - * If and when we have multiple versions to - * deal with, we will be backward compatible. - * We will add this code when needed. - */ - return -EINVAL; - } - pr_debug("FCP: userspace daemon ver. %d connected\n", version); - return 0; -} - -static void fcopy_send_data(struct work_struct *dummy) -{ - struct hv_start_fcopy *smsg_out = NULL; - int operation = fcopy_transaction.fcopy_msg->operation; - struct hv_start_fcopy *smsg_in; - void *out_src; - int rc, out_len; - - /* - * The strings sent from the host are encoded in - * utf16; convert it to utf8 strings. - * The host assures us that the utf16 strings will not exceed - * the max lengths specified. We will however, reserve room - * for the string terminating character - in the utf16s_utf8s() - * function we limit the size of the buffer where the converted - * string is placed to W_MAX_PATH -1 to guarantee - * that the strings can be properly terminated! - */ - - switch (operation) { - case START_FILE_COPY: - out_len = sizeof(struct hv_start_fcopy); - smsg_out = kzalloc(sizeof(*smsg_out), GFP_KERNEL); - if (!smsg_out) - return; - - smsg_out->hdr.operation = operation; - smsg_in = (struct hv_start_fcopy *)fcopy_transaction.fcopy_msg; - - utf16s_to_utf8s((wchar_t *)smsg_in->file_name, W_MAX_PATH, - UTF16_LITTLE_ENDIAN, - (__u8 *)&smsg_out->file_name, W_MAX_PATH - 1); - - utf16s_to_utf8s((wchar_t *)smsg_in->path_name, W_MAX_PATH, - UTF16_LITTLE_ENDIAN, - (__u8 *)&smsg_out->path_name, W_MAX_PATH - 1); - - smsg_out->copy_flags = smsg_in->copy_flags; - smsg_out->file_size = smsg_in->file_size; - out_src = smsg_out; - break; - - case WRITE_TO_FILE: - out_src = fcopy_transaction.fcopy_msg; - out_len = sizeof(struct hv_do_fcopy); - break; - default: - out_src = fcopy_transaction.fcopy_msg; - out_len = fcopy_transaction.recv_len; - break; - } - - fcopy_transaction.state = HVUTIL_USERSPACE_REQ; - rc = hvutil_transport_send(hvt, out_src, out_len, NULL); - if (rc) { - pr_debug("FCP: failed to communicate to the daemon: %d\n", rc); - if (cancel_delayed_work_sync(&fcopy_timeout_work)) { - fcopy_respond_to_host(HV_E_FAIL); - fcopy_transaction.state = HVUTIL_READY; - } - } - kfree(smsg_out); -} - -/* - * Send a response back to the host. - */ - -static void -fcopy_respond_to_host(int error) -{ - struct icmsg_hdr *icmsghdr; - u32 buf_len; - struct vmbus_channel *channel; - u64 req_id; - - /* - * Copy the global state for completing the transaction. Note that - * only one transaction can be active at a time. This is guaranteed - * by the file copy protocol implemented by the host. Furthermore, - * the "transaction active" state we maintain ensures that there can - * only be one active transaction at a time. - */ - - buf_len = fcopy_transaction.recv_len; - channel = fcopy_transaction.recv_channel; - req_id = fcopy_transaction.recv_req_id; - - icmsghdr = (struct icmsg_hdr *) - &recv_buffer[sizeof(struct vmbuspipe_hdr)]; - - if (channel->onchannel_callback == NULL) - /* - * We have raced with util driver being unloaded; - * silently return. - */ - return; - - icmsghdr->status = error; - icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE; - vmbus_sendpacket(channel, recv_buffer, buf_len, req_id, - VM_PKT_DATA_INBAND, 0); -} - -void hv_fcopy_onchannelcallback(void *context) -{ - struct vmbus_channel *channel = context; - u32 recvlen; - u64 requestid; - struct hv_fcopy_hdr *fcopy_msg; - struct icmsg_hdr *icmsghdr; - int fcopy_srv_version; - - if (fcopy_transaction.state > HVUTIL_READY) - return; - - if (vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 2, &recvlen, &requestid)) { - pr_err_ratelimited("Fcopy request received. Could not read into recv buf\n"); - return; - } - - if (!recvlen) - return; - - /* Ensure recvlen is big enough to read header data */ - if (recvlen < ICMSG_HDR) { - pr_err_ratelimited("Fcopy request received. Packet length too small: %d\n", - recvlen); - return; - } - - icmsghdr = (struct icmsg_hdr *)&recv_buffer[ - sizeof(struct vmbuspipe_hdr)]; - - if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) { - if (vmbus_prep_negotiate_resp(icmsghdr, - recv_buffer, recvlen, - fw_versions, FW_VER_COUNT, - fcopy_versions, FCOPY_VER_COUNT, - NULL, &fcopy_srv_version)) { - - pr_info("FCopy IC version %d.%d\n", - fcopy_srv_version >> 16, - fcopy_srv_version & 0xFFFF); - } - } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) { - /* Ensure recvlen is big enough to contain hv_fcopy_hdr */ - if (recvlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) { - pr_err_ratelimited("Invalid Fcopy hdr. Packet length too small: %u\n", - recvlen); - return; - } - fcopy_msg = (struct hv_fcopy_hdr *)&recv_buffer[ICMSG_HDR]; - - /* - * Stash away this global state for completing the - * transaction; note transactions are serialized. - */ - - fcopy_transaction.recv_len = recvlen; - fcopy_transaction.recv_req_id = requestid; - fcopy_transaction.fcopy_msg = fcopy_msg; - - if (fcopy_transaction.state < HVUTIL_READY) { - /* Userspace is not registered yet */ - fcopy_respond_to_host(HV_E_FAIL); - return; - } - fcopy_transaction.state = HVUTIL_HOSTMSG_RECEIVED; - - /* - * Send the information to the user-level daemon. - */ - schedule_work(&fcopy_send_work); - schedule_delayed_work(&fcopy_timeout_work, - HV_UTIL_TIMEOUT * HZ); - return; - } else { - pr_err_ratelimited("Fcopy request received. Invalid msg type: %d\n", - icmsghdr->icmsgtype); - return; - } - icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE; - vmbus_sendpacket(channel, recv_buffer, recvlen, requestid, - VM_PKT_DATA_INBAND, 0); -} - -/* Callback when data is received from userspace */ -static int fcopy_on_msg(void *msg, int len) -{ - int *val = (int *)msg; - - if (len != sizeof(int)) - return -EINVAL; - - if (fcopy_transaction.state == HVUTIL_DEVICE_INIT) - return fcopy_handle_handshake(*val); - - if (fcopy_transaction.state != HVUTIL_USERSPACE_REQ) - return -EINVAL; - - /* - * Complete the transaction by forwarding the result - * to the host. But first, cancel the timeout. - */ - if (cancel_delayed_work_sync(&fcopy_timeout_work)) { - fcopy_transaction.state = HVUTIL_USERSPACE_RECV; - fcopy_respond_to_host(*val); - hv_poll_channel(fcopy_transaction.recv_channel, - fcopy_poll_wrapper); - } - - return 0; -} - -static void fcopy_on_reset(void) -{ - /* - * The daemon has exited; reset the state. - */ - fcopy_transaction.state = HVUTIL_DEVICE_INIT; - - if (cancel_delayed_work_sync(&fcopy_timeout_work)) - fcopy_respond_to_host(HV_E_FAIL); -} - -int hv_fcopy_init(struct hv_util_service *srv) -{ - recv_buffer = srv->recv_buffer; - fcopy_transaction.recv_channel = srv->channel; - fcopy_transaction.recv_channel->max_pkt_size = HV_HYP_PAGE_SIZE * 2; - - /* - * When this driver loads, the user level daemon that - * processes the host requests may not yet be running. - * Defer processing channel callbacks until the daemon - * has registered. - */ - fcopy_transaction.state = HVUTIL_DEVICE_INIT; - - hvt = hvutil_transport_init(fcopy_devname, 0, 0, - fcopy_on_msg, fcopy_on_reset); - if (!hvt) - return -EFAULT; - - return 0; -} - -static void hv_fcopy_cancel_work(void) -{ - cancel_delayed_work_sync(&fcopy_timeout_work); - cancel_work_sync(&fcopy_send_work); -} - -int hv_fcopy_pre_suspend(void) -{ - struct vmbus_channel *channel = fcopy_transaction.recv_channel; - struct hv_fcopy_hdr *fcopy_msg; - - /* - * Fake a CANCEL_FCOPY message for the user space daemon in case the - * daemon is in the middle of copying some file. It doesn't matter if - * there is already a message pending to be delivered to the user - * space since we force fcopy_transaction.state to be HVUTIL_READY, so - * the user space daemon's write() will fail with EINVAL (see - * fcopy_on_msg()), and the daemon will reset the device by closing - * and re-opening it. - */ - fcopy_msg = kzalloc(sizeof(*fcopy_msg), GFP_KERNEL); - if (!fcopy_msg) - return -ENOMEM; - - tasklet_disable(&channel->callback_event); - - fcopy_msg->operation = CANCEL_FCOPY; - - hv_fcopy_cancel_work(); - - /* We don't care about the return value. */ - hvutil_transport_send(hvt, fcopy_msg, sizeof(*fcopy_msg), NULL); - - kfree(fcopy_msg); - - fcopy_transaction.state = HVUTIL_READY; - - /* tasklet_enable() will be called in hv_fcopy_pre_resume(). */ - return 0; -} - -int hv_fcopy_pre_resume(void) -{ - struct vmbus_channel *channel = fcopy_transaction.recv_channel; - - tasklet_enable(&channel->callback_event); - - return 0; -} - -void hv_fcopy_deinit(void) -{ - fcopy_transaction.state = HVUTIL_DEVICE_DYING; - - hv_fcopy_cancel_work(); - - hvutil_transport_destroy(hvt); -} diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index 9c97c4065fe7..c4f525325790 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -154,14 +154,6 @@ static struct hv_util_service util_vss = { .util_deinit = hv_vss_deinit, }; -static struct hv_util_service util_fcopy = { - .util_cb = hv_fcopy_onchannelcallback, - .util_init = hv_fcopy_init, - .util_pre_suspend = hv_fcopy_pre_suspend, - .util_pre_resume = hv_fcopy_pre_resume, - .util_deinit = hv_fcopy_deinit, -}; - static void perform_shutdown(struct work_struct *dummy) { orderly_poweroff(true); @@ -700,10 +692,6 @@ static const struct hv_vmbus_device_id id_table[] = { { HV_VSS_GUID, .driver_data = (unsigned long)&util_vss }, - /* File copy GUID */ - { HV_FCOPY_GUID, - .driver_data = (unsigned long)&util_fcopy - }, { }, }; diff --git a/tools/hv/hv_fcopy_daemon.c b/tools/hv/hv_fcopy_daemon.c deleted file mode 100644 index 16d629b22c25..000000000000 --- a/tools/hv/hv_fcopy_daemon.c +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * An implementation of host to guest copy functionality for Linux. - * - * Copyright (C) 2014, Microsoft, Inc. - * - * Author : K. Y. Srinivasan - */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int target_fd; -static char target_fname[PATH_MAX]; -static unsigned long long filesize; - -static int hv_start_fcopy(struct hv_start_fcopy *smsg) -{ - int error = HV_E_FAIL; - char *q, *p; - - filesize = 0; - p = (char *)smsg->path_name; - snprintf(target_fname, sizeof(target_fname), "%s/%s", - (char *)smsg->path_name, (char *)smsg->file_name); - - syslog(LOG_INFO, "Target file name: %s", target_fname); - /* - * Check to see if the path is already in place; if not, - * create if required. - */ - while ((q = strchr(p, '/')) != NULL) { - if (q == p) { - p++; - continue; - } - *q = '\0'; - if (access((char *)smsg->path_name, F_OK)) { - if (smsg->copy_flags & CREATE_PATH) { - if (mkdir((char *)smsg->path_name, 0755)) { - syslog(LOG_ERR, "Failed to create %s", - (char *)smsg->path_name); - goto done; - } - } else { - syslog(LOG_ERR, "Invalid path: %s", - (char *)smsg->path_name); - goto done; - } - } - p = q + 1; - *q = '/'; - } - - if (!access(target_fname, F_OK)) { - syslog(LOG_INFO, "File: %s exists", target_fname); - if (!(smsg->copy_flags & OVER_WRITE)) { - error = HV_ERROR_ALREADY_EXISTS; - goto done; - } - } - - target_fd = open(target_fname, - O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744); - if (target_fd == -1) { - syslog(LOG_INFO, "Open Failed: %s", strerror(errno)); - goto done; - } - - error = 0; -done: - if (error) - target_fname[0] = '\0'; - return error; -} - -static int hv_copy_data(struct hv_do_fcopy *cpmsg) -{ - ssize_t bytes_written; - int ret = 0; - - bytes_written = pwrite(target_fd, cpmsg->data, cpmsg->size, - cpmsg->offset); - - filesize += cpmsg->size; - if (bytes_written != cpmsg->size) { - switch (errno) { - case ENOSPC: - ret = HV_ERROR_DISK_FULL; - break; - default: - ret = HV_E_FAIL; - break; - } - syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)", - filesize, (long)bytes_written, strerror(errno)); - } - - return ret; -} - -/* - * Reset target_fname to "" in the two below functions for hibernation: if - * the fcopy operation is aborted by hibernation, the daemon should remove the - * partially-copied file; to achieve this, the hv_utils driver always fakes a - * CANCEL_FCOPY message upon suspend, and later when the VM resumes back, - * the daemon calls hv_copy_cancel() to remove the file; if a file is copied - * successfully before suspend, hv_copy_finished() must reset target_fname to - * avoid that the file can be incorrectly removed upon resume, since the faked - * CANCEL_FCOPY message is spurious in this case. - */ -static int hv_copy_finished(void) -{ - close(target_fd); - target_fname[0] = '\0'; - return 0; -} -static int hv_copy_cancel(void) -{ - close(target_fd); - if (strlen(target_fname) > 0) { - unlink(target_fname); - target_fname[0] = '\0'; - } - return 0; - -} - -void print_usage(char *argv[]) -{ - fprintf(stderr, "Usage: %s [options]\n" - "Options are:\n" - " -n, --no-daemon stay in foreground, don't daemonize\n" - " -h, --help print this help\n", argv[0]); -} - -int main(int argc, char *argv[]) -{ - int fcopy_fd = -1; - int error; - int daemonize = 1, long_index = 0, opt; - int version = FCOPY_CURRENT_VERSION; - union { - struct hv_fcopy_hdr hdr; - struct hv_start_fcopy start; - struct hv_do_fcopy copy; - __u32 kernel_modver; - } buffer = { }; - int in_handshake; - - static struct option long_options[] = { - {"help", no_argument, 0, 'h' }, - {"no-daemon", no_argument, 0, 'n' }, - {0, 0, 0, 0 } - }; - - while ((opt = getopt_long(argc, argv, "hn", long_options, - &long_index)) != -1) { - switch (opt) { - case 'n': - daemonize = 0; - break; - case 'h': - default: - print_usage(argv); - exit(EXIT_FAILURE); - } - } - - if (daemonize && daemon(1, 0)) { - syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno)); - exit(EXIT_FAILURE); - } - - openlog("HV_FCOPY", 0, LOG_USER); - syslog(LOG_INFO, "starting; pid is:%d", getpid()); - -reopen_fcopy_fd: - if (fcopy_fd != -1) - close(fcopy_fd); - /* Remove any possible partially-copied file on error */ - hv_copy_cancel(); - in_handshake = 1; - fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR); - - if (fcopy_fd < 0) { - syslog(LOG_ERR, "open /dev/vmbus/hv_fcopy failed; error: %d %s", - errno, strerror(errno)); - exit(EXIT_FAILURE); - } - - /* - * Register with the kernel. - */ - if ((write(fcopy_fd, &version, sizeof(int))) != sizeof(int)) { - syslog(LOG_ERR, "Registration failed: %s", strerror(errno)); - exit(EXIT_FAILURE); - } - - while (1) { - /* - * In this loop we process fcopy messages after the - * handshake is complete. - */ - ssize_t len; - - len = pread(fcopy_fd, &buffer, sizeof(buffer), 0); - if (len < 0) { - syslog(LOG_ERR, "pread failed: %s", strerror(errno)); - goto reopen_fcopy_fd; - } - - if (in_handshake) { - if (len != sizeof(buffer.kernel_modver)) { - syslog(LOG_ERR, "invalid version negotiation"); - exit(EXIT_FAILURE); - } - in_handshake = 0; - syslog(LOG_INFO, "kernel module version: %u", - buffer.kernel_modver); - continue; - } - - switch (buffer.hdr.operation) { - case START_FILE_COPY: - error = hv_start_fcopy(&buffer.start); - break; - case WRITE_TO_FILE: - error = hv_copy_data(&buffer.copy); - break; - case COMPLETE_FCOPY: - error = hv_copy_finished(); - break; - case CANCEL_FCOPY: - error = hv_copy_cancel(); - break; - - default: - error = HV_E_FAIL; - syslog(LOG_ERR, "Unknown operation: %d", - buffer.hdr.operation); - - } - - /* - * pwrite() may return an error due to the faked CANCEL_FCOPY - * message upon hibernation. Ignore the error by resetting the - * dev file, i.e. closing and re-opening it. - */ - if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) { - syslog(LOG_ERR, "pwrite failed: %s", strerror(errno)); - goto reopen_fcopy_fd; - } - } -} From 76457f9a11dd1521efeb553526d350100e1ef422 Mon Sep 17 00:00:00 2001 From: Saurabh Sengar Date: Sat, 30 Mar 2024 01:52:03 -0700 Subject: [PATCH 128/319] uio_hv_generic: Remove use of PAGE_SIZE Remove use of PAGE_SIZE for device ring buffer size calculation, as there is no dependency on device ring buffer size for linux kernel's PAGE_SIZE. Use the absolute value of 2 MB instead. Signed-off-by: Saurabh Sengar Reviewed-by: Long Li Link: https://lore.kernel.org/r/1711788723-8593-8-git-send-email-ssengar@linux.microsoft.com Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_hv_generic.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index 289611c7dfd7..015ddbddd6e1 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -36,7 +36,6 @@ #define DRIVER_AUTHOR "Stephen Hemminger " #define DRIVER_DESC "Generic UIO driver for VMBus devices" -#define HV_RING_SIZE 512 /* pages */ #define SEND_BUFFER_SIZE (16 * 1024 * 1024) #define RECV_BUFFER_SIZE (31 * 1024 * 1024) @@ -146,7 +145,7 @@ static const struct bin_attribute ring_buffer_bin_attr = { .name = "ring", .mode = 0600, }, - .size = 2 * HV_RING_SIZE * PAGE_SIZE, + .size = 2 * SZ_2M, .mmap = hv_uio_ring_mmap, }; @@ -156,7 +155,7 @@ hv_uio_new_channel(struct vmbus_channel *new_sc) { struct hv_device *hv_dev = new_sc->primary_channel->device_obj; struct device *device = &hv_dev->device; - const size_t ring_bytes = HV_RING_SIZE * PAGE_SIZE; + const size_t ring_bytes = SZ_2M; int ret; /* Create host communication ring */ @@ -244,7 +243,7 @@ hv_uio_probe(struct hv_device *dev, size_t ring_size = hv_dev_ring_size(channel); if (!ring_size) - ring_size = HV_RING_SIZE * PAGE_SIZE; + ring_size = SZ_2M; pdata = devm_kzalloc(&dev->device, sizeof(*pdata), GFP_KERNEL); if (!pdata) From d3eb521378a685150396687c3c7a10a877ed065a Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 8 Mar 2024 13:18:48 +0000 Subject: [PATCH 129/319] comedi: remove unused helper function dma_chain_flag_bits The helper function dma_chain_flag_bits is not being called from anywhere, it is redundant and can be removed. Cleans up clang scan build warning: drivers/comedi/drivers/cb_pcidas64.c:377:28: warning: unused function 'dma_chain_flag_bits' [-Wunused-function] Signed-off-by: Colin Ian King Reviewed-by: Ian Abbott Link: https://lore.kernel.org/r/20240308131848.2057693-1-colin.i.king@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/cb_pcidas64.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/comedi/drivers/cb_pcidas64.c b/drivers/comedi/drivers/cb_pcidas64.c index ff19fc3859e4..d398c6df9482 100644 --- a/drivers/comedi/drivers/cb_pcidas64.c +++ b/drivers/comedi/drivers/cb_pcidas64.c @@ -374,11 +374,6 @@ static inline u16 pipe_full_bits(u16 hw_status_bits) return (hw_status_bits >> 10) & 0x3; }; -static inline unsigned int dma_chain_flag_bits(u16 prepost_bits) -{ - return (prepost_bits >> 6) & 0x3; -} - static inline unsigned int adc_upper_read_ptr_code(u16 prepost_bits) { return (prepost_bits >> 12) & 0x3; From 043327875298030095f9c5d04bd41def28275175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 13 Mar 2024 19:19:30 +0100 Subject: [PATCH 130/319] misc/pvpanic: use bit macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macros are easier to read. Suggested-by: Greg Kroah-Hartman Link: https://lore.kernel.org/lkml/2023110407-unselect-uptight-b96d@gregkh/ Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20240313-pvpanic-shutdown-header-v1-1-7f1970d66366@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- include/uapi/misc/pvpanic.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/uapi/misc/pvpanic.h b/include/uapi/misc/pvpanic.h index 54b7485390d3..9ea6a965ca7a 100644 --- a/include/uapi/misc/pvpanic.h +++ b/include/uapi/misc/pvpanic.h @@ -3,7 +3,9 @@ #ifndef __PVPANIC_H__ #define __PVPANIC_H__ -#define PVPANIC_PANICKED (1 << 0) -#define PVPANIC_CRASH_LOADED (1 << 1) +#include + +#define PVPANIC_PANICKED _BITUL(0) +#define PVPANIC_CRASH_LOADED _BITUL(1) #endif /* __PVPANIC_H__ */ From ad76f3e8f57cef368fa98f2d4d8902ad66481a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 13 Mar 2024 19:19:31 +0100 Subject: [PATCH 131/319] misc/pvpanic: add shutdown event definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shutdown requests are normally hardware dependent. By extending pvpanic to also handle shutdown requests, guests can submit such requests with an easily implementable and cross-platform mechanism. The event was added to the specification in qemu commit 73279cecca03 ("docs/specs/pvpanic: document shutdown event"). Signed-off-by: Thomas Weißschuh Acked-by: Arnd Bergmann Link: https://lore.kernel.org/r/20240313-pvpanic-shutdown-header-v1-2-7f1970d66366@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- include/uapi/misc/pvpanic.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/misc/pvpanic.h b/include/uapi/misc/pvpanic.h index 9ea6a965ca7a..3f1745cd1b52 100644 --- a/include/uapi/misc/pvpanic.h +++ b/include/uapi/misc/pvpanic.h @@ -7,5 +7,6 @@ #define PVPANIC_PANICKED _BITUL(0) #define PVPANIC_CRASH_LOADED _BITUL(1) +#define PVPANIC_SHUTDOWN _BITUL(2) #endif /* __PVPANIC_H__ */ From 115ee55351c1d3fee16515f01133144205ddb29b Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Fri, 15 Mar 2024 17:35:40 -0400 Subject: [PATCH 132/319] misc: ds1682: Add NVMEM support Add NVMEM support for the internal EEPROM. Signed-off-by: Sean Anderson Link: https://lore.kernel.org/r/20240315213540.1682964-1-sean.anderson@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ds1682.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c index 21fc5bc85c5c..5f8dcd0e3848 100644 --- a/drivers/misc/ds1682.c +++ b/drivers/misc/ds1682.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -197,11 +198,43 @@ static const struct bin_attribute ds1682_eeprom_attr = { .write = ds1682_eeprom_write, }; +static int ds1682_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct i2c_client *client = priv; + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, DS1682_REG_EEPROM + offset, + bytes, val); + return ret < 0 ? ret : 0; +} + +static int ds1682_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct i2c_client *client = priv; + int ret; + + ret = i2c_smbus_write_i2c_block_data(client, DS1682_REG_EEPROM + offset, + bytes, val); + return ret < 0 ? ret : 0; +} + /* * Called when a ds1682 device is matched with this driver */ static int ds1682_probe(struct i2c_client *client) { + struct nvmem_config config = { + .dev = &client->dev, + .owner = THIS_MODULE, + .type = NVMEM_TYPE_EEPROM, + .reg_read = ds1682_nvmem_read, + .reg_write = ds1682_nvmem_write, + .size = DS1682_EEPROM_SIZE, + .priv = client, + }; + struct nvmem_device *nvmem; int rc; if (!i2c_check_functionality(client->adapter, @@ -211,6 +244,10 @@ static int ds1682_probe(struct i2c_client *client) goto exit; } + nvmem = devm_nvmem_register(&client->dev, &config); + if (IS_ENABLED(CONFIG_NVMEM) && IS_ERR(nvmem)) + return PTR_ERR(nvmem); + rc = sysfs_create_group(&client->dev.kobj, &ds1682_group); if (rc) goto exit; From 355f6a292fa2443f0c4d2017b5cb0f67fcb01ddb Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Mon, 25 Mar 2024 14:01:23 -0600 Subject: [PATCH 133/319] mei: Avoid a bunch of -Wflex-array-member-not-at-end warnings -Wflex-array-member-not-at-end is coming in GCC-14, and we are getting ready to enable it globally. After commit 40292383640a ("mei: revamp mei extension header structure layout.") it seems that flexible-array member `data` in `struct mei_ext_hdr` is no longer needed. So, remove it and, with that, fix 45 of the following -Wflex-array-member-not-at-end warnings[1] in drivers/misc/mei/: drivers/misc/mei/hw.h:280:28: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] Link: https://gist.github.com/GustavoARSilva/62dcc235555a6b29b506269edb83da0b [1] Link: https://github.com/KSPP/linux/issues/202 Signed-off-by: Gustavo A. R. Silva Link: https://lore.kernel.org/r/ZgHYE2s5kBGlv1cw@neat Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index eb800a07a84b..2e9cf6f4efb6 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -247,12 +247,10 @@ enum mei_ext_hdr_type { * struct mei_ext_hdr - extend header descriptor (TLV) * @type: enum mei_ext_hdr_type * @length: length excluding descriptor - * @data: the extended header payload */ struct mei_ext_hdr { u8 type; u8 length; - u8 data[]; } __packed; /** From 5f8fcfc3e509d8a1d458a09baeacc53595607d4d Mon Sep 17 00:00:00 2001 From: Atin Bainada Date: Wed, 27 Mar 2024 11:19:45 +0000 Subject: [PATCH 134/319] misc: ti-st: st_kim: remove unnecessary (void*) conversions No need to type cast (void*) pointer variables. Signed-off-by: Atin Bainada Link: https://lore.kernel.org/r/20240327111914.38104-1-atin4@proton.me Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/st_kim.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c index 47ebe80bf849..c4f963cf96f2 100644 --- a/drivers/misc/ti-st/st_kim.c +++ b/drivers/misc/ti-st/st_kim.c @@ -563,7 +563,7 @@ long st_kim_stop(void *kim_data) static int version_show(struct seq_file *s, void *unused) { - struct kim_data_s *kim_gdata = (struct kim_data_s *)s->private; + struct kim_data_s *kim_gdata = s->private; seq_printf(s, "%04X %d.%d.%d\n", kim_gdata->version.full, kim_gdata->version.chip, kim_gdata->version.maj_ver, kim_gdata->version.min_ver); @@ -572,7 +572,7 @@ static int version_show(struct seq_file *s, void *unused) static int list_show(struct seq_file *s, void *unused) { - struct kim_data_s *kim_gdata = (struct kim_data_s *)s->private; + struct kim_data_s *kim_gdata = s->private; kim_st_list_protocols(kim_gdata->core_data, s); return 0; } From 1d6b84d29779f84f3108acc90681c372a2f70a24 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 5 Apr 2024 16:42:57 +0200 Subject: [PATCH 135/319] parport: mfc3: avoid empty-body warning on m68k builds, the mfc3 driver causes a warning about an empty if() block: drivers/parport/parport_mfc3.c: In function 'control_pc_to_mfc3': drivers/parport/parport_mfc3.c:106:37: warning: suggest braces around empty body in an 'if' statement [-Wempty-body] Remove it in favor of a simpler comment. Acked-by: Sudip Mukherjee Link: https://lore.kernel.org/lkml/20230727122448.2479942-1-arnd@kernel.org/ Signed-off-by: Arnd Bergmann Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20240405144304.1223160-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/parport/parport_mfc3.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/parport/parport_mfc3.c b/drivers/parport/parport_mfc3.c index f4d0da741e85..bb1817218d7b 100644 --- a/drivers/parport/parport_mfc3.c +++ b/drivers/parport/parport_mfc3.c @@ -102,8 +102,7 @@ static unsigned char control_pc_to_mfc3(unsigned char control) ret |= 128; if (control & PARPORT_CONTROL_AUTOFD) /* AUTOLF */ ret &= ~64; - if (control & PARPORT_CONTROL_STROBE) /* Strobe */ - /* Handled directly by hardware */; + /* PARPORT_CONTROL_STROBE handled directly by hardware */ return ret; } From f0a816fb12dadd21326a7c0e54e3253f69d895d5 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Thu, 4 Apr 2024 13:49:17 +0200 Subject: [PATCH 136/319] /dev/port: don't compile file operations without CONFIG_DEVPORT In the future inb() and friends will not be available when compiling with CONFIG_HAS_IOPORT=n so we must only try to access them here if CONFIG_DEVPORT is set which depends on HAS_IOPORT. Co-developed-by: Arnd Bergmann Signed-off-by: Arnd Bergmann Signed-off-by: Niklas Schnelle Link: https://lore.kernel.org/r/20240404114917.3627747-2-schnelle@linux.ibm.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/mem.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 3c6670cf905f..7904e2bb6427 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -383,6 +383,7 @@ static int mmap_mem(struct file *file, struct vm_area_struct *vma) return 0; } +#ifdef CONFIG_DEVPORT static ssize_t read_port(struct file *file, char __user *buf, size_t count, loff_t *ppos) { @@ -424,6 +425,7 @@ static ssize_t write_port(struct file *file, const char __user *buf, *ppos = i; return tmp-buf; } +#endif static ssize_t read_null(struct file *file, char __user *buf, size_t count, loff_t *ppos) @@ -653,12 +655,14 @@ static const struct file_operations null_fops = { .uring_cmd = uring_cmd_null, }; -static const struct file_operations __maybe_unused port_fops = { +#ifdef CONFIG_DEVPORT +static const struct file_operations port_fops = { .llseek = memory_lseek, .read = read_port, .write = write_port, .open = open_port, }; +#endif static const struct file_operations zero_fops = { .llseek = zero_lseek, From 2193014f79472658b8b1efa12a9adaae07f8b0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 10 Apr 2024 14:47:07 +0200 Subject: [PATCH 137/319] powerpc/powernv: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240410124707.194228-2-u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/char/powernv-op-panel.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/char/powernv-op-panel.c b/drivers/char/powernv-op-panel.c index 3c99696b145e..f2cff1a6fed5 100644 --- a/drivers/char/powernv-op-panel.c +++ b/drivers/char/powernv-op-panel.c @@ -195,12 +195,11 @@ free_oppanel_data: return rc; } -static int oppanel_remove(struct platform_device *pdev) +static void oppanel_remove(struct platform_device *pdev) { misc_deregister(&oppanel_dev); kfree(oppanel_lines); kfree(oppanel_data); - return 0; } static const struct of_device_id oppanel_match[] = { @@ -214,7 +213,7 @@ static struct platform_driver oppanel_driver = { .of_match_table = oppanel_match, }, .probe = oppanel_probe, - .remove = oppanel_remove, + .remove_new = oppanel_remove, }; module_platform_driver(oppanel_driver); From 26f0d3b11aff6bf7ffd39809171865871aadb992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 10 Apr 2024 14:48:27 +0200 Subject: [PATCH 138/319] sonypi: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240410124827.194351-2-u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/char/sonypi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c index 22d249333f53..bb5115b1736a 100644 --- a/drivers/char/sonypi.c +++ b/drivers/char/sonypi.c @@ -1408,7 +1408,7 @@ static int sonypi_probe(struct platform_device *dev) return error; } -static int sonypi_remove(struct platform_device *dev) +static void sonypi_remove(struct platform_device *dev) { sonypi_disable(); @@ -1432,8 +1432,6 @@ static int sonypi_remove(struct platform_device *dev) } kfifo_free(&sonypi_device.fifo); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -1470,7 +1468,7 @@ static struct platform_driver sonypi_driver = { .pm = SONYPI_PM, }, .probe = sonypi_probe, - .remove = sonypi_remove, + .remove_new = sonypi_remove, .shutdown = sonypi_shutdown, }; From 25b9cadb1ee3434b92de9096d4a2ae91820146bf Mon Sep 17 00:00:00 2001 From: Elizabeth Figura Date: Thu, 28 Mar 2024 19:05:52 -0500 Subject: [PATCH 139/319] ntsync: Introduce the ntsync driver and character device. ntsync uses a misc device as the simplest and least intrusive uAPI interface. Each file description on the device represents an isolated NT instance, intended to correspond to a single NT virtual machine. Signed-off-by: Elizabeth Figura Link: https://lore.kernel.org/r/20240329000621.148791-2-zfigura@codeweavers.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 11 +++++++++ drivers/misc/Makefile | 1 + drivers/misc/ntsync.c | 52 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 drivers/misc/ntsync.c diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4fb291f0bf7c..801ed229ed7d 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -506,6 +506,17 @@ config OPEN_DICE If unsure, say N. +config NTSYNC + tristate "NT synchronization primitive emulation" + help + This module provides kernel support for emulation of Windows NT + synchronization primitives. It is not a hardware driver. + + To compile this driver as a module, choose M here: the + module will be called ntsync. + + If unsure, say N. + config VCPU_STALL_DETECTOR tristate "Guest vCPU stall detector" depends on OF && HAS_IOMEM diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index ea6ea5bbbc9c..153a3f4837e8 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic/ obj-$(CONFIG_UACCE) += uacce/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o +obj-$(CONFIG_NTSYNC) += ntsync.o obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o obj-$(CONFIG_OPEN_DICE) += open-dice.o obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c new file mode 100644 index 000000000000..bd76e653d83e --- /dev/null +++ b/drivers/misc/ntsync.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ntsync.c - Kernel driver for NT synchronization primitives + * + * Copyright (C) 2024 Elizabeth Figura + */ + +#include +#include +#include + +#define NTSYNC_NAME "ntsync" + +static int ntsync_char_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +static int ntsync_char_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static long ntsync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) +{ + switch (cmd) { + default: + return -ENOIOCTLCMD; + } +} + +static const struct file_operations ntsync_fops = { + .owner = THIS_MODULE, + .open = ntsync_char_open, + .release = ntsync_char_release, + .unlocked_ioctl = ntsync_char_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .llseek = no_llseek, +}; + +static struct miscdevice ntsync_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = NTSYNC_NAME, + .fops = &ntsync_fops, +}; + +module_misc_device(ntsync_misc); + +MODULE_AUTHOR("Elizabeth Figura "); +MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives"); +MODULE_LICENSE("GPL"); From b46271ec40a05580d55f917c9ac52cb93553160a Mon Sep 17 00:00:00 2001 From: Elizabeth Figura Date: Thu, 28 Mar 2024 19:05:53 -0500 Subject: [PATCH 140/319] ntsync: Introduce NTSYNC_IOC_CREATE_SEM. This corresponds to the NT syscall NtCreateSemaphore(). Semaphores are one of three types of object to be implemented in this driver, the others being mutexes and events. An NT semaphore contains a 32-bit counter, and is signaled and can be acquired when the counter is nonzero. The counter has a maximum value which is specified at creation time. The initial value of the semaphore is also specified at creation time. There are no restrictions on the maximum and initial value. Each object is exposed as an file, to which any number of fds may be opened. When all fds are closed, the object is deleted. Objects hold a pointer to the ntsync_device that created them. The device's reference count is driven by struct file. Signed-off-by: Elizabeth Figura Link: https://lore.kernel.org/r/20240329000621.148791-3-zfigura@codeweavers.com Signed-off-by: Greg Kroah-Hartman --- .../userspace-api/ioctl/ioctl-number.rst | 2 + drivers/misc/ntsync.c | 131 ++++++++++++++++++ include/uapi/linux/ntsync.h | 21 +++ 3 files changed, 154 insertions(+) create mode 100644 include/uapi/linux/ntsync.h diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index c472423412bf..a141e8e65c5d 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -174,6 +174,8 @@ Code Seq# Include File Comments 'M' 00-0F drivers/video/fsl-diu-fb.h conflict! 'N' 00-1F drivers/usb/scanner.h 'N' 40-7F drivers/block/nvme.c +'N' 80-8F uapi/linux/ntsync.h NT synchronization primitives + 'O' 00-06 mtd/ubi-user.h UBI 'P' all linux/soundcard.h conflict! 'P' 60-6F sound/sscape_ioctl.h conflict! diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c index bd76e653d83e..20158ec148bc 100644 --- a/drivers/misc/ntsync.c +++ b/drivers/misc/ntsync.c @@ -5,26 +5,157 @@ * Copyright (C) 2024 Elizabeth Figura */ +#include +#include #include #include #include +#include +#include #define NTSYNC_NAME "ntsync" +enum ntsync_type { + NTSYNC_TYPE_SEM, +}; + +/* + * Individual synchronization primitives are represented by + * struct ntsync_obj, and each primitive is backed by a file. + * + * The whole namespace is represented by a struct ntsync_device also + * backed by a file. + * + * Both rely on struct file for reference counting. Individual + * ntsync_obj objects take a reference to the device when created. + */ + +struct ntsync_obj { + enum ntsync_type type; + + union { + struct { + __u32 count; + __u32 max; + } sem; + } u; + + struct file *file; + struct ntsync_device *dev; +}; + +struct ntsync_device { + struct file *file; +}; + +static int ntsync_obj_release(struct inode *inode, struct file *file) +{ + struct ntsync_obj *obj = file->private_data; + + fput(obj->dev->file); + kfree(obj); + + return 0; +} + +static const struct file_operations ntsync_obj_fops = { + .owner = THIS_MODULE, + .release = ntsync_obj_release, + .llseek = no_llseek, +}; + +static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, + enum ntsync_type type) +{ + struct ntsync_obj *obj; + + obj = kzalloc(sizeof(*obj), GFP_KERNEL); + if (!obj) + return NULL; + obj->type = type; + obj->dev = dev; + get_file(dev->file); + + return obj; +} + +static int ntsync_obj_get_fd(struct ntsync_obj *obj) +{ + struct file *file; + int fd; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR); + if (IS_ERR(file)) { + put_unused_fd(fd); + return PTR_ERR(file); + } + obj->file = file; + fd_install(fd, file); + + return fd; +} + +static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp) +{ + struct ntsync_sem_args __user *user_args = argp; + struct ntsync_sem_args args; + struct ntsync_obj *sem; + int fd; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.count > args.max) + return -EINVAL; + + sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM); + if (!sem) + return -ENOMEM; + sem->u.sem.count = args.count; + sem->u.sem.max = args.max; + fd = ntsync_obj_get_fd(sem); + if (fd < 0) { + kfree(sem); + return fd; + } + + return put_user(fd, &user_args->sem); +} + static int ntsync_char_open(struct inode *inode, struct file *file) { + struct ntsync_device *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + file->private_data = dev; + dev->file = file; return nonseekable_open(inode, file); } static int ntsync_char_release(struct inode *inode, struct file *file) { + struct ntsync_device *dev = file->private_data; + + kfree(dev); + return 0; } static long ntsync_char_ioctl(struct file *file, unsigned int cmd, unsigned long parm) { + struct ntsync_device *dev = file->private_data; + void __user *argp = (void __user *)parm; + switch (cmd) { + case NTSYNC_IOC_CREATE_SEM: + return ntsync_create_sem(dev, argp); default: return -ENOIOCTLCMD; } diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h new file mode 100644 index 000000000000..6a4867a6c97b --- /dev/null +++ b/include/uapi/linux/ntsync.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Kernel support for NT synchronization primitive emulation + * + * Copyright (C) 2021-2022 Elizabeth Figura + */ + +#ifndef __LINUX_NTSYNC_H +#define __LINUX_NTSYNC_H + +#include + +struct ntsync_sem_args { + __u32 sem; + __u32 count; + __u32 max; +}; + +#define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) + +#endif From dc806bd48abc1b8a4ae72709a37e65db42a32048 Mon Sep 17 00:00:00 2001 From: Elizabeth Figura Date: Thu, 28 Mar 2024 19:05:54 -0500 Subject: [PATCH 141/319] ntsync: Introduce NTSYNC_IOC_SEM_POST. This corresponds to the NT syscall NtReleaseSemaphore(). This increases the semaphore's internal counter by the given value, and returns the previous value. If the counter would overflow the defined maximum, the function instead fails and returns -EOVERFLOW. Signed-off-by: Elizabeth Figura Link: https://lore.kernel.org/r/20240329000621.148791-4-zfigura@codeweavers.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ntsync.c | 72 +++++++++++++++++++++++++++++++++++-- include/uapi/linux/ntsync.h | 2 ++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c index 20158ec148bc..3c2f743c58b0 100644 --- a/drivers/misc/ntsync.c +++ b/drivers/misc/ntsync.c @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #define NTSYNC_NAME "ntsync" @@ -31,23 +33,70 @@ enum ntsync_type { */ struct ntsync_obj { + spinlock_t lock; + enum ntsync_type type; + struct file *file; + struct ntsync_device *dev; + + /* The following fields are protected by the object lock. */ union { struct { __u32 count; __u32 max; } sem; } u; - - struct file *file; - struct ntsync_device *dev; }; struct ntsync_device { struct file *file; }; +/* + * Actually change the semaphore state, returning -EOVERFLOW if it is made + * invalid. + */ +static int post_sem_state(struct ntsync_obj *sem, __u32 count) +{ + __u32 sum; + + lockdep_assert_held(&sem->lock); + + if (check_add_overflow(sem->u.sem.count, count, &sum) || + sum > sem->u.sem.max) + return -EOVERFLOW; + + sem->u.sem.count = sum; + return 0; +} + +static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp) +{ + __u32 __user *user_args = argp; + __u32 prev_count; + __u32 args; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (sem->type != NTSYNC_TYPE_SEM) + return -EINVAL; + + spin_lock(&sem->lock); + + prev_count = sem->u.sem.count; + ret = post_sem_state(sem, args); + + spin_unlock(&sem->lock); + + if (!ret && put_user(prev_count, user_args)) + ret = -EFAULT; + + return ret; +} + static int ntsync_obj_release(struct inode *inode, struct file *file) { struct ntsync_obj *obj = file->private_data; @@ -58,9 +107,25 @@ static int ntsync_obj_release(struct inode *inode, struct file *file) return 0; } +static long ntsync_obj_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) +{ + struct ntsync_obj *obj = file->private_data; + void __user *argp = (void __user *)parm; + + switch (cmd) { + case NTSYNC_IOC_SEM_POST: + return ntsync_sem_post(obj, argp); + default: + return -ENOIOCTLCMD; + } +} + static const struct file_operations ntsync_obj_fops = { .owner = THIS_MODULE, .release = ntsync_obj_release, + .unlocked_ioctl = ntsync_obj_ioctl, + .compat_ioctl = compat_ptr_ioctl, .llseek = no_llseek, }; @@ -75,6 +140,7 @@ static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev, obj->type = type; obj->dev = dev; get_file(dev->file); + spin_lock_init(&obj->lock); return obj; } diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h index 6a4867a6c97b..dcfa38fdc93c 100644 --- a/include/uapi/linux/ntsync.h +++ b/include/uapi/linux/ntsync.h @@ -18,4 +18,6 @@ struct ntsync_sem_args { #define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args) +#define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32) + #endif From 2adc731188b6123fc44ac01480ac381b41f8d6c6 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 26 Mar 2024 20:42:32 +0100 Subject: [PATCH 142/319] interconnect: qcom: sm6115: Unspaghettify SNoC QoS port numbering When I was creating this driver, my bright mind overlooked the existence of desc->qos_offset and decided to make up for the difference it made by adding 21 (0x15) to the port index on SNoC and its downstream buses. Undo this mistake to make the indices actually mean something. Signed-off-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20240326-topic-rpm_icc_qos_cleanup-v1-1-357e736792be@linaro.org Signed-off-by: Georgi Djakov --- drivers/interconnect/qcom/sm6115.c | 33 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/drivers/interconnect/qcom/sm6115.c b/drivers/interconnect/qcom/sm6115.c index 7e15ddf0a80a..271b07c74862 100644 --- a/drivers/interconnect/qcom/sm6115.c +++ b/drivers/interconnect/qcom/sm6115.c @@ -242,7 +242,7 @@ static struct qcom_icc_node crypto_c0 = { .id = SM6115_MASTER_CRYPTO_CORE0, .channels = 1, .buswidth = 8, - .qos.qos_port = 43, + .qos.qos_port = 22, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = 23, @@ -332,7 +332,7 @@ static struct qcom_icc_node qnm_camera_nrt = { .id = SM6115_MASTER_CAMNOC_SF, .channels = 1, .buswidth = 32, - .qos.qos_port = 25, + .qos.qos_port = 4, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 3, .mas_rpm_id = -1, @@ -346,7 +346,7 @@ static struct qcom_icc_node qxm_venus0 = { .id = SM6115_MASTER_VIDEO_P0, .channels = 1, .buswidth = 16, - .qos.qos_port = 30, + .qos.qos_port = 9, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 3, .qos.urg_fwd_en = true, @@ -361,7 +361,7 @@ static struct qcom_icc_node qxm_venus_cpu = { .id = SM6115_MASTER_VIDEO_PROC, .channels = 1, .buswidth = 8, - .qos.qos_port = 34, + .qos.qos_port = 13, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 4, .mas_rpm_id = -1, @@ -379,7 +379,7 @@ static struct qcom_icc_node qnm_camera_rt = { .id = SM6115_MASTER_CAMNOC_HF, .channels = 1, .buswidth = 32, - .qos.qos_port = 31, + .qos.qos_port = 10, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 3, .qos.urg_fwd_en = true, @@ -394,7 +394,7 @@ static struct qcom_icc_node qxm_mdp0 = { .id = SM6115_MASTER_MDP_PORT0, .channels = 1, .buswidth = 16, - .qos.qos_port = 26, + .qos.qos_port = 5, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 3, .qos.urg_fwd_en = true, @@ -434,7 +434,7 @@ static struct qcom_icc_node qhm_tic = { .id = SM6115_MASTER_TIC, .channels = 1, .buswidth = 4, - .qos.qos_port = 29, + .qos.qos_port = 8, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = -1, @@ -484,7 +484,7 @@ static struct qcom_icc_node qxm_pimem = { .id = SM6115_MASTER_PIMEM, .channels = 1, .buswidth = 8, - .qos.qos_port = 41, + .qos.qos_port = 20, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = -1, @@ -498,7 +498,7 @@ static struct qcom_icc_node qhm_qdss_bam = { .id = SM6115_MASTER_QDSS_BAM, .channels = 1, .buswidth = 4, - .qos.qos_port = 23, + .qos.qos_port = 2, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = -1, @@ -523,7 +523,7 @@ static struct qcom_icc_node qhm_qup0 = { .id = SM6115_MASTER_QUP_0, .channels = 1, .buswidth = 4, - .qos.qos_port = 21, + .qos.qos_port = 0, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = 166, @@ -537,7 +537,7 @@ static struct qcom_icc_node qxm_ipa = { .id = SM6115_MASTER_IPA, .channels = 1, .buswidth = 8, - .qos.qos_port = 24, + .qos.qos_port = 3, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = 59, @@ -551,7 +551,7 @@ static struct qcom_icc_node xm_qdss_etr = { .id = SM6115_MASTER_QDSS_ETR, .channels = 1, .buswidth = 8, - .qos.qos_port = 33, + .qos.qos_port = 12, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = -1, @@ -565,7 +565,7 @@ static struct qcom_icc_node xm_sdc1 = { .id = SM6115_MASTER_SDCC_1, .channels = 1, .buswidth = 8, - .qos.qos_port = 38, + .qos.qos_port = 17, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = 33, @@ -579,7 +579,7 @@ static struct qcom_icc_node xm_sdc2 = { .id = SM6115_MASTER_SDCC_2, .channels = 1, .buswidth = 8, - .qos.qos_port = 44, + .qos.qos_port = 23, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = 35, @@ -593,7 +593,7 @@ static struct qcom_icc_node xm_usb3_0 = { .id = SM6115_MASTER_USB3, .channels = 1, .buswidth = 8, - .qos.qos_port = 45, + .qos.qos_port = 24, .qos.qos_mode = NOC_QOS_MODE_FIXED, .qos.areq_prio = 2, .mas_rpm_id = -1, @@ -1336,6 +1336,7 @@ static const struct qcom_icc_desc sm6115_sys_noc = { .intf_clocks = snoc_intf_clocks, .num_intf_clocks = ARRAY_SIZE(snoc_intf_clocks), .bus_clk_desc = &bus_2_clk, + .qos_offset = 0x15000, .keep_alive = true, }; @@ -1367,6 +1368,7 @@ static const struct qcom_icc_desc sm6115_mmnrt_virt = { .regmap_cfg = &sys_noc_regmap_config, .bus_clk_desc = &mmaxi_0_clk, .keep_alive = true, + .qos_offset = 0x15000, .ab_coeff = 142, }; @@ -1383,6 +1385,7 @@ static const struct qcom_icc_desc sm6115_mmrt_virt = { .regmap_cfg = &sys_noc_regmap_config, .bus_clk_desc = &mmaxi_1_clk, .keep_alive = true, + .qos_offset = 0x15000, .ab_coeff = 139, }; From 230d05b1179f6ce6f8dc8a2b99eba92799ac22d7 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 26 Mar 2024 20:42:33 +0100 Subject: [PATCH 143/319] interconnect: qcom: qcm2290: Fix mas_snoc_bimc QoS port assignment The value was wrong, resulting in misprogramming of the hardware. Fix it. Fixes: 1a14b1ac3935 ("interconnect: qcom: Add QCM2290 driver support") Signed-off-by: Konrad Dybcio Link: https://lore.kernel.org/r/20240326-topic-rpm_icc_qos_cleanup-v1-2-357e736792be@linaro.org Signed-off-by: Georgi Djakov --- drivers/interconnect/qcom/qcm2290.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/interconnect/qcom/qcm2290.c b/drivers/interconnect/qcom/qcm2290.c index 96735800b13c..ba4cc08684d6 100644 --- a/drivers/interconnect/qcom/qcm2290.c +++ b/drivers/interconnect/qcom/qcm2290.c @@ -164,7 +164,7 @@ static struct qcom_icc_node mas_snoc_bimc = { .name = "mas_snoc_bimc", .buswidth = 16, .qos.ap_owned = true, - .qos.qos_port = 2, + .qos.qos_port = 6, .qos.qos_mode = NOC_QOS_MODE_BYPASS, .mas_rpm_id = 164, .slv_rpm_id = -1, From 3735ca0b072656c3aa2cedc617a5e639b583a472 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 30 Mar 2024 18:53:00 +0000 Subject: [PATCH 144/319] 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 145/319] 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 146/319] 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 4b9f86214c054ea2da2917f6c107b34f7076c181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 8 Mar 2024 09:51:05 +0100 Subject: [PATCH 147/319] cdx: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Acked-by: Nipun Gupta Link: https://lore.kernel.org/r/5d40f57e978bcce003133306712ec96439e93595.1709886922.git.u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/cdx/controller/cdx_controller.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/cdx/controller/cdx_controller.c b/drivers/cdx/controller/cdx_controller.c index 112a1541de6d..201f9a6fbde7 100644 --- a/drivers/cdx/controller/cdx_controller.c +++ b/drivers/cdx/controller/cdx_controller.c @@ -222,7 +222,7 @@ mcdi_init_fail: return ret; } -static int xlnx_cdx_remove(struct platform_device *pdev) +static void xlnx_cdx_remove(struct platform_device *pdev) { struct cdx_controller *cdx = platform_get_drvdata(pdev); struct cdx_mcdi *cdx_mcdi = cdx->priv; @@ -234,8 +234,6 @@ static int xlnx_cdx_remove(struct platform_device *pdev) cdx_mcdi_finish(cdx_mcdi); kfree(cdx_mcdi); - - return 0; } static const struct of_device_id cdx_match_table[] = { @@ -252,7 +250,7 @@ static struct platform_driver cdx_pdriver = { .of_match_table = cdx_match_table, }, .probe = xlnx_cdx_probe, - .remove = xlnx_cdx_remove, + .remove_new = xlnx_cdx_remove, }; static int __init cdx_controller_init(void) From d9dd38cb59fb828a428084128569e684dd51cbe9 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Mon, 8 Apr 2024 18:34:33 +0200 Subject: [PATCH 148/319] 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 149/319] 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 150/319] 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 151/319] 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 152/319] 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 ebbc1a4789c666846b9854ef845a37a64879e5f9 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Mon, 15 Apr 2024 09:52:20 +1200 Subject: [PATCH 153/319] uio: update kerneldoc comments for interrupt functions Update the kerneldoc comment for uio_interrupt_handler and add one for uio_interrupt_thread. Reported-by: kernel test robot Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/oe-kbuild-all/202404112227.mIATKoTb-lkp@intel.com/ Fixes: f8a27dfa4b82 ("uio: use threaded interrupts") Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20240414215220.2424597-1-chris.packham@alliedtelesis.co.nz Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index e815856eb46c..5ce429286ab0 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -438,7 +438,7 @@ void uio_event_notify(struct uio_info *info) EXPORT_SYMBOL_GPL(uio_event_notify); /** - * uio_interrupt - hardware interrupt handler + * uio_interrupt_handler - hardware interrupt handler * @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer * @dev_id: Pointer to the devices uio_device structure */ @@ -454,6 +454,11 @@ static irqreturn_t uio_interrupt_handler(int irq, void *dev_id) return ret; } +/** + * uio_interrupt_thread - irq thread handler + * @irq: IRQ number + * @dev_id: Pointer to the devices uio_device structure + */ static irqreturn_t uio_interrupt_thread(int irq, void *dev_id) { struct uio_device *idev = (struct uio_device *)dev_id; From ed8c2dad25eb2fbaa61f2e32385ecc1aa34c2355 Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Sat, 10 Feb 2024 12:10:14 -0300 Subject: [PATCH 154/319] peci: Make peci_bus_type const Now that the driver core can properly handle constant struct bus_type, move the peci_bus_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Cc: Greg Kroah-Hartman Suggested-by: Greg Kroah-Hartman Signed-off-by: "Ricardo B. Marliere" Link: https://lore.kernel.org/r/20240210-bus_cleanup-peci-v1-1-1e64bef6efc0@marliere.net Signed-off-by: Iwona Winiarska --- drivers/peci/core.c | 2 +- drivers/peci/internal.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/peci/core.c b/drivers/peci/core.c index 0f83a9c6093b..b2f8db967e9b 100644 --- a/drivers/peci/core.c +++ b/drivers/peci/core.c @@ -201,7 +201,7 @@ static void peci_bus_device_remove(struct device *dev) driver->remove(device); } -struct bus_type peci_bus_type = { +const struct bus_type peci_bus_type = { .name = "peci", .match = peci_bus_device_match, .probe = peci_bus_device_probe, diff --git a/drivers/peci/internal.h b/drivers/peci/internal.h index 9d75ea54504c..8a896c256c5f 100644 --- a/drivers/peci/internal.h +++ b/drivers/peci/internal.h @@ -81,7 +81,7 @@ extern const struct attribute_group *peci_device_groups[]; int peci_device_create(struct peci_controller *controller, u8 addr); void peci_device_destroy(struct peci_device *device); -extern struct bus_type peci_bus_type; +extern const struct bus_type peci_bus_type; extern const struct attribute_group *peci_bus_groups[]; /** From e6faf2b750ebf3837a63ebe465e7b7933502bdc4 Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Mon, 19 Feb 2024 09:36:35 -0300 Subject: [PATCH 155/319] peci: constify the struct device_type usage Since commit aed65af1cc2f ("drivers: make device_type const"), the driver core can properly handle constant struct device_type. Move the peci_controller_type and peci_device_type variables to be constant structures as well, placing it into read-only memory which can not be modified at runtime. Cc: Greg Kroah-Hartman Signed-off-by: "Ricardo B. Marliere" Reviewed-by: Iwona Winiarska Link: https://lore.kernel.org/r/20240219-device_cleanup-peci-v1-1-0727662616f7@marliere.net Signed-off-by: Iwona Winiarska --- drivers/peci/core.c | 2 +- drivers/peci/device.c | 2 +- drivers/peci/internal.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/peci/core.c b/drivers/peci/core.c index b2f8db967e9b..8f8bda2f2a62 100644 --- a/drivers/peci/core.c +++ b/drivers/peci/core.c @@ -25,7 +25,7 @@ static void peci_controller_dev_release(struct device *dev) kfree(controller); } -struct device_type peci_controller_type = { +const struct device_type peci_controller_type = { .release = peci_controller_dev_release, }; diff --git a/drivers/peci/device.c b/drivers/peci/device.c index e6b0bffb14f4..ee01f03c29b7 100644 --- a/drivers/peci/device.c +++ b/drivers/peci/device.c @@ -246,7 +246,7 @@ static void peci_device_release(struct device *dev) kfree(device); } -struct device_type peci_device_type = { +const struct device_type peci_device_type = { .groups = peci_device_groups, .release = peci_device_release, }; diff --git a/drivers/peci/internal.h b/drivers/peci/internal.h index 8a896c256c5f..506bafcccbbf 100644 --- a/drivers/peci/internal.h +++ b/drivers/peci/internal.h @@ -75,7 +75,7 @@ struct peci_device_id { u8 model; }; -extern struct device_type peci_device_type; +extern const struct device_type peci_device_type; extern const struct attribute_group *peci_device_groups[]; int peci_device_create(struct peci_controller *controller, u8 addr); @@ -129,7 +129,7 @@ void peci_driver_unregister(struct peci_driver *driver); #define module_peci_driver(__peci_driver) \ module_driver(__peci_driver, peci_driver_register, peci_driver_unregister) -extern struct device_type peci_controller_type; +extern const struct device_type peci_controller_type; int peci_controller_scan_devices(struct peci_controller *controller); From bf901e49a9a894021d4366a311223ea5e891c5e4 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 25 Mar 2024 11:40:06 +0100 Subject: [PATCH 156/319] dt-bindings: arm: qcom,coresight-tpda: drop redundant type from ports "in-ports" and "out-ports" are defined by graph schema, so defining its type is redundant. Acked-by: Rob Herring Signed-off-by: Krzysztof Kozlowski Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240325104007.30723-1-krzysztof.kozlowski@linaro.org --- Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml index ea3c5db6b52d..7fbd855a66a0 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml @@ -66,13 +66,11 @@ properties: - const: apb_pclk in-ports: - type: object description: | Input connections from TPDM to TPDA $ref: /schemas/graph.yaml#/properties/ports out-ports: - type: object description: | Output connections from the TPDA to legacy CoreSight trace bus. $ref: /schemas/graph.yaml#/properties/ports From e0b97ddaf4b5babdc30be8a665052af8bcb4610e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 25 Mar 2024 11:40:07 +0100 Subject: [PATCH 157/319] dt-bindings: arm: qcom,coresight-tpda: fix indentation in the example Fix triple-space indentation to double-space in the example DTS. Acked-by: Rob Herring Signed-off-by: Krzysztof Kozlowski Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240325104007.30723-2-krzysztof.kozlowski@linaro.org --- .../bindings/arm/qcom,coresight-tpda.yaml | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml index 7fbd855a66a0..76163abed655 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml @@ -95,33 +95,31 @@ examples: # minimum tpda definition. - | tpda@6004000 { - compatible = "qcom,coresight-tpda", "arm,primecell"; - reg = <0x6004000 0x1000>; + compatible = "qcom,coresight-tpda", "arm,primecell"; + reg = <0x6004000 0x1000>; - clocks = <&aoss_qmp>; - clock-names = "apb_pclk"; + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; - in-ports { - #address-cells = <1>; - #size-cells = <0>; + in-ports { + #address-cells = <1>; + #size-cells = <0>; port@0 { reg = <0>; tpda_qdss_0_in_tpdm_dcc: endpoint { - remote-endpoint = - <&tpdm_dcc_out_tpda_qdss_0>; - }; + remote-endpoint = <&tpdm_dcc_out_tpda_qdss_0>; + }; }; }; - out-ports { - port { - tpda_qdss_out_funnel_in0: endpoint { - remote-endpoint = - <&funnel_in0_in_tpda_qdss>; - }; + out-ports { + port { + tpda_qdss_out_funnel_in0: endpoint { + remote-endpoint = <&funnel_in0_in_tpda_qdss>; }; - }; + }; + }; }; ... From caa41c47dab7e1054f587e592ab21296e3a6781c Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:33 +0530 Subject: [PATCH 158/319] coresight: etm4x: Fix unbalanced pm_runtime_enable() There is an unbalanced pm_runtime_enable() in etm4_probe_platform_dev() when etm4_probe() fails. This problem can be observed via the coresight etm4 module's (load -> unload -> load) sequence when etm4_probe() fails in etm4_probe_platform_dev(). [ 63.379943] coresight-etm4x 7040000.etm: Unbalanced pm_runtime_enable! [ 63.393630] coresight-etm4x 7140000.etm: Unbalanced pm_runtime_enable! [ 63.407455] coresight-etm4x 7240000.etm: Unbalanced pm_runtime_enable! [ 63.420983] coresight-etm4x 7340000.etm: Unbalanced pm_runtime_enable! [ 63.420999] coresight-etm4x 7440000.etm: Unbalanced pm_runtime_enable! [ 63.441209] coresight-etm4x 7540000.etm: Unbalanced pm_runtime_enable! [ 63.454689] coresight-etm4x 7640000.etm: Unbalanced pm_runtime_enable! [ 63.474982] coresight-etm4x 7740000.etm: Unbalanced pm_runtime_enable! This fixes the above problem - with an explicit pm_runtime_disable() call when etm4_probe() fails during etm4_probe_platform_dev(). Cc: Lorenzo Pieralisi Cc: Hanjun Guo Cc: Sudeep Holla Cc: "Rafael J. Wysocki" Cc: Len Brown Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: Leo Yan Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Fixes: 5214b563588e ("coresight: etm4x: Add support for sysreg only devices") Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-2-anshuman.khandual@arm.com --- drivers/hwtracing/coresight/coresight-etm4x-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index c2ca4a02dfce..06a9b94b8c13 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -2213,6 +2213,9 @@ static int etm4_probe_platform_dev(struct platform_device *pdev) ret = etm4_probe(&pdev->dev); pm_runtime_put(&pdev->dev); + if (ret) + pm_runtime_disable(&pdev->dev); + return ret; } From 852e9a32058a73702c016a8be10c60375276e46e Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:34 +0530 Subject: [PATCH 159/319] coresight: stm: Extract device name from AMBA pid based table lookup Instead of using AMBA private data field, extract the device name from AMBA pid based table lookup using new coresight_get_uci_data_from_amba() helper. Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: coresight@lists.linaro.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: linux-stm32@st-md-mailman.stormreply.com Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-3-anshuman.khandual@arm.com --- drivers/hwtracing/coresight/coresight-priv.h | 10 ++++++++++ drivers/hwtracing/coresight/coresight-stm.c | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h index eb365236f9a9..fc3617642b01 100644 --- a/drivers/hwtracing/coresight/coresight-priv.h +++ b/drivers/hwtracing/coresight/coresight-priv.h @@ -222,6 +222,16 @@ static inline void *coresight_get_uci_data(const struct amba_id *id) return uci_id->data; } +static inline void *coresight_get_uci_data_from_amba(const struct amba_id *table, u32 pid) +{ + while (table->mask) { + if ((pid & table->mask) == table->id) + return coresight_get_uci_data(table); + table++; + }; + return NULL; +} + void coresight_release_platform_data(struct coresight_device *csdev, struct device *dev, struct coresight_platform_data *pdata); diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c index 974d37e5f94c..5ae24c33c846 100644 --- a/drivers/hwtracing/coresight/coresight-stm.c +++ b/drivers/hwtracing/coresight/coresight-stm.c @@ -800,6 +800,16 @@ static void stm_init_generic_data(struct stm_drvdata *drvdata, drvdata->stm.set_options = stm_generic_set_options; } +static const struct amba_id stm_ids[]; + +static char *stm_csdev_name(struct coresight_device *csdev) +{ + u32 stm_pid = coresight_get_pid(&csdev->access); + void *uci_data = coresight_get_uci_data_from_amba(stm_ids, stm_pid); + + return uci_data ? (char *)uci_data : "STM"; +} + static int stm_probe(struct amba_device *adev, const struct amba_id *id) { int ret, trace_id; @@ -896,7 +906,7 @@ static int stm_probe(struct amba_device *adev, const struct amba_id *id) pm_runtime_put(&adev->dev); dev_info(&drvdata->csdev->dev, "%s initialized\n", - (char *)coresight_get_uci_data(id)); + stm_csdev_name(drvdata->csdev)); return 0; cs_unregister: From 3ab210297c31b8fef1cfb2aa3d85b227b9ace09b Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:35 +0530 Subject: [PATCH 160/319] coresight: tmc: Extract device properties from AMBA pid based table lookup This extracts device properties from AMBA pid based table lookup. But first this modifies tmc_etr_setup_caps() to accept csdev access. Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: coresight@lists.linaro.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-4-anshuman.khandual@arm.com --- drivers/hwtracing/coresight/coresight-tmc-core.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c index 72005b0c633e..1a5ca65d8677 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-core.c +++ b/drivers/hwtracing/coresight/coresight-tmc-core.c @@ -370,16 +370,23 @@ static inline bool tmc_etr_has_non_secure_access(struct tmc_drvdata *drvdata) return (auth & TMC_AUTH_NSID_MASK) == 0x3; } +static const struct amba_id tmc_ids[]; + /* Detect and initialise the capabilities of a TMC ETR */ -static int tmc_etr_setup_caps(struct device *parent, u32 devid, void *dev_caps) +static int tmc_etr_setup_caps(struct device *parent, u32 devid, + struct csdev_access *access) { int rc; - u32 dma_mask = 0; + u32 tmc_pid, dma_mask = 0; struct tmc_drvdata *drvdata = dev_get_drvdata(parent); + void *dev_caps; if (!tmc_etr_has_non_secure_access(drvdata)) return -EACCES; + tmc_pid = coresight_get_pid(access); + dev_caps = coresight_get_uci_data_from_amba(tmc_ids, tmc_pid); + /* Set the unadvertised capabilities */ tmc_etr_init_caps(drvdata, (u32)(unsigned long)dev_caps); @@ -497,8 +504,7 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id) desc.type = CORESIGHT_DEV_TYPE_SINK; desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_SYSMEM; desc.ops = &tmc_etr_cs_ops; - ret = tmc_etr_setup_caps(dev, devid, - coresight_get_uci_data(id)); + ret = tmc_etr_setup_caps(dev, devid, &desc.access); if (ret) goto out; idr_init(&drvdata->idr); From 075b7cd7ad7dec8651a6a6654fa5ebae436ac00f Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:36 +0530 Subject: [PATCH 161/319] coresight: Add helpers registering/removing both AMBA and platform drivers This adds two different helpers i.e coresight_init_driver()/remove_driver() enabling coresight devices to register or remove AMBA and platform drivers. This changes replicator and funnel devices to use above new helpers. Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: Leo Yan Cc: Alexander Shishkin Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-5-anshuman.khandual@arm.com --- drivers/hwtracing/coresight/coresight-core.c | 29 +++++++++++++++++++ .../hwtracing/coresight/coresight-funnel.c | 19 ++---------- .../coresight/coresight-replicator.c | 20 ++----------- include/linux/coresight.h | 7 +++++ 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c index b83613e34289..9fc6f6b863e0 100644 --- a/drivers/hwtracing/coresight/coresight-core.c +++ b/drivers/hwtracing/coresight/coresight-core.c @@ -1398,6 +1398,35 @@ static void __exit coresight_exit(void) module_init(coresight_init); module_exit(coresight_exit); +int coresight_init_driver(const char *drv, struct amba_driver *amba_drv, + struct platform_driver *pdev_drv) +{ + int ret; + + ret = amba_driver_register(amba_drv); + if (ret) { + pr_err("%s: error registering AMBA driver\n", drv); + return ret; + } + + ret = platform_driver_register(pdev_drv); + if (!ret) + return 0; + + pr_err("%s: error registering platform driver\n", drv); + amba_driver_unregister(amba_drv); + return ret; +} +EXPORT_SYMBOL_GPL(coresight_init_driver); + +void coresight_remove_driver(struct amba_driver *amba_drv, + struct platform_driver *pdev_drv) +{ + amba_driver_unregister(amba_drv); + platform_driver_unregister(pdev_drv); +} +EXPORT_SYMBOL_GPL(coresight_remove_driver); + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Pratik Patel "); MODULE_AUTHOR("Mathieu Poirier "); diff --git a/drivers/hwtracing/coresight/coresight-funnel.c b/drivers/hwtracing/coresight/coresight-funnel.c index ef1a0abfee4e..ff3ea0670a5b 100644 --- a/drivers/hwtracing/coresight/coresight-funnel.c +++ b/drivers/hwtracing/coresight/coresight-funnel.c @@ -410,27 +410,12 @@ static struct amba_driver dynamic_funnel_driver = { static int __init funnel_init(void) { - int ret; - - ret = platform_driver_register(&static_funnel_driver); - if (ret) { - pr_info("Error registering platform driver\n"); - return ret; - } - - ret = amba_driver_register(&dynamic_funnel_driver); - if (ret) { - pr_info("Error registering amba driver\n"); - platform_driver_unregister(&static_funnel_driver); - } - - return ret; + return coresight_init_driver("funnel", &dynamic_funnel_driver, &static_funnel_driver); } static void __exit funnel_exit(void) { - platform_driver_unregister(&static_funnel_driver); - amba_driver_unregister(&dynamic_funnel_driver); + coresight_remove_driver(&dynamic_funnel_driver, &static_funnel_driver); } module_init(funnel_init); diff --git a/drivers/hwtracing/coresight/coresight-replicator.c b/drivers/hwtracing/coresight/coresight-replicator.c index 73452d9dc13b..ddb530a8436f 100644 --- a/drivers/hwtracing/coresight/coresight-replicator.c +++ b/drivers/hwtracing/coresight/coresight-replicator.c @@ -416,27 +416,13 @@ static struct amba_driver dynamic_replicator_driver = { static int __init replicator_init(void) { - int ret; - - ret = platform_driver_register(&static_replicator_driver); - if (ret) { - pr_info("Error registering platform driver\n"); - return ret; - } - - ret = amba_driver_register(&dynamic_replicator_driver); - if (ret) { - pr_info("Error registering amba driver\n"); - platform_driver_unregister(&static_replicator_driver); - } - - return ret; + return coresight_init_driver("replicator", &dynamic_replicator_driver, + &static_replicator_driver); } static void __exit replicator_exit(void) { - platform_driver_unregister(&static_replicator_driver); - amba_driver_unregister(&dynamic_replicator_driver); + coresight_remove_driver(&dynamic_replicator_driver, &static_replicator_driver); } module_init(replicator_init); diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 5f288d475490..653f1712eb77 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include /* Peripheral id registers (0xFD0-0xFEC) */ #define CORESIGHT_PERIPHIDR4 0xfd0 @@ -658,4 +660,9 @@ coresight_find_output_type(struct coresight_platform_data *pdata, enum coresight_dev_type type, union coresight_dev_subtype subtype); +int coresight_init_driver(const char *drv, struct amba_driver *amba_drv, + struct platform_driver *pdev_drv); + +void coresight_remove_driver(struct amba_driver *amba_drv, + struct platform_driver *pdev_drv); #endif /* _LINUX_COREISGHT_H */ From b448c4c72ca3327232dfb20d0065b693b3fcc2e2 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:37 +0530 Subject: [PATCH 162/319] coresight: replicator: Move ACPI support from AMBA driver to platform driver Add support for the dynamic replicator device in the platform driver, which can then be used on ACPI based platforms. This change would now allow runtime power management for replicator devices on ACPI based systems. The driver would try to enable the APB clock if available. Also, rename the code to reflect the fact that it now handles both static and dynamic replicators. But first this refactors replicator_probe() making sure it can be used both for platform and AMBA drivers, by moving the pm_runtime_put() to the callers. Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Tested-by: Sudeep Holla # Boot and driver probe only Acked-by: Sudeep Holla # For ACPI related changes Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-6-anshuman.khandual@arm.com --- drivers/acpi/arm64/amba.c | 1 - .../coresight/coresight-replicator.c | 68 ++++++++++++------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index 171b5c2c7edd..270f4e3819a2 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -27,7 +27,6 @@ static const struct acpi_device_id amba_id_list[] = { {"ARMHC503", 0}, /* ARM CoreSight Debug */ {"ARMHC979", 0}, /* ARM CoreSight TPIU */ {"ARMHC97C", 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */ - {"ARMHC98D", 0}, /* ARM CoreSight Dynamic Replicator */ {"ARMHC9CA", 0}, /* ARM CoreSight CATU */ {"ARMHC9FF", 0}, /* ARM CoreSight Dynamic Funnel */ {"", 0}, diff --git a/drivers/hwtracing/coresight/coresight-replicator.c b/drivers/hwtracing/coresight/coresight-replicator.c index ddb530a8436f..87e590cf1678 100644 --- a/drivers/hwtracing/coresight/coresight-replicator.c +++ b/drivers/hwtracing/coresight/coresight-replicator.c @@ -31,6 +31,7 @@ DEFINE_CORESIGHT_DEVLIST(replicator_devs, "replicator"); * @base: memory mapped base address for this component. Also indicates * whether this one is programmable or not. * @atclk: optional clock for the core parts of the replicator. + * @pclk: APB clock if present, otherwise NULL * @csdev: component vitals needed by the framework * @spinlock: serialize enable/disable operations. * @check_idfilter_val: check if the context is lost upon clock removal. @@ -38,6 +39,7 @@ DEFINE_CORESIGHT_DEVLIST(replicator_devs, "replicator"); struct replicator_drvdata { void __iomem *base; struct clk *atclk; + struct clk *pclk; struct coresight_device *csdev; spinlock_t spinlock; bool check_idfilter_val; @@ -243,6 +245,10 @@ static int replicator_probe(struct device *dev, struct resource *res) return ret; } + drvdata->pclk = coresight_get_enable_apb_pclk(dev); + if (IS_ERR(drvdata->pclk)) + return -ENODEV; + /* * Map the device base for dynamic-replicator, which has been * validated by AMBA core @@ -285,11 +291,12 @@ static int replicator_probe(struct device *dev, struct resource *res) } replicator_reset(drvdata); - pm_runtime_put(dev); out_disable_clk: if (ret && !IS_ERR_OR_NULL(drvdata->atclk)) clk_disable_unprepare(drvdata->atclk); + if (ret && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); return ret; } @@ -301,29 +308,34 @@ static int replicator_remove(struct device *dev) return 0; } -static int static_replicator_probe(struct platform_device *pdev) +static int replicator_platform_probe(struct platform_device *pdev) { + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); int ret; pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - /* Static replicators do not have programming base */ - ret = replicator_probe(&pdev->dev, NULL); - - if (ret) { - pm_runtime_put_noidle(&pdev->dev); + ret = replicator_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) pm_runtime_disable(&pdev->dev); - } return ret; } -static void static_replicator_remove(struct platform_device *pdev) +static void replicator_platform_remove(struct platform_device *pdev) { + struct replicator_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + if (WARN_ON(!drvdata)) + return; + replicator_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); } #ifdef CONFIG_PM @@ -334,6 +346,8 @@ static int replicator_runtime_suspend(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_disable_unprepare(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); return 0; } @@ -344,6 +358,8 @@ static int replicator_runtime_resume(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_prepare_enable(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_prepare_enable(drvdata->pclk); return 0; } #endif @@ -353,31 +369,32 @@ static const struct dev_pm_ops replicator_dev_pm_ops = { replicator_runtime_resume, NULL) }; -static const struct of_device_id static_replicator_match[] = { +static const struct of_device_id replicator_match[] = { {.compatible = "arm,coresight-replicator"}, {.compatible = "arm,coresight-static-replicator"}, {} }; -MODULE_DEVICE_TABLE(of, static_replicator_match); +MODULE_DEVICE_TABLE(of, replicator_match); #ifdef CONFIG_ACPI -static const struct acpi_device_id static_replicator_acpi_ids[] = { +static const struct acpi_device_id replicator_acpi_ids[] = { {"ARMHC985", 0, 0, 0}, /* ARM CoreSight Static Replicator */ + {"ARMHC98D", 0, 0, 0}, /* ARM CoreSight Dynamic Replicator */ {} }; -MODULE_DEVICE_TABLE(acpi, static_replicator_acpi_ids); +MODULE_DEVICE_TABLE(acpi, replicator_acpi_ids); #endif -static struct platform_driver static_replicator_driver = { - .probe = static_replicator_probe, - .remove_new = static_replicator_remove, +static struct platform_driver replicator_driver = { + .probe = replicator_platform_probe, + .remove_new = replicator_platform_remove, .driver = { - .name = "coresight-static-replicator", + .name = "coresight-replicator", /* THIS_MODULE is taken care of by platform_driver_register() */ - .of_match_table = of_match_ptr(static_replicator_match), - .acpi_match_table = ACPI_PTR(static_replicator_acpi_ids), + .of_match_table = of_match_ptr(replicator_match), + .acpi_match_table = ACPI_PTR(replicator_acpi_ids), .pm = &replicator_dev_pm_ops, .suppress_bind_attrs = true, }, @@ -386,7 +403,13 @@ static struct platform_driver static_replicator_driver = { static int dynamic_replicator_probe(struct amba_device *adev, const struct amba_id *id) { - return replicator_probe(&adev->dev, &adev->res); + int ret; + + ret = replicator_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + + return ret; } static void dynamic_replicator_remove(struct amba_device *adev) @@ -416,13 +439,12 @@ static struct amba_driver dynamic_replicator_driver = { static int __init replicator_init(void) { - return coresight_init_driver("replicator", &dynamic_replicator_driver, - &static_replicator_driver); + return coresight_init_driver("replicator", &dynamic_replicator_driver, &replicator_driver); } static void __exit replicator_exit(void) { - coresight_remove_driver(&dynamic_replicator_driver, &static_replicator_driver); + coresight_remove_driver(&dynamic_replicator_driver, &replicator_driver); } module_init(replicator_init); From 8e3227ddfbd7216f14186ec534f43e9dbcde717c Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:38 +0530 Subject: [PATCH 163/319] coresight: funnel: Move ACPI support from AMBA driver to platform driver Add support for the dynamic funnel device in the platform driver, which can then be used on ACPI based platforms. This change would allow runtime power management for ACPI based systems. The driver would try to enable the APB clock if available. Also, rename the code to reflect the fact that it now handles both static and dynamic funnels. But first this refactors funnel_probe() making sure it can be used both for platform and AMBA drivers, by moving the pm_runtime_put() to the callers. Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Tested-by: Sudeep Holla # Boot and driver probe only Acked-by: Sudeep Holla # For ACPI related changes Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-7-anshuman.khandual@arm.com --- drivers/acpi/arm64/amba.c | 1 - .../hwtracing/coresight/coresight-funnel.c | 72 ++++++++++++------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index 270f4e3819a2..afb6afb66967 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -28,7 +28,6 @@ static const struct acpi_device_id amba_id_list[] = { {"ARMHC979", 0}, /* ARM CoreSight TPIU */ {"ARMHC97C", 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */ {"ARMHC9CA", 0}, /* ARM CoreSight CATU */ - {"ARMHC9FF", 0}, /* ARM CoreSight Dynamic Funnel */ {"", 0}, }; diff --git a/drivers/hwtracing/coresight/coresight-funnel.c b/drivers/hwtracing/coresight/coresight-funnel.c index ff3ea0670a5b..616735dc80a6 100644 --- a/drivers/hwtracing/coresight/coresight-funnel.c +++ b/drivers/hwtracing/coresight/coresight-funnel.c @@ -36,6 +36,7 @@ DEFINE_CORESIGHT_DEVLIST(funnel_devs, "funnel"); * struct funnel_drvdata - specifics associated to a funnel component * @base: memory mapped base address for this component. * @atclk: optional clock for the core parts of the funnel. + * @pclk: APB clock if present, otherwise NULL * @csdev: component vitals needed by the framework. * @priority: port selection order. * @spinlock: serialize enable/disable operations. @@ -43,6 +44,7 @@ DEFINE_CORESIGHT_DEVLIST(funnel_devs, "funnel"); struct funnel_drvdata { void __iomem *base; struct clk *atclk; + struct clk *pclk; struct coresight_device *csdev; unsigned long priority; spinlock_t spinlock; @@ -236,6 +238,10 @@ static int funnel_probe(struct device *dev, struct resource *res) return ret; } + drvdata->pclk = coresight_get_enable_apb_pclk(dev); + if (IS_ERR(drvdata->pclk)) + return -ENODEV; + /* * Map the device base for dynamic-funnel, which has been * validated by AMBA core. @@ -272,12 +278,13 @@ static int funnel_probe(struct device *dev, struct resource *res) goto out_disable_clk; } - pm_runtime_put(dev); ret = 0; out_disable_clk: if (ret && !IS_ERR_OR_NULL(drvdata->atclk)) clk_disable_unprepare(drvdata->atclk); + if (ret && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); return ret; } @@ -298,6 +305,9 @@ static int funnel_runtime_suspend(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_disable_unprepare(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); + return 0; } @@ -308,6 +318,8 @@ static int funnel_runtime_resume(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_prepare_enable(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_prepare_enable(drvdata->pclk); return 0; } #endif @@ -316,55 +328,61 @@ static const struct dev_pm_ops funnel_dev_pm_ops = { SET_RUNTIME_PM_OPS(funnel_runtime_suspend, funnel_runtime_resume, NULL) }; -static int static_funnel_probe(struct platform_device *pdev) +static int funnel_platform_probe(struct platform_device *pdev) { + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); int ret; pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - /* Static funnel do not have programming base */ - ret = funnel_probe(&pdev->dev, NULL); - - if (ret) { - pm_runtime_put_noidle(&pdev->dev); + ret = funnel_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) pm_runtime_disable(&pdev->dev); - } return ret; } -static void static_funnel_remove(struct platform_device *pdev) +static void funnel_platform_remove(struct platform_device *pdev) { + struct funnel_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + if (WARN_ON(!drvdata)) + return; + funnel_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); } -static const struct of_device_id static_funnel_match[] = { +static const struct of_device_id funnel_match[] = { {.compatible = "arm,coresight-static-funnel"}, {} }; -MODULE_DEVICE_TABLE(of, static_funnel_match); +MODULE_DEVICE_TABLE(of, funnel_match); #ifdef CONFIG_ACPI -static const struct acpi_device_id static_funnel_ids[] = { - {"ARMHC9FE", 0, 0, 0}, +static const struct acpi_device_id funnel_acpi_ids[] = { + {"ARMHC9FE", 0, 0, 0}, /* ARM Coresight Static Funnel */ + {"ARMHC9FF", 0, 0, 0}, /* ARM CoreSight Dynamic Funnel */ {}, }; -MODULE_DEVICE_TABLE(acpi, static_funnel_ids); +MODULE_DEVICE_TABLE(acpi, funnel_acpi_ids); #endif -static struct platform_driver static_funnel_driver = { - .probe = static_funnel_probe, - .remove_new = static_funnel_remove, - .driver = { - .name = "coresight-static-funnel", +static struct platform_driver funnel_driver = { + .probe = funnel_platform_probe, + .remove_new = funnel_platform_remove, + .driver = { + .name = "coresight-funnel", /* THIS_MODULE is taken care of by platform_driver_register() */ - .of_match_table = static_funnel_match, - .acpi_match_table = ACPI_PTR(static_funnel_ids), + .of_match_table = funnel_match, + .acpi_match_table = ACPI_PTR(funnel_acpi_ids), .pm = &funnel_dev_pm_ops, .suppress_bind_attrs = true, }, @@ -373,7 +391,13 @@ static struct platform_driver static_funnel_driver = { static int dynamic_funnel_probe(struct amba_device *adev, const struct amba_id *id) { - return funnel_probe(&adev->dev, &adev->res); + int ret; + + ret = funnel_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + + return ret; } static void dynamic_funnel_remove(struct amba_device *adev) @@ -410,12 +434,12 @@ static struct amba_driver dynamic_funnel_driver = { static int __init funnel_init(void) { - return coresight_init_driver("funnel", &dynamic_funnel_driver, &static_funnel_driver); + return coresight_init_driver("funnel", &dynamic_funnel_driver, &funnel_driver); } static void __exit funnel_exit(void) { - coresight_remove_driver(&dynamic_funnel_driver, &static_funnel_driver); + coresight_remove_driver(&dynamic_funnel_driver, &funnel_driver); } module_init(funnel_init); From 23567323857d2073197953bb158413be1aceca8b Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:39 +0530 Subject: [PATCH 164/319] coresight: catu: Move ACPI support from AMBA driver to platform driver Add support for the catu devices in a new platform driver, which can then be used on ACPI based platforms. This change would now allow runtime power management for ACPI based systems. The driver would try to enable the APB clock if available. But first this renames and then refactors catu_probe() and catu_remove(), making sure it can be used both for platform and AMBA drivers. This also moves pm_runtime_put() from catu_probe() to the callers. Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Acked-by: Sudeep Holla # For ACPI related changes Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-8-anshuman.khandual@arm.com --- drivers/acpi/arm64/amba.c | 1 - drivers/hwtracing/coresight/coresight-catu.c | 138 ++++++++++++++++--- drivers/hwtracing/coresight/coresight-catu.h | 1 + 3 files changed, 120 insertions(+), 20 deletions(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index afb6afb66967..587061b0fd2f 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -27,7 +27,6 @@ static const struct acpi_device_id amba_id_list[] = { {"ARMHC503", 0}, /* ARM CoreSight Debug */ {"ARMHC979", 0}, /* ARM CoreSight TPIU */ {"ARMHC97C", 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */ - {"ARMHC9CA", 0}, /* ARM CoreSight CATU */ {"", 0}, }; diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c index 3949ded0d4fa..9712be6acd26 100644 --- a/drivers/hwtracing/coresight/coresight-catu.c +++ b/drivers/hwtracing/coresight/coresight-catu.c @@ -7,11 +7,13 @@ * Author: Suzuki K Poulose */ +#include #include #include #include #include #include +#include #include #include "coresight-catu.h" @@ -502,28 +504,20 @@ static const struct coresight_ops catu_ops = { .helper_ops = &catu_helper_ops, }; -static int catu_probe(struct amba_device *adev, const struct amba_id *id) +static int __catu_probe(struct device *dev, struct resource *res) { int ret = 0; u32 dma_mask; - struct catu_drvdata *drvdata; + struct catu_drvdata *drvdata = dev_get_drvdata(dev); struct coresight_desc catu_desc; struct coresight_platform_data *pdata = NULL; - struct device *dev = &adev->dev; void __iomem *base; catu_desc.name = coresight_alloc_device_name(&catu_devs, dev); if (!catu_desc.name) return -ENOMEM; - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) { - ret = -ENOMEM; - goto out; - } - - dev_set_drvdata(dev, drvdata); - base = devm_ioremap_resource(dev, &adev->res); + base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) { ret = PTR_ERR(base); goto out; @@ -567,19 +561,39 @@ static int catu_probe(struct amba_device *adev, const struct amba_id *id) drvdata->csdev = coresight_register(&catu_desc); if (IS_ERR(drvdata->csdev)) ret = PTR_ERR(drvdata->csdev); - else - pm_runtime_put(&adev->dev); out: return ret; } -static void catu_remove(struct amba_device *adev) +static int catu_probe(struct amba_device *adev, const struct amba_id *id) { - struct catu_drvdata *drvdata = dev_get_drvdata(&adev->dev); + struct catu_drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + amba_set_drvdata(adev, drvdata); + ret = __catu_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + + return ret; +} + +static void __catu_remove(struct device *dev) +{ + struct catu_drvdata *drvdata = dev_get_drvdata(dev); coresight_unregister(drvdata->csdev); } +static void catu_remove(struct amba_device *adev) +{ + __catu_remove(&adev->dev); +} + static struct amba_id catu_ids[] = { CS_AMBA_ID(0x000bb9ee), {}, @@ -598,13 +612,99 @@ static struct amba_driver catu_driver = { .id_table = catu_ids, }; +static int catu_platform_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct catu_drvdata *drvdata; + int ret = 0; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); + if (IS_ERR(drvdata->pclk)) + return -ENODEV; + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + dev_set_drvdata(&pdev->dev, drvdata); + ret = __catu_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) { + pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); + } + + return ret; +} + +static int catu_platform_remove(struct platform_device *pdev) +{ + struct catu_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + if (WARN_ON(!drvdata)) + return -ENODEV; + + __catu_remove(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); + return 0; +} + +#ifdef CONFIG_PM +static int catu_runtime_suspend(struct device *dev) +{ + struct catu_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); + return 0; +} + +static int catu_runtime_resume(struct device *dev) +{ + struct catu_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_prepare_enable(drvdata->pclk); + return 0; +} +#endif + +static const struct dev_pm_ops catu_dev_pm_ops = { + SET_RUNTIME_PM_OPS(catu_runtime_suspend, catu_runtime_resume, NULL) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id catu_acpi_ids[] = { + {"ARMHC9CA", 0, 0, 0}, /* ARM CoreSight CATU */ + {}, +}; + +MODULE_DEVICE_TABLE(acpi, catu_acpi_ids); +#endif + +static struct platform_driver catu_platform_driver = { + .probe = catu_platform_probe, + .remove = catu_platform_remove, + .driver = { + .name = "coresight-catu-platform", + .acpi_match_table = ACPI_PTR(catu_acpi_ids), + .suppress_bind_attrs = true, + .pm = &catu_dev_pm_ops, + }, +}; + static int __init catu_init(void) { int ret; - ret = amba_driver_register(&catu_driver); - if (ret) - pr_info("Error registering catu driver\n"); + ret = coresight_init_driver("catu", &catu_driver, &catu_platform_driver); tmc_etr_set_catu_ops(&etr_catu_buf_ops); return ret; } @@ -612,7 +712,7 @@ static int __init catu_init(void) static void __exit catu_exit(void) { tmc_etr_remove_catu_ops(); - amba_driver_unregister(&catu_driver); + coresight_remove_driver(&catu_driver, &catu_platform_driver); } module_init(catu_init); diff --git a/drivers/hwtracing/coresight/coresight-catu.h b/drivers/hwtracing/coresight/coresight-catu.h index 442e034bbfba..141feac1c14b 100644 --- a/drivers/hwtracing/coresight/coresight-catu.h +++ b/drivers/hwtracing/coresight/coresight-catu.h @@ -61,6 +61,7 @@ #define CATU_IRQEN_OFF 0x0 struct catu_drvdata { + struct clk *pclk; void __iomem *base; struct coresight_device *csdev; int irq; From 3d83d4d4904a47626854b6bba44127d21a7dc713 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:40 +0530 Subject: [PATCH 165/319] coresight: tpiu: Move ACPI support from AMBA driver to platform driver Add support for the tpiu device in the platform driver, which can then be used on ACPI based platforms. This change would now allow runtime power management for ACPI based systems. The driver would try to enable the APB clock if available. But first this renames and then refactors tpiu_probe() and tpiu_remove(), making sure it can be used both for platform and AMBA drivers. This also moves pm_runtime_put() from tpiu_probe() to the callers. While here, this also sorts the included headers in alphabetic order. Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Tested-by: Sudeep Holla # Boot and driver probe only Acked-by: Sudeep Holla # For ACPI related changes Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-9-anshuman.khandual@arm.com --- drivers/acpi/arm64/amba.c | 1 - drivers/hwtracing/coresight/coresight-tpiu.c | 121 ++++++++++++++++--- 2 files changed, 102 insertions(+), 20 deletions(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index 587061b0fd2f..6d24a8f7914b 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -25,7 +25,6 @@ static const struct acpi_device_id amba_id_list[] = { {"ARMHC501", 0}, /* ARM CoreSight ETR */ {"ARMHC502", 0}, /* ARM CoreSight STM */ {"ARMHC503", 0}, /* ARM CoreSight Debug */ - {"ARMHC979", 0}, /* ARM CoreSight TPIU */ {"ARMHC97C", 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */ {"", 0}, }; diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c index 29024f880fda..6bf3039839c1 100644 --- a/drivers/hwtracing/coresight/coresight-tpiu.c +++ b/drivers/hwtracing/coresight/coresight-tpiu.c @@ -5,17 +5,19 @@ * Description: CoreSight Trace Port Interface Unit driver */ -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "coresight-priv.h" @@ -52,11 +54,13 @@ DEFINE_CORESIGHT_DEVLIST(tpiu_devs, "tpiu"); /* * @base: memory mapped base address for this component. * @atclk: optional clock for the core parts of the TPIU. + * @pclk: APB clock if present, otherwise NULL * @csdev: component vitals needed by the framework. */ struct tpiu_drvdata { void __iomem *base; struct clk *atclk; + struct clk *pclk; struct coresight_device *csdev; spinlock_t spinlock; }; @@ -122,14 +126,12 @@ static const struct coresight_ops tpiu_cs_ops = { .sink_ops = &tpiu_sink_ops, }; -static int tpiu_probe(struct amba_device *adev, const struct amba_id *id) +static int __tpiu_probe(struct device *dev, struct resource *res) { int ret; void __iomem *base; - struct device *dev = &adev->dev; struct coresight_platform_data *pdata = NULL; struct tpiu_drvdata *drvdata; - struct resource *res = &adev->res; struct coresight_desc desc = { 0 }; desc.name = coresight_alloc_device_name(&tpiu_devs, dev); @@ -142,12 +144,16 @@ static int tpiu_probe(struct amba_device *adev, const struct amba_id *id) spin_lock_init(&drvdata->spinlock); - drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */ + drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ if (!IS_ERR(drvdata->atclk)) { ret = clk_prepare_enable(drvdata->atclk); if (ret) return ret; } + + drvdata->pclk = coresight_get_enable_apb_pclk(dev); + if (IS_ERR(drvdata->pclk)) + return -ENODEV; dev_set_drvdata(dev, drvdata); /* Validity for the resource is already checked by the AMBA core */ @@ -173,21 +179,34 @@ static int tpiu_probe(struct amba_device *adev, const struct amba_id *id) desc.dev = dev; drvdata->csdev = coresight_register(&desc); - if (!IS_ERR(drvdata->csdev)) { - pm_runtime_put(&adev->dev); + if (!IS_ERR(drvdata->csdev)) return 0; - } return PTR_ERR(drvdata->csdev); } -static void tpiu_remove(struct amba_device *adev) +static int tpiu_probe(struct amba_device *adev, const struct amba_id *id) { - struct tpiu_drvdata *drvdata = dev_get_drvdata(&adev->dev); + int ret; + + ret = __tpiu_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + return ret; +} + +static void __tpiu_remove(struct device *dev) +{ + struct tpiu_drvdata *drvdata = dev_get_drvdata(dev); coresight_unregister(drvdata->csdev); } +static void tpiu_remove(struct amba_device *adev) +{ + __tpiu_remove(&adev->dev); +} + #ifdef CONFIG_PM static int tpiu_runtime_suspend(struct device *dev) { @@ -196,6 +215,8 @@ static int tpiu_runtime_suspend(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_disable_unprepare(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); return 0; } @@ -206,6 +227,8 @@ static int tpiu_runtime_resume(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_prepare_enable(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_prepare_enable(drvdata->pclk); return 0; } #endif @@ -245,7 +268,67 @@ static struct amba_driver tpiu_driver = { .id_table = tpiu_ids, }; -module_amba_driver(tpiu_driver); +static int tpiu_platform_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int ret; + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = __tpiu_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int tpiu_platform_remove(struct platform_device *pdev) +{ + struct tpiu_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + if (WARN_ON(!drvdata)) + return -ENODEV; + + __tpiu_remove(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id tpiu_acpi_ids[] = { + {"ARMHC979", 0, 0, 0}, /* ARM CoreSight TPIU */ + {} +}; +MODULE_DEVICE_TABLE(acpi, tpiu_acpi_ids); +#endif + +static struct platform_driver tpiu_platform_driver = { + .probe = tpiu_platform_probe, + .remove = tpiu_platform_remove, + .driver = { + .name = "coresight-tpiu-platform", + .acpi_match_table = ACPI_PTR(tpiu_acpi_ids), + .suppress_bind_attrs = true, + .pm = &tpiu_dev_pm_ops, + }, +}; + +static int __init tpiu_init(void) +{ + return coresight_init_driver("tpiu", &tpiu_driver, &tpiu_platform_driver); +} + +static void __exit tpiu_exit(void) +{ + coresight_remove_driver(&tpiu_driver, &tpiu_platform_driver); +} +module_init(tpiu_init); +module_exit(tpiu_exit); MODULE_AUTHOR("Pratik Patel "); MODULE_AUTHOR("Mathieu Poirier "); From 70750e257aab4cd4d755294ae8b9834214a624bb Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:41 +0530 Subject: [PATCH 166/319] coresight: tmc: Move ACPI support from AMBA driver to platform driver Add support for the tmc devices in the platform driver, which can then be used on ACPI based platforms. This change would now allow runtime power management for ACPI based systems. The driver would try to enable the APB clock if available. But first this renames and then refactors tmc_probe() and tmc_remove(), making sure it can be used both for platform and AMBA drivers. This also moves pm_runtime_put() from tmc_probe() to the callers. Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Tested-by: Sudeep Holla # Boot and driver probe only Acked-by: Sudeep Holla # For ACPI related changes Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-10-anshuman.khandual@arm.com --- drivers/acpi/arm64/amba.c | 2 - .../hwtracing/coresight/coresight-tmc-core.c | 140 ++++++++++++++++-- drivers/hwtracing/coresight/coresight-tmc.h | 2 + 3 files changed, 127 insertions(+), 17 deletions(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index 6d24a8f7914b..d3c1defa7bc8 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -22,10 +22,8 @@ static const struct acpi_device_id amba_id_list[] = { {"ARMH0061", 0}, /* PL061 GPIO Device */ {"ARMH0330", 0}, /* ARM DMA Controller DMA-330 */ - {"ARMHC501", 0}, /* ARM CoreSight ETR */ {"ARMHC502", 0}, /* ARM CoreSight STM */ {"ARMHC503", 0}, /* ARM CoreSight Debug */ - {"ARMHC97C", 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */ {"", 0}, }; diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c index 1a5ca65d8677..c9aaf3567565 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-core.c +++ b/drivers/hwtracing/coresight/coresight-tmc-core.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "coresight-priv.h" #include "coresight-tmc.h" @@ -444,24 +446,17 @@ static u32 tmc_etr_get_max_burst_size(struct device *dev) return burst_size; } -static int tmc_probe(struct amba_device *adev, const struct amba_id *id) +static int __tmc_probe(struct device *dev, struct resource *res) { int ret = 0; u32 devid; void __iomem *base; - struct device *dev = &adev->dev; struct coresight_platform_data *pdata = NULL; - struct tmc_drvdata *drvdata; - struct resource *res = &adev->res; + struct tmc_drvdata *drvdata = dev_get_drvdata(dev); struct coresight_desc desc = { 0 }; struct coresight_dev_list *dev_list = NULL; ret = -ENOMEM; - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - goto out; - - dev_set_drvdata(dev, drvdata); /* Validity for the resource is already checked by the AMBA core */ base = devm_ioremap_resource(dev, res); @@ -536,7 +531,7 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id) ret = PTR_ERR(pdata); goto out; } - adev->dev.platform_data = pdata; + dev->platform_data = pdata; desc.pdata = pdata; drvdata->csdev = coresight_register(&desc); @@ -551,12 +546,27 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id) ret = misc_register(&drvdata->miscdev); if (ret) coresight_unregister(drvdata->csdev); - else - pm_runtime_put(&adev->dev); out: return ret; } +static int tmc_probe(struct amba_device *adev, const struct amba_id *id) +{ + struct tmc_drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + amba_set_drvdata(adev, drvdata); + ret = __tmc_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + + return ret; +} + static void tmc_shutdown(struct amba_device *adev) { unsigned long flags; @@ -579,9 +589,9 @@ out: spin_unlock_irqrestore(&drvdata->spinlock, flags); } -static void tmc_remove(struct amba_device *adev) +static void __tmc_remove(struct device *dev) { - struct tmc_drvdata *drvdata = dev_get_drvdata(&adev->dev); + struct tmc_drvdata *drvdata = dev_get_drvdata(dev); /* * Since misc_open() holds a refcount on the f_ops, which is @@ -592,6 +602,11 @@ static void tmc_remove(struct amba_device *adev) coresight_unregister(drvdata->csdev); } +static void tmc_remove(struct amba_device *adev) +{ + __tmc_remove(&adev->dev); +} + static const struct amba_id tmc_ids[] = { CS_AMBA_ID(0x000bb961), /* Coresight SoC 600 TMC-ETR/ETS */ @@ -617,7 +632,102 @@ static struct amba_driver tmc_driver = { .id_table = tmc_ids, }; -module_amba_driver(tmc_driver); +static int tmc_platform_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct tmc_drvdata *drvdata; + int ret = 0; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); + if (IS_ERR(drvdata->pclk)) + return -ENODEV; + + dev_set_drvdata(&pdev->dev, drvdata); + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = __tmc_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int tmc_platform_remove(struct platform_device *pdev) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + if (WARN_ON(!drvdata)) + return -ENODEV; + + __tmc_remove(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); + return 0; +} + +#ifdef CONFIG_PM +static int tmc_runtime_suspend(struct device *dev) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); + return 0; +} + +static int tmc_runtime_resume(struct device *dev) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_prepare_enable(drvdata->pclk); + return 0; +} +#endif + +static const struct dev_pm_ops tmc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(tmc_runtime_suspend, tmc_runtime_resume, NULL) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id tmc_acpi_ids[] = { + {"ARMHC501", 0, 0, 0}, /* ARM CoreSight ETR */ + {"ARMHC97C", 0, 0, 0}, /* ARM CoreSight SoC-400 TMC, SoC-600 ETF/ETB */ + {}, +}; +MODULE_DEVICE_TABLE(acpi, tmc_acpi_ids); +#endif + +static struct platform_driver tmc_platform_driver = { + .probe = tmc_platform_probe, + .remove = tmc_platform_remove, + .driver = { + .name = "coresight-tmc-platform", + .acpi_match_table = ACPI_PTR(tmc_acpi_ids), + .suppress_bind_attrs = true, + .pm = &tmc_dev_pm_ops, + }, +}; + +static int __init tmc_init(void) +{ + return coresight_init_driver("tmc", &tmc_driver, &tmc_platform_driver); +} + +static void __exit tmc_exit(void) +{ + coresight_remove_driver(&tmc_driver, &tmc_platform_driver); +} +module_init(tmc_init); +module_exit(tmc_exit); MODULE_AUTHOR("Pratik Patel "); MODULE_DESCRIPTION("Arm CoreSight Trace Memory Controller driver"); diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index cef979c897e6..c77763b49de0 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -166,6 +166,7 @@ struct etr_buf { /** * struct tmc_drvdata - specifics associated to an TMC component + * @pclk: APB clock if present, otherwise NULL * @base: memory mapped base address for this component. * @csdev: component vitals needed by the framework. * @miscdev: specifics to handle "/dev/xyz.tmc" entry. @@ -189,6 +190,7 @@ struct etr_buf { * @perf_buf: PERF buffer for ETR. */ struct tmc_drvdata { + struct clk *pclk; void __iomem *base; struct coresight_device *csdev; struct miscdevice miscdev; From 057256aaacc862356417a75ceeb1cfa41408dbf0 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:42 +0530 Subject: [PATCH 167/319] coresight: stm: Move ACPI support from AMBA driver to platform driver Add support for the stm devices in the platform driver, which can then be used on ACPI based platforms. This change would now allow runtime power management for ACPI based systems. The driver would try to enable the APB clock if available. But first this renames and then refactors stm_probe() and stm_remove(), making sure it can be used both for platform and AMBA drivers. Also this moves pm_runtime_put() from stm_probe() to the callers. Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: Maxime Coquelin Cc: Alexandre Torgue Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Cc: linux-stm32@st-md-mailman.stormreply.com Tested-by: Sudeep Holla # Boot and driver probe only Acked-by: Sudeep Holla # For ACPI related changes Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-11-anshuman.khandual@arm.com --- drivers/acpi/arm64/amba.c | 1 - drivers/hwtracing/coresight/coresight-stm.c | 104 ++++++++++++++++++-- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index d3c1defa7bc8..bec0976541da 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -22,7 +22,6 @@ static const struct acpi_device_id amba_id_list[] = { {"ARMH0061", 0}, /* PL061 GPIO Device */ {"ARMH0330", 0}, /* ARM DMA Controller DMA-330 */ - {"ARMHC502", 0}, /* ARM CoreSight STM */ {"ARMHC503", 0}, /* ARM CoreSight Debug */ {"", 0}, }; diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c index 5ae24c33c846..cbf7f17556f8 100644 --- a/drivers/hwtracing/coresight/coresight-stm.c +++ b/drivers/hwtracing/coresight/coresight-stm.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include "coresight-priv.h" #include "coresight-trace-id.h" @@ -115,6 +117,7 @@ DEFINE_CORESIGHT_DEVLIST(stm_devs, "stm"); * struct stm_drvdata - specifics associated to an STM component * @base: memory mapped base address for this component. * @atclk: optional clock for the core parts of the STM. + * @pclk: APB clock if present, otherwise NULL * @csdev: component vitals needed by the framework. * @spinlock: only one at a time pls. * @chs: the channels accociated to this STM. @@ -131,6 +134,7 @@ DEFINE_CORESIGHT_DEVLIST(stm_devs, "stm"); struct stm_drvdata { void __iomem *base; struct clk *atclk; + struct clk *pclk; struct coresight_device *csdev; spinlock_t spinlock; struct channel_space chs; @@ -810,14 +814,12 @@ static char *stm_csdev_name(struct coresight_device *csdev) return uci_data ? (char *)uci_data : "STM"; } -static int stm_probe(struct amba_device *adev, const struct amba_id *id) +static int __stm_probe(struct device *dev, struct resource *res) { int ret, trace_id; void __iomem *base; - struct device *dev = &adev->dev; struct coresight_platform_data *pdata = NULL; struct stm_drvdata *drvdata; - struct resource *res = &adev->res; struct resource ch_res; struct coresight_desc desc = { 0 }; @@ -829,12 +831,16 @@ static int stm_probe(struct amba_device *adev, const struct amba_id *id) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */ + drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ if (!IS_ERR(drvdata->atclk)) { ret = clk_prepare_enable(drvdata->atclk); if (ret) return ret; } + + drvdata->pclk = coresight_get_enable_apb_pclk(dev); + if (IS_ERR(drvdata->pclk)) + return -ENODEV; dev_set_drvdata(dev, drvdata); base = devm_ioremap_resource(dev, res); @@ -882,7 +888,7 @@ static int stm_probe(struct amba_device *adev, const struct amba_id *id) ret = PTR_ERR(pdata); goto stm_unregister; } - adev->dev.platform_data = pdata; + dev->platform_data = pdata; desc.type = CORESIGHT_DEV_TYPE_SOURCE; desc.subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE; @@ -903,8 +909,6 @@ static int stm_probe(struct amba_device *adev, const struct amba_id *id) } drvdata->traceid = (u8)trace_id; - pm_runtime_put(&adev->dev); - dev_info(&drvdata->csdev->dev, "%s initialized\n", stm_csdev_name(drvdata->csdev)); return 0; @@ -917,9 +921,20 @@ stm_unregister: return ret; } -static void stm_remove(struct amba_device *adev) +static int stm_probe(struct amba_device *adev, const struct amba_id *id) { - struct stm_drvdata *drvdata = dev_get_drvdata(&adev->dev); + int ret; + + ret = __stm_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + + return ret; +} + +static void __stm_remove(struct device *dev) +{ + struct stm_drvdata *drvdata = dev_get_drvdata(dev); coresight_trace_id_put_system_id(drvdata->traceid); coresight_unregister(drvdata->csdev); @@ -927,6 +942,11 @@ static void stm_remove(struct amba_device *adev) stm_unregister_device(&drvdata->stm); } +static void stm_remove(struct amba_device *adev) +{ + __stm_remove(&adev->dev); +} + #ifdef CONFIG_PM static int stm_runtime_suspend(struct device *dev) { @@ -935,6 +955,8 @@ static int stm_runtime_suspend(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_disable_unprepare(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); return 0; } @@ -945,6 +967,8 @@ static int stm_runtime_resume(struct device *dev) if (drvdata && !IS_ERR(drvdata->atclk)) clk_prepare_enable(drvdata->atclk); + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_prepare_enable(drvdata->pclk); return 0; } #endif @@ -973,7 +997,67 @@ static struct amba_driver stm_driver = { .id_table = stm_ids, }; -module_amba_driver(stm_driver); +static int stm_platform_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int ret = 0; + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = __stm_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int stm_platform_remove(struct platform_device *pdev) +{ + struct stm_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + if (WARN_ON(!drvdata)) + return -ENODEV; + + __stm_remove(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id stm_acpi_ids[] = { + {"ARMHC502", 0, 0, 0}, /* ARM CoreSight STM */ + {}, +}; +MODULE_DEVICE_TABLE(acpi, stm_acpi_ids); +#endif + +static struct platform_driver stm_platform_driver = { + .probe = stm_platform_probe, + .remove = stm_platform_remove, + .driver = { + .name = "coresight-stm-platform", + .acpi_match_table = ACPI_PTR(stm_acpi_ids), + .suppress_bind_attrs = true, + .pm = &stm_dev_pm_ops, + }, +}; + +static int __init stm_init(void) +{ + return coresight_init_driver("stm", &stm_driver, &stm_platform_driver); +} + +static void __exit stm_exit(void) +{ + coresight_remove_driver(&stm_driver, &stm_platform_driver); +} +module_init(stm_init); +module_exit(stm_exit); MODULE_AUTHOR("Pratik Patel "); MODULE_DESCRIPTION("Arm CoreSight System Trace Macrocell driver"); From 965edae4e6a2bca1f6e56d2c56d992417a53bba4 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 14 Mar 2024 11:28:43 +0530 Subject: [PATCH 168/319] coresight: debug: Move ACPI support from AMBA driver to platform driver Add support for the cpu debug devices in a new platform driver, which can then be used on ACPI based platforms. This change would now allow runtime power management for ACPI based systems. The driver would try to enable the APB clock if available. But first this renames and then refactors debug_probe() and debug_remove(), making sure they can be used both for platform and AMBA drivers. Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: linux-acpi@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: coresight@lists.linaro.org Acked-by: Sudeep Holla # For ACPI related changes Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240314055843.2625883-12-anshuman.khandual@arm.com --- drivers/acpi/arm64/amba.c | 1 - .../hwtracing/coresight/coresight-cpu-debug.c | 138 ++++++++++++++++-- 2 files changed, 122 insertions(+), 17 deletions(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index bec0976541da..e1f0bbb8f393 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -22,7 +22,6 @@ static const struct acpi_device_id amba_id_list[] = { {"ARMH0061", 0}, /* PL061 GPIO Device */ {"ARMH0330", 0}, /* ARM DMA Controller DMA-330 */ - {"ARMHC503", 0}, /* ARM CoreSight Debug */ {"", 0}, }; diff --git a/drivers/hwtracing/coresight/coresight-cpu-debug.c b/drivers/hwtracing/coresight/coresight-cpu-debug.c index 1874df7c6a73..3263fc86cb66 100644 --- a/drivers/hwtracing/coresight/coresight-cpu-debug.c +++ b/drivers/hwtracing/coresight/coresight-cpu-debug.c @@ -4,6 +4,7 @@ * * Author: Leo Yan */ +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +86,7 @@ #define DEBUG_WAIT_TIMEOUT 32000 struct debug_drvdata { + struct clk *pclk; void __iomem *base; struct device *dev; int cpu; @@ -557,18 +560,12 @@ static void debug_func_exit(void) debugfs_remove_recursive(debug_debugfs_dir); } -static int debug_probe(struct amba_device *adev, const struct amba_id *id) +static int __debug_probe(struct device *dev, struct resource *res) { + struct debug_drvdata *drvdata = dev_get_drvdata(dev); void __iomem *base; - struct device *dev = &adev->dev; - struct debug_drvdata *drvdata; - struct resource *res = &adev->res; int ret; - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - drvdata->cpu = coresight_get_cpu(dev); if (drvdata->cpu < 0) return drvdata->cpu; @@ -579,10 +576,7 @@ static int debug_probe(struct amba_device *adev, const struct amba_id *id) return -EBUSY; } - drvdata->dev = &adev->dev; - amba_set_drvdata(adev, drvdata); - - /* Validity for the resource is already checked by the AMBA core */ + drvdata->dev = dev; base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) return PTR_ERR(base); @@ -629,10 +623,21 @@ err: return ret; } -static void debug_remove(struct amba_device *adev) +static int debug_probe(struct amba_device *adev, const struct amba_id *id) { - struct device *dev = &adev->dev; - struct debug_drvdata *drvdata = amba_get_drvdata(adev); + struct debug_drvdata *drvdata; + + drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + amba_set_drvdata(adev, drvdata); + return __debug_probe(&adev->dev, &adev->res); +} + +static void __debug_remove(struct device *dev) +{ + struct debug_drvdata *drvdata = dev_get_drvdata(dev); per_cpu(debug_drvdata, drvdata->cpu) = NULL; @@ -646,6 +651,11 @@ static void debug_remove(struct amba_device *adev) debug_func_exit(); } +static void debug_remove(struct amba_device *adev) +{ + __debug_remove(&adev->dev); +} + static const struct amba_cs_uci_id uci_id_debug[] = { { /* CPU Debug UCI data */ @@ -677,7 +687,103 @@ static struct amba_driver debug_driver = { .id_table = debug_ids, }; -module_amba_driver(debug_driver); +static int debug_platform_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct debug_drvdata *drvdata; + int ret = 0; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); + if (IS_ERR(drvdata->pclk)) + return -ENODEV; + + dev_set_drvdata(&pdev->dev, drvdata); + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = __debug_probe(&pdev->dev, res); + if (ret) { + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); + } + return ret; +} + +static int debug_platform_remove(struct platform_device *pdev) +{ + struct debug_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + if (WARN_ON(!drvdata)) + return -ENODEV; + + __debug_remove(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!IS_ERR_OR_NULL(drvdata->pclk)) + clk_put(drvdata->pclk); + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id debug_platform_ids[] = { + {"ARMHC503", 0, 0, 0}, /* ARM CoreSight Debug */ + {}, +}; +MODULE_DEVICE_TABLE(acpi, debug_platform_ids); +#endif + +#ifdef CONFIG_PM +static int debug_runtime_suspend(struct device *dev) +{ + struct debug_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_disable_unprepare(drvdata->pclk); + return 0; +} + +static int debug_runtime_resume(struct device *dev) +{ + struct debug_drvdata *drvdata = dev_get_drvdata(dev); + + if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) + clk_prepare_enable(drvdata->pclk); + return 0; +} +#endif + +static const struct dev_pm_ops debug_dev_pm_ops = { + SET_RUNTIME_PM_OPS(debug_runtime_suspend, debug_runtime_resume, NULL) +}; + +static struct platform_driver debug_platform_driver = { + .probe = debug_platform_probe, + .remove = debug_platform_remove, + .driver = { + .name = "coresight-debug-platform", + .acpi_match_table = ACPI_PTR(debug_platform_ids), + .suppress_bind_attrs = true, + .pm = &debug_dev_pm_ops, + }, +}; + +static int __init debug_init(void) +{ + return coresight_init_driver("debug", &debug_driver, &debug_platform_driver); +} + +static void __exit debug_exit(void) +{ + coresight_remove_driver(&debug_driver, &debug_platform_driver); +} +module_init(debug_init); +module_exit(debug_exit); MODULE_AUTHOR("Leo Yan "); MODULE_DESCRIPTION("ARM Coresight CPU Debug Driver"); From 499eb311513fa30e136d01abfb316c482379afa1 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 17 Apr 2024 12:04:22 -0500 Subject: [PATCH 169/319] 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 170/319] 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 171/319] 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 172/319] 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 173/319] 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 174/319] 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 @@ err_free: 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 175/319] 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 @@ out_unlock: 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 176/319] 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 @@ out_unlock: 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 177/319] 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 178/319] 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 179/319] 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 180/319] 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 181/319] 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 182/319] 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 183/319] 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 184/319] 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 185/319] 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) From 1e7ba33fa591de1cf60afffcabb45600b3607025 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Fri, 12 Apr 2024 15:26:59 +0100 Subject: [PATCH 186/319] coresight: etm4x: Do not hardcode IOMEM access for register restore When we restore the register state for ETM4x, while coming back from CPU idle, we hardcode IOMEM access. This is wrong and could blow up for an ETM with system instructions access (and for ETE). Fixes: f5bd523690d2 ("coresight: etm4x: Convert all register accesses") Reported-by: Yabin Cui Reviewed-by: Mike Leach Signed-off-by: Suzuki K Poulose Tested-by: Yabin Cui Link: https://lore.kernel.org/r/20240412142702.2882478-2-suzuki.poulose@arm.com --- drivers/hwtracing/coresight/coresight-etm4x-core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 06a9b94b8c13..b9c6c544d759 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -1843,8 +1843,10 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) { int i; struct etmv4_save_state *state = drvdata->save_state; - struct csdev_access tmp_csa = CSDEV_ACCESS_IOMEM(drvdata->base); - struct csdev_access *csa = &tmp_csa; + struct csdev_access *csa = &drvdata->csdev->access; + + if (WARN_ON(!drvdata->csdev)) + return; etm4_cs_unlock(drvdata, csa); etm4x_relaxed_write32(csa, state->trcclaimset, TRCCLAIMSET); From 5eb3a0c2c52368cb9902e9a6ea04888e093c487d Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Fri, 12 Apr 2024 15:27:00 +0100 Subject: [PATCH 187/319] coresight: etm4x: Do not save/restore Data trace control registers ETM4x doesn't support Data trace on A class CPUs. As such do not access the Data trace control registers during CPU idle. This could cause problems for ETE. While at it, remove all references to the Data trace control registers. Fixes: f188b5e76aae ("coresight: etm4x: Save/restore state across CPU low power states") Reported-by: Yabin Cui Reviewed-by: Mike Leach Signed-off-by: Suzuki K Poulose Tested-by: Yabin Cui Link: https://lore.kernel.org/r/20240412142702.2882478-3-suzuki.poulose@arm.com --- .../coresight/coresight-etm4x-core.c | 6 ---- drivers/hwtracing/coresight/coresight-etm4x.h | 28 ------------------- 2 files changed, 34 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index b9c6c544d759..a9765d45a0ee 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -1739,9 +1739,6 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata) state->trcvissctlr = etm4x_read32(csa, TRCVISSCTLR); if (drvdata->nr_pe_cmp) state->trcvipcssctlr = etm4x_read32(csa, TRCVIPCSSCTLR); - state->trcvdctlr = etm4x_read32(csa, TRCVDCTLR); - state->trcvdsacctlr = etm4x_read32(csa, TRCVDSACCTLR); - state->trcvdarcctlr = etm4x_read32(csa, TRCVDARCCTLR); for (i = 0; i < drvdata->nrseqstate - 1; i++) state->trcseqevr[i] = etm4x_read32(csa, TRCSEQEVRn(i)); @@ -1872,9 +1869,6 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, state->trcvissctlr, TRCVISSCTLR); if (drvdata->nr_pe_cmp) etm4x_relaxed_write32(csa, state->trcvipcssctlr, TRCVIPCSSCTLR); - etm4x_relaxed_write32(csa, state->trcvdctlr, TRCVDCTLR); - etm4x_relaxed_write32(csa, state->trcvdsacctlr, TRCVDSACCTLR); - etm4x_relaxed_write32(csa, state->trcvdarcctlr, TRCVDARCCTLR); for (i = 0; i < drvdata->nrseqstate - 1; i++) etm4x_relaxed_write32(csa, state->trcseqevr[i], TRCSEQEVRn(i)); diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index 9ea678bc2e8e..9e430f72bbd6 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -43,9 +43,6 @@ #define TRCVIIECTLR 0x084 #define TRCVISSCTLR 0x088 #define TRCVIPCSSCTLR 0x08C -#define TRCVDCTLR 0x0A0 -#define TRCVDSACCTLR 0x0A4 -#define TRCVDARCCTLR 0x0A8 /* Derived resources registers */ #define TRCSEQEVRn(n) (0x100 + (n * 4)) /* n = 0-2 */ #define TRCSEQRSTEVR 0x118 @@ -90,9 +87,6 @@ /* Address Comparator registers n = 0-15 */ #define TRCACVRn(n) (0x400 + (n * 8)) #define TRCACATRn(n) (0x480 + (n * 8)) -/* Data Value Comparator Value registers, n = 0-7 */ -#define TRCDVCVRn(n) (0x500 + (n * 16)) -#define TRCDVCMRn(n) (0x580 + (n * 16)) /* ContextID/Virtual ContextID comparators, n = 0-7 */ #define TRCCIDCVRn(n) (0x600 + (n * 8)) #define TRCVMIDCVRn(n) (0x640 + (n * 8)) @@ -272,9 +266,6 @@ /* List of registers accessible via System instructions */ #define ETM4x_ONLY_SYSREG_LIST(op, val) \ CASE_##op((val), TRCPROCSELR) \ - CASE_##op((val), TRCVDCTLR) \ - CASE_##op((val), TRCVDSACCTLR) \ - CASE_##op((val), TRCVDARCCTLR) \ CASE_##op((val), TRCOSLAR) #define ETM_COMMON_SYSREG_LIST(op, val) \ @@ -422,22 +413,6 @@ CASE_##op((val), TRCACATRn(13)) \ CASE_##op((val), TRCACATRn(14)) \ CASE_##op((val), TRCACATRn(15)) \ - CASE_##op((val), TRCDVCVRn(0)) \ - CASE_##op((val), TRCDVCVRn(1)) \ - CASE_##op((val), TRCDVCVRn(2)) \ - CASE_##op((val), TRCDVCVRn(3)) \ - CASE_##op((val), TRCDVCVRn(4)) \ - CASE_##op((val), TRCDVCVRn(5)) \ - CASE_##op((val), TRCDVCVRn(6)) \ - CASE_##op((val), TRCDVCVRn(7)) \ - CASE_##op((val), TRCDVCMRn(0)) \ - CASE_##op((val), TRCDVCMRn(1)) \ - CASE_##op((val), TRCDVCMRn(2)) \ - CASE_##op((val), TRCDVCMRn(3)) \ - CASE_##op((val), TRCDVCMRn(4)) \ - CASE_##op((val), TRCDVCMRn(5)) \ - CASE_##op((val), TRCDVCMRn(6)) \ - CASE_##op((val), TRCDVCMRn(7)) \ CASE_##op((val), TRCCIDCVRn(0)) \ CASE_##op((val), TRCCIDCVRn(1)) \ CASE_##op((val), TRCCIDCVRn(2)) \ @@ -907,9 +882,6 @@ struct etmv4_save_state { u32 trcviiectlr; u32 trcvissctlr; u32 trcvipcssctlr; - u32 trcvdctlr; - u32 trcvdsacctlr; - u32 trcvdarcctlr; u32 trcseqevr[ETM_MAX_SEQ_STATES]; u32 trcseqrstevr; From 46bf8d7cd8530eca607379033b9bc4ac5590a0cd Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Fri, 12 Apr 2024 15:27:01 +0100 Subject: [PATCH 188/319] coresight: etm4x: Safe access for TRCQCLTR ETM4x implements TRCQCLTR only when the Q elements are supported and the Q element filtering is supported (TRCIDR0.QFILT). Access to the register otherwise could be fatal. Fix this by tracking the availability, like the others. Fixes: f188b5e76aae ("coresight: etm4x: Save/restore state across CPU low power states") Reported-by: Yabin Cui Reviewed-by: Mike Leach Signed-off-by: Suzuki K Poulose Tested-by: Yabin Cui Link: https://lore.kernel.org/r/20240412142702.2882478-4-suzuki.poulose@arm.com --- drivers/hwtracing/coresight/coresight-etm4x-core.c | 8 ++++++-- drivers/hwtracing/coresight/coresight-etm4x.h | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index a9765d45a0ee..8643b77e50f4 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -1240,6 +1240,8 @@ static void etm4_init_arch_data(void *info) drvdata->nr_event = FIELD_GET(TRCIDR0_NUMEVENT_MASK, etmidr0); /* QSUPP, bits[16:15] Q element support field */ drvdata->q_support = FIELD_GET(TRCIDR0_QSUPP_MASK, etmidr0); + if (drvdata->q_support) + drvdata->q_filt = !!(etmidr0 & TRCIDR0_QFILT); /* TSSIZE, bits[28:24] Global timestamp size field */ drvdata->ts_size = FIELD_GET(TRCIDR0_TSSIZE_MASK, etmidr0); @@ -1732,7 +1734,8 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata) state->trcccctlr = etm4x_read32(csa, TRCCCCTLR); state->trcbbctlr = etm4x_read32(csa, TRCBBCTLR); state->trctraceidr = etm4x_read32(csa, TRCTRACEIDR); - state->trcqctlr = etm4x_read32(csa, TRCQCTLR); + if (drvdata->q_filt) + state->trcqctlr = etm4x_read32(csa, TRCQCTLR); state->trcvictlr = etm4x_read32(csa, TRCVICTLR); state->trcviiectlr = etm4x_read32(csa, TRCVIIECTLR); @@ -1862,7 +1865,8 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, state->trcccctlr, TRCCCCTLR); etm4x_relaxed_write32(csa, state->trcbbctlr, TRCBBCTLR); etm4x_relaxed_write32(csa, state->trctraceidr, TRCTRACEIDR); - etm4x_relaxed_write32(csa, state->trcqctlr, TRCQCTLR); + if (drvdata->q_filt) + etm4x_relaxed_write32(csa, state->trcqctlr, TRCQCTLR); etm4x_relaxed_write32(csa, state->trcvictlr, TRCVICTLR); etm4x_relaxed_write32(csa, state->trcviiectlr, TRCVIIECTLR); diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index 9e430f72bbd6..9e9165f62e81 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -135,6 +135,7 @@ #define TRCIDR0_TRCCCI BIT(7) #define TRCIDR0_RETSTACK BIT(9) #define TRCIDR0_NUMEVENT_MASK GENMASK(11, 10) +#define TRCIDR0_QFILT BIT(14) #define TRCIDR0_QSUPP_MASK GENMASK(16, 15) #define TRCIDR0_TSSIZE_MASK GENMASK(28, 24) @@ -954,6 +955,7 @@ struct etmv4_save_state { * @os_unlock: True if access to management registers is allowed. * @instrp0: Tracing of load and store instructions * as P0 elements is supported. + * @q_filt: Q element filtering support, if Q elements are supported. * @trcbb: Indicates if the trace unit supports branch broadcast tracing. * @trccond: If the trace unit supports conditional * instruction tracing. @@ -1016,6 +1018,7 @@ struct etmv4_drvdata { bool boot_enable; bool os_unlock; bool instrp0; + bool q_filt; bool trcbb; bool trccond; bool retstack; From d6fc00d0f640d6010b51054aa8b0fd191177dbc9 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Fri, 12 Apr 2024 15:27:02 +0100 Subject: [PATCH 189/319] coresight: etm4x: Fix access to resource selector registers Resource selector pair 0 is always implemented and reserved. We must not touch it, even during save/restore for CPU Idle. Rest of the driver is well behaved. Fix the offending ones. Reported-by: Yabin Cui Fixes: f188b5e76aae ("coresight: etm4x: Save/restore state across CPU low power states") Signed-off-by: Suzuki K Poulose Tested-by: Yabin Cui Reviewed-by: Mike Leach Link: https://lore.kernel.org/r/20240412142702.2882478-5-suzuki.poulose@arm.com --- drivers/hwtracing/coresight/coresight-etm4x-core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 8643b77e50f4..a0bdfabddbc6 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -1758,7 +1758,8 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata) state->trccntvr[i] = etm4x_read32(csa, TRCCNTVRn(i)); } - for (i = 0; i < drvdata->nr_resource * 2; i++) + /* Resource selector pair 0 is reserved */ + for (i = 2; i < drvdata->nr_resource * 2; i++) state->trcrsctlr[i] = etm4x_read32(csa, TRCRSCTLRn(i)); for (i = 0; i < drvdata->nr_ss_cmp; i++) { @@ -1889,7 +1890,8 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, state->trccntvr[i], TRCCNTVRn(i)); } - for (i = 0; i < drvdata->nr_resource * 2; i++) + /* Resource selector pair 0 is reserved */ + for (i = 2; i < drvdata->nr_resource * 2; i++) etm4x_relaxed_write32(csa, state->trcrsctlr[i], TRCRSCTLRn(i)); for (i = 0; i < drvdata->nr_ss_cmp; i++) { From 89d5d9e9500826cbd3b15ea7b6e8d9fae966f073 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 22 Apr 2024 17:48:50 +0300 Subject: [PATCH 190/319] counter: Don't use "proxy" headers Update header inclusions to follow IWYU (Include What You Use) principle. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240422144850.2031076-1-andriy.shevchenko@linux.intel.com Signed-off-by: William Breathitt Gray --- include/linux/counter.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/counter.h b/include/linux/counter.h index cd35d8574ee2..426b7d58a438 100644 --- a/include/linux/counter.h +++ b/include/linux/counter.h @@ -6,14 +6,15 @@ #ifndef _COUNTER_H_ #define _COUNTER_H_ +#include #include #include -#include #include #include #include #include #include + #include struct counter_device; From 1eb3816c2757f6ba755a313837cc16dd193f5766 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 15 Apr 2024 17:23:28 +0300 Subject: [PATCH 191/319] fpga: ice40-spi: Don't use "proxy" headers Update header inclusions to follow IWYU (Include What You Use) principle. Signed-off-by: Andy Shevchenko Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240415142428.853812-1-andriy.shevchenko@linux.intel.com Signed-off-by: Xu Yilun --- drivers/fpga/ice40-spi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/fpga/ice40-spi.c b/drivers/fpga/ice40-spi.c index c0028ae4c5b7..62c30266130d 100644 --- a/drivers/fpga/ice40-spi.c +++ b/drivers/fpga/ice40-spi.c @@ -10,8 +10,8 @@ #include #include +#include #include -#include #include #include @@ -199,7 +199,7 @@ static struct spi_driver ice40_fpga_driver = { .probe = ice40_fpga_probe, .driver = { .name = "ice40spi", - .of_match_table = of_match_ptr(ice40_fpga_of_match), + .of_match_table = ice40_fpga_of_match, }, .id_table = ice40_fpga_spi_ids, }; From 7c2dafa60e7a74bd5fd584e35d3176a439495a16 Mon Sep 17 00:00:00 2001 From: Peter Colberg Date: Mon, 15 Apr 2024 19:57:43 -0400 Subject: [PATCH 192/319] fpga: dfl: remove unused function is_dfl_feature_present() The function is_dfl_feature_present() was added but never used. Fixes: 5b57d02a2f94 ("fpga: dfl: add feature device infrastructure") Signed-off-by: Peter Colberg Reviewed-by: Matthew Gerlach Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240415235743.3045-1-peter.colberg@intel.com Signed-off-by: Xu Yilun --- drivers/fpga/dfl.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 1d724a28f00a..5063d73b0d82 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -437,11 +437,6 @@ void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u16 id) return NULL; } -static inline bool is_dfl_feature_present(struct device *dev, u16 id) -{ - return !!dfl_get_feature_ioaddr_by_id(dev, id); -} - static inline struct device *dfl_fpga_pdata_to_parent(struct dfl_feature_platform_data *pdata) { From dbca8048b33c8e612d3bb7d314cfe002d9478daa Mon Sep 17 00:00:00 2001 From: Peter Colberg Date: Mon, 15 Apr 2024 19:59:37 -0400 Subject: [PATCH 193/319] fpga: dfl: remove unused member pdata from struct dfl_{afu,fme} The member pdata in struct dfl_{afu,fme} is set in function {afu,fme}_dev_init(), respectively, but never used. Fixes: 857a26222ff7 ("fpga: dfl: afu: add afu sub feature support") Fixes: 29de76240e86 ("fpga: dfl: fme: add partial reconfiguration sub feature support") Signed-off-by: Peter Colberg Reviewed-by: Matthew Gerlach Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240415235937.3121-1-peter.colberg@intel.com Signed-off-by: Xu Yilun --- drivers/fpga/dfl-afu-main.c | 2 -- drivers/fpga/dfl-afu.h | 3 --- drivers/fpga/dfl-fme-main.c | 2 -- drivers/fpga/dfl-fme.h | 2 -- 4 files changed, 9 deletions(-) diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index c0a75ca360d6..6b97c073849e 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -858,8 +858,6 @@ static int afu_dev_init(struct platform_device *pdev) if (!afu) return -ENOMEM; - afu->pdata = pdata; - mutex_lock(&pdata->lock); dfl_fpga_pdata_set_private(pdata, afu); afu_mmio_region_init(pdata); diff --git a/drivers/fpga/dfl-afu.h b/drivers/fpga/dfl-afu.h index 674e9772f0ea..7bef3e300aa2 100644 --- a/drivers/fpga/dfl-afu.h +++ b/drivers/fpga/dfl-afu.h @@ -67,7 +67,6 @@ struct dfl_afu_dma_region { * @regions: the mmio region linked list of this afu feature device. * @dma_regions: root of dma regions rb tree. * @num_umsgs: num of umsgs. - * @pdata: afu platform device's pdata. */ struct dfl_afu { u64 region_cur_offset; @@ -75,8 +74,6 @@ struct dfl_afu { u8 num_umsgs; struct list_head regions; struct rb_root dma_regions; - - struct dfl_feature_platform_data *pdata; }; /* hold pdata->lock when call __afu_port_enable/disable */ diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c index a2b5da0093da..864924f68f5e 100644 --- a/drivers/fpga/dfl-fme-main.c +++ b/drivers/fpga/dfl-fme-main.c @@ -679,8 +679,6 @@ static int fme_dev_init(struct platform_device *pdev) if (!fme) return -ENOMEM; - fme->pdata = pdata; - mutex_lock(&pdata->lock); dfl_fpga_pdata_set_private(pdata, fme); mutex_unlock(&pdata->lock); diff --git a/drivers/fpga/dfl-fme.h b/drivers/fpga/dfl-fme.h index 4195dd68193e..a566dbc2b485 100644 --- a/drivers/fpga/dfl-fme.h +++ b/drivers/fpga/dfl-fme.h @@ -24,13 +24,11 @@ * @mgr: FME's FPGA manager platform device. * @region_list: linked list of FME's FPGA regions. * @bridge_list: linked list of FME's FPGA bridges. - * @pdata: fme platform device's pdata. */ struct dfl_fme { struct platform_device *mgr; struct list_head region_list; struct list_head bridge_list; - struct dfl_feature_platform_data *pdata; }; extern const struct dfl_feature_ops fme_pr_mgmt_ops; From b7c0e1ecee403a43abc89eb3e75672b01ff2ece9 Mon Sep 17 00:00:00 2001 From: Marco Pagani Date: Fri, 19 Apr 2024 10:35:59 +0200 Subject: [PATCH 194/319] fpga: region: add owner module and take its refcount The current implementation of the fpga region assumes that the low-level module registers a driver for the parent device and uses its owner pointer to take the module's refcount. This approach is problematic since it can lead to a null pointer dereference while attempting to get the region during programming if the parent device does not have a driver. To address this problem, add a module owner pointer to the fpga_region struct and use it to take the module's refcount. Modify the functions for registering a region to take an additional owner module parameter and rename them to avoid conflicts. Use the old function names for helper macros that automatically set the module that registers the region as the owner. This ensures compatibility with existing low-level control modules and reduces the chances of registering a region without setting the owner. Also, update the documentation to keep it consistent with the new interface for registering an fpga region. Fixes: 0fa20cdfcc1f ("fpga: fpga-region: device tree control for FPGA") Suggested-by: Greg Kroah-Hartman Suggested-by: Xu Yilun Reviewed-by: Russ Weight Signed-off-by: Marco Pagani Acked-by: Xu Yilun Link: https://lore.kernel.org/r/20240419083601.77403-1-marpagan@redhat.com Signed-off-by: Xu Yilun --- Documentation/driver-api/fpga/fpga-region.rst | 13 ++++++---- drivers/fpga/fpga-region.c | 24 +++++++++++-------- include/linux/fpga/fpga-region.h | 13 +++++++--- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Documentation/driver-api/fpga/fpga-region.rst b/Documentation/driver-api/fpga/fpga-region.rst index dc55d60a0b4a..2d03b5fb7657 100644 --- a/Documentation/driver-api/fpga/fpga-region.rst +++ b/Documentation/driver-api/fpga/fpga-region.rst @@ -46,13 +46,16 @@ API to add a new FPGA region ---------------------------- * struct fpga_region - The FPGA region struct -* struct fpga_region_info - Parameter structure for fpga_region_register_full() -* fpga_region_register_full() - Create and register an FPGA region using the +* struct fpga_region_info - Parameter structure for __fpga_region_register_full() +* __fpga_region_register_full() - Create and register an FPGA region using the fpga_region_info structure to provide the full flexibility of options -* fpga_region_register() - Create and register an FPGA region using standard +* __fpga_region_register() - Create and register an FPGA region using standard arguments * fpga_region_unregister() - Unregister an FPGA region +Helper macros ``fpga_region_register()`` and ``fpga_region_register_full()`` +automatically set the module that registers the FPGA region as the owner. + The FPGA region's probe function will need to get a reference to the FPGA Manager it will be using to do the programming. This usually would happen during the region's probe function. @@ -82,10 +85,10 @@ following APIs to handle building or tearing down that list. :functions: fpga_region_info .. kernel-doc:: drivers/fpga/fpga-region.c - :functions: fpga_region_register_full + :functions: __fpga_region_register_full .. kernel-doc:: drivers/fpga/fpga-region.c - :functions: fpga_region_register + :functions: __fpga_region_register .. kernel-doc:: drivers/fpga/fpga-region.c :functions: fpga_region_unregister diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index b364a929425c..753cd142503e 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -53,7 +53,7 @@ static struct fpga_region *fpga_region_get(struct fpga_region *region) } get_device(dev); - if (!try_module_get(dev->parent->driver->owner)) { + if (!try_module_get(region->ops_owner)) { put_device(dev); mutex_unlock(®ion->mutex); return ERR_PTR(-ENODEV); @@ -75,7 +75,7 @@ static void fpga_region_put(struct fpga_region *region) dev_dbg(dev, "put\n"); - module_put(dev->parent->driver->owner); + module_put(region->ops_owner); put_device(dev); mutex_unlock(®ion->mutex); } @@ -181,14 +181,16 @@ static struct attribute *fpga_region_attrs[] = { ATTRIBUTE_GROUPS(fpga_region); /** - * fpga_region_register_full - create and register an FPGA Region device + * __fpga_region_register_full - create and register an FPGA Region device * @parent: device parent * @info: parameters for FPGA Region + * @owner: module containing the get_bridges function * * Return: struct fpga_region or ERR_PTR() */ struct fpga_region * -fpga_region_register_full(struct device *parent, const struct fpga_region_info *info) +__fpga_region_register_full(struct device *parent, const struct fpga_region_info *info, + struct module *owner) { struct fpga_region *region; int id, ret = 0; @@ -213,6 +215,7 @@ fpga_region_register_full(struct device *parent, const struct fpga_region_info * region->compat_id = info->compat_id; region->priv = info->priv; region->get_bridges = info->get_bridges; + region->ops_owner = owner; mutex_init(®ion->mutex); INIT_LIST_HEAD(®ion->bridge_list); @@ -241,13 +244,14 @@ err_free: return ERR_PTR(ret); } -EXPORT_SYMBOL_GPL(fpga_region_register_full); +EXPORT_SYMBOL_GPL(__fpga_region_register_full); /** - * fpga_region_register - create and register an FPGA Region device + * __fpga_region_register - create and register an FPGA Region device * @parent: device parent * @mgr: manager that programs this region * @get_bridges: optional function to get bridges to a list + * @owner: module containing the get_bridges function * * This simple version of the register function should be sufficient for most users. * The fpga_region_register_full() function is available for users that need to @@ -256,17 +260,17 @@ EXPORT_SYMBOL_GPL(fpga_region_register_full); * Return: struct fpga_region or ERR_PTR() */ struct fpga_region * -fpga_region_register(struct device *parent, struct fpga_manager *mgr, - int (*get_bridges)(struct fpga_region *)) +__fpga_region_register(struct device *parent, struct fpga_manager *mgr, + int (*get_bridges)(struct fpga_region *), struct module *owner) { struct fpga_region_info info = { 0 }; info.mgr = mgr; info.get_bridges = get_bridges; - return fpga_region_register_full(parent, &info); + return __fpga_region_register_full(parent, &info, owner); } -EXPORT_SYMBOL_GPL(fpga_region_register); +EXPORT_SYMBOL_GPL(__fpga_region_register); /** * fpga_region_unregister - unregister an FPGA region diff --git a/include/linux/fpga/fpga-region.h b/include/linux/fpga/fpga-region.h index 9d4d32909340..5fbc05fe70a6 100644 --- a/include/linux/fpga/fpga-region.h +++ b/include/linux/fpga/fpga-region.h @@ -36,6 +36,7 @@ struct fpga_region_info { * @mgr: FPGA manager * @info: FPGA image info * @compat_id: FPGA region id for compatibility check. + * @ops_owner: module containing the get_bridges function * @priv: private data * @get_bridges: optional function to get bridges to a list */ @@ -46,6 +47,7 @@ struct fpga_region { struct fpga_manager *mgr; struct fpga_image_info *info; struct fpga_compat_id *compat_id; + struct module *ops_owner; void *priv; int (*get_bridges)(struct fpga_region *region); }; @@ -58,12 +60,17 @@ fpga_region_class_find(struct device *start, const void *data, int fpga_region_program_fpga(struct fpga_region *region); +#define fpga_region_register_full(parent, info) \ + __fpga_region_register_full(parent, info, THIS_MODULE) struct fpga_region * -fpga_region_register_full(struct device *parent, const struct fpga_region_info *info); +__fpga_region_register_full(struct device *parent, const struct fpga_region_info *info, + struct module *owner); +#define fpga_region_register(parent, mgr, get_bridges) \ + __fpga_region_register(parent, mgr, get_bridges, THIS_MODULE) struct fpga_region * -fpga_region_register(struct device *parent, struct fpga_manager *mgr, - int (*get_bridges)(struct fpga_region *)); +__fpga_region_register(struct device *parent, struct fpga_manager *mgr, + int (*get_bridges)(struct fpga_region *), struct module *owner); void fpga_region_unregister(struct fpga_region *region); #endif /* _FPGA_REGION_H */ From e22810ab3f5e0de0151a5a0d05a54cfc39a780a8 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Tue, 23 Apr 2024 10:41:33 +0800 Subject: [PATCH 195/319] mei: bus: constify the struct mei_cl_bus_type usage Now that the driver core can properly handle constant struct bus_type, move the mei_cl_bus_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Signed-off-by: Kunwu Chan Acked-by: Tomas Winkler Link: https://lore.kernel.org/r/20240423024133.1890455-1-chentao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index f9bcff197615..99393f610cdf 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -1327,7 +1327,7 @@ static int mei_cl_device_uevent(const struct device *dev, struct kobj_uevent_env return 0; } -static struct bus_type mei_cl_bus_type = { +static const struct bus_type mei_cl_bus_type = { .name = "mei", .dev_groups = mei_cldev_groups, .match = mei_cl_device_match, From 002026272ba523d2ae62a13b0a9febb0cdaf576e Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Wed, 24 Apr 2024 10:36:05 +0800 Subject: [PATCH 196/319] coresight: stm: Remove duplicate linux/acpi.h header ./drivers/hwtracing/coresight/coresight-stm.c: linux/acpi.h is included more than once. Reported-by: Abaci Robot Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=8871 Signed-off-by: Jiapeng Chong Reviewed-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240424023605.90489-1-jiapeng.chong@linux.alibaba.com --- drivers/hwtracing/coresight/coresight-stm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c index cbf7f17556f8..9ca04fac53fa 100644 --- a/drivers/hwtracing/coresight/coresight-stm.c +++ b/drivers/hwtracing/coresight/coresight-stm.c @@ -30,7 +30,6 @@ #include #include #include -#include #include "coresight-priv.h" #include "coresight-trace-id.h" From e8293395b9caa57e38cc05b9102d4ec7835b0a0f Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Wed, 24 Apr 2024 10:24:20 +0800 Subject: [PATCH 197/319] coresight: Remove duplicate linux/amba/bus.h header ./include/linux/coresight.h: linux/amba/bus.h is included more than once. Reported-by: Abaci Robot Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=8869 Signed-off-by: Jiapeng Chong Reviewed-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240424022420.58516-1-jiapeng.chong@linux.alibaba.com --- include/linux/coresight.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 653f1712eb77..f09ace92176e 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -12,7 +12,6 @@ #include #include #include -#include #include /* Peripheral id registers (0xFD0-0xFEC) */ From c01cb419104c4187f2a148f72a7dcc67e77801a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 23 Apr 2024 10:06:58 +0200 Subject: [PATCH 198/319] coresight: catu: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Fixes: 23567323857d ("coresight: catu: Move ACPI support from AMBA driver to platform driver") Signed-off-by: Uwe Kleine-König Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/16a7123efa7d97ae62a02ccbf9b39d146b066860.1713858615.git.u.kleine-koenig@pengutronix.de --- drivers/hwtracing/coresight/coresight-catu.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c index 9712be6acd26..02d9daf265d6 100644 --- a/drivers/hwtracing/coresight/coresight-catu.c +++ b/drivers/hwtracing/coresight/coresight-catu.c @@ -642,18 +642,17 @@ static int catu_platform_probe(struct platform_device *pdev) return ret; } -static int catu_platform_remove(struct platform_device *pdev) +static void catu_platform_remove(struct platform_device *pdev) { struct catu_drvdata *drvdata = dev_get_drvdata(&pdev->dev); if (WARN_ON(!drvdata)) - return -ENODEV; + return; __catu_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!IS_ERR_OR_NULL(drvdata->pclk)) clk_put(drvdata->pclk); - return 0; } #ifdef CONFIG_PM @@ -691,7 +690,7 @@ MODULE_DEVICE_TABLE(acpi, catu_acpi_ids); static struct platform_driver catu_platform_driver = { .probe = catu_platform_probe, - .remove = catu_platform_remove, + .remove_new = catu_platform_remove, .driver = { .name = "coresight-catu-platform", .acpi_match_table = ACPI_PTR(catu_acpi_ids), From 971c2b107b5742ef1dfa3a405cd5ee7033502d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 23 Apr 2024 10:06:59 +0200 Subject: [PATCH 199/319] coresight: debug: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Fixes: 965edae4e6a2 ("coresight: debug: Move ACPI support from AMBA driver to platform driver") Signed-off-by: Uwe Kleine-König Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/fb3d7db82a2490ace41c51b16ad17ef61549e2f6.1713858615.git.u.kleine-koenig@pengutronix.de --- drivers/hwtracing/coresight/coresight-cpu-debug.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-cpu-debug.c b/drivers/hwtracing/coresight/coresight-cpu-debug.c index 3263fc86cb66..75962dae9aa1 100644 --- a/drivers/hwtracing/coresight/coresight-cpu-debug.c +++ b/drivers/hwtracing/coresight/coresight-cpu-debug.c @@ -716,18 +716,17 @@ static int debug_platform_probe(struct platform_device *pdev) return ret; } -static int debug_platform_remove(struct platform_device *pdev) +static void debug_platform_remove(struct platform_device *pdev) { struct debug_drvdata *drvdata = dev_get_drvdata(&pdev->dev); if (WARN_ON(!drvdata)) - return -ENODEV; + return; __debug_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!IS_ERR_OR_NULL(drvdata->pclk)) clk_put(drvdata->pclk); - return 0; } #ifdef CONFIG_ACPI @@ -764,7 +763,7 @@ static const struct dev_pm_ops debug_dev_pm_ops = { static struct platform_driver debug_platform_driver = { .probe = debug_platform_probe, - .remove = debug_platform_remove, + .remove_new = debug_platform_remove, .driver = { .name = "coresight-debug-platform", .acpi_match_table = ACPI_PTR(debug_platform_ids), From 981d5f92ca2e33d1b0ba3e3563de52dc9661fb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 23 Apr 2024 10:07:00 +0200 Subject: [PATCH 200/319] coresight: stm: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Fixes: 057256aaacc8 ("coresight: stm: Move ACPI support from AMBA driver to platform driver") Signed-off-by: Uwe Kleine-König Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/3fefa60744fc68c9c4b40aeb69e34cda22582c4b.1713858615.git.u.kleine-koenig@pengutronix.de --- drivers/hwtracing/coresight/coresight-stm.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c index 9ca04fac53fa..e1c62820dfda 100644 --- a/drivers/hwtracing/coresight/coresight-stm.c +++ b/drivers/hwtracing/coresight/coresight-stm.c @@ -1013,18 +1013,17 @@ static int stm_platform_probe(struct platform_device *pdev) return ret; } -static int stm_platform_remove(struct platform_device *pdev) +static void stm_platform_remove(struct platform_device *pdev) { struct stm_drvdata *drvdata = dev_get_drvdata(&pdev->dev); if (WARN_ON(!drvdata)) - return -ENODEV; + return; __stm_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!IS_ERR_OR_NULL(drvdata->pclk)) clk_put(drvdata->pclk); - return 0; } #ifdef CONFIG_ACPI @@ -1037,7 +1036,7 @@ MODULE_DEVICE_TABLE(acpi, stm_acpi_ids); static struct platform_driver stm_platform_driver = { .probe = stm_platform_probe, - .remove = stm_platform_remove, + .remove_new = stm_platform_remove, .driver = { .name = "coresight-stm-platform", .acpi_match_table = ACPI_PTR(stm_acpi_ids), From 38a38da447579f683114edb1c8254491b4176853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 23 Apr 2024 10:07:01 +0200 Subject: [PATCH 201/319] coresight: tmc: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Fixes: 70750e257aab ("coresight: tmc: Move ACPI support from AMBA driver to platform driver") Signed-off-by: Uwe Kleine-König Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/3cf26d85a8d45f0efb07e07f3307a1b435ebf61e.1713858615.git.u.kleine-koenig@pengutronix.de --- drivers/hwtracing/coresight/coresight-tmc-core.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c index c9aaf3567565..4b50d043ce04 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-core.c +++ b/drivers/hwtracing/coresight/coresight-tmc-core.c @@ -659,18 +659,17 @@ static int tmc_platform_probe(struct platform_device *pdev) return ret; } -static int tmc_platform_remove(struct platform_device *pdev) +static void tmc_platform_remove(struct platform_device *pdev) { struct tmc_drvdata *drvdata = dev_get_drvdata(&pdev->dev); if (WARN_ON(!drvdata)) - return -ENODEV; + return; __tmc_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!IS_ERR_OR_NULL(drvdata->pclk)) clk_put(drvdata->pclk); - return 0; } #ifdef CONFIG_PM @@ -708,7 +707,7 @@ MODULE_DEVICE_TABLE(acpi, tmc_acpi_ids); static struct platform_driver tmc_platform_driver = { .probe = tmc_platform_probe, - .remove = tmc_platform_remove, + .remove_new = tmc_platform_remove, .driver = { .name = "coresight-tmc-platform", .acpi_match_table = ACPI_PTR(tmc_acpi_ids), From ba8c06fe7e16ddfd44ece7fabd9af2dc2e6d49e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 23 Apr 2024 10:07:02 +0200 Subject: [PATCH 202/319] coresight: tpiu: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Fixes: 3d83d4d4904a ("coresight: tpiu: Move ACPI support from AMBA driver to platform driver") Signed-off-by: Uwe Kleine-König Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/ad5e0d3ec081444a5ad04a7be277dde3afcb696b.1713858615.git.u.kleine-koenig@pengutronix.de --- drivers/hwtracing/coresight/coresight-tpiu.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c index 6bf3039839c1..6ecde7d4a25d 100644 --- a/drivers/hwtracing/coresight/coresight-tpiu.c +++ b/drivers/hwtracing/coresight/coresight-tpiu.c @@ -285,18 +285,17 @@ static int tpiu_platform_probe(struct platform_device *pdev) return ret; } -static int tpiu_platform_remove(struct platform_device *pdev) +static void tpiu_platform_remove(struct platform_device *pdev) { struct tpiu_drvdata *drvdata = dev_get_drvdata(&pdev->dev); if (WARN_ON(!drvdata)) - return -ENODEV; + return; __tpiu_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!IS_ERR_OR_NULL(drvdata->pclk)) clk_put(drvdata->pclk); - return 0; } #ifdef CONFIG_ACPI @@ -309,7 +308,7 @@ MODULE_DEVICE_TABLE(acpi, tpiu_acpi_ids); static struct platform_driver tpiu_platform_driver = { .probe = tpiu_platform_probe, - .remove = tpiu_platform_remove, + .remove_new = tpiu_platform_remove, .driver = { .name = "coresight-tpiu-platform", .acpi_match_table = ACPI_PTR(tpiu_acpi_ids), From 17553ba8e19dee8770b3dcc597d49dcc3418f3b0 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 24 Apr 2024 11:21:53 +0800 Subject: [PATCH 203/319] bus: mhi: host: Add sysfs entry to force device to enter EDL Add sysfs entry to allow users of MHI bus to force device to enter EDL (Emergency Download) mode to download the device firmware. Since there is no guarantee that all the devices will support EDL mode, the sysfs entry is kept as an optional one and will appear only for the supported devices. Controllers supporting the EDL mode are expected to provide edl_trigger() callback that puts the device into EDL mode. Signed-off-by: Qiang Yu Reviewed-by: Jeffrey Hugo Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/1713928915-18229-2-git-send-email-quic_qianyu@quicinc.com [mani: fixed the kernel version and reworded the commit message] Signed-off-by: Manivannan Sadhasivam --- Documentation/ABI/stable/sysfs-bus-mhi | 13 ++++++++++ drivers/bus/mhi/host/init.c | 33 ++++++++++++++++++++++++++ include/linux/mhi.h | 2 ++ 3 files changed, 48 insertions(+) diff --git a/Documentation/ABI/stable/sysfs-bus-mhi b/Documentation/ABI/stable/sysfs-bus-mhi index 1a47f9e0cc84..8b9698fa0beb 100644 --- a/Documentation/ABI/stable/sysfs-bus-mhi +++ b/Documentation/ABI/stable/sysfs-bus-mhi @@ -29,3 +29,16 @@ Description: Initiates a SoC reset on the MHI controller. A SoC reset is This can be useful as a method of recovery if the device is non-responsive, or as a means of loading new firmware as a system administration task. + +What: /sys/bus/mhi/devices/.../trigger_edl +Date: April 2024 +KernelVersion: 6.10 +Contact: mhi@lists.linux.dev +Description: Writing a non-zero value to this file will force devices to + enter EDL (Emergency Download) mode. This entry only exists for + devices capable of entering the EDL mode using the standard EDL + triggering mechanism defined in the MHI spec v1.2. Once in EDL + mode, the flash programmer image can be downloaded to the + device to enter the flash programmer execution environment. + This can be useful if user wants to use QDL (Qualcomm Download, + which is used to download firmware over EDL) to update firmware. diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 44f934981de8..7104c1856987 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -127,6 +127,30 @@ static ssize_t soc_reset_store(struct device *dev, } static DEVICE_ATTR_WO(soc_reset); +static ssize_t trigger_edl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) + return ret; + + if (!val) + return -EINVAL; + + ret = mhi_cntrl->edl_trigger(mhi_cntrl); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(trigger_edl); + static struct attribute *mhi_dev_attrs[] = { &dev_attr_serial_number.attr, &dev_attr_oem_pk_hash.attr, @@ -1018,6 +1042,12 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, if (ret) goto err_release_dev; + if (mhi_cntrl->edl_trigger) { + ret = sysfs_create_file(&mhi_dev->dev.kobj, &dev_attr_trigger_edl.attr); + if (ret) + goto err_release_dev; + } + mhi_cntrl->mhi_dev = mhi_dev; mhi_create_debugfs(mhi_cntrl); @@ -1051,6 +1081,9 @@ void mhi_unregister_controller(struct mhi_controller *mhi_cntrl) mhi_deinit_free_irq(mhi_cntrl); mhi_destroy_debugfs(mhi_cntrl); + if (mhi_cntrl->edl_trigger) + sysfs_remove_file(&mhi_dev->dev.kobj, &dev_attr_trigger_edl.attr); + destroy_workqueue(mhi_cntrl->hiprio_wq); kfree(mhi_cntrl->mhi_cmd); kfree(mhi_cntrl->mhi_event); diff --git a/include/linux/mhi.h b/include/linux/mhi.h index cde01e133a1b..d968e1ab44dc 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -353,6 +353,7 @@ struct mhi_controller_config { * @read_reg: Read a MHI register via the physical link (required) * @write_reg: Write a MHI register via the physical link (required) * @reset: Controller specific reset function (optional) + * @edl_trigger: CB function to trigger EDL mode (optional) * @buffer_len: Bounce buffer length * @index: Index of the MHI controller instance * @bounce_buf: Use of bounce buffer @@ -435,6 +436,7 @@ struct mhi_controller { void (*write_reg)(struct mhi_controller *mhi_cntrl, void __iomem *addr, u32 val); void (*reset)(struct mhi_controller *mhi_cntrl); + int (*edl_trigger)(struct mhi_controller *mhi_cntrl); size_t buffer_len; int index; From 553f94fc7667259e47a9318e9e5702c9a814d637 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 24 Apr 2024 11:21:54 +0800 Subject: [PATCH 204/319] bus: mhi: host: Add a new API for getting channel doorbell offset Some controllers may want to access a specific doorbell register. Hence add a new API that reads the CHDBOFF register and returns the offset of the doorbell registers from MMIO base, so that the controller can calculate the address of the specific doorbell register by adding the register offset with doorbell offset and MMIO base address. Signed-off-by: Qiang Yu Reviewed-by: Jeffrey Hugo Link: https://lore.kernel.org/r/1713928915-18229-3-git-send-email-quic_qianyu@quicinc.com [mani: reworded commit message and Kdoc] Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/host/init.c | 8 +++----- drivers/bus/mhi/host/main.c | 16 ++++++++++++++++ include/linux/mhi.h | 9 +++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 7104c1856987..173f79918741 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -541,11 +541,9 @@ int mhi_init_mmio(struct mhi_controller *mhi_cntrl) dev_dbg(dev, "Initializing MHI registers\n"); /* Read channel db offset */ - ret = mhi_read_reg(mhi_cntrl, base, CHDBOFF, &val); - if (ret) { - dev_err(dev, "Unable to read CHDBOFF register\n"); - return -EIO; - } + ret = mhi_get_channel_doorbell_offset(mhi_cntrl, &val); + if (ret) + return ret; if (val >= mhi_cntrl->reg_len - (8 * MHI_DEV_WAKE_DB)) { dev_err(dev, "CHDB offset: 0x%x is out of range: 0x%zx\n", diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c index 15d657af9b5b..4de75674f193 100644 --- a/drivers/bus/mhi/host/main.c +++ b/drivers/bus/mhi/host/main.c @@ -1691,3 +1691,19 @@ void mhi_unprepare_from_transfer(struct mhi_device *mhi_dev) } } EXPORT_SYMBOL_GPL(mhi_unprepare_from_transfer); + +int mhi_get_channel_doorbell_offset(struct mhi_controller *mhi_cntrl, u32 *chdb_offset) +{ + struct device *dev = &mhi_cntrl->mhi_dev->dev; + void __iomem *base = mhi_cntrl->regs; + int ret; + + ret = mhi_read_reg(mhi_cntrl, base, CHDBOFF, chdb_offset); + if (ret) { + dev_err(dev, "Unable to read CHDBOFF register\n"); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mhi_get_channel_doorbell_offset); diff --git a/include/linux/mhi.h b/include/linux/mhi.h index d968e1ab44dc..b573f15762f8 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -816,4 +816,13 @@ int mhi_queue_skb(struct mhi_device *mhi_dev, enum dma_data_direction dir, */ bool mhi_queue_is_full(struct mhi_device *mhi_dev, enum dma_data_direction dir); +/** + * mhi_get_channel_doorbell_offset - Get the channel doorbell offset + * @mhi_cntrl: MHI controller + * @chdb_offset: Read channel doorbell offset + * + * Return: 0 if the read succeeds, a negative error code otherwise + */ +int mhi_get_channel_doorbell_offset(struct mhi_controller *mhi_cntrl, u32 *chdb_offset); + #endif /* _MHI_H_ */ From 48f98496b1de132f2e056605b5330209136066dc Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 24 Apr 2024 11:21:55 +0800 Subject: [PATCH 205/319] bus: mhi: host: pci_generic: Add generic edl_trigger to allow devices to enter EDL mode Some of the MHI modems like SDX65 based ones are capable of entering the EDL mode as per the standard triggering mechanism defined in the MHI spec v1.2. So let's add a common mhi_pci_generic_edl_trigger() function that triggers the EDL mode in the device when user writes to the /sys/bus/mhi/devices/.../trigger_edl file. As per the spec, the EDL mode can be triggered by writing a cookie to the EDL doorbell register and then resetting the device. Devices supporting this standard way of entering EDL mode can set the mhi_pci_dev_info::edl_trigger flag. Signed-off-by: Qiang Yu Reviewed-by: Jeffrey Hugo Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/1713928915-18229-4-git-send-email-quic_qianyu@quicinc.com [mani: reworded commit message] Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/host/pci_generic.c | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index 51639bfcfec7..08844ee79654 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -27,12 +27,16 @@ #define PCI_VENDOR_ID_THALES 0x1269 #define PCI_VENDOR_ID_QUECTEL 0x1eac +#define MHI_EDL_DB 91 +#define MHI_EDL_COOKIE 0xEDEDEDED + /** * struct mhi_pci_dev_info - MHI PCI device specific information * @config: MHI controller configuration * @name: name of the PCI module * @fw: firmware path (if any) * @edl: emergency download mode firmware path (if any) + * @edl_trigger: capable of triggering EDL mode in the device (if supported) * @bar_num: PCI base address register to use for MHI MMIO register space * @dma_data_width: DMA transfer word size (32 or 64 bits) * @mru_default: default MRU size for MBIM network packets @@ -44,6 +48,7 @@ struct mhi_pci_dev_info { const char *name; const char *fw; const char *edl; + bool edl_trigger; unsigned int bar_num; unsigned int dma_data_width; unsigned int mru_default; @@ -292,6 +297,7 @@ static const struct mhi_pci_dev_info mhi_qcom_sdx75_info = { .name = "qcom-sdx75m", .fw = "qcom/sdx75m/xbl.elf", .edl = "qcom/sdx75m/edl.mbn", + .edl_trigger = true, .config = &modem_qcom_v2_mhiv_config, .bar_num = MHI_PCI_DEFAULT_BAR_NUM, .dma_data_width = 32, @@ -302,6 +308,7 @@ static const struct mhi_pci_dev_info mhi_qcom_sdx65_info = { .name = "qcom-sdx65m", .fw = "qcom/sdx65m/xbl.elf", .edl = "qcom/sdx65m/edl.mbn", + .edl_trigger = true, .config = &modem_qcom_v1_mhiv_config, .bar_num = MHI_PCI_DEFAULT_BAR_NUM, .dma_data_width = 32, @@ -312,6 +319,7 @@ static const struct mhi_pci_dev_info mhi_qcom_sdx55_info = { .name = "qcom-sdx55m", .fw = "qcom/sdx55m/sbl1.mbn", .edl = "qcom/sdx55m/edl.mbn", + .edl_trigger = true, .config = &modem_qcom_v1_mhiv_config, .bar_num = MHI_PCI_DEFAULT_BAR_NUM, .dma_data_width = 32, @@ -928,6 +936,40 @@ static void health_check(struct timer_list *t) mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); } +static int mhi_pci_generic_edl_trigger(struct mhi_controller *mhi_cntrl) +{ + void __iomem *base = mhi_cntrl->regs; + void __iomem *edl_db; + int ret; + u32 val; + + ret = mhi_device_get_sync(mhi_cntrl->mhi_dev); + if (ret) { + dev_err(mhi_cntrl->cntrl_dev, "Failed to wakeup the device\n"); + return ret; + } + + pm_wakeup_event(&mhi_cntrl->mhi_dev->dev, 0); + mhi_cntrl->runtime_get(mhi_cntrl); + + ret = mhi_get_channel_doorbell_offset(mhi_cntrl, &val); + if (ret) + goto err_get_chdb; + + edl_db = base + val + (8 * MHI_EDL_DB); + + mhi_cntrl->write_reg(mhi_cntrl, edl_db + 4, upper_32_bits(MHI_EDL_COOKIE)); + mhi_cntrl->write_reg(mhi_cntrl, edl_db, lower_32_bits(MHI_EDL_COOKIE)); + + mhi_soc_reset(mhi_cntrl); + +err_get_chdb: + mhi_cntrl->runtime_put(mhi_cntrl); + mhi_device_put(mhi_cntrl->mhi_dev); + + return ret; +} + static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { const struct mhi_pci_dev_info *info = (struct mhi_pci_dev_info *) id->driver_data; @@ -962,6 +1004,9 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mhi_cntrl->runtime_put = mhi_pci_runtime_put; mhi_cntrl->mru = info->mru_default; + if (info->edl_trigger) + mhi_cntrl->edl_trigger = mhi_pci_generic_edl_trigger; + if (info->sideband_wake) { mhi_cntrl->wake_get = mhi_pci_wake_get_nop; mhi_cntrl->wake_put = mhi_pci_wake_put_nop; From 0d8d62640763edfc11cc8568fe2115ad5af4e67e Mon Sep 17 00:00:00 2001 From: Subhajit Ghosh Date: Sat, 27 Apr 2024 18:39:14 +0930 Subject: [PATCH 206/319] iio: light: apds9306: Fix input arguments to in_range() Third input argument to in_range() function requires the number of values in range, not the last value in that range. Update macro for persistence and adaptive threshold to reflect number of values supported instead of the maximum values supported. Fixes: 620d1e6c7a3f ("iio: light: Add support for APDS9306 Light Sensor") Signed-off-by: Subhajit Ghosh Link: https://lore.kernel.org/r/20240427090914.37274-1-subhajit.ghosh@tweaklogic.com Signed-off-by: Jonathan Cameron --- drivers/iio/light/apds9306.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index 46c647ccd44c..d6627b3e6000 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -55,8 +55,8 @@ #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_THRES_VAR_NUM_VALS 8 +#define APDS9306_ALS_PERSIST_NUM_VALS 16 #define APDS9306_ALS_READ_DATA_DELAY_US (20 * USEC_PER_MSEC) #define APDS9306_NUM_REPEAT_RATES 7 #define APDS9306_INT_SRC_CLEAR 0 @@ -726,7 +726,7 @@ static int apds9306_event_period_get(struct apds9306_data *data, int *val) if (ret) return ret; - if (!in_range(period, 0, APDS9306_ALS_PERSIST_VAL_MAX)) + if (!in_range(period, 0, APDS9306_ALS_PERSIST_NUM_VALS)) return -EINVAL; *val = period; @@ -738,7 +738,7 @@ 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)) + if (!in_range(val, 0, APDS9306_ALS_PERSIST_NUM_VALS)) return -EINVAL; return regmap_field_write(rf->int_persist_val, val); @@ -796,7 +796,7 @@ static int apds9306_event_thresh_adaptive_get(struct apds9306_data *data, int *v if (ret) return ret; - if (!in_range(thr_adpt, 0, APDS9306_ALS_THRES_VAR_VAL_MAX)) + if (!in_range(thr_adpt, 0, APDS9306_ALS_THRES_VAR_NUM_VALS)) return -EINVAL; *val = thr_adpt; @@ -808,7 +808,7 @@ static int apds9306_event_thresh_adaptive_set(struct apds9306_data *data, int va { struct apds9306_regfields *rf = &data->rf; - if (!in_range(val, 0, APDS9306_ALS_THRES_VAR_VAL_MAX)) + if (!in_range(val, 0, APDS9306_ALS_THRES_VAR_NUM_VALS)) return -EINVAL; return regmap_field_write(rf->int_thresh_var_val, val); From 2d7e8a64a1e79f76b9d1165db0b92190d4663848 Mon Sep 17 00:00:00 2001 From: Remington Brasga Date: Sun, 28 Apr 2024 01:14:01 +0000 Subject: [PATCH 207/319] coresight: Docs/ABI/testing/sysfs-bus-coresight-devices: Fix spelling errors Fix spelling and gramatical errors Signed-off-by: Remington Brasga [ Adjusted title ] Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240428011401.1080-1-rbrasga@uci.edu --- Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x | 2 +- Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc | 2 +- Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x index 3acf7fc31659..271b57c571aa 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x @@ -22,7 +22,7 @@ Contact: Mathieu Poirier Description: (RW) Used in conjunction with @addr_idx. Specifies characteristics about the address comparator being configure, for example the access type, the kind of instruction to trace, - processor contect ID to trigger on, etc. Individual fields in + processor context ID to trigger on, etc. Individual fields in the access type register may vary on the version of the trace entity. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc index 96aafa66b4a5..339cec3b2f1a 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc @@ -97,7 +97,7 @@ Date: August 2023 KernelVersion: 6.7 Contact: Anshuman Khandual Description: (Read) Shows all supported Coresight TMC-ETR buffer modes available - for the users to configure explicitly. This file is avaialble only + for the users to configure explicitly. This file is available only for TMC ETR devices. What: /sys/bus/coresight/devices/.tmc/buf_mode_preferred diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index b4d0fc8d319d..bf710ea6e0ef 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -244,7 +244,7 @@ KernelVersion 6.9 Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) Description: (RW) Read or write the status of timestamp upon all interface. - Only value 0 and 1 can be written to this node. Set this node to 1 to requeset + Only value 0 and 1 can be written to this node. Set this node to 1 to request timestamp to all trace packet. Accepts only one of the 2 values - 0 or 1. 0 : Disable the timestamp of all trace packets. From a2c72ed78ab8557ac58cd88d8cf871fcb8c740ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 29 Apr 2024 10:38:25 +0200 Subject: [PATCH 208/319] mcb: lpc: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Signed-off-by: Johannes Thumshirn Link: https://lore.kernel.org/r/cd04e21a7e2646bfe44b2304eea2b3fd0ee84018.1714379722.git.jth@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/mcb/mcb-lpc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/mcb/mcb-lpc.c b/drivers/mcb/mcb-lpc.c index a851e0236464..2bec2086ee17 100644 --- a/drivers/mcb/mcb-lpc.c +++ b/drivers/mcb/mcb-lpc.c @@ -97,13 +97,11 @@ out_mcb_bus: return ret; } -static int mcb_lpc_remove(struct platform_device *pdev) +static void mcb_lpc_remove(struct platform_device *pdev) { struct priv *priv = platform_get_drvdata(pdev); mcb_release_bus(priv->bus); - - return 0; } static struct platform_device *mcb_lpc_pdev; @@ -140,7 +138,7 @@ static struct platform_driver mcb_lpc_driver = { .name = "mcb-lpc", }, .probe = mcb_lpc_probe, - .remove = mcb_lpc_remove, + .remove_new = mcb_lpc_remove, }; static const struct dmi_system_id mcb_lpc_dmi_table[] = { From 7c28f964b7febc00edfced0f499630005a800643 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 14 Apr 2024 17:49:55 +0200 Subject: [PATCH 209/319] eeprom: at25: drop unneeded MODULE_ALIAS The ID table already has respective entry and MODULE_DEVICE_TABLE and creates proper alias for SPI driver. Having another MODULE_ALIAS causes the alias to be duplicated. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240414154957.127113-1-krzk@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/at25.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 65d49a6de1a7..595ceb9a7126 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -529,4 +529,3 @@ module_spi_driver(at25_driver); MODULE_DESCRIPTION("Driver for most SPI EEPROMs"); MODULE_AUTHOR("David Brownell"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("spi:at25"); From 2db26427d7d92e34246ee378214b4c2c193b96e7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 14 Apr 2024 17:49:57 +0200 Subject: [PATCH 210/319] eeprom: 93xx46: drop unneeded MODULE_ALIAS The ID table already has respective entry and MODULE_DEVICE_TABLE and creates proper alias for SPI driver. Having another MODULE_ALIAS causes the alias to be duplicated. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240414154957.127113-3-krzk@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/eeprom_93xx46.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c index e78a76d74ff4..45c8ae0db8f9 100644 --- a/drivers/misc/eeprom/eeprom_93xx46.c +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -578,5 +578,3 @@ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Driver for 93xx46 EEPROMs"); MODULE_AUTHOR("Anatolij Gustschin "); MODULE_ALIAS("spi:93xx46"); -MODULE_ALIAS("spi:eeprom-93xx46"); -MODULE_ALIAS("spi:93lc46b"); From 7a5ffa5a21d327e867c8ee226278d60f7e34e1b9 Mon Sep 17 00:00:00 2001 From: Prasad Pandit Date: Fri, 12 Apr 2024 11:22:20 +0530 Subject: [PATCH 211/319] misc: sgi_gru: indent SGI_GRU option help text Fix indentation of SGI_GRU option's help text by adding leading spaces. Generally help text is indented by two more spaces beyond the leading tab <\t> character. It helps Kconfig parsers to read file without error. Signed-off-by: Prasad Pandit Link: https://lore.kernel.org/r/20240412055221.69411-2-ppandit@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 801ed229ed7d..c9295a0a3305 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -293,21 +293,21 @@ config SGI_GRU depends on X86_UV && SMP select MMU_NOTIFIER help - The GRU is a hardware resource located in the system chipset. The GRU - contains memory that can be mmapped into the user address space. This memory is - used to communicate with the GRU to perform functions such as load/store, - scatter/gather, bcopy, AMOs, etc. The GRU is directly accessed by user - instructions using user virtual addresses. GRU instructions (ex., bcopy) use - user virtual addresses for operands. + The GRU is a hardware resource located in the system chipset. The GRU + contains memory that can be mmapped into the user address space. + This memory is used to communicate with the GRU to perform functions + such as load/store, scatter/gather, bcopy, AMOs, etc. The GRU is + directly accessed by user instructions using user virtual addresses. + GRU instructions (ex., bcopy) use user virtual addresses for operands. - If you are not running on a SGI UV system, say N. + If you are not running on a SGI UV system, say N. config SGI_GRU_DEBUG bool "SGI GRU driver debug" depends on SGI_GRU help - This option enables additional debugging code for the SGI GRU driver. - If you are unsure, say N. + This option enables additional debugging code for the SGI GRU driver. + If you are unsure, say N. config APDS9802ALS tristate "Medfield Avago APDS9802 ALS Sensor module" From 11e5e1aba749cd354e7330bb40f09ffc92282244 Mon Sep 17 00:00:00 2001 From: Prasad Pandit Date: Fri, 12 Apr 2024 11:22:21 +0530 Subject: [PATCH 212/319] misc: sgi_gru: remove default attribute of LATTICE_ECP3_CONFIG Remove 'default n' attribute of 'LATTICE_ECP3_CONFIG' option because it is redundant. 'n' is automatic default value when one is not specified. Suggested-by: Greg Kroah-Hartman Signed-off-by: Prasad Pandit Link: https://lore.kernel.org/r/20240412055221.69411-3-ppandit@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c9295a0a3305..2907b5c23368 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -428,7 +428,6 @@ config LATTICE_ECP3_CONFIG tristate "Lattice ECP3 FPGA bitstream configuration via SPI" depends on SPI && SYSFS select FW_LOADER - default n help This option enables support for bitstream configuration (programming or loading) of the Lattice ECP3 FPGA family via SPI. From 7b51b13733214b0491e935ff6ffc64a5730caa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Fri, 12 Apr 2024 08:26:40 +0200 Subject: [PATCH 213/319] misc/pvpanic: add support for normal shutdowns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shutdown requests are normally hardware dependent. By extending pvpanic to also handle shutdown requests, guests can submit such requests with an easily implementable and cross-platform mechanism. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20240412-pvpanic-shutdown-v3-1-c214e9873b3f@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- drivers/misc/pvpanic/pvpanic.c | 43 +++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/misc/pvpanic/pvpanic.c b/drivers/misc/pvpanic/pvpanic.c index df3457ce1cb1..17c0eb549463 100644 --- a/drivers/misc/pvpanic/pvpanic.c +++ b/drivers/misc/pvpanic/pvpanic.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ struct pvpanic_instance { void __iomem *base; unsigned int capability; unsigned int events; + struct sys_off_handler *sys_off; struct list_head list; }; @@ -78,6 +80,39 @@ static struct notifier_block pvpanic_panic_nb = { .priority = INT_MAX, }; +static int pvpanic_sys_off(struct sys_off_data *data) +{ + pvpanic_send_event(PVPANIC_SHUTDOWN); + + return NOTIFY_DONE; +} + +static void pvpanic_synchronize_sys_off_handler(struct device *dev, struct pvpanic_instance *pi) +{ + /* The kernel core has logic to fall back to system halt if no + * sys_off_handler is registered. + * When the pvpanic sys_off_handler is disabled via sysfs the kernel + * should use that fallback logic, so the handler needs to be unregistered. + */ + + struct sys_off_handler *sys_off; + + if (!(pi->events & PVPANIC_SHUTDOWN) == !pi->sys_off) + return; + + if (!pi->sys_off) { + sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_LOW, + pvpanic_sys_off, NULL); + if (IS_ERR(sys_off)) + dev_warn(dev, "Could not register sys_off_handler: %pe\n", sys_off); + else + pi->sys_off = sys_off; + } else { + unregister_sys_off_handler(pi->sys_off); + pi->sys_off = NULL; + } +} + static void pvpanic_remove(void *param) { struct pvpanic_instance *pi_cur, *pi_next; @@ -91,6 +126,8 @@ static void pvpanic_remove(void *param) } } spin_unlock(&pvpanic_lock); + + unregister_sys_off_handler(pi->sys_off); } static ssize_t capability_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -123,6 +160,7 @@ static ssize_t events_store(struct device *dev, struct device_attribute *attr, return -EINVAL; pi->events = tmp; + pvpanic_synchronize_sys_off_handler(dev, pi); return count; } @@ -156,12 +194,15 @@ int devm_pvpanic_probe(struct device *dev, void __iomem *base) return -ENOMEM; pi->base = base; - pi->capability = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED; + pi->capability = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED | PVPANIC_SHUTDOWN; /* initlize capability by RDPT */ pi->capability &= ioread8(base); pi->events = pi->capability; + pi->sys_off = NULL; + pvpanic_synchronize_sys_off_handler(dev, pi); + spin_lock(&pvpanic_lock); list_add(&pi->list, &pvpanic_list); spin_unlock(&pvpanic_lock); From 427298c632952709a33f3205d489ac496ae5b003 Mon Sep 17 00:00:00 2001 From: Gabriel Schwartz Date: Fri, 26 Apr 2024 17:01:14 -0300 Subject: [PATCH 214/319] iio: adc: rtq6056: Use automated cleanup for mode handling in write_raw Using iio_device_claim_direct_scoped() to automate mode claim and release simplifies code flow and allows for straight-forward error handling with direct returns on errors. Signed-off-by: Gabriel Schwartz Reviewed-by: Marcelo Schmitt Reviewed-by: ChiYuan Huang Link: https://lore.kernel.org/r/20240426200118.20900-1-gschwartz@usp.br Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rtq6056.c | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/drivers/iio/adc/rtq6056.c b/drivers/iio/adc/rtq6056.c index a5464737e527..bcb129840908 100644 --- a/drivers/iio/adc/rtq6056.c +++ b/drivers/iio/adc/rtq6056.c @@ -520,32 +520,20 @@ static int rtq6056_adc_write_raw(struct iio_dev *indio_dev, { struct rtq6056_priv *priv = iio_priv(indio_dev); const struct richtek_dev_data *devdata = priv->devdata; - int ret; - ret = iio_device_claim_direct_mode(indio_dev); - if (ret) - return ret; - - switch (mask) { - case IIO_CHAN_INFO_SAMP_FREQ: - if (devdata->fixed_samp_freq) { - ret = -EINVAL; - break; + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (devdata->fixed_samp_freq) + return -EINVAL; + return rtq6056_adc_set_samp_freq(priv, chan, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return devdata->set_average(priv, val); + default: + return -EINVAL; } - - ret = rtq6056_adc_set_samp_freq(priv, chan, val); - break; - case IIO_CHAN_INFO_OVERSAMPLING_RATIO: - ret = devdata->set_average(priv, val); - break; - default: - ret = -EINVAL; - break; } - - iio_device_release_direct_mode(indio_dev); - - return ret; + unreachable(); } static const char *rtq6056_channel_labels[RTQ6056_MAX_CHANNEL] = { From ef64a4ad8310620976bef97287285fa94799ccc0 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 25 Apr 2024 10:03:27 -0500 Subject: [PATCH 215/319] iio: adc: ad7266: don't set masklength The masklength field is marked as [INTERN] and should not be set by drivers, so remove the assignment in the ad7266 driver. __iio_device_register() will populate this field with the correct value. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240425-b4-iio-masklength-cleanup-v1-1-d3d16318274d@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7266.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/adc/ad7266.c b/drivers/iio/adc/ad7266.c index 468c2656d2be..353a97f9c086 100644 --- a/drivers/iio/adc/ad7266.c +++ b/drivers/iio/adc/ad7266.c @@ -371,7 +371,6 @@ static void ad7266_init_channels(struct iio_dev *indio_dev) indio_dev->channels = chan_info->channels; indio_dev->num_channels = chan_info->num_channels; indio_dev->available_scan_masks = chan_info->scan_masks; - indio_dev->masklength = chan_info->num_channels - 1; } static const char * const ad7266_gpio_labels[] = { From 75616d2e3c9ecb22e41c2bf455d1588ee05f02e2 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 25 Apr 2024 10:03:28 -0500 Subject: [PATCH 216/319] iio: adc: mxs-lradc-adc: don't set masklength The masklength field is marked as [INTERN] and should not be set by drivers, so remove the assignment in the mxs-lradc-adc driver. __iio_device_register() will populate this field with the correct value. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240425-b4-iio-masklength-cleanup-v1-2-d3d16318274d@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/mxs-lradc-adc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/adc/mxs-lradc-adc.c b/drivers/iio/adc/mxs-lradc-adc.c index 2e60c10ee4ff..8c7b64e78dbb 100644 --- a/drivers/iio/adc/mxs-lradc-adc.c +++ b/drivers/iio/adc/mxs-lradc-adc.c @@ -724,7 +724,6 @@ static int mxs_lradc_adc_probe(struct platform_device *pdev) iio->dev.of_node = dev->parent->of_node; iio->info = &mxs_lradc_adc_iio_info; iio->modes = INDIO_DIRECT_MODE; - iio->masklength = LRADC_MAX_TOTAL_CHANS; if (lradc->soc == IMX23_LRADC) { iio->channels = mx23_lradc_chan_spec; From 79df437b5661b2f7e1c0bad097fd18c4e154bb94 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 25 Apr 2024 10:03:29 -0500 Subject: [PATCH 217/319] iio: buffer: initialize masklength accumulator to 0 Since masklength is marked as [INTERN], no drivers should assign it and the value will always be 0. Therefore, the local ml accumulator variable in iio_buffers_alloc_sysfs_and_mask() will always start out as 0. This changes the code to explicitly set ml to 0 to make it clear that drivers should not be trying to override the masklength field. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240425-b4-iio-masklength-cleanup-v1-3-d3d16318274d@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 1d950a3e153b..cec58a604d73 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -1744,7 +1744,7 @@ int iio_buffers_alloc_sysfs_and_mask(struct iio_dev *indio_dev) channels = indio_dev->channels; if (channels) { - int ml = indio_dev->masklength; + int ml = 0; for (i = 0; i < indio_dev->num_channels; i++) ml = max(ml, channels[i].scan_index + 1); From 02eae0bb9538dc7dcb5a6bc2c3066bd6ca682969 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 25 Apr 2024 14:57:51 +0200 Subject: [PATCH 218/319] iio: core: Add iio_read_acpi_mount_matrix() helper function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ACPI "ROTM" rotation matrix parsing code atm is already duplicated between bmc150-accel-core.c and kxcjk-1013.c and a third user of this is coming. Add an iio_read_acpi_mount_matrix() helper function for this. The 2 existing copies of the code are identical, except that the kxcjk-1013.c has slightly better error logging. To new helper is a 1:1 copy of the kxcjk-1013.c version, the only change is the addition of a "char *acpi_method" parameter since some bmc150 dual-accel setups (360° hinges with 1 accel in kbd/base + 1 in display) declare both accels in a single ACPI device with 2 different method names for the 2 matrices. This new acpi_method parameter is not "const char *" because the pathname parameter to acpi_evaluate_object() is not const. The 2 existing copies of this function will be removed in further patches in this series. Acked-by: Rafael J. Wysocki Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240425125754.76010-2-hdegoede@redhat.com Signed-off-by: Jonathan Cameron --- drivers/iio/Makefile | 1 + drivers/iio/industrialio-acpi.c | 85 +++++++++++++++++++++++++++++++++ include/linux/iio/iio.h | 13 +++++ 3 files changed, 99 insertions(+) create mode 100644 drivers/iio/industrialio-acpi.c diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 0ba0e1521ba4..cb80ef837e84 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o industrialio-event.o inkern.o industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +industrialio-$(CONFIG_ACPI) += industrialio-acpi.o obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o obj-$(CONFIG_IIO_GTS_HELPER) += industrialio-gts-helper.o diff --git a/drivers/iio/industrialio-acpi.c b/drivers/iio/industrialio-acpi.c new file mode 100644 index 000000000000..981b75d40780 --- /dev/null +++ b/drivers/iio/industrialio-acpi.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* IIO ACPI helper functions */ + +#include +#include +#include +#include + +/** + * iio_read_acpi_mount_matrix() - Read accelerometer mount matrix info from ACPI + * @dev: Device structure + * @orientation: iio_mount_matrix struct to fill + * @acpi_method: ACPI method name to read the matrix from, usually "ROTM" + * + * Try to read the mount-matrix by calling the specified method on the device's + * ACPI firmware-node. If the device has no ACPI firmware-node; or the method + * does not exist then this will fail silently. This expects the method to + * return data in the ACPI "ROTM" format defined by Microsoft: + * https://learn.microsoft.com/en-us/windows-hardware/drivers/sensors/sensors-acpi-entries + * This is a Microsoft extension and not part of the official ACPI spec. + * The method name is configurable because some dual-accel setups define 2 mount + * matrices in a single ACPI device using separate "ROMK" and "ROMS" methods. + * + * Returns: true if the matrix was successfully, false otherwise. + */ +bool iio_read_acpi_mount_matrix(struct device *dev, + struct iio_mount_matrix *orientation, + char *acpi_method) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_device *adev = ACPI_COMPANION(dev); + char *str; + union acpi_object *obj, *elements; + acpi_status status; + int i, j, val[3]; + bool ret = false; + + if (!adev || !acpi_has_method(adev->handle, acpi_method)) + return false; + + status = acpi_evaluate_object(adev->handle, acpi_method, NULL, &buffer); + if (ACPI_FAILURE(status)) { + dev_err(dev, "Failed to get ACPI mount matrix: %d\n", status); + return false; + } + + obj = buffer.pointer; + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) { + dev_err(dev, "Unknown ACPI mount matrix package format\n"); + goto out_free_buffer; + } + + elements = obj->package.elements; + for (i = 0; i < 3; i++) { + if (elements[i].type != ACPI_TYPE_STRING) { + dev_err(dev, "Unknown ACPI mount matrix element format\n"); + goto out_free_buffer; + } + + str = elements[i].string.pointer; + if (sscanf(str, "%d %d %d", &val[0], &val[1], &val[2]) != 3) { + dev_err(dev, "Incorrect ACPI mount matrix string format\n"); + goto out_free_buffer; + } + + for (j = 0; j < 3; j++) { + switch (val[j]) { + case -1: str = "-1"; break; + case 0: str = "0"; break; + case 1: str = "1"; break; + default: + dev_err(dev, "Invalid value in ACPI mount matrix: %d\n", val[j]); + goto out_free_buffer; + } + orientation->rotation[i * 3 + j] = str; + } + } + + ret = true; + +out_free_buffer: + kfree(buffer.pointer); + return ret; +} +EXPORT_SYMBOL_GPL(iio_read_acpi_mount_matrix); diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index e370a7bb3300..55e2b22086a1 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -788,6 +788,19 @@ static inline struct dentry *iio_get_debugfs_dentry(struct iio_dev *indio_dev) } #endif +#ifdef CONFIG_ACPI +bool iio_read_acpi_mount_matrix(struct device *dev, + struct iio_mount_matrix *orientation, + char *acpi_method); +#else +static inline bool iio_read_acpi_mount_matrix(struct device *dev, + struct iio_mount_matrix *orientation, + char *acpi_method) +{ + return false; +} +#endif + ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals); int iio_str_to_fixpoint(const char *str, int fract_mult, int *integer, From e074cc3080d507b3680160248bb5b450d7009efb Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 25 Apr 2024 14:57:52 +0200 Subject: [PATCH 219/319] iio: accel: kxcjk-1013: Use new iio_read_acpi_mount_matrix() helper Replace the duplicate ACPI "ROTM" data parsing code with the new shared iio_read_acpi_mount_matrix() helper. This also removes the limiting of the "ROTM" mount matrix to only ACPI devices with an ACPI HID (Hardware-ID) of "KIOX000A". If kxcjk-1013 ACPI devices with another HID have a ROTM method that should still be parsed and if the method is not there then iio_read_acpi_mount_matrix() will fail silently. Acked-by: Rafael J. Wysocki Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240425125754.76010-3-hdegoede@redhat.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kxcjk-1013.c | 80 +--------------------------------- 1 file changed, 1 insertion(+), 79 deletions(-) diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 126e8bdd6d0e..8280d2bef0a3 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -636,84 +636,6 @@ static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on) return 0; } -#ifdef CONFIG_ACPI -static bool kxj_acpi_orientation(struct device *dev, - struct iio_mount_matrix *orientation) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_device *adev = ACPI_COMPANION(dev); - char *str; - union acpi_object *obj, *elements; - acpi_status status; - int i, j, val[3]; - bool ret = false; - - if (!acpi_has_method(adev->handle, "ROTM")) - return false; - - status = acpi_evaluate_object(adev->handle, "ROTM", NULL, &buffer); - if (ACPI_FAILURE(status)) { - dev_err(dev, "Failed to get ACPI mount matrix: %d\n", status); - return false; - } - - obj = buffer.pointer; - if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) { - dev_err(dev, "Unknown ACPI mount matrix package format\n"); - goto out_free_buffer; - } - - elements = obj->package.elements; - for (i = 0; i < 3; i++) { - if (elements[i].type != ACPI_TYPE_STRING) { - dev_err(dev, "Unknown ACPI mount matrix element format\n"); - goto out_free_buffer; - } - - str = elements[i].string.pointer; - if (sscanf(str, "%d %d %d", &val[0], &val[1], &val[2]) != 3) { - dev_err(dev, "Incorrect ACPI mount matrix string format\n"); - goto out_free_buffer; - } - - for (j = 0; j < 3; j++) { - switch (val[j]) { - case -1: str = "-1"; break; - case 0: str = "0"; break; - case 1: str = "1"; break; - default: - dev_err(dev, "Invalid value in ACPI mount matrix: %d\n", val[j]); - goto out_free_buffer; - } - orientation->rotation[i * 3 + j] = str; - } - } - - ret = true; - -out_free_buffer: - kfree(buffer.pointer); - return ret; -} - -static bool kxj1009_apply_acpi_orientation(struct device *dev, - struct iio_mount_matrix *orientation) -{ - struct acpi_device *adev = ACPI_COMPANION(dev); - - if (adev && acpi_dev_hid_uid_match(adev, "KIOX000A", NULL)) - return kxj_acpi_orientation(dev, orientation); - - return false; -} -#else -static bool kxj1009_apply_acpi_orientation(struct device *dev, - struct iio_mount_matrix *orientation) -{ - return false; -} -#endif - static int kxcjk1013_chip_update_thresholds(struct kxcjk1013_data *data) { int ret; @@ -1544,7 +1466,7 @@ static int kxcjk1013_probe(struct i2c_client *client) } else { data->active_high_intr = true; /* default polarity */ - if (!kxj1009_apply_acpi_orientation(&client->dev, &data->orientation)) { + if (!iio_read_acpi_mount_matrix(&client->dev, &data->orientation, "ROTM")) { ret = iio_read_mount_matrix(&client->dev, &data->orientation); if (ret) return ret; From dd3f40b53957541c04a9dceed178df38ddbe3447 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 25 Apr 2024 14:57:53 +0200 Subject: [PATCH 220/319] iio: bmc150-accel-core: Use iio_read_acpi_mount_matrix() helper Replace the duplicate ACPI "ROTM" data parsing code with the new shared iio_read_acpi_mount_matrix() helper. Acked-by: Rafael J. Wysocki Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240425125754.76010-4-hdegoede@redhat.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bmc150-accel-core.c | 44 ++------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index 110591804b4c..ae0cd48a3e29 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -386,13 +386,9 @@ static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) static bool bmc150_apply_bosc0200_acpi_orientation(struct device *dev, struct iio_mount_matrix *orientation) { - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; struct iio_dev *indio_dev = dev_get_drvdata(dev); struct acpi_device *adev = ACPI_COMPANION(dev); - char *name, *alt_name, *label, *str; - union acpi_object *obj, *elements; - acpi_status status; - int i, j, val[3]; + char *name, *alt_name, *label; if (strcmp(dev_name(dev), "i2c-BOSC0200:base") == 0) { alt_name = "ROMK"; @@ -411,43 +407,7 @@ static bool bmc150_apply_bosc0200_acpi_orientation(struct device *dev, return false; } - status = acpi_evaluate_object(adev->handle, name, NULL, &buffer); - if (ACPI_FAILURE(status)) { - dev_warn(dev, "Failed to get ACPI mount matrix: %d\n", status); - return false; - } - - obj = buffer.pointer; - if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) - goto unknown_format; - - elements = obj->package.elements; - for (i = 0; i < 3; i++) { - if (elements[i].type != ACPI_TYPE_STRING) - goto unknown_format; - - str = elements[i].string.pointer; - if (sscanf(str, "%d %d %d", &val[0], &val[1], &val[2]) != 3) - goto unknown_format; - - for (j = 0; j < 3; j++) { - switch (val[j]) { - case -1: str = "-1"; break; - case 0: str = "0"; break; - case 1: str = "1"; break; - default: goto unknown_format; - } - orientation->rotation[i * 3 + j] = str; - } - } - - kfree(buffer.pointer); - return true; - -unknown_format: - dev_warn(dev, "Unknown ACPI mount matrix format, ignoring\n"); - kfree(buffer.pointer); - return false; + return iio_read_acpi_mount_matrix(dev, orientation, name); } static bool bmc150_apply_dual250e_acpi_orientation(struct device *dev, From 4a8e1e020a38009d3a352b1e4738f5c4dce70654 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 25 Apr 2024 14:57:54 +0200 Subject: [PATCH 221/319] iio: accel: mxc4005: Read orientation matrix from ACPI ROTM method Some devices use the semi-standard ACPI "ROTM" method to store the accelerometers orientation matrix. Add support for this using the new iio_read_acpi_mount_matrix() helper, if the helper fails to read the matrix fall back to iio_read_mount_matrix() which will try to get it from device-properties (devicetree) and if that fails it will fill the matrix with the identity matrix. Link: https://bugzilla.kernel.org/show_bug.cgi?id=218578 Acked-by: Rafael J. Wysocki Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240425125754.76010-5-hdegoede@redhat.com Signed-off-by: Jonathan Cameron --- drivers/iio/accel/mxc4005.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/iio/accel/mxc4005.c b/drivers/iio/accel/mxc4005.c index 61839be501c2..34d0f7f3f664 100644 --- a/drivers/iio/accel/mxc4005.c +++ b/drivers/iio/accel/mxc4005.c @@ -56,6 +56,7 @@ struct mxc4005_data { struct mutex mutex; struct regmap *regmap; struct iio_trigger *dready_trig; + struct iio_mount_matrix orientation; /* Ensure timestamp is naturally aligned */ struct { __be16 chans[3]; @@ -259,6 +260,20 @@ static int mxc4005_write_raw(struct iio_dev *indio_dev, } } +static const struct iio_mount_matrix * +mxc4005_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mxc4005_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info mxc4005_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, mxc4005_get_mount_matrix), + { } +}; + static const struct iio_info mxc4005_info = { .read_raw = mxc4005_read_raw, .write_raw = mxc4005_write_raw, @@ -285,6 +300,7 @@ static const unsigned long mxc4005_scan_masks[] = { .shift = 4, \ .endianness = IIO_BE, \ }, \ + .ext_info = mxc4005_ext_info, \ } static const struct iio_chan_spec mxc4005_channels[] = { @@ -415,6 +431,12 @@ static int mxc4005_probe(struct i2c_client *client) mutex_init(&data->mutex); + if (!iio_read_acpi_mount_matrix(&client->dev, &data->orientation, "ROTM")) { + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + } + indio_dev->channels = mxc4005_channels; indio_dev->num_channels = ARRAY_SIZE(mxc4005_channels); indio_dev->available_scan_masks = mxc4005_scan_masks; From c19f273cae8ac785f83345f6eb2b2813d35ec148 Mon Sep 17 00:00:00 2001 From: Ramona Gradinariu Date: Wed, 24 Apr 2024 12:41:52 +0300 Subject: [PATCH 222/319] docs: iio: adis16475: fix device files tables Remove in_accel_calibbias_x and in_anglvel_calibbias_x device files description, as they do not exist and were added by mistake. Add correct naming for in_accel_y_calibbias and in_anglvel_y_calibbias device files and update their description. Fixes: 8243b2877eef ("docs: iio: add documentation for adis16475 driver") Signed-off-by: Ramona Gradinariu Link: https://lore.kernel.org/r/20240424094152.103667-2-ramona.gradinariu@analog.com Signed-off-by: Jonathan Cameron --- Documentation/iio/adis16475.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Documentation/iio/adis16475.rst b/Documentation/iio/adis16475.rst index 91cabb7d8d05..130f9e97cc17 100644 --- a/Documentation/iio/adis16475.rst +++ b/Documentation/iio/adis16475.rst @@ -66,11 +66,9 @@ specific device folder path ``/sys/bus/iio/devices/iio:deviceX``. +-------------------------------------------+----------------------------------------------------------+ | in_accel_x_calibbias | Calibration offset for the X-axis accelerometer channel. | +-------------------------------------------+----------------------------------------------------------+ -| in_accel_calibbias_x | x-axis acceleration offset correction | -+-------------------------------------------+----------------------------------------------------------+ | in_accel_x_raw | Raw X-axis accelerometer channel value. | +-------------------------------------------+----------------------------------------------------------+ -| in_accel_calibbias_y | y-axis acceleration offset correction | +| in_accel_y_calibbias | Calibration offset for the Y-axis accelerometer channel. | +-------------------------------------------+----------------------------------------------------------+ | in_accel_y_raw | Raw Y-axis accelerometer channel value. | +-------------------------------------------+----------------------------------------------------------+ @@ -94,11 +92,9 @@ specific device folder path ``/sys/bus/iio/devices/iio:deviceX``. +---------------------------------------+------------------------------------------------------+ | in_anglvel_x_calibbias | Calibration offset for the X-axis gyroscope channel. | +---------------------------------------+------------------------------------------------------+ -| in_anglvel_calibbias_x | x-axis gyroscope offset correction | -+---------------------------------------+------------------------------------------------------+ | in_anglvel_x_raw | Raw X-axis gyroscope channel value. | +---------------------------------------+------------------------------------------------------+ -| in_anglvel_calibbias_y | y-axis gyroscope offset correction | +| in_anglvel_y_calibbias | Calibration offset for the Y-axis gyroscope channel. | +---------------------------------------+------------------------------------------------------+ | in_anglvel_y_raw | Raw Y-axis gyroscope channel value. | +---------------------------------------+------------------------------------------------------+ From 62de5e3e31a863000beccc25d6fa65c6ac2cedb8 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 15 Apr 2024 17:18:52 +0300 Subject: [PATCH 223/319] iio: light: stk3310: Drop most likely fake ACPI ID The commit in question does not proove that ACPI ID exists. Quite likely it was a cargo cult addition while doint that for DT-based enumeration. Drop most likely fake ACPI ID. Googling for STK3335 gives no useful results in regard to DSDT. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240415141852.853490-1-andriy.shevchenko@linux.intel.com Signed-off-by: Jonathan Cameron --- drivers/iio/light/stk3310.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 7b71ad71d78d..08d471438175 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -693,7 +693,6 @@ MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); static const struct acpi_device_id stk3310_acpi_id[] = { {"STK3310", 0}, {"STK3311", 0}, - {"STK3335", 0}, {} }; From 3b0c13361298af009756d040176c8417c5f8146c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 22 Apr 2024 15:22:39 +0000 Subject: [PATCH 224/319] dt-bindings: iio: imu: add icm42686 inside inv_icm42600 Add bindings for ICM-42686-P chip supporting high FSRs. Signed-off-by: Jean-Baptiste Maneyrol Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240422152240.85974-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 5e0bed2c45de..3769f8e8e98c 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,icm42686 - invensense,icm42688 reg: From a1432b5b4f4c44473ee97152c2f356d372ccd45c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 22 Apr 2024 15:22:40 +0000 Subject: [PATCH 225/319] iio: imu: inv_icm42600: add support of ICM-42686-P Add ICM-42686-P chip supporting high FSRs (32G, 4000dps). Create accel and gyro iio device states with dynamic scales table set at device init. Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240422152240.85974-3-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42600/inv_icm42600.h | 35 ++++++++ .../iio/imu/inv_icm42600/inv_icm42600_accel.c | 75 ++++++++++++----- .../imu/inv_icm42600/inv_icm42600_buffer.c | 15 ++-- .../iio/imu/inv_icm42600/inv_icm42600_core.c | 21 +++++ .../iio/imu/inv_icm42600/inv_icm42600_gyro.c | 84 ++++++++++++++----- .../iio/imu/inv_icm42600/inv_icm42600_i2c.c | 3 + .../iio/imu/inv_icm42600/inv_icm42600_spi.c | 3 + 7 files changed, 193 insertions(+), 43 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h index 0566340b2660..c4ac91f6bafe 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "inv_icm42600_buffer.h" @@ -21,6 +22,7 @@ enum inv_icm42600_chip { INV_CHIP_ICM42600, INV_CHIP_ICM42602, INV_CHIP_ICM42605, + INV_CHIP_ICM42686, INV_CHIP_ICM42622, INV_CHIP_ICM42688, INV_CHIP_ICM42631, @@ -57,6 +59,17 @@ enum inv_icm42600_gyro_fs { INV_ICM42600_GYRO_FS_15_625DPS, INV_ICM42600_GYRO_FS_NB, }; +enum inv_icm42686_gyro_fs { + INV_ICM42686_GYRO_FS_4000DPS, + INV_ICM42686_GYRO_FS_2000DPS, + INV_ICM42686_GYRO_FS_1000DPS, + INV_ICM42686_GYRO_FS_500DPS, + INV_ICM42686_GYRO_FS_250DPS, + INV_ICM42686_GYRO_FS_125DPS, + INV_ICM42686_GYRO_FS_62_5DPS, + INV_ICM42686_GYRO_FS_31_25DPS, + INV_ICM42686_GYRO_FS_NB, +}; /* accelerometer fullscale values */ enum inv_icm42600_accel_fs { @@ -66,6 +79,14 @@ enum inv_icm42600_accel_fs { INV_ICM42600_ACCEL_FS_2G, INV_ICM42600_ACCEL_FS_NB, }; +enum inv_icm42686_accel_fs { + INV_ICM42686_ACCEL_FS_32G, + INV_ICM42686_ACCEL_FS_16G, + INV_ICM42686_ACCEL_FS_8G, + INV_ICM42686_ACCEL_FS_4G, + INV_ICM42686_ACCEL_FS_2G, + INV_ICM42686_ACCEL_FS_NB, +}; /* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */ enum inv_icm42600_odr { @@ -151,6 +172,19 @@ struct inv_icm42600_state { } timestamp; }; + +/** + * struct inv_icm42600_sensor_state - sensor state variables + * @scales: table of scales. + * @scales_len: length (nb of items) of the scales table. + * @ts: timestamp module states. + */ +struct inv_icm42600_sensor_state { + const int *scales; + size_t scales_len; + struct inv_sensors_timestamp ts; +}; + /* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */ /* Bank selection register, available in all banks */ @@ -304,6 +338,7 @@ struct inv_icm42600_state { #define INV_ICM42600_WHOAMI_ICM42600 0x40 #define INV_ICM42600_WHOAMI_ICM42602 0x41 #define INV_ICM42600_WHOAMI_ICM42605 0x42 +#define INV_ICM42600_WHOAMI_ICM42686 0x44 #define INV_ICM42600_WHOAMI_ICM42622 0x46 #define INV_ICM42600_WHOAMI_ICM42688 0x47 #define INV_ICM42600_WHOAMI_ICM42631 0x5C diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c index f67bd5a39beb..83d8504ebfff 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -99,7 +99,8 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; unsigned int fifo_en = 0; unsigned int sleep_temp = 0; @@ -210,33 +211,54 @@ static const int inv_icm42600_accel_scale[] = { [2 * INV_ICM42600_ACCEL_FS_2G] = 0, [2 * INV_ICM42600_ACCEL_FS_2G + 1] = 598550, }; +static const int inv_icm42686_accel_scale[] = { + /* +/- 32G => 0.009576807 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_32G] = 0, + [2 * INV_ICM42686_ACCEL_FS_32G + 1] = 9576807, + /* +/- 16G => 0.004788403 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_16G] = 0, + [2 * INV_ICM42686_ACCEL_FS_16G + 1] = 4788403, + /* +/- 8G => 0.002394202 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_8G] = 0, + [2 * INV_ICM42686_ACCEL_FS_8G + 1] = 2394202, + /* +/- 4G => 0.001197101 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_4G] = 0, + [2 * INV_ICM42686_ACCEL_FS_4G + 1] = 1197101, + /* +/- 2G => 0.000598550 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_2G] = 0, + [2 * INV_ICM42686_ACCEL_FS_2G + 1] = 598550, +}; -static int inv_icm42600_accel_read_scale(struct inv_icm42600_state *st, +static int inv_icm42600_accel_read_scale(struct iio_dev *indio_dev, int *val, int *val2) { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); unsigned int idx; idx = st->conf.accel.fs; - *val = inv_icm42600_accel_scale[2 * idx]; - *val2 = inv_icm42600_accel_scale[2 * idx + 1]; + *val = accel_st->scales[2 * idx]; + *val2 = accel_st->scales[2 * idx + 1]; return IIO_VAL_INT_PLUS_NANO; } -static int inv_icm42600_accel_write_scale(struct inv_icm42600_state *st, +static int inv_icm42600_accel_write_scale(struct iio_dev *indio_dev, int val, int val2) { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); struct device *dev = regmap_get_device(st->map); unsigned int idx; struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; int ret; - for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_accel_scale); idx += 2) { - if (val == inv_icm42600_accel_scale[idx] && - val2 == inv_icm42600_accel_scale[idx + 1]) + for (idx = 0; idx < accel_st->scales_len; idx += 2) { + if (val == accel_st->scales[idx] && + val2 == accel_st->scales[idx + 1]) break; } - if (idx >= ARRAY_SIZE(inv_icm42600_accel_scale)) + if (idx >= accel_st->scales_len) return -EINVAL; conf.fs = idx / 2; @@ -309,7 +331,8 @@ static int inv_icm42600_accel_write_odr(struct iio_dev *indio_dev, int val, int val2) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; struct device *dev = regmap_get_device(st->map); unsigned int idx; struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; @@ -565,7 +588,7 @@ static int inv_icm42600_accel_read_raw(struct iio_dev *indio_dev, *val = data; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - return inv_icm42600_accel_read_scale(st, val, val2); + return inv_icm42600_accel_read_scale(indio_dev, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: return inv_icm42600_accel_read_odr(st, val, val2); case IIO_CHAN_INFO_CALIBBIAS: @@ -580,14 +603,16 @@ static int inv_icm42600_accel_read_avail(struct iio_dev *indio_dev, const int **vals, int *type, int *length, long mask) { + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + if (chan->type != IIO_ACCEL) return -EINVAL; switch (mask) { case IIO_CHAN_INFO_SCALE: - *vals = inv_icm42600_accel_scale; + *vals = accel_st->scales; *type = IIO_VAL_INT_PLUS_NANO; - *length = ARRAY_SIZE(inv_icm42600_accel_scale); + *length = accel_st->scales_len; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_SAMP_FREQ: *vals = inv_icm42600_accel_odr; @@ -618,7 +643,7 @@ static int inv_icm42600_accel_write_raw(struct iio_dev *indio_dev, ret = iio_device_claim_direct_mode(indio_dev); if (ret) return ret; - ret = inv_icm42600_accel_write_scale(st, val, val2); + ret = inv_icm42600_accel_write_scale(indio_dev, val, val2); iio_device_release_direct_mode(indio_dev); return ret; case IIO_CHAN_INFO_SAMP_FREQ: @@ -705,8 +730,8 @@ struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st) { struct device *dev = regmap_get_device(st->map); const char *name; + struct inv_icm42600_sensor_state *accel_st; struct inv_sensors_timestamp_chip ts_chip; - struct inv_sensors_timestamp *ts; struct iio_dev *indio_dev; int ret; @@ -714,9 +739,21 @@ struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st) if (!name) return ERR_PTR(-ENOMEM); - indio_dev = devm_iio_device_alloc(dev, sizeof(*ts)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st)); if (!indio_dev) return ERR_PTR(-ENOMEM); + accel_st = iio_priv(indio_dev); + + switch (st->chip) { + case INV_CHIP_ICM42686: + accel_st->scales = inv_icm42686_accel_scale; + accel_st->scales_len = ARRAY_SIZE(inv_icm42686_accel_scale); + break; + default: + accel_st->scales = inv_icm42600_accel_scale; + accel_st->scales_len = ARRAY_SIZE(inv_icm42600_accel_scale); + break; + } /* * clock period is 32kHz (31250ns) @@ -725,8 +762,7 @@ struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st) ts_chip.clock_period = 31250; ts_chip.jitter = 20; ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr); - ts = iio_priv(indio_dev); - inv_sensors_timestamp_init(ts, &ts_chip); + inv_sensors_timestamp_init(&accel_st->ts, &ts_chip); iio_device_set_drvdata(indio_dev, st); indio_dev->name = name; @@ -751,7 +787,8 @@ struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st) int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; ssize_t i, size; unsigned int no; const void *accel, *gyro, *timestamp; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index b52f328fd26c..cfb4a41ab7c1 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -276,7 +276,8 @@ static int inv_icm42600_buffer_preenable(struct iio_dev *indio_dev) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); struct device *dev = regmap_get_device(st->map); - struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_state *sensor_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &sensor_st->ts; pm_runtime_get_sync(dev); @@ -502,6 +503,8 @@ int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) { + struct inv_icm42600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm42600_sensor_state *accel_st = iio_priv(st->indio_accel); struct inv_sensors_timestamp *ts; int ret; @@ -509,7 +512,7 @@ int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) return 0; /* handle gyroscope timestamp and FIFO data parsing */ - ts = iio_priv(st->indio_gyro); + ts = &gyro_st->ts; inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, st->fifo.nb.gyro, st->timestamp.gyro); if (st->fifo.nb.gyro > 0) { @@ -519,7 +522,7 @@ int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) } /* handle accelerometer timestamp and FIFO data parsing */ - ts = iio_priv(st->indio_accel); + ts = &accel_st->ts; inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, st->fifo.nb.accel, st->timestamp.accel); if (st->fifo.nb.accel > 0) { @@ -534,6 +537,8 @@ int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, unsigned int count) { + struct inv_icm42600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm42600_sensor_state *accel_st = iio_priv(st->indio_accel); struct inv_sensors_timestamp *ts; int64_t gyro_ts, accel_ts; int ret; @@ -549,7 +554,7 @@ int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, return 0; if (st->fifo.nb.gyro > 0) { - ts = iio_priv(st->indio_gyro); + ts = &gyro_st->ts; inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, st->fifo.nb.gyro, gyro_ts); @@ -559,7 +564,7 @@ int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, } if (st->fifo.nb.accel > 0) { - ts = iio_priv(st->indio_accel); + ts = &accel_st->ts; inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, st->fifo.nb.accel, accel_ts); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c index 82e0a2e2ad70..96116a68ab29 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -66,6 +66,22 @@ static const struct inv_icm42600_conf inv_icm42600_default_conf = { .temp_en = false, }; +static const struct inv_icm42600_conf inv_icm42686_default_conf = { + .gyro = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42686_GYRO_FS_4000DPS, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .accel = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42686_ACCEL_FS_32G, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .temp_en = false, +}; + static const struct inv_icm42600_hw inv_icm42600_hw[INV_CHIP_NB] = { [INV_CHIP_ICM42600] = { .whoami = INV_ICM42600_WHOAMI_ICM42600, @@ -82,6 +98,11 @@ static const struct inv_icm42600_hw inv_icm42600_hw[INV_CHIP_NB] = { .name = "icm42605", .conf = &inv_icm42600_default_conf, }, + [INV_CHIP_ICM42686] = { + .whoami = INV_ICM42600_WHOAMI_ICM42686, + .name = "icm42686", + .conf = &inv_icm42686_default_conf, + }, [INV_CHIP_ICM42622] = { .whoami = INV_ICM42600_WHOAMI_ICM42622, .name = "icm42622", diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c index 3df0a715e885..e6f8de80128c 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -99,7 +99,8 @@ static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; unsigned int fifo_en = 0; unsigned int sleep_gyro = 0; @@ -222,33 +223,63 @@ static const int inv_icm42600_gyro_scale[] = { [2 * INV_ICM42600_GYRO_FS_15_625DPS] = 0, [2 * INV_ICM42600_GYRO_FS_15_625DPS + 1] = 8322, }; +static const int inv_icm42686_gyro_scale[] = { + /* +/- 4000dps => 0.002130529 rad/s */ + [2 * INV_ICM42686_GYRO_FS_4000DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_4000DPS + 1] = 2130529, + /* +/- 2000dps => 0.001065264 rad/s */ + [2 * INV_ICM42686_GYRO_FS_2000DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_2000DPS + 1] = 1065264, + /* +/- 1000dps => 0.000532632 rad/s */ + [2 * INV_ICM42686_GYRO_FS_1000DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_1000DPS + 1] = 532632, + /* +/- 500dps => 0.000266316 rad/s */ + [2 * INV_ICM42686_GYRO_FS_500DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_500DPS + 1] = 266316, + /* +/- 250dps => 0.000133158 rad/s */ + [2 * INV_ICM42686_GYRO_FS_250DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_250DPS + 1] = 133158, + /* +/- 125dps => 0.000066579 rad/s */ + [2 * INV_ICM42686_GYRO_FS_125DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_125DPS + 1] = 66579, + /* +/- 62.5dps => 0.000033290 rad/s */ + [2 * INV_ICM42686_GYRO_FS_62_5DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_62_5DPS + 1] = 33290, + /* +/- 31.25dps => 0.000016645 rad/s */ + [2 * INV_ICM42686_GYRO_FS_31_25DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_31_25DPS + 1] = 16645, +}; -static int inv_icm42600_gyro_read_scale(struct inv_icm42600_state *st, +static int inv_icm42600_gyro_read_scale(struct iio_dev *indio_dev, int *val, int *val2) { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); unsigned int idx; idx = st->conf.gyro.fs; - *val = inv_icm42600_gyro_scale[2 * idx]; - *val2 = inv_icm42600_gyro_scale[2 * idx + 1]; + *val = gyro_st->scales[2 * idx]; + *val2 = gyro_st->scales[2 * idx + 1]; return IIO_VAL_INT_PLUS_NANO; } -static int inv_icm42600_gyro_write_scale(struct inv_icm42600_state *st, +static int inv_icm42600_gyro_write_scale(struct iio_dev *indio_dev, int val, int val2) { + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); struct device *dev = regmap_get_device(st->map); unsigned int idx; struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; int ret; - for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_gyro_scale); idx += 2) { - if (val == inv_icm42600_gyro_scale[idx] && - val2 == inv_icm42600_gyro_scale[idx + 1]) + for (idx = 0; idx < gyro_st->scales_len; idx += 2) { + if (val == gyro_st->scales[idx] && + val2 == gyro_st->scales[idx + 1]) break; } - if (idx >= ARRAY_SIZE(inv_icm42600_gyro_scale)) + if (idx >= gyro_st->scales_len) return -EINVAL; conf.fs = idx / 2; @@ -321,7 +352,8 @@ static int inv_icm42600_gyro_write_odr(struct iio_dev *indio_dev, int val, int val2) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; struct device *dev = regmap_get_device(st->map); unsigned int idx; struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; @@ -576,7 +608,7 @@ static int inv_icm42600_gyro_read_raw(struct iio_dev *indio_dev, *val = data; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - return inv_icm42600_gyro_read_scale(st, val, val2); + return inv_icm42600_gyro_read_scale(indio_dev, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: return inv_icm42600_gyro_read_odr(st, val, val2); case IIO_CHAN_INFO_CALIBBIAS: @@ -591,14 +623,16 @@ static int inv_icm42600_gyro_read_avail(struct iio_dev *indio_dev, const int **vals, int *type, int *length, long mask) { + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + if (chan->type != IIO_ANGL_VEL) return -EINVAL; switch (mask) { case IIO_CHAN_INFO_SCALE: - *vals = inv_icm42600_gyro_scale; + *vals = gyro_st->scales; *type = IIO_VAL_INT_PLUS_NANO; - *length = ARRAY_SIZE(inv_icm42600_gyro_scale); + *length = gyro_st->scales_len; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_SAMP_FREQ: *vals = inv_icm42600_gyro_odr; @@ -629,7 +663,7 @@ static int inv_icm42600_gyro_write_raw(struct iio_dev *indio_dev, ret = iio_device_claim_direct_mode(indio_dev); if (ret) return ret; - ret = inv_icm42600_gyro_write_scale(st, val, val2); + ret = inv_icm42600_gyro_write_scale(indio_dev, val, val2); iio_device_release_direct_mode(indio_dev); return ret; case IIO_CHAN_INFO_SAMP_FREQ: @@ -716,8 +750,8 @@ struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st) { struct device *dev = regmap_get_device(st->map); const char *name; + struct inv_icm42600_sensor_state *gyro_st; struct inv_sensors_timestamp_chip ts_chip; - struct inv_sensors_timestamp *ts; struct iio_dev *indio_dev; int ret; @@ -725,9 +759,21 @@ struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st) if (!name) return ERR_PTR(-ENOMEM); - indio_dev = devm_iio_device_alloc(dev, sizeof(*ts)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st)); if (!indio_dev) return ERR_PTR(-ENOMEM); + gyro_st = iio_priv(indio_dev); + + switch (st->chip) { + case INV_CHIP_ICM42686: + gyro_st->scales = inv_icm42686_gyro_scale; + gyro_st->scales_len = ARRAY_SIZE(inv_icm42686_gyro_scale); + break; + default: + gyro_st->scales = inv_icm42600_gyro_scale; + gyro_st->scales_len = ARRAY_SIZE(inv_icm42600_gyro_scale); + break; + } /* * clock period is 32kHz (31250ns) @@ -736,8 +782,7 @@ struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st) ts_chip.clock_period = 31250; ts_chip.jitter = 20; ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr); - ts = iio_priv(indio_dev); - inv_sensors_timestamp_init(ts, &ts_chip); + inv_sensors_timestamp_init(&gyro_st->ts, &ts_chip); iio_device_set_drvdata(indio_dev, st); indio_dev->name = name; @@ -763,7 +808,8 @@ struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st) int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - struct inv_sensors_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; ssize_t i, size; unsigned int no; const void *accel, *gyro, *timestamp; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c index ebb28f84ba98..8d33504d770f 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c @@ -81,6 +81,9 @@ static const struct of_device_id inv_icm42600_of_matches[] = { }, { .compatible = "invensense,icm42605", .data = (void *)INV_CHIP_ICM42605, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c index 50217a10e0bb..cc2bf1799a46 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c @@ -77,6 +77,9 @@ static const struct of_device_id inv_icm42600_of_matches[] = { }, { .compatible = "invensense,icm42605", .data = (void *)INV_CHIP_ICM42605, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, From 73e49886a2834b79630cf4db6a98172aa066ce0e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 24 Apr 2024 14:45:30 +0300 Subject: [PATCH 226/319] iio: dac: adi-axi: fix a mistake in axi_dac_ext_info_set() The last parameter of these axi_dac_(frequency|scale|phase)_set() functions is supposed to be true for TONE_2 and false for TONE_1. The bug is the last call where it passes "private - TONE_2". That subtraction is going to be zero/false for TONE_2 and and -1/true for TONE_1. Fix the bug, and re-write it as "private == TONE_2" so it's more obvious what is happening. Fixes: 4e3949a192e4 ("iio: dac: add support for AXI DAC IP core") Signed-off-by: Dan Carpenter Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/df7c6e1b-b619-40c3-9881-838587ed15d4@moroto.mountain Signed-off-by: Jonathan Cameron --- drivers/iio/dac/adi-axi-dac.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c index 9047c5aec0ff..880d83a014a1 100644 --- a/drivers/iio/dac/adi-axi-dac.c +++ b/drivers/iio/dac/adi-axi-dac.c @@ -383,15 +383,15 @@ static int axi_dac_ext_info_set(struct iio_backend *back, uintptr_t 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); + private == AXI_DAC_FREQ_TONE_2); 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); + private == AXI_DAC_SCALE_TONE_2); 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); + private == AXI_DAC_PHASE_TONE_2); default: return -EOPNOTSUPP; } From 64ce7d4348be630badad2af22030bebf028b2861 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 25 Apr 2024 09:09:59 -0500 Subject: [PATCH 227/319] iio: adc: ad7944: add support for chain mode This adds support for the chain mode of the AD7944 ADC. This mode allows multiple ADCs to be daisy-chained together. Data from all of the ADCs in is read by reading multiple words from the first ADC in the chain. Each chip in the chain adds an extra IIO input voltage channel to the IIO device. Only the wiring configuration where the SPI controller CS line is connected to the CNV pin of all of the ADCs in the chain is supported in this patch. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240425-iio-ad7944-chain-mode-v1-1-9d9220ff21e1@baylibre.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7944.c | 186 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index 4af574ffa864..4602ab5ed2a6 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -6,6 +6,7 @@ * Copyright 2024 BayLibre, SAS */ +#include #include #include #include @@ -53,6 +54,7 @@ struct ad7944_adc { enum ad7944_spi_mode spi_mode; struct spi_transfer xfers[3]; struct spi_message msg; + void *chain_mode_buf; /* Chip-specific timing specifications. */ const struct ad7944_timing_spec *timing_spec; /* GPIO connected to CNV pin. */ @@ -214,6 +216,46 @@ 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); } +static int ad7944_chain_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan, + u32 n_chain_dev) +{ + struct spi_transfer *xfers = adc->xfers; + int ret; + + /* + * NB: SCLK has to be low before we toggle CS to avoid triggering the + * busy indication. + */ + if (adc->spi->mode & SPI_CPOL) + return dev_err_probe(dev, -EINVAL, + "chain mode requires ~SPI_CPOL\n"); + + /* + * We only support CNV connected to CS in chain mode and we need CNV + * to be high during the transfer to trigger the conversion. + */ + if (!(adc->spi->mode & SPI_CS_HIGH)) + return dev_err_probe(dev, -EINVAL, + "chain mode requires SPI_CS_HIGH\n"); + + /* CNV has to be high for full conversion time before reading data. */ + xfers[0].delay.value = adc->timing_spec->conv_ns; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = adc->chain_mode_buf; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits) * n_chain_dev; + 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_convert_and_acquire - Perform a single conversion and acquisition * @adc: The ADC device structure @@ -223,7 +265,8 @@ static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc * 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. + * Upon successful return adc->sample.raw will contain the conversion result + * (or adc->chain_mode_buf if the device is using chain mode). */ static int ad7944_convert_and_acquire(struct ad7944_adc *adc, const struct iio_chan_spec *chan) @@ -252,10 +295,17 @@ static int ad7944_single_conversion(struct ad7944_adc *adc, if (ret) return ret; - if (chan->scan_type.storagebits > 16) - *val = adc->sample.raw.u32; - else - *val = adc->sample.raw.u16; + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) { + if (chan->scan_type.storagebits > 16) + *val = ((u32 *)adc->chain_mode_buf)[chan->scan_index]; + else + *val = ((u16 *)adc->chain_mode_buf)[chan->scan_index]; + } else { + 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); @@ -315,8 +365,12 @@ static irqreturn_t ad7944_trigger_handler(int irq, void *p) if (ret) goto out; - iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, - pf->timestamp); + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) + iio_push_to_buffers_with_timestamp(indio_dev, adc->chain_mode_buf, + pf->timestamp); + else + iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, + pf->timestamp); out: iio_trigger_notify_done(indio_dev->trig); @@ -324,6 +378,90 @@ out: return IRQ_HANDLED; } +/** + * ad7944_chain_mode_alloc - allocate and initialize channel specs and buffers + * for daisy-chained devices + * @dev: The device for devm_ functions + * @chan_template: The channel template for the devices (array of 2 channels + * voltage and timestamp) + * @n_chain_dev: The number of devices in the chain + * @chain_chan: Pointer to receive the allocated channel specs + * @chain_mode_buf: Pointer to receive the allocated rx buffer + * @chain_scan_masks: Pointer to receive the allocated scan masks + * Return: 0 on success, a negative error code on failure + */ +static int ad7944_chain_mode_alloc(struct device *dev, + const struct iio_chan_spec *chan_template, + u32 n_chain_dev, + struct iio_chan_spec **chain_chan, + void **chain_mode_buf, + unsigned long **chain_scan_masks) +{ + struct iio_chan_spec *chan; + size_t chain_mode_buf_size; + unsigned long *scan_masks; + void *buf; + int i; + + /* 1 channel for each device in chain plus 1 for soft timestamp */ + + chan = devm_kcalloc(dev, n_chain_dev + 1, sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + for (i = 0; i < n_chain_dev; i++) { + chan[i] = chan_template[0]; + + if (chan_template[0].differential) { + chan[i].channel = 2 * i; + chan[i].channel2 = 2 * i + 1; + } else { + chan[i].channel = i; + } + + chan[i].scan_index = i; + } + + /* soft timestamp */ + chan[i] = chan_template[1]; + chan[i].scan_index = i; + + *chain_chan = chan; + + /* 1 word for each voltage channel + aligned u64 for timestamp */ + + chain_mode_buf_size = ALIGN(n_chain_dev * + BITS_TO_BYTES(chan[0].scan_type.storagebits), sizeof(u64)) + + sizeof(u64); + buf = devm_kzalloc(dev, chain_mode_buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + *chain_mode_buf = buf; + + /* + * Have to limit n_chain_dev due to current implementation of + * available_scan_masks. + */ + if (n_chain_dev > BITS_PER_LONG) + return dev_err_probe(dev, -EINVAL, + "chain is limited to 32 devices\n"); + + scan_masks = devm_kcalloc(dev, 2, sizeof(*scan_masks), GFP_KERNEL); + if (!scan_masks) + return -ENOMEM; + + /* + * Scan mask is needed since we always have to read all devices in the + * chain in one SPI transfer. + */ + scan_masks[0] = GENMASK(n_chain_dev - 1, 0); + + *chain_scan_masks = scan_masks; + + return 0; +} + static const char * const ad7944_power_supplies[] = { "avdd", "dvdd", "bvdd", "vio" }; @@ -341,6 +479,9 @@ static int ad7944_probe(struct spi_device *spi) struct ad7944_adc *adc; bool have_refin = false; struct regulator *ref; + struct iio_chan_spec *chain_chan; + unsigned long *chain_scan_masks; + u32 n_chain_dev; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); @@ -474,14 +615,39 @@ static int ad7944_probe(struct spi_device *spi) break; case AD7944_SPI_MODE_CHAIN: - return dev_err_probe(dev, -EINVAL, "chain mode is not implemented\n"); + ret = device_property_read_u32(dev, "#daisy-chained-devices", + &n_chain_dev); + if (ret) + return dev_err_probe(dev, ret, + "failed to get #daisy-chained-devices\n"); + + ret = ad7944_chain_mode_alloc(dev, chip_info->channels, + n_chain_dev, &chain_chan, + &adc->chain_mode_buf, + &chain_scan_masks); + if (ret) + return ret; + + ret = ad7944_chain_mode_init_msg(dev, adc, &chain_chan[0], + n_chain_dev); + if (ret) + return ret; + + break; } 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); + + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) { + indio_dev->available_scan_masks = chain_scan_masks; + indio_dev->channels = chain_chan; + indio_dev->num_channels = n_chain_dev + 1; + } else { + 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, From 633e3015cae062a424a6dd5099710adfe83a5e14 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Thu, 25 Apr 2024 09:10:00 -0500 Subject: [PATCH 228/319] docs: iio: ad7944: add documentation for chain mode Add documentation for chain mode support that was recently added to the AD7944 ADC driver. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240425-iio-ad7944-chain-mode-v1-2-9d9220ff21e1@baylibre.com Signed-off-by: Jonathan Cameron --- Documentation/iio/ad7944.rst | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Documentation/iio/ad7944.rst b/Documentation/iio/ad7944.rst index f418ab1288ae..0d26e56aba88 100644 --- a/Documentation/iio/ad7944.rst +++ b/Documentation/iio/ad7944.rst @@ -24,7 +24,7 @@ Supported features SPI wiring modes ---------------- -The driver currently supports two of the many possible SPI wiring configurations. +The driver currently supports three of the many possible SPI wiring configurations. CS mode, 3-wire, without busy indicator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -68,6 +68,27 @@ CS mode, 4-wire, without busy indicator To select this mode in the device tree, omit the ``adi,spi-mode`` property and provide the ``cnv-gpios`` property. +Chain mode, without busy indicator +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: + + +-------------+ + +-------------------------+--------------------| CS | + v v | | + +--------------------+ +--------------------+ | HOST | + | CNV | | CNV | | | + +--->| SDI AD7944 SDO |--->| SDI AD7944 SDO |-------->| SDI | + | | SCK | | SCK | | | + GND +--------------------+ +--------------------+ | | + ^ ^ | | + +-------------------------+--------------------| SCLK | + +-------------+ + +To select this mode in the device tree, set the ``adi,spi-mode`` property to +``"chain"``, add the ``spi-cs-high`` flag, add the ``#daisy-chained-devices`` +property, and omit the ``cnv-gpios`` property. + Reference voltage ----------------- @@ -86,7 +107,6 @@ Unimplemented features - ``BUSY`` indication - ``TURBO`` mode -- Daisy chain mode Device attributes @@ -108,6 +128,9 @@ AD7944 and AD7985 are pseudo-differential ADCs and have the following attributes | ``in_voltage0_scale`` | Scale factor to convert raw value to mV. | +---------------------------------------+--------------------------------------------------------------+ +In "chain" mode, additional chips will appear as additional voltage input +channels, e.g. ``in_voltage1_raw``. + Fully-differential ADCs ----------------------- @@ -121,6 +144,9 @@ AD7986 is a fully-differential ADC and has the following attributes: | ``in_voltage0-voltage1_scale`` | Scale factor to convert raw value to mV. | +---------------------------------------+--------------------------------------------------------------+ +In "chain" mode, additional chips will appear as additional voltage input +channels, e.g. ``in_voltage2-voltage3_raw``. + Device buffers ============== From 19fb11d7220b8abc016aa254dc7e6d9f2d49b178 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Apr 2024 17:42:12 +0200 Subject: [PATCH 229/319] dt-bindings: adc: axi-adc: add clocks property Add a required clock property as we can't access the device registers if the AXI bus clock is not properly enabled. Note this clock is a very fundamental one that is typically enabled pretty early during boot. Independently of that, we should really rely on it to be enabled. Reviewed-by: Krzysztof Kozlowski Fixes: 96553a44e96d ("dt-bindings: iio: adc: add bindings doc for AXI ADC driver") Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240426-ad9467-new-features-v2-3-6361fc3ba1cc@analog.com Cc: Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,axi-adc.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,axi-adc.yaml b/Documentation/devicetree/bindings/iio/adc/adi,axi-adc.yaml index 3d49d21ad33d..e1f450b80db2 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,axi-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,axi-adc.yaml @@ -28,6 +28,9 @@ properties: reg: maxItems: 1 + clocks: + maxItems: 1 + dmas: maxItems: 1 @@ -48,6 +51,7 @@ required: - compatible - dmas - reg + - clocks additionalProperties: false @@ -58,6 +62,7 @@ examples: reg = <0x44a00000 0x10000>; dmas = <&rx_dma 0>; dma-names = "rx"; + clocks = <&axi_clk>; #io-backend-cells = <0>; }; ... From 80721776c5af6f6dce7d84ba8df063957aa425a2 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Apr 2024 17:42:13 +0200 Subject: [PATCH 230/319] iio: adc: axi-adc: make sure AXI clock is enabled We can only access the IP core registers if the bus clock is enabled. As such we need to get and enable it and not rely on anyone else to do it. Note this clock is a very fundamental one that is typically enabled pretty early during boot. Independently of that, we should really rely on it to be enabled. 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/20240426-ad9467-new-features-v2-4-6361fc3ba1cc@analog.com Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 9444b0c5a93c..f54830658da8 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -161,6 +161,7 @@ static int adi_axi_adc_probe(struct platform_device *pdev) struct adi_axi_adc_state *st; void __iomem *base; unsigned int ver; + struct clk *clk; int ret; st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); @@ -181,6 +182,10 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (!expected_ver) return -ENODEV; + clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + /* * Force disable the core. Up to the frontend to enable us. And we can * still read/write registers... From 09415814cd1d0b90b898f81d6ad6a1c0a2e22d32 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Apr 2024 17:42:10 +0200 Subject: [PATCH 231/319] iio: backend: change docs padding Using tabs and maintaining the start of the docs aligned is a pain and may lead to lot's of unrelated changes when adding new members. Hence, let#s change things now and just have a simple space after the member name. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240426-ad9467-new-features-v2-1-6361fc3ba1cc@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-backend.c | 76 +++++++++++++++--------------- include/linux/iio/backend.h | 38 +++++++-------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c index f08ed6d70ae5..c27243e88462 100644 --- a/drivers/iio/industrialio-backend.c +++ b/drivers/iio/industrialio-backend.c @@ -115,8 +115,8 @@ static DEFINE_MUTEX(iio_back_lock); /** * iio_backend_chan_enable - Enable a backend channel - * @back: Backend device - * @chan: Channel number + * @back: Backend device + * @chan: Channel number * * RETURNS: * 0 on success, negative error number on failure. @@ -129,8 +129,8 @@ EXPORT_SYMBOL_NS_GPL(iio_backend_chan_enable, IIO_BACKEND); /** * iio_backend_chan_disable - Disable a backend channel - * @back: Backend device - * @chan: Channel number + * @back: Backend device + * @chan: Channel number * * RETURNS: * 0 on success, negative error number on failure. @@ -148,8 +148,8 @@ static void __iio_backend_disable(void *back) /** * devm_iio_backend_enable - Device managed backend enable - * @dev: Consumer device for the backend - * @back: Backend device + * @dev: Consumer device for the backend + * @back: Backend device * * RETURNS: * 0 on success, negative error number on failure. @@ -168,9 +168,9 @@ EXPORT_SYMBOL_NS_GPL(devm_iio_backend_enable, IIO_BACKEND); /** * iio_backend_data_format_set - Configure the channel data format - * @back: Backend device - * @chan: Channel number - * @data: Data format + * @back: Backend device + * @chan: Channel number + * @data: Data format * * Properly configure a channel with respect to the expected data format. A * @struct iio_backend_data_fmt must be passed with the settings. @@ -190,9 +190,9 @@ 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 + * @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. @@ -212,9 +212,9 @@ 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 + * @back: Backend device + * @chan: Channel number + * @sample_rate_hz: Sample rate * * RETURNS: * 0 on success, negative error number on failure. @@ -235,9 +235,9 @@ static void iio_backend_free_buffer(void *arg) /** * devm_iio_backend_request_buffer - Device managed buffer request - * @dev: Consumer device for the backend - * @back: Backend device - * @indio_dev: IIO device + * @dev: Consumer device for the backend + * @back: Backend device + * @indio_dev: IIO device * * Request an IIO buffer from the backend. The type of the buffer (typically * INDIO_BUFFER_HARDWARE) is up to the backend to decide. This is because, @@ -300,10 +300,10 @@ static struct iio_backend *iio_backend_from_indio_dev_parent(const struct device /** * 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 + * @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, @@ -335,11 +335,11 @@ 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 + * @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, @@ -365,9 +365,9 @@ 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 + * @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. @@ -449,8 +449,8 @@ static int __devm_iio_backend_get(struct device *dev, struct iio_backend *back) /** * devm_iio_backend_get - Device managed backend device get - * @dev: Consumer device for the backend - * @name: Backend name + * @dev: Consumer device for the backend + * @name: Backend name * * Get's the backend associated with @dev. * @@ -501,8 +501,8 @@ EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get, IIO_BACKEND); /** * __devm_iio_backend_get_from_fwnode_lookup - Device managed fwnode backend device get - * @dev: Consumer device for the backend - * @fwnode: Firmware node of the backend device + * @dev: Consumer device for the backend + * @fwnode: Firmware node of the backend device * * Search the backend list for a device matching @fwnode. * This API should not be used and it's only present for preventing the first @@ -536,7 +536,7 @@ EXPORT_SYMBOL_NS_GPL(__devm_iio_backend_get_from_fwnode_lookup, IIO_BACKEND); /** * iio_backend_get_priv - Get driver private data - * @back: Backend device + * @back: Backend device */ void *iio_backend_get_priv(const struct iio_backend *back) { @@ -554,9 +554,9 @@ static void iio_backend_unregister(void *arg) /** * devm_iio_backend_register - Device managed backend device register - * @dev: Backend device being registered - * @ops: Backend ops - * @priv: Device private data + * @dev: Backend device being registered + * @ops: Backend ops + * @priv: Device private data * * @ops is mandatory. Not providing it results in -EINVAL. * diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h index 9d144631134d..e3e62f65db14 100644 --- a/include/linux/iio/backend.h +++ b/include/linux/iio/backend.h @@ -24,9 +24,9 @@ enum iio_backend_data_source { /** * 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 + * @_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), \ @@ -38,10 +38,10 @@ enum iio_backend_data_source { /** * struct iio_backend_data_fmt - Backend data format - * @type: Data type. - * @sign_extend: Bool to tell if the data is sign extended. - * @enable: Enable/Disable the data format module. If disabled, - * not formatting will happen. + * @type: Data type. + * @sign_extend: Bool to tell if the data is sign extended. + * @enable: Enable/Disable the data format module. If disabled, + * not formatting will happen. */ struct iio_backend_data_fmt { enum iio_backend_data_type type; @@ -51,18 +51,18 @@ struct iio_backend_data_fmt { /** * struct iio_backend_ops - operations structure for an iio_backend - * @enable: Enable backend. - * @disable: Disable backend. - * @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. + * @enable: Enable backend. + * @disable: Disable backend. + * @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); From c66eabcc1ca64dbf20d0758ce210a85fa83f4b21 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Apr 2024 17:42:11 +0200 Subject: [PATCH 232/319] iio: backend: add API for interface tuning This is in preparation for supporting interface tuning in one for the devices using the axi-adc backend. The new added interfaces are all needed for that calibration: * iio_backend_test_pattern_set(); * iio_backend_chan_status(); * iio_backend_iodelay_set(); * iio_backend_data_sample_trigger(). Interface tuning is the process of going through a set of known points (typically by the frontend), change some clk or data delays (or both) and send/receive some known signal (so called test patterns in this change). The receiving end (either frontend or the backend) is responsible for validating the signal and see if it's good or not. The goal for all of this is to come up with ideal delays at the data interface level so we can have a proper, more reliable data transfer. Also note that for some devices we can change the sampling rate (which typically means changing some reference clock) and that can affect the data interface. In that case, it's import to run the tuning algorithm again as the values we had before may no longer be the best (or even valid) ones. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240426-ad9467-new-features-v2-2-6361fc3ba1cc@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-backend.c | 86 ++++++++++++++++++++++++++++++ include/linux/iio/backend.h | 36 +++++++++++++ 2 files changed, 122 insertions(+) diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c index c27243e88462..929aff4040ed 100644 --- a/drivers/iio/industrialio-backend.c +++ b/drivers/iio/industrialio-backend.c @@ -226,6 +226,92 @@ int iio_backend_set_sampling_freq(struct iio_backend *back, unsigned int chan, } EXPORT_SYMBOL_NS_GPL(iio_backend_set_sampling_freq, IIO_BACKEND); +/** + * iio_backend_test_pattern_set - Configure a test pattern + * @back: Backend device + * @chan: Channel number + * @pattern: Test pattern + * + * Configure a test pattern on the backend. This is typically used for + * calibrating the timings on the data digital interface. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_test_pattern_set(struct iio_backend *back, + unsigned int chan, + enum iio_backend_test_pattern pattern) +{ + if (pattern >= IIO_BACKEND_TEST_PATTERN_MAX) + return -EINVAL; + + return iio_backend_op_call(back, test_pattern_set, chan, pattern); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_test_pattern_set, IIO_BACKEND); + +/** + * iio_backend_chan_status - Get the channel status + * @back: Backend device + * @chan: Channel number + * @error: Error indication + * + * Get the current state of the backend channel. Typically used to check if + * there were any errors sending/receiving data. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_chan_status(struct iio_backend *back, unsigned int chan, + bool *error) +{ + return iio_backend_op_call(back, chan_status, chan, error); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_chan_status, IIO_BACKEND); + +/** + * iio_backend_iodelay_set - Set digital I/O delay + * @back: Backend device + * @lane: Lane number + * @taps: Number of taps + * + * Controls delays on sending/receiving data. One usecase for this is to + * calibrate the data digital interface so we get the best results when + * transferring data. Note that @taps has no unit since the actual delay per tap + * is very backend specific. Hence, frontend devices typically should go through + * an array of @taps (the size of that array should typically match the size of + * calibration points on the frontend device) and call this API. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_iodelay_set(struct iio_backend *back, unsigned int lane, + unsigned int taps) +{ + return iio_backend_op_call(back, iodelay_set, lane, taps); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_iodelay_set, IIO_BACKEND); + +/** + * iio_backend_data_sample_trigger - Control when to sample data + * @back: Backend device + * @trigger: Data trigger + * + * Mostly useful for input backends. Configures the backend for when to sample + * data (eg: rising vs falling edge). + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_sample_trigger(struct iio_backend *back, + enum iio_backend_sample_trigger trigger) +{ + if (trigger >= IIO_BACKEND_SAMPLE_TRIGGER_MAX) + return -EINVAL; + + return iio_backend_op_call(back, data_sample_trigger, trigger); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_sample_trigger, IIO_BACKEND); + static void iio_backend_free_buffer(void *arg) { struct iio_backend_buffer_pair *pair = arg; diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h index e3e62f65db14..8099759d7242 100644 --- a/include/linux/iio/backend.h +++ b/include/linux/iio/backend.h @@ -49,6 +49,20 @@ struct iio_backend_data_fmt { bool enable; }; +/* vendor specific from 32 */ +enum iio_backend_test_pattern { + IIO_BACKEND_NO_TEST_PATTERN, + /* modified prbs9 */ + IIO_BACKEND_ADI_PRBS_9A = 32, + IIO_BACKEND_TEST_PATTERN_MAX +}; + +enum iio_backend_sample_trigger { + IIO_BACKEND_SAMPLE_TRIGGER_EDGE_FALLING, + IIO_BACKEND_SAMPLE_TRIGGER_EDGE_RISING, + IIO_BACKEND_SAMPLE_TRIGGER_MAX +}; + /** * struct iio_backend_ops - operations structure for an iio_backend * @enable: Enable backend. @@ -58,6 +72,10 @@ struct iio_backend_data_fmt { * @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. + * @test_pattern_set: Configure a test pattern. + * @chan_status: Get the channel status. + * @iodelay_set: Set digital I/O delay. + * @data_sample_trigger: Control when to sample data. * @request_buffer: Request an IIO buffer. * @free_buffer: Free an IIO buffer. * @extend_chan_spec: Extend an IIO channel. @@ -75,6 +93,15 @@ struct iio_backend_ops { enum iio_backend_data_source data); int (*set_sample_rate)(struct iio_backend *back, unsigned int chan, u64 sample_rate_hz); + int (*test_pattern_set)(struct iio_backend *back, + unsigned int chan, + enum iio_backend_test_pattern pattern); + int (*chan_status)(struct iio_backend *back, unsigned int chan, + bool *error); + int (*iodelay_set)(struct iio_backend *back, unsigned int chan, + unsigned int taps); + int (*data_sample_trigger)(struct iio_backend *back, + enum iio_backend_sample_trigger trigger); struct iio_buffer *(*request_buffer)(struct iio_backend *back, struct iio_dev *indio_dev); void (*free_buffer)(struct iio_backend *back, @@ -97,6 +124,15 @@ 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 iio_backend_test_pattern_set(struct iio_backend *back, + unsigned int chan, + enum iio_backend_test_pattern pattern); +int iio_backend_chan_status(struct iio_backend *back, unsigned int chan, + bool *error); +int iio_backend_iodelay_set(struct iio_backend *back, unsigned int lane, + unsigned int taps); +int iio_backend_data_sample_trigger(struct iio_backend *back, + enum iio_backend_sample_trigger trigger); int devm_iio_backend_request_buffer(struct device *dev, struct iio_backend *back, struct iio_dev *indio_dev); From fbc186055b41a49678ae3b2793020e67dfd8f7aa Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Apr 2024 17:42:14 +0200 Subject: [PATCH 233/319] iio: adc: adi-axi-adc: remove regmap max register In one of the following patches, we'll have some new functionality that requires reads/writes on registers bigger than 0x8000. Hence, as this is an highly flexible core, don't bother in setting 'max_register' and remove it from regmap_config. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240426-ad9467-new-features-v2-5-6361fc3ba1cc@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index f54830658da8..316750d92ad1 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -142,7 +142,6 @@ static const struct regmap_config axi_adc_regmap_config = { .val_bits = 32, .reg_bits = 32, .reg_stride = 4, - .max_register = 0x0800, }; static const struct iio_backend_ops adi_axi_adc_generic = { From 7ecb8ee5c93be9f00cbf090f7f53b2a32d995184 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Apr 2024 17:42:15 +0200 Subject: [PATCH 234/319] iio: adc: adi-axi-adc: support digital interface calibration Implement the new IIO backend APIs for calibrating the data digital interfaces. While at it, removed the tabs in 'struct adi_axi_adc_state' and used spaces for the members. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240426-ad9467-new-features-v2-6-6361fc3ba1cc@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 121 +++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 316750d92ad1..0cf0d81358fd 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -7,11 +7,13 @@ */ #include +#include #include #include #include #include #include +#include #include #include #include @@ -37,6 +39,9 @@ #define ADI_AXI_REG_RSTN_MMCM_RSTN BIT(1) #define ADI_AXI_REG_RSTN_RSTN BIT(0) +#define ADI_AXI_ADC_REG_CTRL 0x0044 +#define ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK BIT(1) + /* ADC Channel controls */ #define ADI_AXI_REG_CHAN_CTRL(c) (0x0400 + (c) * 0x40) @@ -51,14 +56,28 @@ #define ADI_AXI_REG_CHAN_CTRL_PN_TYPE_OWR BIT(1) #define ADI_AXI_REG_CHAN_CTRL_ENABLE BIT(0) +#define ADI_AXI_ADC_REG_CHAN_STATUS(c) (0x0404 + (c) * 0x40) +#define ADI_AXI_ADC_CHAN_STAT_PN_MASK GENMASK(2, 1) + +#define ADI_AXI_ADC_REG_CHAN_CTRL_3(c) (0x0418 + (c) * 0x40) +#define ADI_AXI_ADC_CHAN_PN_SEL_MASK GENMASK(19, 16) + +/* IO Delays */ +#define ADI_AXI_ADC_REG_DELAY(l) (0x0800 + (l) * 0x4) +#define AXI_ADC_DELAY_CTRL_MASK GENMASK(4, 0) + +#define ADI_AXI_ADC_MAX_IO_NUM_LANES 15 + #define ADI_AXI_REG_CHAN_CTRL_DEFAULTS \ (ADI_AXI_REG_CHAN_CTRL_FMT_SIGNEXT | \ ADI_AXI_REG_CHAN_CTRL_FMT_EN | \ ADI_AXI_REG_CHAN_CTRL_ENABLE) struct adi_axi_adc_state { - struct regmap *regmap; - struct device *dev; + struct regmap *regmap; + struct device *dev; + /* lock to protect multiple accesses to the device registers */ + struct mutex lock; }; static int axi_adc_enable(struct iio_backend *back) @@ -104,6 +123,100 @@ static int axi_adc_data_format_set(struct iio_backend *back, unsigned int chan, ADI_AXI_REG_CHAN_CTRL_FMT_MASK, val); } +static int axi_adc_data_sample_trigger(struct iio_backend *back, + enum iio_backend_sample_trigger trigger) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + switch (trigger) { + case IIO_BACKEND_SAMPLE_TRIGGER_EDGE_RISING: + return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CTRL, + ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK); + case IIO_BACKEND_SAMPLE_TRIGGER_EDGE_FALLING: + return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CTRL, + ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK); + default: + return -EINVAL; + } +} + +static int axi_adc_iodelays_set(struct iio_backend *back, unsigned int lane, + unsigned int tap) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + int ret; + u32 val; + + if (tap > FIELD_MAX(AXI_ADC_DELAY_CTRL_MASK)) + return -EINVAL; + if (lane > ADI_AXI_ADC_MAX_IO_NUM_LANES) + return -EINVAL; + + guard(mutex)(&st->lock); + ret = regmap_write(st->regmap, ADI_AXI_ADC_REG_DELAY(lane), tap); + if (ret) + return ret; + /* + * If readback is ~0, that means there are issues with the + * delay_clk. + */ + ret = regmap_read(st->regmap, ADI_AXI_ADC_REG_DELAY(lane), &val); + if (ret) + return ret; + if (val == U32_MAX) + return -EIO; + + return 0; +} + +static int axi_adc_test_pattern_set(struct iio_backend *back, + unsigned int chan, + enum iio_backend_test_pattern pattern) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + switch (pattern) { + case IIO_BACKEND_NO_TEST_PATTERN: + /* nothing to do */ + return 0; + case IIO_BACKEND_ADI_PRBS_9A: + return regmap_update_bits(st->regmap, ADI_AXI_ADC_REG_CHAN_CTRL_3(chan), + ADI_AXI_ADC_CHAN_PN_SEL_MASK, + FIELD_PREP(ADI_AXI_ADC_CHAN_PN_SEL_MASK, 0)); + default: + return -EINVAL; + } +} + +static int axi_adc_chan_status(struct iio_backend *back, unsigned int chan, + bool *error) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + int ret; + u32 val; + + guard(mutex)(&st->lock); + /* reset test bits by setting them */ + ret = regmap_write(st->regmap, ADI_AXI_ADC_REG_CHAN_STATUS(chan), + ADI_AXI_ADC_CHAN_STAT_PN_MASK); + if (ret) + return ret; + + /* let's give enough time to validate or erroring the incoming pattern */ + fsleep(1000); + + ret = regmap_read(st->regmap, ADI_AXI_ADC_REG_CHAN_STATUS(chan), &val); + if (ret) + return ret; + + if (ADI_AXI_ADC_CHAN_STAT_PN_MASK & val) + *error = true; + else + *error = false; + + return 0; +} + static int axi_adc_chan_enable(struct iio_backend *back, unsigned int chan) { struct adi_axi_adc_state *st = iio_backend_get_priv(back); @@ -152,6 +265,10 @@ static const struct iio_backend_ops adi_axi_adc_generic = { .chan_disable = axi_adc_chan_disable, .request_buffer = axi_adc_request_buffer, .free_buffer = axi_adc_free_buffer, + .data_sample_trigger = axi_adc_data_sample_trigger, + .iodelay_set = axi_adc_iodelays_set, + .test_pattern_set = axi_adc_test_pattern_set, + .chan_status = axi_adc_chan_status, }; static int adi_axi_adc_probe(struct platform_device *pdev) From 05c4081fbf4c98864dcf6c212afe9f611243dc92 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Apr 2024 17:42:16 +0200 Subject: [PATCH 235/319] iio: adc: ad9467: support digital interface calibration To make sure that we have the best timings on the serial data interface we should calibrate it. This means going through the device supported values and see for which ones we get a successful result. To do that, we use a prbs test pattern both in the IIO backend and in the frontend devices. Then for each of the test points we see if there are any errors. Note that the backend is responsible to look for those errors. As calibrating the interface also requires that the data format is disabled (the one thing being done in ad9467_setup()), ad9467_setup() was removed and configuring the data fomat is now part of the calibration process. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240426-ad9467-new-features-v2-7-6361fc3ba1cc@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 374 +++++++++++++++++++++++++++++++++++---- 1 file changed, 335 insertions(+), 39 deletions(-) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index 7475ec2a56c7..e85b763b9ffc 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -4,7 +4,11 @@ * * Copyright 2012-2020 Analog Devices Inc. */ + +#include +#include #include +#include #include #include #include @@ -100,6 +104,8 @@ #define AD9467_DEF_OUTPUT_MODE 0x08 #define AD9467_REG_VREF_MASK 0x0F +#define AD9647_MAX_TEST_POINTS 32 + struct ad9467_chip_info { const char *name; unsigned int id; @@ -110,6 +116,9 @@ struct ad9467_chip_info { unsigned long max_rate; unsigned int default_output_mode; unsigned int vref_mask; + unsigned int num_lanes; + /* data clock output */ + bool has_dco; }; struct ad9467_state { @@ -119,7 +128,16 @@ struct ad9467_state { struct clk *clk; unsigned int output_mode; unsigned int (*scales)[2]; - + /* + * Times 2 because we may also invert the signal polarity and run the + * calibration again. For some reference on the test points (ad9265) see: + * https://www.analog.com/media/en/technical-documentation/data-sheets/ad9265.pdf + * at page 38 for the dco output delay. On devices as ad9467, the + * calibration is done at the backend level. For the ADI axi-adc: + * https://wiki.analog.com/resources/fpga/docs/axi_adc_ip + * at the io delay control section. + */ + DECLARE_BITMAP(calib_map, AD9647_MAX_TEST_POINTS * 2); struct gpio_desc *pwrdown_gpio; /* ensure consistent state obtained on multiple related accesses */ struct mutex lock; @@ -242,6 +260,7 @@ static const struct ad9467_chip_info ad9467_chip_tbl = { .num_channels = ARRAY_SIZE(ad9467_channels), .default_output_mode = AD9467_DEF_OUTPUT_MODE, .vref_mask = AD9467_REG_VREF_MASK, + .num_lanes = 8, }; static const struct ad9467_chip_info ad9434_chip_tbl = { @@ -254,6 +273,7 @@ static const struct ad9467_chip_info ad9434_chip_tbl = { .num_channels = ARRAY_SIZE(ad9434_channels), .default_output_mode = AD9434_DEF_OUTPUT_MODE, .vref_mask = AD9434_REG_VREF_MASK, + .num_lanes = 6, }; static const struct ad9467_chip_info ad9265_chip_tbl = { @@ -266,6 +286,7 @@ static const struct ad9467_chip_info ad9265_chip_tbl = { .num_channels = ARRAY_SIZE(ad9467_channels), .default_output_mode = AD9265_DEF_OUTPUT_MODE, .vref_mask = AD9265_REG_VREF_MASK, + .has_dco = true, }; static int ad9467_get_scale(struct ad9467_state *st, int *val, int *val2) @@ -321,6 +342,246 @@ static int ad9467_set_scale(struct ad9467_state *st, int val, int val2) return -EINVAL; } +static int ad9467_outputmode_set(struct spi_device *spi, unsigned int mode) +{ + int ret; + + ret = ad9467_spi_write(spi, AN877_ADC_REG_OUTPUT_MODE, mode); + if (ret < 0) + return ret; + + return ad9467_spi_write(spi, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + +static int ad9647_calibrate_prepare(const struct ad9467_state *st) +{ + struct iio_backend_data_fmt data = { + .enable = false, + }; + unsigned int c; + int ret; + + ret = ad9467_spi_write(st->spi, AN877_ADC_REG_TEST_IO, + AN877_ADC_TESTMODE_PN9_SEQ); + if (ret) + return ret; + + ret = ad9467_spi_write(st->spi, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); + if (ret) + return ret; + + ret = ad9467_outputmode_set(st->spi, st->info->default_output_mode); + if (ret) + return ret; + + for (c = 0; c < st->info->num_channels; c++) { + ret = iio_backend_data_format_set(st->back, c, &data); + if (ret) + return ret; + } + + ret = iio_backend_test_pattern_set(st->back, 0, + IIO_BACKEND_ADI_PRBS_9A); + if (ret) + return ret; + + return iio_backend_chan_enable(st->back, 0); +} + +static int ad9647_calibrate_polarity_set(const struct ad9467_state *st, + bool invert) +{ + enum iio_backend_sample_trigger trigger; + + if (st->info->has_dco) { + unsigned int phase = AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN; + + if (invert) + phase |= AN877_ADC_INVERT_DCO_CLK; + + return ad9467_spi_write(st->spi, AN877_ADC_REG_OUTPUT_PHASE, + phase); + } + + if (invert) + trigger = IIO_BACKEND_SAMPLE_TRIGGER_EDGE_FALLING; + else + trigger = IIO_BACKEND_SAMPLE_TRIGGER_EDGE_RISING; + + return iio_backend_data_sample_trigger(st->back, trigger); +} + +/* + * The idea is pretty simple. Find the max number of successful points in a row + * and get the one in the middle. + */ +static unsigned int ad9467_find_optimal_point(const unsigned long *calib_map, + unsigned int start, + unsigned int nbits, + unsigned int *val) +{ + unsigned int bit = start, end, start_cnt, cnt = 0; + + for_each_clear_bitrange_from(bit, end, calib_map, nbits + start) { + if (end - bit > cnt) { + cnt = end - bit; + start_cnt = bit; + } + } + + if (cnt) + *val = start_cnt + cnt / 2; + + return cnt; +} + +static int ad9467_calibrate_apply(const struct ad9467_state *st, + unsigned int val) +{ + unsigned int lane; + int ret; + + if (st->info->has_dco) { + ret = ad9467_spi_write(st->spi, AN877_ADC_REG_OUTPUT_DELAY, + val); + if (ret) + return ret; + + return ad9467_spi_write(st->spi, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); + } + + for (lane = 0; lane < st->info->num_lanes; lane++) { + ret = iio_backend_iodelay_set(st->back, lane, val); + if (ret) + return ret; + } + + return 0; +} + +static int ad9647_calibrate_stop(const struct ad9467_state *st) +{ + struct iio_backend_data_fmt data = { + .sign_extend = true, + .enable = true, + }; + unsigned int c, mode; + int ret; + + ret = iio_backend_chan_disable(st->back, 0); + if (ret) + return ret; + + ret = iio_backend_test_pattern_set(st->back, 0, + IIO_BACKEND_NO_TEST_PATTERN); + if (ret) + return ret; + + for (c = 0; c < st->info->num_channels; c++) { + ret = iio_backend_data_format_set(st->back, c, &data); + if (ret) + return ret; + } + + mode = st->info->default_output_mode | AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT; + ret = ad9467_outputmode_set(st->spi, mode); + if (ret) + return ret; + + ret = ad9467_spi_write(st->spi, AN877_ADC_REG_TEST_IO, + AN877_ADC_TESTMODE_OFF); + if (ret) + return ret; + + return ad9467_spi_write(st->spi, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + +static int ad9467_calibrate(struct ad9467_state *st) +{ + unsigned int point, val, inv_val, cnt, inv_cnt = 0; + /* + * Half of the bitmap is for the inverted signal. The number of test + * points is the same though... + */ + unsigned int test_points = AD9647_MAX_TEST_POINTS; + unsigned long sample_rate = clk_get_rate(st->clk); + struct device *dev = &st->spi->dev; + bool invert = false, stat; + int ret; + + /* all points invalid */ + bitmap_fill(st->calib_map, BITS_PER_TYPE(st->calib_map)); + + ret = ad9647_calibrate_prepare(st); + if (ret) + return ret; +retune: + ret = ad9647_calibrate_polarity_set(st, invert); + if (ret) + return ret; + + for (point = 0; point < test_points; point++) { + ret = ad9467_calibrate_apply(st, point); + if (ret) + return ret; + + ret = iio_backend_chan_status(st->back, 0, &stat); + if (ret) + return ret; + + __assign_bit(point + invert * test_points, st->calib_map, stat); + } + + if (!invert) { + cnt = ad9467_find_optimal_point(st->calib_map, 0, test_points, + &val); + /* + * We're happy if we find, at least, three good test points in + * a row. + */ + if (cnt < 3) { + invert = true; + goto retune; + } + } else { + inv_cnt = ad9467_find_optimal_point(st->calib_map, test_points, + test_points, &inv_val); + if (!inv_cnt && !cnt) + return -EIO; + } + + if (inv_cnt < cnt) { + ret = ad9647_calibrate_polarity_set(st, false); + if (ret) + return ret; + } else { + /* + * polarity inverted is the last test to run. Hence, there's no + * need to re-do any configuration. We just need to "normalize" + * the selected value. + */ + val = inv_val - test_points; + } + + if (st->info->has_dco) + dev_dbg(dev, "%sDCO 0x%X CLK %lu Hz\n", inv_cnt >= cnt ? "INVERT " : "", + val, sample_rate); + else + dev_dbg(dev, "%sIDELAY 0x%x\n", inv_cnt >= cnt ? "INVERT " : "", + val); + + ret = ad9467_calibrate_apply(st, val); + if (ret) + return ret; + + /* finally apply the optimal value */ + return ad9647_calibrate_stop(st); +} + static int ad9467_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long m) @@ -345,7 +606,9 @@ static int ad9467_write_raw(struct iio_dev *indio_dev, { struct ad9467_state *st = iio_priv(indio_dev); const struct ad9467_chip_info *info = st->info; + unsigned long sample_rate; long r_clk; + int ret; switch (mask) { case IIO_CHAN_INFO_SCALE: @@ -358,7 +621,23 @@ static int ad9467_write_raw(struct iio_dev *indio_dev, return -EINVAL; } - return clk_set_rate(st->clk, r_clk); + sample_rate = clk_get_rate(st->clk); + /* + * clk_set_rate() would also do this but since we would still + * need it for avoiding an unnecessary calibration, do it now. + */ + if (sample_rate == r_clk) + return 0; + + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + ret = clk_set_rate(st->clk, r_clk); + if (ret) + return ret; + + guard(mutex)(&st->lock); + ret = ad9467_calibrate(st); + } + return ret; default: return -EINVAL; } @@ -411,18 +690,6 @@ static const struct iio_info ad9467_info = { .read_avail = ad9467_read_avail, }; -static int ad9467_outputmode_set(struct spi_device *spi, unsigned int mode) -{ - int ret; - - ret = ad9467_spi_write(spi, AN877_ADC_REG_OUTPUT_MODE, mode); - if (ret < 0) - return ret; - - return ad9467_spi_write(spi, AN877_ADC_REG_TRANSFER, - AN877_ADC_TRANSFER_SYNC); -} - static int ad9467_scale_fill(struct ad9467_state *st) { const struct ad9467_chip_info *info = st->info; @@ -442,29 +709,6 @@ static int ad9467_scale_fill(struct ad9467_state *st) return 0; } -static int ad9467_setup(struct ad9467_state *st) -{ - struct iio_backend_data_fmt data = { - .sign_extend = true, - .enable = true, - }; - unsigned int c, mode; - int ret; - - mode = st->info->default_output_mode | AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT; - ret = ad9467_outputmode_set(st->spi, mode); - if (ret) - return ret; - - for (c = 0; c < st->info->num_channels; c++) { - ret = iio_backend_data_format_set(st->back, c, &data); - if (ret) - return ret; - } - - return 0; -} - static int ad9467_reset(struct device *dev) { struct gpio_desc *gpio; @@ -521,6 +765,52 @@ static int ad9467_iio_backend_get(struct ad9467_state *st) return -ENODEV; } +static ssize_t ad9467_dump_calib_table(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ad9467_state *st = file->private_data; + unsigned int bit, size = BITS_PER_TYPE(st->calib_map); + /* +2 for the newline and +1 for the string termination */ + unsigned char map[AD9647_MAX_TEST_POINTS * 2 + 3]; + ssize_t len = 0; + + guard(mutex)(&st->lock); + if (*ppos) + goto out_read; + + for (bit = 0; bit < size; bit++) { + if (bit == size / 2) + len += scnprintf(map + len, sizeof(map) - len, "\n"); + + len += scnprintf(map + len, sizeof(map) - len, "%c", + test_bit(bit, st->calib_map) ? 'x' : 'o'); + } + + len += scnprintf(map + len, sizeof(map) - len, "\n"); +out_read: + return simple_read_from_buffer(userbuf, count, ppos, map, len); +} + +static const struct file_operations ad9467_calib_table_fops = { + .open = simple_open, + .read = ad9467_dump_calib_table, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static void ad9467_debugfs_init(struct iio_dev *indio_dev) +{ + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + struct ad9467_state *st = iio_priv(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_create_file("calibration_table_dump", 0400, d, st, + &ad9467_calib_table_fops); +} + static int ad9467_probe(struct spi_device *spi) { struct iio_dev *indio_dev; @@ -580,11 +870,17 @@ static int ad9467_probe(struct spi_device *spi) if (ret) return ret; - ret = ad9467_setup(st); + ret = ad9467_calibrate(st); if (ret) return ret; - return devm_iio_device_register(&spi->dev, indio_dev); + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + ad9467_debugfs_init(indio_dev); + + return 0; } static const struct of_device_id ad9467_of_match[] = { From 6a28a72ad2a4bbca81ef73f73bbc9190f4d9ab9c Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sun, 28 Apr 2024 18:40:19 +0100 Subject: [PATCH 236/319] iio: adc: mcp3564: 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: Marcelo Schmitt Reviewed-by: Marius Cristea Link: https://lore.kernel.org/r/20240428174020.1832825-2-jic23@kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/mcp3564.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/iio/adc/mcp3564.c b/drivers/iio/adc/mcp3564.c index 311b613b6057..e2ae13f1e842 100644 --- a/drivers/iio/adc/mcp3564.c +++ b/drivers/iio/adc/mcp3564.c @@ -998,7 +998,6 @@ static int mcp3564_parse_fw_children(struct iio_dev *indio_dev) struct mcp3564_state *adc = iio_priv(indio_dev); struct device *dev = &adc->spi->dev; struct iio_chan_spec *channels; - struct fwnode_handle *child; struct iio_chan_spec chanspec = mcp3564_channel_template; struct iio_chan_spec temp_chanspec = mcp3564_temp_channel_template; struct iio_chan_spec burnout_chanspec = mcp3564_burnout_channel_template; @@ -1025,7 +1024,7 @@ static int mcp3564_parse_fw_children(struct iio_dev *indio_dev) if (!channels) return dev_err_probe(dev, -ENOMEM, "Can't allocate memory\n"); - 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")) { @@ -1033,26 +1032,25 @@ static int mcp3564_parse_fw_children(struct iio_dev *indio_dev) "diff-channels", inputs, ARRAY_SIZE(inputs)); + if (ret) + return ret; + chanspec.differential = 1; } else { ret = fwnode_property_read_u32(child, "reg", &inputs[0]); + if (ret) + return ret; chanspec.differential = 0; inputs[1] = MCP3564_AGND; } - if (ret) { - fwnode_handle_put(child); - return ret; - } if (inputs[0] > MCP3564_INTERNAL_VCM || - inputs[1] > MCP3564_INTERNAL_VCM) { - fwnode_handle_put(child); + inputs[1] > MCP3564_INTERNAL_VCM) return dev_err_probe(&indio_dev->dev, -EINVAL, "Channel index > %d, for %s\n", MCP3564_INTERNAL_VCM + 1, node_name); - } chanspec.address = (inputs[0] << 4) | inputs[1]; chanspec.channel = inputs[0]; From da8cf0d7327f148d2847f58ae17a4dd6aac6b5bf Mon Sep 17 00:00:00 2001 From: Gustavo Rodrigues Date: Sun, 28 Apr 2024 16:43:24 -0300 Subject: [PATCH 237/319] iio: adc: ad799x: change 'unsigned' to 'unsigned int' declaration Prefer 'unsigned int' instead of bare use of 'unsigned' declarations to to improve code readbility. This ceases one of the warning messages pointed by checkpatch. Co-developed-by: Bruna Lopes Signed-off-by: Bruna Lopes Signed-off-by: Gustavo Rodrigues Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240428194326.2836387-2-ogustavo@usp.br Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad799x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c index b757cc45c4de..3040575793a2 100644 --- a/drivers/iio/adc/ad799x.c +++ b/drivers/iio/adc/ad799x.c @@ -128,7 +128,7 @@ struct ad799x_state { struct regulator *vref; /* lock to protect against multiple access to the device */ struct mutex lock; - unsigned id; + unsigned int id; u16 config; u8 *rx_buf; @@ -253,7 +253,7 @@ static int ad799x_update_scan_mode(struct iio_dev *indio_dev, } } -static int ad799x_scan_direct(struct ad799x_state *st, unsigned ch) +static int ad799x_scan_direct(struct ad799x_state *st, unsigned int ch) { u8 cmd; From 0fb3e211acf0b60e6987831136986bc5a664b6d7 Mon Sep 17 00:00:00 2001 From: Gustavo Rodrigues Date: Sun, 28 Apr 2024 16:43:25 -0300 Subject: [PATCH 238/319] iio: adc: ad799x: add blank line to avoid warning messages Add a blank line before if statement to avoid warning messages pointed by checkpatch. Co-developed-by: Bruna Lopes Signed-off-by: Bruna Lopes Signed-off-by: Gustavo Rodrigues Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240428194326.2836387-3-ogustavo@usp.br Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad799x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c index 3040575793a2..9a12e562c259 100644 --- a/drivers/iio/adc/ad799x.c +++ b/drivers/iio/adc/ad799x.c @@ -335,6 +335,7 @@ static ssize_t ad799x_read_frequency(struct device *dev, struct ad799x_state *st = iio_priv(indio_dev); int ret = i2c_smbus_read_byte_data(st->client, AD7998_CYCLE_TMR_REG); + if (ret < 0) return ret; From 80f87d6bbc6ddcc47969c996649e8a995587e7ae Mon Sep 17 00:00:00 2001 From: Gustavo Rodrigues Date: Sun, 28 Apr 2024 16:43:26 -0300 Subject: [PATCH 239/319] iio: adc: ad799x: Prefer to use octal permission Octal permissions are preferred over the symbolics ones for readbility. This ceases warning message pointed by checkpatch. Co-developed-by: Bruna Lopes Signed-off-by: Bruna Lopes Signed-off-by: Gustavo Rodrigues Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240428194326.2836387-4-ogustavo@usp.br Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad799x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c index 9a12e562c259..0f0dcd9ca6b6 100644 --- a/drivers/iio/adc/ad799x.c +++ b/drivers/iio/adc/ad799x.c @@ -524,7 +524,7 @@ done: return IRQ_HANDLED; } -static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, +static IIO_DEV_ATTR_SAMP_FREQ(0644, ad799x_read_frequency, ad799x_write_frequency); static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("15625 7812 3906 1953 976 488 244 0"); From d1648883355a25488836daf1ca26e10577c2c50c Mon Sep 17 00:00:00 2001 From: Lincoln Yuji Date: Mon, 29 Apr 2024 10:22:33 -0300 Subject: [PATCH 240/319] iio: adc: ti-ads1015: use device_for_each_child_node_scoped() This loop definition removes the need for manual releasing of the fwnode_handle in early exit paths (here an error path) allow simplification of the code and reducing the chance of future modifications not releasing fwnode_handle correctly. Co-developed-by: Luiza Soezima Signed-off-by: Luiza Soezima Co-developed-by: Sabrina Araujo Signed-off-by: Sabrina Araujo Signed-off-by: Lincoln Yuji Reviewed-by: Marcelo Schmitt Link: https://lore.kernel.org/r/20240429132233.6266-1-lincolnyuji@usp.br Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads1015.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c index 6ae967e4d8fa..d3363d02f292 100644 --- a/drivers/iio/adc/ti-ads1015.c +++ b/drivers/iio/adc/ti-ads1015.c @@ -902,10 +902,9 @@ static int ads1015_client_get_channels_config(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct ads1015_data *data = iio_priv(indio_dev); struct device *dev = &client->dev; - struct fwnode_handle *node; int i = -1; - device_for_each_child_node(dev, node) { + device_for_each_child_node_scoped(dev, node) { u32 pval; unsigned int channel; unsigned int pga = ADS1015_DEFAULT_PGA; @@ -927,7 +926,6 @@ static int ads1015_client_get_channels_config(struct i2c_client *client) pga = pval; if (pga > 5) { dev_err(dev, "invalid gain on %pfw\n", node); - fwnode_handle_put(node); return -EINVAL; } } @@ -936,7 +934,6 @@ static int ads1015_client_get_channels_config(struct i2c_client *client) data_rate = pval; if (data_rate > 7) { dev_err(dev, "invalid data_rate on %pfw\n", node); - fwnode_handle_put(node); return -EINVAL; } } From 4d68b25c642186e14223ea38bbdf2216d7e13519 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:04 +0200 Subject: [PATCH 241/319] iio: adc: ad_sigma_delta: use 'time_left' variable with wait_for_completion_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_timeout() causing patterns like: timeout = wait_for_completion_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Reviewed-by: Nuno Sa Link: https://lore.kernel.org/r/20240429113313.68359-2-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad_sigma_delta.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c index 97a05f325df7..a2b87f6b7a07 100644 --- a/drivers/iio/adc/ad_sigma_delta.c +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -206,7 +206,7 @@ int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta, unsigned int mode, unsigned int channel) { int ret; - unsigned long timeout; + unsigned long time_left; ret = ad_sigma_delta_set_channel(sigma_delta, channel); if (ret) @@ -223,8 +223,8 @@ int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta, sigma_delta->irq_dis = false; enable_irq(sigma_delta->irq_line); - timeout = wait_for_completion_timeout(&sigma_delta->completion, 2 * HZ); - if (timeout == 0) { + time_left = wait_for_completion_timeout(&sigma_delta->completion, 2 * HZ); + if (time_left == 0) { sigma_delta->irq_dis = true; disable_irq_nosync(sigma_delta->irq_line); ret = -EIO; From 860e36b7023580068e1857262a7f25a1836fc30b Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:05 +0200 Subject: [PATCH 242/319] iio: adc: exynos_adc: use 'time_left' variable with wait_for_completion_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_timeout() causing patterns like: timeout = wait_for_completion_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20240429113313.68359-3-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/exynos_adc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 614de9644800..78fada4b7b1c 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -538,7 +538,7 @@ static int exynos_read_raw(struct iio_dev *indio_dev, long mask) { struct exynos_adc *info = iio_priv(indio_dev); - unsigned long timeout; + unsigned long time_left; int ret; if (mask == IIO_CHAN_INFO_SCALE) { @@ -562,9 +562,9 @@ static int exynos_read_raw(struct iio_dev *indio_dev, if (info->data->start_conv) info->data->start_conv(info, chan->address); - timeout = wait_for_completion_timeout(&info->completion, - EXYNOS_ADC_TIMEOUT); - if (timeout == 0) { + time_left = wait_for_completion_timeout(&info->completion, + EXYNOS_ADC_TIMEOUT); + if (time_left == 0) { dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); if (info->data->init_hw) info->data->init_hw(info); @@ -583,7 +583,7 @@ static int exynos_read_raw(struct iio_dev *indio_dev, static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y) { struct exynos_adc *info = iio_priv(indio_dev); - unsigned long timeout; + unsigned long time_left; int ret; mutex_lock(&info->lock); @@ -597,9 +597,9 @@ static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y) /* Select the ts channel to be used and Trigger conversion */ info->data->start_conv(info, ADC_S3C2410_MUX_TS); - timeout = wait_for_completion_timeout(&info->completion, - EXYNOS_ADC_TIMEOUT); - if (timeout == 0) { + time_left = wait_for_completion_timeout(&info->completion, + EXYNOS_ADC_TIMEOUT); + if (time_left == 0) { dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); if (info->data->init_hw) info->data->init_hw(info); From 265b81bb7578d5c30866932cab5acde5a9fa0df0 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:06 +0200 Subject: [PATCH 243/319] iio: adc: fsl-imx25-gcq: use 'time_left' variable with wait_for_completion_interruptible_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_interruptible_timeout() causing patterns like: timeout = wait_for_completion_interruptible_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20240429113313.68359-4-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/fsl-imx25-gcq.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c index 394396b91630..b680690631db 100644 --- a/drivers/iio/adc/fsl-imx25-gcq.c +++ b/drivers/iio/adc/fsl-imx25-gcq.c @@ -108,7 +108,7 @@ static int mx25_gcq_get_raw_value(struct device *dev, struct mx25_gcq_priv *priv, int *val) { - long timeout; + long time_left; u32 data; /* Setup the configuration we want to use */ @@ -121,12 +121,12 @@ static int mx25_gcq_get_raw_value(struct device *dev, regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, MX25_ADCQ_CR_FQS); - timeout = wait_for_completion_interruptible_timeout( + time_left = wait_for_completion_interruptible_timeout( &priv->completed, MX25_GCQ_TIMEOUT); - if (timeout < 0) { + if (time_left < 0) { dev_err(dev, "ADC wait for measurement failed\n"); - return timeout; - } else if (timeout == 0) { + return time_left; + } else if (time_left == 0) { dev_err(dev, "ADC timed out\n"); return -ETIMEDOUT; } From 1fa9d4a0a4ad6ff98f4d96e98161dcd4e4b7e039 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:07 +0200 Subject: [PATCH 244/319] iio: adc: intel_mrfld_adc: use 'time_left' variable with wait_for_completion_interruptible_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_interruptible_timeout() causing patterns like: timeout = wait_for_completion_interruptible_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20240429113313.68359-5-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/intel_mrfld_adc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/intel_mrfld_adc.c b/drivers/iio/adc/intel_mrfld_adc.c index 7263ad76124d..c7f40ae6e608 100644 --- a/drivers/iio/adc/intel_mrfld_adc.c +++ b/drivers/iio/adc/intel_mrfld_adc.c @@ -75,7 +75,7 @@ static int mrfld_adc_single_conv(struct iio_dev *indio_dev, struct mrfld_adc *adc = iio_priv(indio_dev); struct regmap *regmap = adc->regmap; unsigned int req; - long timeout; + long time_left; __be16 value; int ret; @@ -95,13 +95,13 @@ static int mrfld_adc_single_conv(struct iio_dev *indio_dev, if (ret) goto done; - timeout = wait_for_completion_interruptible_timeout(&adc->completion, - BCOVE_ADC_TIMEOUT); - if (timeout < 0) { - ret = timeout; + time_left = wait_for_completion_interruptible_timeout(&adc->completion, + BCOVE_ADC_TIMEOUT); + if (time_left < 0) { + ret = time_left; goto done; } - if (timeout == 0) { + if (time_left == 0) { ret = -ETIMEDOUT; goto done; } From 3cd191fce83726c39b74515820b3ec8f23e1768b Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:08 +0200 Subject: [PATCH 245/319] iio: adc: stm32-adc: use 'time_left' variable with wait_for_completion_interruptible_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_interruptible_timeout() causing patterns like: timeout = wait_for_completion_interruptible_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20240429113313.68359-6-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/stm32-adc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 36add95212c3..375aa7720f80 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -1408,7 +1408,7 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev, struct stm32_adc *adc = iio_priv(indio_dev); struct device *dev = indio_dev->dev.parent; const struct stm32_adc_regspec *regs = adc->cfg->regs; - long timeout; + long time_left; u32 val; int ret; @@ -1440,12 +1440,12 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev, adc->cfg->start_conv(indio_dev, false); - timeout = wait_for_completion_interruptible_timeout( + time_left = wait_for_completion_interruptible_timeout( &adc->completion, STM32_ADC_TIMEOUT); - if (timeout == 0) { + if (time_left == 0) { ret = -ETIMEDOUT; - } else if (timeout < 0) { - ret = timeout; + } else if (time_left < 0) { + ret = time_left; } else { *res = adc->buffer[0]; ret = IIO_VAL_INT; From 2f62fd78a14500973d757acb2ebf1b831f708efb Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:09 +0200 Subject: [PATCH 246/319] iio: adc: stm32-dfsdm-adc: use 'time_left' variable with wait_for_completion_interruptible_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_interruptible_timeout() causing patterns like: timeout = wait_for_completion_interruptible_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20240429113313.68359-7-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/stm32-dfsdm-adc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c index ca08ae3108b2..9a47d2c87f05 100644 --- a/drivers/iio/adc/stm32-dfsdm-adc.c +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -1116,7 +1116,7 @@ static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, int *res) { struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); - long timeout; + long time_left; int ret; reinit_completion(&adc->completion); @@ -1141,17 +1141,17 @@ static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, goto stop_dfsdm; } - timeout = wait_for_completion_interruptible_timeout(&adc->completion, - DFSDM_TIMEOUT); + time_left = wait_for_completion_interruptible_timeout(&adc->completion, + DFSDM_TIMEOUT); /* Mask IRQ for regular conversion achievement*/ regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); - if (timeout == 0) + if (time_left == 0) ret = -ETIMEDOUT; - else if (timeout < 0) - ret = timeout; + else if (time_left < 0) + ret = time_left; else ret = IIO_VAL_INT; From b0329b3c7ecaaa9a7ebc9fa2c5cdf53fad17ddc9 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:10 +0200 Subject: [PATCH 247/319] iio: adc: twl6030-gpadc: use 'time_left' variable with wait_for_completion_interruptible_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_interruptible_timeout() causing patterns like: timeout = wait_for_completion_interruptible_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20240429113313.68359-8-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/twl6030-gpadc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/twl6030-gpadc.c b/drivers/iio/adc/twl6030-gpadc.c index 78bf55438b2c..6a3db2bce460 100644 --- a/drivers/iio/adc/twl6030-gpadc.c +++ b/drivers/iio/adc/twl6030-gpadc.c @@ -519,7 +519,7 @@ static int twl6030_gpadc_read_raw(struct iio_dev *indio_dev, { struct twl6030_gpadc_data *gpadc = iio_priv(indio_dev); int ret; - long timeout; + long time_left; mutex_lock(&gpadc->lock); @@ -529,12 +529,12 @@ static int twl6030_gpadc_read_raw(struct iio_dev *indio_dev, goto err; } /* wait for conversion to complete */ - timeout = wait_for_completion_interruptible_timeout( + time_left = wait_for_completion_interruptible_timeout( &gpadc->irq_complete, msecs_to_jiffies(5000)); - if (timeout == 0) { + if (time_left == 0) { ret = -ETIMEDOUT; goto err; - } else if (timeout < 0) { + } else if (time_left < 0) { ret = -EINTR; goto err; } From 8d0c93761606ffc97af0cd00901579c5972017ca Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 29 Apr 2024 13:33:11 +0200 Subject: [PATCH 248/319] iio: pressure: zpa2326: use 'time_left' variable with wait_for_completion_interruptible_timeout() There is a confusing pattern in the kernel to use a variable named 'timeout' to store the result of wait_for_completion_interruptible_timeout() causing patterns like: timeout = wait_for_completion_interruptible_timeout(...) if (!timeout) return -ETIMEDOUT; with all kinds of permutations. Use 'time_left' as a variable to make the code self explaining. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20240429113313.68359-9-wsa+renesas@sang-engineering.com Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/zpa2326.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iio/pressure/zpa2326.c b/drivers/iio/pressure/zpa2326.c index 421e059d1f19..dcc87a9015e8 100644 --- a/drivers/iio/pressure/zpa2326.c +++ b/drivers/iio/pressure/zpa2326.c @@ -861,13 +861,13 @@ static int zpa2326_wait_oneshot_completion(const struct iio_dev *indio_dev, struct zpa2326_private *private) { unsigned int val; - long timeout; + long time_left; zpa2326_dbg(indio_dev, "waiting for one shot completion interrupt"); - timeout = wait_for_completion_interruptible_timeout( + time_left = wait_for_completion_interruptible_timeout( &private->data_ready, ZPA2326_CONVERSION_JIFFIES); - if (timeout > 0) + if (time_left > 0) /* * Interrupt handler completed before timeout: return operation * status. @@ -877,10 +877,10 @@ static int zpa2326_wait_oneshot_completion(const struct iio_dev *indio_dev, /* Clear all interrupts just to be sure. */ regmap_read(private->regmap, ZPA2326_INT_SOURCE_REG, &val); - if (!timeout) { + if (!time_left) { /* Timed out. */ zpa2326_warn(indio_dev, "no one shot interrupt occurred (%ld)", - timeout); + time_left); return -ETIME; } From 561e2e3e90b4307f9a47a6382fa5cd15462aacf9 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Mon, 29 Apr 2024 15:54:39 +0200 Subject: [PATCH 249/319] iio: dac: ad9739a: write complete MU_CNT1 register during lock As specified by the datasheet we should write the value 0x3 (enable plus tracking gain) into the MU_CNT1 register during the MU lock phase. Currently we were only setting the enable bit (bit 0) as the tracking gain default value is already set to 1. While we should be mostly fine in assuming the tracking gain will have the value it should, better to explicitly write it. On top of that the datasheet also states to re-attempt the writes in case the lock fails which we were not doing for the tracking gain bit. Lastly, the recommended value for the MU phase slope lock (bit 6) is 0 but for some reason the default value is 1 and hence, we were not changing it accordingly. Note there was no problem with the MU lock mechanism so this is not being treated as a fix but rather an improvement. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240429-ad9739a-improv-v1-1-c076a06a697d@analog.com Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad9739a.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/iio/dac/ad9739a.c b/drivers/iio/dac/ad9739a.c index ff33120075bf..f56eabe53723 100644 --- a/drivers/iio/dac/ad9739a.c +++ b/drivers/iio/dac/ad9739a.c @@ -45,6 +45,7 @@ #define AD9739A_REG_MU_DUTY 0x25 #define AD9739A_REG_MU_CNT1 0x26 #define AD9739A_MU_EN_MASK BIT(0) +#define AD9739A_MU_GAIN_MASK BIT(1) #define AD9739A_REG_MU_CNT2 0x27 #define AD9739A_REG_MU_CNT3 0x28 #define AD9739A_REG_MU_CNT4 0x29 @@ -220,8 +221,8 @@ static int ad9739a_init(struct device *dev, const struct ad9739a_state *st) return ret; /* Enable the Mu controller search and track mode. */ - ret = regmap_set_bits(st->regmap, AD9739A_REG_MU_CNT1, - AD9739A_MU_EN_MASK); + ret = regmap_write(st->regmap, AD9739A_REG_MU_CNT1, + AD9739A_MU_EN_MASK | AD9739A_MU_GAIN_MASK); if (ret) return ret; From 0340dc4c82590d8735c58cf904a8aa1173273ab5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Fri, 26 Apr 2024 13:58:14 +0000 Subject: [PATCH 250/319] iio: invensense: fix interrupt timestamp alignment Restrict interrupt timestamp alignment for not overflowing max/min period thresholds. Fixes: 0ecc363ccea7 ("iio: make invensense timestamp module generic") Cc: stable@vger.kernel.org Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240426135814.141837-1-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c index 3b0f9598a7c7..4b8ec16240b5 100644 --- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c @@ -101,6 +101,9 @@ static bool inv_update_chip_period(struct inv_sensors_timestamp *ts, static void inv_align_timestamp_it(struct inv_sensors_timestamp *ts) { + const int64_t period_min = ts->min_period * ts->mult; + const int64_t period_max = ts->max_period * ts->mult; + int64_t add_max, sub_max; int64_t delta, jitter; int64_t adjust; @@ -108,11 +111,13 @@ static void inv_align_timestamp_it(struct inv_sensors_timestamp *ts) delta = ts->it.lo - ts->timestamp; /* adjust timestamp while respecting jitter */ + add_max = period_max - (int64_t)ts->period; + sub_max = period_min - (int64_t)ts->period; jitter = INV_SENSORS_TIMESTAMP_JITTER((int64_t)ts->period, ts->chip.jitter); if (delta > jitter) - adjust = jitter; + adjust = add_max; else if (delta < -jitter) - adjust = -jitter; + adjust = sub_max; else adjust = 0; From 8877ef45ef9ec281849870d88039f8dc84cde774 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 4 Apr 2024 12:59:34 +0530 Subject: [PATCH 251/319] coresight: tmc: Enable SG capability on ACPI based SoC-400 TMC ETR devices This detects and enables the scatter gather capability (SG) on ACPI based Soc-400 TMC ETR devices via a new property called 'arm-armhc97c-sg-enable'. The updated ACPI spec can be found below, which contains this new property. https://developer.arm.com/documentation/den0067/latest/ This preserves current handling for the property 'arm,scatter-gather' both on ACPI and DT based platforms i.e the presence of the property is checked instead of the value. Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: Alexander Shishkin Cc: coresight@lists.linaro.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Anshuman Khandual Reviewed-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240404072934.940760-1-anshuman.khandual@arm.com --- .../hwtracing/coresight/coresight-tmc-core.c | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c index 4b50d043ce04..741ce1748e75 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-core.c +++ b/drivers/hwtracing/coresight/coresight-tmc-core.c @@ -4,6 +4,7 @@ * Description: CoreSight Trace Memory Controller driver */ +#include #include #include #include @@ -362,7 +363,32 @@ static const struct attribute_group *coresight_etr_groups[] = { static inline bool tmc_etr_can_use_sg(struct device *dev) { - return fwnode_property_present(dev->fwnode, "arm,scatter-gather"); + int ret; + u8 val_u8; + + /* + * Presence of the property 'arm,scatter-gather' is checked + * on the platform for the feature support, rather than its + * value. + */ + if (is_of_node(dev->fwnode)) { + return fwnode_property_present(dev->fwnode, "arm,scatter-gather"); + } else if (is_acpi_device_node(dev->fwnode)) { + /* + * TMC_DEVID_NOSCAT test in tmc_etr_setup_caps(), has already ensured + * this property is only checked for Coresight SoC 400 TMC configured + * as ETR. + */ + ret = fwnode_property_read_u8(dev->fwnode, "arm-armhc97c-sg-enable", &val_u8); + if (!ret) + return !!val_u8; + + if (fwnode_property_present(dev->fwnode, "arm,scatter-gather")) { + pr_warn_once("Deprecated ACPI property - arm,scatter-gather\n"); + return true; + } + } + return false; } static inline bool tmc_etr_has_non_secure_access(struct tmc_drvdata *drvdata) From 1f82d58ddbe21ed75c04d04403d8268a95e0190a Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Fri, 12 Apr 2024 17:10:56 +0100 Subject: [PATCH 252/319] Documentation: ABI + trace: hisi_ptt: update paths to bus/event_source To allow for assigning a suitable parent to the struct pmu device update the documentation to describe the device via the event_source bus where it will remain accessible. For the ABI documention file also rename the file as it is named after the path. Reviewed-by: Yicong Yang Signed-off-by: Jonathan Cameron Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240412161057.14099-30-Jonathan.Cameron@huawei.com --- ...i_ptt => sysfs-bus-event_source-devices-hisi_ptt} | 12 ++++++------ Documentation/trace/hisi-ptt.rst | 4 ++-- MAINTAINERS | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename Documentation/ABI/testing/{sysfs-devices-hisi_ptt => sysfs-bus-event_source-devices-hisi_ptt} (90%) diff --git a/Documentation/ABI/testing/sysfs-devices-hisi_ptt b/Documentation/ABI/testing/sysfs-bus-event_source-devices-hisi_ptt similarity index 90% rename from Documentation/ABI/testing/sysfs-devices-hisi_ptt rename to Documentation/ABI/testing/sysfs-bus-event_source-devices-hisi_ptt index d7e206b4901c..1119766564d7 100644 --- a/Documentation/ABI/testing/sysfs-devices-hisi_ptt +++ b/Documentation/ABI/testing/sysfs-bus-event_source-devices-hisi_ptt @@ -1,4 +1,4 @@ -What: /sys/devices/hisi_ptt_/tune +What: /sys/bus/event_source/devices/hisi_ptt_/tune Date: October 2022 KernelVersion: 6.1 Contact: Yicong Yang @@ -8,7 +8,7 @@ Description: This directory contains files for tuning the PCIe link See Documentation/trace/hisi-ptt.rst for more information. -What: /sys/devices/hisi_ptt_/tune/qos_tx_cpl +What: /sys/bus/event_source/devices/hisi_ptt_/tune/qos_tx_cpl Date: October 2022 KernelVersion: 6.1 Contact: Yicong Yang @@ -18,7 +18,7 @@ Description: (RW) Controls the weight of Tx completion TLPs, which influence will return an error, and out of range values will be converted to 2. The value indicates a probable level of the event. -What: /sys/devices/hisi_ptt_/tune/qos_tx_np +What: /sys/bus/event_source/devices/hisi_ptt_/tune/qos_tx_np Date: October 2022 KernelVersion: 6.1 Contact: Yicong Yang @@ -28,7 +28,7 @@ Description: (RW) Controls the weight of Tx non-posted TLPs, which influence will return an error, and out of range values will be converted to 2. The value indicates a probable level of the event. -What: /sys/devices/hisi_ptt_/tune/qos_tx_p +What: /sys/bus/event_source/devices/hisi_ptt_/tune/qos_tx_p Date: October 2022 KernelVersion: 6.1 Contact: Yicong Yang @@ -38,7 +38,7 @@ Description: (RW) Controls the weight of Tx posted TLPs, which influence the will return an error, and out of range values will be converted to 2. The value indicates a probable level of the event. -What: /sys/devices/hisi_ptt_/tune/rx_alloc_buf_level +What: /sys/bus/event_source/devices/hisi_ptt_/tune/rx_alloc_buf_level Date: October 2022 KernelVersion: 6.1 Contact: Yicong Yang @@ -49,7 +49,7 @@ Description: (RW) Control the allocated buffer watermark for inbound packets. will return an error, and out of range values will be converted to 2. The value indicates a probable level of the event. -What: /sys/devices/hisi_ptt_/tune/tx_alloc_buf_level +What: /sys/bus/event_source/devices/hisi_ptt_/tune/tx_alloc_buf_level Date: October 2022 KernelVersion: 6.1 Contact: Yicong Yang diff --git a/Documentation/trace/hisi-ptt.rst b/Documentation/trace/hisi-ptt.rst index 989255eb5622..6eef28ebb0c7 100644 --- a/Documentation/trace/hisi-ptt.rst +++ b/Documentation/trace/hisi-ptt.rst @@ -40,7 +40,7 @@ IO dies (SICL, Super I/O Cluster), where there's one PCIe Root Complex for each SICL. :: - /sys/devices/hisi_ptt_ + /sys/bus/event_source/devices/hisi_ptt_ Tune ==== @@ -53,7 +53,7 @@ Each event is presented as a file under $(PTT PMU dir)/tune, and a simple open/read/write/close cycle will be used to tune the event. :: - $ cd /sys/devices/hisi_ptt_/tune + $ cd /sys/bus/event_source/devices/hisi_ptt_/tune $ ls qos_tx_cpl qos_tx_np qos_tx_p tx_path_rx_req_alloc_buf_level diff --git a/MAINTAINERS b/MAINTAINERS index c23fda1aa1f0..63a55f1559f4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9790,7 +9790,7 @@ M: Yicong Yang M: Jonathan Cameron L: linux-kernel@vger.kernel.org S: Maintained -F: Documentation/ABI/testing/sysfs-devices-hisi_ptt +F: Documentation/ABI/testing/sysfs-bus-event_source-devices-hisi_ptt F: Documentation/trace/hisi-ptt.rst F: drivers/hwtracing/ptt/ F: tools/perf/arch/arm64/util/hisi-ptt.c From 9b47d9982d1de5adf0abcb8f1a400e51d68cca1f Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Fri, 12 Apr 2024 17:10:57 +0100 Subject: [PATCH 253/319] hwtracing: hisi_ptt: Assign parent for event_source device Currently the PMU device appears directly under /sys/devices/ Only root busses should appear there, so instead assign the pmu->dev parent to be the PCI device. Link: https://lore.kernel.org/linux-cxl/ZCLI9A40PJsyqAmq@kroah.com/ Cc: Suzuki K Poulose Reviewed-by: Yicong Yang Signed-off-by: Jonathan Cameron Acked-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20240412161057.14099-31-Jonathan.Cameron@huawei.com --- drivers/hwtracing/ptt/hisi_ptt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwtracing/ptt/hisi_ptt.c b/drivers/hwtracing/ptt/hisi_ptt.c index 4bf04a977840..3090479a2979 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.c +++ b/drivers/hwtracing/ptt/hisi_ptt.c @@ -1221,6 +1221,7 @@ static int hisi_ptt_register_pmu(struct hisi_ptt *hisi_ptt) hisi_ptt->hisi_ptt_pmu = (struct pmu) { .module = THIS_MODULE, + .parent = &hisi_ptt->pdev->dev, .capabilities = PERF_PMU_CAP_EXCLUSIVE | PERF_PMU_CAP_NO_EXCLUDE, .task_ctx_nr = perf_sw_context, .attr_groups = hisi_ptt_pmu_groups, From 73df3d6f2e9533e93a5039a33c40dd7216b81801 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Wed, 24 Apr 2024 14:27:23 +0200 Subject: [PATCH 254/319] VMCI: Fix an error handling path in vmci_guest_probe_device() After a successful pci_iomap_range() call, pci_iounmap() should be called in the error handling path, as already done in the remove function. Add the missing call. The corresponding call was added in the remove function in commit 5ee109828e73 ("VMCI: dma dg: allocate send and receive buffers for DMA datagrams") Fixes: e283a0e8b7ea ("VMCI: dma dg: add MMIO access to registers") Signed-off-by: Christophe JAILLET Acked-by: Vishnu Dasa Link: https://lore.kernel.org/r/a35bbc3876ae1da70e49dafde4435750e1477be3.1713961553.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_guest.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/misc/vmw_vmci/vmci_guest.c b/drivers/misc/vmw_vmci/vmci_guest.c index 4f8d962bb5b2..1300ccab3d21 100644 --- a/drivers/misc/vmw_vmci/vmci_guest.c +++ b/drivers/misc/vmw_vmci/vmci_guest.c @@ -625,7 +625,8 @@ static int vmci_guest_probe_device(struct pci_dev *pdev, if (!vmci_dev) { dev_err(&pdev->dev, "Can't allocate memory for VMCI device\n"); - return -ENOMEM; + error = -ENOMEM; + goto err_unmap_mmio_base; } vmci_dev->dev = &pdev->dev; @@ -642,7 +643,8 @@ static int vmci_guest_probe_device(struct pci_dev *pdev, if (!vmci_dev->tx_buffer) { dev_err(&pdev->dev, "Can't allocate memory for datagram tx buffer\n"); - return -ENOMEM; + error = -ENOMEM; + goto err_unmap_mmio_base; } vmci_dev->data_buffer = dma_alloc_coherent(&pdev->dev, VMCI_DMA_DG_BUFFER_SIZE, @@ -893,6 +895,10 @@ err_free_notification_bitmap: err_free_data_buffers: vmci_free_dg_buffers(vmci_dev); +err_unmap_mmio_base: + if (mmio_base != NULL) + pci_iounmap(pdev, mmio_base); + /* The rest are managed resources and will be freed by PCI core */ return error; } From 6d0ca4a2a7e25f9ad07c1f335f20b4d9e048cdd5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 30 Apr 2024 09:49:11 +0100 Subject: [PATCH 255/319] nvmem: layouts: store owner from modules with nvmem_layout_driver_register() Modules registering driver with nvmem_layout_driver_register() might forget to set .owner field. The field is used by some of other kernel parts for reference counting (try_module_get()), so it is expected that drivers will set it. Solve the problem by moving this task away from the drivers to the core code, just like we did for platform_driver in commit 9447057eaff8 ("platform_device: use a macro instead of platform_driver_register"). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Michael Walle Reviewed-by: Miquel Raynal Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-2-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/layouts.c | 6 ++++-- include/linux/nvmem-provider.h | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c index 8b5e2de138eb..64dc7013a098 100644 --- a/drivers/nvmem/layouts.c +++ b/drivers/nvmem/layouts.c @@ -52,13 +52,15 @@ static const struct bus_type nvmem_layout_bus_type = { .remove = nvmem_layout_bus_remove, }; -int nvmem_layout_driver_register(struct nvmem_layout_driver *drv) +int __nvmem_layout_driver_register(struct nvmem_layout_driver *drv, + struct module *owner) { drv->driver.bus = &nvmem_layout_bus_type; + drv->driver.owner = owner; return driver_register(&drv->driver); } -EXPORT_SYMBOL_GPL(nvmem_layout_driver_register); +EXPORT_SYMBOL_GPL(__nvmem_layout_driver_register); void nvmem_layout_driver_unregister(struct nvmem_layout_driver *drv) { diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h index f0ba0e03218f..3ebeaa0ded00 100644 --- a/include/linux/nvmem-provider.h +++ b/include/linux/nvmem-provider.h @@ -199,7 +199,10 @@ int nvmem_add_one_cell(struct nvmem_device *nvmem, int nvmem_layout_register(struct nvmem_layout *layout); void nvmem_layout_unregister(struct nvmem_layout *layout); -int nvmem_layout_driver_register(struct nvmem_layout_driver *drv); +#define nvmem_layout_driver_register(drv) \ + __nvmem_layout_driver_register(drv, THIS_MODULE) +int __nvmem_layout_driver_register(struct nvmem_layout_driver *drv, + struct module *owner); void nvmem_layout_driver_unregister(struct nvmem_layout_driver *drv); #define module_nvmem_layout_driver(__nvmem_layout_driver) \ module_driver(__nvmem_layout_driver, nvmem_layout_driver_register, \ From 21833338eccb91194fec6ba7548d9c454824eca0 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 30 Apr 2024 09:49:12 +0100 Subject: [PATCH 256/319] nvmem: layouts: onie-tlv: drop driver owner initialization Core in nvmem_layout_driver_register() already sets the .owner, so driver does not need to. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Michael Walle Acked-by: Miquel Raynal Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-3-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/layouts/onie-tlv.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c index 9d2ad5f2dc10..0967a32319a2 100644 --- a/drivers/nvmem/layouts/onie-tlv.c +++ b/drivers/nvmem/layouts/onie-tlv.c @@ -247,7 +247,6 @@ MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table); static struct nvmem_layout_driver onie_tlv_layout = { .driver = { - .owner = THIS_MODULE, .name = "onie-tlv-layout", .of_match_table = onie_tlv_of_match_table, }, From 23fd602f21953c03c0714257d36685cd6b486f04 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 30 Apr 2024 09:49:13 +0100 Subject: [PATCH 257/319] nvmem: layouts: sl28vpd: drop driver owner initialization Core in nvmem_layout_driver_register() already sets the .owner, so driver does not need to. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Michael Walle Reviewed-by: Miquel Raynal Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-4-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/layouts/sl28vpd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/nvmem/layouts/sl28vpd.c b/drivers/nvmem/layouts/sl28vpd.c index 53fa50f17dca..e93b020b0836 100644 --- a/drivers/nvmem/layouts/sl28vpd.c +++ b/drivers/nvmem/layouts/sl28vpd.c @@ -156,7 +156,6 @@ MODULE_DEVICE_TABLE(of, sl28vpd_of_match_table); static struct nvmem_layout_driver sl28vpd_layout = { .driver = { - .owner = THIS_MODULE, .name = "kontron-sl28vpd-layout", .of_match_table = sl28vpd_of_match_table, }, From dc3d88ade857ba3dca34f008e0b0aed3ef79cb15 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 30 Apr 2024 09:49:14 +0100 Subject: [PATCH 258/319] nvmem: sc27xx: fix module autoloading Add MODULE_DEVICE_TABLE(), so the module could be properly autoloaded based on the alias from of_device_id table. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-5-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/sc27xx-efuse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvmem/sc27xx-efuse.c b/drivers/nvmem/sc27xx-efuse.c index bff27011f4ff..4e2ffefac96c 100644 --- a/drivers/nvmem/sc27xx-efuse.c +++ b/drivers/nvmem/sc27xx-efuse.c @@ -262,6 +262,7 @@ static const struct of_device_id sc27xx_efuse_of_match[] = { { .compatible = "sprd,sc2730-efuse", .data = &sc2730_edata}, { } }; +MODULE_DEVICE_TABLE(of, sc27xx_efuse_of_match); static struct platform_driver sc27xx_efuse_driver = { .probe = sc27xx_efuse_probe, From 154c1ec943e34f3188c9305b0c91d5e7dc1373b8 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 30 Apr 2024 09:49:15 +0100 Subject: [PATCH 259/319] nvmem: sprd: fix module autoloading Add MODULE_DEVICE_TABLE(), so the module could be properly autoloaded based on the alias from of_device_id table. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-6-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/sprd-efuse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvmem/sprd-efuse.c b/drivers/nvmem/sprd-efuse.c index bb3105f3291f..1a7e4e5d8b86 100644 --- a/drivers/nvmem/sprd-efuse.c +++ b/drivers/nvmem/sprd-efuse.c @@ -426,6 +426,7 @@ static const struct of_device_id sprd_efuse_of_match[] = { { .compatible = "sprd,ums312-efuse", .data = &ums312_data }, { } }; +MODULE_DEVICE_TABLE(of, sprd_efuse_of_match); static struct platform_driver sprd_efuse_driver = { .probe = sprd_efuse_probe, From 8d8fc146dd7a0d6a6b37695747a524310dfb9d57 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 30 Apr 2024 09:49:16 +0100 Subject: [PATCH 260/319] nvmem: core: switch to use device_add_groups() devm_device_add_groups() is being removed from the kernel, so move the nvmem driver to use device_add_groups() instead. The logic is identical, when the device is removed the driver core will properly clean up and remove the groups, and the memory used by the attribute groups will be freed because it was created with dev_* calls, so this is functionally identical overall. Cc: Srinivas Kandagatla Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20240430084921.33387-7-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 2c6b99402df8..e1ec3b7200d7 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -478,7 +478,7 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem) nvmem_cells_group.bin_attrs = cells_attrs; - ret = devm_device_add_groups(&nvmem->dev, nvmem_cells_groups); + ret = device_add_groups(&nvmem->dev, nvmem_cells_groups); if (ret) goto unlock_mutex; From 693d2f629962628ddefc88f4b6b453edda5ac32e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 30 Apr 2024 09:49:17 +0100 Subject: [PATCH 261/319] nvmem: lpc18xx_eeprom: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Acked-by: Vladimir Zapolskiy Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-8-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/lpc18xx_eeprom.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/nvmem/lpc18xx_eeprom.c b/drivers/nvmem/lpc18xx_eeprom.c index a0275b29afd5..a73acc7377d2 100644 --- a/drivers/nvmem/lpc18xx_eeprom.c +++ b/drivers/nvmem/lpc18xx_eeprom.c @@ -249,13 +249,11 @@ err_clk: return ret; } -static int lpc18xx_eeprom_remove(struct platform_device *pdev) +static void lpc18xx_eeprom_remove(struct platform_device *pdev) { struct lpc18xx_eeprom_dev *eeprom = platform_get_drvdata(pdev); clk_disable_unprepare(eeprom->clk); - - return 0; } static const struct of_device_id lpc18xx_eeprom_of_match[] = { @@ -266,7 +264,7 @@ MODULE_DEVICE_TABLE(of, lpc18xx_eeprom_of_match); static struct platform_driver lpc18xx_eeprom_driver = { .probe = lpc18xx_eeprom_probe, - .remove = lpc18xx_eeprom_remove, + .remove_new = lpc18xx_eeprom_remove, .driver = { .name = "lpc18xx-eeprom", .of_match_table = lpc18xx_eeprom_of_match, From e2c7d6e02382d0298779d37f79388498c5173178 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Tue, 30 Apr 2024 09:49:18 +0100 Subject: [PATCH 262/319] dt-bindings: nvmem: Add compatible for sm8450, sm8550 and sm8650 Document QFPROM compatible for sm8450, sm8550 and sm8650 SoCs. Signed-off-by: Mukesh Ojha Reviewed-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-9-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index 8c8f05d9eaf1..aed90aff3593 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -42,6 +42,9 @@ properties: - qcom,sm6375-qfprom - qcom,sm8150-qfprom - qcom,sm8250-qfprom + - qcom,sm8450-qfprom + - qcom,sm8550-qfprom + - qcom,sm8650-qfprom - const: qcom,qfprom reg: From dc5d4043510be6147dd6b4f625a94305a2776441 Mon Sep 17 00:00:00 2001 From: David Collins Date: Tue, 30 Apr 2024 09:49:19 +0100 Subject: [PATCH 263/319] dt-bindings: nvmem: qcom,spmi-sdam: update maintainer Emails to Shyam bounce (reason: 585 5.1.1 : Recipient address rejected: undeliverable address: No such user here.) so change the maintainer to be me. I work on qcom,spmi-sdam as well as other PMIC peripheral devices. Signed-off-by: David Collins Acked-by: Krzysztof Kozlowski Reviewed-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-10-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,spmi-sdam.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,spmi-sdam.yaml b/Documentation/devicetree/bindings/nvmem/qcom,spmi-sdam.yaml index 068bedf5dbc9..5d7be0b34536 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,spmi-sdam.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,spmi-sdam.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Qualcomm Technologies, Inc. SPMI SDAM maintainers: - - Shyam Kumar Thella + - David Collins description: | The SDAM provides scratch register space for the PMIC clients. This From a5888ae5b3c33a8b339989e13b5e8ea2f927bcc4 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 30 Apr 2024 09:49:20 +0100 Subject: [PATCH 264/319] dt-bindings: nvmem: Add compatible for SC8280XP Document the QFPROM block found on SC8280XP. Signed-off-by: Konrad Dybcio Acked-by: Rob Herring Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-11-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index aed90aff3593..80845c722ae4 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -34,6 +34,7 @@ properties: - qcom,qcs404-qfprom - qcom,sc7180-qfprom - qcom,sc7280-qfprom + - qcom,sc8280xp-qfprom - qcom,sdm630-qfprom - qcom,sdm670-qfprom - qcom,sdm845-qfprom From 2a1ad6b75292d38aa2f6ded7335979e0632521da Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Tue, 30 Apr 2024 09:49:21 +0100 Subject: [PATCH 265/319] nvmem: meson-mx-efuse: Remove nvmem_device from efuse struct nvmem_device is used at one place while registering nvmem device and it is not required to be present in efuse struct for just this purpose. Drop nvmem_device and manage with nvmem device stack variable. Signed-off-by: Mukesh Ojha Reviewed-by: Martin Blumenstingl Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430084921.33387-12-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/meson-mx-efuse.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/nvmem/meson-mx-efuse.c b/drivers/nvmem/meson-mx-efuse.c index 3ff04d5ca8f8..8a16f5f02657 100644 --- a/drivers/nvmem/meson-mx-efuse.c +++ b/drivers/nvmem/meson-mx-efuse.c @@ -43,7 +43,6 @@ struct meson_mx_efuse_platform_data { struct meson_mx_efuse { void __iomem *base; struct clk *core_clk; - struct nvmem_device *nvmem; struct nvmem_config config; }; @@ -193,6 +192,7 @@ static int meson_mx_efuse_probe(struct platform_device *pdev) { const struct meson_mx_efuse_platform_data *drvdata; struct meson_mx_efuse *efuse; + struct nvmem_device *nvmem; drvdata = of_device_get_match_data(&pdev->dev); if (!drvdata) @@ -223,9 +223,9 @@ static int meson_mx_efuse_probe(struct platform_device *pdev) return PTR_ERR(efuse->core_clk); } - efuse->nvmem = devm_nvmem_register(&pdev->dev, &efuse->config); + nvmem = devm_nvmem_register(&pdev->dev, &efuse->config); - return PTR_ERR_OR_ZERO(efuse->nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static struct platform_driver meson_mx_efuse_driver = { From 4286dbcecc3f8775852fee04c2ea8af265549af3 Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Tue, 30 Apr 2024 10:16:55 +0100 Subject: [PATCH 266/319] slimbus: qcom-ngd-ctrl: Reduce auto suspend delay Currently we have auto suspend delay of 1s which is very high and it takes long time to driver for runtime suspend after use case is done. Hence to optimize runtime PM ops, reduce auto suspend delay to 100ms. Signed-off-by: Viken Dadhaniya Acked-by: Konrad Dybcio Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430091657.35428-2-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index efeba8275a66..5e132273c6c4 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -81,7 +81,6 @@ #define SLIM_USR_MC_DISCONNECT_PORT 0x2E #define SLIM_USR_MC_REPEAT_CHANGE_VALUE 0x0 -#define QCOM_SLIM_NGD_AUTOSUSPEND MSEC_PER_SEC #define SLIM_RX_MSGQ_TIMEOUT_VAL 0x10000 #define SLIM_LA_MGR 0xFF @@ -1571,7 +1570,7 @@ static int qcom_slim_ngd_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ctrl); pm_runtime_use_autosuspend(dev); - pm_runtime_set_autosuspend_delay(dev, QCOM_SLIM_NGD_AUTOSUSPEND); + pm_runtime_set_autosuspend_delay(dev, 100); pm_runtime_set_suspended(dev); pm_runtime_enable(dev); pm_runtime_get_noresume(dev); From 880b33b0580c8ac9d0202b6e592cb116c99d1b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 30 Apr 2024 10:16:56 +0100 Subject: [PATCH 267/319] slimbus: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert the slimbus drivers from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430091657.35428-3-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ctrl.c | 5 ++--- drivers/slimbus/qcom-ngd-ctrl.c | 11 ++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/slimbus/qcom-ctrl.c b/drivers/slimbus/qcom-ctrl.c index 400b7b385a44..7d632fad1300 100644 --- a/drivers/slimbus/qcom-ctrl.c +++ b/drivers/slimbus/qcom-ctrl.c @@ -626,7 +626,7 @@ err_request_irq_failed: return ret; } -static int qcom_slim_remove(struct platform_device *pdev) +static void qcom_slim_remove(struct platform_device *pdev) { struct qcom_slim_ctrl *ctrl = platform_get_drvdata(pdev); @@ -635,7 +635,6 @@ static int qcom_slim_remove(struct platform_device *pdev) clk_disable_unprepare(ctrl->rclk); clk_disable_unprepare(ctrl->hclk); destroy_workqueue(ctrl->rxwq); - return 0; } /* @@ -721,7 +720,7 @@ static const struct of_device_id qcom_slim_dt_match[] = { static struct platform_driver qcom_slim_driver = { .probe = qcom_slim_probe, - .remove = qcom_slim_remove, + .remove_new = qcom_slim_remove, .driver = { .name = "qcom_slim_ctrl", .of_match_table = qcom_slim_dt_match, diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 5e132273c6c4..a33b5f9090fe 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1674,14 +1674,12 @@ err_pdr_lookup: return ret; } -static int qcom_slim_ngd_ctrl_remove(struct platform_device *pdev) +static void qcom_slim_ngd_ctrl_remove(struct platform_device *pdev) { platform_driver_unregister(&qcom_slim_ngd_driver); - - return 0; } -static int qcom_slim_ngd_remove(struct platform_device *pdev) +static void qcom_slim_ngd_remove(struct platform_device *pdev) { struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); @@ -1696,7 +1694,6 @@ static int qcom_slim_ngd_remove(struct platform_device *pdev) kfree(ctrl->ngd); ctrl->ngd = NULL; - return 0; } static int __maybe_unused qcom_slim_ngd_runtime_idle(struct device *dev) @@ -1739,7 +1736,7 @@ static const struct dev_pm_ops qcom_slim_ngd_dev_pm_ops = { static struct platform_driver qcom_slim_ngd_ctrl_driver = { .probe = qcom_slim_ngd_ctrl_probe, - .remove = qcom_slim_ngd_ctrl_remove, + .remove_new = qcom_slim_ngd_ctrl_remove, .driver = { .name = "qcom,slim-ngd-ctrl", .of_match_table = qcom_slim_ngd_dt_match, @@ -1748,7 +1745,7 @@ static struct platform_driver qcom_slim_ngd_ctrl_driver = { static struct platform_driver qcom_slim_ngd_driver = { .probe = qcom_slim_ngd_probe, - .remove = qcom_slim_ngd_remove, + .remove_new = qcom_slim_ngd_remove, .driver = { .name = QCOM_SLIM_NGD_DRV_NAME, .pm = &qcom_slim_ngd_dev_pm_ops, From 35230d31056d82a416bba881cceca420fc90d079 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 30 Apr 2024 10:16:57 +0100 Subject: [PATCH 268/319] slimbus: qcom-ctrl: fix module autoloading Add MODULE_DEVICE_TABLE(), so the module could be properly autoloaded based on the alias from of_device_id table. Pin controllers are considered core components, so usually they are built-in, however these Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20240430091657.35428-4-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ctrl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/slimbus/qcom-ctrl.c b/drivers/slimbus/qcom-ctrl.c index 7d632fad1300..0274bc285b60 100644 --- a/drivers/slimbus/qcom-ctrl.c +++ b/drivers/slimbus/qcom-ctrl.c @@ -717,6 +717,7 @@ static const struct of_device_id qcom_slim_dt_match[] = { { .compatible = "qcom,slim", }, {} }; +MODULE_DEVICE_TABLE(of, qcom_slim_dt_match); static struct platform_driver qcom_slim_driver = { .probe = qcom_slim_probe, From 8003f00d895310d409b2bf9ef907c56b42a4e0f4 Mon Sep 17 00:00:00 2001 From: Hagar Gamal Halim Hemdan Date: Tue, 30 Apr 2024 08:59:16 +0000 Subject: [PATCH 269/319] vmci: prevent speculation leaks by sanitizing event in event_deliver() Coverity spotted that event_msg is controlled by user-space, event_msg->event_data.event is passed to event_deliver() and used as an index without sanitization. This change ensures that the event index is sanitized to mitigate any possibility of speculative information leaks. This bug was discovered and resolved using Coverity Static Analysis Security Testing (SAST) by Synopsys, Inc. Only compile tested, no access to HW. Fixes: 1d990201f9bb ("VMCI: event handling implementation.") Cc: stable Signed-off-by: Hagar Gamal Halim Hemdan Link: https://lore.kernel.org/stable/20231127193533.46174-1-hagarhem%40amazon.com Link: https://lore.kernel.org/r/20240430085916.4753-1-hagarhem@amazon.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_event.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/misc/vmw_vmci/vmci_event.c b/drivers/misc/vmw_vmci/vmci_event.c index 5d7ac07623c2..9a41ab65378d 100644 --- a/drivers/misc/vmw_vmci/vmci_event.c +++ b/drivers/misc/vmw_vmci/vmci_event.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -86,9 +87,12 @@ static void event_deliver(struct vmci_event_msg *event_msg) { struct vmci_subscription *cur; struct list_head *subscriber_list; + u32 sanitized_event, max_vmci_event; rcu_read_lock(); - subscriber_list = &subscriber_array[event_msg->event_data.event]; + max_vmci_event = ARRAY_SIZE(subscriber_array); + sanitized_event = array_index_nospec(event_msg->event_data.event, max_vmci_event); + subscriber_list = &subscriber_array[sanitized_event]; list_for_each_entry_rcu(cur, subscriber_list, node) { cur->callback(cur->id, &event_msg->event_data, cur->callback_data); From bf8367b00c33c64a9391c262bb2e11d274c9f2a4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Fri, 26 Apr 2024 09:48:35 +0000 Subject: [PATCH 270/319] iio: invensense: fix timestamp glitches when switching frequency When a sensor is running and there is a FIFO frequency change due to another sensor turned on/off, there are glitches on timestamp. Fix that by using only interrupt timestamp when there is the corresponding sensor data in the FIFO. Delete FIFO period handling and simplify internal functions. Update integration inside inv_mpu6050 and inv_icm42600 drivers. Fixes: 0ecc363ccea7 ("iio: make invensense timestamp module generic") Cc: Stable@vger.kernel.org Signed-off-by: Jean-Baptiste Maneyrol Link: https://lore.kernel.org/r/20240426094835.138389-1-inv.git-commit@tdk.com Signed-off-by: Jonathan Cameron --- .../inv_sensors/inv_sensors_timestamp.c | 24 +++++++++---------- .../imu/inv_icm42600/inv_icm42600_buffer.c | 20 +++++++--------- drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 2 +- .../linux/iio/common/inv_sensors_timestamp.h | 3 +-- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c index 4b8ec16240b5..fa205f17bd90 100644 --- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c @@ -70,13 +70,13 @@ int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts, } EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP); -static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period, uint32_t mult) +static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period) { uint32_t period_min, period_max; /* check that period is acceptable */ - period_min = ts->min_period * mult; - period_max = ts->max_period * mult; + period_min = ts->min_period * ts->mult; + period_max = ts->max_period * ts->mult; if (period > period_min && period < period_max) return true; else @@ -84,15 +84,15 @@ static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t perio } static bool inv_update_chip_period(struct inv_sensors_timestamp *ts, - uint32_t mult, uint32_t period) + uint32_t period) { uint32_t new_chip_period; - if (!inv_validate_period(ts, period, mult)) + if (!inv_validate_period(ts, period)) return false; /* update chip internal period estimation */ - new_chip_period = period / mult; + new_chip_period = period / ts->mult; inv_update_acc(&ts->chip_period, new_chip_period); ts->period = ts->mult * ts->chip_period.val; @@ -125,16 +125,14 @@ static void inv_align_timestamp_it(struct inv_sensors_timestamp *ts) } void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, - uint32_t fifo_period, size_t fifo_nb, - size_t sensor_nb, int64_t timestamp) + size_t sample_nb, int64_t timestamp) { struct inv_sensors_timestamp_interval *it; int64_t delta, interval; - const uint32_t fifo_mult = fifo_period / ts->chip.clock_period; uint32_t period; bool valid = false; - if (fifo_nb == 0) + if (sample_nb == 0) return; /* update interrupt timestamp and compute chip and sensor periods */ @@ -144,14 +142,14 @@ void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, delta = it->up - it->lo; if (it->lo != 0) { /* compute period: delta time divided by number of samples */ - period = div_s64(delta, fifo_nb); - valid = inv_update_chip_period(ts, fifo_mult, period); + period = div_s64(delta, sample_nb); + valid = inv_update_chip_period(ts, period); } /* no previous data, compute theoritical value from interrupt */ if (ts->timestamp == 0) { /* elapsed time: sensor period * sensor samples number */ - interval = (int64_t)ts->period * (int64_t)sensor_nb; + interval = (int64_t)ts->period * (int64_t)sample_nb; ts->timestamp = it->up - interval; return; } diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index cfb4a41ab7c1..63b85ec88c13 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -512,20 +512,20 @@ int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) return 0; /* handle gyroscope timestamp and FIFO data parsing */ - ts = &gyro_st->ts; - inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, - st->fifo.nb.gyro, st->timestamp.gyro); if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, + st->timestamp.gyro); ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); if (ret) return ret; } /* handle accelerometer timestamp and FIFO data parsing */ - ts = &accel_st->ts; - inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, - st->fifo.nb.accel, st->timestamp.accel); if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, + st->timestamp.accel); ret = inv_icm42600_accel_parse_fifo(st->indio_accel); if (ret) return ret; @@ -555,9 +555,7 @@ int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, if (st->fifo.nb.gyro > 0) { ts = &gyro_st->ts; - inv_sensors_timestamp_interrupt(ts, st->fifo.period, - st->fifo.nb.total, st->fifo.nb.gyro, - gyro_ts); + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts); ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); if (ret) return ret; @@ -565,9 +563,7 @@ int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, if (st->fifo.nb.accel > 0) { ts = &accel_st->ts; - inv_sensors_timestamp_interrupt(ts, st->fifo.period, - st->fifo.nb.total, st->fifo.nb.accel, - accel_ts); + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); ret = inv_icm42600_accel_parse_fifo(st->indio_accel); if (ret) return ret; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c index 86465226f7e1..0dc0f22a5582 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -100,7 +100,7 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) goto end_session; /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */ fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); - inv_sensors_timestamp_interrupt(&st->timestamp, fifo_period, nb, nb, pf->timestamp); + inv_sensors_timestamp_interrupt(&st->timestamp, nb, pf->timestamp); inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, nb, 0); /* clear internal data buffer for avoiding kernel data leak */ diff --git a/include/linux/iio/common/inv_sensors_timestamp.h b/include/linux/iio/common/inv_sensors_timestamp.h index a47d304d1ba7..8d506f1e9df2 100644 --- a/include/linux/iio/common/inv_sensors_timestamp.h +++ b/include/linux/iio/common/inv_sensors_timestamp.h @@ -71,8 +71,7 @@ int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts, uint32_t period, bool fifo); void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, - uint32_t fifo_period, size_t fifo_nb, - size_t sensor_nb, int64_t timestamp); + size_t sample_nb, int64_t timestamp); static inline int64_t inv_sensors_timestamp_pop(struct inv_sensors_timestamp *ts) { From 51fafb3cd7fcf4f4682693b4d2883e2a5bfffe33 Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Thu, 25 Apr 2024 14:42:32 +0300 Subject: [PATCH 271/319] iio: adc: PAC1934: fix accessing out of bounds array index Fix accessing out of bounds array index for average current and voltage measurements. The device itself has only 4 channels, but in sysfs there are "fake" channels for the average voltages and currents too. Fixes: 0fb528c8255b ("iio: adc: adding support for PAC193x") Reported-by: Conor Dooley Signed-off-by: Marius Cristea Closes: https://lore.kernel.org/linux-iio/20240405-embellish-bonnet-ab5f10560d93@wendy/ Tested-by: Conor Dooley Link: https://lore.kernel.org/r/20240425114232.81390-1-marius.cristea@microchip.com Signed-off-by: Jonathan Cameron --- drivers/iio/adc/pac1934.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index f751260605e4..456f12faa348 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -787,6 +787,15 @@ static int pac1934_read_raw(struct iio_dev *indio_dev, s64 curr_energy; int ret, channel = chan->channel - 1; + /* + * For AVG the index should be between 5 to 8. + * To calculate PAC1934_CH_VOLTAGE_AVERAGE, + * respectively PAC1934_CH_CURRENT real index, we need + * to remove the added offset (PAC1934_MAX_NUM_CHANNELS). + */ + if (channel >= PAC1934_MAX_NUM_CHANNELS) + channel = channel - PAC1934_MAX_NUM_CHANNELS; + ret = pac1934_retrieve_data(info, PAC1934_MIN_UPDATE_WAIT_TIME_US); if (ret < 0) return ret; From 827dca3129708a8465bde90c86c2e3c38e62dd4f Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Wed, 24 Apr 2024 20:59:10 +0200 Subject: [PATCH 272/319] iio: temperature: mcp9600: Fix temperature reading for negative values Temperature is stored as 16bit value in two's complement format. Current implementation ignores the sign bit. Make it aware of the sign bit by using sign_extend32. Fixes: 3f6b9598b6df ("iio: temperature: Add MCP9600 thermocouple EMF converter") Signed-off-by: Dimitri Fedrau Reviewed-by: Marcelo Schmitt Tested-by: Andrew Hepp Link: https://lore.kernel.org/r/20240424185913.1177127-1-dima.fedrau@gmail.com Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/temperature/mcp9600.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/temperature/mcp9600.c b/drivers/iio/temperature/mcp9600.c index 46845804292b..7a3eef5d5e75 100644 --- a/drivers/iio/temperature/mcp9600.c +++ b/drivers/iio/temperature/mcp9600.c @@ -52,7 +52,8 @@ static int mcp9600_read(struct mcp9600_data *data, if (ret < 0) return ret; - *val = ret; + + *val = sign_extend32(ret, 15); return 0; } From 3df463865ba42b8f88a590326f4c9ea17a1ce459 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 29 Apr 2024 16:01:05 +0300 Subject: [PATCH 273/319] stm class: Fix a double free in stm_register_device() The put_device(&stm->dev) call will trigger stm_device_release() which frees "stm" so the vfree(stm) on the next line is a double free. Fixes: 389b6699a2aa ("stm class: Fix stm device initialization order") Signed-off-by: Dan Carpenter Reviewed-by: Amelie Delaunay Reviewed-by: Andy Shevchenko Signed-off-by: Alexander Shishkin Link: https://lore.kernel.org/r/20240429130119.1518073-2-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/stm/core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/hwtracing/stm/core.c b/drivers/hwtracing/stm/core.c index 534fbefc7f6a..20895d391562 100644 --- a/drivers/hwtracing/stm/core.c +++ b/drivers/hwtracing/stm/core.c @@ -868,8 +868,11 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data, return -ENOMEM; stm->major = register_chrdev(0, stm_data->name, &stm_fops); - if (stm->major < 0) - goto err_free; + if (stm->major < 0) { + err = stm->major; + vfree(stm); + return err; + } device_initialize(&stm->dev); stm->dev.devt = MKDEV(stm->major, 0); @@ -913,10 +916,8 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data, err_device: unregister_chrdev(stm->major, stm_data->name); - /* matches device_initialize() above */ + /* calls stm_device_release() */ put_device(&stm->dev); -err_free: - vfree(stm); return err; } From 07cf835689d700d46d88b5bbffc32bc210319f5f Mon Sep 17 00:00:00 2001 From: Mikhail Lappo Date: Mon, 29 Apr 2024 16:01:06 +0300 Subject: [PATCH 274/319] stm class: Add source type Currently kernel HW tracing infrastrtucture and specifically its SyS-T part treats all source data in the same way. Treating and encoding different trace data sources differently might allow decoding software to make use of e.g. ftrace event ids by converting them to a SyS-T message catalog. The solution is to keep source type stored within stm_source_data structure to allow different handling by stm output/protocol. Currently we only differentiate between STM_USER and STM_FTRACE sources. Signed-off-by: Mikhail Lappo Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-3-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/stm/console.c | 1 + drivers/hwtracing/stm/ftrace.c | 1 + drivers/hwtracing/stm/heartbeat.c | 1 + include/linux/stm.h | 12 ++++++++++++ 4 files changed, 15 insertions(+) diff --git a/drivers/hwtracing/stm/console.c b/drivers/hwtracing/stm/console.c index a00f65e21747..097a00ac43a7 100644 --- a/drivers/hwtracing/stm/console.c +++ b/drivers/hwtracing/stm/console.c @@ -22,6 +22,7 @@ static struct stm_console { .data = { .name = "console", .nr_chans = 1, + .type = STM_USER, .link = stm_console_link, .unlink = stm_console_unlink, }, diff --git a/drivers/hwtracing/stm/ftrace.c b/drivers/hwtracing/stm/ftrace.c index 3bb606dfa634..a7cea7ea0163 100644 --- a/drivers/hwtracing/stm/ftrace.c +++ b/drivers/hwtracing/stm/ftrace.c @@ -23,6 +23,7 @@ static struct stm_ftrace { .data = { .name = "ftrace", .nr_chans = STM_FTRACE_NR_CHANNELS, + .type = STM_FTRACE, .link = stm_ftrace_link, .unlink = stm_ftrace_unlink, }, diff --git a/drivers/hwtracing/stm/heartbeat.c b/drivers/hwtracing/stm/heartbeat.c index 81d7b21d31ec..e9496fe97baa 100644 --- a/drivers/hwtracing/stm/heartbeat.c +++ b/drivers/hwtracing/stm/heartbeat.c @@ -78,6 +78,7 @@ static int stm_heartbeat_init(void) } stm_heartbeat[i].data.nr_chans = 1; + stm_heartbeat[i].data.type = STM_USER; stm_heartbeat[i].data.link = stm_heartbeat_link; stm_heartbeat[i].data.unlink = stm_heartbeat_unlink; hrtimer_init(&stm_heartbeat[i].hrtimer, CLOCK_MONOTONIC, diff --git a/include/linux/stm.h b/include/linux/stm.h index 3b22689512be..2fcbef9608f6 100644 --- a/include/linux/stm.h +++ b/include/linux/stm.h @@ -30,6 +30,16 @@ enum stp_packet_flags { STP_PACKET_TIMESTAMPED = 0x2, }; +/** + * enum stm_source_type - STM source driver + * @STM_USER: any STM trace source + * @STM_FTRACE: ftrace STM source + */ +enum stm_source_type { + STM_USER, + STM_FTRACE, +}; + struct stp_policy; struct stm_device; @@ -106,6 +116,7 @@ struct stm_source_device; * @name: device name, will be used for policy lookup * @src: internal structure, only used by stm class code * @nr_chans: number of channels to allocate + * @type: type of STM source driver represented by stm_source_type * @link: called when this source gets linked to an STM device * @unlink: called when this source is about to get unlinked from its STM * @@ -117,6 +128,7 @@ struct stm_source_data { struct stm_source_device *src; unsigned int percpu; unsigned int nr_chans; + unsigned int type; int (*link)(struct stm_source_data *data); void (*unlink)(struct stm_source_data *data); }; From ee27f44e1592d849cb90b1f1c38cae0c7e32defa Mon Sep 17 00:00:00 2001 From: Mikhail Lappo Date: Mon, 29 Apr 2024 16:01:07 +0300 Subject: [PATCH 275/319] stm class: Propagate source type to protocols Pass stm source type via stm_write() to allow different handling on protocol level. The measure above should allow protocol level encoder to differentiate and accordingly pack the messages. As an example SyS-T might get use of ftrace message ID's and instead of applying regular header, pack them as SyS-T catalog or SyS-T Structured Binary Data message to allow proper decoding on the other side. Signed-off-by: Mikhail Lappo Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-4-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/stm/core.c | 8 ++++---- drivers/hwtracing/stm/p_basic.c | 3 ++- drivers/hwtracing/stm/p_sys-t.c | 3 ++- drivers/hwtracing/stm/stm.h | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/hwtracing/stm/core.c b/drivers/hwtracing/stm/core.c index 20895d391562..ccf39a80dc4f 100644 --- a/drivers/hwtracing/stm/core.c +++ b/drivers/hwtracing/stm/core.c @@ -600,7 +600,7 @@ EXPORT_SYMBOL_GPL(stm_data_write); static ssize_t notrace stm_write(struct stm_device *stm, struct stm_output *output, - unsigned int chan, const char *buf, size_t count) + unsigned int chan, const char *buf, size_t count, struct stm_source_data *source) { int err; @@ -608,7 +608,7 @@ stm_write(struct stm_device *stm, struct stm_output *output, if (!stm->pdrv) return -ENODEV; - err = stm->pdrv->write(stm->data, output, chan, buf, count); + err = stm->pdrv->write(stm->data, output, chan, buf, count, source); if (err < 0) return err; @@ -657,7 +657,7 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf, pm_runtime_get_sync(&stm->dev); - count = stm_write(stm, &stmf->output, 0, kbuf, count); + count = stm_write(stm, &stmf->output, 0, kbuf, count, NULL); pm_runtime_mark_last_busy(&stm->dev); pm_runtime_put_autosuspend(&stm->dev); @@ -1299,7 +1299,7 @@ int notrace stm_source_write(struct stm_source_data *data, stm = srcu_dereference(src->link, &stm_source_srcu); if (stm) - count = stm_write(stm, &src->output, chan, buf, count); + count = stm_write(stm, &src->output, chan, buf, count, data); else count = -ENODEV; diff --git a/drivers/hwtracing/stm/p_basic.c b/drivers/hwtracing/stm/p_basic.c index 8980a6a5fd6c..5525c975cc6f 100644 --- a/drivers/hwtracing/stm/p_basic.c +++ b/drivers/hwtracing/stm/p_basic.c @@ -10,7 +10,8 @@ #include "stm.h" static ssize_t basic_write(struct stm_data *data, struct stm_output *output, - unsigned int chan, const char *buf, size_t count) + unsigned int chan, const char *buf, size_t count, + struct stm_source_data *source) { unsigned int c = output->channel + chan; unsigned int m = output->master; diff --git a/drivers/hwtracing/stm/p_sys-t.c b/drivers/hwtracing/stm/p_sys-t.c index 8254971c02e7..5b4b9f350ec1 100644 --- a/drivers/hwtracing/stm/p_sys-t.c +++ b/drivers/hwtracing/stm/p_sys-t.c @@ -285,7 +285,8 @@ sys_t_clock_sync(struct stm_data *data, unsigned int m, unsigned int c) } static ssize_t sys_t_write(struct stm_data *data, struct stm_output *output, - unsigned int chan, const char *buf, size_t count) + unsigned int chan, const char *buf, size_t count, + struct stm_source_data *source) { struct sys_t_output *op = output->pdrv_private; unsigned int c = output->channel + chan; diff --git a/drivers/hwtracing/stm/stm.h b/drivers/hwtracing/stm/stm.h index a9be49fc7a6b..85dda6e0d10c 100644 --- a/drivers/hwtracing/stm/stm.h +++ b/drivers/hwtracing/stm/stm.h @@ -96,7 +96,7 @@ struct stm_protocol_driver { const char *name; ssize_t (*write)(struct stm_data *data, struct stm_output *output, unsigned int chan, - const char *buf, size_t count); + const char *buf, size_t count, struct stm_source_data *source); void (*policy_node_init)(void *arg); int (*output_open)(void *priv, struct stm_output *output); void (*output_close)(struct stm_output *output); From 3c720592908014979873b7611333300641263d2d Mon Sep 17 00:00:00 2001 From: Mikhail Lappo Date: Mon, 29 Apr 2024 16:01:08 +0300 Subject: [PATCH 276/319] stm class: sys-t: Improve ftrace source handling Package messages from ftrace source with SyS-T Structured Binary Data (later SBD) header and 64-bit ID. This provides modification-free compatibility between ftrace and SyS-T arguments structure by applying 0xFFFF mask on message ID. This happens due to the fact that SBD and ftrace structures have the same principle of data storage:
. The headers are bit-to-bit compatible and both contain event/catalog ID with the exception, that ftrace header contains more fields within 64 bits which needs to be masked during encoding process, since SBD standard doesn't support mask of ID field. 0 15 16 23 24 31 32 39 40 63 ftrace: <-pid-> <----> SBD: <------- msg_id ------------------------------> Signed-off-by: Mikhail Lappo Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-5-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/stm/p_sys-t.c | 90 ++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/drivers/hwtracing/stm/p_sys-t.c b/drivers/hwtracing/stm/p_sys-t.c index 5b4b9f350ec1..1e75aa0025a3 100644 --- a/drivers/hwtracing/stm/p_sys-t.c +++ b/drivers/hwtracing/stm/p_sys-t.c @@ -20,6 +20,7 @@ enum sys_t_message_type { MIPI_SYST_TYPE_RAW = 6, MIPI_SYST_TYPE_SHORT64, MIPI_SYST_TYPE_CLOCK, + MIPI_SYST_TYPE_SBD, }; enum sys_t_message_severity { @@ -53,6 +54,19 @@ enum sys_t_message_string_subtype { MIPI_SYST_STRING_PRINTF_64 = 12, }; +/** + * enum sys_t_message_sbd_subtype - SyS-T SBD message subtypes + * @MIPI_SYST_SBD_ID32: SBD message with 32-bit message ID + * @MIPI_SYST_SBD_ID64: SBD message with 64-bit message ID + * + * Structured Binary Data messages can send information of arbitrary length, + * together with ID's that describe BLOB's content and layout. + */ +enum sys_t_message_sbd_subtype { + MIPI_SYST_SBD_ID32 = 0, + MIPI_SYST_SBD_ID64 = 1, +}; + #define MIPI_SYST_TYPE(t) ((u32)(MIPI_SYST_TYPE_ ## t)) #define MIPI_SYST_SEVERITY(s) ((u32)(MIPI_SYST_SEVERITY_ ## s) << 4) #define MIPI_SYST_OPT_LOC BIT(8) @@ -75,6 +89,20 @@ enum sys_t_message_string_subtype { #define CLOCK_SYNC_HEADER (MIPI_SYST_TYPES(CLOCK, TRANSPORT_SYNC) | \ MIPI_SYST_SEVERITY(MAX)) +/* + * SyS-T and ftrace headers are compatible to an extent that ftrace event ID + * and args can be treated as SyS-T SBD message with 64-bit ID and arguments + * BLOB right behind the header without modification. Bits [16:63] coming + * together with message ID from ftrace aren't used by SBD and must be zeroed. + * + * 0 15 16 23 24 31 32 39 40 63 + * ftrace: <-pid-> <----> + * SBD: <------- msg_id ------------------------------> + */ +#define SBD_HEADER (MIPI_SYST_TYPES(SBD, ID64) | \ + MIPI_SYST_SEVERITY(INFO) | \ + MIPI_SYST_OPT_GUID) + struct sys_t_policy_node { uuid_t uuid; bool do_len; @@ -284,6 +312,59 @@ sys_t_clock_sync(struct stm_data *data, unsigned int m, unsigned int c) return sizeof(header) + sizeof(payload); } +static inline u32 sys_t_header(struct stm_source_data *source) +{ + if (source && source->type == STM_FTRACE) + return SBD_HEADER; + return DATA_HEADER; +} + +static ssize_t sys_t_write_data(struct stm_data *data, + struct stm_source_data *source, + unsigned int master, unsigned int channel, + bool ts_first, const void *buf, size_t count) +{ + ssize_t sz; + const unsigned char nil = 0; + + /* + * Ftrace is zero-copy compatible with SyS-T SBD, but requires + * special handling of first 64 bits. Trim and send them separately + * to avoid damage on original ftrace buffer. + */ + if (source && source->type == STM_FTRACE) { + u64 compat_ftrace_header; + ssize_t header_sz; + ssize_t buf_sz; + + if (count < sizeof(compat_ftrace_header)) + return -EINVAL; + + /* SBD only makes use of low 16 bits (event ID) from ftrace event */ + compat_ftrace_header = *(u64 *)buf & 0xffff; + header_sz = stm_data_write(data, master, channel, false, + &compat_ftrace_header, + sizeof(compat_ftrace_header)); + if (header_sz != sizeof(compat_ftrace_header)) + return header_sz; + + buf_sz = stm_data_write(data, master, channel, false, + buf + header_sz, count - header_sz); + if (buf_sz != count - header_sz) + return buf_sz; + sz = header_sz + buf_sz; + } else { + sz = stm_data_write(data, master, channel, false, buf, count); + } + + if (sz <= 0) + return sz; + + data->packet(data, master, channel, STP_PACKET_FLAG, 0, 0, &nil); + + return sz; +} + static ssize_t sys_t_write(struct stm_data *data, struct stm_output *output, unsigned int chan, const char *buf, size_t count, struct stm_source_data *source) @@ -291,8 +372,7 @@ static ssize_t sys_t_write(struct stm_data *data, struct stm_output *output, struct sys_t_output *op = output->pdrv_private; unsigned int c = output->channel + chan; unsigned int m = output->master; - const unsigned char nil = 0; - u32 header = DATA_HEADER; + u32 header = sys_t_header(source); u8 uuid[UUID_SIZE]; ssize_t sz; @@ -349,11 +429,7 @@ static ssize_t sys_t_write(struct stm_data *data, struct stm_output *output, } /* DATA */ - sz = stm_data_write(data, m, c, false, buf, count); - if (sz > 0) - data->packet(data, m, c, STP_PACKET_FLAG, 0, 0, &nil); - - return sz; + return sys_t_write_data(data, source, m, c, false, buf, count); } static const struct stm_protocol_driver sys_t_pdrv = { From 1592e84c7d7b3015df29f67db014560f0a9b5c66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 29 Apr 2024 16:01:09 +0300 Subject: [PATCH 277/319] intel_th: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-6-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/acpi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/intel_th/acpi.c b/drivers/hwtracing/intel_th/acpi.c index 87f9024e4bbb..503620e9fd10 100644 --- a/drivers/hwtracing/intel_th/acpi.c +++ b/drivers/hwtracing/intel_th/acpi.c @@ -60,18 +60,16 @@ static int intel_th_acpi_probe(struct platform_device *pdev) return 0; } -static int intel_th_acpi_remove(struct platform_device *pdev) +static void intel_th_acpi_remove(struct platform_device *pdev) { struct intel_th *th = platform_get_drvdata(pdev); intel_th_free(th); - - return 0; } static struct platform_driver intel_th_acpi_driver = { .probe = intel_th_acpi_probe, - .remove = intel_th_acpi_remove, + .remove_new = intel_th_acpi_remove, .driver = { .name = DRIVER_NAME, .acpi_match_table = intel_th_acpi_ids, From 8dc0b2d385d28d06dd17963f4f003738a3c67d3f Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Mon, 29 Apr 2024 16:01:10 +0300 Subject: [PATCH 278/319] intel_th: Constify the struct device_type usage Since commit aed65af1cc2f ("drivers: make device_type const"), the driver core can properly handle constant struct device_type. Move the intel_th_source_device_type, intel_th_output_device_type, intel_th_switch_device_type and intel_th_device_type variables to be constant structures as well, placing it into read-only memory which can not be modified at runtime. Signed-off-by: "Ricardo B. Marliere" Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-7-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c index cc7f879bb175..3511f3618f2d 100644 --- a/drivers/hwtracing/intel_th/core.c +++ b/drivers/hwtracing/intel_th/core.c @@ -180,7 +180,7 @@ static void intel_th_device_release(struct device *dev) intel_th_device_free(to_intel_th_device(dev)); } -static struct device_type intel_th_source_device_type = { +static const struct device_type intel_th_source_device_type = { .name = "intel_th_source_device", .release = intel_th_device_release, }; @@ -333,19 +333,19 @@ static struct attribute *intel_th_output_attrs[] = { ATTRIBUTE_GROUPS(intel_th_output); -static struct device_type intel_th_output_device_type = { +static const struct device_type intel_th_output_device_type = { .name = "intel_th_output_device", .groups = intel_th_output_groups, .release = intel_th_device_release, .devnode = intel_th_output_devnode, }; -static struct device_type intel_th_switch_device_type = { +static const struct device_type intel_th_switch_device_type = { .name = "intel_th_switch_device", .release = intel_th_device_release, }; -static struct device_type *intel_th_device_type[] = { +static const struct device_type *intel_th_device_type[] = { [INTEL_TH_SOURCE] = &intel_th_source_device_type, [INTEL_TH_OUTPUT] = &intel_th_output_device_type, [INTEL_TH_SWITCH] = &intel_th_switch_device_type, From 212886f5d930082d31eae617bf4d4b6c89800328 Mon Sep 17 00:00:00 2001 From: Li Zhijian Date: Mon, 29 Apr 2024 16:01:11 +0300 Subject: [PATCH 279/319] intel_th: Convert sprintf/snprintf to sysfs_emit Per filesystems/sysfs.rst, show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. coccinelle complains that there are still a couple of functions that use snprintf(). Convert them to sysfs_emit(). sprintf() will be converted as weel if they have. Generally, this patch is generated by make coccicheck M= MODE=patch \ COCCI=scripts/coccinelle/api/device_attr_show.cocci No functional change intended Signed-off-by: Li Zhijian Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-8-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/gth.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/intel_th/gth.c b/drivers/hwtracing/intel_th/gth.c index b3308934a687..3883f99fd5d5 100644 --- a/drivers/hwtracing/intel_th/gth.c +++ b/drivers/hwtracing/intel_th/gth.c @@ -154,9 +154,9 @@ static ssize_t master_attr_show(struct device *dev, spin_unlock(>h->gth_lock); if (port >= 0) - count = snprintf(buf, PAGE_SIZE, "%x\n", port); + count = sysfs_emit(buf, "%x\n", port); else - count = snprintf(buf, PAGE_SIZE, "disabled\n"); + count = sysfs_emit(buf, "disabled\n"); return count; } @@ -332,8 +332,8 @@ static ssize_t output_attr_show(struct device *dev, pm_runtime_get_sync(dev); spin_lock(>h->gth_lock); - count = snprintf(buf, PAGE_SIZE, "%x\n", - gth_output_parm_get(gth, oa->port, oa->parm)); + count = sysfs_emit(buf, "%x\n", + gth_output_parm_get(gth, oa->port, oa->parm)); spin_unlock(>h->gth_lock); pm_runtime_put(dev); From 988001c735944015a99292e4d996f052a0f5c0b7 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 29 Apr 2024 16:01:12 +0300 Subject: [PATCH 280/319] intel_th: Remove redundant initialization of pointer outp The pointer outp is being initialized with a value that is never read. All the reads of outp occur after outp has neen set to an appropriate value rather than using the first value is initialized with. The assignment is redundant and can be removed. Cleans up clang scan warning: drivers/hwtracing/intel_th/sth.c:73:15: warning: Value stored to 'outp' during its initialization is never read [deadcode.DeadStores] Signed-off-by: Colin Ian King Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-9-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/sth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwtracing/intel_th/sth.c b/drivers/hwtracing/intel_th/sth.c index 9ca8c4e045f8..428f595a28a0 100644 --- a/drivers/hwtracing/intel_th/sth.c +++ b/drivers/hwtracing/intel_th/sth.c @@ -70,8 +70,8 @@ static ssize_t notrace sth_stm_packet(struct stm_data *stm_data, struct sth_device *sth = container_of(stm_data, struct sth_device, stm); struct intel_th_channel __iomem *out = sth_channel(sth, master, channel); - u64 __iomem *outp = &out->Dn; unsigned long reg = REG_STH_TRIG; + u64 __iomem *outp; #ifndef CONFIG_64BIT if (size > 4) From 76e9f4389d4b778e613b2c34beb462ba80102e3b Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 29 Apr 2024 16:01:13 +0300 Subject: [PATCH 281/319] intel_th: msu: Fix kernel-doc warnings Correct function comments to prevent kernel-doc warnings found when using "W=1". msu.c:77: warning: Function parameter or member 'msc' not described in 'msc_window' msu.c:122: warning: bad line: msu.c:760: warning: No description found for return value of 'msc_configure' msu.c:1309: warning: Function parameter or member 'nr_pages' not described in 'msc_buffer_alloc' msu.c:1309: warning: Function parameter or member 'nr_wins' not described in 'msc_buffer_alloc' msu.c:1309: warning: Excess function parameter 'size' description in 'msc_buffer_alloc' msu.c:1376: warning: No description found for return value of 'msc_buffer_free_unless_used' msu.c:1444: warning: No description found for return value of 'msc_win_to_user' Signed-off-by: Randy Dunlap Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240429130119.1518073-10-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/msu.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/hwtracing/intel_th/msu.c b/drivers/hwtracing/intel_th/msu.c index 9621efe0e95c..be63d5b8f193 100644 --- a/drivers/hwtracing/intel_th/msu.c +++ b/drivers/hwtracing/intel_th/msu.c @@ -61,6 +61,7 @@ enum lockout_state { * @lo_lock: lockout state serialization * @nr_blocks: number of blocks (pages) in this window * @nr_segs: number of segments in this window (<= @nr_blocks) + * @msc: pointer to the MSC device * @_sgt: array of block descriptors * @sgt: array of block descriptors */ @@ -119,7 +120,6 @@ struct msc_iter { * @user_count: number of users of the buffer * @mmap_count: number of mappings * @buf_mutex: mutex to serialize access to buffer-related bits - * @enabled: MSC is enabled * @wrap: wrapping is enabled * @mode: MSC operating mode @@ -755,6 +755,8 @@ unlock: * Program storage mode, wrapping, burst length and trace buffer address * into a given MSC. Then, enable tracing and set msc::enabled. * The latter is serialized on msc::buf_mutex, so make sure to hold it. + * + * Return: %0 for success or a negative error code otherwise. */ static int msc_configure(struct msc *msc) { @@ -1291,7 +1293,8 @@ static void msc_buffer_free(struct msc *msc) /** * msc_buffer_alloc() - allocate a buffer for MSC * @msc: MSC device - * @size: allocation size in bytes + * @nr_pages: number of pages for each window + * @nr_wins: number of windows * * Allocate a storage buffer for MSC, depending on the msc::mode, it will be * either done via msc_buffer_contig_alloc() for SINGLE operation mode or @@ -1370,6 +1373,9 @@ static int msc_buffer_unlocked_free_unless_used(struct msc *msc) * @msc: MSC device * * This is a locked version of msc_buffer_unlocked_free_unless_used(). + * + * Return: 0 on successful deallocation or if there was no buffer to + * deallocate, -EBUSY if there are active users. */ static int msc_buffer_free_unless_used(struct msc *msc) { @@ -1438,6 +1444,8 @@ struct msc_win_to_user_struct { * @data: callback's private data * @src: source buffer * @len: amount of data to copy from the source buffer + * + * Return: >= %0 for success or -errno for error. */ static unsigned long msc_win_to_user(void *data, void *src, size_t len) { From e44937889bdf4ecd1f0c25762b7226406b9b7a69 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Mon, 29 Apr 2024 16:01:14 +0300 Subject: [PATCH 282/319] intel_th: pci: Add Granite Rapids support Add support for the Trace Hub in Granite Rapids. Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Cc: stable@kernel.org Link: https://lore.kernel.org/r/20240429130119.1518073-11-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index 147d338c191e..beb4b2766aae 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -299,6 +299,11 @@ static const struct pci_device_id intel_th_pci_id_table[] = { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa76f), .driver_data = (kernel_ulong_t)&intel_th_2x, }, + { + /* Granite Rapids */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0963), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, { /* Alder Lake CPU */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x466f), From 854afe461b009801a171b3a49c5f75ea43e4c04c Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Mon, 29 Apr 2024 16:01:15 +0300 Subject: [PATCH 283/319] intel_th: pci: Add Granite Rapids SOC support Add support for the Trace Hub in Granite Rapids SOC. Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Cc: stable@kernel.org Link: https://lore.kernel.org/r/20240429130119.1518073-12-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index beb4b2766aae..44c08db253d8 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -304,6 +304,11 @@ static const struct pci_device_id intel_th_pci_id_table[] = { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0963), .driver_data = (kernel_ulong_t)&intel_th_2x, }, + { + /* Granite Rapids SOC */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3256), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, { /* Alder Lake CPU */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x466f), From 2e1da7efabe05cb0cf0b358883b2bc89080ed0eb Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Mon, 29 Apr 2024 16:01:16 +0300 Subject: [PATCH 284/319] intel_th: pci: Add Sapphire Rapids SOC support Add support for the Trace Hub in Sapphire Rapids SOC. Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Cc: stable@kernel.org Link: https://lore.kernel.org/r/20240429130119.1518073-13-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index 44c08db253d8..08c956fb4995 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -309,6 +309,11 @@ static const struct pci_device_id intel_th_pci_id_table[] = { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3256), .driver_data = (kernel_ulong_t)&intel_th_2x, }, + { + /* Sapphire Rapids SOC */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3456), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, { /* Alder Lake CPU */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x466f), From c4a30def564d75e84718b059d1a62cc79b137cf9 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Mon, 29 Apr 2024 16:01:17 +0300 Subject: [PATCH 285/319] intel_th: pci: Add Meteor Lake-S support Add support for the Trace Hub in Meteor Lake-S. Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Cc: stable@kernel.org Link: https://lore.kernel.org/r/20240429130119.1518073-14-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index 08c956fb4995..7bb50d414027 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -289,6 +289,11 @@ static const struct pci_device_id intel_th_pci_id_table[] = { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7e24), .driver_data = (kernel_ulong_t)&intel_th_2x, }, + { + /* Meteor Lake-S */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7f26), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, { /* Raptor Lake-S */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7a26), From a4f813c3ec9d1c32bc402becd1f011b3904dd699 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Mon, 29 Apr 2024 16:01:18 +0300 Subject: [PATCH 286/319] intel_th: pci: Add Meteor Lake-S CPU support Add support for the Trace Hub in Meteor Lake-S CPU. Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Cc: stable@kernel.org Link: https://lore.kernel.org/r/20240429130119.1518073-15-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index 7bb50d414027..0db8ff9b1c48 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -294,6 +294,11 @@ static const struct pci_device_id intel_th_pci_id_table[] = { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7f26), .driver_data = (kernel_ulong_t)&intel_th_2x, }, + { + /* Meteor Lake-S CPU */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xae24), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, { /* Raptor Lake-S */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7a26), From f866b65322bfbc8fcca13c25f49e1a5c5a93ae4d Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Mon, 29 Apr 2024 16:01:19 +0300 Subject: [PATCH 287/319] intel_th: pci: Add Lunar Lake support Add support for the Trace Hub in Lunar Lake. Signed-off-by: Alexander Shishkin Reviewed-by: Andy Shevchenko Cc: stable@kernel.org Link: https://lore.kernel.org/r/20240429130119.1518073-16-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index 0db8ff9b1c48..0d7b9839e5b6 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -324,6 +324,11 @@ static const struct pci_device_id intel_th_pci_id_table[] = { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3456), .driver_data = (kernel_ulong_t)&intel_th_2x, }, + { + /* Lunar Lake */ + PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa824), + .driver_data = (kernel_ulong_t)&intel_th_2x, + }, { /* Alder Lake CPU */ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x466f), From fbf740aeb86a4fe82ad158d26d711f2f3be79b3e Mon Sep 17 00:00:00 2001 From: Huai-Yuan Liu Date: Fri, 12 Apr 2024 16:38:40 +0800 Subject: [PATCH 288/319] ppdev: Add an error check in register_device In register_device, the return value of ida_simple_get is unchecked, in witch ida_simple_get will use an invalid index value. To address this issue, index should be checked after ida_simple_get. When the index value is abnormal, a warning message should be printed, the port should be dropped, and the value should be recorded. Fixes: 9a69645dde11 ("ppdev: fix registering same device name") Signed-off-by: Huai-Yuan Liu Link: https://lore.kernel.org/r/20240412083840.234085-1-qq810974084@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/ppdev.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c index ee951b265213..58e9dcc2a308 100644 --- a/drivers/char/ppdev.c +++ b/drivers/char/ppdev.c @@ -296,28 +296,35 @@ static int register_device(int minor, struct pp_struct *pp) if (!port) { pr_warn("%s: no associated port!\n", name); rc = -ENXIO; - goto err; + goto err_free_name; } index = ida_alloc(&ida_index, GFP_KERNEL); + if (index < 0) { + pr_warn("%s: failed to get index!\n", name); + rc = index; + goto err_put_port; + } + memset(&ppdev_cb, 0, sizeof(ppdev_cb)); ppdev_cb.irq_func = pp_irq; ppdev_cb.flags = (pp->flags & PP_EXCL) ? PARPORT_FLAG_EXCL : 0; ppdev_cb.private = pp; pdev = parport_register_dev_model(port, name, &ppdev_cb, index); - parport_put_port(port); if (!pdev) { pr_warn("%s: failed to register device!\n", name); rc = -ENXIO; ida_free(&ida_index, index); - goto err; + goto err_put_port; } pp->pdev = pdev; pp->index = index; dev_dbg(&pdev->dev, "registered pardevice\n"); -err: +err_put_port: + parport_put_port(port); +err_free_name: kfree(name); return rc; } From 5c9c5d7f26acc2c669c1dcf57d1bb43ee99220ce Mon Sep 17 00:00:00 2001 From: Sicong Huang Date: Tue, 16 Apr 2024 16:03:13 +0800 Subject: [PATCH 289/319] greybus: Fix use-after-free bug in gb_interface_release due to race condition. In gb_interface_create, &intf->mode_switch_completion is bound with gb_interface_mode_switch_work. Then it will be started by gb_interface_request_mode_switch. Here is the relevant code. if (!queue_work(system_long_wq, &intf->mode_switch_work)) { ... } If we call gb_interface_release to make cleanup, there may be an unfinished work. This function will call kfree to free the object "intf". However, if gb_interface_mode_switch_work is scheduled to run after kfree, it may cause use-after-free error as gb_interface_mode_switch_work will use the object "intf". The possible execution flow that may lead to the issue is as follows: CPU0 CPU1 | gb_interface_create | gb_interface_request_mode_switch gb_interface_release | kfree(intf) (free) | | gb_interface_mode_switch_work | mutex_lock(&intf->mutex) (use) Fix it by canceling the work before kfree. Signed-off-by: Sicong Huang Link: https://lore.kernel.org/r/20240416080313.92306-1-congei42@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/greybus/interface.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/greybus/interface.c b/drivers/greybus/interface.c index fd58a86b0888..d022bfb5e95d 100644 --- a/drivers/greybus/interface.c +++ b/drivers/greybus/interface.c @@ -693,6 +693,7 @@ static void gb_interface_release(struct device *dev) trace_gb_interface_release(intf); + cancel_work_sync(&intf->mode_switch_work); kfree(intf); } From 203b283cc1f478f5ea87de53a9fd2da3052f1c9a Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 18 Apr 2024 23:41:45 +0200 Subject: [PATCH 290/319] virt: acrn: Fix typos - s/need/needs/ - s/No/Not/ Signed-off-by: Thorsten Blum Link: https://lore.kernel.org/r/20240418214143.1253-3-thorsten.blum@toblux.com Signed-off-by: Greg Kroah-Hartman --- samples/acrn/vm-sample.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/acrn/vm-sample.c b/samples/acrn/vm-sample.c index 704402c64ea3..c61e0f91456e 100644 --- a/samples/acrn/vm-sample.c +++ b/samples/acrn/vm-sample.c @@ -3,7 +3,7 @@ * A sample program to run a User VM on the ACRN hypervisor * * This sample runs in a Service VM, which is a privileged VM of ACRN. - * CONFIG_ACRN_HSM need to be enabled in the Service VM. + * CONFIG_ACRN_HSM needs to be enabled in the Service VM. * * Guest VM code in guest16.s will be executed after the VM launched. * @@ -55,7 +55,7 @@ int main(int argc, char **argv) ret = posix_memalign(&guest_memory, 4096, GUEST_MEMORY_SIZE); if (ret < 0) { - printf("No enough memory!\n"); + printf("Not enough memory!\n"); return -1; } hsm_fd = open("/dev/acrn_hsm", O_RDWR|O_CLOEXEC); From 42316941335644a98335f209daafa4c122f28983 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Sun, 21 Apr 2024 17:37:49 +0000 Subject: [PATCH 291/319] binder: fix max_thread type inconsistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type defined for the BINDER_SET_MAX_THREADS ioctl was changed from size_t to __u32 in order to avoid incompatibility issues between 32 and 64-bit kernels. However, the internal types used to copy from user and store the value were never updated. Use u32 to fix the inconsistency. Fixes: a9350fc859ae ("staging: android: binder: fix BINDER_SET_MAX_THREADS declaration") Reported-by: Arve Hjønnevåg Cc: stable@vger.kernel.org Signed-off-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://lore.kernel.org/r/20240421173750.3117808-1-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 2 +- drivers/android/binder_internal.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index dd6923d37931..b21a7b246a0d 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -5367,7 +5367,7 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) goto err; break; case BINDER_SET_MAX_THREADS: { - int max_threads; + u32 max_threads; if (copy_from_user(&max_threads, ubuf, sizeof(max_threads))) { diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h index 7270d4d22207..5b7c80b99ae8 100644 --- a/drivers/android/binder_internal.h +++ b/drivers/android/binder_internal.h @@ -421,7 +421,7 @@ struct binder_proc { struct list_head todo; struct binder_stats stats; struct list_head delivered_death; - int max_threads; + u32 max_threads; int requested_threads; int requested_threads_started; int tmp_ref; From bd86030943bf57ca230b486090067b58da49b837 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Tue, 23 Apr 2024 10:38:10 +0800 Subject: [PATCH 292/319] tifm: constify the struct tifm_bus_type usage Now that the driver core can properly handle constant struct bus_type, move the tifm_bus_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Signed-off-by: Kunwu Chan Link: https://lore.kernel.org/r/20240423023810.1889264-1-chentao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/tifm_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/tifm_core.c b/drivers/misc/tifm_core.c index d2eb31f39aa7..fd9c3cbbc51e 100644 --- a/drivers/misc/tifm_core.c +++ b/drivers/misc/tifm_core.c @@ -148,7 +148,7 @@ static struct attribute *tifm_dev_attrs[] = { }; ATTRIBUTE_GROUPS(tifm_dev); -static struct bus_type tifm_bus_type = { +static const struct bus_type tifm_bus_type = { .name = "tifm", .dev_groups = tifm_dev_groups, .match = tifm_bus_match, From 82fecafeadd4428ddac0afddf27cd28d6019b35f Mon Sep 17 00:00:00 2001 From: Ricky Wu Date: Tue, 30 Apr 2024 14:01:08 +0800 Subject: [PATCH 293/319] misc: rtsx: do clear express reg every SD_INT orginal only do clear EXPRESS_LINK_FAIL reg when card removed, this patch moved the flow to SD_INT statement, make sure the reg status is correct when inserted/removed card every time. Signed-off-by: Ricky Wu Link: https://lore.kernel.org/r/20240430060108.580685-1-ricky_wu@realtek.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cardreader/rtsx_pcr.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/misc/cardreader/rtsx_pcr.c b/drivers/misc/cardreader/rtsx_pcr.c index 0ad2ff9065aa..117b3c24f910 100644 --- a/drivers/misc/cardreader/rtsx_pcr.c +++ b/drivers/misc/cardreader/rtsx_pcr.c @@ -1002,12 +1002,14 @@ static irqreturn_t rtsx_pci_isr(int irq, void *dev_id) } else { pcr->card_removed |= SD_EXIST; pcr->card_inserted &= ~SD_EXIST; - if ((PCI_PID(pcr) == PID_5261) || (PCI_PID(pcr) == PID_5264)) { - rtsx_pci_write_register(pcr, RTS5261_FW_STATUS, - RTS5261_EXPRESS_LINK_FAIL_MASK, 0); - pcr->extra_caps |= EXTRA_CAPS_SD_EXPRESS; - } } + + if ((PCI_PID(pcr) == PID_5261) || (PCI_PID(pcr) == PID_5264)) { + rtsx_pci_write_register(pcr, RTS5261_FW_STATUS, + RTS5261_EXPRESS_LINK_FAIL_MASK, 0); + pcr->extra_caps |= EXTRA_CAPS_SD_EXPRESS; + } + pcr->dma_error_count = 0; } From 5015f8a5ced7039de354c1103b0912f5a64fab03 Mon Sep 17 00:00:00 2001 From: "Bird, Tim" Date: Tue, 30 Apr 2024 17:58:13 +0000 Subject: [PATCH 294/319] scripts/spdxcheck: Add count of missing files to stats output Add a count of files missing an SPDX header to the stats output. This is useful detailed information for working on SPDX header additions. Signed-off-by: Tim Bird Link: https://lore.kernel.org/r/SA3PR13MB6372DB9F9F2C09F8A1E1B99BFD1A2@SA3PR13MB6372.namprd13.prod.outlook.com Signed-off-by: Greg Kroah-Hartman --- scripts/spdxcheck.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/spdxcheck.py b/scripts/spdxcheck.py index 18cb9f5b3d3d..8b8fb115fc81 100755 --- a/scripts/spdxcheck.py +++ b/scripts/spdxcheck.py @@ -412,6 +412,9 @@ if __name__ == '__main__': if parser.checked: pc = int(100 * parser.spdx_valid / parser.checked) sys.stderr.write('Files with SPDX: %12d %3d%%\n' %(parser.spdx_valid, pc)) + missing = parser.checked - parser.spdx_valid + mpc = int(100 * missing / parser.checked) + sys.stderr.write('Files without SPDX:%12d %3d%%\n' %(missing, mpc)) sys.stderr.write('Files with errors: %12d\n' %parser.spdx_errors) if ndirs: sys.stderr.write('\n') From 217f036242c7f7d5cf874f00ee39807162b90794 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 22 Feb 2024 14:41:22 +0900 Subject: [PATCH 295/319] extcon: intel-mrfld: Switch to use dev_err_probe() Switch to use dev_err_probe() to simplify the error path and unify a message template. Link: https://lore.kernel.org/lkml/20231222161954.2955905-1-andriy.shevchenko@linux.intel.com/ Signed-off-by: Andy Shevchenko Acked-by: MyungJoo Ham Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-mrfld.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-intel-mrfld.c b/drivers/extcon/extcon-intel-mrfld.c index cd1a5f230077..e96947fb76ee 100644 --- a/drivers/extcon/extcon-intel-mrfld.c +++ b/drivers/extcon/extcon-intel-mrfld.c @@ -217,24 +217,18 @@ static int mrfld_extcon_probe(struct platform_device *pdev) return -ENOMEM; ret = devm_extcon_dev_register(dev, data->edev); - if (ret < 0) { - dev_err(dev, "can't register extcon device: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "can't register extcon device\n"); ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_extcon_interrupt, IRQF_ONESHOT | IRQF_SHARED, pdev->name, data); - if (ret) { - dev_err(dev, "can't register IRQ handler: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "can't register IRQ handler\n"); ret = regmap_read(regmap, BCOVE_ID, &id); - if (ret) { - dev_err(dev, "can't read PMIC ID: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "can't read PMIC ID\n"); data->id = id; From b1781d0a1458070d40134e4f3412ec9d70099bec Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 12 Feb 2024 22:00:28 -0800 Subject: [PATCH 296/319] extcon: max8997: select IRQ_DOMAIN instead of depending on it IRQ_DOMAIN is a hidden (not user visible) symbol. Users cannot set it directly thru "make *config", so drivers should select it instead of depending on it if they need it. Relying on it being set for a dependency is risky. Consistently using "select" or "depends on" can also help reduce Kconfig circular dependency issues. Therefore, change EXTCON_MAX8997's use of "depends on" for IRQ_DOMAIN to "select". Link: https://lore.kernel.org/lkml/20240213060028.9744-1-rdunlap@infradead.org/ Fixes: dca1a71e4108 ("extcon: Add support irq domain for MAX8997 muic") Signed-off-by: Randy Dunlap Acked-by: Arnd Bergmann Signed-off-by: Chanwoo Choi --- drivers/extcon/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 5f869eacd19a..3da94b382292 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -116,7 +116,8 @@ config EXTCON_MAX77843 config EXTCON_MAX8997 tristate "Maxim MAX8997 EXTCON Support" - depends on MFD_MAX8997 && IRQ_DOMAIN + depends on MFD_MAX8997 + select IRQ_DOMAIN help If you say yes here you get support for the MUIC device of Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory From b1a8804f532a67843e78e0fa37703947d13a68ba Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Feb 2024 16:42:01 +0900 Subject: [PATCH 297/319] extcon: intel-mrfld: Don't shadow error from devm_extcon_dev_allocate() Don't shadow error from devm_extcon_dev_allocate() and return it as is. Link: https://lore.kernel.org/lkml/20231222161854.2955859-1-andriy.shevchenko@linux.intel.com/ Signed-off-by: Andy Shevchenko Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-mrfld.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-intel-mrfld.c b/drivers/extcon/extcon-intel-mrfld.c index e96947fb76ee..718fe374f95d 100644 --- a/drivers/extcon/extcon-intel-mrfld.c +++ b/drivers/extcon/extcon-intel-mrfld.c @@ -214,7 +214,7 @@ static int mrfld_extcon_probe(struct platform_device *pdev) data->edev = devm_extcon_dev_allocate(dev, mrfld_extcon_cable); if (IS_ERR(data->edev)) - return -ENOMEM; + return PTR_ERR(data->edev); ret = devm_extcon_dev_register(dev, data->edev); if (ret < 0) From b2da7e249833899497d8a381c7eb8d6bf0465ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 25 Feb 2024 16:54:50 +0100 Subject: [PATCH 298/319] extcon: adc-jack: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. LinkL: https://lore.kernel.org/lkml/14d30788ecd288b1b0983a8ea224499bbaa5de19.1708876186.git.u.kleine-koenig@pengutronix.de/ Signed-off-by: Uwe Kleine-König Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-adc-jack.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index 0317b614b680..cf5050a244b2 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -158,14 +158,12 @@ static int adc_jack_probe(struct platform_device *pdev) return 0; } -static int adc_jack_remove(struct platform_device *pdev) +static void adc_jack_remove(struct platform_device *pdev) { struct adc_jack_data *data = platform_get_drvdata(pdev); free_irq(data->irq, data); cancel_work_sync(&data->handler.work); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -196,7 +194,7 @@ static SIMPLE_DEV_PM_OPS(adc_jack_pm_ops, static struct platform_driver adc_jack_driver = { .probe = adc_jack_probe, - .remove = adc_jack_remove, + .remove_new = adc_jack_remove, .driver = { .name = "adc-jack", .pm = &adc_jack_pm_ops, From 67d4b2cedca0c2a5b75576d1d8d7c7e78a15308d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 25 Feb 2024 16:54:51 +0100 Subject: [PATCH 299/319] extcon: intel-cht-wc: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Link: https://lore.kernel.org/lkml/87f0b8f158565cb9ea68b42db2bb018f82a7ee27.1708876186.git.u.kleine-koenig@pengutronix.de/ Signed-off-by: Uwe Kleine-König Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-cht-wc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index 2c55f06ba699..733c470c3102 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -617,13 +617,11 @@ disable_sw_control: return ret; } -static int cht_wc_extcon_remove(struct platform_device *pdev) +static void cht_wc_extcon_remove(struct platform_device *pdev) { struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); cht_wc_extcon_sw_control(ext, false); - - return 0; } static const struct platform_device_id cht_wc_extcon_table[] = { @@ -634,7 +632,7 @@ MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table); static struct platform_driver cht_wc_extcon_driver = { .probe = cht_wc_extcon_probe, - .remove = cht_wc_extcon_remove, + .remove_new = cht_wc_extcon_remove, .id_table = cht_wc_extcon_table, .driver = { .name = "cht_wcove_pwrsrc", From 6d472f20c3c00a6fe61fd97646ffc627a697c760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 25 Feb 2024 16:54:52 +0100 Subject: [PATCH 300/319] extcon: intel-mrfld: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Link: https://lore.kernel.org/lkml/7223e19152980ef553e38cf56c2b38ec099586e0.1708876186.git.u.kleine-koenig@pengutronix.de/ Signed-off-by: Uwe Kleine-König Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-mrfld.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-intel-mrfld.c b/drivers/extcon/extcon-intel-mrfld.c index 718fe374f95d..a1f737f13d49 100644 --- a/drivers/extcon/extcon-intel-mrfld.c +++ b/drivers/extcon/extcon-intel-mrfld.c @@ -257,13 +257,11 @@ static int mrfld_extcon_probe(struct platform_device *pdev) return 0; } -static int mrfld_extcon_remove(struct platform_device *pdev) +static void mrfld_extcon_remove(struct platform_device *pdev) { struct mrfld_extcon_data *data = platform_get_drvdata(pdev); mrfld_extcon_sw_control(data, false); - - return 0; } static const struct platform_device_id mrfld_extcon_id_table[] = { @@ -277,7 +275,7 @@ static struct platform_driver mrfld_extcon_driver = { .name = "mrfld_bcove_pwrsrc", }, .probe = mrfld_extcon_probe, - .remove = mrfld_extcon_remove, + .remove_new = mrfld_extcon_remove, .id_table = mrfld_extcon_id_table, }; module_platform_driver(mrfld_extcon_driver); From ba6985eac33f510bf850210cb7c2b75492104919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 25 Feb 2024 16:54:53 +0100 Subject: [PATCH 301/319] extcon: max3355: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Link: https://lore.kernel.org/lkml/2c017ea490f721646bd472e7d427eb377e4e8423.1708876186.git.u.kleine-koenig@pengutronix.de/ Signed-off-by: Uwe Kleine-König Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max3355.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-max3355.c b/drivers/extcon/extcon-max3355.c index d7795607f693..e62ce7a8d131 100644 --- a/drivers/extcon/extcon-max3355.c +++ b/drivers/extcon/extcon-max3355.c @@ -112,13 +112,11 @@ static int max3355_probe(struct platform_device *pdev) return 0; } -static int max3355_remove(struct platform_device *pdev) +static void max3355_remove(struct platform_device *pdev) { struct max3355_data *data = platform_get_drvdata(pdev); gpiod_set_value_cansleep(data->shdn_gpiod, 0); - - return 0; } static const struct of_device_id max3355_match_table[] = { @@ -129,7 +127,7 @@ MODULE_DEVICE_TABLE(of, max3355_match_table); static struct platform_driver max3355_driver = { .probe = max3355_probe, - .remove = max3355_remove, + .remove_new = max3355_remove, .driver = { .name = "extcon-max3355", .of_match_table = max3355_match_table, From 9688b1d07089e5d1d67e79f9cb9cb1306e4d9195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 25 Feb 2024 16:54:54 +0100 Subject: [PATCH 302/319] extcon: max77843: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Link: https://lore.kernel.org/lkml/30097beba928bf2073645f85d21fb9c1aee64991.1708876186.git.u.kleine-koenig@pengutronix.de/ Signed-off-by: Uwe Kleine-König Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77843.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c index acb11a54f875..9849e3b8327e 100644 --- a/drivers/extcon/extcon-max77843.c +++ b/drivers/extcon/extcon-max77843.c @@ -928,7 +928,7 @@ err_muic_irq: return ret; } -static int max77843_muic_remove(struct platform_device *pdev) +static void max77843_muic_remove(struct platform_device *pdev) { struct max77843_muic_info *info = platform_get_drvdata(pdev); struct max77693_dev *max77843 = info->max77843; @@ -936,8 +936,6 @@ static int max77843_muic_remove(struct platform_device *pdev) cancel_work_sync(&info->irq_work); regmap_del_irq_chip(max77843->irq, max77843->irq_data_muic); i2c_unregister_device(max77843->i2c_muic); - - return 0; } static const struct platform_device_id max77843_muic_id[] = { @@ -958,7 +956,7 @@ static struct platform_driver max77843_muic_driver = { .of_match_table = of_max77843_muic_dt_match, }, .probe = max77843_muic_probe, - .remove = max77843_muic_remove, + .remove_new = max77843_muic_remove, .id_table = max77843_muic_id, }; From 5be3dfe6e5f8834e5793a75983be827b4294ae43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 25 Feb 2024 16:54:55 +0100 Subject: [PATCH 303/319] extcon: usb-gpio: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Link: https://lore.kernel.org/lkml/8914cd71b32e1f6298e65b84fb84370c73b4fe37.1708876186.git.u.kleine-koenig@pengutronix.de/ Signed-off-by: Uwe Kleine-König Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-usb-gpio.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index 40d967a11e87..9b61eb99b7dc 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -193,14 +193,12 @@ static int usb_extcon_probe(struct platform_device *pdev) return 0; } -static int usb_extcon_remove(struct platform_device *pdev) +static void usb_extcon_remove(struct platform_device *pdev) { struct usb_extcon_info *info = platform_get_drvdata(pdev); cancel_delayed_work_sync(&info->wq_detcable); device_init_wakeup(&pdev->dev, false); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -281,7 +279,7 @@ MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids); static struct platform_driver usb_extcon_driver = { .probe = usb_extcon_probe, - .remove = usb_extcon_remove, + .remove_new = usb_extcon_remove, .driver = { .name = "extcon-usb-gpio", .pm = &usb_extcon_pm_ops, From bff3f0e3035056e5b28b3d3817752c1e5f56b5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Sun, 25 Feb 2024 16:54:56 +0100 Subject: [PATCH 304/319] extcon: usbc-cros-ec: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Link: https://lore.kernel.org/lkml/52d0a4317d5372f1135259d4fbbd2822b86ba8f4.1708876186.git.u.kleine-koenig@pengutronix.de/ Signed-off-by: Uwe Kleine-König Reviewed-by: Tzung-Bi Shih Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-usbc-cros-ec.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-usbc-cros-ec.c b/drivers/extcon/extcon-usbc-cros-ec.c index fde1db62be0d..805a47230689 100644 --- a/drivers/extcon/extcon-usbc-cros-ec.c +++ b/drivers/extcon/extcon-usbc-cros-ec.c @@ -480,14 +480,12 @@ unregister_notifier: return ret; } -static int extcon_cros_ec_remove(struct platform_device *pdev) +static void extcon_cros_ec_remove(struct platform_device *pdev) { struct cros_ec_extcon_info *info = platform_get_drvdata(pdev); blocking_notifier_chain_unregister(&info->ec->event_notifier, &info->notifier); - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -531,7 +529,7 @@ static struct platform_driver extcon_cros_ec_driver = { .of_match_table = of_match_ptr(extcon_cros_ec_of_match), .pm = DEV_PM_OPS, }, - .remove = extcon_cros_ec_remove, + .remove_new = extcon_cros_ec_remove, .probe = extcon_cros_ec_probe, }; From 986c51b95948e0aff81295a34c4498a7547f7bf6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 4 Mar 2024 19:49:13 +0200 Subject: [PATCH 305/319] extcon: realtek: Remove unused of_gpio.h of_gpio.h is deprecated and subject to remove. The driver doesn't use it, simply remove the unused header. Link: https://lore.kernel.org/lkml/20240304174913.1198974-1-andriy.shevchenko@linux.intel.com/ Signed-off-by: Andy Shevchenko Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-rtk-type-c.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/extcon/extcon-rtk-type-c.c b/drivers/extcon/extcon-rtk-type-c.c index a592bab77538..19a01e663733 100644 --- a/drivers/extcon/extcon-rtk-type-c.c +++ b/drivers/extcon/extcon-rtk-type-c.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include From 3e8e45b65d9f3f1811c020325a306da0f01e485b Mon Sep 17 00:00:00 2001 From: Yang Li Date: Fri, 26 Apr 2024 18:00:54 +0800 Subject: [PATCH 306/319] extcon: adc-jack: Document missing struct members This patch adds kernel-doc comments for the previously undocumented members `dev` and `wakeup_source` in the struct adc_jack_data in adc-jack device driver. Link: https://lore.kernel.org/lkml/20240426100054.61506-1-yang.lee@linux.alibaba.com/ Signed-off-by: Yang Li Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-adc-jack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index cf5050a244b2..125016da7fde 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -26,6 +26,7 @@ /** * struct adc_jack_data - internal data for adc_jack device driver + * @dev: The device structure associated with the adc_jack. * @edev: extcon device. * @cable_names: list of supported cables. * @adc_conditions: list of adc value conditions. @@ -35,6 +36,7 @@ * handling at handling_delay jiffies. * @handler: extcon event handler called by interrupt handler. * @chan: iio channel being queried. + * @wakeup_source: Indicates if the device can wake up the system. */ struct adc_jack_data { struct device *dev; From b928d357576a58b02d8e997212c0351a9ee41ab0 Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Tue, 7 May 2024 14:07:38 -0700 Subject: [PATCH 307/319] spmi: make spmi_bus_type const Now that the driver core can properly handle constant struct bus_type, move the spmi_bus_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Cc: Greg Kroah-Hartman Suggested-by: Greg Kroah-Hartman Signed-off-by: "Ricardo B. Marliere" Link: https://lore.kernel.org/r/20240204-bus_cleanup-spmi-v1-1-ef7f278f1987@marliere.net Reviewed-by: Greg Kroah-Hartman Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-2-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index 3a60fd2e09e1..667085cb199d 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -378,7 +378,7 @@ static int spmi_drv_uevent(const struct device *dev, struct kobj_uevent_env *env return 0; } -static struct bus_type spmi_bus_type = { +static const struct bus_type spmi_bus_type = { .name = "spmi", .match = spmi_device_match, .probe = spmi_drv_probe, From c6c1b27f9a9a20ad2db663628fccaed72c6a0f1f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 7 May 2024 14:07:39 -0700 Subject: [PATCH 308/319] dt-bindings: spmi: hisilicon,hisi-spmi-controller: fix binding references Fix up the free text binding references which were not updated when moving the bindings out of staging and which had a leading current directory component, respectively. Fixes: 9bd9e0de1cf5 ("mfd: hi6421-spmi-pmic: move driver from staging") Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20231130173757.13011-2-johan+linaro@kernel.org Acked-by: Krzysztof Kozlowski Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-3-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/spmi/hisilicon,hisi-spmi-controller.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml index f882903769f9..eee7c8d4cf4a 100644 --- a/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml +++ b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml @@ -14,7 +14,7 @@ description: | It is a MIPI System Power Management (SPMI) controller. The PMIC part is provided by - ./Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml. + Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml. allOf: - $ref: spmi.yaml# @@ -48,7 +48,7 @@ patternProperties: PMIC properties, which are specific to the used SPMI PMIC device(s). When used in combination with HiSilicon 6421v600, the properties are documented at - drivers/staging/hikey9xx/hisilicon,hi6421-spmi-pmic.yaml. + Documentation/devicetree/bindings/mfd/hisilicon,hi6421-spmi-pmic.yaml unevaluatedProperties: false From 45b8221f0dcf1cda4dcab8f487eb60996c67a998 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 7 May 2024 14:07:40 -0700 Subject: [PATCH 309/319] dt-bindings: spmi: hisilicon,hisi-spmi-controller: clean up example Clean up the binding example by dropping the unnecessary parent bus node, using a define for the second register value of the PMIC child and increasing indentation to four spaces. Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20231130173757.13011-3-johan+linaro@kernel.org Acked-by: Krzysztof Kozlowski Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-4-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../spmi/hisilicon,hisi-spmi-controller.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml index eee7c8d4cf4a..3ccf35de3719 100644 --- a/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml +++ b/Documentation/devicetree/bindings/spmi/hisilicon,hisi-spmi-controller.yaml @@ -54,20 +54,17 @@ unevaluatedProperties: false examples: - | - bus { - #address-cells = <2>; - #size-cells = <2>; + #include - spmi: spmi@fff24000 { + spmi@fff24000 { compatible = "hisilicon,kirin970-spmi-controller"; + reg = <0xfff24000 0x1000>; #address-cells = <2>; #size-cells = <0>; - reg = <0x0 0xfff24000 0x0 0x1000>; hisilicon,spmi-channel = <2>; pmic@0 { - reg = <0 0>; - /* pmic properties */ + reg = <0 SPMI_USID>; + /* pmic properties */ }; - }; }; From eda4923d78d634482227c0b189d9b7ca18824146 Mon Sep 17 00:00:00 2001 From: Vamshi Gajjela Date: Tue, 7 May 2024 14:07:41 -0700 Subject: [PATCH 310/319] spmi: hisi-spmi-controller: Do not override device identifier 'nr' member of struct spmi_controller, which serves as an identifier for the controller/bus. This value is a dynamic ID assigned in spmi_controller_alloc, and overriding it from the driver results in an ida_free error "ida_free called for id=xx which is not allocated". Signed-off-by: Vamshi Gajjela Fixes: 70f59c90c819 ("staging: spmi: add Hikey 970 SPMI controller driver") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20240228185116.1269-1-vamshigajjela@google.com Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-5-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/hisi-spmi-controller.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/spmi/hisi-spmi-controller.c b/drivers/spmi/hisi-spmi-controller.c index 674a350cc676..fa068b34b040 100644 --- a/drivers/spmi/hisi-spmi-controller.c +++ b/drivers/spmi/hisi-spmi-controller.c @@ -300,7 +300,6 @@ static int spmi_controller_probe(struct platform_device *pdev) spin_lock_init(&spmi_controller->lock); - ctrl->nr = spmi_controller->channel; ctrl->dev.parent = pdev->dev.parent; ctrl->dev.of_node = of_node_get(pdev->dev.of_node); From c86f90e30a347ef0a28d0df3975c46389d0cc7fc Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Tue, 7 May 2024 14:07:42 -0700 Subject: [PATCH 311/319] spmi: pmic-arb: Replace three IS_ERR() calls by null pointer checks in spmi_pmic_arb_probe() The devm_ioremap() function does not return error pointers. It returns NULL on error. This issue was detected once more also by using the Coccinelle software. Update three checks (and corresponding error codes) for failed function calls accordingly. Fixes: ffdfbafdc4f4 ("spmi: Use devm_spmi_controller_alloc()") Fixes: 231601cd22bd ("spmi: pmic-arb: Add support for PMIC v7") Signed-off-by: Markus Elfring Link: https://lore.kernel.org/r/82a0768e-95b0-4091-bdd1-14c3e893726b@web.de Reviewed-by: Dmitry Baryshkov Reviewed-by: David Collins Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-6-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-pmic-arb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 9ed1180fe31f..937c15324513 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -1462,8 +1462,8 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); core = devm_ioremap(&ctrl->dev, res->start, resource_size(res)); - if (IS_ERR(core)) - return PTR_ERR(core); + if (!core) + return -ENOMEM; pmic_arb->core_size = resource_size(res); @@ -1495,15 +1495,15 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) "obsrvr"); pmic_arb->rd_base = devm_ioremap(&ctrl->dev, res->start, resource_size(res)); - if (IS_ERR(pmic_arb->rd_base)) - return PTR_ERR(pmic_arb->rd_base); + if (!pmic_arb->rd_base) + return -ENOMEM; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "chnls"); pmic_arb->wr_base = devm_ioremap(&ctrl->dev, res->start, resource_size(res)); - if (IS_ERR(pmic_arb->wr_base)) - return PTR_ERR(pmic_arb->wr_base); + if (!pmic_arb->wr_base) + return -ENOMEM; } pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS; From e72efb5d4f0470fbbe310ecd75770e2fdc97682b Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 7 May 2024 14:07:43 -0700 Subject: [PATCH 312/319] dt-bindings: spmi: Add X1E80100 SPMI PMIC ARB schema Add dedicated schema for X1E80100 PMIC ARB. This is not the first platform to introduce multiple buses. In fact, all platforms that implement the version 7 for the SPMI PMIC arbiter have multiple buses. Since the compatible should not be version based, the platform specific one is used. The X1E80100 platform is the first platform to really need the second master, as all the available boards have the PMICs that provide the eUSB2 repeater on the second bus. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20240417-spmi-multi-master-support-v10-1-5bc6d322e266@linaro.org Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-7-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../spmi/qcom,x1e80100-spmi-pmic-arb.yaml | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml diff --git a/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml new file mode 100644 index 000000000000..a28b70fb330a --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/qcom,x1e80100-spmi-pmic-arb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm X1E80100 SPMI Controller (PMIC Arbiter v7) + +maintainers: + - Stephen Boyd + +description: | + The X1E80100 SPMI PMIC Arbiter implements HW version 7 and it's an SPMI + controller with wrapping arbitration logic to allow for multiple on-chip + devices to control up to 2 SPMI separate buses. + + The PMIC Arbiter can also act as an interrupt controller, providing interrupts + to slave devices. + +properties: + compatible: + const: qcom,x1e80100-spmi-pmic-arb + + reg: + items: + - description: core registers + - description: tx-channel per virtual slave registers + - description: rx-channel (called observer) per virtual slave registers + + reg-names: + items: + - const: core + - const: chnls + - const: obsrvr + + ranges: true + + '#address-cells': + const: 2 + + '#size-cells': + const: 2 + + qcom,ee: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 5 + description: > + indicates the active Execution Environment identifier + + qcom,channel: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 5 + description: > + which of the PMIC Arb provided channels to use for accesses + +patternProperties: + "^spmi@[a-f0-9]+$": + type: object + $ref: /schemas/spmi/spmi.yaml + unevaluatedProperties: false + + properties: + reg: + items: + - description: configuration registers + - description: interrupt controller registers + + reg-names: + items: + - const: cnfg + - const: intr + + interrupts: + maxItems: 1 + + interrupt-names: + const: periph_irq + + interrupt-controller: true + + '#interrupt-cells': + const: 4 + description: | + cell 1: slave ID for the requested interrupt (0-15) + cell 2: peripheral ID for requested interrupt (0-255) + cell 3: the requested peripheral interrupt (0-7) + cell 4: interrupt flags indicating level-sense information, + as defined in dt-bindings/interrupt-controller/irq.h + +required: + - compatible + - reg-names + - qcom,ee + - qcom,channel + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + spmi: arbiter@c400000 { + compatible = "qcom,x1e80100-spmi-pmic-arb"; + reg = <0 0x0c400000 0 0x3000>, + <0 0x0c500000 0 0x4000000>, + <0 0x0c440000 0 0x80000>; + reg-names = "core", "chnls", "obsrvr"; + + qcom,ee = <0>; + qcom,channel = <0>; + + #address-cells = <2>; + #size-cells = <2>; + ranges; + + spmi_bus0: spmi@c42d000 { + reg = <0 0x0c42d000 0 0x4000>, + <0 0x0c4c0000 0 0x10000>; + reg-names = "cnfg", "intr"; + + interrupt-names = "periph_irq"; + interrupts-extended = <&pdc 1 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <4>; + + #address-cells = <2>; + #size-cells = <0>; + }; + }; + }; From 345fbbca3f76b7904219c0bf953dd6dc906680dd Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 7 May 2024 14:07:44 -0700 Subject: [PATCH 313/319] dt-bindings: spmi: Deprecate qcom,bus-id As it is optional and no platform is actually using the secondary bus, deprecate the qcom,bus-id property. For newer platforms that implement SPMI PMIC ARB v7 in HW, the X1E80100 approach should be used. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20240417-spmi-multi-master-support-v10-2-5bc6d322e266@linaro.org Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-8-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml index f983b4af6db9..51daf1b847a9 100644 --- a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml +++ b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml @@ -92,6 +92,7 @@ properties: description: > SPMI bus instance. only applicable to PMIC arbiter version 7 and beyond. Supported values, 0 = primary bus, 1 = secondary bus + deprecated: true required: - compatible From 83bfd7a81fc4d287658ddcd9490d7545f7693aa5 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 7 May 2024 14:07:45 -0700 Subject: [PATCH 314/319] spmi: pmic-arb: Fix some compile warnings about members not being described Fix the following compile warnings: warning: Function parameter or struct member 'core' not described in 'spmi_pmic_arb' warning: Function parameter or struct member 'core_size' not described in 'spmi_pmic_arb' warning: Function parameter or struct member 'mapping_table_valid' not described in 'spmi_pmic_arb' warning: Function parameter or struct member 'pmic_arb' not described in 'pmic_arb_read_data' warning: Function parameter or struct member 'pmic_arb' not described in 'pmic_arb_write_data' Reviewed-by: Neil Armstrong Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20240417-spmi-multi-master-support-v10-3-5bc6d322e266@linaro.org Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-9-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-pmic-arb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 937c15324513..12c0efd744c2 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -132,6 +132,8 @@ struct apid_data { * @wr_base: on v1 "core", on v2 "chnls" register base off DT. * @intr: address of the SPMI interrupt control registers. * @cnfg: address of the PMIC Arbiter configuration registers. + * @core: core register base for v2 and above only (see above) + * @core_size: core register base size * @lock: lock to synchronize accesses. * @channel: execution environment channel to use for accesses. * @irq: PMIC ARB interrupt. @@ -144,6 +146,7 @@ struct apid_data { * @apid_count: on v5 and v7: number of APIDs associated with the * particular SPMI bus instance * @mapping_table: in-memory copy of PPID -> APID mapping table. + * @mapping_table_valid:bitmap containing valid-only periphs * @domain: irq domain object for PMIC IRQ domain * @spmic: SPMI controller object * @ver_ops: version dependent operations. @@ -232,6 +235,7 @@ static inline void pmic_arb_set_rd_cmd(struct spmi_pmic_arb *pmic_arb, /** * pmic_arb_read_data: reads pmic-arb's register and copy 1..4 bytes to buf + * @pmic_arb: the SPMI PMIC arbiter * @bc: byte count -1. range: 0..3 * @reg: register's address * @buf: output parameter, length must be bc + 1 @@ -246,6 +250,7 @@ pmic_arb_read_data(struct spmi_pmic_arb *pmic_arb, u8 *buf, u32 reg, u8 bc) /** * pmic_arb_write_data: write 1..4 bytes from buf to pmic-arb's register + * @pmic_arb: the SPMI PMIC arbiter * @bc: byte-count -1. range: 0..3. * @reg: register's address. * @buf: buffer to write. length must be bc + 1. From 8bc03763e15b6783fdf149677d89a84e935aea8c Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 7 May 2024 14:07:46 -0700 Subject: [PATCH 315/319] spmi: pmic-arb: Make the APID init a version operation Rather than using conditionals in probe function, add the APID init as a version specific operation. Due to v7, which supports multiple buses, pass on the bus index to be used for sorting out the apid base and count. Reviewed-by: Neil Armstrong Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20240417-spmi-multi-master-support-v10-4-5bc6d322e266@linaro.org Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-10-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-pmic-arb.c | 146 +++++++++++++++++------------------ 1 file changed, 70 insertions(+), 76 deletions(-) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 12c0efd744c2..fe45d62a42e2 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -186,6 +186,7 @@ struct spmi_pmic_arb { * struct pmic_arb_ver_ops - version dependent functionality. * * @ver_str: version string. + * @init_apid: finds the apid base and count * @ppid_to_apid: finds the apid for a given ppid. * @non_data_cmd: on v1 issues an spmi non-data command. * on v2 no HW support, returns -EOPNOTSUPP. @@ -205,6 +206,7 @@ struct spmi_pmic_arb { */ struct pmic_arb_ver_ops { const char *ver_str; + int (*init_apid)(struct spmi_pmic_arb *pmic_arb); int (*ppid_to_apid)(struct spmi_pmic_arb *pmic_arb, u16 ppid); /* spmi commands (read_cmd, write_cmd, cmd) functionality */ int (*offset)(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, @@ -947,6 +949,32 @@ static int qpnpint_irq_domain_alloc(struct irq_domain *domain, return 0; } +static int pmic_arb_init_apid_min_max(struct spmi_pmic_arb *pmic_arb) +{ + /* + * Initialize max_apid/min_apid to the opposite bounds, during + * the irq domain translation, we are sure to update these + */ + pmic_arb->max_apid = 0; + pmic_arb->min_apid = pmic_arb->max_periphs - 1; + + return 0; +} + +static int pmic_arb_init_apid_v1(struct spmi_pmic_arb *pmic_arb) +{ + u32 *mapping_table; + + mapping_table = devm_kcalloc(&pmic_arb->spmic->dev, pmic_arb->max_periphs, + sizeof(*mapping_table), GFP_KERNEL); + if (!mapping_table) + return -ENOMEM; + + pmic_arb->mapping_table = mapping_table; + + return pmic_arb_init_apid_min_max(pmic_arb); +} + static int pmic_arb_ppid_to_apid_v1(struct spmi_pmic_arb *pmic_arb, u16 ppid) { u32 *mapping_table = pmic_arb->mapping_table; @@ -1149,6 +1177,34 @@ static int pmic_arb_offset_v2(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, return 0x1000 * pmic_arb->ee + 0x8000 * apid; } +static int pmic_arb_init_apid_v5(struct spmi_pmic_arb *pmic_arb) +{ + int ret; + + pmic_arb->base_apid = 0; + pmic_arb->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & + PMIC_ARB_FEATURES_PERIPH_MASK; + + if (pmic_arb->base_apid + pmic_arb->apid_count > pmic_arb->max_periphs) { + dev_err(&pmic_arb->spmic->dev, "Unsupported APID count %d detected\n", + pmic_arb->base_apid + pmic_arb->apid_count); + return -EINVAL; + } + + ret = pmic_arb_init_apid_min_max(pmic_arb); + if (ret) + return ret; + + ret = pmic_arb_read_apid_map_v5(pmic_arb); + if (ret) { + dev_err(&pmic_arb->spmic->dev, "could not read APID->PPID mapping table, rc= %d\n", + ret); + return ret; + } + + return 0; +} + /* * v5 offset per ee and per apid for observer channels and per apid for * read/write channels. @@ -1363,6 +1419,7 @@ pmic_arb_apid_owner_v7(struct spmi_pmic_arb *pmic_arb, u16 n) static const struct pmic_arb_ver_ops pmic_arb_v1 = { .ver_str = "v1", + .init_apid = pmic_arb_init_apid_v1, .ppid_to_apid = pmic_arb_ppid_to_apid_v1, .non_data_cmd = pmic_arb_non_data_cmd_v1, .offset = pmic_arb_offset_v1, @@ -1377,6 +1434,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v1 = { static const struct pmic_arb_ver_ops pmic_arb_v2 = { .ver_str = "v2", + .init_apid = pmic_arb_init_apid_v1, .ppid_to_apid = pmic_arb_ppid_to_apid_v2, .non_data_cmd = pmic_arb_non_data_cmd_v2, .offset = pmic_arb_offset_v2, @@ -1391,6 +1449,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v2 = { static const struct pmic_arb_ver_ops pmic_arb_v3 = { .ver_str = "v3", + .init_apid = pmic_arb_init_apid_v1, .ppid_to_apid = pmic_arb_ppid_to_apid_v2, .non_data_cmd = pmic_arb_non_data_cmd_v2, .offset = pmic_arb_offset_v2, @@ -1405,6 +1464,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v3 = { static const struct pmic_arb_ver_ops pmic_arb_v5 = { .ver_str = "v5", + .init_apid = pmic_arb_init_apid_v5, .ppid_to_apid = pmic_arb_ppid_to_apid_v5, .non_data_cmd = pmic_arb_non_data_cmd_v2, .offset = pmic_arb_offset_v5, @@ -1419,6 +1479,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v5 = { static const struct pmic_arb_ver_ops pmic_arb_v7 = { .ver_str = "v7", + .init_apid = pmic_arb_init_apid_v5, .ppid_to_apid = pmic_arb_ppid_to_apid_v5, .non_data_cmd = pmic_arb_non_data_cmd_v2, .offset = pmic_arb_offset_v7, @@ -1444,7 +1505,6 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) struct spmi_controller *ctrl; struct resource *res; void __iomem *core; - u32 *mapping_table; u32 channel, ee, hw_ver; int err; @@ -1472,12 +1532,6 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) pmic_arb->core_size = resource_size(res); - pmic_arb->ppid_to_apid = devm_kcalloc(&ctrl->dev, PMIC_ARB_MAX_PPID, - sizeof(*pmic_arb->ppid_to_apid), - GFP_KERNEL); - if (!pmic_arb->ppid_to_apid) - return -ENOMEM; - hw_ver = readl_relaxed(core + PMIC_ARB_VERSION); if (hw_ver < PMIC_ARB_VERSION_V2_MIN) { @@ -1511,59 +1565,18 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) return -ENOMEM; } - pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS; - - if (hw_ver >= PMIC_ARB_VERSION_V7_MIN) { - pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS_V7; - /* Optional property for v7: */ - of_property_read_u32(pdev->dev.of_node, "qcom,bus-id", - &pmic_arb->bus_instance); - if (pmic_arb->bus_instance > 1) { - dev_err(&pdev->dev, "invalid bus instance (%u) specified\n", - pmic_arb->bus_instance); - return -EINVAL; - } - - if (pmic_arb->bus_instance == 0) { - pmic_arb->base_apid = 0; - pmic_arb->apid_count = - readl_relaxed(core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; - } else { - pmic_arb->base_apid = - readl_relaxed(core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; - pmic_arb->apid_count = - readl_relaxed(core + PMIC_ARB_FEATURES1) & - PMIC_ARB_FEATURES_PERIPH_MASK; - } - - if (pmic_arb->base_apid + pmic_arb->apid_count > pmic_arb->max_periphs) { - dev_err(&pdev->dev, "Unsupported APID count %d detected\n", - pmic_arb->base_apid + pmic_arb->apid_count); - return -EINVAL; - } - } else if (hw_ver >= PMIC_ARB_VERSION_V5_MIN) { - pmic_arb->base_apid = 0; - pmic_arb->apid_count = readl_relaxed(core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; - - if (pmic_arb->apid_count > pmic_arb->max_periphs) { - dev_err(&pdev->dev, "Unsupported APID count %d detected\n", - pmic_arb->apid_count); - return -EINVAL; - } - } - - pmic_arb->apid_data = devm_kcalloc(&ctrl->dev, pmic_arb->max_periphs, - sizeof(*pmic_arb->apid_data), - GFP_KERNEL); - if (!pmic_arb->apid_data) - return -ENOMEM; - dev_info(&ctrl->dev, "PMIC arbiter version %s (0x%x)\n", pmic_arb->ver_ops->ver_str, hw_ver); + if (hw_ver < PMIC_ARB_VERSION_V7_MIN) + pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS; + else + pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS_V7; + + err = pmic_arb->ver_ops->init_apid(pmic_arb); + if (err) + return err; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr"); pmic_arb->intr = devm_ioremap_resource(&ctrl->dev, res); if (IS_ERR(pmic_arb->intr)) @@ -1604,16 +1617,6 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) } pmic_arb->ee = ee; - mapping_table = devm_kcalloc(&ctrl->dev, pmic_arb->max_periphs, - sizeof(*mapping_table), GFP_KERNEL); - if (!mapping_table) - return -ENOMEM; - - pmic_arb->mapping_table = mapping_table; - /* Initialize max_apid/min_apid to the opposite bounds, during - * the irq domain translation, we are sure to update these */ - pmic_arb->max_apid = 0; - pmic_arb->min_apid = pmic_arb->max_periphs - 1; platform_set_drvdata(pdev, ctrl); raw_spin_lock_init(&pmic_arb->lock); @@ -1622,15 +1625,6 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) ctrl->read_cmd = pmic_arb_read_cmd; ctrl->write_cmd = pmic_arb_write_cmd; - if (hw_ver >= PMIC_ARB_VERSION_V5_MIN) { - err = pmic_arb_read_apid_map_v5(pmic_arb); - if (err) { - dev_err(&pdev->dev, "could not read APID->PPID mapping table, rc= %d\n", - err); - return err; - } - } - dev_dbg(&pdev->dev, "adding irq domain\n"); pmic_arb->domain = irq_domain_add_tree(pdev->dev.of_node, &pmic_arb_irq_domain_ops, pmic_arb); From 994ac434b7261ac2d89c65c7fa75fa697bfd7054 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 7 May 2024 14:07:47 -0700 Subject: [PATCH 316/319] spmi: pmic-arb: Make core resources acquiring a version operation Rather than setting up the core, obsrv and chnls in probe by using version specific conditionals, add a dedicated "get_core_resources" version specific op and move the acquiring in there. Since there are no current users of the second bus yet, drop the comment about why devm_platform_ioremap_resource can't be used in case of "core", as it is not applicable anymore. Don't switch to devm_platform_ioremap_resource though as we need to keep track of core size. Reviewed-by: Neil Armstrong Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20240417-spmi-multi-master-support-v10-5-5bc6d322e266@linaro.org Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-11-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-pmic-arb.c | 114 ++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index fe45d62a42e2..db33ca0e11b8 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -186,6 +186,7 @@ struct spmi_pmic_arb { * struct pmic_arb_ver_ops - version dependent functionality. * * @ver_str: version string. + * @get_core_resources: initializes the core, observer and channels * @init_apid: finds the apid base and count * @ppid_to_apid: finds the apid for a given ppid. * @non_data_cmd: on v1 issues an spmi non-data command. @@ -206,6 +207,7 @@ struct spmi_pmic_arb { */ struct pmic_arb_ver_ops { const char *ver_str; + int (*get_core_resources)(struct platform_device *pdev, void __iomem *core); int (*init_apid)(struct spmi_pmic_arb *pmic_arb); int (*ppid_to_apid)(struct spmi_pmic_arb *pmic_arb, u16 ppid); /* spmi commands (read_cmd, write_cmd, cmd) functionality */ @@ -961,6 +963,19 @@ static int pmic_arb_init_apid_min_max(struct spmi_pmic_arb *pmic_arb) return 0; } +static int pmic_arb_get_core_resources_v1(struct platform_device *pdev, + void __iomem *core) +{ + struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); + + pmic_arb->wr_base = core; + pmic_arb->rd_base = core; + + pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS; + + return 0; +} + static int pmic_arb_init_apid_v1(struct spmi_pmic_arb *pmic_arb) { u32 *mapping_table; @@ -1062,6 +1077,33 @@ static u16 pmic_arb_find_apid(struct spmi_pmic_arb *pmic_arb, u16 ppid) return apid; } +static int pmic_arb_get_obsrvr_chnls_v2(struct platform_device *pdev) +{ + struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); + + pmic_arb->rd_base = devm_platform_ioremap_resource_byname(pdev, "obsrvr"); + if (IS_ERR(pmic_arb->rd_base)) + return PTR_ERR(pmic_arb->rd_base); + + pmic_arb->wr_base = devm_platform_ioremap_resource_byname(pdev, "chnls"); + if (IS_ERR(pmic_arb->wr_base)) + return PTR_ERR(pmic_arb->wr_base); + + return 0; +} + +static int pmic_arb_get_core_resources_v2(struct platform_device *pdev, + void __iomem *core) +{ + struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); + + pmic_arb->core = core; + + pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS; + + return pmic_arb_get_obsrvr_chnls_v2(pdev); +} + static int pmic_arb_ppid_to_apid_v2(struct spmi_pmic_arb *pmic_arb, u16 ppid) { u16 apid_valid; @@ -1239,6 +1281,18 @@ static int pmic_arb_offset_v5(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, return offset; } +static int pmic_arb_get_core_resources_v7(struct platform_device *pdev, + void __iomem *core) +{ + struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); + + pmic_arb->core = core; + + pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS_V7; + + return pmic_arb_get_obsrvr_chnls_v2(pdev); +} + /* * v7 offset per ee and per apid for observer channels and per apid for * read/write channels. @@ -1419,6 +1473,7 @@ pmic_arb_apid_owner_v7(struct spmi_pmic_arb *pmic_arb, u16 n) static const struct pmic_arb_ver_ops pmic_arb_v1 = { .ver_str = "v1", + .get_core_resources = pmic_arb_get_core_resources_v1, .init_apid = pmic_arb_init_apid_v1, .ppid_to_apid = pmic_arb_ppid_to_apid_v1, .non_data_cmd = pmic_arb_non_data_cmd_v1, @@ -1434,6 +1489,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v1 = { static const struct pmic_arb_ver_ops pmic_arb_v2 = { .ver_str = "v2", + .get_core_resources = pmic_arb_get_core_resources_v2, .init_apid = pmic_arb_init_apid_v1, .ppid_to_apid = pmic_arb_ppid_to_apid_v2, .non_data_cmd = pmic_arb_non_data_cmd_v2, @@ -1449,6 +1505,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v2 = { static const struct pmic_arb_ver_ops pmic_arb_v3 = { .ver_str = "v3", + .get_core_resources = pmic_arb_get_core_resources_v2, .init_apid = pmic_arb_init_apid_v1, .ppid_to_apid = pmic_arb_ppid_to_apid_v2, .non_data_cmd = pmic_arb_non_data_cmd_v2, @@ -1464,6 +1521,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v3 = { static const struct pmic_arb_ver_ops pmic_arb_v5 = { .ver_str = "v5", + .get_core_resources = pmic_arb_get_core_resources_v2, .init_apid = pmic_arb_init_apid_v5, .ppid_to_apid = pmic_arb_ppid_to_apid_v5, .non_data_cmd = pmic_arb_non_data_cmd_v2, @@ -1479,6 +1537,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v5 = { static const struct pmic_arb_ver_ops pmic_arb_v7 = { .ver_str = "v7", + .get_core_resources = pmic_arb_get_core_resources_v7, .init_apid = pmic_arb_init_apid_v5, .ppid_to_apid = pmic_arb_ppid_to_apid_v5, .non_data_cmd = pmic_arb_non_data_cmd_v2, @@ -1515,16 +1574,6 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) pmic_arb = spmi_controller_get_drvdata(ctrl); pmic_arb->spmic = ctrl; - /* - * Please don't replace this with devm_platform_ioremap_resource() or - * devm_ioremap_resource(). These both result in a call to - * devm_request_mem_region() which prevents multiple mappings of this - * register address range. SoCs with PMIC arbiter v7 may define two - * arbiter devices, for the two physical SPMI interfaces, which share - * some register address ranges (i.e. "core", "obsrvr", and "chnls"). - * Ensure that both devices probe successfully by calling devm_ioremap() - * which does not result in a devm_request_mem_region() call. - */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); core = devm_ioremap(&ctrl->dev, res->start, resource_size(res)); if (!core) @@ -1534,44 +1583,23 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) hw_ver = readl_relaxed(core + PMIC_ARB_VERSION); - if (hw_ver < PMIC_ARB_VERSION_V2_MIN) { + if (hw_ver < PMIC_ARB_VERSION_V2_MIN) pmic_arb->ver_ops = &pmic_arb_v1; - pmic_arb->wr_base = core; - pmic_arb->rd_base = core; - } else { - pmic_arb->core = core; - - if (hw_ver < PMIC_ARB_VERSION_V3_MIN) - pmic_arb->ver_ops = &pmic_arb_v2; - else if (hw_ver < PMIC_ARB_VERSION_V5_MIN) - pmic_arb->ver_ops = &pmic_arb_v3; - else if (hw_ver < PMIC_ARB_VERSION_V7_MIN) - pmic_arb->ver_ops = &pmic_arb_v5; - else - pmic_arb->ver_ops = &pmic_arb_v7; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "obsrvr"); - pmic_arb->rd_base = devm_ioremap(&ctrl->dev, res->start, - resource_size(res)); - if (!pmic_arb->rd_base) - return -ENOMEM; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "chnls"); - pmic_arb->wr_base = devm_ioremap(&ctrl->dev, res->start, - resource_size(res)); - if (!pmic_arb->wr_base) - return -ENOMEM; - } + else if (hw_ver < PMIC_ARB_VERSION_V3_MIN) + pmic_arb->ver_ops = &pmic_arb_v2; + else if (hw_ver < PMIC_ARB_VERSION_V5_MIN) + pmic_arb->ver_ops = &pmic_arb_v3; + else if (hw_ver < PMIC_ARB_VERSION_V7_MIN) + pmic_arb->ver_ops = &pmic_arb_v5; + else + pmic_arb->ver_ops = &pmic_arb_v7; dev_info(&ctrl->dev, "PMIC arbiter version %s (0x%x)\n", pmic_arb->ver_ops->ver_str, hw_ver); - if (hw_ver < PMIC_ARB_VERSION_V7_MIN) - pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS; - else - pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS_V7; + err = pmic_arb->ver_ops->get_core_resources(pdev, core); + if (err) + return err; err = pmic_arb->ver_ops->init_apid(pmic_arb); if (err) From 02922ccbb330128a5cf70f28e9d06b2eb0594796 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 7 May 2024 14:07:48 -0700 Subject: [PATCH 317/319] spmi: pmic-arb: Register controller for bus instead of arbiter Introduce the bus object in order to decouple the resources that are bus specific from the arbiter. This way the SPMI controller is registered with the generic framework at a bus level rather than arbiter. This is needed in order to prepare for multi bus support. Reviewed-by: Neil Armstrong Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20240417-spmi-multi-master-support-v10-6-5bc6d322e266@linaro.org Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/53c31752-c8a2-4098-837e-2f84f03c8748@moroto.mountain Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-12-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-pmic-arb.c | 659 ++++++++++++++++++++--------------- 1 file changed, 374 insertions(+), 285 deletions(-) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index db33ca0e11b8..c3dd5d2aa244 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -125,61 +126,72 @@ struct apid_data { u8 irq_ee; }; +struct spmi_pmic_arb; + /** - * struct spmi_pmic_arb - SPMI PMIC Arbiter object + * struct spmi_pmic_arb_bus - SPMI PMIC Arbiter Bus object * - * @rd_base: on v1 "core", on v2 "observer" register base off DT. - * @wr_base: on v1 "core", on v2 "chnls" register base off DT. + * @pmic_arb: the SPMI PMIC Arbiter the bus belongs to. + * @domain: irq domain object for PMIC IRQ domain * @intr: address of the SPMI interrupt control registers. * @cnfg: address of the PMIC Arbiter configuration registers. - * @core: core register base for v2 and above only (see above) - * @core_size: core register base size + * @spmic: spmi controller registered for this bus * @lock: lock to synchronize accesses. - * @channel: execution environment channel to use for accesses. - * @irq: PMIC ARB interrupt. - * @ee: the current Execution Environment - * @bus_instance: on v7: 0 = primary SPMI bus, 1 = secondary SPMI bus - * @min_apid: minimum APID (used for bounding IRQ search) - * @max_apid: maximum APID * @base_apid: on v7: minimum APID associated with the particular SPMI * bus instance * @apid_count: on v5 and v7: number of APIDs associated with the * particular SPMI bus instance * @mapping_table: in-memory copy of PPID -> APID mapping table. * @mapping_table_valid:bitmap containing valid-only periphs - * @domain: irq domain object for PMIC IRQ domain - * @spmic: SPMI controller object - * @ver_ops: version dependent operations. * @ppid_to_apid: in-memory copy of PPID -> APID mapping table. * @last_apid: Highest value APID in use * @apid_data: Table of data for all APIDs - * @max_periphs: Number of elements in apid_data[] + * @min_apid: minimum APID (used for bounding IRQ search) + * @max_apid: maximum APID + * @irq: PMIC ARB interrupt. */ -struct spmi_pmic_arb { - void __iomem *rd_base; - void __iomem *wr_base; +struct spmi_pmic_arb_bus { + struct spmi_pmic_arb *pmic_arb; + struct irq_domain *domain; void __iomem *intr; void __iomem *cnfg; - void __iomem *core; - resource_size_t core_size; + struct spmi_controller *spmic; raw_spinlock_t lock; - u8 channel; - int irq; - u8 ee; - u32 bus_instance; - u16 min_apid; - u16 max_apid; u16 base_apid; int apid_count; u32 *mapping_table; DECLARE_BITMAP(mapping_table_valid, PMIC_ARB_MAX_PERIPHS); - struct irq_domain *domain; - struct spmi_controller *spmic; - const struct pmic_arb_ver_ops *ver_ops; u16 *ppid_to_apid; u16 last_apid; struct apid_data *apid_data; + u16 min_apid; + u16 max_apid; + int irq; +}; + +/** + * struct spmi_pmic_arb - SPMI PMIC Arbiter object + * + * @rd_base: on v1 "core", on v2 "observer" register base off DT. + * @wr_base: on v1 "core", on v2 "chnls" register base off DT. + * @core: core register base for v2 and above only (see above) + * @core_size: core register base size + * @channel: execution environment channel to use for accesses. + * @ee: the current Execution Environment + * @ver_ops: version dependent operations. + * @max_periphs: Number of elements in apid_data[] + * @bus: per arbiter bus instance + */ +struct spmi_pmic_arb { + void __iomem *rd_base; + void __iomem *wr_base; + void __iomem *core; + resource_size_t core_size; + u8 channel; + u8 ee; + const struct pmic_arb_ver_ops *ver_ops; int max_periphs; + struct spmi_pmic_arb_bus *bus; }; /** @@ -208,21 +220,21 @@ struct spmi_pmic_arb { struct pmic_arb_ver_ops { const char *ver_str; int (*get_core_resources)(struct platform_device *pdev, void __iomem *core); - int (*init_apid)(struct spmi_pmic_arb *pmic_arb); - int (*ppid_to_apid)(struct spmi_pmic_arb *pmic_arb, u16 ppid); + int (*init_apid)(struct spmi_pmic_arb_bus *bus); + int (*ppid_to_apid)(struct spmi_pmic_arb_bus *bus, u16 ppid); /* spmi commands (read_cmd, write_cmd, cmd) functionality */ - int (*offset)(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, - enum pmic_arb_channel ch_type); + int (*offset)(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, + enum pmic_arb_channel ch_type); u32 (*fmt_cmd)(u8 opc, u8 sid, u16 addr, u8 bc); int (*non_data_cmd)(struct spmi_controller *ctrl, u8 opc, u8 sid); /* Interrupts controller functionality (offset of PIC registers) */ - void __iomem *(*owner_acc_status)(struct spmi_pmic_arb *pmic_arb, u8 m, + void __iomem *(*owner_acc_status)(struct spmi_pmic_arb_bus *bus, u8 m, u16 n); - void __iomem *(*acc_enable)(struct spmi_pmic_arb *pmic_arb, u16 n); - void __iomem *(*irq_status)(struct spmi_pmic_arb *pmic_arb, u16 n); - void __iomem *(*irq_clear)(struct spmi_pmic_arb *pmic_arb, u16 n); + void __iomem *(*acc_enable)(struct spmi_pmic_arb_bus *bus, u16 n); + void __iomem *(*irq_status)(struct spmi_pmic_arb_bus *bus, u16 n); + void __iomem *(*irq_clear)(struct spmi_pmic_arb_bus *bus, u16 n); u32 (*apid_map_offset)(u16 n); - void __iomem *(*apid_owner)(struct spmi_pmic_arb *pmic_arb, u16 n); + void __iomem *(*apid_owner)(struct spmi_pmic_arb_bus *bus, u16 n); }; static inline void pmic_arb_base_write(struct spmi_pmic_arb *pmic_arb, @@ -272,13 +284,14 @@ static int pmic_arb_wait_for_done(struct spmi_controller *ctrl, void __iomem *base, u8 sid, u16 addr, enum pmic_arb_channel ch_type) { - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb_bus *bus = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u32 status = 0; u32 timeout = PMIC_ARB_TIMEOUT_US; u32 offset; int rc; - rc = pmic_arb->ver_ops->offset(pmic_arb, sid, addr, ch_type); + rc = pmic_arb->ver_ops->offset(bus, sid, addr, ch_type); if (rc < 0) return rc; @@ -321,24 +334,25 @@ static int pmic_arb_wait_for_done(struct spmi_controller *ctrl, static int pmic_arb_non_data_cmd_v1(struct spmi_controller *ctrl, u8 opc, u8 sid) { - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb_bus *bus = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; unsigned long flags; u32 cmd; int rc; u32 offset; - rc = pmic_arb->ver_ops->offset(pmic_arb, sid, 0, PMIC_ARB_CHANNEL_RW); + rc = pmic_arb->ver_ops->offset(bus, sid, 0, PMIC_ARB_CHANNEL_RW); if (rc < 0) return rc; offset = rc; cmd = ((opc | 0x40) << 27) | ((sid & 0xf) << 20); - raw_spin_lock_irqsave(&pmic_arb->lock, flags); + raw_spin_lock_irqsave(&bus->lock, flags); pmic_arb_base_write(pmic_arb, offset + PMIC_ARB_CMD, cmd); rc = pmic_arb_wait_for_done(ctrl, pmic_arb->wr_base, sid, 0, PMIC_ARB_CHANNEL_RW); - raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); + raw_spin_unlock_irqrestore(&bus->lock, flags); return rc; } @@ -363,20 +377,21 @@ static int pmic_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) return pmic_arb->ver_ops->non_data_cmd(ctrl, opc, sid); } -static int pmic_arb_fmt_read_cmd(struct spmi_pmic_arb *pmic_arb, u8 opc, u8 sid, +static int pmic_arb_fmt_read_cmd(struct spmi_pmic_arb_bus *bus, u8 opc, u8 sid, u16 addr, size_t len, u32 *cmd, u32 *offset) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u8 bc = len - 1; int rc; - rc = pmic_arb->ver_ops->offset(pmic_arb, sid, addr, + rc = pmic_arb->ver_ops->offset(bus, sid, addr, PMIC_ARB_CHANNEL_OBS); if (rc < 0) return rc; *offset = rc; if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { - dev_err(&pmic_arb->spmic->dev, "pmic-arb supports 1..%d bytes per trans, but:%zu requested", + dev_err(&bus->spmic->dev, "pmic-arb supports 1..%d bytes per trans, but:%zu requested", PMIC_ARB_MAX_TRANS_BYTES, len); return -EINVAL; } @@ -400,7 +415,8 @@ static int pmic_arb_read_cmd_unlocked(struct spmi_controller *ctrl, u32 cmd, u32 offset, u8 sid, u16 addr, u8 *buf, size_t len) { - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb_bus *bus = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u8 bc = len - 1; int rc; @@ -422,38 +438,39 @@ static int pmic_arb_read_cmd_unlocked(struct spmi_controller *ctrl, u32 cmd, static int pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, u8 *buf, size_t len) { - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb_bus *bus = spmi_controller_get_drvdata(ctrl); unsigned long flags; u32 cmd, offset; int rc; - rc = pmic_arb_fmt_read_cmd(pmic_arb, opc, sid, addr, len, &cmd, + rc = pmic_arb_fmt_read_cmd(bus, opc, sid, addr, len, &cmd, &offset); if (rc) return rc; - raw_spin_lock_irqsave(&pmic_arb->lock, flags); + raw_spin_lock_irqsave(&bus->lock, flags); rc = pmic_arb_read_cmd_unlocked(ctrl, cmd, offset, sid, addr, buf, len); - raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); + raw_spin_unlock_irqrestore(&bus->lock, flags); return rc; } -static int pmic_arb_fmt_write_cmd(struct spmi_pmic_arb *pmic_arb, u8 opc, +static int pmic_arb_fmt_write_cmd(struct spmi_pmic_arb_bus *bus, u8 opc, u8 sid, u16 addr, size_t len, u32 *cmd, u32 *offset) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u8 bc = len - 1; int rc; - rc = pmic_arb->ver_ops->offset(pmic_arb, sid, addr, + rc = pmic_arb->ver_ops->offset(bus, sid, addr, PMIC_ARB_CHANNEL_RW); if (rc < 0) return rc; *offset = rc; if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { - dev_err(&pmic_arb->spmic->dev, "pmic-arb supports 1..%d bytes per trans, but:%zu requested", + dev_err(&bus->spmic->dev, "pmic-arb supports 1..%d bytes per trans, but:%zu requested", PMIC_ARB_MAX_TRANS_BYTES, len); return -EINVAL; } @@ -479,7 +496,8 @@ static int pmic_arb_write_cmd_unlocked(struct spmi_controller *ctrl, u32 cmd, u32 offset, u8 sid, u16 addr, const u8 *buf, size_t len) { - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb_bus *bus = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u8 bc = len - 1; /* Write data to FIFOs */ @@ -498,20 +516,20 @@ static int pmic_arb_write_cmd_unlocked(struct spmi_controller *ctrl, u32 cmd, static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, const u8 *buf, size_t len) { - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb_bus *bus = spmi_controller_get_drvdata(ctrl); unsigned long flags; u32 cmd, offset; int rc; - rc = pmic_arb_fmt_write_cmd(pmic_arb, opc, sid, addr, len, &cmd, + rc = pmic_arb_fmt_write_cmd(bus, opc, sid, addr, len, &cmd, &offset); if (rc) return rc; - raw_spin_lock_irqsave(&pmic_arb->lock, flags); + raw_spin_lock_irqsave(&bus->lock, flags); rc = pmic_arb_write_cmd_unlocked(ctrl, cmd, offset, sid, addr, buf, len); - raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); + raw_spin_unlock_irqrestore(&bus->lock, flags); return rc; } @@ -519,23 +537,23 @@ static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, static int pmic_arb_masked_write(struct spmi_controller *ctrl, u8 sid, u16 addr, const u8 *buf, const u8 *mask, size_t len) { - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); + struct spmi_pmic_arb_bus *bus = spmi_controller_get_drvdata(ctrl); u32 read_cmd, read_offset, write_cmd, write_offset; u8 temp[PMIC_ARB_MAX_TRANS_BYTES]; unsigned long flags; int rc, i; - rc = pmic_arb_fmt_read_cmd(pmic_arb, SPMI_CMD_EXT_READL, sid, addr, len, + rc = pmic_arb_fmt_read_cmd(bus, SPMI_CMD_EXT_READL, sid, addr, len, &read_cmd, &read_offset); if (rc) return rc; - rc = pmic_arb_fmt_write_cmd(pmic_arb, SPMI_CMD_EXT_WRITEL, sid, addr, + rc = pmic_arb_fmt_write_cmd(bus, SPMI_CMD_EXT_WRITEL, sid, addr, len, &write_cmd, &write_offset); if (rc) return rc; - raw_spin_lock_irqsave(&pmic_arb->lock, flags); + raw_spin_lock_irqsave(&bus->lock, flags); rc = pmic_arb_read_cmd_unlocked(ctrl, read_cmd, read_offset, sid, addr, temp, len); if (rc) @@ -547,7 +565,7 @@ static int pmic_arb_masked_write(struct spmi_controller *ctrl, u8 sid, u16 addr, rc = pmic_arb_write_cmd_unlocked(ctrl, write_cmd, write_offset, sid, addr, temp, len); done: - raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); + raw_spin_unlock_irqrestore(&bus->lock, flags); return rc; } @@ -573,25 +591,25 @@ struct spmi_pmic_arb_qpnpint_type { static void qpnpint_spmi_write(struct irq_data *d, u8 reg, void *buf, size_t len) { - struct spmi_pmic_arb *pmic_arb = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb_bus *bus = irq_data_get_irq_chip_data(d); u8 sid = hwirq_to_sid(d->hwirq); u8 per = hwirq_to_per(d->hwirq); - if (pmic_arb_write_cmd(pmic_arb->spmic, SPMI_CMD_EXT_WRITEL, sid, + if (pmic_arb_write_cmd(bus->spmic, SPMI_CMD_EXT_WRITEL, sid, (per << 8) + reg, buf, len)) - dev_err_ratelimited(&pmic_arb->spmic->dev, "failed irqchip transaction on %x\n", + dev_err_ratelimited(&bus->spmic->dev, "failed irqchip transaction on %x\n", d->irq); } static void qpnpint_spmi_read(struct irq_data *d, u8 reg, void *buf, size_t len) { - struct spmi_pmic_arb *pmic_arb = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb_bus *bus = irq_data_get_irq_chip_data(d); u8 sid = hwirq_to_sid(d->hwirq); u8 per = hwirq_to_per(d->hwirq); - if (pmic_arb_read_cmd(pmic_arb->spmic, SPMI_CMD_EXT_READL, sid, + if (pmic_arb_read_cmd(bus->spmic, SPMI_CMD_EXT_READL, sid, (per << 8) + reg, buf, len)) - dev_err_ratelimited(&pmic_arb->spmic->dev, "failed irqchip transaction on %x\n", + dev_err_ratelimited(&bus->spmic->dev, "failed irqchip transaction on %x\n", d->irq); } @@ -599,47 +617,49 @@ static int qpnpint_spmi_masked_write(struct irq_data *d, u8 reg, const void *buf, const void *mask, size_t len) { - struct spmi_pmic_arb *pmic_arb = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb_bus *bus = irq_data_get_irq_chip_data(d); u8 sid = hwirq_to_sid(d->hwirq); u8 per = hwirq_to_per(d->hwirq); int rc; - rc = pmic_arb_masked_write(pmic_arb->spmic, sid, (per << 8) + reg, buf, + rc = pmic_arb_masked_write(bus->spmic, sid, (per << 8) + reg, buf, mask, len); if (rc) - dev_err_ratelimited(&pmic_arb->spmic->dev, "failed irqchip transaction on %x rc=%d\n", + dev_err_ratelimited(&bus->spmic->dev, "failed irqchip transaction on %x rc=%d\n", d->irq, rc); return rc; } -static void cleanup_irq(struct spmi_pmic_arb *pmic_arb, u16 apid, int id) +static void cleanup_irq(struct spmi_pmic_arb_bus *bus, u16 apid, int id) { - u16 ppid = pmic_arb->apid_data[apid].ppid; + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + u16 ppid = bus->apid_data[apid].ppid; u8 sid = ppid >> 8; u8 per = ppid & 0xFF; u8 irq_mask = BIT(id); - dev_err_ratelimited(&pmic_arb->spmic->dev, "%s apid=%d sid=0x%x per=0x%x irq=%d\n", - __func__, apid, sid, per, id); - writel_relaxed(irq_mask, pmic_arb->ver_ops->irq_clear(pmic_arb, apid)); + dev_err_ratelimited(&bus->spmic->dev, "%s apid=%d sid=0x%x per=0x%x irq=%d\n", + __func__, apid, sid, per, id); + writel_relaxed(irq_mask, pmic_arb->ver_ops->irq_clear(bus, apid)); } -static int periph_interrupt(struct spmi_pmic_arb *pmic_arb, u16 apid) +static int periph_interrupt(struct spmi_pmic_arb_bus *bus, u16 apid) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; unsigned int irq; u32 status, id; int handled = 0; - u8 sid = (pmic_arb->apid_data[apid].ppid >> 8) & 0xF; - u8 per = pmic_arb->apid_data[apid].ppid & 0xFF; + u8 sid = (bus->apid_data[apid].ppid >> 8) & 0xF; + u8 per = bus->apid_data[apid].ppid & 0xFF; - status = readl_relaxed(pmic_arb->ver_ops->irq_status(pmic_arb, apid)); + status = readl_relaxed(pmic_arb->ver_ops->irq_status(bus, apid)); while (status) { id = ffs(status) - 1; status &= ~BIT(id); - irq = irq_find_mapping(pmic_arb->domain, - spec_to_hwirq(sid, per, id, apid)); + irq = irq_find_mapping(bus->domain, + spec_to_hwirq(sid, per, id, apid)); if (irq == 0) { - cleanup_irq(pmic_arb, apid, id); + cleanup_irq(bus, apid, id); continue; } generic_handle_irq(irq); @@ -651,16 +671,17 @@ static int periph_interrupt(struct spmi_pmic_arb *pmic_arb, u16 apid) static void pmic_arb_chained_irq(struct irq_desc *desc) { - struct spmi_pmic_arb *pmic_arb = irq_desc_get_handler_data(desc); + struct spmi_pmic_arb_bus *bus = irq_desc_get_handler_data(desc); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; const struct pmic_arb_ver_ops *ver_ops = pmic_arb->ver_ops; struct irq_chip *chip = irq_desc_get_chip(desc); - int first = pmic_arb->min_apid; - int last = pmic_arb->max_apid; + int first = bus->min_apid; + int last = bus->max_apid; /* * acc_offset will be non-zero for the secondary SPMI bus instance on * v7 controllers. */ - int acc_offset = pmic_arb->base_apid >> 5; + int acc_offset = bus->base_apid >> 5; u8 ee = pmic_arb->ee; u32 status, enable, handled = 0; int i, id, apid; @@ -671,7 +692,7 @@ static void pmic_arb_chained_irq(struct irq_desc *desc) chained_irq_enter(chip, desc); for (i = first >> 5; i <= last >> 5; ++i) { - status = readl_relaxed(ver_ops->owner_acc_status(pmic_arb, ee, i - acc_offset)); + status = readl_relaxed(ver_ops->owner_acc_status(bus, ee, i - acc_offset)); if (status) acc_valid = true; @@ -685,9 +706,9 @@ static void pmic_arb_chained_irq(struct irq_desc *desc) continue; } enable = readl_relaxed( - ver_ops->acc_enable(pmic_arb, apid)); + ver_ops->acc_enable(bus, apid)); if (enable & SPMI_PIC_ACC_ENABLE_BIT) - if (periph_interrupt(pmic_arb, apid) != 0) + if (periph_interrupt(bus, apid) != 0) handled++; } } @@ -696,19 +717,19 @@ static void pmic_arb_chained_irq(struct irq_desc *desc) if (!acc_valid) { for (i = first; i <= last; i++) { /* skip if APPS is not irq owner */ - if (pmic_arb->apid_data[i].irq_ee != pmic_arb->ee) + if (bus->apid_data[i].irq_ee != pmic_arb->ee) continue; irq_status = readl_relaxed( - ver_ops->irq_status(pmic_arb, i)); + ver_ops->irq_status(bus, i)); if (irq_status) { enable = readl_relaxed( - ver_ops->acc_enable(pmic_arb, i)); + ver_ops->acc_enable(bus, i)); if (enable & SPMI_PIC_ACC_ENABLE_BIT) { - dev_dbg(&pmic_arb->spmic->dev, + dev_dbg(&bus->spmic->dev, "Dispatching IRQ for apid=%d status=%x\n", i, irq_status); - if (periph_interrupt(pmic_arb, i) != 0) + if (periph_interrupt(bus, i) != 0) handled++; } } @@ -723,12 +744,13 @@ static void pmic_arb_chained_irq(struct irq_desc *desc) static void qpnpint_irq_ack(struct irq_data *d) { - struct spmi_pmic_arb *pmic_arb = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb_bus *bus = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u8 irq = hwirq_to_irq(d->hwirq); u16 apid = hwirq_to_apid(d->hwirq); u8 data; - writel_relaxed(BIT(irq), pmic_arb->ver_ops->irq_clear(pmic_arb, apid)); + writel_relaxed(BIT(irq), pmic_arb->ver_ops->irq_clear(bus, apid)); data = BIT(irq); qpnpint_spmi_write(d, QPNPINT_REG_LATCHED_CLR, &data, 1); @@ -744,14 +766,15 @@ static void qpnpint_irq_mask(struct irq_data *d) static void qpnpint_irq_unmask(struct irq_data *d) { - struct spmi_pmic_arb *pmic_arb = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb_bus *bus = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; const struct pmic_arb_ver_ops *ver_ops = pmic_arb->ver_ops; u8 irq = hwirq_to_irq(d->hwirq); u16 apid = hwirq_to_apid(d->hwirq); u8 buf[2]; writel_relaxed(SPMI_PIC_ACC_ENABLE_BIT, - ver_ops->acc_enable(pmic_arb, apid)); + ver_ops->acc_enable(bus, apid)); qpnpint_spmi_read(d, QPNPINT_REG_EN_SET, &buf[0], 1); if (!(buf[0] & BIT(irq))) { @@ -808,9 +831,9 @@ static int qpnpint_irq_set_type(struct irq_data *d, unsigned int flow_type) static int qpnpint_irq_set_wake(struct irq_data *d, unsigned int on) { - struct spmi_pmic_arb *pmic_arb = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb_bus *bus = irq_data_get_irq_chip_data(d); - return irq_set_irq_wake(pmic_arb->irq, on); + return irq_set_irq_wake(bus->irq, on); } static int qpnpint_get_irqchip_state(struct irq_data *d, @@ -832,17 +855,18 @@ static int qpnpint_get_irqchip_state(struct irq_data *d, static int qpnpint_irq_domain_activate(struct irq_domain *domain, struct irq_data *d, bool reserve) { - struct spmi_pmic_arb *pmic_arb = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb_bus *bus = irq_data_get_irq_chip_data(d); + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u16 periph = hwirq_to_per(d->hwirq); u16 apid = hwirq_to_apid(d->hwirq); u16 sid = hwirq_to_sid(d->hwirq); u16 irq = hwirq_to_irq(d->hwirq); u8 buf; - if (pmic_arb->apid_data[apid].irq_ee != pmic_arb->ee) { - dev_err(&pmic_arb->spmic->dev, "failed to xlate sid = %#x, periph = %#x, irq = %u: ee=%u but owner=%u\n", + if (bus->apid_data[apid].irq_ee != pmic_arb->ee) { + dev_err(&bus->spmic->dev, "failed to xlate sid = %#x, periph = %#x, irq = %u: ee=%u but owner=%u\n", sid, periph, irq, pmic_arb->ee, - pmic_arb->apid_data[apid].irq_ee); + bus->apid_data[apid].irq_ee); return -ENODEV; } @@ -869,15 +893,16 @@ static int qpnpint_irq_domain_translate(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type) { - struct spmi_pmic_arb *pmic_arb = d->host_data; + struct spmi_pmic_arb_bus *bus = d->host_data; + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u32 *intspec = fwspec->param; u16 apid, ppid; int rc; - dev_dbg(&pmic_arb->spmic->dev, "intspec[0] 0x%1x intspec[1] 0x%02x intspec[2] 0x%02x\n", + dev_dbg(&bus->spmic->dev, "intspec[0] 0x%1x intspec[1] 0x%02x intspec[2] 0x%02x\n", intspec[0], intspec[1], intspec[2]); - if (irq_domain_get_of_node(d) != pmic_arb->spmic->dev.of_node) + if (irq_domain_get_of_node(d) != bus->spmic->dev.of_node) return -EINVAL; if (fwspec->param_count != 4) return -EINVAL; @@ -885,37 +910,37 @@ static int qpnpint_irq_domain_translate(struct irq_domain *d, return -EINVAL; ppid = intspec[0] << 8 | intspec[1]; - rc = pmic_arb->ver_ops->ppid_to_apid(pmic_arb, ppid); + rc = pmic_arb->ver_ops->ppid_to_apid(bus, ppid); if (rc < 0) { - dev_err(&pmic_arb->spmic->dev, "failed to xlate sid = %#x, periph = %#x, irq = %u rc = %d\n", - intspec[0], intspec[1], intspec[2], rc); + dev_err(&bus->spmic->dev, "failed to xlate sid = %#x, periph = %#x, irq = %u rc = %d\n", + intspec[0], intspec[1], intspec[2], rc); return rc; } apid = rc; /* Keep track of {max,min}_apid for bounding search during interrupt */ - if (apid > pmic_arb->max_apid) - pmic_arb->max_apid = apid; - if (apid < pmic_arb->min_apid) - pmic_arb->min_apid = apid; + if (apid > bus->max_apid) + bus->max_apid = apid; + if (apid < bus->min_apid) + bus->min_apid = apid; *out_hwirq = spec_to_hwirq(intspec[0], intspec[1], intspec[2], apid); *out_type = intspec[3] & IRQ_TYPE_SENSE_MASK; - dev_dbg(&pmic_arb->spmic->dev, "out_hwirq = %lu\n", *out_hwirq); + dev_dbg(&bus->spmic->dev, "out_hwirq = %lu\n", *out_hwirq); return 0; } static struct lock_class_key qpnpint_irq_lock_class, qpnpint_irq_request_class; -static void qpnpint_irq_domain_map(struct spmi_pmic_arb *pmic_arb, +static void qpnpint_irq_domain_map(struct spmi_pmic_arb_bus *bus, struct irq_domain *domain, unsigned int virq, irq_hw_number_t hwirq, unsigned int type) { irq_flow_handler_t handler; - dev_dbg(&pmic_arb->spmic->dev, "virq = %u, hwirq = %lu, type = %u\n", + dev_dbg(&bus->spmic->dev, "virq = %u, hwirq = %lu, type = %u\n", virq, hwirq, type); if (type & IRQ_TYPE_EDGE_BOTH) @@ -926,7 +951,7 @@ static void qpnpint_irq_domain_map(struct spmi_pmic_arb *pmic_arb, irq_set_lockdep_class(virq, &qpnpint_irq_lock_class, &qpnpint_irq_request_class); - irq_domain_set_info(domain, virq, hwirq, &pmic_arb_irqchip, pmic_arb, + irq_domain_set_info(domain, virq, hwirq, &pmic_arb_irqchip, bus, handler, NULL, NULL); } @@ -934,7 +959,7 @@ static int qpnpint_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *data) { - struct spmi_pmic_arb *pmic_arb = domain->host_data; + struct spmi_pmic_arb_bus *bus = domain->host_data; struct irq_fwspec *fwspec = data; irq_hw_number_t hwirq; unsigned int type; @@ -945,20 +970,22 @@ static int qpnpint_irq_domain_alloc(struct irq_domain *domain, return ret; for (i = 0; i < nr_irqs; i++) - qpnpint_irq_domain_map(pmic_arb, domain, virq + i, hwirq + i, + qpnpint_irq_domain_map(bus, domain, virq + i, hwirq + i, type); return 0; } -static int pmic_arb_init_apid_min_max(struct spmi_pmic_arb *pmic_arb) +static int pmic_arb_init_apid_min_max(struct spmi_pmic_arb_bus *bus) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + /* * Initialize max_apid/min_apid to the opposite bounds, during * the irq domain translation, we are sure to update these */ - pmic_arb->max_apid = 0; - pmic_arb->min_apid = pmic_arb->max_periphs - 1; + bus->max_apid = 0; + bus->min_apid = pmic_arb->max_periphs - 1; return 0; } @@ -976,37 +1003,38 @@ static int pmic_arb_get_core_resources_v1(struct platform_device *pdev, return 0; } -static int pmic_arb_init_apid_v1(struct spmi_pmic_arb *pmic_arb) +static int pmic_arb_init_apid_v1(struct spmi_pmic_arb_bus *bus) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u32 *mapping_table; - mapping_table = devm_kcalloc(&pmic_arb->spmic->dev, pmic_arb->max_periphs, + mapping_table = devm_kcalloc(&bus->spmic->dev, pmic_arb->max_periphs, sizeof(*mapping_table), GFP_KERNEL); if (!mapping_table) return -ENOMEM; - pmic_arb->mapping_table = mapping_table; + bus->mapping_table = mapping_table; - return pmic_arb_init_apid_min_max(pmic_arb); + return pmic_arb_init_apid_min_max(bus); } -static int pmic_arb_ppid_to_apid_v1(struct spmi_pmic_arb *pmic_arb, u16 ppid) +static int pmic_arb_ppid_to_apid_v1(struct spmi_pmic_arb_bus *bus, u16 ppid) { - u32 *mapping_table = pmic_arb->mapping_table; + u32 *mapping_table = bus->mapping_table; int index = 0, i; u16 apid_valid; u16 apid; u32 data; - apid_valid = pmic_arb->ppid_to_apid[ppid]; + apid_valid = bus->ppid_to_apid[ppid]; if (apid_valid & PMIC_ARB_APID_VALID) { apid = apid_valid & ~PMIC_ARB_APID_VALID; return apid; } for (i = 0; i < SPMI_MAPPING_TABLE_TREE_DEPTH; ++i) { - if (!test_and_set_bit(index, pmic_arb->mapping_table_valid)) - mapping_table[index] = readl_relaxed(pmic_arb->cnfg + + if (!test_and_set_bit(index, bus->mapping_table_valid)) + mapping_table[index] = readl_relaxed(bus->cnfg + SPMI_MAPPING_TABLE_REG(index)); data = mapping_table[index]; @@ -1016,9 +1044,9 @@ static int pmic_arb_ppid_to_apid_v1(struct spmi_pmic_arb *pmic_arb, u16 ppid) index = SPMI_MAPPING_BIT_IS_1_RESULT(data); } else { apid = SPMI_MAPPING_BIT_IS_1_RESULT(data); - pmic_arb->ppid_to_apid[ppid] + bus->ppid_to_apid[ppid] = apid | PMIC_ARB_APID_VALID; - pmic_arb->apid_data[apid].ppid = ppid; + bus->apid_data[apid].ppid = ppid; return apid; } } else { @@ -1026,9 +1054,9 @@ static int pmic_arb_ppid_to_apid_v1(struct spmi_pmic_arb *pmic_arb, u16 ppid) index = SPMI_MAPPING_BIT_IS_0_RESULT(data); } else { apid = SPMI_MAPPING_BIT_IS_0_RESULT(data); - pmic_arb->ppid_to_apid[ppid] + bus->ppid_to_apid[ppid] = apid | PMIC_ARB_APID_VALID; - pmic_arb->apid_data[apid].ppid = ppid; + bus->apid_data[apid].ppid = ppid; return apid; } } @@ -1038,24 +1066,26 @@ static int pmic_arb_ppid_to_apid_v1(struct spmi_pmic_arb *pmic_arb, u16 ppid) } /* v1 offset per ee */ -static int pmic_arb_offset_v1(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, - enum pmic_arb_channel ch_type) +static int pmic_arb_offset_v1(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, + enum pmic_arb_channel ch_type) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; return 0x800 + 0x80 * pmic_arb->channel; } -static u16 pmic_arb_find_apid(struct spmi_pmic_arb *pmic_arb, u16 ppid) +static u16 pmic_arb_find_apid(struct spmi_pmic_arb_bus *bus, u16 ppid) { - struct apid_data *apidd = &pmic_arb->apid_data[pmic_arb->last_apid]; + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + struct apid_data *apidd = &bus->apid_data[bus->last_apid]; u32 regval, offset; u16 id, apid; - for (apid = pmic_arb->last_apid; ; apid++, apidd++) { + for (apid = bus->last_apid; ; apid++, apidd++) { offset = pmic_arb->ver_ops->apid_map_offset(apid); if (offset >= pmic_arb->core_size) break; - regval = readl_relaxed(pmic_arb->ver_ops->apid_owner(pmic_arb, + regval = readl_relaxed(pmic_arb->ver_ops->apid_owner(bus, apid)); apidd->irq_ee = SPMI_OWNERSHIP_PERIPH2OWNER(regval); apidd->write_ee = apidd->irq_ee; @@ -1065,14 +1095,14 @@ static u16 pmic_arb_find_apid(struct spmi_pmic_arb *pmic_arb, u16 ppid) continue; id = (regval >> 8) & PMIC_ARB_PPID_MASK; - pmic_arb->ppid_to_apid[id] = apid | PMIC_ARB_APID_VALID; + bus->ppid_to_apid[id] = apid | PMIC_ARB_APID_VALID; apidd->ppid = id; if (id == ppid) { apid |= PMIC_ARB_APID_VALID; break; } } - pmic_arb->last_apid = apid & ~PMIC_ARB_APID_VALID; + bus->last_apid = apid & ~PMIC_ARB_APID_VALID; return apid; } @@ -1104,21 +1134,22 @@ static int pmic_arb_get_core_resources_v2(struct platform_device *pdev, return pmic_arb_get_obsrvr_chnls_v2(pdev); } -static int pmic_arb_ppid_to_apid_v2(struct spmi_pmic_arb *pmic_arb, u16 ppid) +static int pmic_arb_ppid_to_apid_v2(struct spmi_pmic_arb_bus *bus, u16 ppid) { u16 apid_valid; - apid_valid = pmic_arb->ppid_to_apid[ppid]; + apid_valid = bus->ppid_to_apid[ppid]; if (!(apid_valid & PMIC_ARB_APID_VALID)) - apid_valid = pmic_arb_find_apid(pmic_arb, ppid); + apid_valid = pmic_arb_find_apid(bus, ppid); if (!(apid_valid & PMIC_ARB_APID_VALID)) return -ENODEV; return apid_valid & ~PMIC_ARB_APID_VALID; } -static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb *pmic_arb) +static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; struct apid_data *apidd; struct apid_data *prev_apidd; u16 i, apid, ppid, apid_max; @@ -1140,9 +1171,9 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb *pmic_arb) * where N = number of APIDs supported by the primary bus and * M = number of APIDs supported by the secondary bus */ - apidd = &pmic_arb->apid_data[pmic_arb->base_apid]; - apid_max = pmic_arb->base_apid + pmic_arb->apid_count; - for (i = pmic_arb->base_apid; i < apid_max; i++, apidd++) { + apidd = &bus->apid_data[bus->base_apid]; + apid_max = bus->base_apid + bus->apid_count; + for (i = bus->base_apid; i < apid_max; i++, apidd++) { offset = pmic_arb->ver_ops->apid_map_offset(i); if (offset >= pmic_arb->core_size) break; @@ -1153,19 +1184,18 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb *pmic_arb) ppid = (regval >> 8) & PMIC_ARB_PPID_MASK; is_irq_ee = PMIC_ARB_CHAN_IS_IRQ_OWNER(regval); - regval = readl_relaxed(pmic_arb->ver_ops->apid_owner(pmic_arb, - i)); + regval = readl_relaxed(pmic_arb->ver_ops->apid_owner(bus, i)); apidd->write_ee = SPMI_OWNERSHIP_PERIPH2OWNER(regval); apidd->irq_ee = is_irq_ee ? apidd->write_ee : INVALID_EE; - valid = pmic_arb->ppid_to_apid[ppid] & PMIC_ARB_APID_VALID; - apid = pmic_arb->ppid_to_apid[ppid] & ~PMIC_ARB_APID_VALID; - prev_apidd = &pmic_arb->apid_data[apid]; + valid = bus->ppid_to_apid[ppid] & PMIC_ARB_APID_VALID; + apid = bus->ppid_to_apid[ppid] & ~PMIC_ARB_APID_VALID; + prev_apidd = &bus->apid_data[apid]; if (!valid || apidd->write_ee == pmic_arb->ee) { /* First PPID mapping or one for this EE */ - pmic_arb->ppid_to_apid[ppid] = i | PMIC_ARB_APID_VALID; + bus->ppid_to_apid[ppid] = i | PMIC_ARB_APID_VALID; } else if (valid && is_irq_ee && prev_apidd->write_ee == pmic_arb->ee) { /* @@ -1176,42 +1206,43 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb *pmic_arb) } apidd->ppid = ppid; - pmic_arb->last_apid = i; + bus->last_apid = i; } /* Dump the mapping table for debug purposes. */ - dev_dbg(&pmic_arb->spmic->dev, "PPID APID Write-EE IRQ-EE\n"); + dev_dbg(&bus->spmic->dev, "PPID APID Write-EE IRQ-EE\n"); for (ppid = 0; ppid < PMIC_ARB_MAX_PPID; ppid++) { - apid = pmic_arb->ppid_to_apid[ppid]; + apid = bus->ppid_to_apid[ppid]; if (apid & PMIC_ARB_APID_VALID) { apid &= ~PMIC_ARB_APID_VALID; - apidd = &pmic_arb->apid_data[apid]; - dev_dbg(&pmic_arb->spmic->dev, "%#03X %3u %2u %2u\n", - ppid, apid, apidd->write_ee, apidd->irq_ee); + apidd = &bus->apid_data[apid]; + dev_dbg(&bus->spmic->dev, "%#03X %3u %2u %2u\n", + ppid, apid, apidd->write_ee, apidd->irq_ee); } } return 0; } -static int pmic_arb_ppid_to_apid_v5(struct spmi_pmic_arb *pmic_arb, u16 ppid) +static int pmic_arb_ppid_to_apid_v5(struct spmi_pmic_arb_bus *bus, u16 ppid) { - if (!(pmic_arb->ppid_to_apid[ppid] & PMIC_ARB_APID_VALID)) + if (!(bus->ppid_to_apid[ppid] & PMIC_ARB_APID_VALID)) return -ENODEV; - return pmic_arb->ppid_to_apid[ppid] & ~PMIC_ARB_APID_VALID; + return bus->ppid_to_apid[ppid] & ~PMIC_ARB_APID_VALID; } /* v2 offset per ppid and per ee */ -static int pmic_arb_offset_v2(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, - enum pmic_arb_channel ch_type) +static int pmic_arb_offset_v2(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, + enum pmic_arb_channel ch_type) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u16 apid; u16 ppid; int rc; ppid = sid << 8 | ((addr >> 8) & 0xFF); - rc = pmic_arb_ppid_to_apid_v2(pmic_arb, ppid); + rc = pmic_arb_ppid_to_apid_v2(bus, ppid); if (rc < 0) return rc; @@ -1219,27 +1250,28 @@ static int pmic_arb_offset_v2(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, return 0x1000 * pmic_arb->ee + 0x8000 * apid; } -static int pmic_arb_init_apid_v5(struct spmi_pmic_arb *pmic_arb) +static int pmic_arb_init_apid_v5(struct spmi_pmic_arb_bus *bus) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; int ret; - pmic_arb->base_apid = 0; - pmic_arb->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; + bus->base_apid = 0; + bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & + PMIC_ARB_FEATURES_PERIPH_MASK; - if (pmic_arb->base_apid + pmic_arb->apid_count > pmic_arb->max_periphs) { - dev_err(&pmic_arb->spmic->dev, "Unsupported APID count %d detected\n", - pmic_arb->base_apid + pmic_arb->apid_count); + if (bus->base_apid + bus->apid_count > pmic_arb->max_periphs) { + dev_err(&bus->spmic->dev, "Unsupported APID count %d detected\n", + bus->base_apid + bus->apid_count); return -EINVAL; } - ret = pmic_arb_init_apid_min_max(pmic_arb); + ret = pmic_arb_init_apid_min_max(bus); if (ret) return ret; - ret = pmic_arb_read_apid_map_v5(pmic_arb); + ret = pmic_arb_read_apid_map_v5(bus); if (ret) { - dev_err(&pmic_arb->spmic->dev, "could not read APID->PPID mapping table, rc= %d\n", + dev_err(&bus->spmic->dev, "could not read APID->PPID mapping table, rc= %d\n", ret); return ret; } @@ -1251,15 +1283,16 @@ static int pmic_arb_init_apid_v5(struct spmi_pmic_arb *pmic_arb) * v5 offset per ee and per apid for observer channels and per apid for * read/write channels. */ -static int pmic_arb_offset_v5(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, - enum pmic_arb_channel ch_type) +static int pmic_arb_offset_v5(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, + enum pmic_arb_channel ch_type) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u16 apid; int rc; u32 offset = 0; u16 ppid = (sid << 8) | (addr >> 8); - rc = pmic_arb_ppid_to_apid_v5(pmic_arb, ppid); + rc = pmic_arb_ppid_to_apid_v5(bus, ppid); if (rc < 0) return rc; @@ -1269,8 +1302,8 @@ static int pmic_arb_offset_v5(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, offset = 0x10000 * pmic_arb->ee + 0x80 * apid; break; case PMIC_ARB_CHANNEL_RW: - if (pmic_arb->apid_data[apid].write_ee != pmic_arb->ee) { - dev_err(&pmic_arb->spmic->dev, "disallowed SPMI write to sid=%u, addr=0x%04X\n", + if (bus->apid_data[apid].write_ee != pmic_arb->ee) { + dev_err(&bus->spmic->dev, "disallowed SPMI write to sid=%u, addr=0x%04X\n", sid, addr); return -EPERM; } @@ -1297,15 +1330,16 @@ static int pmic_arb_get_core_resources_v7(struct platform_device *pdev, * v7 offset per ee and per apid for observer channels and per apid for * read/write channels. */ -static int pmic_arb_offset_v7(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, - enum pmic_arb_channel ch_type) +static int pmic_arb_offset_v7(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, + enum pmic_arb_channel ch_type) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u16 apid; int rc; u32 offset = 0; u16 ppid = (sid << 8) | (addr >> 8); - rc = pmic_arb->ver_ops->ppid_to_apid(pmic_arb, ppid); + rc = pmic_arb->ver_ops->ppid_to_apid(bus, ppid); if (rc < 0) return rc; @@ -1315,8 +1349,8 @@ static int pmic_arb_offset_v7(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr, offset = 0x8000 * pmic_arb->ee + 0x20 * apid; break; case PMIC_ARB_CHANNEL_RW: - if (pmic_arb->apid_data[apid].write_ee != pmic_arb->ee) { - dev_err(&pmic_arb->spmic->dev, "disallowed SPMI write to sid=%u, addr=0x%04X\n", + if (bus->apid_data[apid].write_ee != pmic_arb->ee) { + dev_err(&bus->spmic->dev, "disallowed SPMI write to sid=%u, addr=0x%04X\n", sid, addr); return -EPERM; } @@ -1338,104 +1372,110 @@ static u32 pmic_arb_fmt_cmd_v2(u8 opc, u8 sid, u16 addr, u8 bc) } static void __iomem * -pmic_arb_owner_acc_status_v1(struct spmi_pmic_arb *pmic_arb, u8 m, u16 n) +pmic_arb_owner_acc_status_v1(struct spmi_pmic_arb_bus *bus, u8 m, u16 n) { - return pmic_arb->intr + 0x20 * m + 0x4 * n; + return bus->intr + 0x20 * m + 0x4 * n; } static void __iomem * -pmic_arb_owner_acc_status_v2(struct spmi_pmic_arb *pmic_arb, u8 m, u16 n) +pmic_arb_owner_acc_status_v2(struct spmi_pmic_arb_bus *bus, u8 m, u16 n) { - return pmic_arb->intr + 0x100000 + 0x1000 * m + 0x4 * n; + return bus->intr + 0x100000 + 0x1000 * m + 0x4 * n; } static void __iomem * -pmic_arb_owner_acc_status_v3(struct spmi_pmic_arb *pmic_arb, u8 m, u16 n) +pmic_arb_owner_acc_status_v3(struct spmi_pmic_arb_bus *bus, u8 m, u16 n) { - return pmic_arb->intr + 0x200000 + 0x1000 * m + 0x4 * n; + return bus->intr + 0x200000 + 0x1000 * m + 0x4 * n; } static void __iomem * -pmic_arb_owner_acc_status_v5(struct spmi_pmic_arb *pmic_arb, u8 m, u16 n) +pmic_arb_owner_acc_status_v5(struct spmi_pmic_arb_bus *bus, u8 m, u16 n) { - return pmic_arb->intr + 0x10000 * m + 0x4 * n; + return bus->intr + 0x10000 * m + 0x4 * n; } static void __iomem * -pmic_arb_owner_acc_status_v7(struct spmi_pmic_arb *pmic_arb, u8 m, u16 n) +pmic_arb_owner_acc_status_v7(struct spmi_pmic_arb_bus *bus, u8 m, u16 n) { - return pmic_arb->intr + 0x1000 * m + 0x4 * n; + return bus->intr + 0x1000 * m + 0x4 * n; } static void __iomem * -pmic_arb_acc_enable_v1(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_acc_enable_v1(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->intr + 0x200 + 0x4 * n; + return bus->intr + 0x200 + 0x4 * n; } static void __iomem * -pmic_arb_acc_enable_v2(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_acc_enable_v2(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->intr + 0x1000 * n; + return bus->intr + 0x1000 * n; } static void __iomem * -pmic_arb_acc_enable_v5(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_acc_enable_v5(struct spmi_pmic_arb_bus *bus, u16 n) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; return pmic_arb->wr_base + 0x100 + 0x10000 * n; } static void __iomem * -pmic_arb_acc_enable_v7(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_acc_enable_v7(struct spmi_pmic_arb_bus *bus, u16 n) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; return pmic_arb->wr_base + 0x100 + 0x1000 * n; } static void __iomem * -pmic_arb_irq_status_v1(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_status_v1(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->intr + 0x600 + 0x4 * n; + return bus->intr + 0x600 + 0x4 * n; } static void __iomem * -pmic_arb_irq_status_v2(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_status_v2(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->intr + 0x4 + 0x1000 * n; + return bus->intr + 0x4 + 0x1000 * n; } static void __iomem * -pmic_arb_irq_status_v5(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_status_v5(struct spmi_pmic_arb_bus *bus, u16 n) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; return pmic_arb->wr_base + 0x104 + 0x10000 * n; } static void __iomem * -pmic_arb_irq_status_v7(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_status_v7(struct spmi_pmic_arb_bus *bus, u16 n) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; return pmic_arb->wr_base + 0x104 + 0x1000 * n; } static void __iomem * -pmic_arb_irq_clear_v1(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_clear_v1(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->intr + 0xA00 + 0x4 * n; + return bus->intr + 0xA00 + 0x4 * n; } static void __iomem * -pmic_arb_irq_clear_v2(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_clear_v2(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->intr + 0x8 + 0x1000 * n; + return bus->intr + 0x8 + 0x1000 * n; } static void __iomem * -pmic_arb_irq_clear_v5(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_clear_v5(struct spmi_pmic_arb_bus *bus, u16 n) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; return pmic_arb->wr_base + 0x108 + 0x10000 * n; } static void __iomem * -pmic_arb_irq_clear_v7(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_irq_clear_v7(struct spmi_pmic_arb_bus *bus, u16 n) { + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; return pmic_arb->wr_base + 0x108 + 0x1000 * n; } @@ -1455,9 +1495,9 @@ static u32 pmic_arb_apid_map_offset_v7(u16 n) } static void __iomem * -pmic_arb_apid_owner_v2(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_apid_owner_v2(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->cnfg + 0x700 + 0x4 * n; + return bus->cnfg + 0x700 + 0x4 * n; } /* @@ -1466,9 +1506,9 @@ pmic_arb_apid_owner_v2(struct spmi_pmic_arb *pmic_arb, u16 n) * 0. */ static void __iomem * -pmic_arb_apid_owner_v7(struct spmi_pmic_arb *pmic_arb, u16 n) +pmic_arb_apid_owner_v7(struct spmi_pmic_arb_bus *bus, u16 n) { - return pmic_arb->cnfg + 0x4 * (n - pmic_arb->base_apid); + return bus->cnfg + 0x4 * (n - bus->base_apid); } static const struct pmic_arb_ver_ops pmic_arb_v1 = { @@ -1558,29 +1598,121 @@ static const struct irq_domain_ops pmic_arb_irq_domain_ops = { .translate = qpnpint_irq_domain_translate, }; +static int spmi_pmic_arb_bus_init(struct platform_device *pdev, + struct device_node *node, + struct spmi_pmic_arb *pmic_arb) +{ + struct spmi_pmic_arb_bus *bus; + struct device *dev = &pdev->dev; + struct spmi_controller *ctrl; + void __iomem *intr; + void __iomem *cnfg; + int index, ret; + int irq; + + ctrl = devm_spmi_controller_alloc(dev, sizeof(*bus)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->cmd = pmic_arb_cmd; + ctrl->read_cmd = pmic_arb_read_cmd; + ctrl->write_cmd = pmic_arb_write_cmd; + + bus = spmi_controller_get_drvdata(ctrl); + + pmic_arb->bus = bus; + + raw_spin_lock_init(&bus->lock); + + bus->ppid_to_apid = devm_kcalloc(dev, PMIC_ARB_MAX_PPID, + sizeof(*bus->ppid_to_apid), + GFP_KERNEL); + if (!bus->ppid_to_apid) + return -ENOMEM; + + bus->apid_data = devm_kcalloc(dev, pmic_arb->max_periphs, + sizeof(*bus->apid_data), + GFP_KERNEL); + if (!bus->apid_data) + return -ENOMEM; + + index = of_property_match_string(node, "reg-names", "cnfg"); + if (index < 0) { + dev_err(dev, "cnfg reg region missing"); + return -EINVAL; + } + + cnfg = devm_of_iomap(dev, node, index, NULL); + if (IS_ERR(cnfg)) + return PTR_ERR(cnfg); + + index = of_property_match_string(node, "reg-names", "intr"); + if (index < 0) { + dev_err(dev, "intr reg region missing"); + return -EINVAL; + } + + intr = devm_of_iomap(dev, node, index, NULL); + if (IS_ERR(intr)) + return PTR_ERR(intr); + + irq = of_irq_get_byname(node, "periph_irq"); + if (irq <= 0) + return irq ?: -ENXIO; + + bus->pmic_arb = pmic_arb; + bus->intr = intr; + bus->cnfg = cnfg; + bus->irq = irq; + bus->spmic = ctrl; + + ret = pmic_arb->ver_ops->init_apid(bus); + if (ret) + return ret; + + dev_dbg(&pdev->dev, "adding irq domain\n"); + + bus->domain = irq_domain_add_tree(dev->of_node, + &pmic_arb_irq_domain_ops, bus); + if (!bus->domain) { + dev_err(&pdev->dev, "unable to create irq_domain\n"); + return -ENOMEM; + } + + irq_set_chained_handler_and_data(bus->irq, + pmic_arb_chained_irq, bus); + + ctrl->dev.of_node = node; + + ret = devm_spmi_controller_add(dev, ctrl); + if (ret) + return ret; + + return 0; +} + static int spmi_pmic_arb_probe(struct platform_device *pdev) { struct spmi_pmic_arb *pmic_arb; - struct spmi_controller *ctrl; + struct device *dev = &pdev->dev; struct resource *res; void __iomem *core; u32 channel, ee, hw_ver; int err; - ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*pmic_arb)); - if (IS_ERR(ctrl)) - return PTR_ERR(ctrl); - - pmic_arb = spmi_controller_get_drvdata(ctrl); - pmic_arb->spmic = ctrl; + pmic_arb = devm_kzalloc(dev, sizeof(*pmic_arb), GFP_KERNEL); + if (!pmic_arb) + return -ENOMEM; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); - core = devm_ioremap(&ctrl->dev, res->start, resource_size(res)); + core = devm_ioremap(dev, res->start, resource_size(res)); if (!core) return -ENOMEM; pmic_arb->core_size = resource_size(res); + platform_set_drvdata(pdev, pmic_arb); + hw_ver = readl_relaxed(core + PMIC_ARB_VERSION); if (hw_ver < PMIC_ARB_VERSION_V2_MIN) @@ -1594,30 +1726,12 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) else pmic_arb->ver_ops = &pmic_arb_v7; - dev_info(&ctrl->dev, "PMIC arbiter version %s (0x%x)\n", - pmic_arb->ver_ops->ver_str, hw_ver); - err = pmic_arb->ver_ops->get_core_resources(pdev, core); if (err) return err; - err = pmic_arb->ver_ops->init_apid(pmic_arb); - if (err) - return err; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr"); - pmic_arb->intr = devm_ioremap_resource(&ctrl->dev, res); - if (IS_ERR(pmic_arb->intr)) - return PTR_ERR(pmic_arb->intr); - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cnfg"); - pmic_arb->cnfg = devm_ioremap_resource(&ctrl->dev, res); - if (IS_ERR(pmic_arb->cnfg)) - return PTR_ERR(pmic_arb->cnfg); - - pmic_arb->irq = platform_get_irq_byname(pdev, "periph_irq"); - if (pmic_arb->irq < 0) - return pmic_arb->irq; + dev_info(dev, "PMIC arbiter version %s (0x%x)\n", + pmic_arb->ver_ops->ver_str, hw_ver); err = of_property_read_u32(pdev->dev.of_node, "qcom,channel", &channel); if (err) { @@ -1646,42 +1760,17 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) pmic_arb->ee = ee; - platform_set_drvdata(pdev, ctrl); - raw_spin_lock_init(&pmic_arb->lock); - - ctrl->cmd = pmic_arb_cmd; - ctrl->read_cmd = pmic_arb_read_cmd; - ctrl->write_cmd = pmic_arb_write_cmd; - - dev_dbg(&pdev->dev, "adding irq domain\n"); - pmic_arb->domain = irq_domain_add_tree(pdev->dev.of_node, - &pmic_arb_irq_domain_ops, pmic_arb); - if (!pmic_arb->domain) { - dev_err(&pdev->dev, "unable to create irq_domain\n"); - return -ENOMEM; - } - - irq_set_chained_handler_and_data(pmic_arb->irq, pmic_arb_chained_irq, - pmic_arb); - err = spmi_controller_add(ctrl); - if (err) - goto err_domain_remove; - - return 0; - -err_domain_remove: - irq_set_chained_handler_and_data(pmic_arb->irq, NULL, NULL); - irq_domain_remove(pmic_arb->domain); - return err; + return spmi_pmic_arb_bus_init(pdev, dev->of_node, pmic_arb); } static void spmi_pmic_arb_remove(struct platform_device *pdev) { - struct spmi_controller *ctrl = platform_get_drvdata(pdev); - struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl); - spmi_controller_remove(ctrl); - irq_set_chained_handler_and_data(pmic_arb->irq, NULL, NULL); - irq_domain_remove(pmic_arb->domain); + struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); + struct spmi_pmic_arb_bus *bus = pmic_arb->bus; + + irq_set_chained_handler_and_data(bus->irq, + NULL, NULL); + irq_domain_remove(bus->domain); } static const struct of_device_id spmi_pmic_arb_match_table[] = { From 9799873717398e8fa1727482e578b9d777da645e Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Tue, 7 May 2024 14:07:49 -0700 Subject: [PATCH 318/319] spmi: pmic-arb: Add multi bus support Starting with HW version 7, there are actually two separate buses (with two separate sets of wires). So add support for the second bus. The first platform that needs this support for the second bus is the Qualcomm X1 Elite, so add the compatible for it as well. Reviewed-by: Neil Armstrong Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20240417-spmi-multi-master-support-v10-7-5bc6d322e266@linaro.org Signed-off-by: Stephen Boyd Link: https://lore.kernel.org/r/20240507210809.3479953-13-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-pmic-arb.c | 138 ++++++++++++++++++++++++++++++----- 1 file changed, 120 insertions(+), 18 deletions(-) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index c3dd5d2aa244..791cdc160c51 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,8 @@ enum pmic_arb_channel { PMIC_ARB_CHANNEL_OBS, }; +#define PMIC_ARB_MAX_BUSES 2 + /* Maximum number of support PMIC peripherals */ #define PMIC_ARB_MAX_PERIPHS 512 #define PMIC_ARB_MAX_PERIPHS_V7 1024 @@ -149,6 +152,7 @@ struct spmi_pmic_arb; * @min_apid: minimum APID (used for bounding IRQ search) * @max_apid: maximum APID * @irq: PMIC ARB interrupt. + * @id: unique ID of the bus */ struct spmi_pmic_arb_bus { struct spmi_pmic_arb *pmic_arb; @@ -167,6 +171,7 @@ struct spmi_pmic_arb_bus { u16 min_apid; u16 max_apid; int irq; + u8 id; }; /** @@ -180,7 +185,8 @@ struct spmi_pmic_arb_bus { * @ee: the current Execution Environment * @ver_ops: version dependent operations. * @max_periphs: Number of elements in apid_data[] - * @bus: per arbiter bus instance + * @buses: per arbiter buses instances + * @buses_available: number of buses registered */ struct spmi_pmic_arb { void __iomem *rd_base; @@ -191,7 +197,8 @@ struct spmi_pmic_arb { u8 ee; const struct pmic_arb_ver_ops *ver_ops; int max_periphs; - struct spmi_pmic_arb_bus *bus; + struct spmi_pmic_arb_bus *buses[PMIC_ARB_MAX_BUSES]; + int buses_available; }; /** @@ -220,7 +227,7 @@ struct spmi_pmic_arb { struct pmic_arb_ver_ops { const char *ver_str; int (*get_core_resources)(struct platform_device *pdev, void __iomem *core); - int (*init_apid)(struct spmi_pmic_arb_bus *bus); + int (*init_apid)(struct spmi_pmic_arb_bus *bus, int index); int (*ppid_to_apid)(struct spmi_pmic_arb_bus *bus, u16 ppid); /* spmi commands (read_cmd, write_cmd, cmd) functionality */ int (*offset)(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, @@ -309,8 +316,8 @@ static int pmic_arb_wait_for_done(struct spmi_controller *ctrl, } if (status & PMIC_ARB_STATUS_FAILURE) { - dev_err(&ctrl->dev, "%s: %#x %#x: transaction failed (%#x)\n", - __func__, sid, addr, status); + dev_err(&ctrl->dev, "%s: %#x %#x: transaction failed (%#x) reg: 0x%x\n", + __func__, sid, addr, status, offset); WARN_ON(1); return -EIO; } @@ -326,8 +333,8 @@ static int pmic_arb_wait_for_done(struct spmi_controller *ctrl, udelay(1); } - dev_err(&ctrl->dev, "%s: %#x %#x: timeout, status %#x\n", - __func__, sid, addr, status); + dev_err(&ctrl->dev, "%s: %#x %#x %#x: timeout, status %#x\n", + __func__, bus->id, sid, addr, status); return -ETIMEDOUT; } @@ -1003,11 +1010,17 @@ static int pmic_arb_get_core_resources_v1(struct platform_device *pdev, return 0; } -static int pmic_arb_init_apid_v1(struct spmi_pmic_arb_bus *bus) +static int pmic_arb_init_apid_v1(struct spmi_pmic_arb_bus *bus, int index) { struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; u32 *mapping_table; + if (index) { + dev_err(&bus->spmic->dev, "Unsupported buses count %d detected\n", + index); + return -EINVAL; + } + mapping_table = devm_kcalloc(&bus->spmic->dev, pmic_arb->max_periphs, sizeof(*mapping_table), GFP_KERNEL); if (!mapping_table) @@ -1250,11 +1263,17 @@ static int pmic_arb_offset_v2(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, return 0x1000 * pmic_arb->ee + 0x8000 * apid; } -static int pmic_arb_init_apid_v5(struct spmi_pmic_arb_bus *bus) +static int pmic_arb_init_apid_v5(struct spmi_pmic_arb_bus *bus, int index) { struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; int ret; + if (index) { + dev_err(&bus->spmic->dev, "Unsupported buses count %d detected\n", + index); + return -EINVAL; + } + bus->base_apid = 0; bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & PMIC_ARB_FEATURES_PERIPH_MASK; @@ -1326,6 +1345,50 @@ static int pmic_arb_get_core_resources_v7(struct platform_device *pdev, return pmic_arb_get_obsrvr_chnls_v2(pdev); } +/* + * Only v7 supports 2 buses. Each bus will get a different apid count, read + * from different registers. + */ +static int pmic_arb_init_apid_v7(struct spmi_pmic_arb_bus *bus, int index) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + int ret; + + if (index == 0) { + bus->base_apid = 0; + bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & + PMIC_ARB_FEATURES_PERIPH_MASK; + } else if (index == 1) { + bus->base_apid = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & + PMIC_ARB_FEATURES_PERIPH_MASK; + bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES1) & + PMIC_ARB_FEATURES_PERIPH_MASK; + } else { + dev_err(&bus->spmic->dev, "Unsupported buses count %d detected\n", + bus->id); + return -EINVAL; + } + + if (bus->base_apid + bus->apid_count > pmic_arb->max_periphs) { + dev_err(&bus->spmic->dev, "Unsupported APID count %d detected\n", + bus->base_apid + bus->apid_count); + return -EINVAL; + } + + ret = pmic_arb_init_apid_min_max(bus); + if (ret) + return ret; + + ret = pmic_arb_read_apid_map_v5(bus); + if (ret) { + dev_err(&bus->spmic->dev, "could not read APID->PPID mapping table, rc= %d\n", + ret); + return ret; + } + + return 0; +} + /* * v7 offset per ee and per apid for observer channels and per apid for * read/write channels. @@ -1578,7 +1641,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v5 = { static const struct pmic_arb_ver_ops pmic_arb_v7 = { .ver_str = "v7", .get_core_resources = pmic_arb_get_core_resources_v7, - .init_apid = pmic_arb_init_apid_v5, + .init_apid = pmic_arb_init_apid_v7, .ppid_to_apid = pmic_arb_ppid_to_apid_v5, .non_data_cmd = pmic_arb_non_data_cmd_v2, .offset = pmic_arb_offset_v7, @@ -1602,6 +1665,7 @@ static int spmi_pmic_arb_bus_init(struct platform_device *pdev, struct device_node *node, struct spmi_pmic_arb *pmic_arb) { + int bus_index = pmic_arb->buses_available; struct spmi_pmic_arb_bus *bus; struct device *dev = &pdev->dev; struct spmi_controller *ctrl; @@ -1620,7 +1684,7 @@ static int spmi_pmic_arb_bus_init(struct platform_device *pdev, bus = spmi_controller_get_drvdata(ctrl); - pmic_arb->bus = bus; + pmic_arb->buses[bus_index] = bus; raw_spin_lock_init(&bus->lock); @@ -1665,12 +1729,13 @@ static int spmi_pmic_arb_bus_init(struct platform_device *pdev, bus->cnfg = cnfg; bus->irq = irq; bus->spmic = ctrl; + bus->id = bus_index; - ret = pmic_arb->ver_ops->init_apid(bus); + ret = pmic_arb->ver_ops->init_apid(bus, bus_index); if (ret) return ret; - dev_dbg(&pdev->dev, "adding irq domain\n"); + dev_dbg(&pdev->dev, "adding irq domain for bus %d\n", bus_index); bus->domain = irq_domain_add_tree(dev->of_node, &pmic_arb_irq_domain_ops, bus); @@ -1683,14 +1748,53 @@ static int spmi_pmic_arb_bus_init(struct platform_device *pdev, pmic_arb_chained_irq, bus); ctrl->dev.of_node = node; + dev_set_name(&ctrl->dev, "spmi-%d", bus_index); ret = devm_spmi_controller_add(dev, ctrl); if (ret) return ret; + pmic_arb->buses_available++; + return 0; } +static int spmi_pmic_arb_register_buses(struct spmi_pmic_arb *pmic_arb, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct device_node *child; + int ret; + + /* legacy mode doesn't provide child node for the bus */ + if (of_device_is_compatible(node, "qcom,spmi-pmic-arb")) + return spmi_pmic_arb_bus_init(pdev, node, pmic_arb); + + for_each_available_child_of_node(node, child) { + if (of_node_name_eq(child, "spmi")) { + ret = spmi_pmic_arb_bus_init(pdev, child, pmic_arb); + if (ret) + return ret; + } + } + + return ret; +} + +static void spmi_pmic_arb_deregister_buses(struct spmi_pmic_arb *pmic_arb) +{ + int i; + + for (i = 0; i < pmic_arb->buses_available; i++) { + struct spmi_pmic_arb_bus *bus = pmic_arb->buses[i]; + + irq_set_chained_handler_and_data(bus->irq, + NULL, NULL); + irq_domain_remove(bus->domain); + } +} + static int spmi_pmic_arb_probe(struct platform_device *pdev) { struct spmi_pmic_arb *pmic_arb; @@ -1760,21 +1864,19 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) pmic_arb->ee = ee; - return spmi_pmic_arb_bus_init(pdev, dev->of_node, pmic_arb); + return spmi_pmic_arb_register_buses(pmic_arb, pdev); } static void spmi_pmic_arb_remove(struct platform_device *pdev) { struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); - struct spmi_pmic_arb_bus *bus = pmic_arb->bus; - irq_set_chained_handler_and_data(bus->irq, - NULL, NULL); - irq_domain_remove(bus->domain); + spmi_pmic_arb_deregister_buses(pmic_arb); } static const struct of_device_id spmi_pmic_arb_match_table[] = { { .compatible = "qcom,spmi-pmic-arb", }, + { .compatible = "qcom,x1e80100-spmi-pmic-arb", }, {}, }; MODULE_DEVICE_TABLE(of, spmi_pmic_arb_match_table); From f5b335dc025cfee90957efa90dc72fada0d5abb4 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 14 May 2024 09:16:51 +0200 Subject: [PATCH 319/319] misc: ntsync: mark driver as "broken" to prevent from building The ntsync code is only partially enabled in the kernel at this point in time, creating the device node and that's about it. Don't confuse systems that expect to see a working ntsync interface by teasing it with this basic structure at this point in time, so mark the code as "broken" so that it is not built and enabled just yet. Once the rest of the code is accepted, this will be reverted so that the driver can be correctly built and used, but for now, this is the safest way forward. Reviewed-by: Elizabeth Figura Link: https://lore.kernel.org/r/2024051450-abrasion-swizzle-550b@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 2907b5c23368..faf983680040 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -507,6 +507,7 @@ config OPEN_DICE config NTSYNC tristate "NT synchronization primitive emulation" + depends on BROKEN help This module provides kernel support for emulation of Windows NT synchronization primitives. It is not a hardware driver.