From nobody Fri Nov 29 10:45:05 2024 Received: from mx0b-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 543FE194147; Mon, 23 Sep 2024 10:17:43 +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=1727086666; cv=none; b=XjjVs5/UmWFVW8QzFySU8ivrHMThXvy+KB4yo3EXjd2SmtKOXjFsbZPIicZ1mL9eOGzlzNfZV8Dh2oMg8VnpR0SCQVVM0nJi34+L2xpg3fFGl1gYXRA/f6H0f7VlQ7gr2aZmPZ/iXUcCqobHHrrcSxTkHJ9Czwi8H26PluvBAEc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727086666; c=relaxed/simple; bh=9KIoUBGUVN88AOSD2vG+BJVBJUJuV/S9MpIT29vjUb0=; h=From:To:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ZMjWgb2I5TZs9XCKZC8lKPNz/H/dqIyUpsOGkOAnpDqxl0HK2dP/slSDkJAgPg09vId15rW0IBC6yjYJ9DNIfdrW+ORKFEoYxoG+j1FG9ZTQdKXr1RBtrKtvHFDMAOKSU0l1m9LLJkl1pM8QXA5KMmUdwM2J1BdIQUQ3eT0d900= 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=y5QJ74sj; 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="y5QJ74sj" Received: from pps.filterd (m0375855.ppops.net [127.0.0.1]) by mx0b-00128a01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 48N73x5h021522; Mon, 23 Sep 2024 06:17:17 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=analog.com; h= content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=DKIM; bh=Yhz33 5x56zQmobll+LrR5UxfSS3wOwLMtcbk6kkTem4=; b=y5QJ74sjRr9l1RHkErvZI TAfchZ8UcUSyZ64oRCHA89Abyi3bydc/BNtv3rvtxeu7Smk0PV865knV13quyA3I hm1QaRMMxZrWdHAIv0nH6SENw0M6E+wruDK9baYUkaQjQ6dtff6lWWyNbmybLOS8 qCHzfDT6dh8VktiNmVNknUmVmZCXOMNYe+ece/+xU+v0ItJzlwafIIgdSBrFXGfR LIxLVc1tR5lKOsDhq1W8XOhxYVq/r3wBuCMd6zfhqqYgksAKcGhpV95C51JKx41d Q9ZKvbFTAhZs8hGsDoRz7osTxrb8+DFefRPOeSOZzegPiURL9ZuLvgx5uN9FNGzd w== Received: from nwd2mta4.analog.com ([137.71.173.58]) by mx0b-00128a01.pphosted.com (PPS) with ESMTPS id 41u3m58nns-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 23 Sep 2024 06:17:17 -0400 (EDT) Received: from ASHBMBX8.ad.analog.com (ASHBMBX8.ad.analog.com [10.64.17.5]) by nwd2mta4.analog.com (8.14.7/8.14.7) with ESMTP id 48NAHGgP026084 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Mon, 23 Sep 2024 06:17:16 -0400 Received: from ASHBMBX9.ad.analog.com (10.64.17.10) 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.986.14; Mon, 23 Sep 2024 06:17:16 -0400 Received: from zeus.spd.analog.com (10.66.68.11) by ashbmbx9.ad.analog.com (10.64.17.10) with Microsoft SMTP Server id 15.2.986.14 via Frontend Transport; Mon, 23 Sep 2024 06:17:16 -0400 Received: from amiclaus-VirtualBox.ad.analog.com ([10.65.36.213]) by zeus.spd.analog.com (8.15.1/8.15.1) with ESMTP id 48NAFIQW001918; Mon, 23 Sep 2024 06:17:08 -0400 From: Antoniu Miclaus To: Jonathan Cameron , Lars-Peter Clausen , Michael Hennerich , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nuno Sa , Olivier Moysan , =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= , Andy Shevchenko , David Lechner , Marcelo Schmitt , =?UTF-8?q?Jo=C3=A3o=20Paulo=20Gon=C3=A7alves?= , Mike Looijmans , Dumitru Ceclan , Antoniu Miclaus , AngeloGioacchino Del Regno , Alisa-Dariana Roman , Sergiu Cuciurean , Dragos Bogdan , , , , Subject: [PATCH 6/7] iio: adc: ad485x: add ad485x driver Date: Mon, 23 Sep 2024 13:10:23 +0300 Message-ID: <20240923101206.3753-7-antoniu.miclaus@analog.com> X-Mailer: git-send-email 2.46.0 In-Reply-To: <20240923101206.3753-1-antoniu.miclaus@analog.com> References: <20240923101206.3753-1-antoniu.miclaus@analog.com> 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-GUID: EtcjLKPydnIszb32pP1EIXBSEi5zd8WJ X-Proofpoint-ORIG-GUID: EtcjLKPydnIszb32pP1EIXBSEi5zd8WJ X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1039,Hydra:6.0.680,FMLib:17.12.60.29 definitions=2024-09-06_09,2024-09-06_01,2024-09-02_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 bulkscore=0 impostorscore=0 suspectscore=0 mlxlogscore=999 mlxscore=0 malwarescore=0 spamscore=0 priorityscore=1501 adultscore=0 phishscore=0 lowpriorityscore=0 clxscore=1015 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2408220000 definitions=main-2409230075 Content-Type: text/plain; charset="utf-8" Add support for the AD485X DAS familiy. Signed-off-by: Antoniu Miclaus --- drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad485x.c | 1061 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1074 insertions(+) create mode 100644 drivers/iio/adc/ad485x.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index f60fe85a30d5..83f55229d731 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -36,6 +36,18 @@ config AD4130 To compile this driver as a module, choose M here: the module will be called ad4130. =20 +config AD485X + tristate "Analog Device AD485x DAS Driver" + depends on SPI + select REGMAP_SPI + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD485x high speed + data acquisition system (DAS). + + To compile this driver as a module, choose M here: the module will be + called ad485x. + config AD7091R tristate =20 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d370e066544e..26c1670c8913 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_AB8500_GPADC) +=3D ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) +=3D ad_sigma_delta.o obj-$(CONFIG_AD4130) +=3D ad4130.o +obj-$(CONFIG_AD485X) +=3D ad485x.o obj-$(CONFIG_AD7091R) +=3D ad7091r-base.o obj-$(CONFIG_AD7091R5) +=3D ad7091r5.o obj-$(CONFIG_AD7091R8) +=3D ad7091r8.o diff --git a/drivers/iio/adc/ad485x.c b/drivers/iio/adc/ad485x.c new file mode 100644 index 000000000000..3333cad9ed8f --- /dev/null +++ b/drivers/iio/adc/ad485x.c @@ -0,0 +1,1061 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD485x DAS driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD485X_REG_INTERFACE_CONFIG_A 0x00 +#define AD485X_REG_INTERFACE_CONFIG_B 0x01 +#define AD485X_REG_PRODUCT_ID_L 0x04 +#define AD485X_REG_PRODUCT_ID_H 0x05 +#define AD485X_REG_DEVICE_CTRL 0x25 +#define AD485X_REG_PACKET 0x26 + +#define AD485X_REG_CH_CONFIG_BASE 0x2A +#define AD485X_REG_CHX_SOFTSPAN(ch) ((0x12 * (ch)) + AD485X_REG_CH_CONFIG_= BASE) +#define AD485X_REG_CHX_OFFSET(ch) (AD485X_REG_CHX_SOFTSPAN(ch) + 0x01) +#define AD485X_REG_CHX_OFFSET_LSB(ch) AD485X_REG_CHX_OFFSET(ch) +#define AD485X_REG_CHX_OFFSET_MID(ch) (AD485X_REG_CHX_OFFSET_LSB(ch) + 0x0= 1) +#define AD485X_REG_CHX_OFFSET_MSB(ch) (AD485X_REG_CHX_OFFSET_MID(ch) + 0x0= 1) +#define AD485X_REG_CHX_GAIN(ch) (AD485X_REG_CHX_OFFSET(ch) + 0x03) +#define AD485X_REG_CHX_GAIN_LSB(ch) AD485X_REG_CHX_GAIN(ch) +#define AD485X_REG_CHX_GAIN_MSB(ch) (AD485X_REG_CHX_GAIN(ch) + 0x01) +#define AD485X_REG_CHX_PHASE(ch) (AD485X_REG_CHX_GAIN(ch) + 0x02) +#define AD485X_REG_CHX_PHASE_LSB(ch) AD485X_REG_CHX_PHASE(ch) +#define AD485X_REG_CHX_PHASE_MSB(ch) (AD485X_REG_CHX_PHASE_LSB(ch) + 0x01) + +#define AD485X_REG_TESTPAT_0(c) (0x38 + (c) * 0x12) +#define AD485X_REG_TESTPAT_1(c) (0x39 + (c) * 0x12) +#define AD485X_REG_TESTPAT_2(c) (0x3A + (c) * 0x12) +#define AD485X_REG_TESTPAT_3(c) (0x3B + (c) * 0x12) + +#define AD485X_SW_RESET (BIT(7) | BIT(0)) +#define AD485X_SDO_ENABLE BIT(4) +#define AD485X_SINGLE_INSTRUCTION BIT(7) +#define AD485X_ECHO_CLOCK_MODE BIT(0) + +#define AD485X_PACKET_FORMAT_0 0 +#define AD485X_PACKET_FORMAT_1 1 +#define AD485X_PACKET_FORMAT_MASK GENMASK(1, 0) +#define AD485X_OS_EN BIT(7) + +#define AD485X_TEST_PAT BIT(2) + +#define AD4858_PACKET_SIZE_20 0 +#define AD4858_PACKET_SIZE_24 1 +#define AD4858_PACKET_SIZE_32 2 + +#define AD4857_PACKET_SIZE_16 0 +#define AD4857_PACKET_SIZE_24 1 + +#define AD485X_TESTPAT_0_DEFAULT 0x2A +#define AD485X_TESTPAT_1_DEFAULT 0x3C +#define AD485X_TESTPAT_2_DEFAULT 0xCE +#define AD485X_TESTPAT_3_DEFAULT(c) (0x0A + (0x10 * (c))) + +#define AD485X_SOFTSPAN_0V_2V5 0 +#define AD485X_SOFTSPAN_N2V5_2V5 1 +#define AD485X_SOFTSPAN_0V_5V 2 +#define AD485X_SOFTSPAN_N5V_5V 3 +#define AD485X_SOFTSPAN_0V_6V25 4 +#define AD485X_SOFTSPAN_N6V25_6V25 5 +#define AD485X_SOFTSPAN_0V_10V 6 +#define AD485X_SOFTSPAN_N10V_10V 7 +#define AD485X_SOFTSPAN_0V_12V5 8 +#define AD485X_SOFTSPAN_N12V5_12V5 9 +#define AD485X_SOFTSPAN_0V_20V 10 +#define AD485X_SOFTSPAN_N20V_20V 11 +#define AD485X_SOFTSPAN_0V_25V 12 +#define AD485X_SOFTSPAN_N25V_25V 13 +#define AD485X_SOFTSPAN_0V_40V 14 +#define AD485X_SOFTSPAN_N40V_40V 15 + +#define AD485X_MAX_LANES 8 +#define AD485X_MAX_IODELAY 32 + +#define AD485X_T_CNVH_NS 40 + +#define AD4858_PROD_ID 0x60 +#define AD4857_PROD_ID 0x61 +#define AD4856_PROD_ID 0x62 +#define AD4855_PROD_ID 0x63 +#define AD4854_PROD_ID 0x64 +#define AD4853_PROD_ID 0x65 +#define AD4852_PROD_ID 0x66 +#define AD4851_PROD_ID 0x67 +#define AD4858I_PROD_ID 0x6F + +struct ad485x_chip_info { + const char *name; + unsigned int product_id; + const unsigned int (*scale_table)[2]; + int num_scales; + const int *offset_table; + int num_offset; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned long throughput; + unsigned int resolution; +}; + +struct ad485x_state { + struct spi_device *spi; + struct pwm_device *cnv; + struct iio_backend *back; + /* + * Synchronize access to members the of driver state, and ensure + * atomicity of consecutive regmap operations. + */ + struct mutex lock; + struct regmap *regmap; + const struct ad485x_chip_info *info; + unsigned long sampling_freq; + unsigned int (*scales)[2]; + int *offsets; +}; + +static int ad485x_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad485x_state *st =3D iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad485x_set_sampling_freq(struct ad485x_state *st, unsigned int = freq) +{ + struct pwm_state cnv_state =3D { + .duty_cycle =3D AD485X_T_CNVH_NS, + .enabled =3D true, + }; + int ret; + + if (freq > st->info->throughput) + freq =3D st->info->throughput; + + cnv_state.period =3D DIV_ROUND_CLOSEST_ULL(1000000000, freq); + + ret =3D pwm_apply_might_sleep(st->cnv, &cnv_state); + if (ret) + return ret; + + st->sampling_freq =3D freq; + + return 0; +} + +static int ad485x_setup(struct ad485x_state *st) +{ + unsigned int product_id; + int ret; + + ret =3D ad485x_set_sampling_freq(st, 1000000); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_INTERFACE_CONFIG_A, + AD485X_SW_RESET); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_INTERFACE_CONFIG_B, + AD485X_SINGLE_INSTRUCTION); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_INTERFACE_CONFIG_A, + AD485X_SDO_ENABLE); + if (ret) + return ret; + + ret =3D regmap_read(st->regmap, AD485X_REG_PRODUCT_ID_L, &product_id); + if (ret) + return ret; + + if (product_id !=3D st->info->product_id) + dev_warn(&st->spi->dev, "Unknown product ID: 0x%02X\n", + product_id); + + ret =3D regmap_write(st->regmap, AD485X_REG_DEVICE_CTRL, + AD485X_ECHO_CLOCK_MODE); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_PACKET, 0); +} + +static int ad485x_find_opt(bool *field, u32 size, u32 *ret_start) +{ + int i, cnt =3D 0, max_cnt =3D 0, start, max_start =3D 0; + + for (i =3D 0, start =3D -1; i < size; i++) { + if (field[i] =3D=3D 0) { + if (start =3D=3D -1) + start =3D i; + cnt++; + } else { + if (cnt > max_cnt) { + max_cnt =3D cnt; + max_start =3D start; + } + start =3D -1; + cnt =3D 0; + } + } + + if (cnt > max_cnt) { + max_cnt =3D cnt; + max_start =3D start; + } + + if (!max_cnt) + return -EIO; + + *ret_start =3D max_start; + + return max_cnt; +} + +static int ad485x_calibrate(struct ad485x_state *st) +{ + int opt_delay, lane_num, delay, i, s, c; + enum iio_backend_interface_type interface_type; + bool pn_status[AD485X_MAX_LANES][AD485X_MAX_IODELAY]; + int ret; + + ret =3D iio_backend_interface_type_get(st->back, &interface_type); + if (ret) + return ret; + + if (interface_type =3D=3D IIO_BACKEND_INTERFACE_CMOS) + lane_num =3D st->info->num_channels; + else + lane_num =3D 1; + + if (st->info->resolution =3D=3D 16) { + ret =3D iio_backend_data_size_set(st->back, 24); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_PACKET, + AD485X_TEST_PAT | AD4857_PACKET_SIZE_24); + if (ret) + return ret; + } else { + ret =3D iio_backend_data_size_set(st->back, 32); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_PACKET, + AD485X_TEST_PAT | AD4858_PACKET_SIZE_32); + if (ret) + return ret; + } + + for (i =3D 0; i < st->info->num_channels; i++) { + ret =3D regmap_write(st->regmap, AD485X_REG_TESTPAT_0(i), + AD485X_TESTPAT_0_DEFAULT); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_TESTPAT_1(i), + AD485X_TESTPAT_1_DEFAULT); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_TESTPAT_2(i), + AD485X_TESTPAT_2_DEFAULT); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_TESTPAT_3(i), + AD485X_TESTPAT_3_DEFAULT(i)); + if (ret) + return ret; + + ret =3D iio_backend_chan_enable(st->back, i); + if (ret) + return ret; + } + + for (i =3D 0; i < lane_num; i++) { + for (delay =3D 0; delay < AD485X_MAX_IODELAY; delay++) { + ret =3D iio_backend_iodelay_set(st->back, i, delay); + if (ret) + return ret; + ret =3D iio_backend_chan_status(st->back, i, + &pn_status[i][delay]); + if (ret) + return ret; + } + } + + for (i =3D 0; i < lane_num; i++) { + c =3D ad485x_find_opt(&pn_status[i][0], AD485X_MAX_IODELAY, &s); + if (c < 0) + return c; + + opt_delay =3D s + c / 2; + ret =3D iio_backend_iodelay_set(st->back, i, opt_delay); + if (ret) + return ret; + } + + for (i =3D 0; i < st->info->num_channels; i++) { + ret =3D iio_backend_chan_disable(st->back, i); + if (ret) + return ret; + } + + ret =3D iio_backend_data_size_set(st->back, 20); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_PACKET, 0); +} + +static const char *const ad4858_packet_fmts[] =3D { + "20-bit", "24-bit", "32-bit" +}; + +static const char *const ad4857_packet_fmts[] =3D { + "16-bit", "24-bit" +}; + +static int ad485x_set_packet_format(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int format) +{ + struct ad485x_state *st =3D iio_priv(indio_dev); + unsigned int val; + int ret; + + if (chan->scan_type.realbits =3D=3D 20) + switch (format) { + case 0: + val =3D 20; + break; + case 1: + val =3D 24; + break; + case 2: + val =3D 32; + break; + default: + return -EINVAL; + } + else if (chan->scan_type.realbits =3D=3D 16) + switch (format) { + case 0: + val =3D 16; + break; + case 1: + val =3D 24; + break; + default: + return -EINVAL; + } + else + return -EINVAL; + + ret =3D iio_backend_data_size_set(st->back, val); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, AD485X_REG_PACKET, + AD485X_PACKET_FORMAT_MASK, format); +} + +static int ad485x_get_packet_format(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad485x_state *st =3D iio_priv(indio_dev); + unsigned int format; + int ret; + + ret =3D regmap_read(st->regmap, AD485X_REG_PACKET, &format); + if (ret) + return ret; + + format =3D FIELD_GET(AD485X_PACKET_FORMAT_MASK, format); + + return format; +} + +static const struct iio_enum ad4858_packet_fmt =3D { + .items =3D ad4858_packet_fmts, + .num_items =3D ARRAY_SIZE(ad4858_packet_fmts), + .set =3D ad485x_set_packet_format, + .get =3D ad485x_get_packet_format, +}; + +static const struct iio_enum ad4857_packet_fmt =3D { + .items =3D ad4857_packet_fmts, + .num_items =3D ARRAY_SIZE(ad4857_packet_fmts), + .set =3D ad485x_set_packet_format, + .get =3D ad485x_get_packet_format, +}; + +static int ad485x_get_calibscale(struct ad485x_state *st, int ch, int *val, + int *val2) +{ + unsigned int reg_val; + int gain; + int ret; + + guard(mutex)(&st->lock); + + ret =3D regmap_read(st->regmap, AD485X_REG_CHX_GAIN_MSB(ch), + ®_val); + if (ret) + return ret; + + gain =3D (reg_val & 0xFF) << 8; + + ret =3D regmap_read(st->regmap, AD485X_REG_CHX_GAIN_LSB(ch), + ®_val); + if (ret) + return ret; + + gain |=3D reg_val & 0xFF; + + *val =3D gain; + *val2 =3D 32768; + + return IIO_VAL_FRACTIONAL; +} + +static int ad485x_set_calibscale(struct ad485x_state *st, int ch, int val, + int val2) +{ + unsigned long long gain; + unsigned int reg_val; + int ret; + + gain =3D val * 1000000 + val2; + gain =3D gain * 32768; + gain =3D DIV_U64_ROUND_CLOSEST(gain, 1000000); + + reg_val =3D gain; + + guard(mutex)(&st->lock); + + ret =3D regmap_write(st->regmap, AD485X_REG_CHX_GAIN_MSB(ch), + reg_val >> 8); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_CHX_GAIN_LSB(ch), + reg_val & 0xFF); +} + +static int ad485x_get_calibbias(struct ad485x_state *st, int ch, int *val, + int *val2) +{ + unsigned int lsb, mid, msb; + int ret; + + guard(mutex)(&st->lock); + + ret =3D regmap_read(st->regmap, AD485X_REG_CHX_OFFSET_MSB(ch), + &msb); + if (ret) + return ret; + + ret =3D regmap_read(st->regmap, AD485X_REG_CHX_OFFSET_MID(ch), + &mid); + if (ret) + return ret; + + ret =3D regmap_read(st->regmap, AD485X_REG_CHX_OFFSET_LSB(ch), + &lsb); + if (ret) + return ret; + + if (st->info->resolution =3D=3D 16) { + *val =3D msb << 8; + *val |=3D mid; + *val =3D sign_extend32(*val, 15); + } else { + *val =3D msb << 12; + *val |=3D mid << 4; + *val |=3D lsb >> 4; + *val =3D sign_extend32(*val, 19); + } + + return IIO_VAL_INT; +} + +static int ad485x_set_calibbias(struct ad485x_state *st, int ch, int val, + int val2) +{ + unsigned int lsb, mid, msb; + int ret; + + if (st->info->resolution =3D=3D 16) { + lsb =3D 0; + mid =3D val & 0xFF; + msb =3D (val >> 8) & 0xFF; + } else { + lsb =3D (val << 4) & 0xFF; + mid =3D (val >> 4) & 0xFF; + msb =3D (val >> 12) & 0xFF; + } + + guard(mutex)(&st->lock); + + ret =3D regmap_write(st->regmap, AD485X_REG_CHX_OFFSET_LSB(ch), lsb); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD485X_REG_CHX_OFFSET_MID(ch), mid); + if (ret) + return ret; + + return regmap_write(st->regmap, AD485X_REG_CHX_OFFSET_MSB(ch), msb); +} + +static const unsigned int ad485x_scale_table[][2] =3D { + {2500, 0x0}, {5000, 0x1}, {5000, 0x2}, {10000, 0x3}, {6250, 0x04}, + {12500, 0x5}, {10000, 0x6}, {20000, 0x7}, {12500, 0x8}, {25000, 0x9}, + {20000, 0xA}, {40000, 0xB}, {25000, 0xC}, {50000, 0xD}, {40000, 0xE}, + {80000, 0xF} +}; + +static const int ad4857_offset_table[] =3D { + 0, -32768 +}; + +static const int ad4858_offset_table[] =3D { + 0, -524288 +}; + +static const unsigned int ad485x_scale_avail[] =3D { + 2500, 5000, 10000, 6250, 12500, 20000, 25000, 40000, 50000, 80000 +}; + +static void __ad485x_get_scale(struct ad485x_state *st, int scale_tbl, + unsigned int *val, unsigned int *val2) +{ + const struct ad485x_chip_info *info =3D st->info; + const struct iio_chan_spec *chan =3D &info->channels[0]; + unsigned int tmp; + + tmp =3D (scale_tbl * 1000000ULL) >> chan->scan_type.realbits; + *val =3D tmp / 1000000; + *val2 =3D tmp % 1000000; +} + +static int ad485x_set_scale(struct ad485x_state *st, + const struct iio_chan_spec *chan, int val, int val2) +{ + const struct ad485x_chip_info *info =3D st->info; + unsigned int scale_val[2]; + unsigned int i, j =3D 0; + + for (i =3D 0; i < info->num_scales; i++) { + __ad485x_get_scale(st, info->scale_table[i][0], + &scale_val[0], &scale_val[1]); + if (scale_val[0] !=3D val || scale_val[1] !=3D val2) + continue; + + /* + * Adjust the softspan value (differential or single ended) + * based on the scale value selected and current offset of + * the channel. + * + * If the offset is 0 then continue iterations until finding + * the next matching scale value which always corresponds to + * the single ended mode. + */ + if (!st->offsets[chan->channel] && !j) { + j++; + continue; + } + + return regmap_write(st->regmap, + AD485X_REG_CHX_SOFTSPAN(chan->channel), + info->scale_table[i][1]); + } + + return -EINVAL; +} + +static int ad485x_get_scale(struct ad485x_state *st, + const struct iio_chan_spec *chan, int *val, + int *val2) +{ + const struct ad485x_chip_info *info =3D st->info; + unsigned int i, softspan_val; + int ret; + + ret =3D regmap_read(st->regmap, AD485X_REG_CHX_SOFTSPAN(chan->channel), + &softspan_val); + if (ret) + return ret; + + for (i =3D 0; i < info->num_scales; i++) { + if (softspan_val =3D=3D info->scale_table[i][1]) + break; + } + + if (i =3D=3D info->num_scales) + return -EIO; + + __ad485x_get_scale(st, info->scale_table[i][0], val, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ad485x_set_offset(struct ad485x_state *st, + const struct iio_chan_spec *chan, int val) +{ + guard(mutex)(&st->lock); + + if (st->offsets[chan->channel] !=3D val) { + st->offsets[chan->channel] =3D val; + /* Restore to the default range if offset changes */ + if (st->offsets[chan->channel]) + return regmap_write(st->regmap, + AD485X_REG_CHX_SOFTSPAN(chan->channel), + AD485X_SOFTSPAN_N40V_40V); + return regmap_write(st->regmap, + AD485X_REG_CHX_SOFTSPAN(chan->channel), + AD485X_SOFTSPAN_0V_40V); + } + + return 0; +} + +static int ad485x_scale_offset_fill(struct ad485x_state *st) +{ + unsigned int i, val1, val2; + + st->offsets =3D devm_kcalloc(&st->spi->dev, st->info->num_channels, + sizeof(*st->offsets), GFP_KERNEL); + if (!st->offsets) + return -ENOMEM; + + st->scales =3D devm_kmalloc_array(&st->spi->dev, ARRAY_SIZE(ad485x_scale_= avail), + sizeof(*st->scales), GFP_KERNEL); + if (!st->scales) + return -ENOMEM; + + for (i =3D 0; i < ARRAY_SIZE(ad485x_scale_avail); i++) { + __ad485x_get_scale(st, ad485x_scale_avail[i], &val1, &val2); + st->scales[i][0] =3D val1; + st->scales[i][1] =3D val2; + } + + return 0; +} + +static int ad485x_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad485x_state *st =3D iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val =3D st->sampling_freq; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + return ad485x_get_calibscale(st, chan->channel, val, val2); + case IIO_CHAN_INFO_SCALE: + return ad485x_get_scale(st, chan, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad485x_get_calibbias(st, chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + scoped_guard(mutex, &st->lock) + * val =3D st->offsets[chan->channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad485x_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad485x_state *st =3D iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad485x_set_sampling_freq(st, val); + case IIO_CHAN_INFO_SCALE: + return ad485x_set_scale(st, chan, val, val2); + case IIO_CHAN_INFO_CALIBSCALE: + return ad485x_set_calibscale(st, chan->channel, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad485x_set_calibbias(st, chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + return ad485x_set_offset(st, chan, val); + default: + return -EINVAL; + } +} + +static int ad485x_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad485x_state *st =3D iio_priv(indio_dev); + unsigned int c; + int ret; + + for (c =3D 0; c < st->info->num_channels; c++) { + if (test_bit(c, scan_mask)) + ret =3D iio_backend_chan_enable(st->back, c); + else + ret =3D iio_backend_chan_disable(st->back, c); + if (ret) + return ret; + } + + return 0; +} + +static int ad485x_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad485x_state *st =3D iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals =3D (const int *)st->scales; + *type =3D IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length =3D ARRAY_SIZE(ad485x_scale_avail) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OFFSET: + *vals =3D st->info->offset_table; + *type =3D IIO_VAL_INT; + *length =3D st->info->num_offset; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +#define AD485X_IIO_CHANNEL(index, real, storage, info) \ +{ \ + .type =3D IIO_VOLTAGE, \ + .ext_info =3D info, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all =3D BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available =3D \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .indexed =3D 1, \ + .channel =3D index, \ + .scan_index =3D index, \ + .scan_type =3D { \ + .sign =3D 's', \ + .realbits =3D real, \ + .storagebits =3D storage, \ + }, \ +} + +static struct iio_chan_spec_ext_info ad4858_ext_info[] =3D { + IIO_ENUM("packet_format", IIO_SHARED_BY_ALL, &ad4858_packet_fmt), + IIO_ENUM_AVAILABLE("packet_format", + IIO_SHARED_BY_ALL, &ad4858_packet_fmt), + {}, +}; + +static struct iio_chan_spec_ext_info ad4857_ext_info[] =3D { + IIO_ENUM("packet_format", IIO_SHARED_BY_ALL, &ad4857_packet_fmt), + IIO_ENUM_AVAILABLE("packet_format", + IIO_SHARED_BY_ALL, &ad4857_packet_fmt), + {}, +}; + +static const struct iio_chan_spec ad4858_channels[] =3D { + AD485X_IIO_CHANNEL(0, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(1, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(2, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(3, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(4, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(5, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(6, 20, 32, ad4858_ext_info), + AD485X_IIO_CHANNEL(7, 20, 32, ad4858_ext_info), +}; + +static const struct iio_chan_spec ad4857_channels[] =3D { + AD485X_IIO_CHANNEL(0, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(1, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(2, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(3, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(4, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(5, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(6, 16, 16, ad4857_ext_info), + AD485X_IIO_CHANNEL(7, 16, 16, ad4857_ext_info), +}; + +static const struct ad485x_chip_info ad4858_info =3D { + .name =3D "ad4858", + .product_id =3D AD4858_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4858_offset_table, + .num_offset =3D ARRAY_SIZE(ad4858_offset_table), + .channels =3D ad4858_channels, + .num_channels =3D ARRAY_SIZE(ad4858_channels), + .throughput =3D 1 * MEGA, + .resolution =3D 20, +}; + +static const struct ad485x_chip_info ad4857_info =3D { + .name =3D "ad4857", + .product_id =3D AD4857_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4857_offset_table, + .num_offset =3D ARRAY_SIZE(ad4857_offset_table), + .channels =3D ad4857_channels, + .num_channels =3D ARRAY_SIZE(ad4857_channels), + .throughput =3D 1 * MEGA, + .resolution =3D 16, +}; + +static const struct ad485x_chip_info ad4856_info =3D { + .name =3D "ad4856", + .product_id =3D AD4856_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4858_offset_table, + .num_offset =3D ARRAY_SIZE(ad4858_offset_table), + .channels =3D ad4858_channels, + .num_channels =3D ARRAY_SIZE(ad4858_channels), + .throughput =3D 250 * KILO, + .resolution =3D 20, + .resolution =3D 16, +}; + +static const struct ad485x_chip_info ad4855_info =3D { + .name =3D "ad4855", + .product_id =3D AD4855_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4857_offset_table, + .num_offset =3D ARRAY_SIZE(ad4857_offset_table), + .channels =3D ad4857_channels, + .num_channels =3D ARRAY_SIZE(ad4857_channels), + .throughput =3D 250 * KILO, + .resolution =3D 16, +}; + +static const struct ad485x_chip_info ad4854_info =3D { + .name =3D "ad4854", + .product_id =3D AD4854_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4858_offset_table, + .num_offset =3D ARRAY_SIZE(ad4858_offset_table), + .channels =3D ad4858_channels, + .num_channels =3D ARRAY_SIZE(ad4858_channels), + .throughput =3D 1 * MEGA, + .resolution =3D 20, +}; + +static const struct ad485x_chip_info ad4853_info =3D { + .name =3D "ad4853", + .product_id =3D AD4853_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4857_offset_table, + .num_offset =3D ARRAY_SIZE(ad4857_offset_table), + .channels =3D ad4857_channels, + .num_channels =3D ARRAY_SIZE(ad4857_channels), + .throughput =3D 1 * MEGA, + .resolution =3D 16, +}; + +static const struct ad485x_chip_info ad4852_info =3D { + .name =3D "ad4852", + .product_id =3D AD4852_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4858_offset_table, + .num_offset =3D ARRAY_SIZE(ad4858_offset_table), + .channels =3D ad4858_channels, + .num_channels =3D ARRAY_SIZE(ad4858_channels), + .throughput =3D 250 * KILO, + .resolution =3D 20, +}; + +static const struct ad485x_chip_info ad4851_info =3D { + .name =3D "ad4851", + .product_id =3D AD4851_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4857_offset_table, + .num_offset =3D ARRAY_SIZE(ad4857_offset_table), + .channels =3D ad4857_channels, + .num_channels =3D ARRAY_SIZE(ad4857_channels), + .throughput =3D 250 * KILO, + .resolution =3D 16, +}; + +static const struct ad485x_chip_info ad4858i_info =3D { + .name =3D "ad4858i", + .product_id =3D AD4858I_PROD_ID, + .scale_table =3D ad485x_scale_table, + .num_scales =3D ARRAY_SIZE(ad485x_scale_table), + .offset_table =3D ad4858_offset_table, + .num_offset =3D ARRAY_SIZE(ad4858_offset_table), + .channels =3D ad4858_channels, + .num_channels =3D ARRAY_SIZE(ad4858_channels), + .throughput =3D 1000000, + .resolution =3D 20, +}; + +static const struct iio_info ad485x_info =3D { + .debugfs_reg_access =3D ad485x_reg_access, + .read_raw =3D ad485x_read_raw, + .write_raw =3D ad485x_write_raw, + .update_scan_mode =3D ad485x_update_scan_mode, + .read_avail =3D ad485x_read_avail, +}; + +static const struct regmap_config regmap_config =3D { + .reg_bits =3D 16, + .val_bits =3D 8, + .read_flag_mask =3D BIT(7), +}; + +static const char * const ad485x_power_supplies[] =3D { + "vcc", "vdd", "vddh", "vio" +}; + +static int ad485x_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad485x_state *st; + int ret; + + indio_dev =3D devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st =3D iio_priv(indio_dev); + st->spi =3D spi; + + mutex_init(&st->lock); + + ret =3D devm_regulator_bulk_get_enable(&spi->dev, + ARRAY_SIZE(ad485x_power_supplies), + ad485x_power_supplies); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to get and enable supplies\n"); + + st->cnv =3D devm_pwm_get(&spi->dev, NULL); + if (IS_ERR(st->cnv)) + return PTR_ERR(st->cnv); + + st->info =3D spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + st->regmap =3D devm_regmap_init_spi(spi, ®map_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret =3D ad485x_scale_offset_fill(st); + if (ret) + return ret; + + ret =3D ad485x_setup(st); + if (ret) + return ret; + + indio_dev->name =3D st->info->name; + indio_dev->channels =3D st->info->channels; + indio_dev->num_channels =3D st->info->num_channels; + indio_dev->info =3D &ad485x_info; + + st->back =3D devm_iio_backend_get(&spi->dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret =3D devm_iio_backend_request_buffer(&spi->dev, st->back, indio_dev); + if (ret) + return ret; + + ret =3D devm_iio_backend_enable(&spi->dev, st->back); + if (ret) + return ret; + + ret =3D ad485x_calibrate(st); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad485x_of_match[] =3D { + { .compatible =3D "adi,ad4858", .data =3D &ad4858_info, }, + { .compatible =3D "adi,ad4857", .data =3D &ad4857_info, }, + { .compatible =3D "adi,ad4856", .data =3D &ad4856_info, }, + { .compatible =3D "adi,ad4855", .data =3D &ad4855_info, }, + { .compatible =3D "adi,ad4854", .data =3D &ad4854_info, }, + { .compatible =3D "adi,ad4853", .data =3D &ad4853_info, }, + { .compatible =3D "adi,ad4852", .data =3D &ad4852_info, }, + { .compatible =3D "adi,ad4851", .data =3D &ad4851_info, }, + { .compatible =3D "adi,ad4858i", .data =3D &ad4858i_info, }, + {} +}; + +static const struct spi_device_id ad485x_spi_id[] =3D { + { "ad4858", (kernel_ulong_t)&ad4858_info }, + { "ad4857", (kernel_ulong_t)&ad4857_info }, + { "ad4856", (kernel_ulong_t)&ad4856_info }, + { "ad4855", (kernel_ulong_t)&ad4855_info }, + { "ad4854", (kernel_ulong_t)&ad4854_info }, + { "ad4853", (kernel_ulong_t)&ad4853_info }, + { "ad4852", (kernel_ulong_t)&ad4852_info }, + { "ad4851", (kernel_ulong_t)&ad4851_info }, + { "ad4858i", (kernel_ulong_t)&ad4858i_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad485x_spi_id); + +static struct spi_driver ad485x_driver =3D { + .probe =3D ad485x_probe, + .driver =3D { + .name =3D "ad485x", + .of_match_table =3D ad485x_of_match, + }, + .id_table =3D ad485x_spi_id, +}; +module_spi_driver(ad485x_driver); + +MODULE_AUTHOR("Sergiu Cuciurean "); +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices AD485x DAS driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_BACKEND); --=20 2.46.0