From nobody Wed Oct 8 10:58:16 2025 Received: from mx0a-00128a01.pphosted.com (mx0a-00128a01.pphosted.com [148.163.135.77]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D8E2F28B7E9; Mon, 30 Jun 2025 14:00:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.135.77 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751292055; cv=none; b=JaoalQfXo3vHxfm9XbP51ZINBBfLn9pxC5oHLarmRrxlKeCYFc/BugAH2Wz/Sz8A7nGPcdVQXcVFevCHPcmu4/BVDXf0QyQvuI1sf6SE2Jx1C/Flxv19MSckgdhEFJhtX96wWW4yuT2aco6040RTaZi4Fciej21kjWi+SGF867E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751292055; c=relaxed/simple; bh=Fl6zlh+uYcUpdpgt4gWbAQOFr+XRDzVsxHCZzo/hjPw=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ZaPZMiRwa9eaE5VLeEz88VpH/RY0ZbeeH2Ld3zNzaUKQdP6YNtjTWE0iB9kNeLP62VsJEF1nvSYxYRugvOGlDrK6NJKe/d2tHIMJh3JgMdJs0H+G0eR5iXLHxjzF6aDp/bqDhsdRmkBM5Uah6BNKpbVt3j2rd/GqwtEs6Hf4Z5Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com; spf=pass smtp.mailfrom=analog.com; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b=V1BP4ZBL; arc=none smtp.client-ip=148.163.135.77 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=analog.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=analog.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=analog.com header.i=@analog.com header.b="V1BP4ZBL" Received: from pps.filterd (m0167088.ppops.net [127.0.0.1]) by mx0a-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 55UCoede011048; Mon, 30 Jun 2025 10:00:34 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h=cc :content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=HOp4Z ogsM3nggVKZfQ9998bNegJoyQ5GTlcDJBn0HVM=; b=V1BP4ZBLcRiyYSwNSfN00 f8YTwobHxOTk/VdxbkoPYqiHU9fVKD4FIA/PKm60/R9iRbGM9QrPQRQJOWTBlTNR hra0QfgSYXwm+RBH/1/3aIEYHiom12hvV2LnVCHypBNGwcDqen10LwqwkHoD2gxN ldGoOCzjn1nJ1W3HdBpLGSnM/g13WC7ZZLQzTkPMU8135LRTztKeJq/58Vts3nup UzRJEQebiyU7DCPm/taP/AvgfRUgjhDetq5RalPLqOqVtSvyqkJTYHpxco97p5ZJ 6zK21/ISE9dLUblxzf0/VTycjUU+7aUcJGEt31Sazyfc1GuQqpnpDHwlhqI4jbQR A== Received: from nwd2mta3.analog.com ([137.71.173.56]) by mx0a-00128a01.pphosted.com (PPS) with ESMTPS id 47kqwj18sc-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 30 Jun 2025 10:00:33 -0400 (EDT) Received: from ASHBMBX8.ad.analog.com (ASHBMBX8.ad.analog.com [10.64.17.5]) by nwd2mta3.analog.com (8.14.7/8.14.7) with ESMTP id 55UE0WEE039705 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Mon, 30 Jun 2025 10:00:32 -0400 Received: from ASHBCASHYB5.ad.analog.com (10.64.17.133) by ASHBMBX8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Mon, 30 Jun 2025 10:00:32 -0400 Received: from ASHBMBX8.ad.analog.com (10.64.17.5) by ASHBCASHYB5.ad.analog.com (10.64.17.133) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Mon, 30 Jun 2025 10:00:32 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx8.ad.analog.com (10.64.17.5) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Mon, 30 Jun 2025 10:00:32 -0400 Received: from work.ad.analog.com (HYB-hERzalRezfV.ad.analog.com [10.65.205.9]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 55UE0E0s008659; Mon, 30 Jun 2025 10:00:17 -0400 From: Marcelo Schmitt To: , , , CC: , , , , , , , , , , , , , Subject: [PATCH v7 07/12] iio: adc: ad4170-4: Add support for buffered data capture Date: Mon, 30 Jun 2025 11:00:13 -0300 Message-ID: <5ac9ffec07b24e75a55caed027a76f7d69f65221.1751289747.git.marcelo.schmitt@analog.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ADIRuleOP-NewSCL: Rule Triggered X-Proofpoint-Spam-Details-Enc: AW1haW4tMjUwNjMwMDExNSBTYWx0ZWRfX2nJCOEKgycoI jAn4pzZoHRpaAoP3r9IcisGnO2OYpFfHg8wE6IGntA4hn2E7lfz2lZG0DS5OX6ZKV0R5Pp/k3Df uyUNUq9Whr6qiTxOmm3Vpgz9fwAv3GhQbco6/LnMUZPdhFMg32q0FOhYAxUW0YUKrC9HYhuoJhz XNKhE3NC9XF9z/4ICcz9wsPfeYqw0CdcxuYTS0fhgKt1O7ILvbI72ZUm1/xYl3fLXRO9L1aNdHL YCob3XPJjT9zE+VtNqsrtNH/GWORlmhe2SNzUQteX9IXrTKOvhQI7S0bw3fK6K71N1fE7wW/bqo 4wfBGHLjUQIKj0BNErCuCSjluk7pwTnF4m+gkSGSwUlOiVYpr2ppTYgzfCCTII/383+dCwcXHz6 4U5+easad+jikJaBr7ODnSwWeUpAaDUmJ9hCb7+pqkBNtjWDiFY/+rH0pzjcg/POLKY60buk X-Proofpoint-GUID: JgIYgNt6XCYxceFitQim5Ogft1XWhBKw X-Proofpoint-ORIG-GUID: JgIYgNt6XCYxceFitQim5Ogft1XWhBKw X-Authority-Analysis: v=2.4 cv=SsiQ6OO0 c=1 sm=1 tr=0 ts=68629881 cx=c_pps a=PpDZqlmH/M8setHirZLBMw==:117 a=PpDZqlmH/M8setHirZLBMw==:17 a=6IFa9wvqVegA:10 a=gAnH3GRIAAAA:8 a=TEtCmmvL8P_ouh62KQIA:9 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1099,Hydra:6.1.7,FMLib:17.12.80.40 definitions=2025-06-30_03,2025-06-27_01,2025-03-28_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 mlxscore=0 priorityscore=1501 malwarescore=0 clxscore=1015 mlxlogscore=999 spamscore=0 impostorscore=0 adultscore=0 lowpriorityscore=0 suspectscore=0 bulkscore=0 phishscore=0 classifier=spam authscore=0 authtc=n/a authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2505280000 definitions=main-2506300115 Content-Type: text/plain; charset="utf-8" Extend the AD4170-4 driver to allow buffered data capture in continuous read mode. In continuous read mode, the chip skips the instruction phase and outputs just ADC sample data, enabling faster sample rates to be reached. The internal channel sequencer always starts sampling from channel 0 and channel 0 must be enabled if more than one channel is selected for data capture. The scan mask validation callback checks if the aforementioned condition is met. Signed-off-by: Marcelo Schmitt --- Change log v6 -> v7 - Dropped extra assignment of st->trig->dev.parent. drivers/iio/adc/Kconfig | 2 + drivers/iio/adc/ad4170-4.c | 216 ++++++++++++++++++++++++++++++++++++- 2 files changed, 217 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 79fcb9dc680b..538929b3df6e 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -89,6 +89,8 @@ config AD4170_4 tristate "Analog Device AD4170-4 ADC Driver" depends on SPI select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER help Say yes here to build support for Analog Devices AD4170-4 SPI analog to digital converters (ADC). diff --git a/drivers/iio/adc/ad4170-4.c b/drivers/iio/adc/ad4170-4.c index abd8a292625b..9202a8dfcc16 100644 --- a/drivers/iio/adc/ad4170-4.c +++ b/drivers/iio/adc/ad4170-4.c @@ -16,7 +16,11 @@ #include #include #include +#include #include +#include +#include +#include #include #include #include @@ -61,6 +65,7 @@ #define AD4170_FILTER_FS_REG(x) (0xC7 + 14 * (x)) #define AD4170_OFFSET_REG(x) (0xCA + 14 * (x)) #define AD4170_GAIN_REG(x) (0xCD + 14 * (x)) +#define AD4170_ADC_CTRL_CONT_READ_EXIT_REG 0x200 /* virtual reg */ =20 #define AD4170_REG_READ_MASK BIT(14) =20 @@ -72,6 +77,7 @@ =20 /* AD4170_ADC_CTRL_REG */ #define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7) +#define AD4170_ADC_CTRL_CONT_READ_MSK GENMASK(5, 4) #define AD4170_ADC_CTRL_MODE_MSK GENMASK(3, 0) =20 /* AD4170_CHAN_EN_REG */ @@ -116,9 +122,13 @@ #define AD4170_PIN_MUXING_DIG_AUX1_RDY 0x1 =20 /* AD4170_ADC_CTRL_REG constants */ +#define AD4170_ADC_CTRL_MODE_CONT 0x0 #define AD4170_ADC_CTRL_MODE_SINGLE 0x4 #define AD4170_ADC_CTRL_MODE_IDLE 0x7 =20 +#define AD4170_ADC_CTRL_CONT_READ_DISABLE 0x0 +#define AD4170_ADC_CTRL_CONT_READ_ENABLE 0x1 + /* AD4170_FILTER_REG constants */ #define AD4170_FILTER_FILTER_TYPE_SINC5_AVG 0x0 #define AD4170_FILTER_FILTER_TYPE_SINC5 0x4 @@ -150,6 +160,8 @@ =20 #define AD4170_GAIN_REG_DEFAULT 0x555555 =20 +#define AD4170_ADC_CTRL_CONT_READ_EXIT 0xA5 + static const unsigned int ad4170_reg_size[] =3D { [AD4170_CONFIG_A_REG] =3D 1, [AD4170_DATA_24B_REG] =3D 3, @@ -186,6 +198,7 @@ static const unsigned int ad4170_reg_size[] =3D { [AD4170_OFFSET_REG(5) ... AD4170_GAIN_REG(5)] =3D 3, [AD4170_OFFSET_REG(6) ... AD4170_GAIN_REG(6)] =3D 3, [AD4170_OFFSET_REG(7) ... AD4170_GAIN_REG(7)] =3D 3, + [AD4170_ADC_CTRL_CONT_READ_EXIT_REG] =3D 0, }; =20 enum ad4170_ref_buf { @@ -320,6 +333,10 @@ struct ad4170_state { struct spi_device *spi; struct regmap *regmap; int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2]; + __be32 bounce_buffer[AD4170_MAX_CHANNELS]; + struct spi_message msg; + struct spi_transfer xfer; + struct iio_trigger *trig; struct completion completion; unsigned int pins_fn[AD4170_NUM_ANALOG_PINS]; u32 int_pin_sel; @@ -407,6 +424,10 @@ static int ad4170_reg_write(void *context, unsigned in= t reg, unsigned int val) case 1: tx_buf[AD4170_SPI_INST_PHASE_LEN] =3D val; break; + case 0: + /* Write continuous read exit code */ + tx_buf[0] =3D AD4170_ADC_CTRL_CONT_READ_EXIT; + return spi_write_then_read(st->spi, tx_buf, 1, NULL, 0); default: return -EINVAL; } @@ -800,6 +821,7 @@ static const struct iio_chan_spec ad4170_channel_templa= te =3D { .scan_type =3D { .realbits =3D 24, .storagebits =3D 32, + .shift =3D 8, .endianness =3D IIO_BE, }, }; @@ -1384,11 +1406,27 @@ static int ad4170_write_raw_get_fmt(struct iio_dev = *indio_dev, } } =20 +static int ad4170_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + unsigned int chan_index; + int ret; + + iio_for_each_active_channel(indio_dev, chan_index) { + ret =3D ad4170_set_channel_enable(st, chan_index, true); + if (ret) + return ret; + } + return 0; +} + static const struct iio_info ad4170_info =3D { .read_raw =3D ad4170_read_raw, .read_avail =3D ad4170_read_avail, .write_raw =3D ad4170_write_raw, .write_raw_get_fmt =3D ad4170_write_raw_get_fmt, + .update_scan_mode =3D ad4170_update_scan_mode, .debugfs_reg_access =3D ad4170_debugfs_reg_access, }; =20 @@ -1683,16 +1721,178 @@ static int ad4170_initial_config(struct iio_dev *i= ndio_dev) AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK); } =20 +static int ad4170_prepare_spi_message(struct ad4170_state *st) +{ + /* + * Continuous data register read is enabled on buffer postenable so + * no instruction phase is needed meaning we don't need to send the + * register address to read data. Transfer only needs the read buffer. + */ + st->xfer.rx_buf =3D &st->rx_buf; + st->xfer.len =3D BITS_TO_BYTES(ad4170_channel_template.scan_type.realbits= ); + + spi_message_init_with_transfers(&st->msg, &st->xfer, 1); + + return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->msg); +} + +static int ad4170_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + int ret; + + ret =3D regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, + AD4170_ADC_CTRL_MODE_CONT)); + if (ret) + return ret; + + /* + * This enables continuous read of the ADC data register. The ADC must + * be in continuous conversion mode. + */ + return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK, + AD4170_ADC_CTRL_CONT_READ_ENABLE)); +} + +static int ad4170_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + unsigned int i; + int ret; + + /* + * Use a high register address (virtual register) to request a write of + * 0xA5 to the ADC during the first 8 SCLKs of the ADC data read cycle, + * thus exiting continuous read. + */ + ret =3D regmap_write(st->regmap, AD4170_ADC_CTRL_CONT_READ_EXIT_REG, 0); + if (ret) + return ret; + + ret =3D regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK, + AD4170_ADC_CTRL_CONT_READ_DISABLE)); + if (ret) + return ret; + + ret =3D regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, + AD4170_ADC_CTRL_MODE_IDLE)); + if (ret) + return ret; + + /* + * The ADC sequences through all the enabled channels (see datasheet + * page 95). That can lead to incorrect channel being read if a + * single-shot read (or buffered read with different active_scan_mask) + * is done after buffer disable. Disable all channels so only requested + * channels will be read. + */ + for (i =3D 0; i < indio_dev->num_channels; i++) { + ret =3D ad4170_set_channel_enable(st, i, false); + if (ret) + return ret; + } + + return 0; +} + +static bool ad4170_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + unsigned int masklength =3D iio_get_masklength(indio_dev); + unsigned int enabled; + + /* + * The channel sequencer cycles through the enabled channels in + * sequential order, from channel 0 to channel 15, bypassing disabled + * channels. When more than one channel is enabled, channel 0 must + * always be enabled. See datasheet channel_en register description at + * page 95. + */ + enabled =3D bitmap_weight(scan_mask, masklength); + if (enabled > 1) + return test_bit(0, scan_mask); + + return enabled =3D=3D 1; +} + +static const struct iio_buffer_setup_ops ad4170_buffer_ops =3D { + .postenable =3D ad4170_buffer_postenable, + .predisable =3D ad4170_buffer_predisable, + .validate_scan_mask =3D ad4170_validate_scan_mask, +}; + +static irqreturn_t ad4170_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf =3D p; + struct iio_dev *indio_dev =3D pf->indio_dev; + struct ad4170_state *st =3D iio_priv(indio_dev); + unsigned int chan_index; + unsigned int i =3D 0; + int ret; + + iio_for_each_active_channel(indio_dev, chan_index) { + ret =3D spi_sync(st->spi, &st->msg); + if (ret) + goto err_out; + + memcpy(&st->bounce_buffer[i++], st->rx_buf, ARRAY_SIZE(st->rx_buf)); + } + + iio_push_to_buffers(indio_dev, st->bounce_buffer); +err_out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops ad4170_trigger_ops =3D { + .validate_device =3D iio_trigger_validate_own_device, +}; + static irqreturn_t ad4170_irq_handler(int irq, void *dev_id) { struct iio_dev *indio_dev =3D dev_id; struct ad4170_state *st =3D iio_priv(indio_dev); =20 - complete(&st->completion); + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->completion); =20 return IRQ_HANDLED; }; =20 +static int ad4170_trigger_setup(struct iio_dev *indio_dev) +{ + struct ad4170_state *st =3D iio_priv(indio_dev); + struct device *dev =3D &st->spi->dev; + int ret; + + st->trig =3D devm_iio_trigger_alloc(dev, "%s-trig%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops =3D &ad4170_trigger_ops; + + iio_trigger_set_drvdata(st->trig, indio_dev); + ret =3D devm_iio_trigger_register(dev, st->trig); + if (ret) + return dev_err_probe(dev, ret, "Failed to register trigger\n"); + + indio_dev->trig =3D iio_trigger_get(st->trig); + + return 0; +} + static int ad4170_regulator_setup(struct ad4170_state *st) { struct device *dev =3D &st->spi->dev; @@ -1817,8 +2017,22 @@ static int ad4170_probe(struct spi_device *spi) IRQF_ONESHOT, indio_dev->name, indio_dev); if (ret) return ret; + + ret =3D ad4170_trigger_setup(indio_dev); + if (ret) + return ret; } =20 + ret =3D ad4170_prepare_spi_message(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to prepare SPI message\n"); + + ret =3D devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &ad4170_trigger_handler, + &ad4170_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup read buffer\n"); + return devm_iio_device_register(dev, indio_dev); } =20 --=20 2.47.2